温度实时监测——ESP8266部分/固件库编译/SmartWifi/MQTT/LFS 篇一

本文主要对NodeMCU固件库以及SmartWifi等相关知识进行整理,并基于DS18B20形成温度监控传感器模块。

NodeMCU编译
首先clone NodeMCU源码

git clone -b dev https://github.com/nodemcu/nodemcu-firmware

在user_modules.h选择相应的模块,然后在user_config.h中设置相应的配置,大致看了一下除了smartconfig外,还有关于debug、I2C、LFS、TLS等的设置,包括此前涉及到的波特率、整数以及浮点的固件版本等都在这里面设置,由此可见之前对ESP8266调试lua代码的波特率的理解是有问题的,实际上还是在固件中就设置好了,正好是115200而已,要修改只能在这里修改
这两个文件都在app/include中

配置好之后,就可以进行编译了

sudo make clean
sudo make

编译过程需要gcc工具组件的支持,期间遇到了很多问题,也是花了不少功夫才结解决,以后有空详细学习整理一下编译相关的知识

编译完成后的二进制固件在bin文件夹里面,会包含两个文件0x00000.bin 和 0x10000.bin,具体这两个文件是什么,目前我还不是特别清楚,以后在看吧

将编译好的固件下载到ESP82666,还是使用之前的工具,即esptool

esptool.py --port /dev/ttyUSB0 write_flash 0x0000 0x00000.bin 0x10000 0x10000.bin

需要注意的是SmartConfig只有在dev版本中才有,一开始没注意到折腾了一会
在user_config.h中设置WIFI_SMART_ENABLE开启,完成上面的步骤后ESP8266中的固件就带有SmartConfig功能了

SmartWIFI

进入到SmartWIFI后,ESP8266模块会进入到监控所有数据包的模式,通过手机中的相应组件,就可以把SSID和密码等信息广播出去,这样ESP8266就可以接受到对应的wifi连接信息,由此完成wifi配置。
nodemcu中的smartwifi比较简单,示例如下:

wifi.setmode(wifi.STATION)
wifi.startsmart(0,
    function(ssid, password)
        print(string.format("Success. SSID:%s ; PASSWORD:%s", ssid, password))
        wifi_connect(ssid,password)
    end
)

通过上面的代码,结合手机端的设置,就可以获得SSID和密码。
手机端app可以用Espressif,下载链接https://github.com/espressifapp

NodeMCU的 SmartConfig功能默认是关闭的,需要下载源码进行编译固件完成,直接通过之前网络编译的方法好象没有那个选项

DS18B20
这个传感器上大学的时候就用过,当时使用单片机照着1-wire的总线协议进行位操作形成的驱动程序,当时废了不少劲才成功,现在直接就有现成的可以用,真是方便了好多

NodeMCU中DS18B20的驱动是用lua编的,并没有像其他很多传感器一样是用c编的,这样就需要使用lua文件调用来完成温度采集和转换了

在NodeMCU中下载ds18b20.lua,然后通过下面的程序就可以完成温度采集了

local t = require("ds18b20")
local pin = 3 -- gpio0 = 3, gpio2 = 4

local function readout(temp)
  if t.sens then
    print("Total number of DS18B20 sensors: ".. #t.sens)
    for i, s in ipairs(t.sens) do
      print(string.format("  sensor #%d address: %s%s",  i, ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(s:byte(1,8)), s:byte(9) == 1 and " (parasite)" or ""))
    end
  end
  for addr, temp in pairs(temp) do
    print(string.format("Sensor %s: %s °C", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(addr:byte(1,8)), temp))
  end

  -- Module can be released when it is no longer needed
  --t = nil
  package.loaded["ds18b20"] = nil
end

if not tmr.create():alarm(2000, tmr.ALARM_AUTO, function()
  t:read_temp(readout, pin, t.C)
end)
then
  print("whoopsie")
end 

需要注意的是,这个模块的正常使用需要OW也就是OneWire Module 的支持,OW模块是用c编写的,所以会在固件源码中进行编译。

nodemcu-tool上传和调试
在使用ESPlorer中总是会出现上传不成功或者出错等问题,有其实比较长的代码,不知道是不是我的配置问题,一直没有解决,所以就找了个替代的代码上传工具,即nodemcu-tool

这个工具的应用比较简单,感觉比ESPlorer上传代码还要方便一些
格式化/清除已经上传的文件:

nodemcu-uploader file format

上传代码:

nodemcu-tool upload --port=/dev/ttyUSB0 ds18b20.lua  init.lua  init_test.lua

执行文件:

nodemcu-tool run init_test.lua

列出已上传文件目录:

nodemcu-uploader file list

显示文件内容:

nodemcu-uploader file print init.lua

显示堆栈大小:

nodemcu-uploader node heap

重启:

nodemcu-uploader node restart

设置默认串口:

set SERIALPORT=/dev/ttyUSB0

删除特定文件:

nodemcu-uploader file remove foo.lua

Node命令
可在串口工具(例如cutecom)中使用的命令,结合上面的工具就可以实现Esplorer的大部分功能
重启:

=node.restart()

执行文件:

=dofile("init.lua")

显示堆栈大小:

=node.heap()

MQTT
之前就提到了可以使用MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)协议作为传感器的通信协议,它工作在TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,最早由IBM发布。

MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
a.“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
b.“至少一次”,确保消息到达,但消息重复可能会发生。
c.“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。

其中,有一个比较特别的是遗嘱消息Last Will, 即用于连接丢失后发送的消息。

MQTT支持加密登录功能,可以在服务器端设置用户密码进行登录。

本项目中MQTT分别订阅两种信息,一种用于数据传输,一种用于控制数据采集开关。
通过发送temp send open打开温度采集,发送temp send close关闭温度采集。
MQTT中需要加入处理连接错误的流程。
最终形成的MQTT客户端程序如下:

print("mqtt init")

node.flashindex("_init")()   --后续LFS添加的
node.flashindex("ds18b20")()

humi=0.01
tempinfo=""
--****************************************************************
local t = require("ds18b20")
local pin = 3 -- gpio0 = 3, gpio2 = 4
local function readout(temp)
  if t.sens then
    print("Total number of DS18B20 sensors: ".. #t.sens)
    for i, s in ipairs(t.sens) do
      print(string.format("  sensor #%d address: %s%s",  i, ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(s:byte(1,8)), s:byte(9) == 1 and " (parasite)" or ""))
    end
  end
  for addr, temp in pairs(temp) do
    print(string.format("Sensor %s: %s °C", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(addr:byte(1,8)), temp))
    tempinfo=string.format("DHT Temperature:%f;Humidity:%f\r\n",
                      temp,                      
                      humi                      
                      )
  end
end
--****************************************************************

ismqttconnected = false
temp_flag = false

clientid = "ESP8266-" .. node.chipid()

m = mqtt.Client(clientid, 10) 

m:lwt("/lwt", "offline", 0, 0)

m:on("connect", function(client) print ("connected") end)
m:on("offline", function(client) 
  print ("offline")
  ismqttconnected = false
  print ("mqtt will be reconnected for offline after 10s.")  
  tmr.create():alarm(10 * 1000, tmr.ALARM_SINGLE, do_mqtt_connect)
end)

m:on("message", function(client, topic, data)
  print(topic .. ":" )
  if data ~= nil then
    print(data)
  end

  if string.find(data, "temp send open", 1)  then
  print("temp_true")
  temp_flag = true
  tempsendtmr:start()
  end

  if string.find(data, "temp send close", 1)  then
  print("temp_false")
  temp_flag = true
  tempsendtmr:stop()
  end

end)

m:on("overflow", function(client, topic, data)
  print(topic .. " partial overflowed message: " .. data )
end)

function handle_mqtt_error(client, reason)
  print ("mqtt will be reconnected for error after 10s.")
  tmr.create():alarm(10 * 1000, tmr.ALARM_SINGLE, do_mqtt_connect)
end

function do_mqtt_connect()
m:connect("host_ip", 1883, false, function(client)  --这里是连接服务器的地址
  print("connected")
  ismqttconnected = true
  -- subscribe topic with qos = 0
  client:subscribe("/topic", 0, function(client) print("subscribe success") end)
  -- publish a message with data = hello, QoS = 0, retain = 0
  client:publish("/topic", "hello", 0, 0, function(client) print("sent a topic") end)
end,
function(client, reason)
  print("failed reason: " .. reason)
  handle_mqtt_error(client, reason)
end)
end

do_mqtt_connect()
--m:close()

--****************************************************************
tempsendtmr = tmr.create()
tempsendtmr:register(3000, tmr.ALARM_AUTO, function()
t:read_temp(readout, pin, t.C)
m:publish("/tempinfo", tempinfo, 0, 0, function(client) print("sent a tempinfo") end)
end)
tempsendtmr:stop()
--****************************************************************

LFS(Lua Flash Store)

按理说上面的问题都已经解决完了,但是当我把所有的程序弄到一起,然后运行的时候就悲剧了,出现了下面的错误

 PANIC: unprotected error in call to Lua API (error loading module 'ds18b20' from file 'ds18b20.lua':␍␊
[22:10:00:345] ⇥	not enough memory

看字面意思是RAM不够用了,可能是因为使用了18b20的Lua模块后,RAM需求增加较多导致的

这样就请出了这一节的主角——LFS,下面是Nodemcu关于LFS的介绍
The Lua Flash Store (LFS) patch modifies the Lua RTS to support a modified Harvard architecture by
allowing the Lua code and its associated constant data to be executed directly out of flash-memory
(just as the NodeMCU firmware is itself executed). This now allows NodeMCU Lua developers to create
Lua applications with up to 256Kb Lua code and read-only (RO) constants executing out of flash,
with all of the RAM is available for read-write (RW) data.

有图NodeMCU所执行的Lua程序都是在RAM中运行的(包括代码和数据),这样所执行的代码大小就比较有限,因此如果如果可以将Lua代码写入Flash然后直接执行,那么就可以大大提高Lua程序大小
也就是说LFS使得Lua代码及其常量数据可以直接Flash中执行,就像NodeMCU的固件一样,这样就可以最大允许执行256Kb的Lua代码。

主要通过一下四步

1.LFS的使用需要在编译固件前在这里app/include/user_config.h 通过#define LUA_FLASH_STORE 0x0 调整LFS空间大小
注意需要是4Kb的倍数

2.固件映像(包含需要通过LFS运行的Lua代码)需要通过luac.cross进行编译,这个文件就在NodeMCU固件程序的主目录下
通过下面的命令进行编译

luac.cross -o lfs.img -f *.lua

上面的命令会编译当前文件夹中的所有lua文件,编译中下面这两个文件是必须要包括的

lua_examples/lfs/_init.lua. LFS helper routines and functions.
lua_examples/lfs/dummy_strings.lua. Moving common strings into LFS.

针对本项目,需要编译的文件为

luac.cross -o lfs.img -f _init.lua dummy_strings.lua ds18b20.lua

3.生成的lfs.img需要通过与Lua代码一样的方式上传至ESP8266

为了使用这些固件映像,需要执行载入命令

 node.flashreload("lfs.img")

执行完上述命令后ESP8266会自动进行一次重启

4.此后,还需要在调用具体的Lua模块之前执行下面的命令对模块进行注册

node.flashindex("_init")()
node.flashindex("ds18b20")()

这样就可以像普通模块一样执行相应的Lua代码了

按照上述步骤重新运行程序,发现正常了。

再次查看堆栈

[23:26:51:849] =node.heap()
[23:26:51:861] 26744

其它
对于NodeMCU的组成和原理,后面有机会研究,初步看了一下,好像是基于NON-OS SDK和SPIFFS实现的lua语言编程

NON-OS SDK是ESP8266的非操作系统固件库,从名字中就可以看出来这个固件库不是基于操作系统的,而是基于事件驱动的,提供了很多核心功能API,例如数据接收/发送,TCP/IP功能,硬件接口等,与之对应的还有一个ESP8266_RTOS_SDK。

SPIFFS 是一个用于闪存的文件系统,SPI就是SPI接口,这个文件系统用于简单的场景需求,对资源要求较少,因此适用的容量也比较小,而且不支持文件夹功能。

后面有机会再看吧,这块就先到这里了。

完。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值