硬件设备

为记录以及复盘项目中使用的技术记录,本篇文章将以无人售货机设备举例。


一、万物互联

物联网是万物互联的网络。 更简单的说就是:利用无线网络技术,人们可以与物体“对话”,物体和物体也可以“交流”。

二、 技术支持

数据通讯

如何保证通讯的稳定性,数据一致性,如何进行双向数据通信

开发中遇到的首要问题就是如何进行设备与服务器的数据通信,当用户下载或使用小程序时,怎么才能及时发送指令让设备做出相应的动作。

比如:售货机中,用户扫码后,怎么能够让设备做出打开舱门的动作。支付后如何关闭舱门以及数据计算。

日常生活中可用到的方法包含:

蓝牙控制,消息推送

1、蓝牙控制在共享单车等场景中非常常见,属于是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的近距离无线技术连接。

但即使低成本也是需要成本的,需要采购对应数量的蓝牙模块,如果一个设备只使用一个蓝牙模块还可能会造成多个舱门同时操作时并发的数据错乱问题。

2、消息推送能够确保消息的实时性,稳定性,符合需求。技术选用Socket通信,可保证可双向通信。当然会有断网ip切换问题

Socket

功能1:心跳检测

心跳检测,就是判断对方是否还存活,一般采用定时的发送一些简单的包,如果在指定的时间段内没有收到对方的回应,则判断对方已经挂掉

心跳检测

/// <summary>
/// 开始心跳
/// </summary>
private void StartHeartbeat()
{
// HeartbeatThread:心跳检测线程
// this.ClientOption.HeartRate=5*60:心跳频率
if (HeartbeatThread == null)
{
HeartbeatThread = new Thread(() =>
{
//循环检测
while (true)
{
try
{
//当前时间戳
var dateTimeString = GetTimeStamp(DateTime.Now).ToString();
//发送当前时间戳作为在线状态
Writer.Write(dateTimeString);
//发送成功后休息5分钟
Thread.Sleep(this.ClientOption.HeartRate * 1000);
}
catch (IOException)
{
//发送失败则意味着网络连接断开,将重新连接
ReConnect();
//停止本次连接的心跳线程
break;
}
}
});
HeartbeatThread.Name = "Socket心跳线程";
}
//开始心跳
HeartbeatThread.Start();
}

功能2:通讯加密

为了防止数据抓包恶意攻击,必须每个与服务器通讯的包都需要使用加密

性能优化:

可参考文章:https://juejin.cn/post/6861560765200105486

数据存储

售货机作为一个面向社区选型的项目,数据当然是重中之重。每个社区的喜好,购买力,再准确到用户画像等都要通过数据分析来产生。

作为售货机端因为有很多不稳定因素,所以要保证数据存储持久性,还要保证数据安全以及环境配置不能太过繁琐,以免给运维和实施带来不必要的麻烦。

所以选用了EF+SqlLite数据库

  • 不需要一个单独的服务器进程或操作的系统(无服务器的)。
  • SQLite 不需要配置,这意味着不需要安装或管理。
  • 一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘文件。
  • SQLite 是非常小的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时小于250KiB。
  • SQLite 是自给自足的,这意味着不需要任何外部的依赖。
  • SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。
  • SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能。
  • SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API。
  • SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中运行。

如何操作硬件

一般情况下,设备之间的通信方式可以分成并行通信和串行通信两种。它们的区别是:

并行通信串行通信
传输原理数据各个位同时传输数据按位顺序传输
优点速度快占用引脚资源少
缺点占用引脚资源多速度相对较慢

在项目中使用的是串行通信来匹配对应的设备

定义:https://hanblog.xyz/2019/12/26/%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1/

C#如何操作:https://hanblog.xyz/2019/11/12/Serial-Communication-Note/

举个例子

一条操作命令包含

1,在信号线上共有两种状态,分别是逻辑1(高电平)和逻辑0(低电平)来区分。

2,起始位(Start Bit):发送器是通过发送起始位而开始一个字符的传送,使数据线处于逻辑0状态,提示接受器数据传输即将开始。

3,数据位(Data Bits):数据位一般为8位一个字节的数据(也有6位、7位的情况),低位(LSB)在前,高位(MSB)在后。

4,校验位(parity Bit):可以认为是一个特殊的数据位。校验位一般用来判断接收的数据位有无错误,一般是奇偶校验。在使用中,该位常常取消。

5,停止位:停止位在最后,用以标志一个字符传送的结束,它对应于逻辑1状态。

6,位时间:即时间宽度。起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。

7,帧:从起始位开始到停止位结束的时间间隔称之为一帧。

8,波特率:如波特率9600=9600bps(位/秒)。UART的传送速率,用于说明数据传送的快慢。在串行通信中,数据是按位进行传送的,因此传送速率用每秒钟传送数据位的数目来表示,称之为波特率。

这些都是通过硬件厂商提供的对接文档一一对应即可

串口调试助手

比如我们操作电控锁,实现一个开锁功能:

电控锁

我们先打开对应的串口,将参数对应

using System.IO.Ports;
static void Main(string[] args)
{
var port = new SerialPort("COM1")//端口号
{
BaudRate=9600,//波特率
Parity=Parity.None,//校验位(流控)
DataBits =8,//数据位
StopBits =1,//停止位
}
port.Open();//打开一个新的串口连接。
}

文档中描述开锁命令为:

开锁命令

var data = new byte[]
{
0x8A,
0x01,
0x01,
0x11,
0x9B
};
port.Write(data, 0, data.Length);
port.Close();

这样即可实现开锁功能。

当然命令有时候是动态的,所以最后的校验需要以对应的规则去计算。

遇到的问题:

1.串口在开启后如果再次开启会抛出异常,为了防止串口开起后未释放资源,建议操作后将串口关闭。

2.传输的数据一般为16进制,如遇到自定义信息的传输,需要先转换为16进制后进行传输

3.在多个设备同时操作时,如果是半双工则可能遇到数据错乱问题。比如两个设备的命令被混合在一起发送或响应,建议做好响应校验和并发处理