Overview

The software part is developed based on the Python programming language, and achieves network communication and hardware control including the microcontroller (ESP32), the hydrogen sulfide sensor (MQ-136), the temperature and humidity sensor (SHT30), the pump and TFT-LCD screen (ST7735). It has implemented functions such as real-time monitoring, data display, data upload, and remote control. The whole program is divided into two parts: embedded part (handle hardware driving) and PC-side application software part (for data display on the computer).

Embedded part

1.Software modules

1.Display driver: ST773.py – TFT screen driver

2.Font library: sysfont.py – system font definition

3.User interface: gui.py – interface display logic

4.Main program: main.py – system main logic

5.Network communication: wifi.py – WiFi connection management/mqtt.py – MQTT client implementation

6.Sensor driver: sht.py – SHT30 temperature and humidity sensor driver

Function
Main function

1.H₂S concentration detection: measure H₂S concentration using the MQ-136 sensor

2.Temperature and humidity monitoring: measure ambient temperature and humidity using the SHT30 sensor

3.Data display: real-time display of measured data on a TFT screen

4.Data upload: upload data to the server via the MQTT protocol

5.Remote control: receive server commands to control device status

实物细节4

Operating modes

1.One-shot measurement mode (OneShot): trigger a single measurement manually

2.Cyclic Measurement Mode (Start): automatically perform timed measurements and upload data

3.Pump control: manually or remotely control the on/off state of the pump.

实物细节2

Program explanation
Main program (main.py)

1. Initialization:

  • Import libraries and define global variables
  • TFT screen initialization
  • WiFi connection
  • SHT30 and MQ-136 sensor initialization
  • Buttons (control screen contents) and motor initialization
  • MQTT connection
Core function
  • update(): manages change in system state and renders the menu, controls the pump state

  
def update():
    global command,menuID,state,prestate,pumpFlag
    prestate=state
    if state ==1:
        if command == "OK":
            if menuID==0:
                state=21
            if menuID==1:
                state=22
            if menuID==2:
                pumpFlag =1-pumpFlag
        if command == "Left":
            menuID -=1
        if command == "Right":
            menuID +=1
        if menuID==0:
            tft.text((0,40),"OneShot",TFT.YELLOW,sysfont,2,nowrap=True)
            tft.text((0,56),"Start",TFT.WHITE,sysfont,2,nowrap=True)
            tft.fillrect((90,72),(15,16),TFT.BLACK)
        if menuID==1:
            tft.text((0,40),"OneShot",TFT.WHITE,sysfont,2,nowrap=True)
            tft.text((0,56),"Start",TFT.YELLOW,sysfont,2,nowrap=True)
            tft.fillrect((90,72),(15,16),TFT.BLACK)
        if menuID==2:
            tft.text((0,40),"OneShot",TFT.WHITE,sysfont,2,nowrap=True)
            tft.text((0,56),"Start",TFT.WHITE,sysfont,2,nowrap=True)
            tft.text((90,72),"<",TFT.YELLOW,sysfont,2)
        if pumpFlag ==1:
            tft.fillrect((0,72),(90,16),TFT.BLACK)
            tft.text((0,72),"Pump-ON",TFT.MAROON,sysfont,2,nowrap=True)
        else:
            tft.fillrect((0,72),(90,16),TFT.BLACK)
            tft.text((0,72),"Pump-OFF",TFT.GRAY,sysfont,2,nowrap=True)
        command = 
    if state ==21:
        if command == "OK":
            do_one()
        if command == "Back":
            state=1
    if state ==22:
        if command == "OK":
            do_cycle()
        if command == "Back":
            state=1
    if pumpFlag==1:
        pumpON()
    else:
        pumpOFF()
    if prestate!=state:
        if state ==1:
            GUI.show(tft,GUI.welcome)
            if menuID==0:
                tft.text((0,40),"OneShot",TFT.YELLOW,sysfont,2,nowrap=True)
                tft.text((0,56),"Start",TFT.WHITE,sysfont,2,nowrap=True)
            if menuID==1:
                tft.text((0,40),"OneShot",TFT.WHITE,sysfont,2,nowrap=True)
                tft.text((0,56),"Start",TFT.YELLOW,sysfont,2,nowrap=True)
        if state ==21:
            GUI.show(tft,GUI.oneshot)
        if state ==22:
            GUI.show(tft,GUI.start)
  
  • on_KeyDown(): maps buttons to commands and calls update() function

  
def on_KeyDown(event): 
    global command,menuID,state
    time.sleep(0.1)
    if event.value()==0:
        if event==Pin(3):
            command="OK"
        if event==Pin(8):
            command="Back"
        if event==Pin(18):
            command="Left"
        if event==Pin(17):
            command="Right"
        update()   
  
  • do_command(): processes command and call different functions to execute operations
  
def do_command(cmd):
    print(cmd)
    global pumpFlag,state
    tft.text((13,96),cmd,TFT.BLUE,sysfont,1,nowrap=True)
    if cmd=="do_one":
        state=21
        GUI.show(tft,GUI.oneshot)
        do_one()
    if cmd=="do_cycle":
        state=22
        GUI.show(tft,GUI.start)
        do_cycle()
    if cmd=="pump_on":
        pumpFlag=1
        pumpON()
    if cmd=="pump_off":
        pumpFlag=0
        pumpOFF()
    update()
    cmd=
  
  
  • on_Timer_H2S(): periodic callbacks for sensor data collection
  
def on_Timer_H2S(timer):
    global rt,temparrT,temparrH,temparrV
    if rt>0:
        tft.text((40,24),str(rt)+" seconds.   ",TFT.RED,sysfont,1,nowrap=True)
    x=4.5*(sysfont["Width"]*2)
    global mq,sht,val,tem,hum
    tem,t,hum,h=sht.measure_int()
    val=mq.read_uv()/1000
    if rt>0:
        temparrT.append(tem)
        temparrH.append(hum)
        temparrV.append(val)
    
    v=2.5*sysfont["Height"]*2
    tft.fillrect((x,v),(sysfont["Width"]*16,sysfont["Height"]*2),TFT.BLACK)
    tft.text((x,v),str(val),TFT.YELLOW,sysfont,2,nowrap=True)
    v+=sysfont["Height"]*2
    tft.fillrect((x,v),(sysfont["Width"]*16,sysfont["Height"]*2),TFT.BLACK)
    tft.text((x,v),str(tem),TFT.YELLOW,sysfont,1,nowrap=True)
    v+=sysfont["Height"]*2
    tft.fillrect((x,v),(sysfont["Width"]*16,sysfont["Height"]*2),TFT.BLACK)
    tft.text((x,v),str(hum),TFT.YELLOW,sysfont,1,nowrap=True)
    if rt>0:
        rt-=1
    if(rt==0):do_send()
  
  • on_Timer_Alarm(): stops the timer
  
def on_Timer_Alarm(timer):
    global tmerH2S
    tmerH2S.deinit()

tft.text((0,16), "MQ is ready!        ",TFT.GREEN, sysfont, 1, nowrap=True)
GUI.show(tft,GUI.welcome)
state=1
prestate=state
while state == 0:
    pass
  
  • do_one(): triggers a single measurement
  
def do_one():
    global rt
    rt=-1
    on_Timer_H2S(None)
  
  • do_cycle(): triggers a 20-time cyclic measurements
  
def do_cycle():
    global tmerAlarm,tmerH2S,rt,temparrT,temparrH,temparrV
    rt=20
    temparrT=[]
    temparrH=[]
    temparrV=[]
    try:
        tmerH2S.deinit()
        tmerAlarm.deinit()
    except AttributeError:
        pass
    tft.fillrect((40,16),(100,8),TFT.BLACK)
    tft.text((40,16),"Measuring..",TFT.RED,sysfont,1,nowrap=True)
    tmerH2S = Timer(0)
    tmerAlarm = Timer(1)
    tmerAlarm.init(period=20000,mode=Timer.ONE_SHOT,callback=on_Timer_Alarm)
    tmerH2S.init(period=1000,mode=Timer.PERIODIC,callback=on_Timer_H2S)  
  
  • pumpON(): turns on the pump motor
  
def pumpON():
    print("on")
    motor.on()
    pass
  
  • pumpOFF(): turns off the pump motor
  
def pumpOFF():
    print("off")
    motor.off()
    pass   
  
  • do_send(): sends data to MQTT
  
def do_send():
    global mqttc,temparrT,temparrH,temparrV
    tft.fillrect((40,16),(100,8),TFT.BLACK)
    tft.text((40,16),"Sending data..",TFT.YELLOW,sysfont,1,nowrap=True)
    msg_dict = {
        'H2S': temparrV,
        'Tem': temparrT,
        'Hum': temparrH,
    }
    mqtt.send_one(mqttc,msg_dict)
    tft.fillrect((40,16),(100,8),TFT.BLACK)
    tft.text((40,16),"Done!",TFT.GREEN,sysfont,1,nowrap=True)
  
  
Main loop
  • wait for MQTT message
  • process button interaction
  • execute sensor measurements (single or cyclic)
  • process remote instructions
  • recover network anomaly

(Each step has corresponding contents displayed on the screen.)

PC-side software part
Software modules
  • Communication module: MessageProcess.py - core for MQTT Communication
  • WiFi configuration tool: setWiFi.py - WiFi configuration tool for ESP32
  • Data visualization: DataVisual.py - 3D data visualization/showfig.py - segmented data visualization
  • User interface: LogWindow.py - graphical log and control interface
Function

Data reception: reception of H₂S concentration, temperature, and humidity data in real time

Data storage: automatic saving of data to a CSV file.

Visual analysis:

  • 3D scatter plots for displaying multi-parameter relationships.
  • Data fitting curves.
  • Segmented viewing of historical data.
  • Device control:
  • Switching between single or continuous measurement modes.
  • Control of the pump (ventilation system).
Main program

1. MQTT communication (MessageProcess.py)

  
    def on_message(client, userdata, msg):
    try:
        # Parse JSON data
        data = json.loads(msg.payload.decode())

        # Process command
        if all(key in data for key in ['command']):
            print("Command received:", data['command'])
            return

        # Verify data format is correct
        if not all(key in data for key in ['Hum', 'H2S', 'Tem']):
            print("Received message format is incorrect, skipping processing")
            print("Received data:", data)
            return

        # Verify data is of list type
        if not all(isinstance(data[key], list) for key in ['Hum', 'H2S', 'Tem']):
            print("Data is not of list type, skipping processing")
            print("Received data:", data)
            return

        # Update global variables
        global Hum, H2S, Tem
        Hum = data['Hum']
        H2S = data['H2S']
        Tem = data['Tem']

        # Print received data
        print("\nNew data received:")
        print("Humidity data:", Hum)
        print("H2S concentration:", H2S)
        print("Temperature data:", Tem)
        # Save data to CSV file
        save_to_csv(Hum, H2S, Tem)

    except json.JSONDecodeError:
        print("JSON parsing error")
        print("Received raw data:", msg.payload.decode())
    except Exception as e:
        print(f"Error occurred while processing message: {e}")
        print("Received data:", msg.payload.decode())
  
Security consideration
  • TLS Encryption: using SSL encryption for MQTT communication.
  • Authentication Mechanism: MQTT username and password authentication.
  • Data Verification: strict input data validation