Node giám sát môi trường 4G dựa trên Air700E (nhiệt độ, độ ẩm, áp suất...), upload lên nền tảng IoT Alibaba qua MQTT

Node giám sát môi trường 4G dựa trên Air700E (dữ liệu nhiệt độ, độ ẩm, áp suất, v.v.), tải lên nền tảng Internet vạn vật Alibaba Cloud qua MQTT.

Giới thiệu

Module 4G Air700E của [Hezhou] đọc dữ liệu từ cảm biến (nhiệt độ - độ ẩm, áp suất, v.v.) và tải lên nền tảng Internet vạn vật Alibaba Cloud qua giao thức MQTT, đồng thời hiển thị dữ liệu trên màn hình OLED 0,96 inch, sử dụng thư viện đồ họa U8g2.

Module WiFi ESP32C3 đăng ký dữ liệu do node 4G tải lên qua MQTT và hiển thị trên màn hình LCD, sử dụng thư viện đồ họa LVGL.

Đây là một bài thiết kế môn học của tôi, làm cho có, chưa hoàn thiện lắm.

Yêu cầu đề bài: Thiết kế và chế tạo hệ thống truyền thông không dây, kết hợp kiến thức đã học kỳ trước, thu thập nhiệt độ, độ ẩm / khoảng cách đo siêu âm cục bộ, truyền đến máy chủ / thiết bị thu qua phương thức không dây, và hiển thị thông tin liên quan trên LCD ở thiết bị thu, cụ thể như sau:

⑴ Thiết kế và hiện thực hệ thống truyền dẫn không dây Bluetooth

⑵ Thiết kế và hiện thực hệ thống truyền dẫn không dây WiFi

⑶ Thiết kế và hiện thực hệ thống truyền dẫn không dây ZigBee

⑷ Thiết kế và hiện thực hệ thống truyền dẫn không dây GPRS/GSM

(5) Thiết kế và hiện thực hệ thống truyền dẫn không dây 4G

(6) Thiết kế và hiện thực hệ thống truyền dẫn không dây NBIoT/LoRa

Gợi ý: Có thể dùng bộ xử lý 8/16/32 bit làm bộ điều khiển chính, chọn loại theo nội dung tổng hợp nhóm đã chọn, hoàn thành ít nhất 2 mục nhỏ, trong đó chọn 1 trong (1)(2)(3), chọn 1 trong (4)(5)(6).

Tôi phát triển cả Air700E và ESP32C3 bằng hệ thống LuatOS + kịch bản Lua.

Firmware LuatOS do tôi build qua cloud compile của Hezhou.

Flash 4 MByte tích hợp trên ESP32C3 đã được tôi thay bằng 8 MByte, firmware cũng lớn hơn 4 MB vì thêm thư viện đồ họa LVGL và vài kiểu font.

Link ưu đãi sinh viên Alibaba Cloud: https://www.aliyun.com/daily-act/ecs/activity_share?userCode=jdjc69nf

Lưu ý: Do nền tảng Internet vạn vật Alibaba Cloud không cho các thiết bị khác nhau đăng ký topic của nhau, chỉ đăng ký topic của thiết bị mình, nên cần thêm một quy tắc tại Chuyển tiếp tin nhắn → Chuyển tiếp sản phẩm đám mây, chuyển tin nhắn do thiết bị 4G publish sang một topic của thiết bị WiFi.

Link mở nguồn trên nền tảng Lichuang: https://oshwhub.com/zeruns/wen-shi-du-cai-ji-4g-shang-chuan

Nhóm kỹ thuật điện tử/vi điều khiển: 2169025065

Giới thiệu Air700E

Air700E là module truyền thông LTE Cat.1 bis do Hezhou Communication ra mắt, dùng nền tảng Yixin EC618, hỗ trợ công nghệ LTE 3GPP Rel.13. Module chỉ giữ lại băng tần LTE TDD, tương thích với băng tần chính của China Mobile, có kích thước siêu nhỏ và chi phí tối ưu, đáp ứng nhu cầu thu nhỏ và giá rẻ.

Đặc điểm chính gồm:

  • Hỗ trợ giao diện USIM đơn 1,8/3,3 V
  • Hỗ trợ cổng nối tiếp 1,8/3,3 V cấu hình được
  • Hỗ trợ USB 2.0
  • Hỗ trợ nâng cấp firmware OTA từ xa
  • Hỗ trợ giao diện âm thanh số PSM
  • Hỗ trợ nhiều cách phát triển như USB lên mạng, phát triển AT chuẩn, phát triển thứ cấp open CPU (LuatOS, C-SDK) v.v.

Air700E tích hợp sẵn nhiều giao thức mạng, tích hợp nhiều giao diện tiêu chuẩn công nghiệp và hỗ trợ nhiều driver và chức năng phần mềm (như driver USB trên Windows 7/8/8.1/10, Linux, Android v.v.), mở rộng mạnh mẽ ứng dụng trong lĩnh vực M2M như CPE, router, thẻ dữ liệu, máy tính bảng, ô tô, an ninh và PDA công nghiệp.

Thông số kỹ thuật Air700E:

Băng tần LTE-TDD: B34/B38/B39/B40/B41

Tốc độ dữ liệu LTE-TDD:

  • Tỷ lệ up/down 2: tối đa 8 Mbps (DL) / tối đa 2 Mbps (UL)
  • Tỷ lệ up/down 1: tối đa 6 Mbps (DL) / tối đa 4 Mbps (UL)

Giao diện:

  • 1 cổng USB 2.0 tốc độ cao (tối đa 480 Mbps)
  • 1 giao diện thẻ (U)SIM 1,8 V/3,0 V
  • 1 giao diện NETLIGHT (NET_STATUS)
  • 1 giao diện I2S số, hỗ trợ codec ngoài
  • 3 giao diện UART (cổng chính, cổng chung, cổng gỡ lỗi)
  • PWRKEY (mức thấp có hiệu lực)
  • 2 giao diện ADC
  • 13 GPIO chung + 2 ngõ vào ngắt
  • 1 giao diện I2C

Giới thiệu Hezhou ESP32-C3

Bo mạch lõi CORE ESP32 được thiết kế dựa trên ESP32-C3 của Espressif, kích thước chỉ 21 mm × 51 mm, viền bo dạng tem thư, thuận tiện cho nhà phát triển trong nhiều kịch bản. Bo mạch hỗ trợ UART, GPIO, SPI, I2C, ADC, PWM v.v., có thể chọn theo nhu cầu thực tế.

Tài nguyên phần cứng:

  • Kích thước 21 mm × 51 mm
  • 1 SPI FLASH, 4 MB trên bo, hỗ trợ tối đa 16 MB
  • 2 giao diện UART, UART0~UART1, trong đó cổng tải là UART0
  • 5 ADC 12 bit, tốc độ lấy mẫu tối đa 100 KSPS
  • 1 giao diện SPI tốc độ thấp, hỗ trợ chế độ chủ
  • 1 bộ điều khiển IIC
  • 4 giao diện PWM, có thể dùng bất kỳ GPIO
  • 15 chân GPIO ngoài, có thể tái sử dụng
  • 2 đèn LED dán
  • 1 nút reset + 1 nút BOOT
  • 1 cổng tải/gỡ lỗi USB-TTL
  • Antenna PCB 2,4 GHz trên bo

Ảnh thực tế

Video demo: https://www.bilibili.com/video/BV1MH4y1B7Df/

Toàn cảnh

Node 4G

Node WiFi

Khi kết nối WiFi:

Sau khi kết nối WiFi, bắt đầu kết nối máy chủ MQTT và đồng bộ thời gian NTP:

Hiển thị dữ liệu thu thập từ node 4G:

Hiển thị đường cong thay đổi nhiệt độ - độ ẩm:

Nền tảng Internet vạn vật

Dữ liệu hiển thị trên nền tảng Internet vạn vật Alibaba Cloud:

Sơ đồ nguyên lý

Node 4G

Node WiFi

PCB

Node 4G

Package module 4G này không chính xác lắm.

Lớp trên:

Lớp dưới:

Node WiFi

Lớp trên:

Lớp dưới:

Code

Tôi không hướng dẫn chi tiết cách tải code, tự xem tài liệu chính thức.

Tài liệu Air700E: https://doc.openluat.com/wiki/44?wiki_page_id=4730

Handbook LVGL cho LuatOS: https://url.zeruns.com/7z7fN

Tài liệu ESP32-C3: https://url.zeruns.com/497AP

Học Lua: https://url.zeruns.com/Pc4PA

Node 4G

Link tải firmware: https://url.zeruns.com/2G3K7

File main.lua:

-- LuaTools cần PROJECT và VERSION
PROJECT = "Node giám sát môi trường"
VERSION = "1.1.0"

-- Nhập thư viện cần thiết (viết bằng lua), thư viện nội bộ không cần require
sys = require("sys")
aht10 = require "aht10"
bmx = require "bmx"

-- Thêm watchdog cứng tránh treo chương trình
if wdt then
    wdt.init(9000)              -- khởi tạo watchdog 9 s
    sys.timerLoopStart(wdt.feed, 3000)  -- feed mỗi 3 s
end

print(_VERSION)

log.info(mobile.ipv6(true))     -- bật ipv6

local ProductKey  = "xxxxxx"                         -- product key
local DeviceName  = "xxxxxx"                -- đổi tên thiết bị của bạn
local DeviceSecret = "xxxxxx"               -- đổi mật khẩu thiết bị của bạn
local client_id, user_name, password = iotauth.aliyun(ProductKey, DeviceName ,DeviceSecret) -- tạo bộ ba MQTT
log.info("Thông số MQTT", client_id, user_name, password)

local softI2C = i2c.createSoft(29, 31, 2)   -- khởi tạo I2C mềm
local aht10_data,bmx_data
local RH,Temp,Press,High,distance,BAT_Voltage,CPU_T = 0,0,0,0,0,0,0                 -- trung bình, độ ẩm, nhiệt độ, áp suất, độ cao, khoảng cách, điện áp pin, nhiệt độ CPU
local RH_C,Temp_C,Press_C,High_C,distance_C,BAT_Voltage_C,CPU_T_C = 0,0,0,0,0,0,0   -- giá trị hiện tại
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()   -- đọc module đo khoảng cách siêu âm US-100
    uart.write(1, string.char(0x55))  -- gửi hex 0x55
    sys.wait(50)    -- delay 50 ms
    local hData,lData  = string.byte(uart.read(1, 2),1,2)   -- nhận 2 byte qua uart
    uart.rxClear(1) -- xóa bộ đệm nhận
    if hData and lData then -- kiểm tra đã nhận đủ
        local Distance_mm = tonumber((hData * 256 ) + lData)    -- dịch 8 bit cao 8 bit rồi cộng 8 bit thấp
        if Distance_mm > 4500 then  -- kiểm tra vượt ngưỡng
            Distance_mm = 4500
        end
        --log.info("Siêu âm: "..Distance_mm.."mm")     -- log khoảng cách
        return (Distance_mm / 10)                  -- trả về cm
    end
end
-- https://blog.zeruns.com
sys.taskInit(function() -- tạo luồng, đọc các cảm biến

    sys.wait(500)       -- delay 500 ms
    aht10.init(softI2C) -- khởi tạo AHT10, truyền i2c_id
    bmx.init(softI2C)   -- khởi tạo BMP180, truyền i2c_id
    uart.setup(1, 9600, 8, 1, uart.NONE)    -- khởi tạo uart1
    adc.open(0)         -- mở kênh adc0 và đọc
    adc.open(adc.CH_CPU)-- mở kênh ADC - nhiệt độ CPU
    --adc.setRange(adc.ADC_RANGE_3_8) -- bật phân áp ADC0,1, phạm vi 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 -- tổng, tính trung bình
    local avg_count, avg_count2,avg_count3,avg_count4,avg_count5 = 0,0,0,0,0

    while 1 do
        aht10_data = aht10.get_data()   -- đọc AHT10
        bmx_data = bmx.get_data()       -- đọc BMP180
        distance_C = US_100()           -- đọc US-100
        -- lấy mẫu điện áp pin, tỷ lệ phân áp: 4,7 k/10 k
        BAT_Voltage_C = ((adc.get(0) / 1000 / 1007) * 1473) -- đọc AD0, tính điện áp pin
        CPU_T_C = adc.get(adc.CH_CPU) / 1000                -- đọc nhiệt độ CPU

        -- tính trung bình nhiệt độ - độ ẩm AHT10
        if aht10_data.RH and aht10_data.T then  -- kiểm tra AHT10 đã đọc
            all_data.params.CurrentTemperature = aht10_data.T
            all_data.params.CurrentHumidity = aht10_data.RH*100
            if avg_count < 6 then       -- nếu < 6, cộng dồn trung bình
                RH_C,Temp_C = aht10_data.RH*100, aht10_data.T   -- đọc giá trị hiện tại
                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         -- tính trung bình độ ẩm
                Temp = Temp_sum / 6     -- tính trung bình nhiệt độ
                log.info("AHT10_data.RH: "..(string.format("%.2f", RH)).." %"," AHT10_data.T: "..(string.format("%.2f", Temp)).." ℃")    -- log
                AHT10_flag = true       -- đặt cờ, thu thập xong
                sys.publish("MQTT_P")   -- publish, tính xong, MQTT có thể gửi
                avg_count,RH_sum,Temp_sum = 0,0,0   -- reset bộ đếm và tổng
            end
        end

```--BMP180 tính toán giá trị trung bình áp suất và độ cao
        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       --kiểm tra xem có nhỏ hơn 10 không, tích lũy để tính trung bình
                Press_C,High_C = bmx_data.press, bmx_data.high  --đọc giá trị hiện tại của áp suất và độ cao
                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 đo khoảng cách siêu âm tính toán giá trị trung bình
        if distance_C then    --kiểm tra xem có đọc được dữ liệu không
            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

        -- Điện áp pin
        if BAT_Voltage_C then    --kiểm tra xem có đọc được dữ liệu không
            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

        -- Nhiệt độ CPU
        if CPU_T_C then    --kiểm tra xem có đọc được dữ liệu không
            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() --tạo một tác vụ, đo khoảng cách siêu âm, mỗi 1,5 giây một lần
    uart.setup(1, 9600, 8, 1, uart.NONE)    --khởi tạo cổng nối tiếp 1
    sys.wait(1000)
    while 1 do
        distance = US_100() --đọc dữ liệu US-100
        if distance then    --kiểm tra xem có đọc được dữ liệu không
            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() --tạo một luồng, khởi tạo MQTT và báo cáo dữ liệu
    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) -- giá trị mặc định 240s
    mqttc:autoreconn(true, 3000) -- cơ chế tự động kết nối lại
    mqttc:on(function(mqtt_client, event, data, payload)
        if event == "conack" then
            sys.publish("mqtt_conack")
            log.info("mqtt", "mqtt đã kết nối")
            mqtt_client:subscribe("/sys/"..ProductKey.."/"..DeviceName.."/thing/service/property/set")
        elseif event == "recv" then
            log.info("mqtt", "nhận được tin nhắn", 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") -- chuyển dữ liệu sang định dạng JSON, giữ 2 chữ số thập phân
            mqttc:publish("/sys/"..ProductKey.."/".. DeviceName.."/thing/event/property/post", json_str)  -- báo cáo thuộc tính qua MQTT
            mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f"))  -- báo cáo thuộc tính qua MQTT
            AHT10_flag = false
        end
        if BMP180_flag then
            local json_str = json.encode({params = {Atmosphere = Press, Altitude = High}}, "2f") -- chuyển dữ liệu sang định dạng JSON, giữ 2 chữ số thập phân
            mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str)  -- báo cáo thuộc tính qua MQTT
            mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f"))  -- báo cáo thuộc tính qua MQTT
            BMP180_flag = false
        end
        if US100_flag then
            local json_str = json.encode({params = {DetectDistance = distance}}, "1f") -- chuyển dữ liệu sang định dạng JSON, giữ 2 chữ số thập phân
            mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str)  -- báo cáo thuộc tính qua MQTT
            mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f"))  -- báo cáo thuộc tính qua MQTT
            US100_flag = false
        end
        if BAT_Voltage_flag then
            local json_str = json.encode({params = {CurrentVoltage = BAT_Voltage}}, "2f") -- chuyển dữ liệu sang định dạng JSON, giữ 2 chữ số thập phân
            mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str)  -- báo cáo thuộc tính qua MQTT
            mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f"))  -- báo cáo thuộc tính qua MQTT
            BAT_Voltage_flag = false
        end
        if CPU_T_flag then
            local json_str = json.encode({params = {CPUTemperature = CPU_T}}, "2f") -- chuyển dữ liệu sang định dạng JSON
            mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", json_str)  -- báo cáo thuộc tính qua MQTT
            mqttc:publish("/"..ProductKey.."/".. DeviceName.."/user/update", json.encode(all_data,"2f"))  -- báo cáo thuộc tính qua MQTT
            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 có thể chọn 0 90 180 270
    u8g2.ClearBuffer()  --xóa màn hình
    u8g2.SetFont(u8g2.font_opposansm10)  --chuyển font chữ
    u8g2.DrawUTF8("IMEI:", 0, 16)
    u8g2.DrawUTF8(mobile.imei(), 0, 32)
    u8g2.SendBuffer()

    --đồng bộ thời gian qua ntp
    --socket.sntp()
    socket.sntp({"ntp.aliyun.com","ntp1.aliyun.com","ntp2.aliyun.com"}) --địa chỉ máy chủ sntp tùy chỉnh
    --socket.sntp(nil, socket.ETH0) --sntp tùy chỉnh số thứ tự adapter
    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()  --xóa màn hình
    u8g2.SetFont(u8g2.font_opposansm10)  --chuyển font chữ
    u8g2.DrawUTF8(os.date("%Y-%m-%d"), 0, 16) --hiển thị thời gian
    u8g2.DrawUTF8(os.date("%H:%M:%S"), 0, 32) --hiển thị thời gian
    local IP = socket.localIP()     --hiển thị địa chỉ IP
    if IP then
        u8g2.DrawUTF8("IP:"..IP, 0, 63)
    end
    u8g2.SendBuffer()
    sys.wait(2500)

    while true do
        u8g2.ClearBuffer()  --xóa màn hình
        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()   --cập nhật dữ liệu hiển thị lên màn hình
        sys.wait(200)
    end
end)sys.taskInit(function() --Tạo một luồng, LED nhấp nháy mỗi 500 ms
    gpio.setup(27, 0)   -- Cấu hình gpio27 làm ngõ ra, mức logic ban đầu thấp, dùng điện trở kéo mặc định của phần cứng
    while true do
        gpio.toggle(27) --Đảo mức gpio
        sys.wait(500)
    end
end)

--[[
    Các font được hỗ trợ
    ["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"]
]]
-- Kết thúc phần mã người dùng---------------------------------------------
-- Luôn kết thúc bằng câu lệnh này
sys.run()
-- Không được thêm bất kỳ câu lệnh nào sau sys.run()!!!!!

Nút WiFi

Địa chỉ tải firmware: https://url.zeruns.com/a7eXJ

Tệp main.lua:

-- LuaTools cần PROJECT và VERSION
PROJECT = "Nút hiển thị dữ liệu"
VERSION = "1.0.5"

-- Thư viện sys là bắt buộc
_G.sys = require("sys")
require("sysplus")

--Thêm watchdog cứng để tránh treo chương trình
wdt.init(9000)--Khởi tạo watchdog 9 s
wdt_timer_id = sys.timerLoopStart(function()
    wdt.feed()
end, 2000)  --Nháy watchdog mỗi 2 s

local ProductKey= "xxxxxx"                         --Khóa sản phẩm
local DeviceName= "xxxxxx"                --Đổi thành tên thiết bị của bạn
local DeviceSecret = "xxxxxx" --Đổi thành khóa bí mật thiết bị của bạn
local client_id, user_name, password = iotauth.aliyun(ProductKey, DeviceName ,DeviceSecret) --Tạo bộ ba MQTT
log.info("Thông số MQTT", 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() -- Tạo một luồng
    
    log.info("wlan", "wlan_init:", wlan.init()) -- Khởi tạo wlan
    wlan.setMode(wlan.STATION)                  -- Đặt chế độ wlan là STATION
    wlan.connect("Mate 40 Pro", "123456789", 1)     -- Kết nối mạng wlan
    local result, data = sys.waitUntil("IP_READY",6000)-- Chờ sự kiện IP_READY
    log.info("wlan", "IP_READY", result, data)  -- In kết quả và dữ liệu sự kiện IP_READY
    if result then         
        socket.setDNS(socket.STA, 1, "114.114.114.114")    
        log.info("wlan", "info", json.encode(wlan.getInfo()))   -- In chi tiết mạng wlan
        sys.publish("WiFi_connected_OK",data)   -- Phát thông điệp WiFi đã kết nối thành công
        --Đồng bộ thời gian qua ntp
        --socket.sntp()
        socket.sntp({"ntp.aliyun.com","time.windows.com","ntp.tencent.com","ntp2.aliyun.com"}, socket.STA) --Địa chỉ máy chủ sntp tùy chỉnh
        --socket.sntp(nil, socket.STA) --Adapter tùy chỉnh cho sntp
        -- Đăng ký sự kiện NTP_UPDATE
        sys.subscribe("NTP_UPDATE", function()
            sys.publish("NTP_OK")   -- Phát thông điệp đồng bộ NTP thành công
            log.info("sntp", "time", os.date()) -- In thời gian hệ thống
        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)-- Mở kênh ADC-nhiệt độ CPU
    local CH_CPU = adc.get(adc.CH_CPU) / 1000    -- Đọc nhiệt độ CPU
    if CH_CPU then  -- Kiểm tra dữ liệu có hợp lệ
        local_CPU_T_C = CH_CPU
    end

    mqttc = mqtt.create(nil, "a1sJbDQiEqr.iot-as-mqtt.cn-shanghai.aliyuncs.com", 1883,true)    -- Tạo client MQTT
    mqttc:auth(client_id, user_name, password)  -- Xác thực MQTT
    mqttc:keepalive(60)                        -- Thời gian giữ kết nối MQTT, mặc định 60 s
    mqttc:autoreconn(true, 3000)                -- Bật tự động kết nối lại
    mqttc:on(function(mqtt_client, event, data, payload)-- Định nghĩa hàm callback sự kiện MQTT
        if event == "conack" then               -- Khi nhận được sự kiện xác nhận kết nối (conack)
            sys.publish("mqtt_conack")          -- Phát thông điệp mqtt_conack
            log.info("mqtt", "mqtt đã kết nối")       -- In thông báo kết nối thành công
            mqtt_client:subscribe("/sys/"..ProductKey.."/"..DeviceName.."/thing/service/property/set")  -- Đăng ký chủ đề
            mqtt_client:subscribe("/"..ProductKey.."/"..DeviceName.."/user/get")
        elseif event == "recv" then             -- Khi nhận được sự kiện tin nhắn (recv)
            if data == "/"..ProductKey.."/"..DeviceName.."/user/get" then   -- Kiểm tra chủ đề nhận
                all_data = json.decode(payload)             -- Giải mã nội dung tin nhắn sang JSON
                mqttc:publish("/sys/"..ProductKey.."/"..DeviceName.."/thing/event/property/post", payload)  -- Gửi thuộc tính qua MQTT
            else
                log.info("mqtt", "nhận tin nhắn", data, payload) -- In thông tin tin nhắn nhận được
                local mqtt_date = json.decode(payload)      -- Giải mã nội dung tin nhắn sang JSON
            end
        elseif event == "sent" then             -- Khi nhận được sự kiện gửi tin nhắn (sent)
            --log.info("mqtt", "sent", "pkgid", data)-- In thông tin tin nhắn đã gửi
        end
    end)
    mqttc:connect()

    while true do
        local CH_CPU = adc.get(adc.CH_CPU) / 1000    -- Đọc nhiệt độ CPU
        if CH_CPU then  -- Kiểm tra dữ liệu có hợp lệ
            CPU_T_C = CH_CPU
        end

        gpio.toggle(12) --Đảo mức gpio
        sys.wait(500)
    end
end)

sys.taskInit(function() -- Tạo một luồng 
    spi.setup(2, 7, 0, 0, 8, 80 * 1000 * 1000, spi.MSB, 1, 1)   -- Khởi tạo SPI
    log.info("lcd.init", lcd.init("st7789",{
        port = 2,           -- cổng spi
        -- pin_cs = 7,         -- Chọn SPI
        pin_dc = 8,         -- Chân lệnh/dữ liệu lcd
        -- pin_pwr = 18,       -- Chân nền lcd, tùy chọn, có thể bỏ
        pin_rst = 6,        -- Chân reset lcd
        direction = 2,      -- Hướng màn hình lcd 0:0° 1:180° 2:270° 3:90°
        w = 320,            -- Độ phân giải ngang lcd
        h = 172,            -- Độ phân giải dọc lcd
        xoffset = 0,        -- Dịch x (tùy IC màn hình và hướng)
        yoffset = 34;       -- Dịch y (tùy IC màn hình và hướng)
    }))

    log.info("lvgl", lvgl.init())   --- Khởi tạo LVGL

    local style_screen_label_main = lvgl.style_create()         -- Tạo đối tượng style tên style_screen_label_main
    lvgl.style_set_text_font(style_screen_label_main, lvgl.STATE_DEFAULT, lvgl.font_get("opposans_m_16")) -- Đặt font mặc định cho nhãn

```local Init_scr = lvgl.obj_create(nil, nil) -- tạo một đối tượng màn hình
    lvgl.scr_load(Init_scr)  -- tải đối tượng màn hình, hiển thị trên màn hình
    local spinner = lvgl.spinner_create(Init_scr, nil) -- tạo một đối tượng biểu tượng quay
    lvgl.obj_set_size(spinner, 100, 100) -- thiết lập kích thước đối tượng biểu tượng quay
    lvgl.obj_align(spinner, nil, lvgl.ALIGN_CENTER, 0, -20) -- căn giữa đối tượng biểu tượng quay
    local label_WiFiConnecting = lvgl.label_create(Init_scr, nil) -- tạo một đối tượng nhãn
    lvgl.label_set_recolor(label_WiFiConnecting, true) -- bật chức năng tô màu lại nhãn, tức là tự động cập nhật màu khi nội dung văn bản thay đổi
    lvgl.obj_add_style(label_WiFiConnecting, lvgl.LABEL_PART_MAIN, style_screen_label_main) -- gán đối tượng kiểu cho phần main của đối tượng nhãn
    lvgl.label_set_text(label_WiFiConnecting, "Đang kết nối WiFi...") -- thiết lập nội dung văn bản của đối tượng nhãn
    lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- thiết lập cách căn chỉnh văn bản của đối tượng nhãn là căn giữa
    lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 46) -- căn giữa đối tượng nhãn
    local result, ip_data = sys.waitUntil("WiFi_connected_OK", 5000)    -- chờ tin nhắn kết nối WiFi thành công, timeout 5 giây
    if result == false then
        lvgl.label_set_text(label_WiFiConnecting, "#ff0000 Kết nối WiFi thất bại!#\\nVui lòng khởi động lại thiết bị hoặc kiểm tra mạng") -- thiết lập nội dung văn bản của đối tượng nhãn (hiển thị kết nối WiFi thất bại)
        lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- thiết lập cách căn chỉnh văn bản của đối tượng nhãn là căn giữa
        lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 51) -- căn giữa đối tượng nhãn
        while true do
            sys.wait(1000) -- chờ trong vòng lặp để ngăn chương trình tiếp tục thực thi
        end
    end
    lvgl.label_set_text(label_WiFiConnecting, "Kết nối WiFi thành công!") -- thiết lập nội dung văn bản của đối tượng nhãn (hiển thị kết nối WiFi thành công)
    lvgl.label_set_align(label_WiFiConnecting, lvgl.LABEL_ALIGN_CENTER) -- thiết lập cách căn chỉnh văn bản của đối tượng nhãn là căn giữa
    lvgl.obj_align(label_WiFiConnecting, nil, lvgl.ALIGN_CENTER, 0, 51) -- căn giữa đối tượng nhãn
    sys.wait(500) -- chờ 500 mili giây
    lvgl.obj_del(label_WiFiConnecting) -- xóa đối tượng nhãn
    lvgl.obj_align(spinner, nil, lvgl.ALIGN_CENTER, 0, 0) -- căn giữa đối tượng biểu tượng quay
    local label_Init_log = lvgl.label_create(Init_scr, nil) -- tạo một đối tượng nhãn
    lvgl.label_set_recolor(label_Init_log, true) -- bật chức năng tô màu lại nhãn, tức là tự động cập nhật màu khi nội dung văn bản thay đổi
    lvgl.label_set_align(label_Init_log, lvgl.LABEL_ALIGN_LEFT) -- thiết lập cách căn chỉnh văn bản của đối tượng nhãn là căn trái
    lvgl.obj_align(label_Init_log, nil, lvgl.ALIGN_IN_TOP_LEFT, 10, 5) -- căn chỉnh đối tượng nhãn vào góc trên bên trái màn hình
    local Init_log = "#0000ff Nhật ký khởi tạo: #\\nKết nối WiFi thành công!\\n" -- thông tin nhật ký khởi tạo
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    Init_log = Init_log.."IP: "..ip_data.."\\nĐang kết nối máy chủ MQTT...\\n" -- cập nhật thông tin nhật ký
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    if sys.waitUntil("mqtt_conack", 10000) == false then
        Init_log = Init_log.."#ff0000 Kết nối máy chủ MQTT thất bại!\\nVui lòng khởi động lại thiết bị!#\\n" -- cập nhật thông tin nhật ký (hiển thị kết nối MQTT thất bại)
        lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
        mqttc:disconnect() -- ngắt kết nối MQTT
        mqttc:close() -- đóng kết nối MQTT
        while true do
            sys.wait(1000) -- chờ trong vòng lặp để ngăn chương trình tiếp tục thực thi
        end
    end
    Init_log = Init_log.."Kết nối máy chủ MQTT thành công!\\n" -- cập nhật thông tin nhật ký (hiển thị kết nối MQTT thành công)
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    Init_log = Init_log.."Đang đồng bộ thời gian NTP...\\n" -- cập nhật thông tin nhật ký
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    local result = sys.waitUntil("NTP_OK", 3500) -- chờ tin nhắn đồng bộ thời gian NTP thành công, timeout 3 giây
    if result then
        Init_log = Init_log.."Đồng bộ thời gian NTP thành công!\\n" -- cập nhật thông tin nhật ký (hiển thị đồng bộ NTP thành công)
        lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    else
        Init_log = Init_log.."Đồng bộ thời gian NTP thất bại!\\n" -- cập nhật thông tin nhật ký (hiển thị đồng bộ NTP thất bại)
        lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    end
    Init_log = Init_log.."Thời gian hiện tại: "..(os.date("%Y-%m-%d %H:%M:%S")).."\\n" -- cập nhật thông tin nhật ký (hiển thị thời gian hiện tại)
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn
    Init_log = Init_log.."Khởi tạo hệ thống hoàn tất!\\n" -- cập nhật thông tin nhật ký (hiển thị khởi tạo hệ thống hoàn tất)
    lvgl.label_set_text(label_Init_log, Init_log) -- thiết lập nội dung văn bản của đối tượng nhãn

    sys.wait(2000)

    local alldata_scr = lvgl.obj_create(nil, nil)               -- tạo một đối tượng màn hình để hiển thị tất cả dữ liệu, không có đối tượng cha và kiểu
    lvgl.scr_load(alldata_scr)                                  -- tải đối tượng màn hình, hiển thị trên màn hình

    local label_alldata = lvgl.label_create(alldata_scr, nil)   -- tạo một đối tượng Label để hiển thị tất cả dữ liệu
    lvgl.label_set_recolor(label_alldata, true)                 -- bật chức năng tô màu lại nhãn, tức là tự động cập nhật màu khi nội dung văn bản thay đổi
    lvgl.label_set_align(label_alldata, lvgl.LABEL_ALIGN_LEFT)  -- thiết lập cách căn chỉnh nhãn là căn trái
    lvgl.obj_align(label_alldata, nil, lvgl.ALIGN_IN_TOP_LEFT, 10, 5)   -- căn chỉnh nhãn vào góc trên bên trái màn hình, cách lề trái 10 pixel, lề trên 5 pixel
    lvgl.obj_add_style(label_alldata, lvgl.LABEL_PART_MAIN, style_screen_label_main)-- gán đối tượng kiểu cho phần main của nhãn label_alldata
    
    local label_time = lvgl.label_create(alldata_scr, nil)      -- tạo một đối tượng Label để hiển thị thời gian hiện tại
    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)   -- thiết lập cách căn chỉnh nhãn là căn giữa
    lvgl.obj_align(label_time, nil, lvgl.ALIGN_IN_TOP_RIGHT, -25, 10)-- căn chỉnh nhãn vào góc trên bên phải màn hình, cách lề trái 10 pixel, lề trên 5 pixel
    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)               -- tạo một đối tượng màn hình để hiển thị biểu đồ, không có đối tượng cha và kiểu

    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);   --Hiển thị cả đường và điểm*/
    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));
    

    -- In thông tin sử dụng bộ nhớ
    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 Nhiệt độ môi trường: %.2f°C#
        #ff00ff Độ ẩm môi trường: %.2f%%#
        #ff0000 Áp suất khí quyển: %.2fhPa#
        #800080 Độ cao so với mực nước biển: %.2fm#
        #0000ff Khoảng cách siêu âm: %.2fcm#
        #ff00ff Điện áp pin nút từ xa: %.2fV#
        #ff0000 Nhiệt độ CPU nút từ xa: %.0f°C#
        #800080 Nhiệt độ CPU nút cục bộ: %.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)); -- thiết lập văn bản nhãn
        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) --đảo mức gpio
        sys.wait(300)
    end
end)

sys.taskInit(function() --tạo một luồng, LED nhấp nháy 500ms một lần
    gpio.setup(13, 0)   -- thiết lập gpio13 làm đầu ra, mức ban đầu thấp, sử dụng cấu hình pull-up/pull-down mặc định của phần cứng
    gpio.setup(12, 0)   -- thiết lập gpio12 làm đầu ra, mức ban đầu thấp, sử dụng cấu hình pull-up/pull-down mặc định của phần cứng
    while true do
        gpio.toggle(13) --đảo mức gpio
        sys.wait(500)
    end
end)

sys.taskInit(function() --tạo một luồng, quét phím
    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)


--[[
    Font được hỗ trợ ["sarasa_m8_ascii","sarasa_m10_ascii","sarasa_m12_ascii",
    "sarasa_m14_ascii","sarasa_m16_ascii","sarasa_m18_ascii","sarasa_m20_ascii","sarasa_m22_ascii"]
]]

-- Mã người dùng đã kết thúc---------------------------------------------
-- Luôn là câu này ở cuối
sys.run()
-- Không được thêm bất kỳ câu lệnh nào sau sys.run()!!!!!

## Phần cứng được sử dụng

Các mô-đun phần cứng được sử dụng và địa chỉ mua hàng:- Mô-đun 4G: Air700E, [https://s.click.taobao.com/gMXE14u](https://uland.taobao.com/coupon/edetail?e=LUUvClZZaoalhHvvyUNXZfh8CuWt5YH5OVuOuRD5gLJMmdsrkidbOUV9IBA4kmjLCKSz6NMEOpOk6DkEOOy39ANONOd65bn6k%2BQD1WLWk7D58pWrh7e7ViECg58T7b6E7qOXUj4MT3dyBOK%2B8KjzSuzY3MUSAX0G1TP3uC6T%2BzrKa4jyh4U%2Bo5gBVnx58RcK3x24QQZ%2BkKRI%2Bf4%2BtcDu44oLvKgaQ5D83qmGq7GPCXsH2XAjD13xyT%2FhMZgljYbZj4NAR0X%2B9c23Sv8LMk9JOkIQtV1V6tmtIciWFcjrsPzH6emLlAySAVD9ZzTfOOXe%2Bm9PGdJ%2FlaUZUAGa9XIpkqJ7%2BkHL3AEW&traceId=213d811317025749037687621e65ab&union_lens=lensId%3APUB%401702574899%40212be3f7_0d90_18c695f4255_c520%400212cIwjWn6h4JZ67isD7Et6%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_ea9ed1dc-121c-4a26-85aa-1c27c9c4e982_)
- Mô-đun WiFi: ESP32-C3, [https://s.click.taobao.com/VU6E14u](https://s.click.taobao.com/t?e=m%3D2%26s%3DZlxzb7VA%2BERw4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFhiPRfK6WZZbW%2BG7BH3F%2BVP0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMA7PT7P4MIjv0iDnQnCfxRCKLWMw3EOEsyJN2owMjhufwDudUsQ2T%2BdtwdcXrvJnQ7ZHuYE9Ro5OOZ8DKCp9t3JAIHG%2FUGGufvqH4FkV%2B42f2z1OkmXn0f%2BU%2FuprW1TdmBLeMqtJBmsqBWpXpRELkuYcbEo3ADJdJgcSpj5qSCmbA%3D&union_lens=lensId%3APUB%401702575245%4021334feb_0c8e_18c6964886f_3037%4002FXjqZ71jUAMAWD59wyxDr%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_86a564e3-eb01-4328-b966-1f368fd8dd53_)
- Cảm biến nhiệt độ & độ ẩm: AHT10, [https://s.click.taobao.com/0bmD14u](https://s.click.taobao.com/t?e=m%3D2%26s%3D771Jk01ERgJw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFhiPRfK6WZZbARW0aicXof30JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt9wh0O3n7rEs7VSraZkgkhwlqDRRVlGxRqvyzFinE2vlF34xAtPUu%2BiG3j8B3S26WYwmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYy4m7hkqalpkwvY%2BXqRXyCksYOae24fhW0&union_lens=lensId%3APUB%401702575301%4021055555_16ff_18c69656404_8e69%40024dxCNyvH1jSaZZpYn2bbD3%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_e4951d39-761b-41f5-8840-673a3c0c7f1f_)
- Cảm biến áp suất: BMP180, [https://s.click.taobao.com/lj5E14u](https://s.click.taobao.com/t?e=m%3D2%26s%3DbnhyRgad73lw4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFhiPRfK6WZZbbJVKleC4LJX0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAFBcM8wkWlGmBPeR%2FUFCetKLWMw3EOEsyJN2owMjhufwDudUsQ2T%2BduXhptvf7sm0vQrr15PsJfJByQLpIiyxZXY%2Fio3PXH82JxEGSxl4yKUtXkuY7xjNZU%2FuprW1TdmBLeMqtJBmsqAZVo9hr9WaqYgTf7YQgcVJcSpj5qSCmbA%3D&union_lens=lensId%3APUB%401702575360%402106d2ff_0cdf_18c69664982_7aaf%40021C2JTg214QOPnilxsDb2w%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_f8a710ac-0037-4ec2-bbff-896f3c1af2c5_)
- Mô-đun đo khoảng cách siêu âm: US-100 (dùng chế độ giao tiếp UART), [https://s.click.taobao.com/K0o5X3u](https://s.click.taobao.com/t?e=m%3D2%26s%3DInz6n3%2B%2FZwxw4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFhiPRfK6WZZbq%2FcxWpaeRwj0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAFBcM8wkWlGmBPeR%2FUFCetKLWMw3EOEsyJN2owMjhufwDudUsQ2T%2Bdg68505k9IiwAZQz22%2FRFMA5pQILDo6IOf16Gn%2BAIx7L96W3Eb16pLNyU%2FAPg17%2FFE%2FuprW1TdmBLeMqtJBmsqB4r3mjD%2F4H9bgUNPZMnoHFcSpj5qSCmbA%3D&skuId=4276770277565&union_lens=lensId%3APUB%401702575435%40213e7f1b_0c9e_18c69676fe9_4d3a%40036cIiBD2TZI9c9YU2Vchohd%40eyJmbG9vcklkIjo2MTQyOSwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfZGV0YWlsX2h0bSIsInNyY0Zsb29ySWQiiOiiI4MDY3NCJ9%3Bscm%3A1007.30148.329090.pub_search-item_972f304d-0e03-4fbf-b740-fd750c7af1d6_)
- Màn hình LCD: ST7789 1,47 inch, độ phân giải 172×320, [https://s.click.taobao.com/qalD14u](https://s.click.taobao.com/t?e=m%3D2%26s%3DgP4fG3vZI%2FNw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFhiPRfK6WZZbfpmjeIyJHHf0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt8KAViuQdyPNc8ER84P6KktsP7OMD4hiwaj%2Fx4cHJV5ILJ9HqoY3BUsLMohkjCbsqowmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYyruoikO2OymYJLixcAe8pyMYOae24fhW0&union_lens=lensId%3APUB%401702575502%40212c48cb_0c8f_18c69687670_23aa%40024B9sLEl2iRKmna6a9po7FA%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_5e0d1922-0ca6-44f9-99a9-821166c9de7d_)
- Màn hình OLED: SSD1306 0,96 inch, giao tiếp I²C, [https://s.click.taobao.com/otWD14u](https://s.click.taobao.com/t?e=m%3D2%26s%3DKH7sw7eX%2F2Bw4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFhiPRfK6WZZbUnl%2Fg%2F1GNuL0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAc7QBxLpsf4TZIsq6%2BWiNSqLWMw3EOEsyJN2owMjhufwDudUsQ2T%2BdqwhF6wjgAjmYU48%2FUVSeeMab%2F%2BCSuws4OiHLQ4Yn8csh%2BdzzS7mHW11b5eySA3UA0%2FuprW1TdmBLeMqtJBmsqChj81ryXyWXeLqYXvNN1cOcSpj5qSCmbA%3D&union_lens=lensId%3APUB%401702575558%40213f6e5c_0cfd_18c69694f33_999c%40021vqRKMT5nzj9ALhoKTH8lx%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_b4129703-d754-49d5-b48a-82cdfefb2b5f_)
- IC sạc/xả pin: IP5306-CK (chú ý mua phiên bản CK, bản thường khi dòng xả quá nhỏ sẽ tự ngắt, không phù hợp cho vi điều khiển), [https://s.click.taobao.com/eu85X3u](https://s.click.taobao.com/t?e=m%3D2%26s%3DjX7JisUJSllw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFhiPRfK6WZZbiJKehPSaPYD0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt4mEKVwjSJcGFXvRQPy5FMG3%2FxXe%2BxQvKcLHPTEEdUKrO5xtKw8OA8bUMx%2BC19Mn3YwmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYy5%2FXtHaC5QvUT2J2P4FlLNcYOae24fhW0&union_lens=lensId%3APUB%401702575629%4021055b80_0c9f_18c696a64f6_4c08%40022k2R4UBS6bcyB2aIphUK2l%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_9fa456ea-a73f-470b-94dc-a6e6199bf4a4_)
- Pin: EVE 35V-18650, 3500 mAh, [https://s.click.taobao.com/uUc5X3u](https://s.click.taobao.com/t?e=m%3D2%26s%3DS952IFxDAbxw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFhiPRfK6WZZbhQCvBpU9uKb0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMA2wPTjyt1VFhJDk80jsGTwv1SarTXhIOT%2FcbizUjsed%2BEr%2BInjt7kJlMJo4xVYoI6QTU2mSOmawi8xTwUuWBJq9spaWqoW5XN2MTzYpfP%2FlmjO9AJYjY8CXJ%2BwEVkOqHFdIW9JNkz7%2FgDXc3BRboPm%2FkFZVUCNgR0oMbC7PHmBq9RLBgaW5udaw%3D%3D&union_lens=lensId%3APUB%401702575711%400b8b8151_148e_18c696ba6d8_29d5%40021a7vszDH3fGcvCMrLJyErd%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3Bscm%3A1007.30148.329090.pub_search-item_52261151-42b3-4abc-8483-08f2c5c3cc3d_)

**Nên mua linh kiện tại LCSC, đăng ký ưu đãi: [https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html](https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html)**

**Tất cả linh kiện trên bo đều có sẵn tại LCSC; trong liên kết mã nguồn, bấm “Mua ngay tại LCSC” trong bảng BOM để nhập tự động toàn bộ linh kiện vào giỏ hàng.**

## Hướng dẫn cấu hình nền tảng IoT

Trước tiên tạo sản phẩm mới trên nền tảng IoT của Alibaba Cloud, chọn kiểu nút là “Thiết bị kết nối trực tiếp”.

![](upload://xLPj0bhLZ1rL7RZEilLQN6ik7Bg.png)

Thiết lập định nghĩa chức năng:

![](upload://zZrpMZ1m81Csig5pO9hwdwzZdVd.png)

Tiếp theo thêm hai thiết bị

![](upload://2GKNz5ZfLb0gJ4QKViv65uUFYQx.png)

Sửa khóa sản phẩm và các tham số trong script, sau đó tải firmware và script xuống Air700E, kiểm tra xem thiết bị có kết nối và đẩy dữ liệu bình thường không.

![](upload://AsnIwH6TlnbEPb35i9fHDakJ7SQ.png)

Vào “Chuyển tiếp tin nhắn” → “Chuyển tiếp sản phẩm đám mây” để tạo nguồn dữ liệu:

![](upload://bEfZtuEN8lpD89diTlYSFGzClTK.png)

Bấm “Xem” bên phải nguồn dữ liệu vừa tạo, thêm Topic: cái thứ nhất chọn “Tùy chỉnh”, thứ hai chọn sản phẩm bạn tạo, thứ ba chọn thiết bị 4G, thứ tư chọn `user/update`.

![](upload://mrY2OUxHOIUvmF53kY8QYf5sxzj.png)

Tạo đích dữ liệu, chọn thao tác “Publish sang Topic khác”, sản phẩm vẫn chọn sản phẩm bạn tạo.

![](upload://55n4tbfW0Ag9TVFQ3laxDjBmNIB.png)

Tạo trình phân tích (parser), bấm “Đi tới chỉnh sửa” hoặc “Xem”.

![](upload://rTDklqhUCwOY2zZ5ZkEfyJzo9At.png)

Chọn nguồn dữ liệu bạn vừa tạo:

![](upload://vw789eZvaAKycMT2jbHJupaoWk4.png)

Chọn đích dữ liệu bạn vừa tạo

![](upload://rGanTyGyLN3DK8NVT5MJFI02Fvx.png)

Tại phần script parser, đổi `deviceName()` thành tên thiết bị WiFi của bạn như hình.

![](upload://ctS29c3tGAFXsA5Oz6vCWFGRZeN.png)

Sau khi sửa xong bấm “Publish”, rồi khởi động; hệ thống sẽ tự động chuyển tiếp dữ liệu từ chủ đề `/ProductKey/DeviceName/user/update` sang chủ đề `/ProductKey/DeviceName/user/get`.

## Các dự án mã nguồn mở khác đề xuất- Bo mạch hệ thống tối thiểu STM32F030C8T6 và đèn chạy (sơ đồ nguyên lý & PCB): [https://blog.zeruns.com/archives/715.html](https://blog.zeruns.com/archives/715.html)
- Vẽ xong bo mạch hệ thống tối thiểu MSP430F149, đã mở nguồn: [https://blog.zeruns.com/archives/713.html](https://blog.zeruns.com/archives/713.html)
- Đề nguồn kỳ thi điện tử 2007: mô-đun DCDC tăng áp 30–36 V điều chỉnh được (UC3843): [https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842](https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842)
- Bo mạch hệ thống tối thiểu STC12C5A60S2 / vi điều khiển 51 hiển thị nhiệt độ và quạt điều khiển theo nhiệt độ: [https://blog.zeruns.com/archives/721.html](https://blog.zeruns.com/archives/721.html)
- Template dự án STM32F407 thư viện chuẩn đã port sẵn thư viện đồ họa U8g2: [https://blog.zeruns.com/archives/722.html](https://blog.zeruns.com/archives/722.html)
- Mở nguồn bo mạch hệ thống tối thiểu QinHeng CH32V307VCT6: [https://blog.zeruns.com/archives/726.html](https://blog.zeruns.com/archives/726.html)
- Mô-đun nguồn DCDC tăng–giảm áp tự động LM25118 điều chỉnh được: [https://blog.zeruns.com/archives/727.html](https://blog.zeruns.com/archives/727.html)
- Mở nguồn mô-đun tăng áp đồng bộ công suất lớn EG1164, hiệu suất cực đại 97 %: [https://blog.zeruns.com/archives/730.html](https://blog.zeruns.com/archives/730.html)

## Bài viết đề xuất

- **Gợi ý VPS/máy chủ đám mây giá rẻ, tỷ lệ chi phí/hiệu năng cao:** [https://blog.zeruns.com/archives/383.html](https://blog.zeruns.com/archives/383.html)
- Hướng dẫn dựng máy chủ Minecraft: [https://blog.zeruns.com/tag/mc/](https://blog.zeruns.com/tag/mc/)
- Khoe tủ mạng gia đình, gợi ý thiết bị mạng cho nhà ở: [https://blog.zeruns.com/archives/732.html](https://blog.zeruns.com/archives/732.html)
- Mở hộp & đánh giá bo phát triển Orange Pi 3B (RK3566): [https://blog.zeruns.com/archives/729.html](https://blog.zeruns.com/archives/729.html)
- Nâng cấp PC, thay card màn hình Yeston RTX3070: [https://blog.zeruns.com/archives/746.html](https://blog.zeruns.com/archives/746.html)