Air700E-based 4G environmental monitoring node (temperature, humidity, pressure, etc.) uploads to Alibaba Cloud IoT platform via MQTT

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

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

Recommended Articles