Hackster is hosting Impact Spotlights: Edge AI. Watch the stream live on Thursday!Hackster is hosting Impact Spotlights: Edge AI. Stream on Thursday!
Doris ThadarnathanPreo TanNicholas Dubs
Published

Smart Urban Farming Powered by Aquaponics

We aim to develop a smart, cost-effective aquaponics system that grows diverse crops sustainably with smart automation and monitoring.

IntermediateShowcase (no instructions)24 hours382
Smart Urban Farming Powered by Aquaponics

Things used in this project

Hardware components

M5Stack FIRE V2.7
M5Stack FIRE V2.7
×3
Light Unit with Photo-resistance
M5Stack Light Unit with Photo-resistance
×1
Mini Angle Unit Potentiometer Inside
M5Stack Mini Angle Unit Potentiometer Inside
×1
ENV III Unit with Temperature Humidity Air Pressure Sensor (SHT30+QMP6988)
M5Stack ENV III Unit with Temperature Humidity Air Pressure Sensor (SHT30+QMP6988)
×1
I2C Hub 1 to 6 Expansion TCA9548A Module
M5Stack I2C Hub 1 to 6 Expansion TCA9548A Module
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1

Software apps and online services

Node-RED
Node-RED
M5Stack Uiflow 2.

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Scissors, Free Fall
Scissors, Free Fall

Story

Read more

Schematics

Project Sketches

Prototype

Main M5 stack

Secondary M5 stack

Dashboard

Block Diagram

Flowchart for Main M5 stack

Flowchart for Main M5 stack (BUTTONS)

Flowchart for Main M5 stack (User Input Selections)

Flowchart for Secondary M5 stack

Code

Main M5 Code

Python
Code for the Master (Main). Using angle unit to select and edit options on screen
import os, sys, io
import math
import M5
from M5 import *
from unit import AngleUnit
from m5espnow import M5ESPNow
import json
from hardware import Timer
import time
import network
from umqtt.simple import MQTTClient




home = None
Row1Label = None
Row2Label = None
option = None
label4 = None
paramLabel = None
valueLabel = None
plant1Label = None
plant2Label = None
plant3Label = None
plant4Label = None
SUFLabel = None
powerLabel = None
angle_0 = None
tableLabel = None
LEVEL = None
plant_profiles = None
angle = None
selectedPlant = None
selectedRow = None
selectedParam = None
flowRateLabel = None
co2LevelLabel = None
lightDurationLabel = None
lightIntensityLabel = None
humidityLabel = None
waterTempLabel = None
airTempLabel = None
clicked = False
timer0 = None
sendLabel = None
rotateRow=False

espnow_0 = None
mqtt_client = None
wlan = None
SSID = 'WIFI'
WLAN_PW = 'WIFI_PASSWORD'


# Row MAC addresses (for ESP-NOW communication)
ROW_MAC_LIST = ['2cbcbb81f7c0', '2cbcbb81F924']
MQTT = True #If MQTT is true, the master will send data to the mqtt server. if false, will only use espnow

def timer0_cb(t):
  global espnow_0, clicked, selectedPlant, selectedRow
  if clicked:
    # label0.setText(str('a'))
    clicked = False
    profile_to_data(selectedRow, selectedPlant)
  time.sleep_ms(10)



#Sends data from a plant profile dict, to send_data
#This is becuase old version had the ability to send/update single params, and keeping this setup allows for that to be reimplemented
def profile_to_data(row_number, plant_profile):
    """
    Translates the plant profile data to the dataAsList format.
    
    Args:
    plant_profile (dict): The plant profile containing the plant parameters.
    row_number (int): The row number to use for the MQTT topic.
    """
    send_data(
        row_number, 
        plant_profile['plantName'], 
        plant_profile['airTemp'], 
        plant_profile['waterTemp'], 
        plant_profile['humidity'], 
        plant_profile['lightIntensity'], 
        plant_profile['lightDuration'], 
        plant_profile['co2Level'], 
        plant_profile['flowRate']
    )

# Function to send data to a specific row
def send_data(rowNo, plantName, airTemp, waterTemp, humidity, lightIntensity, lightDuration, co2Level, flowRate):
    global espnow_0
    
    # Convert data to dict 
    rowData = {
        "plantName": plantName,
        "airTemp": airTemp,
        "waterTemp": waterTemp,
        "humidity": humidity,
        "lIntensity": lightIntensity,
        "lDuration": lightDuration,
        "co2Level": co2Level,
        "flowRate": flowRate,
    }

    try:
        #Convert to json
        json_data = json.dumps(rowData)
        payload = json_data.encode('utf-8')

        # Send the data to the selectedRow
        print(f"Sending to peer ID: {rowNo}, Data: {json_data}")
        espnow_0.send_data(int(rowNo), payload)
        print(f"Successfully sent to peer ID: {rowNo}")
    except Exception as e:
        print(f"Failed to send data: {e}")

    # MQTT Publishing (optional)
    if MQTT:
        time.sleep_ms(100)
        print("Mqtt send is on")
        if not wlan.isconnected():
            print("Wi-Fi not connected")
            return
        
        try:
            json_data = json.dumps(rowData)
            publish_with_retry(f'm5stack/row{rowNo}/dataList', json_data)
        except Exception as e:
            print(f"Publish error: {e}")

# MQTT Publishing with Retry
def publish_with_retry(topic, message, retries=3):
    global mqtt_client
    for attempt in range(retries):
        try:
            mqtt_client.publish(topic, message, qos=1)
            print(f"Published: {message} to {topic}")
            return
        except Exception as e:
            print(f"Publish failed ({attempt + 1}/{retries}): {e}")
            if not reconnect_mqtt():
                break
    print("Failed to publish message after retries")

# MQTT Reconnect Function
def reconnect_mqtt():
    global mqtt_client
    try:
        mqtt_client.connect()
        print("MQTT reconnected")
        return True
    except Exception as e:
        print(f"Failed to reconnect MQTT: {e}")
        return False

#hdies all labels exceot the ones passed as a list
def hide_except(labelsToKeep):
    all_labels = [sendLabel, Row1Label, Row2Label, plant1Label, plant2Label, plant3Label, plant4Label, paramLabel, valueLabel, option, SUFLabel, powerLabel, home, tableLabel, flowRateLabel, co2LevelLabel, lightDurationLabel, lightIntensityLabel, humidityLabel, waterTempLabel, airTempLabel]
    
    # Hide all labels
    for label in all_labels:
        label.setVisible(False)

    # Make labels in labelsToKeep visible
    for label in labelsToKeep:
        label.setVisible(True)

#Displays the selected plant profile using multiple labels.
def display_selected_plant(selected_plant_dict):
    if not selected_plant_dict:
        return

    # Create or update labels
    # plantNameLabel.setText(f"Plant: {selected_plant_dict['plantname']}")
    airTempLabel.setText(f"Air Temp: {selected_plant_dict['airTemp']} C")
    waterTempLabel.setText(f"Water Temp: {selected_plant_dict['waterTemp']} C")
    humidityLabel.setText(f"Humidity: {selected_plant_dict['humidity']}%")
    lightIntensityLabel.setText(f"Light Intensity: {selected_plant_dict['lightIntensity']}")
    lightDurationLabel.setText(f"Light Duration: {selected_plant_dict['lightDuration']} hrs")
    co2LevelLabel.setText(f"CO2 Level: {selected_plant_dict['co2Level']} ppm")
    flowRateLabel.setText(f"Flow Rate: {selected_plant_dict['flowRate']} L/min")

    # Position labels dynamically
    labels = [
        plantNameLabel, airTempLabel, waterTempLabel, humidityLabel,
        lightIntensityLabel, lightDurationLabel, co2LevelLabel, flowRateLabel
    ]

    for i, label in enumerate(labels):
        # label.setPosition(base_x, base_y + (i * spacing))
        label.setVisible(True)  # Ensure it's visible



#Hide all labels except home labels
def MAIN():
    global home, Row1Label, Row2Label, option, label4, paramLabel, valueLabel, plant1Label, plant2Label, plant3Label, plant4Label, SUFLabel, powerLabel, angle_0
    # home.setImage("res/img/bg.png")
    hide_except([SUFLabel, powerLabel, home])
    powerLabel.setText('Powered by Aquaponics')
    SUFLabel.setText('Smart Urban Farming')


def SEND():
  hide_except([sendLabel,flowRateLabel, co2LevelLabel, lightDurationLabel, lightIntensityLabel, humidityLabel, waterTempLabel, airTempLabel])
  display_selected_plant(selectedPlant)
  # Describe this function...
def Options():
    global rotateRow,LEVEL, plant_profiles, angle, selectedPlant, selectedRow, selectedParam, home, Row1Label, Row2Label, option, label4, paramLabel, valueLabel, plant1Label, plant2Label, plant3Label, plant4Label, SUFLabel, powerLabel, angle_0
    
    # Helper function to set label colors
    def set_plant_labels_color(selected_label, other_labels):
        selected_label.setColor(0x330033, 0xffffff)
        for label in other_labels:
            label.setColor(0xffffff, 0x990000)

    if LEVEL == 1:
        # Handle row label colors based on angle
        if angle < 45:
            rotateRow = True
            Row1Label.setColor(0xffffff, 0x5a2a2a)
            Row2Label.setColor(0xffffff, 0x5a2a2a)
        elif angle < 90:
            rotateRow = False

            selectedRow = 1
            Row1Label.setColor(0x330033, 0xffffff)
            Row2Label.setColor(0xffffff, 0x5a2a2a)
        else:
            rotateRow = False
            selectedRow = 2
            Row1Label.setColor(0xffffff, 0x5a2a2a)
            Row2Label.setColor(0x330033, 0xffffff)

    elif LEVEL == 2:
        Row1Label.setVisible(False)
        Row2Label.setVisible(False)

        # Set plant labels based on angle and level
        if angle < 45:
            selectedPlant = plant_profiles['Lettuce']
            set_plant_labels_color(plant1Label, [plant2Label, plant3Label, plant4Label])
        elif angle < 90:
            selectedPlant = plant_profiles['Basil']
            set_plant_labels_color(plant2Label, [plant1Label, plant3Label, plant4Label])
        elif angle < 135:
            selectedPlant = plant_profiles['Strawberry']
            set_plant_labels_color(plant3Label, [plant1Label, plant2Label, plant4Label])
        else:
            selectedPlant = plant_profiles['CUSTOM']
            set_plant_labels_color(plant4Label, [plant1Label, plant2Label, plant3Label])

        option.setText(str(selectedRow))

    elif LEVEL == 3:
        # Set parameters based on angle
        params = [
            ('airTemp', 'Air Temp', selectedPlant['airTemp']),
            ('waterTemp', 'Water Temp', selectedPlant['waterTemp']),
            ('humidity', 'Humidity', selectedPlant['humidity']),
            ('lightIntensity', 'Light Intensity', selectedPlant['lightIntensity']),
            ('lightDuration', 'Light Duration', selectedPlant['lightDuration']),
            ('co2Level', 'CO2 Level', selectedPlant['co2Level']),
            ('flowRate', 'Flow Rate', selectedPlant['flowRate']),
        ]
        for i, (param, label, value) in enumerate(params):
            if angle < (i + 1) * 25:
                selectedParam = param
                paramLabel.setText(label)
                valueLabel.setText(str(value))
                break

        option.setText(str(selectedPlant['plantName']))

    elif LEVEL == 4:
        # Update plant values based on selected parameter
        param_updates = {
            'airTemp': 10 + round(angle / 8),
            'waterTemp': 5 + round(angle / 8),
            'humidity': round(100 * (angle / 180)),
            'lightIntensity': round(angle / 0.09),
            'lightDuration': 10 + round(angle / 90),
            'co2Level': 300 + round(900 * (angle / 180)),
            'flowRate': round(angle / 18)
        }

        if selectedParam in param_updates:
            selectedPlant[selectedParam] = param_updates[selectedParam]
            valueLabel.setText(str(selectedPlant[selectedParam]))

# Describe this function...
def ROW():
    # Use hide_except function to only show relevant labels
    hide_except([Row1Label, Row2Label])

    # Set row labels text
    Row1Label.setText('Row1')
    Row2Label.setText('Row2')

# Describe this function...
def PLANT():
    # Use hide_except function to only show relevant labels
    hide_except([option, plant1Label, plant2Label, plant3Label, plant4Label])

    # Set plant labels text
    plant1Label.setText('Lettuce')
    plant2Label.setText('Basil')
    plant3Label.setText('Strawberry')
    plant4Label.setText('CUSTOM')


# Describe this function...
def PARAM():
    hide_except([option, paramLabel, valueLabel])

def btnA_wasPressed_event(state):
    global LEVEL, rotateRow, SUFLabel

    # Increment LEVEL, reset to 0 if it exceeds 4
    LEVEL += 1

    # Call appropriate function based on LEVEL
    if LEVEL == 1:
        ROW()
    elif LEVEL == 2:
      if rotateRow:
        LEVEL = 0
        hide_except([SUFLabel])
        for i in range(10):
          SUFLabel.setText("Rotating Row...")
          time.sleep_ms(500)
          SUFLabel.setText("")
          time.sleep_ms(500)
        SUFLabel("Smart Urban Farming")
        MAIN()
      else:
        PLANT()
    elif LEVEL == 3:
        PARAM()
    elif LEVEL == 5:
      SEND()
    elif LEVEL > 5:  # LEVEL > 4
        LEVEL = 0
        MAIN()
def btnB_wasPressed_event(state):
  global clicked
  print("beans")
  if LEVEL == 5:
    clicked = True

def btnC_wasPressed_event(state):
    global LEVEL

    # Decrement LEVEL, reset to 0 if it goes below 1
    LEVEL -= 1
    if LEVEL == 1:
        ROW()
    elif LEVEL == 2:
        PLANT()
    elif LEVEL == 3:
        PARAM()
    elif LEVEL == 4:
      PARAM()
    elif LEVEL ==5:
      SEND()
    else:
        LEVEL = 0
        MAIN()



def setup():
  global sendLabel, plantNameLabel, airTempLabel, waterTempLabel, humidityLabel, espnow_0, timer0,wlan, mqtt_client
  global lightIntensityLabel, lightDurationLabel, co2LevelLabel, flowRateLabel
  global tableLabel, home, Row1Label, Row2Label, option, label4, paramLabel, valueLabel, plant1Label, plant2Label, plant3Label, plant4Label, SUFLabel, powerLabel, angle_0, LEVEL, plant_profiles, angle, selectedPlant, selectedRow, selectedParam

  if MQTT:
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, WLAN_PW)
 

  M5.begin()
  Widgets.fillScreen(0x5a2a2a)

  # Initialize widgets
  home = Widgets.Image("res/img/default.png", 0, -30, scale_x=.4, scale_y=.4)
  Row1Label = Widgets.Label("Row1", 131, 71, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  Row2Label = Widgets.Label("Row2", 131, 108, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  option = Widgets.Label("label0", 0, 0, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu24)
  label4 = Widgets.Label("label4", 282, 215, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  paramLabel = Widgets.Label("Param:", 85, 63, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu24)
  valueLabel = Widgets.Label("value", 134, 121, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  plant1Label = Widgets.Label("plant1", 120, 50, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  plant2Label = Widgets.Label("pant2", 135, 100, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  plant3Label = Widgets.Label("plant3", 105, 145, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  plant4Label = Widgets.Label("plant4", 127, 191, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  SUFLabel = Widgets.Label("Smart Urban Farming", 22, 94, 1.0, 0xffffff, 0x54ca2c, Widgets.FONTS.DejaVu24)
  powerLabel = Widgets.Label("Powered by Aquaponics", 46, 121, 1.0, 0xffffff, 0x32637e, Widgets.FONTS.DejaVu18)
  tableLabel = Widgets.Label("Table", 0,0,1.0,0xffffff, 0x32637e, Widgets.FONTS.DejaVu18)
  plantNameLabel = Widgets.Label("", 20, 50, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  airTempLabel = Widgets.Label("", 20, 25, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  waterTempLabel = Widgets.Label("", 20, 50, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  humidityLabel = Widgets.Label("", 20, 75, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  lightIntensityLabel = Widgets.Label("", 20, 100, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  lightDurationLabel = Widgets.Label("", 20, 125, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  co2LevelLabel = Widgets.Label("", 20, 150, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  flowRateLabel = Widgets.Label("", 20, 175, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)
  sendLabel = Widgets.Label("send", 133, 214, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu18)

  
  if MQTT:
    while not wlan.isconnected():
        print("Connecting to Wi-Fi...")
        time.sleep(1)
    print("Connected to Wi-Fi")
    
    try:
        mqtt_client = MQTTClient('TacoMaster', 'test.mosquitto.org', port=1883, user='', password='', keepalive=60)
        mqtt_client.connect()
    except Exception as e:
        print(f"MQTT connection error: {str(e)}")


  # Set button callbacks
  BtnA.setCallback(type=BtnA.CB_TYPE.WAS_PRESSED, cb=btnA_wasPressed_event)
  BtnB.setCallback(type=BtnB.CB_TYPE.WAS_PRESSED, cb=btnB_wasPressed_event)
  BtnC.setCallback(type=BtnC.CB_TYPE.WAS_PRESSED, cb=btnC_wasPressed_event)

  espnow_0 = M5ESPNow(0)
  for index, mac in enumerate(ROW_MAC_LIST):
      espnow_0.set_add_peer(mac, (index + 1), 0, False)
      print(f"Peer added: {mac} with ID: {index + 1}")

  print(f"Peer added: 2cbcbb81f7c0 with ID: 1")


  # Initialize plant profiles
  plant_profiles = {
    'Lettuce': {'plantName': 'Lettuce', 'airTemp': 22, 'waterTemp': 18, 'humidity': 70, 'lightIntensity': 400, 'lightDuration': 16, 'co2Level': 400, 'flowRate': 1},
    'Basil': {'plantName': 'Basil', 'airTemp': 25, 'waterTemp': 22, 'humidity': 65, 'lightIntensity': 500, 'lightDuration': 14, 'co2Level': 450, 'flowRate': 1.2},
    'Strawberry': {'plantName': 'Strawberry', 'airTemp': 20, 'waterTemp': 19, 'humidity': 75, 'lightIntensity': 350, 'lightDuration': 12, 'co2Level': 350, 'flowRate': 1.5},
    'CUSTOM': {'plantName': 'CUSTOM', 'airTemp': 0, 'waterTemp': 0, 'humidity': 0, 'lightIntensity': 0, 'lightDuration': 0, 'co2Level': 0, 'flowRate': 0}
  }
  timer0 = Timer(0)

  timer0.init(mode=Timer.PERIODIC, period=500, callback=timer0_cb)

  LEVEL = 0
  angle_0 = AngleUnit((36, 26))


  # Start the main process
  MAIN()



def loop():
  global home, Row1Label, Row2Label, option, label4, paramLabel, valueLabel, plant1Label, plant2Label, plant3Label, plant4Label, SUFLabel, powerLabel, angle_0, LEVEL, plant_profiles, angle, selectedPlant, selectedRow, selectedParam
  
  M5.update()
  # get to roughly between 0 and 180
  angle = (angle_0.get_value()) / 364
  label4.setText(str(LEVEL))
  Options()


if __name__ == '__main__':
  try:
    setup()
    while True:
      loop()
  except (Exception, KeyboardInterrupt) as e:
    try:
      from utility import print_error_msg
      print_error_msg(e)
    except ImportError:
      print("please update to latest firmware")

Secondary M5 Code

Python
Code of the secondary unit. Receives ESPNow (or MQTT) data and updates the stored params. Will only share one, because only thing to change between rows is ROW_NO
import os, sys, io
import M5
from M5 import *
from m5espnow import M5ESPNow
from unit import LightUnit
from hardware import I2C
from hardware import Pin
from unit import AngleUnit
from unit import ENVUnit
import time
import network
from umqtt.simple import MQTTClient
import json
from hardware import Timer



title = None
sensorLight = None
sensorTemp = None
sensorHumidity = None
LIGHT_label = None
AIR_label = None
HUMIDITY_label = None
dataLabel1 = None
plantname = None
dataLabel2 = None
airTemp = None
dataLabel3 = None
waterTemp = None
dataLabel4 = None
humidity = None
dataLabel5 = None
lightIntensity = None
dataLabel6 = None
lightDuration = None
dataLabel7 = None
co2Level = None
dataLabel8 = None
flowRate = None
espnow_0 = None
i2c1 = None
env3_0 = None
angle_0 = None
light_0 = None


light_label = None
LUX = None
LIGHT = None
AIR = None
HUMIDITY = None
stored_params = None
sensor_data = None


stored_params = {
    "plantName": "Lettuce",
    "airTemp": 22.0,
    "waterTemp": 18.0,
    "humidity": 70.0,
    "lIntensity": 400,
    "lDuration": 16,
    "co2Level": 400.0,
    "flowRate": 1.0,
}

sensor_data = {
    "airTemp": 0.0,
    "waterTemp": 0.0,
    "humidity": 0.0,
    "lIntensity": 0,
    "lDuration": 0,
    "co2Level": 0.0,
    "flowRate": 0.0,
}

espnow_0 = None
espnow_mac = None
espnow_data = None
ROW_NO = 1

use_mqtt = True  #Only subscribe to MQTT topic if true
SSID = "WIFI"
WLAN_PW = "WIFI_PASSWORD"
mqtt_client = None
MQTT = None


#publishes sensor data to mqtt reqularly
def timer0_cb(t):
  try:
      json_data = json.dumps(sensor_data)
      print(f'sent {json_data}')
      publish_with_retry(f'm5stack/data/row{ROW_NO}/dataList', json_data)
  except Exception as e:
      print(f"Publish error: {e}")
  time.sleep_ms(5)

#publishes to mqtt with 3 retreis
def publish_with_retry(topic, message, retries=3):
    global mqtt_client
    for attempt in range(retries):
        try:
            mqtt_client.publish(topic, message, qos=1)
            print(f"Published: {message} to {topic}")
            return
        except Exception as e:
            print(f"Publish failed ({attempt + 1}/{retries}): {e}")
            if not reconnect_mqtt():
                break
    print("Failed to publish message after retries")

# MQTT Reconnect Function
def reconnect_mqtt():
    global mqtt_client
    try:
        mqtt_client.connect()
        print("MQTT reconnected")
        return True
    except Exception as e:
        print(f"Failed to reconnect MQTT: {e}")
        return False


#Shows sensor data labels 
def SensorData():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  dataLabel1.setVisible(False)
  dataLabel2.setVisible(False)
  dataLabel3.setVisible(False)
  dataLabel4.setVisible(False)
  dataLabel5.setVisible(False)
  dataLabel6.setVisible(False)
  dataLabel7.setVisible(False)
  dataLabel8.setVisible(False)
  plantname.setVisible(False)
  airTemp.setVisible(False)
  waterTemp.setVisible(False)
  humidity.setVisible(False)
  lightIntensity.setVisible(False)
  lightDuration.setVisible(False)
  co2Level.setVisible(False)
  flowRate.setVisible(False)
  sensorLight.setVisible(True)
  sensorTemp.setVisible(True)
  sensorHumidity.setVisible(True)
  LIGHT_label.setVisible(True)
  AIR_label.setVisible(True)
  HUMIDITY_label.setVisible(True)
  sensorLight.setText(f"LightIntensity: {sensor_data['lIntensity']} µmol/m²/s")
  sensorTemp.setText(f"AirTemperature: {sensor_data['airTemp']} C")
  sensorHumidity.setText(f"Humidity: {sensor_data['humidity']} %")

# displayes stored params
def DataDisplay():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  sensorLight.setVisible(False)
  sensorTemp.setVisible(False)
  sensorHumidity.setVisible(False)
  LIGHT_label.setVisible(False)
  AIR_label.setVisible(False)
  HUMIDITY_label.setVisible(False)
  dataLabel1.setVisible(True)
  dataLabel2.setVisible(True)
  dataLabel3.setVisible(True)
  dataLabel4.setVisible(True)
  dataLabel5.setVisible(True)
  dataLabel6.setVisible(True)
  dataLabel7.setVisible(True)
  dataLabel8.setVisible(True)
  plantname.setVisible(True)
  airTemp.setVisible(True)
  waterTemp.setVisible(True)
  humidity.setVisible(True)
  lightIntensity.setVisible(True)
  lightDuration.setVisible(True)
  co2Level.setVisible(True)
  flowRate.setVisible(True)
  dataLabel1.setText(str('Plantname:'))
  dataLabel2.setText(str('AirTemperature:'))
  dataLabel3.setText(str('WaterTemperature:'))
  dataLabel4.setText(str('Humidity:'))
  dataLabel5.setText(str('LightIntensity:'))
  dataLabel6.setText(str('LightDuration:'))
  dataLabel7.setText(str('CO2Level:'))
  dataLabel8.setText(str('FlowRate:'))
  plantname.setText(f"{stored_params['plantName']}")
  airTemp.setText(f"{stored_params['airTemp']} C")
  waterTemp.setText(f"{stored_params['waterTemp']} C")
  humidity.setText(f"{stored_params['humidity']}%")
  lightIntensity.setText(f"{stored_params['lIntensity']} µmol/m²/s")
  lightDuration.setText(f"{stored_params['lDuration']} hrs")
  co2Level.setText(f"{stored_params['co2Level']} ppm")
  flowRate.setText(f"{stored_params['flowRate']} L/min")

# simple sensor normalization
def calc():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  LUX = 65535 - (light_0.get_analog_value())
  LUX = LUX / 65535
  LIGHT = int(LUX * 1000)
  AIR = int(env3_0.read_temperature())
  HUMIDITY = int(env3_0.read_humidity())
  sensor_data['lIntensity'] = LIGHT
  sensor_data['airTemp'] = AIR
  sensor_data['humidity'] = HUMIDITY
  SensorData()
  Light()
  AirTemp()
  Humidity()
  time.sleep(10)
  DataDisplay()
  time.sleep(3)

# Check necessary change in light
def Light():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  if LIGHT < (stored_params['lIntensity']): 
    LIGHT_label.setText(str('Increasing Light Intensity'))
  elif LIGHT > (stored_params['lIntensity']):
    LIGHT_label.setText(str('Decreasing Light Intensity'))
  else:
    LIGHT_label.setVisible(False)

# Check necessary change in airtemp
def AirTemp():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  if AIR < (stored_params['airTemp']):
    AIR_label.setText(str('Increasing Air Temperature'))
  elif AIR > (stored_params['airTemp']):
    AIR_label.setText(str('Decreasing Air Temperature'))
  else:
    AIR_label.setVisible(False)

#Check necessary change in humidity
def Humidity():
  global light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data, title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0
  if HUMIDITY < (stored_params['humidity']):
    HUMIDITY_label.setText(str('Increasing Humidity'))
  elif HUMIDITY > (stored_params['humidity']):
    HUMIDITY_label.setText(str('Decreasing Humidity'))
  else:
    HUMIDITY_label.setVisible(False)

#duh
def setupLabels():
  global title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0, light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data
  
  Widgets.fillScreen(0x5a2a2a)
  title = Widgets.Title(f"ROW {ROW_NO}", 244, 0xffffff, 0x8d1a5c, Widgets.FONTS.DejaVu18)
  sensorLight = Widgets.Label("", 78, 36, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  sensorTemp = Widgets.Label("", 65, 95, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  sensorHumidity = Widgets.Label("", 108, 161, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  LIGHT_label = Widgets.Label("", 40, 62, 1.0, 0xffffff, 0xce4a4a, Widgets.FONTS.DejaVu18)
  AIR_label = Widgets.Label("", 29, 126, 1.0, 0xffffff, 0xce4a4a, Widgets.FONTS.DejaVu18)
  HUMIDITY_label = Widgets.Label("", 62, 187, 1.0, 0xffffff, 0xce4a4a, Widgets.FONTS.DejaVu18)
  dataLabel1 = Widgets.Label("Plantname:", 79, 43, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  plantname = Widgets.Label("", 179, 43, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel2 = Widgets.Label("AirTemperature:", 47, 64, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  airTemp = Widgets.Label("", 179, 64, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel3 = Widgets.Label("WaterTemperature:", 25, 85, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  waterTemp = Widgets.Label("", 179, 85, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel4 = Widgets.Label("Humidity:", 92, 105, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  humidity = Widgets.Label("", 179, 105, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel5 = Widgets.Label("LightIntensity:", 60, 125, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  lightIntensity = Widgets.Label("", 179, 125, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel6 = Widgets.Label("LightDuration:", 59, 147, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  lightDuration = Widgets.Label("", 179, 147, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel7 = Widgets.Label("CO2Level:", 88, 166, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  co2Level = Widgets.Label("", 179, 166, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  dataLabel8 = Widgets.Label("FlowRate:", 90, 186, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)
  flowRate = Widgets.Label("", 179, 186, 1.0, 0xffffff, 0x5a2a2a, Widgets.FONTS.DejaVu12)

  espnow_0 = M5ESPNow(0)
  i2c1 = I2C(1, scl=Pin(22), sda=Pin(21), freq=100000)
  light_0 = LightUnit((36, 26))
  angle_0 = AngleUnit((36, 26))
  env3_0 = ENVUnit(i2c=i2c1, type=3)


def mqtt_callback(topic, msg):
    # Handle incoming MQTT message
    try:
        topic = topic.decode('utf-8')
        msg = msg.decode('utf-8')
        print(f"Received MQTT message on topic {topic}: {msg}")
        
        topic_parts = topic.split('/')
        if len(topic_parts) >= 3 and topic_parts[1] == f"row{ROW_NO}":
            param_name = topic_parts[2]

            # Handle JSON payloads or single values
            if msg.startswith('{') and msg.endswith('}'):
                try:
                    json_data = json.loads(msg)
                    stored_params.update(json_data)
                    print(f"Updated parameters: {json_data}")
                except json.JSONDecodeError as e:
                    print(f"JSON decoding error: {e}")
            else:
                print("single param")
                # Handle single parameter updates
                print(param_name)
                if param_name in stored_params:
                    # Update parameter with type conversion if possible
                    stored_params[param_name] = (
                        float(msg) if msg.replace('.', '', 1).isdigit() else msg
                    )
                    print(f"Updated {param_name} to {stored_params[param_name]}")
                else:
                    print(f"Unknown parameter: {param_name}")

            DataDisplay()
        else:
            print(f"Irrelevant topic: {topic}")
    except Exception as e:
        print(f"MQTT callback error: {e}")
def espnow_recv_callback(espnow_obj):
    # Run when espnow data recieved
    global espnow_mac, espnow_data
    print("got")
    espnow_mac, espnow_data = espnow_obj.recv_data()
    print(espnow_data, espnow_mac)
    
    if espnow_data:
        try:
            espnow_data_dict = json.loads(espnow_data.decode('utf-8'))
            stored_params.update(espnow_data_dict)
            print("ESP-NOW data updated:", espnow_data_dict)
        except Exception as e:
            print(e)
            print("Error decoding ESP-NOW data")
    else:
      print("espnow error bruh")
      
    DataDisplay()

def setup_mqtt():
    # Set up the MQTT
    global mqtt_client
    try:
        mqtt_client = MQTTClient('Subscriber', 'test.mosquitto.org', port=1883)
        mqtt_client.set_callback(mqtt_callback)
        mqtt_client.connect()
        mqtt_client.subscribe(f'm5stack/row{ROW_NO}/#')
        print(f"Subscribed to topic: m5stack/row{ROW_NO}/#")
    except Exception as e:
        print(f"MQTT connection error: {str(e)}")



def setup():
  global espnow_0
  M5.begin()
  setupLabels()
  
  
  # Initialize ESP-NOW
  espnow_0 = M5ESPNow(0)
  espnow_0.set_irq_callback(espnow_recv_callback)
  print("ESP-NOW initialized")

  # Initialize MQTT if enabled
  if use_mqtt:
      connect_wifi()

      setup_mqtt()
      timer0 = Timer(0)

      timer0.init(mode=Timer.PERIODIC, period=5000, callback=timer0_cb)

def connect_wifi():
    # Connect to Wi-Fi
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, WLAN_PW)
    
    while not wlan.isconnected():
        print("Connecting to Wi-Fi...")
        time.sleep(1)
    
    print("Connected to Wi-Fi")


def loop():
  global title, sensorLight, sensorTemp, sensorHumidity, LIGHT_label, AIR_label, HUMIDITY_label, dataLabel1, plantname, dataLabel2, airTemp, dataLabel3, waterTemp, dataLabel4, humidity, dataLabel5, lightIntensity, dataLabel6, lightDuration, dataLabel7, co2Level, dataLabel8, flowRate, espnow_0, i2c1, rgb, env3_0, angle_0, light_0, light_label, LUX, LIGHT, AIR, HUMIDITY, stored_params, sensor_data
  M5.update()
  calc()


if __name__ == '__main__':
  try:
    setup()
    while True:
      loop()
  except (Exception, KeyboardInterrupt) as e:
    try:
      from utility import print_error_msg
      print_error_msg(e)
    except ImportError:
      print("please update to latest firmware")

Credits

Doris Thadar
1 project • 3 followers
Contact
nathan
0 projects • 3 followers
Contact
Preo Tan
0 projects • 3 followers
Contact
Nicholas Dubs
0 projects • 3 followers
Contact

Comments

Please log in or sign up to comment.