65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 或 VB.NET 控制的自动浇水系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (85投票s)

2007年10月14日

CPOL

14分钟阅读

viewsIcon

270799

downloadIcon

10704

使用 VB.NET 或 C# 和并行端口继电器控制器设置一个简单且廉价的可电脑控制的自动浇水系统

Screenshot - ezSprinkleScreen.png

要求

*注意,操作 ezSprinkle 软件界面的说明包含在本文顶部二进制文件和源代码下载中的 readme.txt 文件里。

要运行二进制文件,您必须 **安装 .NET Framework 2.0**。

另外请注意,此项目在上面的源代码下载中提供 VB.NET 和 C# 两个版本。

引言

这是一个有趣的 DIY 项目,您可以使用廉价的并行继电器控制器、螺丝刀、现成的电磁阀、阀门的电源、一些基本的布线和焊接(任何人都可以完成),当然还有一些巧妙的 VB.NET/C# 代码,来构建您自己的 PC 控制的自动浇水系统。

现在,如果在一句话中同时出现 PC、继电器、电磁阀、电源、布线、焊接、螺丝刀和 VB.NET/C# 这些词语,都不能让极客居家维修达人(无论男女)垂涎欲滴,那我就不知道还有什么能做到了!

我的院子现在就在用这个系统浇水,我对此非常满意。可以说,它与专用的浇水控制器一样可靠,而且方便得多。我不再打算重新启用我以前的专用浇水控制器了。

开箱即用的 ezSprinkle 软件项目提供了以下功能:

  • 自动定时器,支持每两周编程,用于设置交替周的程序(可选择日期)。
  • 为选定的日期提供 **四种独立的运行时间**。
  • 可以编程设置 **任意数量的“区域”**,尽管我的设备物理上最多支持八个区域。
  • 区域“智能复制” - 通过先定义第一个运行两周的区域,您可以将其“智能复制”到新区域,新区域将复制所有日期和时间,但会为每个定义的添加时长。效果是轻松配置的背靠背浇水程序。这将为许多人节省大量时间。
  • 时间重叠检测 - ezSprinkle 仍然允许您重叠区域时间,但会有一个指示器警告您重叠。由于水压限制和电磁阀电源的限制,重叠有时并不可行。软件本身并不限制这一点。
  • 手动定时器 - 非常适合测试您的接线是否正常工作!如果您觉得您的植物缺水,在调整定时之前,您也可以使用此功能给它们充分浇水。
  • 连续端口信号 - 我发现继电器有时会在我的程序未运行时神秘地开启。通过使用定时器持续与端口通信,这个问题得到了解决。任何未经授权的继电器开启都会立即被关闭。
  • 雨量传感器检测和日志记录 - 雨量传感器检测将自动暂停任何浇水,并在雨量传感器不再激活时恢复浇水。您可以随时查看日志,显示雨量传感器的激活和停用时间。如果您真的、真的不想使用雨量传感器,您可以简单地将引脚 13 和 25 短接,或者修改软件使其不查找“始终开启”作为输入。
  • “实时”界面 - 您可以随时更改配置的任何部分,控制器将实时做出反应。

背景

我已经发现了 kitsrus K74A 控制器,并找到了一家本地供应商 ozitronics。我甚至对 Frank 在 ozitronics 的服务感到惊喜。作为一名电子和继电器接线方面的新手,Frank 给了我关于如何连接所有设备的完整解释,然后给我发送了一份 PDF 文件(参见上面的链接)来说明细节。考虑到我是第一次、可能也是唯一的客户,这是非常棒的服务。

在随机搜索端口控制代码时,我偶然发现了 Levent Saltuklaroglu 关于控制 LED 的文章(链接已过期,抱歉)。

那篇出色的文章激发了我的一些想法,我意识到这可以成为我构建浇水系统控制器的基础。

在进一步开发过程中,我发现 LED 示例并没有告诉我关于读取雨量传感器输入的信息,所以很快我就找到了 phebejtsov 关于使用 inpout32 从并行端口读取数据的文章——这与 LED 示例中使用的 DLL 相同。那篇文章在这里:使用 Inpout32.dll 从并行端口读取

如果您查看这些文章,它们将为您提供关于读/写并行端口以及为什么需要外部 DLL 来从 .NET 控制并行端口的深刻见解。

这是一张雨量传感器连接到我的棚屋的照片,以说明我实现的一部分。

Screenshot - rainsensor.jpg

起初,这项任务似乎有些艰巨——我必须了解端口控制的原理,必须设计一个带有数据持久化的调度系统,需要仔细考虑用户如何最轻松地设置浇水程序,而且最重要的是,因为这段代码连接到现实世界,我必须仔细审查所有“万一”的情况,并通过大量的验证和在硬件本身的限制内进行测试,来避免任何潜在的灾难。

连接 K74

基本接线图,请 下载 K74-SIMP.zip,非常简单易懂。这是我的 K74 连接方式:

Screenshot - K74V2-Wired.jpg

我将接线穿过天花板,然后沿着墙壁向下延伸到一个墙壁插座,再向上连接到我的 K74。

这是我的两个电磁阀,随时准备工作。

Screenshot - solenoids.jpg

Using the Code

代码已完成,可以开始为您浇水了。随附了 Visual Basic .NET 2005 解决方案和 C# 解决方案(VS2005),包含了此项目的所有源代码。我在代码中添加了很多注释,因此应该很容易理解每个方法的作用。

端口号来自前面提到的端口控制文章,但如果您遇到困难,您可能需要更仔细地查看 phebejtsov 的文章 和 Levent Saltuklaroglu 的文章(链接已过期),它们将从基本角度更详细地解释如何从并行端口进行控制。

对于用户界面,因为我为不同的浇水程序使用了大量重复控件,所以我实现了 cucgachthe 的 FindControl 函数来为我处理繁重的工作。给定控件名称,FindControl 返回一个控件对象,然后可以对其进行操作。我的重复控件名称在命名时都是按顺序编号的,所以 FindControl 可以轻松地遍历所有控件以访问它们。

数据本身存储在一个数据集中,并在启动和退出时分别序列化进出 ezSprinkle。用户也可以按保存按钮,以防止在窗体未正常关闭时数据丢失。数据结构是平坦的,以便轻松扩展。该系统效率极低,但考虑到此设计中最多八个电磁阀的限制,效率不是问题。

我实现了一个主定时器结合主端口值来控制整个过程。主定时器会查看是否处于自动模式,如果是,它会调用代码来迭代所有区域的所有编程时间,以查看哪些应该运行。为了处理重叠时间,主时间值使用“或”而不是“+”进行组合,这样我们就可以轻松地将值不断添加到主时间中,而不会意外地迭代。

实际的方法是:

Private Sub CloseAutoRelays(ByVal auto_area As Integer)
    Dim x As Integer

    ' iterate the relay selections for the selected area

    For x = 1 To 8
        If CBool(bindingSource1.List(auto_area).Row("Relay" & x)) Then
            mastervalue = (mastervalue Or CShort(Math.Pow(2, x - 1)))
        End If
    Next x
End Sub
private void CloseAutoRelays(int auto_area)
{
    int x;
    // iterate the relay selections for the selected area

    for (x = 1; x <= 8; x++) {
        System.Data.DataRowView drv = 
           (System.Data.DataRowView)bindingSource1.List[auto_area];
        if ((bool)drv.Row["Relay" + x])
        {
            mastervalue = (short)(mastervalue | (short)Math.Pow(2, x - 1));
        }
    }
}

导航主要由一个 bindingsource 处理;所有 databound 控件都连接到 bindingsource,而不是 dataset。为了好玩,我在窗体顶部添加了一个 combobox,它也可以辅助导航。

combobox 的设置非常基础;它能用就行;)。

Sub CboAreaSelectedIndexChanged(ByVal sender As Object, _
    ByVal e As EventArgs) Handles cboArea.SelectedIndexChanged
    If Not navFlag Then
        navFlag = True
        bindingSource1.Position = bindingSource1.Find("AreaNum", _
        cboArea.SelectedValue)
    End If
    
    navFlag = False
End Sub
public void CboAreaSelectedIndexChanged(object sender, EventArgs e)
{
    if (!navFlag)
    {
        navFlag = true;
        bindingSource1.Position = 
          bindingSource1.Find("AreaNum", cboArea.SelectedValue);
    }
    navFlag = false;
}

关注点

关于 numeric up/down 控件,我发现一个令人困惑的、有据可查的 bug,它阻止了验证(以及与数据集的同步),因为它无法通过键入控件的文本部分来完成,而是必须使用上下箭头。我在各种派生和增强型控件中都发现了这个问题。解决控件问题的简单方法是处理 TextChanged 事件。听起来很简单,但处理 TextChanged 是关键。这个事件在属性框中不可用,智能感知中也不提供,但如果代码中包含它,就能正常工作。这样做有一个缺点是,自动验证和数据同步仅在您访问控件的值成员时发生,因此我的 CheckTimeClashUD 方法中的时间重叠检查实际上会触发验证。在您自己的项目中,您可以简单地将 numeric up/down 控件的值读取到一个虚拟变量中来启动验证。如果您将 up/down 控件的值设置为其文本,您还可以防止文本被删除或被非数字字符覆盖。这是我的简单验证:

Private Sub validatenudManual(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles nudManual.TextChanged
    nudManual.Text = nudManual.Value
End Sub
private void validatenudManual(System.Object sender, System.EventArgs e)
{
    nudManual.Text = nudManual.Value.ToString();
}

另一个值得注意的点是我的故障安全机制,旨在降低粘滞继电器导致过度浇水的可能性。在此设计中,继电器 8 连接到电磁阀电源输出的公共线。基本上,除非继电器 8 同时激活,否则任何继电器都无法激活电磁阀。因此,如果连接到电磁阀的继电器粘住,继电器 8 将断开对其的供电。反之,如果继电器 8 粘住,受影响的继电器将切断自己的电源。不太可能同时出现继电器 8 和另一个继电器开始粘滞的情况。您需要更换任何粘滞的继电器,以免稍后出现继电器 8 和电磁阀继电器同时粘滞的情况。这会减少一条输出线,但值得损失。

对于时间重叠检测,我发现这是一个棘手的问题,不像最初看起来那么明显。如果您发现需要测试两个时间集是否重叠,那么以下方法将非常有帮助:

Private Function TimesOverlap(ByVal dt1a As Date, ByVal dt2a As Date, _
        ByVal dt1b As Date, ByVal dt2b As Date) As Boolean
    ' take a minute off end dates so as not to confuse the issue -
    ' technically the end time and start time of any back-to-back
    ' programs will fall within the same second

    dt2a = DateAdd(DateInterval.Minute, -1, dt2a)
    dt2b = DateAdd(DateInterval.Minute, -1, dt2b)
    If (dt1a <= dt1b And dt2a >= dt1b) Or (dt1a >= dt1b _
        And dt1a <= dt2b) Then
        Return True
    End If
    Return False
End Function
private bool TimesOverlap(System.DateTime dt1a, System.DateTime dt2a, 
        System.DateTime dt1b, System.DateTime dt2b)
{
    // take a minute off end dates so as not to confuse the issue -
    // technically the end time and start time of any back-to-back
    // programs will fall within the same second

    dt2a = DateAndTime.DateAdd(DateInterval.Minute, -1, dt2a);
    dt2b = DateAndTime.DateAdd(DateInterval.Minute, -1, dt2b);
    if ((dt1a <= dt1b && dt2a >= dt1b) || (dt1a >= dt1b && dt1a <= dt2b))
    {
        return true;
    }
    return false;
}

该方法接收 timeset1 开始时间、timeset1 结束时间、timeset2 开始时间、timeset2 结束时间作为输入值。如果存在重叠,它将返回 true

在 ezSprinkle 中,与另一个时间重叠的时间将显示警告,指示它与哪个时间重叠,如下面的屏幕截图所示:

Screenshot - timesingleoverlap.png

如果您的时间与多个其他时间重叠,将在警告后显示一个加号,如下所示:

Screenshot - timemultioverlap.png

当您更改时间使其不重叠时,警告指示符将被删除。在多重重叠的情况下,必须移除所有重叠项才能移除警告指示符。当您只剩最后一个重叠时,加号将自动被移除。

还有一个 bug(或设计不当的行为)是在查看数据绑定值时处理复选框的更改。当我实现了 CheckedChanged 时,我发现数据源仍然查看旧值。我尝试了各种方法,包括实现 EndEdit,但这导致复选框控件行为异常。

最终,我发现了 Jerry Nixon 的博客,他在其中描述了类似的经历。通过使用 CheckStateChanged 而不是 CheckedChanged,数据绑定在我开始查看行值之前就已经发生,而且由于不需要强制 EndEdit 等操作,控件的行为就很正常。

结论

K74 是此项目的正确选择。尽管后来我发现 K108 在很多方面可能更好,但它更贵,而且对于 *此项目* 而言,并没有带来任何显著的优势来证明成本差异是合理的。K108 更适合您的 PC 将控制的不仅仅是浇水系统(或更复杂的浇水系统实现)的项目——它适合更详细的输入监控,并且还消除了 Windows XP 的一个恼人怪癖(见下一段),即 XP 在启动时会激活继电器。我将在开始我的自动车库门项目时使用 K108 :D。

当您启动(假设您使用的是 Windows XP)时,XP 会向所有 K74 引脚发送信号,导致所有继电器闭合——并保持闭合。我的解决方法是让 ezSprinkle 在启动时加载,这样主继电器值会将所有继电器设置为预期值(自动浇水或所有继电器都打开)。要实现这一点,您需要安装并运行 TweakUI 并配置自动登录——通过设置密码,您的 PC 将绕过启动屏幕并正常登录。这在域环境中可能不起作用,但在家庭系统中没问题。

这样一来,如果您的 PC 在您不在家时自行重启,您就不会回家发现所有继电器都卡在闭合状态并且您的浇水系统一直在运行。相反,由于 ezSprinkle 会以您离开时的状态启动,您也不会从假期回家后发现您的植物干枯(前提是您的 PC 没有崩溃)。我的 PC 的 BIOS 有一个在断电后自动启动 PC 的选项,所以您应该研究一下是否可以将您的 PC 设置成这样。这将防止在停电后恢复供电后系统保持关闭状态。否则,我认为 UPS 系统可以配置为在停电时启动您的 PC;UPS 电池耗尽,然后电源恢复。

我旧的控制器有一个功能是我的设置没有的,那就是检测不到电磁阀负载时会生成错误。这本身并不是一个大问题,而且无论如何都需要观察来确定故障(即,线路中断导致某个喷水区域未开启)。

为了达到近乎偏执的设置,您会在电磁阀之后安装一个流量计,以便能够检测预期的水流。如果流量计没有反馈,您将切断阀门的电源。这将防止在程序运行时,阀门处或周围的管道破裂导致水到处喷射。此外,还可以实现湿度传感器,仅在干燥时浇水。显然,除非您实现额外的流量传感器,否则您无法检测到电磁阀之后的管道破裂,这需要更多的设置。

顺便说一句,我有一个阀门在系统未开启的情况下破裂了。故障是由于压力始终作用在出口连接处。幸运的是,那是在周日,我正好在家,能够迅速关闭水源。这里的教训是,最好使用高质量的聚乙烯接头,或者使用黄铜接头——我个人将开始升级到黄铜。故障发生在接头处。

Screenshot - failure.jpg

在考虑软件设计时,我曾考虑从本地天气 datasource 获取信息以提供实时浇水数据等,但认为天气数据可能对气象站所在地准确,但可能无法反映家中的情况。例如,城镇的一半在下雨,而另一半却不下雨。

我考虑的另一个因素是季节变化。我认为这里的季节变化不会持续数周。尽管植物生长可能会变化,但干燥时间保持不变,并在季节中的某个随机点发生变化。因此,我让 ezSprinkle 手动更改以适应任何变化的浇水需求。对于那些了解植物在一年中不同时间浇水需求的人来说,序列化的 XML 可以轻松地保存为不同的季节,并通过将其复制到名为 ezSprinkle.xml 的实时数据中来使相应的文件生效。

最后,这就是我们最终想要看到的,我们的 PC 为我们浇灌院子:

Screenshot - popups.jpg

:)

历史

ezSprinkle 的当前版本适用于一组八个继电器。我希望更新这个小项目,切换到串行控制器及其 RF 附加组件,以消除将布线束连接回 PC 的需要。

  • 2007 年 10 月 27 日: 添加了解决方案的 C# 转换版本
  • 2007 年 11 月 10 日: 修复了若干问题
    • NumericUpDown 手动程序持续时间控件添加了 TextChanged 处理程序,以确保其始终得到验证——之前手动更改持续时间不会启用/禁用手动启动按钮。
    • 添加了代码以更全面地检查重叠时间。以前,重叠不考虑日期选择,所以在设置交替日期的相同浇水时间时,重叠警告会错误地显示。
    • 为日期选择添加了 CheckStateChanged 处理程序,以便它们也能触发重叠检测。
  • 2007 年 11 月 10 日: 编辑 #2,修复了用于查找重叠的二进制比较变量中的一个 bug。变量在循环中未重置。

参考文献

© . All rights reserved.