使用 Python 构建家庭自动化和安全系统
在本文中,我描述了使用现成的产品、Python 和 Linux 创建家庭自动化系统的过程。我首先描述了构成系统硬件部分的串行 I/O 套件、车道警报器、水浸报警器、烟雾探测器和摄像头。接下来,我简要描述了 Python 程序使用的命令行程序和软件包。最后,我简要概述了 Python 程序的主要部分,这些部分将所有硬件和软件包结合在一起。
这个系统的想法诞生于我的邻居的一些派对客人将车停在我的车道上几分钟,同时他们试图弄清楚他们是否到了正确的房子。我措手不及,因为我没有立即注意到有人开了进来。我非常清楚,他们可能在没有引起注意的情况下进入我的住所或商店。我决定自己创建一个系统,因为我知道一个商业安全系统,可以监控车道交通并捕获来自安装在我房产周围各个摄像头的图像,这将是昂贵的。
因为我在工作中用 Python 编写了几个程序,所以我亲身体验了 Python 提供的快速开发。它使我能够专注于手头的问题,而不是复杂的语言语法和语义。我建议任何处理基于文本的数据的程序都使用 Python,因为它对于初学者程序员来说足够容易,但功能强大且足够灵活,可以处理更大的任务。
Python 家庭自动化系统以 Quality Kits 的隔离串行 I/O 套件(图 1)为中心,该套件可以组装好,额外收费 10 美元。该套件包含四个输入和八个继电器。输入检测来自 6-24 伏范围内的电源的直流电压。继电器可用于打开或关闭电源的电压,因此它们可用于控制多个 12 伏灯泡或任何其他需要继电器指定电压范围内的直流电压的小工具的电源。串行 I/O 套件使用简单的读取和写入命令到串行端口连接,用于设置继电器和监控输入。输入和继电器电路是隔离的,这意味着这些电路与计算机的串行连接之间没有直接连接。如果串行 I/O 盒出现问题,这可以防止损坏计算机。图 2 显示了如何将 Mier 车道警报器和警报器连接到串行 I/O 盒输入。
系统的第二个主要部分是 Mier 车道警报器(图 3)。该系统可靠,并且在检测到活动时还提供可调节定时的 24 伏输出。如果您决定使用 Mier 车道警报器,请将串行 I/O 输入之一连接到 Mier 车道警报器控制器上标记为 Neg 和 NO 的端子。否则,任何在检测到活动时提供 6-24 伏电压输出的车道警报系统都应该可以工作。如果您不确定您选择的单元是否提供这种类型的输出,您可能需要联系该公司。
Mier 控制器盒检测非常小的电压变化,这些变化是由传感器探头由于金属物体经过传感器探头时地球磁场的变化而产生的。这就是为什么传感器探头不能直接连接到串行 I/O 套件的输入端,因为探头产生的电压远小于输入的最小电压检测值 5 伏。
还有其他车道警报系统可供选择,它们使用光束或压力激活开关和橡胶软管。这种类型曾经在汽车餐厅和全方位服务加油站非常常见。我没有选择光束类型,因为它会检测到任何移动的物体,而且我怀疑橡胶软管在我的砾石车道上是否能很好地保持住。Mier 装置偶尔会因恶劣天气和雷击而产生误报,但这些误报很少,如果调整控制器灵敏度,可以减少这些误报。
众所周知,水和房屋内部不宜混合,这就是为什么我将这个廉价而简单的水检测电路添加到组合中的原因。该电路在装有水的锅的测试环境中表现良好,但缺乏任何实际生活测试。图 4 所示的电路是从在线资源中列出的原理图创建的。我移除了定时器、蜂鸣器和其他不必要的组件,留下了一个单开关晶体管和一个电阻器。该电路可以由 6-9 伏范围内的直流适配器供电。我的系统中的探头只是一小段废铜管,它们只是相隔几英寸地放在我的商店浴室的地板上。我已经将多个探头连接到同一个水浸报警电路,这为每个电路提供了多个监控点,但代价是无法确定泄漏的确切位置。我将由您来确定您的应用的正确探头间距,因为我没有任何确切的距离。我创建的水浸报警器(图 5)包含两个晶体管,因此在同一块板上有两个报警电路。
烟雾报警器从未在真正的紧急情况下进行过测试,并且是事后添加的。不要用这个修改后的版本替换任何现有的烟雾报警器,因为它必须被篡改。以下原因是非常好的理由,不要在发生火灾时依赖它,因为如果你的计算机、串行 I/O 套件连接和/或你的互联网连接是第一个出现故障的,那么报警器就毫无用处。如果您决定复制此项目的一部分,请自行承担风险。本项目中使用的烟雾探测器只是一个带有出口指示灯的型号,应该可以从您当地的家居五金中心购买到。它使用两节 9 伏电池;一节为报警器供电,另一节为指示灯供电。现在,您需要通过以下方式修改探测器来使保修失效。首先取下盖子并找到出口指示灯。使用电压表确定指示灯的正负极连接。然后只需将几根电线焊接到正确的连接点,并将它们连接到串行 I/O 套件的输入之一。
该系统使用几个相对便宜的罗技网络摄像头以及几个网络摄像头。任何 Linux 视频支持的摄像头以及大多数网络摄像头型号都应该可以工作。我能提供的最好的建议是验证摄像头是否受 Linux 支持以及是否有驱动程序可用。罗技 Quickcam Pro 型号性能相当好,但并非所有罗技型号都受同一驱动程序支持。Quickcam Pro 型号使用 saillard.org 提供的 pwc 模块。
该系统使用几个可用于 Linux 的命令行程序,这些程序处理 WAV 文件播放、zip 文件创建和图像文件捕获。SOX 软件包是 Linux 的声音转换和实用程序软件包。它提供了 play 命令,用于播放各种报警警告声音。有几个声音包装模块可用于 Python,但我发现对 play 命令进行系统调用相当容易。zip 命令用于从摄像头图像捕获创建 zip 文件。Python 包括一个 zipfile 模块,但使用命令行版本更容易,命令行版本可以很容易地替换为另一个命令,例如 tar。
图像文件捕获使用 Motion 和 Curl 处理。后者是一个相当强大的程序,可以从服务器传输数据。根据手册页,它处理 http、https、ftp、telnet 和其他一些格式。我对其进行系统调用,以使用类似于以下命令的命令从 D-Link 网络摄像头的 HTTP 服务器检索图像,curl http://192.168.0.98/IMAGE.JPG -uusername:password -m2 >outputfile.jpg,其中 -m2 告诉它在两秒后停止尝试。Motion 创建 MPEG 运动捕获和延时电影文件以及预设速率的单个 JPEG 图像文件。Motion 中内置的简单 HTTP 服务器提供图像流,但不允许使用 Curl 进行单个图像检索。有关 Motion 的更多信息,请参阅 2005 年 3 月的Linux Journal文章“GNU Motion:您在计算机房监控中的眼睛”,作者是 Phil Hollenback,因为它很好地解释了使其启动和运行所需的详细信息。
一旦您拥有一个工作的 Motion 设置,您应该将 Motion 配置文件中的 snapshot_interval 更改为 1 或您的应用程序可接受的值。较低的值更好,因为它们可以防止重复的图像捕获。现在将 snapshot_filename 值设置为临时文件名,例如 tempmotionimagefile,以便 Motion 仅创建一个捕获文件。保留默认的 motion.conf snapshot_filename 将起作用,但 Motion 将使用快照间隔值创建一个新文件,这可能会导致存储大量图像文件。这些设置使 Motion 创建一个名为 lastsnap.jpg 的符号链接,该链接始终指向最新的快照文件,在上述设置的情况下,该文件始终被最新图像覆盖。
现在,上述所有硬件和必需程序都已配置、安装并自行运行,是时候讨论将所有这些组合在一起的 Python 程序了。我简要介绍了程序中最重要的部分,以及任何需要的第三方模块。pyserial 模块包含 Linux 的 posix 串行 I/O 实现,用于处理与串行 I/O 套件的读取和写入。程序需要线程启用的 Python 安装,因为它必须执行几个同步操作才能监控输入活动等。
MonitorInputs 类是最重要的类之一,因为它通过使用 GetInputStatus 方法(清单 1)来处理输入监控。此方法通过将输入名称(I1 到 I4)写入串行端口来检查输入活动。写入方法由 pyserial 模块中的 Serial 类提供。请注意,示例代码中定义了一个 Python 列表,名为 Expect。这是来自串行类读取命令执行的预期输出列表,这些执行是清除和验证从写入请求返回的信息所必需的。如果获得意外的读取结果,则重置串行端口连接。这允许程序从串行通信故障和串行 I/O 套件的短暂断电中恢复。
清单 1. 使用 GetInputStatus 方法
def GetInputStatus(self, Input = None): self.ser.write(Input + '\r\n') Expect = [Input[0], Input[1], '\r', '\n'] cnt = 0 while cnt <= 3: a = self.ser.read() if a == '' or a != Expect[cnt]: return -1 cnt += 1 val= self.ser.read() Expect = ['\r', '\n', '#'] cnt = 0 while cnt <= 2: a = self.ser.read() if a == '' or a != Expect[cnt]: return -1 cnt += 1 if val == '1': return 1 else: return 0
当检测到活动时,MonitorInputs 会检查以确保输入活动未在输入活动超时时间内发生。超时用于防止警报线程(仅发送纯文本电子邮件)在单个输入活动电压条件期间执行太多次。超时不是最佳解决方案,因为烟雾和/或水浸报警器仍然每 60 秒发送一封新电子邮件。这对我来说是可以接受的,因为如果我在工作时收到水浸报警电子邮件,我会赶回家。未使用的串行 I/O 套件继电器可用于纠正此缺点,因为每个输入正极连接都可以通过继电器路由,继电器可以关闭以禁用报警电压。
另一种解决方案是向 GetInputStatus 方法发出信号,以忽略特定输入上的输入活动。这两种方法都有效,但在任何一种情况下都需要远程触发机制,因为串行端口连接由家庭自动化程序维护。一个可能的解决方案是在家庭自动化程序中添加一个服务器线程,该线程将接受来自客户端连接的简单字符串命令。这将允许一个简单的 Python CGI 脚本发送可以控制输入监控和/或继电器状态的命令。Pyro 是一个 Python 分布式对象系统,它使用事件服务器提供了另一个更复杂的解决方案。这与客户端/服务器方法非常相似,但 Pyro 更健壮,并提供了超出本文范围的机会。这些解决方案之一可能会在未来对家庭自动化程序的升级中找到其位置。
现在程序正在监控输入活动,它需要生成通知,例如检测到活动时的警告声音或电子邮件。烟雾和水浸报警活动由通用线程 Alarm 类处理,而车道警报活动则单独处理。Alarm 类使用 PlayWav 类播放 WAV 文件,并且它还使用 MailAttachment 类发送通知电子邮件。PlayWav 类使用 popen 调用在配置文件中设置的 wavcmd 值(sox play 命令)。PlayWav 类是线程化的,以防止繁忙的声音设备延迟电子邮件通知。所有线程类的最终结果是,输入活动几乎连续监控,只有轻微的延迟。
DriveAlert 类处理检测到的车道警报信号的输入活动。此类使用 GetImage(清单 2)、PlayWav 和 SSHRemote 线程类。为配置文件中设置的每个摄像头命令 (camcmd) 创建一个新的 GetImage 实例,以便可以几乎同时从每个摄像头收集图像。GetImage 类对摄像头命令进行 popen 调用,并等待其完成。重复此操作,直到已收集配置文件中设置的图像数量并将其保存在配置文件的 camdir 部分定义的目录中。收集完所有图像后,GetImage 类使用 ZipIt 类通过对 zip 命令的 popen 调用来创建 zip 文件。当所有图像文件都被压缩后,MailAttachment 例程用于通过电子邮件发送 zip 文件。如果您想错开从摄像头收集的图像,您可以在配置文件中添加摄像头图像延迟部分,并通过添加对 sleep 函数的调用来修改 GetImage 类,使用预设的摄像头延迟作为输入。
清单 2. DriveAlert 类
class GetImage(threading.Thread): def __init__(self, cam = None, numImages = 1): self.cam = cam self.JobBegin = -1 self.camCmd = CamCOMMANDS[cam] self.numImages = NumCamImages[cam] self.Zip = None threading.Thread.__init__(self) def run(self): for i in range(self.numImages): self.JobBegin = int(time.strftime("%H%M%S",time.localtime(time.time()))) if QUIET == 0: print 'Getting %s image' %self.cam filename = time.strftime("%H%M%S", time.localtime(time.time())) + '.jpg' execcmd = self.camCmd %filename self.p = popen2.Popen3("exec " + execcmd, 1024) self.errReader = PipeReader(self.p.childerr); self.errReader.start() self.outReader = PipeReader(self.p.fromchild); self.outReader.start() try: self.p.wait() except OSError, (errno, errnostr): if QUIET == 0: print 'ERROR: GetImage self.p.wait Errno %s: %s' %(`errno`, `errnostr`) except: if QUIET == 0: print 'ERROR: self.p.wait Unknown error' time.sleep(IMAGE_DELAY) #Popen complete - create zipfiles self.Zip = ZipIt(self.cam) self.Zip.start() self.Zip.join() #Wait on zip file creation
我简要提一下 SSHRemote 类,因为名称不明确。此类可用于执行任何命令,方法是将配置文件中的 ssh 远程命令替换为另一个命令。我目前使用它在我的商店机器上播放一些音乐,使其看起来有人在家。ssh 调用在远程机器上执行另一个简单的 Python 脚本,该脚本使用 play 命令播放指定目录中的所有 WAV 文件。
本文展示了如何使用 Linux、Python 和一些廉价的现成硬件在合理的时间内创建一个家庭自动化系统。本文重点介绍了系统的主要部分,不可能详细描述所有必需组件的设置。我还必须强调,该系统尚未在生产环境中进行测试,因此对其适用于上述任何目的不作任何明示或暗示的保证,因此请您自担风险使用。我期待着进行未来的增强,例如语音调制解调器,它可以拨打预设号码并播放消息。这将补充经常延迟的不可靠的电子邮件通知。我希望本文能激发您对简单监控系统和本项目中使用的串行 I/O 套件的灵活性的兴趣。
本文资源: /article/8696。
Fred Stelter 拥有韦科贝勒大学计算机科学学士学位。当他不为当地一家公司编写代码时,他喜欢在当地的山地自行车道上玩轮胎,修理他的老爷车,或者偶尔下水玩滑水板。