第 3 阶段:使用巧妙的串行通信和 ASP.Net Web 服务将您的基本 Arduino 设备(无需任何互联网防护罩)转换为物联网






4.94/5 (15投票s)
一项将 Arduino 转换为物联网设备的初学者技巧,使用 C# 和 Asp.Net Web 服务
目录
2.3 基于 ArdOS 的 Arduino 草图处理串行通信
3. 开发我们自己的 Web 服务作为 C# 客户端与世界之间的中间件
图 1:将 Arduino 转换为物联网设备的架构
1. 背景
Arduino 是 DIY 项目和开发硬件原型的流行平台。物联网 (IoT) 是将此类设备和硬件平台通过唯一 IP 地址连接到互联网的抽象。因此,连接到物联网的 Arduino 可以被远程控制,其数据也可以远程获取。但为此,Arduino 必须连接到互联网。
Arduino Yun 是 Arduino 硬件家族中最新的一款,它同时拥有 Wifi 和以太网模块来连接互联网。一旦连接,我们需要某种机制来在互联网上发现该设备。常见的技术有端口转发、WebSockets 和轮询。但要使这些技术中的任何一个工作,首要条件是设备必须具有 TCP/IP 堆栈,而 Yun 当然具备。
然而,如果我们有一个真正的低成本 Arduino 设备(例如 Arduino Dueminolova 或 Decimilia),价格低于 10 美元,并且没有任何连接模块,如果我们仍然希望该设备连接到网络,我们该怎么办?
本教程将重点介绍:
1) 演示 Arduino 的串行通信原理
2) 开发一个 Web 服务来连接 Arduino 和互联网
3) 演示一种基于 Android 的技术来远程访问连接的设备。
图 1 应该清楚地告诉您我们将在本教程中做什么。
任何对嵌入式编程和硬件设备了解甚少或一无所知的开发人员都应该相对轻松地完成本教程,并且会发现入门物联网很容易。最好的是,该技术也可以用于支持串行通信的各种硬件。
此外,一旦理解了物联网在不同设计级别的基本功能,使用服务(连接即服务、平台即服务)将变得更容易,因为您已经了解了物联网的所有层次。
如果您对 Arduino 相对陌生,我建议您阅读我的 Arduino 和 ArdOS 基础教程。这应该能让您对设置 Arduino 环境和进行基本 Arduino 编程有清晰的了解。我在本教程中使用了 Arduino Duemilanove,您可以随意使用 Arduino Uno R3、Decimilia 甚至 Freeduino。
2. Arduino 与 C# 的串行通信
2.1 Arduino 串行入门
首先将 Arduino USB 线插入笔记本电脑。我们将首先测试 Arduino 串行通信,然后将其与 C# 应用程序集成。
让我们编写一个简单的 Arduino 草图,以 19200 波特率启动串行通信。应用程序读取模拟引脚 5,并每秒将值打印到串行端口一次。
void setup()
{
Serial.begin(19200);
}
void loop()
{
int a=analogRead(5);
Serial.println(a);
delay(1000);
}
编译并将草图上传到 Arduino 板后,可以通过 Arduino 的串行监视器来测试结果,从“工具”->“串行监视器”打开串行监视器窗口。
图 2.1:简单 Arduino 串行打印模拟引脚 5 电压的结果
串行监视器是一个串行通信客户端,可用于绑定任何支持串行通信的设备。现在我们的目标是构建一个 C# 客户端,用于与设备通信。让我们看看基本的客户端设计。
2.2 C# 串行客户端
2.2.1 通过串行端口读取数据
图 2.2:C#.Net 串行客户端设计
设计非常简单。顶部的组合框必须显示所有可用的端口,用户将从中选择适当的 USB 端口。选择后,他将点击连接按钮,该按钮应请求所选串行端口中连接的设备启动串行通信会话。一旦建立通信,我们第一个 Arduino 草图在串行端口打印的数据将显示在列表框中。
我们还创建了一个文本框和一个按钮,用于通过串行端口向设备发送命令。但首先我们将测试从端口读取串行值。
在 Form_Load 事件中,我们将读取所有可用的串行端口并将值分配给组合框。
private void Form1_Load(object sender, EventArgs e)
{
string[] ports = System.IO.Ports.SerialPort.GetPortNames();
for (int i = 0; i < ports.Length; i++)
{
comboBox1.Items.Add(ports[i]);
}
}
您还可以创建另一个组合框,用于存储波特率值,如 9600、19200、57600、115200 等。但由于我使用的是经过时间考验的 19200 波特率,我没有使用其他波特率选择选项。
现在我们为连接按钮附加事件处理程序。
private void button2_Click(object sender, EventArgs e)
{
try
{
serialPort1.PortName = comboBox1.SelectedItem.ToString();
serialPort1.BaudRate = 19200;
serialPort1.Open();
MessageBox.Show("Success");
}
catch (Exception ex)
{
MessageBox.Show("Failed: "+ex.Message);
}
}
serialPort1
是 SerialPort
类的一个未初始化对象。正如您所看到的,一旦用户从组合框中选择了端口名称并点击了连接按钮,SerialPort
对象将使用选定的端口名称和指定的 19200 波特率进行初始化。最后,端口被打开。
我们的目标是读取从串行端口传入的数据。由于 serialPort1 将从与主线程不同的线程发送和接收数据,我们无法通过主线程访问从串行端口传入的数据,因为它会导致跨线程应用程序。因此,我们将使用一个简单的委托将数据从串行端口传递到主 UI 组件。
让我们声明一个简单的委托,指向一个接受字符串参数的 void 方法。串行数据将作为参数传递。
delegate void del(string data);
现在让我们创建这个委托将要初始化的方法
void Display(string s)
{
listBox1.Items.Add(s);
}
最后,在表单的构造函数中将我们的 del
委托初始化为 Display
方法。
del MyDlg;
public Form1()
{
InitializeComponent();
MyDlg = new del(Display);
}
串行数据的到达是异步的,并且幸运地会引发一个事件。因此,我们必须在 serialPort1
初始化后,在 Initialize component 中初始化串行数据接收事件处理程序。
serialPort1.DataReceived +=new System.IO.Ports.SerialDataReceivedEventHandler(this.serialPort1_DataReceived);
最后,当串行数据到达时,使用 SerialPort 类的 ReadLine() 方法将整行作为字符串读取,并通过 BeginInvoke
方法调用 MyDlg 将字符串分配给 ListBox。
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
String s = serialPort1.ReadLine();
this.BeginInvoke(MyDlg, s);
}
就是这样,我们已经准备好从 Arduino 设备读取数据了。
为了测试,首先确保您已关闭 Arduino IDE 的串行监视器界面。由于它也是一个客户端,因此在串行监视器打开并与您的设备通信时,其他应用程序无法打开该端口。
关闭 Arduino 串行监视器,插入 USB 电缆,然后构建并运行 C# 应用程序。选择设备连接的端口,然后单击连接按钮。
您会看到应用程序正在接收数据并将其记录在列表框中。您可以通过用手指触摸端口值来验证数据确实是模拟端口电压。您会看到值有一个跳动。
图:2.3:C# 应用程序从 Arduino 接收串行数据
成功实现串行读取后,现在让我们着手向串行端口发送命令。
2.2.2 向串行端口写入数据
在完成串行读取之前,我想讨论一些对于串行通信至关重要的关键点。在您的 loop()
方法中添加以下代码片段。
if(Serial.available())
{
n2=Serial.read() ;
if(n2>=0)
{
Serial.print(" DATA RECEIVED AS SERIAL INPUT COMMAND IS :");
Serial.println(n2);
}
}
当您在 Arduino 设备中编译并上传草图,并用串行监视器测试结果时,您会看到一些令人惊讶的结果。
当您在串行监视器输入窗口中输入 1 时,您将看到 49;输入 0 时,您将看到 48;输入 10 时,您将看到两行:49 后跟 48。参见图 2.4。
图 2.4:来自串行监视器的串行输入
那是因为串行监视器界面接受字符作为输入。所以当您输入“1”时,您提供的不是数字,而是字符“1”,其 ASCII 等价物是 49,因此当您输入 1 时,您会看到 49。
然而,C# 的串行通信可以向串行端口发送字符串和数字。因此,在处理串行输入时,您制定的逻辑将根据您是从串行监视器客户端还是 C# 客户端提供输入而有所不同。您还可以编写一些简单的逻辑来处理两种类型的输入。另外,不建议在 Arduino 中从客户端接收串行数据时打印任何内容,因为打印的文本也将对客户端可用。
2.3 基于 ArdOS 的 Arduino 草图处理串行通信
现在当 C# 客户端生成命令时,Arduino 草图必须异步读取。因此,Arduino 必须“并行”运行两个任务(更精确地说是并发)。在一个任务中,它应该每秒打印模拟值,在另一个任务中,它必须不断检查串行端口中可用的值并处理该值。
因此,我们将为 Arduino 草图使用 ArdOS。ArdOS 为多任务提供了非常好的支持,这在这里很简单。如果您不熟悉 ArdOS,我强烈建议您阅读我的 ArdOS 教程,并设置您的 Arduino 环境以支持 ArdOS。
那么,让我们创建一个包含两个任务的 ArdOS 草图。一个任务每秒将模拟数据打印到串行端口,另一个任务应从串行端口获取数据作为命令。如果用户通过串行端口提供 1,引脚 13 的 LED 应亮起;如果提供 2,LED 应熄灭。
#include <kernel.h>
#include <queue.h>
#include <sema.h>
#define NUM_TASKS 2
void taskSerialRead(void *p)
{
int n2=0;
while(1)
{
if(Serial.available())
{
n2=Serial.read() ;
////////////// Switching Logic///////////
if(n2>0)
{
if(n2==1)
{
digitalWrite(13,HIGH);
}
if(n2==2)
{
digitalWrite(13,LOW);
}
/////////////////////////
/*
/////////// Uncomment following lines while testing Serial Data
Serial.print(" DATA RECEIVED AS SERIAL INPUT COMMAND IS :");
Serial.println(n2);
*/
}
OSSleep(100);
}
}
}
void taskSerialWrite(void * p)
{
int n1=0;
while(1)
{
n1=analogRead(5);
Serial.println(n1);
OSSleep(1000);
}
}
void setup()
{
OSInit(NUM_TASKS);
Serial.begin(19200);
pinMode(13, OUTPUT);
digitalWrite(13,LOW);
OSCreateTask(0, taskSerialRead, NULL);
OSCreateTask(1, taskSerialWrite, NULL);
OSRun();
}
void loop()
{
// Empty
}
编译并上传草图,并准备好您的 C# 客户端通过串行端口向设备发送数据。不难猜测,C# 客户端写入数据将是同步的,并与按钮点击事件相关联。
所以让我们完成 button1 的事件处理程序,并允许客户端向串行端口写入数据。
private void button1_Click_1(object sender, EventArgs e)
{
try
{
serialPort1.Write(new byte[] { byte.Parse(textBox1.Text) }, 0, 1);
}
catch
{
}
}
所以我们将命令作为字节发送,并通知数组应从偏移量 0 读取,且只读取 1 个元素。
现在,当您运行 .Net 应用程序并在输入文本框中输入 1 后单击发送按钮时,您会看到引脚 13 上的 LED 亮起,当您提供命令 2 时,它将熄灭。
因此,我们现在有了一个 Arduino 草图,可以通过串行通信与 C# 客户端应用程序通信。
现在我们需要开发一个 Web 服务,它可以从其他应用程序读取命令,并且 C# 客户端可以从 Web 服务轮询命令。
3. 开发我们自己的 Web 服务作为 C# 客户端与世界之间的中间件
3.1 通信协议
现在有三个实体必须无缝通信,才能控制 Arduino 设备并从中获取数据。参与此通信的实体包括:通过串行端口连接到 Arduino 的 C# 客户端、Web 服务以及请求特定设备数据的远程客户端。
因此,必须开发一个合适的协议来支持这种通信。请看下图以了解通信基础知识。
图:3.1:所提议自定义服务的通信协议
我们可以将协议总结如下:
1) C# 客户端必须持续从 Arduino 接收数据并分析数据。如果发生事件(例如温度超过某个阈值或 LDR 低于某个范围等),则必须触发事件并向 Web 服务发送一些通知数据以及系统的 IP 地址。
2) 当 Web 服务从 C# 客户端接收到数据时,它会保存数据并附带当前时间戳。
3) 当来自同一 C# 客户端的任何新通知到达时,Web 服务会删除以前的条目并存储当前条目(这是为了限制数据库使用)
4) 远程客户端(例如 Android 客户端)必须知道物联网 Android 设备连接到的系统的 IP 地址,并且可以请求数据。Web 服务将在数据库中搜索特定 IP 的数据并返回数据。
5) 远程客户端可能会请求执行某些命令。此请求也存储在数据库中。
6) C# 客户端定期轮询 WebService,检查是否有可执行的命令。WebService 将存储在数据库中的数据转发给 C# 客户端。这通过串行端口通知 Arduino 设备。Arduino 执行命令并通知结果。如果结果成功,C# 会向 WebService 发送成功通知。收到成功通知后,WebService 必须删除存储的命令,以避免重复执行相同的命令。
尽管理想情况下,身份验证、授权和加密也必须是上述协议的一部分,以使其更安全,但我本文的重点将放在开发通信协议上。您始终可以在安全层上进行工作。
3.2 数据库设计
所以让我们开始在数据库中创建两个名为 Commands 和 Notifications 的表
create table Commands( projId varchar(100),toDevice varchar(100),commandText varchar(100),generatedBy varchar(100), whatTime datetime, commandStatus varchar(100));
create table Notifications(projId varchar(100),fromDevice varchar(100), notificationMessage text, whatTime datetime);
我已经在我的服务器数据库中创建了该表。您可以在本地 SqlServer 数据库中测试逻辑。除了其他字段,我还创建了一个名为 projId 的字段,以方便该表在不同应用程序之间重用。这是在处理 Web 数据库时将不同应用程序的数据分开的好方法。
现在让我们创建一个轻量级的 ASP.Net Web 服务,它在两个表中插入、更新、删除和搜索数据。
3.3 Web 服务
由于我们打算在不同平台上使用 Web 服务,因此我们不会返回任何对象数据或 DataSet。相反,我们将为查询返回一个封送的字符串。
这是我将实时部署的 Web 服务。
<%@ WebService language="C#" class="IoTService" %>
using System;
using System.Web.Services;
using System.Xml.Serialization;
public class IoTService
{
[WebMethod]
public int InsertCommand(string projId, string toDevice, string commandText, string generatedBy, System.DateTime whatTime, string commandStatus) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "INSERT INTO [Commands] ([projId], [toDevice], [commandText], [generatedBy], [what" +
"Time], [commandStatus]) VALUES (@projId, @toDevice, @commandText, @generatedBy, " +
"@whatTime, @commandStatus)";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_toDevice = new System.Data.SqlClient.SqlParameter();
dbParam_toDevice.ParameterName = "@toDevice";
dbParam_toDevice.Value = toDevice;
dbParam_toDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_toDevice);
System.Data.IDataParameter dbParam_commandText = new System.Data.SqlClient.SqlParameter();
dbParam_commandText.ParameterName = "@commandText";
dbParam_commandText.Value = commandText;
dbParam_commandText.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_commandText);
System.Data.IDataParameter dbParam_generatedBy = new System.Data.SqlClient.SqlParameter();
dbParam_generatedBy.ParameterName = "@generatedBy";
dbParam_generatedBy.Value = generatedBy;
dbParam_generatedBy.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_generatedBy);
System.Data.IDataParameter dbParam_whatTime = new System.Data.SqlClient.SqlParameter();
dbParam_whatTime.ParameterName = "@whatTime";
dbParam_whatTime.Value = whatTime;
dbParam_whatTime.DbType = System.Data.DbType.DateTime;
dbCommand.Parameters.Add(dbParam_whatTime);
System.Data.IDataParameter dbParam_commandStatus = new System.Data.SqlClient.SqlParameter();
dbParam_commandStatus.ParameterName = "@commandStatus";
dbParam_commandStatus.Value = commandStatus;
dbParam_commandStatus.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_commandStatus);
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}
[WebMethod]
public string CommandToExecute(string projId, string toDevice) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "SELECT [Commands].[commandText], [Commands].[generatedBy], [Commands].[whatTime]," +
" [Commands].[commandStatus] FROM [Commands] WHERE (([Commands].[projId] = @projI" +
"d) AND ([Commands].[toDevice] = @toDevice))";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_toDevice = new System.Data.SqlClient.SqlParameter();
dbParam_toDevice.ParameterName = "@toDevice";
dbParam_toDevice.Value = toDevice;
dbParam_toDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_toDevice);
dbConnection.Open();
System.Data.IDataReader dataReader = dbCommand.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
string s="";
while(dataReader.Read())
{
s=dataReader[0]+"#"+dataReader[1]+"#"+dataReader[2];
}
return s;
}
[WebMethod]
public int InsertNotification(string projId, string fromDevice, string notificationMessage, System.DateTime whatTime) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "INSERT INTO [Notifications] ([projId], [fromDevice], [notificationMessage], [what" +
"Time]) VALUES (@projId, @fromDevice, @notificationMessage, @whatTime)";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_fromDevice = new System.Data.SqlClient.SqlParameter();
dbParam_fromDevice.ParameterName = "@fromDevice";
dbParam_fromDevice.Value = fromDevice;
dbParam_fromDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_fromDevice);
System.Data.IDataParameter dbParam_notificationMessage = new System.Data.SqlClient.SqlParameter();
dbParam_notificationMessage.ParameterName = "@notificationMessage";
dbParam_notificationMessage.Value = notificationMessage;
dbParam_notificationMessage.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_notificationMessage);
System.Data.IDataParameter dbParam_whatTime = new System.Data.SqlClient.SqlParameter();
dbParam_whatTime.ParameterName = "@whatTime";
dbParam_whatTime.Value = whatTime;
dbParam_whatTime.DbType = System.Data.DbType.DateTime;
dbCommand.Parameters.Add(dbParam_whatTime);
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}
[WebMethod]
public string FetchNotification(string projId, string fromDevice) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "SELECT [Notifications].[notificationMessage], [Notifications].[whatTime] FROM [No" +
"tifications] WHERE (([Notifications].[projId] = @projId) AND ([Notifications].[f" +
"romDevice] = @fromDevice))";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_fromDevice = new System.Data.SqlClient.SqlParameter();
dbParam_fromDevice.ParameterName = "@fromDevice";
dbParam_fromDevice.Value = fromDevice;
dbParam_fromDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_fromDevice);
dbConnection.Open();
System.Data.IDataReader dataReader = dbCommand.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
string s="";
while(dataReader.Read())
{
s=dataReader[0]+"#"+dataReader[1];
}
return s;
}
[WebMethod]
public int UpdateCommandStatus(string projId, string toDevice, string commandStatus) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "UPDATE [Commands] SET [commandStatus]=@commandStatus WHERE (([Commands].[projId] " +
"= @projId) AND ([Commands].[toDevice] = @toDevice))";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_toDevice = new System.Data.SqlClient.SqlParameter();
dbParam_toDevice.ParameterName = "@toDevice";
dbParam_toDevice.Value = toDevice;
dbParam_toDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_toDevice);
System.Data.IDataParameter dbParam_commandStatus = new System.Data.SqlClient.SqlParameter();
dbParam_commandStatus.ParameterName = "@commandStatus";
dbParam_commandStatus.Value = commandStatus;
dbParam_commandStatus.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_commandStatus);
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}
[WebMethod]
public int DeleteCommand(string projId, string toDevice) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "DELETE FROM [Commands] WHERE (([Commands].[projId] = @projId) AND ([Commands].[to" +
"Device] = @toDevice))";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_toDevice = new System.Data.SqlClient.SqlParameter();
dbParam_toDevice.ParameterName = "@toDevice";
dbParam_toDevice.Value = toDevice;
dbParam_toDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_toDevice);
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}
[WebMethod]
public int DeleteNotification(string projId, string fromDevice) {
string connectionString = "server=\'YOUR_SERVER_ADDRESS\'; user id=\'USER_ID\'; password=\'PASSWORD" +
"\'; database=\'DATABASE_NAME\'";
System.Data.IDbConnection dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
string queryString = "DELETE FROM [Notifications] WHERE (([Notifications].[projId] = @projId) AND ([Not" +
"ifications].[fromDevice] = @fromDevice))";
System.Data.IDbCommand dbCommand = new System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_projId = new System.Data.SqlClient.SqlParameter();
dbParam_projId.ParameterName = "@projId";
dbParam_projId.Value = projId;
dbParam_projId.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_projId);
System.Data.IDataParameter dbParam_fromDevice = new System.Data.SqlClient.SqlParameter();
dbParam_fromDevice.ParameterName = "@fromDevice";
dbParam_fromDevice.Value = fromDevice;
dbParam_fromDevice.DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_fromDevice);
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}
}
当您在 Web 服务器中运行并测试您的 WebService 时,它应该如下图所示。asmx 文件也随代码 zip 文件一起分发,名为 IoTService_CP_Distro.asmx。您所要做的就是创建数据库表,并用您的服务器 ID、用户名、密码和数据库名称凭据分别替换 YOUR_SERVER_ADDRESS、USERNAME、PASSWORD、DATABASE_NAME 字段。
图 3.2:本地运行 Web 服务
因此,远程客户端将首先使用状态为“NEW”的 Web 方法 InsertCommand()
插入要执行的命令,C# 客户端在通过 CommandToExecute()
方法轮询命令时,如果看到一个带有 NEW 标记的命令,则会获取并执行它。成功执行后,它将使用 UpdateCommandStatus()
将状态更改为“EXECUTED”。一旦远程客户端发现其命令已执行,便通过调用 DeleteCommand()
从堆栈中删除该命令。
C# 客户端持续分析来自 Arduino 设备通过串行端口的数据,当有触发器(例如模拟端口的值超过 800)时,就会使用 InsertNotification()
生成通知。远程客户端在轮询特定设备的数据时,如果看到新的通知(取决于时间戳),就会生成警报。通知不会被删除,因为在任何给定时间点,许多客户端都可能需要它们。
另请观察 CommandToExecute()
方法中的以下代码块
string s="";
while(dataReader.Read())
{
s=dataReader[0]+"#"+dataReader[1]+"#"+dataReader[2];
}
return s;
您可以看到,在从 Web 方法返回数据时,我们将所有字段封送到一个由 # 分隔的字符串中。这种数据格式易于在任何 Web 服务使用者客户端解码。
4. 将 C# 客户端绑定到 Web 服务
4.1 Web 服务发现
我已将此 Web 服务发布到公共领域,您可以用于测试。
http://grasshoppernetwork.com/IoTService.asmx
我正在使用 Visual Studio 2010。在 VS 2010 中,您可以从以下位置发现并添加 Web 服务的服务引用:
项目->添加服务引用,如图 3.3 所示
图 3.3 在 C# 客户端中添加 Web 服务引用
现在我们将在实际操作中看到该服务。正如您可能知道的,一旦部署到服务器,Web 服务就无法从网页进行测试,但同一个 Web 服务可以在本地进行测试。由于无论该服务在哪里运行,它都会将数据写入单个数据库,我们始终可以通过本地运行的 Web 服务来测试结果。
因此,我们的 C# 客户端将通过部署在服务器上的远程服务写入和访问数据,我们将通过访问本地部署的服务中的数据库数据来测试协议。
4.2 生成通知
请记住,通知需要从具有有效 ID 的设备发送。因此,我们将使用 Arduino 板连接到的系统的 IP 地址。为了获取计算机的 IP 地址,我们将使用以下名为 LocalIpAddress()
的简单方法,它通过 DNS 请求返回您计算机的 IP 地址。
public string LocalIPAddress()
{
IPHostEntry host;
string localIP = "";
host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
localIP = ip.ToString();
break;
}
}
return localIP;
}
我们将在表单加载时将 IP 地址的值保存在名为 ipAddress 的变量中,以便我们可以在项目的其余部分使用此值。
现在,在处理传感器事件时,您需要非常小心。例如,如果您想在温度传感器值超过 30 度时生成通知,那么您的 C# 客户端将每秒从 Arduino 读取数据,如果温度超过 30 摄氏度,则会每秒触发一次。但您不能每秒都生成远程通知。那样会淹没服务器。因此,我们将使用一个名为 timNotDelay 的计时器。一旦我们生成了通知,我们将启动计时器,并且在计时器到期之前不会生成任何通知。
由于 Display()
方法正在处理来自串行端口的数据,我们将在此处编写我们的通知生成逻辑。我们通过调用 IoTServiceSoapClient
类对象(它是我们远程 IoTService
类的代理(存根)类)的 InsertNotification()
来插入通知。ipAddress
是存储系统 IP 地址的局部变量,projID
是用“ArduinoIoT”初始化的全局字符串。如果您只想测试自己的逻辑,可以随意使用不同的项目名称。
void Display(string s)
{
listBox1.Items.Add(s);
this.Text = s;
try
{
int n = int.Parse(s);
/////////// Event Trigger////////
if (n > 800)
{
ArduinoSerial.ServiceReference1.IoTServiceSoapClient iotClient = new ArduinoSerial.ServiceReference1.IoTServiceSoapClient();
if (!timNotDelay.Enabled)
{
iotClient.InsertNotification(projId, ipAddress, "VALUE EXCEEDS 800", DateTime.Now);
timNotDelay.Enabled = true;
}
}
////////////////////////////////////
}
catch
{
}
}
这是输出的屏幕截图。我触摸了 Arduino 板上的所有模拟引脚。因此模拟值显然超过了 800。因此它生成了通知。我可以通过本地运行的服务读取通知,因为远程和本地服务都使用相同的数据库。
图 3.4 生成通知的屏幕截图
以下是表 Notifications 的数据库值快照
图 3.5:通知表条目
您可以看到条目之间至少相隔 20 秒,这是我们想要的。然而,仍然会有几个条目。设计者可能会忘记删除这些条目。为了保持穷人数据库的有序性,我将从 InsertNotification 调用 DeleteNotifications() 方法。我也可以使用 Update 方法,但在我可能需要实际记录不同数据以制作图表的应用程序中,这种技术将会失败。
[WebMethod]
public int InsertNotification(string projId, string fromDevice, string notificationMessage, System.DateTime whatTime)
{
DeleteNotification( projId, fromDevice);
.... Rest of Code
}
4.3 生成命令
让我们考虑用户可以分别远程生成“LED ON”和“LED OFF”命令,以指示设备打开或关闭 Arduino 引脚 13 的 LED。现在 C# 客户端应每隔特定时间(例如 10 秒)通过调用 CommandToExecute()
Web 方法进行轮询。该方法返回一个字符串,其中包含命令、状态和日期时间,以“#”分隔。轮询部分应将命令和状态分开,并检查状态是否为“OK”,如果不是 OK,则执行命令,并在成功执行后,将命令状态更新为 OK,以避免再次重复执行同一命令。
一旦获取到命令,它会检查是否为 LED ON 和 LED OFF,然后向串行端口发送适当的数据。Arduino 期望 1 来打开 LED,2 来关闭 LED。因此 C# 客户端必须根据命令分别发送 1 和 2。
这种技术最好的地方在于,您可以设计自己的命令集,并且无需更改 C# 客户端代码即可集成至少 10 个命令(0-9)。确实,当您设计自己的命令集时,您必须相应地更新 Arduino 草图。
远程客户端必须通过调用 InsertCommand()
方法生成一个状态不是 OK(例如 EXECUTE)的命令。生成命令后,客户端必须轮询以检查状态。当状态变为 OK 时,它必须通知用户他发出的命令已成功执行。
这是用于轮询命令的计时器滴答事件处理程序。
private void timPollCommands_Tick(object sender, EventArgs e)
{
timPollCommands.Enabled = false;
string []result=iotClient.CommandToExecute(projId, ipAddress).Split(new char[]{'#'});
string command = result[0];
string status = result[1];
if (command.Equals("LED ON")&& !status.Equals("OK"))
{
try
{
serialPort1.Write(new byte[] { 1 }, 0, 1);
iotClient.UpdateCommandStatus(projId, ipAddress, "OK");
}
catch
{
iotClient.DeleteCommand(projId, ipAddress);
}
}
if (command.Equals("LED OFF") && !status.Equals("OK"))
{
try
{
serialPort1.Write(new byte[] { 2 }, 0, 1);
iotClient.UpdateCommandStatus(projId, ipAddress, "OK");
}
catch
{
iotClient.UpdateCommandStatus(projId, ipAddress, "FAILED");
}
}
timPollCommands.Enabled = true;
}
5. 开发 Web 客户端通过我们的服务与物联网设备通信
使用本地服务进行测试一切都很好。但是,在真实互联网上测试您的设备呢?由于 WebService 在服务器运行时无法在没有客户端的情况下进行测试,因此我们需要一个 Web 客户端。
在本节中,我们将开发一个 ASP.Net 客户端来测试我们的服务。
ASP.Net 客户端有两个文本框,用于输入物联网设备的 IP 地址(即其连接的系统的 IP 地址)和命令。我们使用两个按钮,一个用于生成命令,另一个用于查询通知。TestClient.aspx 使用代理与 WebService 通信。
我没有将整个 HTML 代码填满本文页面,而是将 Aspx 文件与代码文件夹以及 grasshoppernetwork.com/IoTService.asmx 的代理一起提供。您可以在本地测试它们,也可以修改它们。代理位于 bin 文件夹中。
两个按钮点击事件如下所示。
void Button1_Click(object sender, EventArgs e)
{
iics.IoTService c=new iics.IoTService();
c.InsertCommand("ArduinoIot",txtIp.Text,txtCommand.Text,"WebClient",DateTime.Now,"EXECUTE");
Label4.Text="Command Sent";
}
void Button2_Click(object sender, EventArgs e)
{
iics.IoTService c=new iics.IoTService();
string s=c.FetchNotification("ArduinoIot",txtIp.Text);
Label4.Text=s;
}
您可以在以下地址测试客户端:
http://grasshoppernetwork.com/TestClient.aspx
图 4:用于测试物联网服务的 Web 远程客户端
您可以随意使用 JavaScript 在客户端实现轮询以检查命令状态。您还可以开发一种基于 JavaScript 的技术来在后台检查通知状态并生成事件以在网页中显示通知。
6. 我们的物联网服务的 Android 客户端
我们已经看到 Aspx 客户端如何使用代理与 WebService 通信。由于网页部署在服务器上,它可以直接用于任何设备。但是,我还会添加一个 Android Native 应用程序,以表明我们的协议也适用于标准移动应用程序。
事实上,我请您查看我的 Android 文章“处理数据”中的 .Net Web 服务与 Android 集成部分。
这应该能让您很好地了解如何使用与 Web 服务通信的 Android 应用程序。
现在对于布局设计,我们将修改用于上述文章的相同布局。但我们会更改 EditText 和 Button 的 Id 和标签以适应我们当前的工作。
我将 Android 应用程序保持得相当简单,通过两个事件驱动的同步调用 InsertCommand()
和 FetchCommand()
方法。我没有使用任何轮询或后台进程,只是为了保持事情简单。您可以将该应用程序作为起点,开发更复杂的工作流。
这是我们的布局设计,名为 activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.integratedideas.androidiotclient.MainActivity$PlaceholderFragment"
android:fillViewport="true">
<TextView
android:id="@+id/tvResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="56dp"
android:text="@string/hello_world" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/edIp"
android:layout_centerHorizontal="true"
android:layout_marginBottom="56dp"
android:text="IP Of IoT Node"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/btnCheckNotification"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/btnSendCommand"
android:layout_alignBottom="@+id/btnSendCommand"
android:layout_toRightOf="@+id/btnSendCommand"
android:text="Check Message" />
<EditText
android:id="@+id/edCommand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/btnCheckNotification"
android:layout_centerVertical="true"
android:ems="1"
android:inputType="text|textUri" >
<requestFocus />
</EditText>
<Button
android:id="@+id/btnSendCommand"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/tvResult"
android:layout_alignLeft="@+id/edIp"
android:layout_marginBottom="26dp"
android:text="Send Command" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/edCommand"
android:layout_alignRight="@+id/tvResult"
android:layout_marginBottom="14dp"
android:text="Command"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/edIp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/textView2"
android:layout_alignParentLeft="true"
android:layout_marginBottom="35dp"
android:ems="1"
android:gravity="top|left"
android:inputType="textMultiLine"
android:lines="1"
android:maxLines="2"
android:minLines="1"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
</RelativeLayout>
在测试这个布局时,我发现软键盘会使布局变得灵活,也就是说,每当键盘可见时,它都会缩小控件,改变整个布局。
所以我不得不对 Manifest 文件做一些鲜为人知的修改,并添加
android:windowSoftInputMode="stateVisible|adjustPan"
Activity 部分中的部分。这是我们的 Manifest 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.integratedideas.androidiotclient"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.integratedideas.androidiotclient.MainActivity"
android:windowSoftInputMode="stateVisible|adjustPan"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
现在让我们编写一个名为 CallSoap 的类,以包含 GetNotification()
和 InsertCommand()
方法,我们将通过它们分别调用 FetchNotification()
和 InsertCommand()
Web 方法。
我将这些方法保持为无参数,以便在从实现 Runnable 接口的类实例或从 BackgroundThread 调用它们时更容易。我们将初始化类属性以处理 Web 方法的参数。
public class CallSoap
{
public String projId="ArduinoIoT";
public String iotDevIp="";
public String command="";
public static String SOAP_ACTION = "http://tempuri.org/IoTService";
//public final String OPERATION_NAME = "Add";
public static String OPERATION_NAME = "AdminLogin";
public static final String WSDL_TARGET_NAMESPACE = "http://tempuri.org/";
//public final String SOAP_ADDRESS = "http://grasshoppernetwork.com/NewFile.asmx";
public static final String SOAP_ADDRESS = "http://grasshoppernetwork.com/IoTService.asmx";
public CallSoap()
{
}
public String GetNotification()
{
// TODO Auto-generated method stub
SOAP_ACTION = "http://tempuri.org/FetchNotification";
OPERATION_NAME = "FetchNotification";
Object response=null;
//public final String SOAP_ADDRESS = "http://grasshoppernetwork.com/NewFile.asmx";
// TODO Auto-generated method stub
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);
PropertyInfo p=new PropertyInfo();
p.setName("projId");
p.setType(String.class);
p.setValue(projId);
request.addProperty(p);
p=new PropertyInfo();
p.setName("fromDevice");
p.setType(String.class);
p.setValue(iotDevIp);
request.addProperty(p);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE httpTransport = new HttpTransportSE(SOAP_ADDRESS);
try
{
httpTransport.call(SOAP_ACTION, envelope);
response = envelope.getResponse();
}
catch (Exception exception)
{
response=response+"Here it is"+exception.toString();
}
return response.toString();
}
private static Object getSOAPDateString(java.util.Date itemValue) {
String lFormatTemplate = "yyyy-MM-dd'T'hh:mm:ss";
SimpleDateFormat lDateFormat = new SimpleDateFormat(lFormatTemplate);
String lDate = lDateFormat.format(itemValue);
return lDate;
}
public String InsertCommand()
{
// TODO Auto-generated method stub
SOAP_ACTION = "http://tempuri.org/InsertCommand";
OPERATION_NAME = "InsertCommand";
Object response=null;
//public final String SOAP_ADDRESS = "http://grasshoppernetwork.com/NewFile.asmx";
// TODO Auto-generated method stub
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);
PropertyInfo p=new PropertyInfo();
p.setName("projId");
p.setType(String.class);
p.setValue(projId);
request.addProperty(p);
p=new PropertyInfo();
p.setName("toDevice");
p.setType(String.class);
p.setValue(iotDevIp);
request.addProperty(p);
p=new PropertyInfo();
p.setName("commandText");
p.setType(String.class);
p.setValue(command);
request.addProperty(p);
p=new PropertyInfo();
p.setName("generatedBy");
p.setType(String.class);
p.setValue("Android Client");
request.addProperty(p);
p=new PropertyInfo();
p.setName("whatTime");
p.setType(java.util.Date.class);
java.util.Date utilDate = new java.util.Date();
Object ob=getSOAPDateString(utilDate);
p.setValue(ob.toString());
request.addProperty(p);
p=new PropertyInfo();
p.setName("commandStatus");
p.setType(String.class);
p.setValue("Execute");
request.addProperty(p);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE httpTransport = new HttpTransportSE(SOAP_ADDRESS);
try
{
httpTransport.call(SOAP_ACTION, envelope);
response = envelope.getResponse();
}
catch (Exception exception)
{
response=response+"Here it is"+exception.toString();
}
return response.toString();
}
}
这个类有一个非常有趣的部分。当您从 Android 代码调用远程 Asp.Net Web 服务时,它可以很好地处理任何属性。但是,将 Java 特定的日期时间转换为 C# 特定的日期时间存在一个很大的问题。问题在于数据是通过 XML 传递的。
XML 规范要求日期时间格式为 yyyy-MM-dd'T'HH:mm:ss 格式。所以我们首先需要使用 SimpleDateFormat
对象格式化 java.util.Date 对象。因此,我们编写了一个名为 getSOAPDateString
的小型实用方法,它正是完成了这项工作。
最后,我们可以从按钮点击事件处理程序中调用这些方法。
public void onClick(View arg0)
{
Button b=(Button)arg0;
if(b.getText().toString().trim().equals("Send Command"))
{
CallSoap cs=new CallSoap();
cs.projId="ArduinoIoT";
cs.iotDevIp=edIp.getText().toString().trim();
cs.command=edCommand.getText().toString().trim();
webResult=cs.InsertCommand();
tvResult.setText(webResult);
}
if(b.getText().toString().trim().equals("Check Message"))
{
CallSoap cs=new CallSoap();
cs.projId="ArduinoIoT";
cs.iotDevIp=edIp.getText().toString().trim();
webResult=cs.GetNotification();
tvResult.setText(webResult);
// TODO Auto-generated method stub
}
// TODO Auto-generated method stub
}
但 Android 不允许您从主线程调用网络特定操作。因此我在 createView 方法的末尾添加了以下几行,以允许从主线程访问与网络相关的操作。
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
请注意,使用上述技巧从主线程调用网络线程绝不是一个好的编程实践。在您的实际应用程序中,您应该尽一切办法避免这样做。
我这样做是为了保持逻辑部分的简单性。
就这样。我们现在准备测试我们的客户端了。
图 6:Android 屏幕截图
7. 结论
图 7:最终工作输出
上图展示了工作的总体结果,您可以看到我们能够通过我们的 Android 手机和浏览器打开和关闭连接到 Arduino 引脚 13 的 LED。尽管现在有几个云服务提供商,它们消除了编写自己的物联网服务的需要。但是,在某些特定情况下,您可能希望设计自己的解决方案,因为您的逻辑可能有所不同。您可能希望能够将您的物联网设备与您企业更大且现有的业务逻辑连接起来。在这种情况下,所描述的自定义服务架构可能会派上用场。例如,在不久的将来,铁路决定取消纸质车票,并引入物联网支持的基于 RFID 的智能车票。拥有自己的服务可以将预订系统通过您的服务直接与 RFID 数据读写器集成。自定义服务还有助于在服务级别本身实现复杂的处理。例如,您可以编写一个服务,返回存储数据的标准差。
在本教程中,我概述了使用 C# 串行通信和 Asp.Net Web 服务将嵌入式设备连接到互联网的整个协议。我还展示了如何为该服务设计一个 Android 客户端。这是为了让人们了解服务如何抽象客户端技术,以及嵌入式设备如何与任何平台进行通信。我们还将该服务实时部署。我希望您喜欢本教程,就像我喜欢编写代码一样。