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

一个完整的防盗自制监控系统

starIconstarIconstarIconstarIconstarIcon

5.00/5 (38投票s)

2017年3月10日

CPOL

14分钟阅读

viewsIcon

49531

downloadIcon

2581

在您的手机上接收闯入者的通知,并拍摄他们的照片

引言

在本文中,我将介绍一些DIY技巧来构建一个监控系统,该系统可以检测家中的闯入者,拍摄他们的照片,并在您的手机上向您发出通知,必要时您可以致电警方并提供照片,以便尽快识别窃贼并增加追回被盗物品的机会。

当然,除了此软件,您还需要提供一些硬件,但我使用相对便宜的材料在我家中构建了这个系统,除了摄像头,它们是安装中最昂贵的部分。但是,您可以对摄像头做很多事情,所以这可能是一项好而有趣的投资。

基本上,这是系统的架构图,包含所有参与的元素

ThiefWatchewr schema

尽管在架构图中我表示了一些具体的子系统,但实际上我设计解决方案的方式是,所有这些元素都可以独立开发,通过实现一个通用接口并使用依赖注入将它们链接到应用程序。不同的子系统或协议如下:

  • 摄像头协议:定义与视频摄像头的通信
  • 存储协议:定义文件传输、图像和控制命令/响应
  • 触发协议:启动监控系统
  • 警报协议:远程向用户传达事件

该解决方案使用 Visual Studio 2015 和 .NET Framework 4.5 版本实现。

您可以在 这里 找到本文的较长版本,以及 西班牙语版本。由于本网站的文件大小限制为 10MB,我不得不删除源代码中的许多文件,包括所有 NuGet 包、obj 目录以及所有二进制文件。尽管您可以从 Visual Studio 恢复包,但您可能无法重新编译代码。在这种情况下,您可以从我网站上的先前链接下载项目的完整文件集。

硬件

让我们回顾一下我在家中构建系统所使用的硬件。由于应用程序在许多方面都是可扩展的,您可以选择自己的不同硬件来安装它。

首先是摄像头。我有两个 IP 摄像头,每个摄像头使用不同的协议。较便宜的是一个 Conceptronic Wi-Fi 摄像头,价格约 50 欧元,使用 `NetWave` CGI 协议。另一个是专业级的,性能很高,但价格也非常高。它是一个 Axis 摄像头,使用 `VAPIX` CGI 协议。

IP cameras

为了拨打手机,我购买了一个简单的 USB AT 调制解调器,价格约 17 欧元。

AT Modem

作为触发器,我使用了 **Arduino** 板(约 20 欧元)、一个存在传感器开关(约 10 欧元)和一个继电器。由于存在传感器开关使用 220V 电压,直接连接到 Arduino 板是一个糟糕的主意。因此,我将传感器连接到 12V 电源,并将电源连接到继电器,继电器充当另一个开关,在 5V Arduino 电源和一个输入引脚之间闭合电路。这完全隔离了 Arduino 板(以及计算机)与 220V 市电。

Presence detector switch

Arduino Mega Board

您可以轻松构建一个类似的继电器电路。只需将 12V 电源连接到继电器线圈,放置一个从地线到 12V 线的二极管,然后在 Arduino 端,使用一个输入引脚 (PI) 作为触发引脚,一个输出引脚 (PO) 在电路打开时强制输入引脚为 0V,以及 5V 电源信号来激活输入引脚。

Relay circuit

这是 Arduino 代码,我使用了引脚 28 作为输入,24 作为输出,因为在 Arduino Mega 板上它们靠近 5V 引脚,但您当然可以使用任何您想要的引脚。

int pin1 = 28;
int pin0 = 24;
void setup() {
// Initialize pins
    pinMode(pin0, OUTPUT); 
    digitalWrite(pin0, LOW);
    pinMode(pin1, INPUT);
    digitalWrite(pin1, LOW);
    Serial.begin(9600);
}
void loop() {
    int val = digitalRead(pin1);
    if (val == HIGH) {
        Serial.write(1);
    }
    delay(1000);
}

最后,虽然这不算真正意义上的硬件,但我将提一下我使用的存储协议。我选择了 `Dropbox`,因为它是在云端上传照片最简单且成本最低的方式,我还可以使用这个媒体通过 **JSON** 格式的文本文件与移动客户端通信。

控制中心

在 `ThiefWatcher` 项目中,实现了中央控制应用程序。它是一个桌面 MDI Windows 应用程序,基本上有两种不同的窗口类型。其中之一是控制面板,您可以在其中设置除摄像头协议之外的所有协议。

ThiefWatcher Control Panel

顶部面板是用于 **触发协议** 的。在这里,您可以选择要使用的协议,提供一个包含相应设置的连接字符串(这些设置可能因协议而异),一个系统必须开始监控模式的开始日期/时间(如果您不提供,系统将立即开始),一个停止监控的结束日期/时间,并且您可以配置在检测到闯入者时拍摄的照片数量以及照片之间的秒数(整数)。

在此面板下方是用于 **通知(警报)协议** 的。在选择协议的下拉列表右侧,有一个 `测试` 按钮,允许您在无需模拟的情况下测试此协议。您还必须提供一个包含参数设置的连接字符串,以及一个可选消息(如果协议允许数据传输)。

底部面板是用于 **存储协议** 的。您有一个连接字符串来设置参数(如果有),以及一个用于存储数据的容器名称,它可以是本地文件夹、FTP 文件夹、Azure blob 容器名称等。

命令按钮从左到右分别是:**启动模拟**,它会启动或停止系统,就好像检测到闯入者一样,因此您可以测试摄像头和存储协议,以及与客户端的通信。在此模式下,不考虑开始和结束日期。接下来是 `启动` 按钮,它会启动或停止真正的监控模式。摄像头窗口中不会显示图像(假设没有人存在)。最后,`保存` 按钮将更改写入配置文件。

在代码使用部分,我将注释我实现的所有协议的连接字符串参数。

关于摄像头协议,每个摄像头的配置在摄像头窗口中进行,您可以使用 **文件 / 新建摄像头…** 菜单选项显示该窗口。首先,您必须为要添加的摄像头选择正确的摄像头协议,然后,您必须提供连接数据、摄像头 URL、用户名和密码。然后,您会看到一个类似这样的窗口。

Camera window

工具栏中从左到右的第一个按钮是更改访问设置,第二个按钮显示摄像头设置对话框,该对话框在相应协议中实现。然后,有一个按钮用于启动和停止摄像头,这样您就可以在配置摄像头时观看图像。摄像头 ID 必须是唯一的并且是强制性的,因为您将使用此 ID 从客户端选择摄像头。最后两个按钮是将摄像头保存到配置文件或将其删除。

所有这些设置都存储在应用程序的 `App.config` 文件中。连接字符串在 `connectionStrings` 部分,其他协议设置在 `appSettings` 部分。还有两个自定义部分用于存储协议列表以及不同的摄像头及其设置。

`cameraSection` 如下所示:

<camerasSection>
    <cameras>
        <cameraData id="CAMNW"
            protocolName="NetWave IP camera"
            connectionStringName="CAMNW" />
        <cameraData id="VAPIX"
            protocolName="VAPIX IP Camera"
            connectionStringName="VAPIX" />
    </cameras>
</camerasSection>

每个摄像头都是一个 `cameraData` 元素,具有 `id` 属性、一个 `protocolName` 属性(包含相应协议的名称)和一个 `connectionStringName` 属性(用于连接数据:`url`、`userName` 和 `password`),这些数据存储在 `connectionStrings` 部分的一个连接字符串中。

还有一个 `protocolsSection`,其中包含已安装协议的列表:

<protocolsSection>
    <protocols>
        <protocolData name="Arduino Simple Trigger"
            class="trigger"
            type="ArduinoSimpleTriggerProtocol.ArduinoTrigger, ArduinoSimpleTriggerProtocol,
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <protocolData name="Lync Notifications"
            class="alarm"
            type="LyncProtocol.LyncAlarmChannel, LyncProtocol, Version=1.0.0.0,
                 Culture=neutral, PublicKeyToken=null" />
        <protocolData name="AT Modem Notifications"
            class="alarm"
            type="ATModemProtocol.ATModemAlarmChannel, ATModemProtocol, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <protocolData name="Azure Blob Storage"
            class="storage"
            type="AzureBlobProtocol.AzureBlobManager, AzureBlobProtocol, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <protocolData name="NetWave IP camera"
            class="camera"
            type="NetWaveProtocol.NetWaveCamera, NetWaveProtocol, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <protocolData name="VAPIX IP Camera"
            class="camera"
            type="VAPIXProtocol.VAPIXCamera, VAPIXProtocol, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <protocolData name="DropBox Storage"
            class="storage"
            type="DropBoxProtocol.DropBoxStorage, DropBoxProtocol, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </protocols>
</protocolsSection>

每个协议都有一个 `name`、一个 `class`(用于识别其用途:`trigger`、`alarm`、`storage` 或 `camera`)和一个 `type`(包含实现该协议的类的完整类型)。

您可以使用 **文件 / 安装协议…** 菜单选项,选择包含协议实现的类库,将新协议添加到此部分。

客户端

应用程序必须无论您身在何处都能通知您可能的入侵,因此我将客户端实现为移动应用程序。快速获得几乎所有平台的应用程序版本的最简单方法是使用 `Xamarin`,这就是我选择的方法。

`TWClientApp` PCL(可移植类库)项目包含客户端几乎所有的代码。在不同平台的特定项目中,只有用于保存文件、保存摄像头在手机上拍摄的照片的代码,以便您可以尽快将其提供给警方。

这是我的第一个移动应用程序项目,所以它不是很复杂。在这里,我没有使用依赖注入。相反,我只实现了 `Dropbox` 存储协议,所以如果您想使用其他协议,您需要更改 PCL 库中的代码。此协议的优点是您可以使用 `Dropbox` 实际客户端来获取照片,而无需使用 `ThiefWatcher` 客户端(尽管您会失去应用程序控制能力)。

当您启动客户端应用程序时,您必须按 `连接` 按钮以发送识别消息到主应用程序。

Connect button in the mobile client App

然后,会向客户端发送一个摄像头列表。您可以通过按相应的按钮来选择其中一个。

Select the camera inthe client App

您可以观看摄像头的当前图像。通常,您无法获得真正的视频流,因为上传每张图像可能会非常慢。中央控制会实时捕获帧,但 Dropbox 上传每张图像需要长达两秒钟。

Camera image in the client App

您可以使用按钮来启动/停止摄像头,拍照,或结束警报模式(结束警报模式之前无需停止摄像头)。

照片显示在底部的列表中,您可以将其保存到手机或删除它们。

List of photos

我无法测试 iOS 版本,因为我没有 Mac,但 Windows Phone 和 Android 应用程序运行正常。

Using the Code

不同的协议接口在 `WatcherCommons` 项目的 `Interfaces` 命名空间中定义。摄像头协议是 `IWatcherCamera`,定义如下:

public class FrameEventArgs : EventArgs
{
 public FrameEventArgs(Bitmap bmp)
 {
 Image = bmp;
 }
 public Bitmap Image { get; private set; }
}
public delegate void NewFrameEventHandler(object sender, FrameEventArgs e);
public interface IWatcherCamera
{
 event NewFrameEventHandler OnNewFrame;
 Size FrameSize { get; }
 string ConnectionString { get; set; }
 string UserName { get; set; }
 string Password { get; set; }
 string Uri { get; set; }
 int MaxFPS { get; set; }
 bool Status { get; }
 ICameraSetupManager SetupManager { get; }
 void Initialize();
 void ShowCameraConfiguration(Form parent);
 void Start();
 void Close();
}
  • `OnNewFrame`:当图像准备好发送到应用程序时触发的事件处理程序。图像作为 Bitmap 传递在 `FrameEventArgs` 参数的 `Image` 属性中。
  • `FrameSize`:摄像头图像的当前宽度和高度。
  • `ConnectionString`:一个分号分隔的字符串,用于定义摄像头访问参数。在我实现的协议中,参数是 `url`、`userName` 和 `password`,如下所示:url=http://192.168.1.20;userName=root;password=root
  • `UserName`、`Password` 和 `Uri`:与连接字符串中的相同。
  • `MaxFps`:用于设置捕获速率。
  • `Status`:如果摄像头正在运行,则为 true。
  • `SetupManager`:与摄像头设置对话框的接口。用于在用户更改摄像头图像大小时在应用程序中触发事件,以便摄像头窗口可以正确调整大小。
  • `Initialize`:根据需要重置内部状态。
  • `ShowCameraConfiguration`:显示摄像头配置对话框。它不应该是模态的,这样您就可以在摄像头实时显示图像时查看更改。
  • `Start`:启动图像捕获。这在单独的线程中进行,当在新帧事件中与摄像头交互时,您必须考虑这一点。
  • `Stop`:停止捕获。

`NetWave` 协议实现在 `NetWaveProtocol` 项目中,`VAPIX` 协议实现在 `VAPIXProtocol` 项目中。

触发协议 `ITrigger` 如下:

public interface ITrigger
{
 event EventHandler OnTriggerFired;
 string ConnectionString { get; set; }
 void Initialize();
 void Start();
 void Stop();
}
  • `OnTriggerFired`:当检测到触发条件时触发。
  • `ConnectionString`:包含配置参数的字符串。在我实现的协议中,在 `ArduinoSimpleTriggerProtocol` 项目中,它们是 `port` 和 `baudrate`,如下所示:port=COM4;baudrate=9600。请记住在 `Arduino` 代码中设置相同的波特率。
  • `Initialize`:根据需要重置内部状态。
  • `Start`:开始监听触发条件。这在一个单独的线程中完成。
  • `Stop`:停止监听。

通知协议 `IAlarmChannel` 也非常简单:

public interface IAlarmChannel
{
 string ConnectionString { get; set; }
 string MessageText { get; set; }
 void Initialice();
 void SendAlarm();
}
  • `ConnectionString`:包含配置参数的字符串。
  • `MessageText`:要发送的消息,如果协议允许的话。
  • `Initialize`:重置内部状态。
  • `SendAlarm`:向客户端发送通知。

我实现的协议是 `ATModemProtocol` 项目,它使用 AT 调制解调器拨打一个或多个电话号码,并具有以下配置参数:

  • `port`:调制解调器连接的 COM 端口
  • `baudrate`:设置端口波特率
  • `initdelay`:拨号前等待的毫秒延迟
  • `number`:逗号分隔的电话号码列表
  • `ringduration`:挂断前的响铃毫秒数

另一个协议使用 `Skype` 或 `Lync` 来通知用户。它实现在 `LyncProtocol` 项目中。连接字符串是以分号分隔的 `Skype` 或 `Lync` 用户地址列表。您必须在主计算机和客户端上都安装 `Lync` 客户端。

后者是存储协议,此协议使用的数据定义在 `WatcherCommons` 类库的 `Data` 命名空间中。有两个不同的类,`ControlCommand` 用于摄像头命令。

[DataContract]
public class ControlCommand
{
 public const int cmdGetCameraList = 1;
 public const int cmdStopAlarm = 2;
 public ControlCommand()
 {
 }
 public static ControlCommand FromJSON(Stream s)
 {
 s.Position = 0;
 StreamReader rdr = new StreamReader(s);
 string str = rdr.ReadToEnd();
 return JsonConvert.DeserializeObject<ControlCommand>(str);
 }
 public static void ToJSON(Stream s, ControlCommand cc)
 {
 s.Position = 0;
 string js = JsonConvert.SerializeObject(cc);
 StreamWriter wr = new StreamWriter(s);
 wr.Write(js);
 wr.Flush();
 }
 [DataMember]
 public int Command { get; set; }
 [DataMember]
 public string ClientID { get; set; }
}

命令以 JSON 格式发送和接收。有两个不同的命令,在 `Command` 成员中传递,一个是注册应用程序并获取摄像头列表,另一个是停止警报并将应用程序重置为监控模式。

`ClientID` 成员唯一标识每个客户端。

`CameraInfo` 用于以 JSON 格式交换关于摄像头的请求和响应。

[DataContract]
public class CameraInfo
{
 public CameraInfo()
 {
 }
 public static List<CameraInfo> FromJSON(Stream s)
 {
 s.Position = 0;
 StreamReader rdr = new StreamReader(s);
 return JsonConvert.DeserializeObject<List<CameraInfo>>(rdr.ReadToEnd());
 }
 public static void ToJSON(Stream s, List<CameraInfo> ci)
 {
 s.Position = 0;
 string js = JsonConvert.SerializeObject(ci);
 StreamWriter wr = new StreamWriter(s);
 wr.Write(js);
 wr.Flush();
 }
 [DataMember]
 public string ID { get; set; }
 [DataMember]
 public bool Active { get; set; }
 [DataMember]
 public bool Photo { get; set; }
 [DataMember]
 public int Width { get; set; }
 [DataMember]
 public int Height { get; set; }
 [DataMember]
 public string ClientID { get; set; }
}
  • `ID`:摄像头标识符。
  • `Active`:摄像头状态。
  • `Photo`:用于请求摄像头拍照。
  • `Width` 和 `Height`:摄像头图像尺寸。
  • `ClientID`:客户端唯一标识符。

当您请求摄像头列表时,您会收到一个包含 `CameraInfo` 对象数组的响应,每个摄像头一个。

实现协议的接口是 `IStorageManager`。

public interface IStorageManager
{
 string ConnsecionString { get; set; }
 string ContainerPath { get; set; }
 void UploadFile(string filename, Stream s);
 void DownloadFile(string filename, Stream s);
 void DeleteFile(string filename);
 bool ExistsFile(string filename);
 IEnumerable<string> ListFiles(string model);
 IEnumerable<ControlCommand> GetCommands();
 IEnumerable<List<CameraInfo>> GetRequests();
 void SendResponse(List<CameraInfo> resp);
}
  • `ConnectionString`:包含配置参数的字符串。
  • `ContainerPath`:用于标识文件夹、blob 容器名称等。
  • `UploadFile`:发送文件,文件由 `Stream` 对象提供。
  • `DownloadFile`:获取提供的 `Stream` 对象中的文件。
  • `DeleteFile`:删除文件。
  • `ExistsFile`:测试文件是否存在。
  • `ListFiles`:枚举文件夹中的文件,它们的文件名开头必须与 `model` 参数匹配。
  • `GetCommands`:枚举客户端发送的命令。
  • `GetRequests`:枚举客户端发送的摄像头请求。
  • `SendResponse`:发送命令或摄像头请求的响应。

我实现了两个存储协议。`DropBoxProtocol` 项目实现了与 `Dropbox` 配合使用的协议。在服务器端,这只不过是将文件读写到 `Dropbox` 文件夹。无需连接字符串,因为文件夹是单独配置的。

在客户端,这是实现的协议。它略有不同,接口定义在 `TWClientApp` 项目中。

public interface IStorageManager
{
 Task DownloadFile(string filename, Stream s);
 Task DeleteFile(string filename);
 Task<bool> ExistsFile(string filename);
 Task<List<string>> ListFiles(string model);
 Task SendCommand(ControlCommand cmd);
 Task SendRequest(List<CameraInfo> req);
 Task<List<CameraInfo>> GetResponse(string id);
}

它是一个异步接口,并且成员比服务器端的少。实现不像服务器端那样容易;我们必须使用 `Dropbox` API 与之交互。实现位于 `DropBoxStorage` 类中,在 `_accessKey` 常量中,您必须设置安全密钥以成功建立连接(编译代码时不要忘记首次执行此操作,因为没有默认值)。

private const string _accessKey = "";

客户端应用程序几乎所有的代码都在 `TWClientApp` 项目的 `CameraPage` 类中。数据交换协议是通过文件进行的,每个文件都有一个特殊名称来标识它。这些是不同的文件名模式:

  • 摄像头只写入一个帧文件,当客户端读取帧后,它会删除文件,服务器就可以写入另一个文件。该文件是 jpg 图片,名称为 <摄像头 ID>_FRAME_<客户端 ID>.jpg
  • 照片的名称类似,可能有多张照片。名称模式为:<摄像头 ID>_PHOTO_yyyyMMddHHmmss.jpg
  • 客户端可以向服务器发送命令,一次一个,采用 JSON 文本格式,文件名为 cmd_<客户端 ID>.json
  • 当服务器获取命令文件时,它会删除该文件,因此客户端可以发送另一个命令,然后执行该命令。然后,它会以 `resp_<客户端 ID>.json` 的文件名写入一个响应文件。
  • 最后,客户端可以发送摄像头请求,例如拍照,或启动或停止摄像头,以 JSON 格式存储在名为 `req_<客户端 ID>.json` 的文件中。服务器读取该文件,删除它,然后将请求传递给摄像头处理,然后,服务器写入一个响应文件,与命令的情况类似,其中包含摄像头状态。

`NetWave` 摄像头协议配置对话框非常简单,您可以在 我的博客 上阅读有关此协议的更多信息。

至于 `VAPIX` 协议,它更复杂,因为它是一个用于专业摄像头的协议。而不是一个复杂的对话框,其中包含大量控件,我实现了一个包含所有配置参数(数量很多)的树状视图,您可以在其中选择每个参数并更改其值。

就是这样,尽情享受这个解决方案吧,感谢您的阅读!!!

历史

  • 2017年3月10日:初始版本
© . All rights reserved.