物联网设备最受欢迎的目的地是遥测收集。 迄今为止,云物联网服务的价格已经下降了很多,甚至普通普通用户也负担不起使用它们的费用。 今天,我们将讨论如何使用Lua语言从NodeMCU板向云发送数据。

注意:我们将继续阅读Hacker杂志的文章的完整版本系列。 作者的拼写和标点符号得以保存。
我请作者发言。
由于我在Microsoft技术堆栈中工作,因此我使用Azure功能和表存储来创建IoT解决方案的云部分,但是我的NodeMCU和Lua的PoC也可以与其他IoT解决方案的云提供商一起使用。
资讯
Expressif NodeMCU是最便宜的主板之一,带有Wi-Fi,微型USB和板载编程器。 它基于ESP8266模块。 第二代板的价格约为6-7美元。 您可以通过Arduino IDE使用该板。 此外,该板还支持一种称为Lua的脚本语言(从葡萄牙语翻译为“ Moon”)。
连接并配置设备
为了在Windows下识别设备,您需要从以下链接下载驱动程序: CP210x USB至UART Bridge VCP驱动程序
NodeMCU的标准串行端口速度为115'200bps。 您可以设置不同的速度,在第一次重置设备时,它将返回到115200。
将驱动程序设置为完全相同的速度非常重要:

韧体
初始固件很可能会遗漏某些东西,因此理想情况下请自己刷新设备。 有几种构建固件映像的方法。 使用云服务 , Docker映像或使用Linux的说明 。 我使用云服务收集了。 我也建议您使用此选项。
如果需要将数据发送到云,则选择所需的功能是SNTP,MQTT,HTTP(默认情况下已选择WiFi,计时器,文件,GPIO,网络,节点,UART)。 还需要在其他选项中标记为必需的TLS / SSL支持
带有bin文件的链接包含在邮件中。 更准确地说,即使有2个链接也会立即出现。 一个带有支持浮点运算的映像,第二个带有不支持浮点运算的映像。
刷新ESP8266之前,必须将其置于特殊模式。 板上有一个单独的FLASH按钮。 在加电期间按它或按复位会使设备进入引导加载程序模式。 如果您的电路板修改没有这样的按钮,则在闪烁之前,您需要将GPIO0连接至GND并按reset(此方法适用于ESP-12)。
可以使用PyFlasher实用程序来刷新固件。 名称中的Py表示应用程序是用Python编写的。 也有nodemcu-flasher ,但是很长一段时间没有更新。 我没有尝试过。
PyFlasher窗口如下所示:

根据使用的板卡选择闪光模式。 大多数基于ESP8266 ESP-12和ESP32模块的现代主板都使用DIO模式。 ESP8266 01到07适合更快的QIO模式。 ESP8285使用DOUT。
IDE设置
从ESPlorer链接下载免费的IDE。 另外,还有ZeroBrane Studio 。 我最喜欢ESPlorer,因此我将举一个使用它的示例。 ESPlorer用JAVA编写。 应用程序界面是

左侧是代码,设置和其他一些类似的功能。 右侧是监视窗口和设备管理命令。 打开应用程序,选择端口。 设置交换发生的速度(最有可能是115200),然后单击“打开”。

要预热,您可以运行一个简单的脚本,该脚本以内置的LED闪烁:
LED = 0 gpio.mode(LED, gpio.OUTPUT) function flash_led() gpio.write(LED, gpio.LOW) tmr.delay(500000) gpio.write(LED, gpio.HIGH) end tmr.alarm(1, 1000, tmr.ALARM_AUTO, flash_led)
如果您的板上没有内置LED(或者您完全厌倦了LED =闪烁的示例),那么您可以尝试执行一个更简单的脚本来显示该行:
print("Hello from Lua!")
创建.lua文件(例如test.lua)后,向其中添加代码并将其保存到磁盘,然后可以将其下载到设备中。 为此,请打开未打开的端口(“打开”按钮),然后单击“上载”按钮。 您可以在代码下方的按钮(左侧)中找到它。
下载文件后,您可以通过发送以下命令来执行该文件:
dofile("test.lua")
可以在显示器右下方的下部字段中手动输入命令。 或者,如果您不想输入任何文本,则可以单击“重新加载”按钮(右侧最右边的按钮)。 单击此按钮后,您将收到一个按钮列表,其中在板上加载了.lua文件。 单击带有文件名的按钮将启动该文件以执行。
如果要在打开主板后立即启动文件,请创建一个名为init.lua的文件。
配置云部分以使用设备
让我们离开设备一段时间,然后在云中创建它的两倍。 最近,可以使用新功能在Azure门户上直接创建双设备。 在名为“资源管理器”的IoT中心设置组中,您需要选择IoT设备并单击“ +添加”
要将设备连接到IoT中心,我们需要生成SAS(共享访问签名)。 为了生成SAS,使用了设备双键,可以使用一些辅助实用程序(设备资源管理器,iothub-explorer,用于Azure CLI 2.0的IoT扩展)来获得它。 但是最简单的方法是,通过转到IoT中心-> IoT设备,在Azure门户上的同一位置获取密钥。

SAS可以在设备上生成,也可以使用其任何在线服务生成。 如果您使用SDK,则它可以自动为您生成SAS(足以在代码中指定设备的双键)。

在特定的有限时间内由Web服务生成SAS令牌的方式稍微更安全。 虽然有一定的细微差别。 如果仅将设备名称发送到服务,则有人可以搜索名称以获取其他设备的令牌。 因此,为了使过程更加安全,我建议采用以下解决方案:让我们将设备双键的Azure哈希保存在设备上。 在服务代码中,在生成SAS之前,我们将检查哈希值是否与设备密钥的哈希值匹配。 因此,有可能仅知道设备名称和其密钥的哈希来获得SAS。
在设备上生成SAS的第一种方法更简单,更方便,但安全性稍差。 由于在获得对设备的访问权限之后,攻击者将能够获取密钥并自行生成SAS设备。 在第二种情况下,访问设备后,破解者将只能接收寿命有限的SAS令牌。
事实证明,如果黑客可以访问设备,那么这两种方法基本上都不理想。 即使使用VPN保护连接也无济于事。 在这种情况下,传输通道将受到保护,但是将设备拿到手中的人将可以访问该通道。 不幸的是,在NodeMCU,Arduino等设备上 无法将密钥/密码存储在任何安全存储中。 低成本物联网设备市场可能需要新的硬件功能。
为SAS生成创建Azure功能
作为一项在线服务,最容易使用Azure功能。 这些独特的片段可以立即在浏览器的Azure门户中编写。 开个玩笑,但您甚至可以通过智能手机进行编程。 当然,没有人禁止从Visual Studio创建和调试它们,然后才以编译形式将其发布到Azure。
该功能的任务是执行一些通常不是很复杂的操作。 根据微服务的想法,每个功能都可以做一件事,但这非常好(单责任原则)。
您可以通过填写简短的表格在门户上创建一个Azure Function应用

消费计划仅允许您为已落实的那些功能调用付费。 这是最便宜的选择。 目前,有100万个免费的功能调用。
请注意,随着功能的创建,还将创建辅助数据存储(存储)。
创建功能应用程序后,您可以创建功能本身。 在这种情况下,我们需要像Webhook + API这样的函数。 该功能可能对所有人开放(匿名访问),并且可能仅对特殊代码的所有者可用。 通过单击</>获取函数URL链接,可以从带有函数的窗口中获取代码:

函数可以用多种语言编写。 我更喜欢C#。
using System.Net; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Common.Security; using System.Globalization; using System.Security.Cryptography; using System.Text; public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) { string deviceid = req.GetQueryNameValuePairs() .FirstOrDefault(q => string.Compare(q.Key, "deviceid", true, CultureInfo.InvariantCulture) == 0).Value; string hash = req.GetQueryNameValuePairs() .FirstOrDefault(q => string.Compare(q.Key, "hash", true, CultureInfo.InvariantCulture) == 0).Value; if (String.IsNullOrEmpty(deviceid)) return req.CreateResponse(HttpStatusCode.BadRequest, "device id missing"); if (String.IsNullOrEmpty(hash)) return req.CreateResponse(HttpStatusCode.BadRequest, "hash missing"); var resourceUri ="ArduinoDemoHub.azure-devices.net/devices/"+deviceid; // taken from IoT Hub user with Connect devices rights (not from Device Explorer) var connectionString = "HostName=ArduinoDemoHub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=cuYBKc42lfJr4oSRGQGQ8IiKWxGQkLre7rprZDZ/ths="; var registryManager = RegistryManager.CreateFromConnectionString(connectionString); var device = await registryManager.GetDeviceAsync(deviceid); var key = device.Authentication.SymmetricKey.PrimaryKey; HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes("somerandomkeyKJBWyfy4gski")); var hashedkey = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(key))); if (hashedkey!=hash) return req.CreateResponse(HttpStatusCode.BadRequest, "wrong hash"); SharedAccessSignatureBuilder sasBuilder = new SharedAccessSignatureBuilder() { Key = key, Target = resourceUri, TimeToLive = TimeSpan.FromDays(Convert.ToDouble(7)) }; var SAS = sasBuilder.ToSignature(); return req.CreateResponse(HttpStatusCode.OK, SAS); }
我们创建project.json文件,并向其中添加以下内容:
{ "frameworks": { "net46":{ "dependencies": { "Microsoft.Azure.Devices": "1.4.1" } } } }
该代码使用到IoT中心的连接字符串。 可以将其与设备的连接字符串混淆。 为了让您不会感到困惑,让我提醒您在哪里得到它:

必须从具有设备连接权限的某些策略中获取连接字符串。
最好不要像我那样在代码中指定连接字符串本身。 我这样做仅仅是为了举例。 最好进入“应用程序设置”功能。

并在那里指定连接字符串。 之后,您可以使用以下方法从安全存储中“获取”它:
ConfigurationManager.ConnectionStrings["___"].ConnectionString
在设备上,我们需要保存哈希键。 但是首先,您需要对字符进行编码。 System.Web空间中的HttpUtility.UrlEncode将帮助我们解决此问题。
hashedkey = HttpUtility.UrlEncode(hashedkey);
我们将使用Get发送请求,但并非所有字符都可以作为参数值传递。
编写代码以将数据发送到云
我在Lua上编写了一些代码,将数据发送到云。 结果是一种PoC。 您可以使用它,并根据需要对其进行修改。
创建2个init.lua和SendDataToCloud.lua文件
第一个内容:
-- print('init.lua ver 1.2') wifi.setmode(wifi.STATION) print('set mode=STATION (mode='..wifi.getmode()..')') print('MAC: '..wifi.sta.getmac()) print('chip: '..node.chipid()) print('heap: '..node.heap()) -- Wifi station_cfg={} station_cfg.ssid="_SSID" station_cfg.pwd="___" station_cfg.save=false wifi.sta.config(station_cfg) wifi_status_codes = { [0] = "Idle", [1] = "Connecting", [2] = "Wrong Password", [3] = "No AP Found", [4] = "Connection Failed", [5] = "Got IP" } sntp_connect_status_codes = { [1] = "DNS lookup failed", [2] = "Memory allocation failure", [3] = "UDP send failed", [4] = "Timeout, no NTP response received" } -- Wi-fi ( ) tmr.alarm(6,1000, 1, function() if wifi.sta.getip()==nil then print("Waiting for IP address! (Status: "..wifi_status_codes[wifi.sta.status()]..")") else print("New IP address is "..wifi.sta.getip()) tmr.stop(6) -- NTP sntp.sync({'pool.ntp.org'}, function(sec, usec, server) print("Clock Synced: "..sec..", "..usec..", "..server) tls.cert.verify(false) -- dofile('SendDataToCloud.lua') end, function(error_code) print("Clock Sync Failed: "..sntp_connect_status_codes[error_code]) end, 1 -- ) end end )
如果连接成功,此文件将连接到网络并执行SendDataToCloud.lua文件中的代码。
您必须将Wi-Fi接入点的数据指定为station_cfg.ssid和station_cfg.pwd的值。
在以下文件中,您需要更改设备的名称和集线器的IoT(DEVICE和IOTHUB变量)。 funcurl变量包含SAS生成函数的地址和设备密钥的哈希(我们之前使用HttpUtility.UrlEncode对其进行编码)作为哈希参数的值
-- DEVICE = "LuaDevice" IOTHUB = "ArduinoDemoHub.azure-devices.net" PORT = 8883 USER = "ArduinoDemoHub.azure-devices.net/"..DEVICE.."/api-version=2016-11-14" telemetry_topic="devices/"..DEVICE.."/messages/events/" connected = false local headers = 'Content-Type: application/x-www-form-urlencoded\r\n'.. 'Accept: */*\r\n'.. 'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' funcurl = "https://arduinofunction.azurewebsites.net/api/GenerateSASFunction?code=Jn7j54PbR31BSRa0UZrDwp4ZEltjmWHmblG9zLo0Ne0tyGM7w/wQ7w==" funcurl = funcurl.."&hash=oJzykimyQsTPtzgJxYq90Xfqmw1rZTPTCH%2bJ5sSurKI%3d" funcurl = funcurl.."&deviceid="..DEVICE tmr.alarm(1,5000, 1, function() http.get(funcurl, headers, function(code, data, header) if (code < 0) then print("HTTP request failed") else sas = true print(code, data) if string.match(data, "Shared") then tmr.stop(1) SAS = string.sub(data,2,string.len(data)-1) print(SAS) connect(SAS) end end end) end) function connect(SAS) -- MQTT client = mqtt.Client(DEVICE, 240, USER, SAS) -- IoTHub MQTT print ("Connecting to MQTT broker. Please wait...") tmr.alarm(2,1000, 1, function() client:connect(IOTHUB, PORT, 1, -- Callback function(client) tmr.stop(2) print("Connected to MQTT: "..IOTHUB..":"..PORT.." as "..DEVICE) connected = true senddata() end, -- Callback function(client, reason) print("Error Connecting: "..reason) end ) end) end function senddata() math.randomseed(1) tmr.alarm(3, 1000, tmr.ALARM_AUTO, publish_data) -- , callback client:on("offline", function(client) print("MQTT Disconnected.") connected = false end) end -- function publish_data() if connected == true then somedata = math.random(1,100) -- payload = "{ \"deviceId\" : \""..DEVICE.."\",".. "\"iotdata\" :"..somedata.."}" -- client:publish(telemetry_topic, payload, 1, 0, function(client) print("Data published successfully.") end) end end
无需使用Azure SDK即可发送数据,因此您不仅可以使用此代码将数据发送到Azure。 有很多选择:AWS,Google Cloud IoT,IBM Watson IoT Platform。
该示例使用MQTT(消息队列遥测传输)协议。 这是专门为物联网设备设计的开放协议。 数据以JSON格式发送。 在实际项目中,从传感器获取数据的地方,在示例中会生成一个随机数。
在设备与IoT中心之间的握手过程中,服务器可以发送其证书,也可以请求设备证书。 如果您还记得的话,这是我们上一次使用Arduino设备时,我们用证书对其进行了刷新。 现在,一个代码接收器就足够了:
tls.cert.verify(false)
我们仅限于服务器将发送给我们的证书。
资讯
使用以下OpenSSL命令,您可能对集线器的证书内容感兴趣。
openssl s_client -showcerts -connect ArduinoDemoHub.azure-devices.net:8883
为了准备材料,使用了实验室,可以通过以下链接获得这些实验室: 发送设备到云(D2C)消息
该代码不是最新的,我不得不对其进行一些更新,但是总的来说,该链接可能有用。
从Arduino IDE使用NodeMCU
我不能忽略使用SDK的主题。 您的解决方案很好,但是SDK是已经调试,简化并且可以使用的相同代码。 关于如何配置和使用Arduino IDE与NodeMCU一起使用的几句话。
安装Arduino IDE之后,您需要转到文件-首选项菜单

并添加指向其他董事会经理的链接-在“其他董事会经理URL”字段中输入以下地址: http : //arduino.esp8266.com/versions/2.4.0/package_esp8266com_index.json
然后转到工具-董事会xxx-Boardx Manager菜单并安装ESP8266
安装库AzureIoTHub,AzureIoTUtility,AzureIoTProtocol_MQTT。 在示例(文件菜单-示例-AzureIoTProtocol_MQTT)中安装最后一个库后,您可以找到ESP8266的示例simplesample_mqtt。
该示例已准备就绪。 只需在iot_configs.h文件中填写变量值
我提到一个小减号。 与Lua相比,编译项目并下载到开发板需要花费很长时间。
在Azure云中保存数据
通过数据发送,一切都变得清晰了,但是将数据保存到云中的成本却不高。
将数据从IoT中心发送到数据库的最便宜的方法是Azure Functions。 最便宜的数据存储是Azure表存储。
有趣的是,当您创建Function App时,也会自动创建Storage,而该功能本身需要使用。 如果创建单独的存储库,则建议进行如下基本设置:

LSR复制是当前最便宜的选择。 自动创建绑定到功能的存储库时,将选择该选项。
现在,我们需要从IoT中心接收数据并将其写入存储。 对于这种情况,在创建功能时,“快速入门”窗口将无法为我们提供所需的选项。

因此,单击位于底部的自定义功能链接,然后选择IoT中心(事件中心)选项。
该窗口将为我们打开:

在其中,我们可以通过简单的选择(单击“新建”)来填写“事件中心”连接字段。 但是要指示事件中心名称,您需要转到IoT中心。 在集线器中,您需要转到端点(端点)并从那里获取与事件中心兼容的名称

让我们继续进行功能代码。 以下代码段从IoT中心接收数据并将其存储在表存储中:
#r "Microsoft.WindowsAzure.Storage" #r "Newtonsoft.Json" using Microsoft.Azure; // Namespace for CloudConfigurationManager using Microsoft.WindowsAzure.Storage; // Namespace for CloudStorageAccount using Microsoft.WindowsAzure.Storage.Table; // Namespace for Table storage types using Newtonsoft.Json; public static void Run(string myIoTHubMessage, TraceWriter log) { var e = JsonConvert.DeserializeObject<EventDataEntity>(myIoTHubMessage); log.Info($"C# IoT Hub trigger function processed a message: {myIoTHubMessage}"); CloudStorageAccount storageAccount = CloudStorageAccount.Parse ("DefaultEndpointsProtocol=https;AccountName=iotdatademostorage;AccountKey=JgStNcJvlQYeNsVCmpkHQUkWlZiQ7tJwAm6OCL34+lGx3XrR+0CPiY9RoxIDA6VSvMKlOEUrVWL+KWP0qLMLrw==;EndpointSuffix=core.windows.net"); CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); CloudTable table = tableClient.GetTableReference("iottable"); table.CreateIfNotExists(); EventDataEntity edata = new EventDataEntity("IOTpartition", Guid.NewGuid().ToString()); edata.DeviceId = e.DeviceId; edata.IotData = e.IotData; TableOperation insertOperation = TableOperation.Insert(edata); table.Execute(insertOperation); } public class EventDataEntity : TableEntity { public EventDataEntity(string pkey, string rkey) { this.PartitionKey = pkey; this.RowKey = rkey; } public EventDataEntity() { } public string DeviceId { get; set; } public int IotData { get; set; } }
如果要在实际项目中使用此代码,请不要忘记将连接字符串放在更安全的位置-在“应用程序”设置中(与第一个功能的连接字符串完全相同)。
连接字符串本身可以在名为访问键的设置项中获取:

使用免费的Azure Storage Explorer实用程序查看表内容
由于Azure表存储的成本非常低,并且Azure功能和IoT中心每月免费提供某些资源,因此每月整个解决方案的成本可以低于1美元。 当然,这取决于数据量。 为自己计数。 如今,1 GB的数据每月花费7美分,每百万笔交易您只需支付4美分。
资讯
使用任何提供商的云服务时,我始终建议您将金额最少的信用卡链接到您的帐户。 通过选择某种错误的设置,您付出的钱比预期的要多得多,这是很偶然的。
我们提醒您,这是《黑客》杂志上文章的完整版。 它的作者是Alexey Sommer 。
有用的材料
云应用架构指南
采用结构化方法开发云应用程序。 这本关于云计算体系结构的300页电子书讨论了无论选择哪种云平台都适用的体系结构,开发和实施指南。 本指南包括以下步骤:
- 为您的应用程序或解决方案选择正确的云应用程序架构样式;
- 选择适当的计算和数据存储技术;
- 实施10条开发原则以创建可伸缩,有弹性且可管理的应用程序;
- 遵循创建保证您的云应用程序成功的优质软件的五项原则;
- 使用针对您要解决的问题设计的设计模式。
→ 下载
Azure开发人员指南

在《 Azure开发人员指南》的此更新中,您将看到Azure软件平台的整套服务如何满足您的需求。 在这里,您将找到有关架构方法以及创建云应用程序时出现的最常见情况的信息。
→ 下载
Microsoft Azure基础
本书为云计算新手开发人员和IT专业人员提供了对关键Azure服务的重要见解。 包含分步演示,以帮助读者了解如何开始使用每个关键服务。 每章都是独立的,不需要为了理解任何特定章节而进行前几章的实际演示。
本书涵盖以下主题:
- Azure入门;
- Azure应用程序服务和Web应用程序;
- 虚拟机;
- 仓储服务;
- 资料库
- 其他Azure服务。
→ 下载
有用的链接