4G Environmental Monitoring Node Based on Hezhou Air700E (temperature, humidity, barometric pressure, etc.), uploads to Alibaba Cloud IoT Platform via MQTT.
Introduction
The Hezhou Air700E 4G module reads sensors (temperature & humidity, barometric pressure, etc.) and uploads the data to the Alibaba Cloud IoT platform through the MQTT protocol. Data is simultaneously shown on a 0.96-inch OLED screen using the U8g2 graphics library.
An ESP32-C3 Wi-Fi module subscribes to the data published by the 4G node via MQTT and displays it on an LCD screen using the LVGL graphics library.
This is one of my course projects, done casually and not very refined.
Project Requirements: Design and build a wireless communication system. Combine knowledge learned last semester to collect local temperature, humidity / ultrasonic distance measurements and transmit them wirelessly to a host/receiver, then display relevant information on an LCD at the receiver. Specific requirements:
⑴ Bluetooth wireless transmission system design & implementation
⑵ Wi-Fi wireless transmission system design & implementation
⑶ ZigBee wireless transmission system design & implementation
⑷ GPRS/GSM wireless transmission system design & implementation
(5) 4G wireless transmission system design & implementation
(6) NB-IoT/LoRa wireless transmission system design & implementation
Hint: Use 8/16/32-bit processors as the main controller, select according to the comprehensive content chosen by your group. Complete at least 2 sub-items: choose one from (1)(2)(3), and one from (4)(5)(6).
Both Air700E and ESP32-C3 are developed with the LuatOS system + Lua scripts.
The LuatOS firmware was generated using Hezhou’s cloud build service.
The 4 MByte flash on the ESP32-C3 was replaced by an 8 MByte chip; the firmware exceeds 4 MB because LVGL and several fonts were added.
Alibaba Cloud student discount: https://www.aliyun.com/daily-act/ecs/activity_share?userCode=jdjc69nf
Note: Because Alibaba Cloud IoT Platform does not allow devices to subscribe to each other’s topics—only to their own—you must add a rule under Message Routing → Cloud Product Flow to forward messages from the 4G node to a topic of the Wi-Fi node.
Open-source project on LCSC: https://oshwhub.com/zeruns/wen-shi-du-cai-ji-4g-shang-chuan
Electronic / MCU tech group: 2169025065
Hezhou Air700E Introduction
Air700E is an LTE Cat.1 bis communication module launched by Hezhou Communication, built on the Yixin EC618 platform, supporting LTE 3GPP Rel.13. The module retains only LTE TDD bands, matching China Mobile’s mainstream bands, with ultra-small packaging and extreme cost, meeting miniaturization and low-cost needs.
Key features:
- Single 1.8/3.3 V USIM interface
- 1.8/3.3 V configurable UART
- USB 2.0
- Remote OTA firmware upgrade
- PSM digital voice interface
- Multiple development modes: USB networking, standard AT, open-CPU secondary development (LuatOS, C-SDK), etc.
Air700E integrates rich network protocols, multiple industrial-standard interfaces, and supports various drivers and software functions (USB drivers for Windows 7/8/8.1/10, Linux, Android, etc.), greatly expanding its M2M applications such as CPE, routers, data cards, tablets, automotive, security, and industrial PDAs.
Air700E specifications:
LTE-TDD bands: B34/B38/B39/B40/B41
LTE-TDD data rates:
- Ratio 2: max 8 Mbps (DL) / 2 Mbps (UL)
- Ratio 1: max 6 Mbps (DL) / 4 Mbps (UL)
Interfaces:
- 1× USB 2.0 high-speed (up to 480 Mbps)
- 1× 1.8 V/3.0 V (U)SIM
- 1× NETLIGHT (NET_STATUS)
- 1× digital I²S, external codec support
- 3× UART (main, general, debug)
- PWRKEY (active low)
- 2× ADC
- 13× GPIO + 2× interrupt input
- 1× I²C
Hezhou ESP32-C3 Introduction
The CORE ESP32 core board is designed around Espressif’s ESP32-C3, measuring only 21 mm × 51 mm with castellated edges for easy use in various scenarios. The board supports UART, GPIO, SPI, I²C, ADC, PWM, etc., selectable as needed.
Hardware resources:
- Size 21 mm × 51 mm
- 1× SPI FLASH, 4 MB on board, up to 16 MB supported
- 2× UART (UART0~UART1), UART0 for download
- 5× 12-bit ADC, max 100 kSPS
- 1× low-speed SPI, master mode
- 1× I²C controller
- 4× PWM, any GPIO usable
- 15× GPIO pins, multiplexed
- 2× onboard LEDs
- 1× reset key + 1× BOOT key
- 1× USB-to-TTL download/debug port
- 2.4 GHz PCB onboard antenna
Photos
Demo video: https://www.bilibili.com/video/BV1MH4y1B7Df/
Overall View
4G Node
Wi-Fi Node
Connecting to Wi-Fi:
After Wi-Fi connected, connecting to MQTT broker and syncing NTP time:
Displaying data collected by 4G node:
Showing temperature & humidity curve:
IoT Platform
Data shown on Alibaba Cloud IoT Platform:
Schematics
4G Node
Wi-Fi Node
PCB
4G Node
The 4G module footprint here is not quite correct.
Top layer:
Bottom layer:
Wi-Fi Node
Top layer:
Bottom layer:
Code
I won’t detail how to download the code; please refer to the official docs.
Air700E docs: https://doc.openluat.com/wiki/44?wiki_page_id=4730
LVGL for LuatOS manual: https://url.zeruns.com/7z7fN
ESP32-C3 docs: https://url.zeruns.com/497AP
Lua tutorial: https://url.zeruns.com/Pc4PA
4G Node
Firmware download: https://url.zeruns.com/2G3K7
main.lua:
-- LuaTools needs PROJECT and VERSION
PROJECT = "Environmental Monitor Node"
VERSION = "1.1.0"
-- import required libraries (written in lua), built-in libs need no require
sys = require("sys")
aht10 = require "aht10"
bmx = require "bmx"
-- add hardware watchdog to prevent lock-ups
if wdt then
wdt.init(9000) -- init watchdog 9 s
sys.timerLoopStart(wdt.feed, 3000) -- feed every 3 s
end
print(_VERSION)
log.info(mobile.ipv6(true)) -- enable ipv6
local ProductKey = "xxxxxx" -- product key
local DeviceName = "xxxxxx" -- change to your device name
local DeviceSecret = "xxxxxx" -- change to your device secret
local client_id, user_name, password = iotauth.aliyun(ProductKey, DeviceName, DeviceSecret) -- generate MQTT triplet
log.info("MQTT params", client_id, user_name, password)
local softI2C = i2c.createSoft(29, 31, 2) -- init software I²C
local aht10_data, bmx_data
local RH, Temp, Press, High, distance, BAT_Voltage, CPU_T = 0,0,0,0,0,0,0 -- averages: humidity, temp, pressure, altitude, distance, battery voltage, CPU temp
local RH_C, Temp_C, Press_C, High_C, distance_C, BAT_Voltage_C, CPU_T_C = 0,0,0,0,0,0,0 -- current values
local AHT10_flag, BMP180_flag, US100_flag, BAT_Voltage_flag, CPU_T_flag = false,false,false,false,false
all_data = {params = {CurrentVoltage = 0, CurrentTemperature = 0, CurrentHumidity = 0, Atmosphere = 0, Altitude = 0, DetectDistance = 0, CPUTemperature = 0}}
function US_100() -- read US-100 ultrasonic distance module
uart.write(1, string.char(0x55)) -- send hex 0x55
sys.wait(50) -- delay 50 ms
local hData, lData = string.byte(uart.read(1, 2), 1, 2) -- read 2 bytes
uart.rxClear(1) -- clear rx buffer
if hData and lData then -- check data received
local Distance_mm = tonumber((hData * 256) + lData) -- high 8 bits shifted left + low 8 bits
if Distance_mm > 4500 then -- out of range
Distance_mm = 4500
end
-- log.info("Ultrasonic: "..Distance_mm.."mm") -- log distance
return (Distance_mm / 10) -- return cm
end
end
-- https://blog.zeruns.com
sys.taskInit(function() -- create a task to read sensors
sys.wait(500) -- delay 500 ms
aht10.init(softI2C) -- init AHT10, pass i2c_id
bmx.init(softI2C) -- init BMP180, pass i2c_id
uart.setup(1, 9600, 8, 1, uart.NONE) -- init uart1
adc.open(0) -- open adc channel 0
adc.open(adc.CH_CPU)-- open ADC channel - CPU internal temp
-- adc.setRange(adc.ADC_RANGE_3_8) -- enable ADC0,1 divider, range 0~3.8 V
sys.wait(500) -- delay 500 ms
local RH_sum, Temp_sum, Press_sum, High_sum, distance_sum, BAT_Voltage_sum, CPU_T_sum = 0,0,0,0,0,0,0 -- sums for averaging
local avg_count, avg_count2, avg_count3, avg_count4, avg_count5 = 0,0,0,0,0
while 1 do
aht10_data = aht10.get_data() -- read AHT10
bmx_data = bmx.get_data() -- read BMP180
distance_C = US_100() -- read US-100
-- battery voltage sampling, divider 4.7 k / 10 k
BAT_Voltage_C = ((adc.get(0) / 1000 / 1007) * 1473) -- read AD0, calc battery voltage
CPU_T_C = adc.get(adc.CH_CPU) / 1000 -- read CPU temp
-- AHT10 temp & humidity averaging
if aht10_data.RH and aht10_data.T then -- check AHT10 data valid
all_data.params.CurrentTemperature = aht10_data.T
all_data.params.CurrentHumidity = aht10_data.RH * 100
if avg_count < 6 then -- accumulate for average
RH_C, Temp_C = aht10_data.RH * 100, aht10_data.T -- current values
RH_sum = RH_C + RH_sum
Temp_sum = Temp_C + Temp_sum
avg_count = avg_count + 1
elseif avg_count == 6 then
RH = RH_sum / 6 -- average humidity
Temp = Temp_sum / 6 -- average temperature
log.info("AHT10_data.RH: "..(string.format("%.2f", RH)).." %", " AHT10_data.T: "..(string.format("%.2f", Temp)).." ℃") -- log
AHT10_flag = true -- data ready flag
sys.publish("MQTT_P") -- publish: data ready, MQTT can upload
avg_count, RH_sum, Temp_sum = 0,0,0 -- reset
end
end
```--BMP180 pressure and altitude averaging
if bmx_data.press and bmx_data.high then
all_data.params.Atmosphere = bmx_data.press
all_data.params.Altitude = bmx_data.high
if avg_count2 < 10 then --Check if less than 10, accumulate for average
Press_C,High_C = bmx_data.press, bmx_data.high --Read current pressure and altitude
Press_sum = Press_C + Press_sum
High_sum = High_C + High_sum
avg_count2 = avg_count2 + 1
elseif avg_count2 == 10 then
Press = Press_sum / 10
High = High_sum / 10
log.info("BMP180_data.press: "..(string.format("%.2f", Press)).." hPa"," BMP180_data.high: "..(string.format("%.2f", High)).." m")
BMP180_flag = true
sys.publish("MQTT_P")
avg_count2,Press_sum,High_sum=0,0,0
end
end
-- US-100 ultrasonic ranging distance averaging
if distance_C then --Check if data was read
all_data.params.DetectDistance = distance_C
if avg_count3 < 3 then
distance_sum = distance_C + distance_sum
avg_count3 = avg_count3 + 1
elseif avg_count3 == 3 then
distance = distance_sum / 3
log.info("US-100: "..(string.format("%.1f", distance)).." cm")
US100_flag = true
sys.publish("MQTT_P")
avg_count3,distance_sum = 0,0
end
end
-- Battery voltage
if BAT_Voltage_C then --Check if data was read
all_data.params.CurrentVoltage = BAT_Voltage_C
if avg_count4 < 20 then
BAT_Voltage_sum = BAT_Voltage_C + BAT_Voltage_sum
avg_count4 = avg_count4 + 1
elseif avg_count4 == 20 then
BAT_Voltage = BAT_Voltage_sum / 20
log.info("BAT_Voltage: "..(string.format("%.2f", BAT_Voltage)).." V")
BAT_Voltage_flag = true
sys.publish("MQTT_P")
avg_count4,BAT_Voltage_sum = 0,0
end
end
-- CPU temperature
if CPU_T_C then --Check if data was read
all_data.params.CPUTemperature = CPU_T_C
if avg_count5 < 20 then
CPU_T_sum = CPU_T_C + CPU_T_sum
avg_count5 = avg_count4 + 1
elseif avg_count5 == 20 then
CPU_T = CPU_T_sum / 20
log.info("CPU_T: "..(string.format("%.2f", CPU_T)).." ℃")
CPU_T_flag = true
sys.publish("MQTT_P")
avg_count5,CPU_T_sum = 0,0
end
end
sys.wait(500)
end
end)
--[[sys.taskInit(function() --Create a task, ultrasonic ranging, every 1.5 seconds
uart.setup(1, 9600, 8, 1, uart.NONE) --Initialize serial port 1
sys.wait(1000)
while 1 do
distance = US_100() --Read US-100 data
if distance then --Check if data was read
log.info("US-100: "..(string.format("%.1f", distance)).." cm")
US100_flag = true
sys.publish("MQTT_P")
end
sys.wait(1500)
end
end)]]
sys.taskInit(function() --Create a thread, MQTT initialization and data reporting
mqttc = mqtt.create(nil, "a1sJbDQiEqr.iot-as-mqtt.cn-shanghai.aliyuncs.com", 1883,true,true)
mqttc:auth(client_id, user_name, password)
mqttc:keepalive(120) -- Default 240s
mqttc:autoreconn(true, 3000) -- Auto-reconnect mechanism
mqttc:on(function(mqtt_client, event, data, payload)
if event == "conack" then
sys.publish("mqtt_conack")
log.info("mqtt", "mqtt connected")
mqtt_client:subscribe("/sys/"..ProductKey.."/"..DeviceName.."/thing/service/property/set")
elseif event == "recv" then
log.info("mqtt", "message received", data, payload)
local mqtt_date = json.decode(payload)
elseif event == "sent" then
log.info("mqtt", "sent", "pkgid", data)
end
end)
mqttc:connect()
while true do
sys.waitUntil("MQTT_P", 3000)
if AHT10_flag then
local json_str = json.encode({params = {CurrentTemperature = Temp, CurrentHumidity = RH}}, "2f") -- Convert data to JSON format, keep 2 decimal places
mqttc:publish("/sys/"..ProductKey.."/".. DeviceName.."/thing/event/property/post", json_str) -- MQTT report property
mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f")) -- MQTT report property
AHT10_flag = false
end
if BMP180_flag then
local json_str = json.encode({params = {Atmosphere = Press, Altitude = High}}, "2f") -- Convert data to JSON format, keep 2 decimal places
mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str) -- MQTT report property
mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f")) -- MQTT report property
BMP180_flag = false
end
if US100_flag then
local json_str = json.encode({params = {DetectDistance = distance}}, "1f") -- Convert data to JSON format, keep 2 decimal places
mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str) -- MQTT report property
mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f")) -- MQTT report property
US100_flag = false
end
if BAT_Voltage_flag then
local json_str = json.encode({params = {CurrentVoltage = BAT_Voltage}}, "2f") -- Convert data to JSON format, keep 2 decimal places
mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str) -- MQTT report property
mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f")) -- MQTT report property
BAT_Voltage_flag = false
end
if CPU_T_flag then
local json_str = json.encode({params = {CPUTemperature = CPU_T}}, "2f") -- Convert data to JSON format
mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str) -- MQTT report property
mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f")) -- MQTT report property
CPU_T_flag = false
end
end
end)
-- https://blog.zeruns.com
sys.taskInit(function()
u8g2.begin({ic = "ssd1306",direction = 0,mode="i2c_hw",i2c_id=1,i2c_speed = i2c.FAST}) -- direction optional 0 90 180 270
u8g2.ClearBuffer() --Clear screen
u8g2.SetFont(u8g2.font_opposansm10) --Switch font
u8g2.DrawUTF8("IMEI:", 0, 16)
u8g2.DrawUTF8(mobile.imei(), 0, 32)
u8g2.SendBuffer()
--ntp sync time
--socket.sntp()
socket.sntp({"ntp.aliyun.com","ntp1.aliyun.com","ntp2.aliyun.com"}) --sntp custom server address
--socket.sntp(nil, socket.ETH0) --sntp custom adapter number
sys.subscribe("NTP_UPDATE", function()
log.info("sntp", "time", os.date())
sys.publish("NTP_OK")
end)
sys.subscribe("NTP_ERROR", function()
log.info("socket", "sntp error")
socket.sntp()
end)
sys.wait(1000)
sys.waitUntil("NTP_OK", 3000)
u8g2.ClearBuffer() --Clear screen
u8g2.SetFont(u8g2.font_opposansm10) --Switch font
u8g2.DrawUTF8(os.date("%Y-%m-%d"), 0, 16) --Display time
u8g2.DrawUTF8(os.date("%H:%M:%S"), 0, 32) --Display time
local IP = socket.localIP() --Display IP address
if IP then
u8g2.DrawUTF8("IP:"..IP, 0, 63)
end
u8g2.SendBuffer()
sys.wait(2500)
while true do
u8g2.ClearBuffer() --Clear screen
u8g2.SetFont(u8g2.font_sarasa_m10_ascii)
if Temp_C then
u8g2.DrawUTF8("T:"..(string.format("%.2f", Temp_C)).."°C", 0, 16)
end
if RH_C then
u8g2.DrawUTF8("RH:"..(string.format("%.2f", RH_C)).."%", 0, 32)
end
if Press_C then
u8g2.DrawUTF8("P:"..(string.format("%.2f", Press_C)).."hPa", 0, 48)
end
if distance_C then
u8g2.DrawUTF8("D:"..(string.format("%.1f", distance_C)).."cm", 0, 63)
end
if High_C then
u8g2.DrawUTF8("H:"..(string.format("%.2f", High_C)).."m", 61, 16)
end
if BAT_Voltage_C then
u8g2.DrawUTF8("B:"..(string.format("%.2f", BAT_Voltage_C)).."V", 66, 32)
end
if CPU_T_C then
u8g2.DrawUTF8("CPUT:"..(string.format("%d", CPU_T_C)).."°C", 63, 63)
end
u8g2.SendBuffer() --Update display data to screen
sys.wait(200)
end
end)```lua
sys.taskInit(function() --Create a thread, LED blinks every 500 ms
gpio.setup(27, 0) -- Set gpio27 as output, initialize level to low, use hardware default pull configuration
while true do
gpio.toggle(27) --Toggle gpio level
sys.wait(500)
end
end)
--[[
Supported fonts
["unifont_t_symbols","open_iconic_weather_6x_t","opposansm8","opposansm10","opposansm12","opposansm16","opposansm20",
"opposansm24","opposansm32","sarasa_m8_ascii","sarasa_m10_ascii","sarasa_m12_ascii","sarasa_m14_ascii",
"sarasa_m16_ascii","sarasa_m18_ascii","sarasa_m20_ascii","sarasa_m22_ascii"]
]]
-- User code ended---------------------------------------------
-- Always end with this line
sys.run()
-- Do NOT add any statements after sys.run()!!!!!
WiFi Node
Firmware download address: https://url.zeruns.com/a7eXJ
main.lua file:
-- LuaTools requires PROJECT and VERSION
PROJECT = "Data Display Node"
VERSION = "1.0.5"
-- sys library is standard
_G.sys = require("sys")
require("sysplus")
--Add hardware watchdog to prevent program freeze
wdt.init(9000)--Initialize watchdog to 6s
wdt_timer_id = sys.timerLoopStart(function()
wdt.feed()
end, 2000) --Feed dog every 2s
local ProductKey= "xxxxxx" --Product key
local DeviceName= "xxxxxx" --Change to your own device name
local DeviceSecret = "xxxxxx" --Change to your own device secret
local client_id, user_name, password = iotauth.aliyun(ProductKey, DeviceName ,DeviceSecret) --Generate MQTT triplet
log.info("MQTT parameters", client_id, user_name, password)
local local_CPU_T_C = 0;
local all_data = {params = {CurrentVoltage = 0,CurrentTemperature = 0, CurrentHumidity = 0,
Atmosphere = 0, Altitude = 0,DetectDistance = 0,CPUTemperature = 0}};
local key1_flag,key2_flag = false,false
sys.taskInit(function() -- Create a thread
log.info("wlan", "wlan_init:", wlan.init()) -- Initialize wlan
wlan.setMode(wlan.STATION) -- Set wlan mode to STATION
wlan.connect("Mate 40 Pro", "123456789", 1) -- Connect to wlan network
local result, data = sys.waitUntil("IP_READY",6000)-- Wait for IP_READY event
log.info("wlan", "IP_READY", result, data) -- Print IP_READY event result and data
if result then
socket.setDNS(socket.STA, 1, "114.114.114.114")
log.info("wlan", "info", json.encode(wlan.getInfo())) -- Print wlan network details
sys.publish("WiFi_connected_OK",data) -- Publish message, WiFi connected successfully
--ntp sync time
--socket.sntp()
socket.sntp({"ntp.aliyun.com","time.windows.com","ntp.tencent.com","ntp2.aliyun.com"}, socket.STA) --sntp custom server address
--socket.sntp(nil, socket.STA) --sntp custom adapter number
-- Subscribe to NTP_UPDATE event
sys.subscribe("NTP_UPDATE", function()
sys.publish("NTP_OK") -- Publish message, NTP time sync successful
log.info("sntp", "time", os.date()) -- Print system time
end)
sys.subscribe("NTP_ERROR", function()
log.info("socket", "sntp error")
socket.sntp()
end)
else
while true do
sys.wait(1000)
end
end
adc.open(adc.CH_CPU)-- Open ADC channel - CPU internal temperature channel
local CH_CPU = adc.get(adc.CH_CPU) / 1000 -- Read CPU temperature
if CH_CPU then -- Check if data is valid
local_CPU_T_C = CH_CPU
end
mqttc = mqtt.create(nil, "a1sJbDQiEqr.iot-as-mqtt.cn-shanghai.aliyuncs.com", 1883,true) -- Create MQTT client
mqttc:auth(client_id, user_name, password) -- Perform MQTT authentication
mqttc:keepalive(60) -- Set MQTT keepalive time, default is 60 seconds
mqttc:autoreconn(true, 3000) -- Enable auto-reconnect mechanism
mqttc:on(function(mqtt_client, event, data, payload)-- Define MQTT event callback function
if event == "conack" then -- When connection acknowledgment event (conack) is received
sys.publish("mqtt_conack") -- Publish a mqtt_conack message
log.info("mqtt", "mqtt connected") -- Print connection success info
mqtt_client:subscribe("/sys/"..ProductKey.."/"..DeviceName.."/thing/service/property/set") -- Subscribe to specified topic
mqtt_client:subscribe("/"..ProductKey.."/"..DeviceName.."/user/get")
elseif event == "recv" then -- When message received event (recv) occurs
if data == "/"..ProductKey.."/"..DeviceName.."/user/get" then -- Check received topic
all_data = json.decode(payload) -- Parse message content as JSON
mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", payload) -- MQTT report properties
else
log.info("mqtt", "message received", data, payload) -- Print received message info
local mqtt_date = json.decode(payload) -- Parse message content as JSON
end
elseif event == "sent" then -- When message sent event (sent) occurs
--log.info("mqtt", "sent", "pkgid", data)-- Print sent message info
end
end)
mqttc:connect()
while true do
local CH_CPU = adc.get(adc.CH_CPU) / 1000 -- Read CPU temperature
if CH_CPU then -- Check if data is valid
CPU_T_C = CH_CPU
end
gpio.toggle(12) --Toggle gpio level
sys.wait(500)
end
end)
sys.taskInit(function() -- Create a thread
spi.setup(2, 7, 0, 0, 8, 80 * 1000 * 1000, spi.MSB, 1, 1) -- Initialize SPI
log.info("lcd.init", lcd.init("st7789",{
port = 2, -- spi port
-- pin_cs = 7, -- SPI chip select
pin_dc = 8, -- lcd data/command select pin
-- pin_pwr = 18, -- lcd backlight pin optional, can be omitted
pin_rst = 6, -- lcd reset pin
direction = 2, -- lcd screen direction 0:0° 1:180° 2:270° 3:90°
w = 320, -- lcd horizontal resolution
h = 172, -- lcd vertical resolution
xoffset = 0, -- x offset (varies by screen ic and orientation)
yoffset = 34; -- y offset (varies by screen ic and orientation)
}))
log.info("lvgl", lvgl.init()) --- Initialize LVGL
local style_screen_label_main = lvgl.style_create() -- Create a style object named style_screen_label_main
lvgl.style_set_text_font(style_screen_label_main, lvgl.STATE_DEFAULT, lvgl.font_get("opposans_m_16")) -- Set default font for label
```local Init_scr = lvgl.obj_create(nil, nil) -- create a screen object
lvgl.scr_load(Init_scr) -- load the screen object and display it on the monitor
local spinner = lvgl.spinner_create(Init_scr, nil) -- create a spinner object
lvgl.obj_set_size(spinner, 100, 100) -- set the size of the spinner object
lvgl.obj_align(spinner, nil, lvgl.ALIGN_CENTER, 0, -20) -- center-align the spinner object
local label_WiFiConnecting = lvgl.label_create(Init_scr, nil) -- create a label object
lvgl.label_set_recolor(label_WiFiConnecting, true) -- enable label recolor, i.e. auto-update color when text changes
lvgl.obj_add_style(label_WiFiConnecting, lvgl.LABEL_PART_MAIN, style_screen_label_main) -- assign style to the main part of the label
lvgl.label_set_text(label_WiFiConnecting, "WiFi connecting...") -- set label text
lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- center-align label text
lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 46) -- center-align the label object
local result, ip_data = sys.waitUntil("WiFi_connected_OK", 5000) -- wait for WiFi connection success, 5 s timeout
if result == false then
lvgl.label_set_text(label_WiFiConnecting, "#ff0000 WiFi connection failed!#\\nPlease restart the device or check the network") -- set label text (WiFi failed)
lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- center-align label text
lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 51) -- center-align the label object
while true do
sys.wait(1000) -- loop wait to prevent further execution
end
end
lvgl.label_set_text(label_WiFiConnecting, "WiFi connected!") -- set label text (WiFi success)
lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- center-align label text
lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 51) -- center-align the label object
sys.wait(500) -- wait 500 ms
lvgl.obj_del(label_WiFiConnecting) -- delete the label object
lvgl.obj_align(spinner, nil, lvgl.ALIGN_CENTER, 0, 0) -- center-align the spinner object
local label_Init_log = lvgl.label_create(Init_scr, nil) -- create a label object
lvgl.label_set_recolor(label_Init_log, true) -- enable label recolor
lvgl.label_set_align(label_Init_log, lvgl.LABEL_ALIGN_LEFT) -- left-align label text
lvgl.obj_align(label_Init_log, nil, lvgl.ALIGN_IN_TOP_LEFT, 10, 5) -- align label to top-left corner
local Init_log = "#0000ff Init log: #\\nWiFi connected!\\n" -- init log message
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
Init_log = Init_log.."IP: "..ip_data.."\\nConnecting to MQTT server...\\n" -- update log
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
if sys.waitUntil("mqtt_conack", 10000) == false then
Init_log = Init_log.."#ff0000 MQTT server connection failed!\\nPlease restart the device!#\\n" -- update log (MQTT failed)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
mqttc:disconnect() -- disconnect MQTT
mqttc:close() -- close MQTT
while true do
sys.wait(1000) -- loop wait
end
end
Init_log = Init_log.."MQTT server connected!\\n" -- update log (MQTT success)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
Init_log = Init_log.."Syncing NTP time...\\n" -- update log
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
local result = sys.waitUntil("NTP_OK", 3500) -- wait for NTP sync success, 3.5 s timeout
if result then
Init_log = Init_log.."NTP time sync successful!\\n" -- update log (NTP success)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
else
Init_log = Init_log.."NTP time sync failed!\\n" -- update log (NTP failed)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
end
Init_log = Init_log.."Current time: "..(os.date("%Y-%m-%d %H:%M:%S")).."\\n" -- update log (show current time)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
Init_log = Init_log.."System initialization complete!\\n" -- update log (init complete)
lvgl.label_set_text(label_Init_log, Init_log) -- set label text
sys.wait(2000)
local alldata_scr = lvgl.obj_create(nil, nil) -- create a screen object to show all data, no parent or style
lvgl.scr_load(alldata_scr) -- load the screen and display it
local label_alldata = lvgl.label_create(alldata_scr, nil) -- create a Label to show all data
lvgl.label_set_recolor(label_alldata, true) -- enable label recolor
lvgl.label_set_align(label_alldata, lvgl.LABEL_ALIGN_LEFT) -- left-align label
lvgl.obj_align(label_alldata, nil, lvgl.ALIGN_IN_TOP_LEFT, 10, 5) -- align label to top-left, 10 px left, 5 px top
lvgl.obj_add_style(label_alldata, lvgl.LABEL_PART_MAIN, style_screen_label_main)-- assign style to label main part
local label_time = lvgl.label_create(alldata_scr, nil) -- create a Label to show current time
lvgl.label_set_text(label_time, os.date("%Y-%m-%d").."\\n"..os.date("%H:%M:%S"))
lvgl.label_set_align(label_time, lvgl.LABEL_ALIGN_CENTER) -- center-align label
lvgl.obj_align(label_time, nil, lvgl.ALIGN_IN_TOP_RIGHT, -25, 10)-- align label to top-right, 25 px right, 10 px top
lvgl.obj_add_style(label_time, lvgl.LABEL_PART_MAIN, style_screen_label_main)
lvgl.obj_del(Init_scr)
local chart_scr = lvgl.obj_create(nil, nil) -- create a screen object for charts, no parent or style
local chart = lvgl.chart_create(chart_scr, nil);
lvgl.obj_set_size(chart, 310, 150);
lvgl.obj_align(chart, nil, lvgl.ALIGN_CENTER, 0, 0);
lvgl.chart_set_type(chart, lvgl.CHART_TYPE_LINE); --Show lines and points too*/
lvgl.chart_set_point_count(chart, 20)
lvgl.chart_set_y_range(chart, lvgl.CHART_AXIS_PRIMARY_Y, 0, 100)
local ser1 = lvgl.chart_add_series(chart, lvgl.color_make(0xFF, 0x00, 0x00));
local ser2 = lvgl.chart_add_series(chart, lvgl.color_make(0x00, 0x80, 0x00));
-- print memory usage
log.info("mem.lua", rtos.meminfo())
log.info("mem.sys", rtos.meminfo("sys"))
while true do
lvgl.label_set_text(label_alldata, string.format([[#0000ff Ambient temperature: %.2f°C#
#ff00ff Ambient humidity: %.2f%%#
#ff0000 Pressure: %.2fhPa#
#800080 Altitude: %.2fm#
#0000ff Ultrasonic distance: %.2fcm#
#ff00ff Remote node battery voltage: %.2fV#
#ff0000 Remote node CPU temperature: %.0f°C#
#800080 Local node CPU temperature: %.0f°C#
]],all_data.params.CurrentTemperature,all_data.params.CurrentHumidity,all_data.params.Atmosphere,
all_data.params.Altitude,all_data.params.DetectDistance,all_data.params.CurrentVoltage,
all_data.params.CPUTemperature,local_CPU_T_C)); -- set label text
lvgl.label_set_text(label_time, os.date("%Y-%m-%d").."\\n"..os.date("%H:%M:%S"))
lvgl.chart_set_next(chart, ser1, all_data.params.CurrentTemperature);
lvgl.chart_set_next(chart, ser2, all_data.params.CurrentHumidity);
lvgl.chart_refresh(chart);
if key1_flag then
lvgl.scr_load(chart_scr)
key1_flag = false
end
if key2_flag then
lvgl.scr_load(alldata_scr)
key2_flag = false
end
gpio.toggle(12) --toggle gpio level
sys.wait(300)
end
end)
sys.taskInit(function() --create a task, blink LED every 500 ms
gpio.setup(13, 0) -- set gpio13 as output, init low, use hardware default pull
gpio.setup(12, 0) -- set gpio12 as output, init low, use hardware default pull
while true do
gpio.toggle(13) --toggle gpio level
sys.wait(500)
end
end)
sys.taskInit(function() --create a task, key scan
gpio.setup(18, nil, gpio.PULLUP)
gpio.setup(19, nil, gpio.PULLUP)
while true do
if gpio.get(18) == 0 then
sys.wait(20)
if gpio.get(18) == 0 then
key1_flag = true
end
end
if gpio.get(19) == 0 then
sys.wait(20)
if gpio.get(19) == 0 then
key2_flag = true
end
end
sys.wait(50)
end
end)
--[[
Supported fonts ["sarasa_m8_ascii","sarasa_m10_ascii","sarasa_m12_ascii",
"sarasa_m14_ascii","sarasa_m16_ascii","sarasa_m18_ascii","sarasa_m20_ascii","sarasa_m22_ascii"]
]]
-- User code ends---------------------------------------------
-- Always end with this
sys.run()
-- Do NOT add anything after sys.run()!!!!!
Hardware Used
Hardware modules used and purchase links:- 4G Module: Air700E, https://s.click.taobao.com/gMXE14u
- WiFi Module: ESP32-C3, https://s.click.taobao.com/VU6E14u
- Temperature & Humidity Sensor: AHT10, https://s.click.taobao.com/0bmD14u
- Barometric Pressure Sensor: BMP180, https://s.click.taobao.com/lj5E14u
- Ultrasonic Distance Module: US-100 (use serial communication mode), https://s.click.taobao.com/K0o5X3u
- LCD Screen: 1.47-inch ST7789, 172×320 resolution, https://s.click.taobao.com/qalD14u
- OLED Screen: 0.96-inch SSD1306, I2C interface, https://s.click.taobao.com/otWD14u
- Battery Charge/Discharge IC: IP5306-CK (be sure to buy the CK version; the regular version will auto-cutoff when discharge current is too low, unsuitable for MCU use), https://s.click.taobao.com/eu85X3u
- Battery: EVE 35V-18650, 3500 mAh, https://s.click.taobao.com/uUc5X3u
Recommended component store: LCSC. Discount registration link: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
All on-board components are available at LCSC; in the open-source link’s BOM page click “Add All to LCSC Cart” to one-click import the parts into your shopping cart.
IoT Platform Setup Instructions
First, create a new product in Alibaba Cloud IoT Platform and choose “Direct-connected Device” as the node type.
Configure the function definitions:
Next, add two devices:
Update the product key and other parameters in the script, then download the firmware and script into the Air700E. Check whether the device comes online and uploads data correctly.
Go to Message Forwarding → Cloud Product Streaming and create a data source:
Click View on the right of the newly created data source, then add a Topic:
- 1st field: choose Custom
- 2nd field: choose your created product
- 3rd field: choose your 4G node device
- 4th field: select
user/update
Create a data destination; set the action to Publish to Another Topic, and again select your product.
Create a parser, then click Go to Edit (or click View on the right).
For Associated Data Source, pick the one you just created:
For Associated Data Destination, choose the destination you just created.
In the parser script, replace deviceName() with your WiFi node’s device name, as shown below.
After editing, click Publish, then Start. The system will now automatically forward messages from the /ProductKey/DeviceName/user/update topic to the /ProductKey/DeviceName/user/get topic.
Other Recommended Open-Source Projects- STM32F030C8T6 minimum system board and running-light (schematic & PCB): https://blog.zeruns.com/archives/715.html
- Drew an MSP430F149 minimum system board and open-sourced it: https://blog.zeruns.com/archives/713.html
- 2007 power-supply contest problem: 30–36 V adjustable boost DC-DC module (UC3843): https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
- STC12C5A60S2 minimum system board / 51-MCU temperature display & fan control: https://blog.zeruns.com/archives/721.html
- STM32F407 standard-peripheral U8g2 graphics library port template: https://blog.zeruns.com/archives/722.html
- Qinheng CH32V307VCT6 minimum system board open source: https://blog.zeruns.com/archives/726.html
- LM25118 auto buck-boost adjustable DC-DC power module: https://blog.zeruns.com/archives/727.html
- EG1164 high-power synchronous-rectification boost module open source, up to 97 % efficiency: https://blog.zeruns.com/archives/730.html
Recommended Articles
- Cost-effective & cheap VPS/cloud server picks: https://blog.zeruns.com/archives/383.html
- Minecraft server setup tutorial: https://blog.zeruns.com/tag/mc/
- Home network cabinet tour & gear recommendations: https://blog.zeruns.com/archives/732.html
- Orange Pi 3B (RK3566) dev board unboxing & review: https://blog.zeruns.com/archives/729.html
- Upgraded my PC with a Yeston RTX 3070 GPU: https://blog.zeruns.com/archives/746.html























