第3章 物联网软件开发技术
联网是当前一个比较热门的词汇,每个人对物联网的理解不完全一样,但是在业界比较统一的认为物联网具备的三个基本条件:第一个是全面感知,让物品“说话”,对物联信息进行识别、采集;第二个是可靠传递;第三个是智能处理。其中前两个条件可以由传感技术和通信技术完成,第三个条件则必须通过软件技术去实现。通过一系列的研究对比,目前中国信息网络与传输基础比较好,但是在传感器和芯片制造、集成,以及信息处理的软件技术还是相当薄弱的,软件技术支撑的数据采集也有待提高。
通过最近的几份调查,编者总结出一个良好的物联网软件平台应该具备包括设备管理、集成、安全性、数据收集协议、分析类型以及支持可视化等的功能。
3.1 常见物联网软件开发平台
3.1.1 Arduino
Arduino不仅是一款方便上手、便捷灵活的开源电子原型平台,也是一套软件,包含集成开发环境(IDE)和Arduino编程语言。Arduino其实是一款工具,包括硬件和软件。Arduino IDE具有跨平台,简单易学,并且软硬件开源被广泛的应用有电子产品中,现在许多企业已经使用Arduino来制造相关的物联网产品。
3.1.2 Eclipse IOT Project
Eclipse是著名的跨平台的自由集成开发环境(IDE),最初当作Java集成开发环境(IDE)来使用,Eclipse
仅支持Java 语言,同时也在C/C++、COBOL、PHP、Android等编程语言的插件中得到应用。Eclipse包括了Mihini、Koneki和Paho项目,分别在物联网的应用框架和服务、使用开源技术实现MQTT CoAP、OMA-DM和OMA LWM2M物联网协议、处理Lua的工具等方面得到了广泛应用。
3.1.3 Kinoma
Kinoma软件平台归Marvell所有,它是基于Android系统的Kinoma Play软件。Kinoma Play软件的环境是开放的,编写语言是Kinoma Play Script (KPS),都使用JavaScript语言。Kinoma Play软件将Kinoma 应用程序上的一系列功能和资讯整合到操控界面从而简化日常工作提升效率。它包括Kimona Create、Kimona Studio、Kimona Connect三个开源项目,用于制作电子设备的原型,将智能手机和平板电脑与物联网设备互联起来。
3.1.4 M2M Labs Mainspring
M2M是机器对机器(Machine-To-Machine)通信的,目前重点在无线通信,主要有机器对机器、移动电话对机器和机器对移动电话三种方式。Mainspring基于Java和Apache Cassandra SQL数据库,是一种开源框架,用于开发M2M应用软件,主要应用在开发远程监控、车队管理和智能网格方面。Mainspring在设备的建模、配置、与应用软件间的通信、数据的验证、存储、检索方面都相当灵活。
3.1.5 Node-RED
Node-RED立足于Node.js,是一款开发物联网应程序的强大工具。Node-RE可以让开发人员进行基于流的可视化编辑,连接诸多设备、服务和API(应用编程接口)。Node-RE可以运行在Raspberry Pi上,Node-R利用60000多个模板进行功能的扩展。
3.2 Arduino软件开发平台
Arduino是一款便捷灵活、方便上手的开源电子原型平台,是物联网技术的一种基础应用台。Arduino IDE是Arduino平台的软件开发环境,Arduino IDE基于processing IDE开发,且具有使用类似Java、C语言的IDE集成开发环境。读者只要在IDE中编写程序代码,并将程序烧写到Arduino电路板中,Arduino电路板就能知道做什么了。
与其他的大多数控制器只能在Windows上开发不同,Arduino IDE具有可以Windows、Macintosh OS X、Linux三大主流操作系统上运行的特点。
3.3 Arduino编程语言
Arduino语言是建立在C/C++基础上的,基于wiring语言开发,把AVR单片机(微控制器)相关的一些参数设置函数化,对初学者来说简单易学,不需要太多的单片机和编程的基础知识也可以进行快速开发。
Arduino语言其实就是基础的C语言,对于有C语言基础的程序爱好者来讲,Arduino语言会更加容易掌握。要想写出一个完整的Arduino程序,需要了解和掌握Arduino语言的基本语法,本节主要学习Arduino语言的基本知识。
3.3.1 常量和变量
1.变量
变量是用于储存计算结果或某一值的抽象概念,其值可以改变。变量需要有正确的类型和名字才能使用,变量名字是由标识符定义的。
标识符是由字母、数字和下划线组成的,并且由字母或下划线开头的字符序列。标识符中的大小写是有区别的,如china和China是两个不同的标识符。
关键字不能是标识符,C语言中的关键字主要有如下一些:
数据类型关键字(12个):char, double, enum, float, int, long, short, signed, struct, union, unsigned, void
控制语句关键字(12个):break, case, continue, default, do, else, for, goto, if, return, switch, while
存储类型关键字(4个):auto, extern, register, static
其它关键字(4个):const, sizeof, typedef, volatile
变量根据其作用域不同可分为局部变量和全局变量,其中局部变量指变量的声明在函数的内部,作用域为该函数内部,当离开主程序或函数时,该局部变量将自动消失;而全局变量
变量的声明在函数外部,变量作用域为整个程序。
2.常量
跟变量对应的是常量,程序运行时其值不能改变的量(即常数)。一般情况下,我们会将常量赋值给对应的变量,如c=’a’;该语句中,’a’是字符型常量,而c为字符型变量。常量可以自定义,也可以是Arduino核心代码中自带的。Arduino核心代码自带的常量包括:布尔常量、数字引脚常量、引脚电压常量。
布尔常量:有false和true。通常情况下定义false=0,true=1,但在实际应用过程序中,我们一般把任何非零整数都定义为true。例如-1、2和-200都定义为true。
数字引脚常量:有INPUT和OUTPUT。引脚=INPUT时,表示从引脚读取数据;引脚=OUTPUT时,表示引脚向外部电路输出数据。读者特别要注意两个常量都是大写的。
引脚电压常量:有HIGH和LOW,注意大写。HIGH表示高电位,LOW表示低电位。例如:digitalWrite(pin,HIGH);就是将pin这个引脚设置成高电位的。
在Arduino中自定义常量可以通过宏定义#define和关键字const完成,在定义数组时只能使用const。
3.3.2数据类型
Arduino与C语言类似,有多种数据类型,不同数据类型的变量在内存中占不同的长度,有不同的取值范围。
1.常用数据类型
常用的数据类型有整型、无符号整型、长整型、无符号长整型、浮点型、双精度浮点型、字符型、字节型、布尔类型等。
类型 | 类型说明符 | 在内存所占字节数 | 取值范围 |
整型 | Int | 2 | -32768~~32767 |
无符号整型 | unsigned int | 2 | 0~65535 |
长整型 | long | 4 | -2147483648~2147483647 |
无符号长整型 | unsigned long | 4 | 0~4294967295 |
单精度实型 | float | 4 | 3.4e-38~~3.4e+38 |
双精度实型 | double | 8 | 1.7e-308~~1.7e+308 |
字符型 | char | 1 | -128~+128 |
字节型 | byte | 1 | 0~255 |
布尔类型是一种逻辑值,只有真与假,或是false和true,最常用的布尔运算符是与运算(&&)、或运算(||)和非运算(!)。
2.结构体
结构体是用户构造数据类型的手段,结构体类型是一种构造类型,它是将具有内在联系的不同类型封装在一起,能够处理复杂的数据类型。分为结构体类型和结构体变量。例如:
struct student
{int number; /*学号*/
char name[8]; /*姓名*/
char sex; /*性别*/
float score[4]; /*成绩*/
};
3.数组
数组是一种构造类型,数组中的每个成员具有相同的类型,每一个数据成员的类型都是基本类型(如int、float、char等)。数组分为一维数组、二维数组(多维数组)和字符数组。
数组的创建和声明,与C语言基本一致,如下例子:
arrayInts [12];
arrayNums [] = {2,4,5,6,8,9};
arrayVals [6] = {3,5,-9,9,1-};
char arrayString[8] = "Arduino1"
数组的引用
数组名表示数组的首地址,那么我们还要对数组元素进行各种不同的使用,这类操作叫做对数据元素的使用(也叫数组的引用)。在数组的引用过程中,应该注意以下几点:
在C语言中不能引用整个数组,只能引用单个数组元素。
一个数组元素就是相当于一个变量,它的使用与同类型的普通变量是相同的。
数组元素的引用形式为(一维为例):数组名[下标]
(1)数组名后方括号内是数组下标,下标表示该元素是数组的第几个元素。数组名后面的方括号内的内容只有在数组定义时才是数组的长度,其他时候都是数组下标。
(2)数组元素的下标是整型的常量、变量或表达式。下标的取值范围是[0,数组长度-1]的整型值。
(3)程序运行时,编译系统并不检查数组元素的下标是否越界,需要编程人员自己保证数组元素的下标不要越界。
3.3.3 运算符
1. 算术运算符:(+ - * / % ++ --)
+(加)求和,例如:Z=m+n,将m与m变量的值相加,其和放入Z变量。
-(减)做差,例如:Z=m-n,将 m变量的值减去n变量的值,其差放入Z变量。
*(乘)乘法运算,例如:Z = m *y,将m与n变量的值相乘,其积放入Z变量。
/(除)除法运算,例如:Z = m /y,将m变量的值除以n变量的值,其商放入Z变量。
%(取余)对两个值进行取余运算,例如:Z=m%n,将m变量的值除以n变量的值,其余数放入Z变量。其中m和n必须都是整数。
++(自增)--(自减):自增运算使单个变量的值增1,自减运算使单个变量的值减1。自增、自减运算符都有两种用法:
(1)前置运算──运算符放在变量之前:++变量、--变量
先使变量的值增(或减)1,然后再以变化后的值参与其它运算,即先增减、后运算。
(2)后置运算──运算符放在变量之后:变量++、变量--
变量先参与其它运算,然后再使变量的值增(或减)1,即先运算、后增减。
如:若a=5,b=6;则x=++a-(b--);这个表达式中x的值为a自增后的值6加上b的值6等于12,同时a自己自增等于6,b自减等于5。
在使用自增自减运算符时应注意以下几点:
a) 只能用于变量,不能用于常量,因为常量的值不能改变,5++,++7这种形式是错误的,常量是不能改变的
b) 对于多个变量的运行结果,也不能使用,即不能用于表达式。如(x+y)++是不能使用的,因为结果是需要保存的,有多个变量的时候不能确定保存在何处。
c) 在表达式中,连续使同一变量进行自增或自减运算时,很容易出错,所以最好避免这种用法。
2. 关系运算符:(< <= == > >= !=)
<(小于) 例如a<b a小于b时返回真;否则返回假
<=(小于等于) 例如a<=b a小于等于b时返回真;否则返回假
>(大于) 例如a>b a大于b时返回真;否则返回假
>=(大于等) 例如a>=b a大于等于b时返回真;否则返回假
==(等于) 例如a==b a等于b时返回真;否则返回假
!=(不等) 例如a!=b a不等于b时返回真;否则返回假
3. 逻辑运算符:((! && ||)
! (非)运算量为真时,结果为假;运算量为假时,结果为真。例如:!(5>0)的结果为假.
&& (与运算)运算的两个量都为真时,结果才为真,否则为假。例如:
6>0 && 5>2由于6>0为真,5>2也为真,相与的结果也为真。
|| (或运算)运算的两个量只要有一个为真,结果就为真。 两个量都为假时,结果为假。例如: 3>0||4>7
由于3>0为真,相或的结果也就为真。
赋值运算符:(= 及其扩展)
简单赋值运算符=(等于)指定某个变量的值,例如:Z=m,将m变量的值放入Z变量。
复合赋值运算符(+= -= *= /= %= 《= 》= &= ^= |=)是由赋值运算符之前再加一个双目运算符构成的。
例如:x += 3 /* 等价于x=x+3 */
y *= x + 6 /* 等价于y=y*(x+6),而不是y=y*x+6 */
3.3.4 Arduino程序基本结构
Arduino程序的基本结构由setup()和loop()两个函数组成,其格式表示为:
void setup()
{
}
void loop()
{
}
setup()函数在程序开始时使用,并且程序只会执行一次。通常会在setup()函数中初始化变量、接口模式、启用库等,如配置I/O口状态和初始化串口。
loop()函数理在setup()函数之后执行,loop()函数里面的程序会不间断的执行。通常可以loop()函数中完成主函数功能。
3.3.5 常用函数
1.pinMode()
pinMode(port, mode)接字IO口模式定义函数,用在setup()函数里。 Port表示端口号0~13, mode表示输入模式(INPUT)或输出模式(OUTPUT)。
2.digitalWrite()
digitalWrite(port, value) 数字IO口电平定义函数,Port表示端口号0~13,value表示高电平(HIGH)或低电平(LOW)。
3.digitalRead()
digitalRead(port)数字IO口读取电平值函数。Port表示端口号0~13,value表示高电平(HIGH)或低电平(LOW)。
4.analogWrite()
analogWrite(port, value)给一个接口写入模拟值(PWM波)。port表示3, 5, 6, 9, 10, 11,value表示为0~255。
5.analogRead()
int analogRead(port) 从模拟IO读取值,port表示为0~5,这个方法将输入的0-5电压值转换为 0到1023间的整数值。
6.delay()
delay()延时函数,delay(1000)表示延时间1秒。
7.Serial.begin()
Serial.begin (speed)设置串行每秒传输数据的速率(波特率)。speed表示波特率:300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600或 115200。
8.Serial.read()
Int Serial.read() 读取持续输入的数据。
9.Serial.print()
Serial.print(data)从串行端口输出数据。)默认为十进制等于Serial.print(data,DEC)。
10.Serial.println()
Serial.println(data)从串行端口输出数据,并输出一个回车和一个换行符,取得的值与 Serial.print()一样。
3.3.6 控制结构
1.选择结构
(1)if语句
单分支语句:用来解决“根据一个条件就可以决定某个操作做还是不做”的问题。
if(表达式)
{
执行语句1;
}
图3-1 单分支结构流程图
双分支语句:用来解决“根据一个条件,从两个操作中选择一个操作来做”的问题。
if(表达式)
{
执行语句1;
}
Else
{
执行语句2;
}
图3-2 双分支结构流程图
多分支语句:用来解决“根据n个条件,从n+1个操作中选择一个操作来做”的问题。
if(表达式1)
{
执行语句1;
}
else if(表达式2)
{
执行语句2;
}
else if(表达式3)
{
执行语句3;
}
……
else
{
执行语句n;
}
图3-3 多分支结构流程图
功能描述:如果表达式1成立,则执行语句1,然后退出if……else语句,不执行下面的语句。否则,如果表达是2成立,则执行语句2,然后退出if……else语句,不执行下面的语句,依次类推,如果所有表达式都不成立,则执行else下的执行语句n。
(2)开关语句(switch语句)
当表达式的值与某个常量表达式的值相等并执行完其后的语句后,不想继续执行所有case后面的语句,在语句后面加上“break”,以跳出switch{ }。
形式:
switch(表达式)
{
case 常量表达式1:
语句1;
break;
case 常量表达式2:
语句2;
break;
……
default:
语句m;
break;
}
图3-4 多分支结构(开关语句)流程图
2.循环结构
(1)while语句(当型循环语句):当某个条件成立(条件值非0)时,反复执行某个操作。
形式:
While (表达式)
{
循环体语句;
}
图3-5 当型循环结构流程图
(2)do-while语句(直到型循环语句):反复执行某个操作,直到某个条件不成立(条件值位0)时结束循环。
形式:
do
{
循环体语句;
}
while (表达式)
图3-6 直到型循环结构流程图
(3)for语句(循环次数确定的循环语句):由循环的初值、终值和增量控制的有限次数循环。
形式:
for (表达式1;表达式2;表达式3)
{
循环体语句;
}
图3-7 for循环结构流程图