diff --git a/README.md b/README.md index 2c2e0fc..e7ea699 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ The classes that are currently supported are: - umqttsimple.py - implementation of simple MQTT protocol for MicroPython. - ads1x15.py - ADS1x15 precide Analog-to-digital chip (available on extension board). +- mcp23017.py - MCP23017 16-bit IO Expander (available on extension board). - at24c02.py - AT24C02 EEPROM module. - bh1750.py - BH1750 photodiode (light sensor). - bme280.py - 3 in 1: temperature, humidity and barometric sensor. - ds1307.py - DS1307 RTC module. -- mcp23017.py - MCP23017 16-bit IO Expander (available on extension board). - pcf8574.py - PCF8574 8-bit IO Expander (available on valve extension board). - tds.py - To control TDS sensor through extension board. diff --git a/examples/eduponics_mqtt/boot.py b/examples/eduponics_mqtt/boot.py new file mode 100644 index 0000000..e1c2e92 --- /dev/null +++ b/examples/eduponics_mqtt/boot.py @@ -0,0 +1,43 @@ +""" +MicroPython MQTT Eduponics APP Client +https://github.com/STEMinds/micropython-eduponics +MIT License +Copyright (c) 2020 STEMinds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from umqttsimple import MQTTClient +import network +import esp +esp.osdebug(None) +import gc +gc.collect() + +ssid = 'WIFI_NAME' +password = 'WIFI_PASSWORD' + +station = network.WLAN(network.STA_IF) + +station.active(True) +station.connect(ssid, password) + +while station.isconnected() == False: + pass + +print('Connected to WiFi successfully, IP: %s' % station.ifconfig()[0]) diff --git a/examples/eduponics_mqtt/main.py b/examples/eduponics_mqtt/main.py new file mode 100644 index 0000000..ef85549 --- /dev/null +++ b/examples/eduponics_mqtt/main.py @@ -0,0 +1,218 @@ +""" +MicroPython MQTT Eduponics APP Client +https://github.com/STEMinds/micropython-eduponics +MIT License +Copyright (c) 2020 STEMinds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from Eduponics import umqttsimple,bh1750,bme280 +import machine +import time +import json + +# set adc (analog to digital) on pin 35 +adc = machine.ADC(machine.Pin(35)) +# set 11dB input attenuation (voltage range roughly 0.0v - 3.6v) +adc.atten(machine.ADC.ATTN_11DB) + +# Configure light sensor +light_sensor = bh1750.BH1750() + +# Configure BME280 +# setup I2C connection +i2c = machine.I2C(scl=machine.Pin(15), sda=machine.Pin(4)) +# Initialize BME280 object with default address 0x76 +bme_sensor = bme280.BME280(i2c=i2c) +# initialize dht object, DHT11 coonected to IO19 +#d = dht.DHT11(machine.Pin(19)) + +# define water level sensor as INPUT on IO pin number 21 +water_level = machine.Pin(21, machine.Pin.IN) + +# define pump on pin IO23 as OUTPUT, define pump state +pump = machine.Pin(23, machine.Pin.OUT) +pump_state = False + +# MQTT Unique ID +UUID = "YOUR_UUID_GENERATED_ID" +# MQTT Topics +topics = ["plants/soil","plants/environment","plants/water"] + +def handle_water_level(pin): + global pump_state + # water level triggered, turn off the pump + # wait for 0.3 seconds to make sure it's just a little below the water sensor + # else the pump might become unstable + time.sleep(0.3) + pump.value(0) + pump_state = False + +def get_soil_moisture(): + + # sensor min and max values + # can be changed and callibrated + minVal = 710 + maxVal = 4095 + + # read soil moisture sensor data + val = adc.read() + + # scale the value based on maxVal and minVal + scale = 100 / (minVal - maxVal) + # get calculated scale + normal_reading = ("%s%s" % (int((val - maxVal) * scale),"%")) + # we can also get inverted value if needed + inverted_reading = ("%s%s" % (int((minVal - val) * scale),"%")) + # for this example we'll return only the normal reading + # put everything in a JSON format suitable for the eduponics app + plant = { + "id":0, + "name":"Plant A", + "enabled":1, + "moisture":normal_reading + } + # return the data + return str(plant).replace("'",'"') + +def get_environmental_data(): + + # get light from the light sensor + lux = int(light_sensor.readLight()) + # get bme280 sensor data + bme280_values = bme_sensor.values + temperature = bme280_values[0].replace("C","") + pressure = bme280_values[1] + humidity = bme280_values[2].replace("%","") + # get DHT11 sensor data + # measure sensor data + #d.measure() + # get temperature and humidity + #temperature = d.temperature() + #humidity = d.humidity() + # get water quantity + water_quantity = water_level.value() + + # put all this data into a JSON object + data = { + "temp":temperature, + "humidity":humidity, + "sunlight":lux, + "water_quantity":water_quantity + } + + return str(data).replace("'",'"') + +def water_plant(): + global pump_state + if(pump_state or water_level.value() == 1): + # turn off the pump + pump.value(0) + pump_state = False + else: + # turn on the pump + pump.value(1) + pump_state = True + return True + +def on_message_callback(topic, msg): + ''' + this is a callback, will be called when the app asks for certain information + such as to water the plants when the watering button pressed + ''' + # convert topic and message byte to string + topic = str(topic, 'utf-8') + msg = json.loads(str(msg, 'utf-8')) + + if(topic == "%s/plants/soil" % UUID or topic == "%s/plants/environment" % UUID): + # Do nothing, we only publish to those topics + pass + elif(topic == "%s/plants/water" % UUID): + # when the app request for plant watering it goes here + if("key" in msg and "status" in msg): + # valid request, let's process it + if(msg["status"] == "pending"): + # it's waiting for us to water it, let's water it + water_plant() + # after watering, publish success message of watering + response = {"key":msg["key"],"status":"ok"} + client.publish("%s/plants/water" % UUID, str(response).replace("'",'"')) + else: + print((topic, msg)) + +def connect_and_subscribe(): + + print("[-] Connecting to MQTT client ...") + # set the MQTT broker object + client = umqttsimple.MQTTClient() + # set a callback for incoming messages (subscribed topics) + client.set_callback(on_message_callback) + # connect to the broker + client.connect() + + # subscribe to the topics + for topic in topics: + client.subscribe("%s/%s" % (UUID,topic)) + print("[-] Subscribed to %s successfully" % topic) + print("[-] Connected to %s MQTT broker successfully" % client.server) + + return client + +def restart_and_reconnect(): + # something went wrong, reconnect in 5 seconds ... + print('[-] Failed to connect to MQTT broker. Reconnecting...') + time.sleep(5) + machine.reset() + +try: + client = connect_and_subscribe() +except OSError as e: + restart_and_reconnect() + +# configure few variables +last_message = 0 +message_interval = 5 +# set callback on the water level sensor, if no water stop the pump +water_level.irq(trigger=machine.Pin.IRQ_RISING, handler=handle_water_level) + +while True: + try: + # check if there are new messages pending to be processed + # if there are, redirect them to callback on_message_callback() + client.check_msg() + + # check if the last published data wasn't less than message_interval + if (time.time() - last_message) > message_interval: + # get soil moisture + soil_moisture = get_soil_moisture() + # publish soil moisture data + client.publish("%s/plants/soil" % UUID, soil_moisture) + #print("[-] published soil moisture") + + # update environmetal data + env = get_environmental_data() + client.publish("%s/plants/environment" % UUID, env) + #print("[-] published evironmental data") + + # update last message timestamp + last_message = time.time() + + except OSError as e: + # if something goes wrong, reconnct to MQTT server + restart_and_reconnect() diff --git a/examples/eduponics_mqtt/uuid_generator.py b/examples/eduponics_mqtt/uuid_generator.py new file mode 100644 index 0000000..52c5a2e --- /dev/null +++ b/examples/eduponics_mqtt/uuid_generator.py @@ -0,0 +1,41 @@ +""" +MicroPython MQTT Eduponics APP Client +https://github.com/STEMinds/micropython-eduponics +MIT License +Copyright (c) 2020 STEMinds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import uuid + +''' +There are 3 different ways to generate unique UUID, choose which one suitable for you. +''' + +# make a UUID based on the host ID and current time, best option. +uuid_x = uuid.uuid1() +print(uuid_x) + +# make a UUID using an MD5 hash of a namespace UUID and a name +uuid_y = uuid.uuid3(uuid.NAMESPACE_DNS, 'steminds.com') +print(uuid_y) + +# make a random UUID +uuid_z = uuid.uuid4() +print(uuid_z) diff --git a/examples/mcp23017_relays.py b/extensions/extension_board/mcp23017_relays.py similarity index 100% rename from examples/mcp23017_relays.py rename to extensions/extension_board/mcp23017_relays.py diff --git a/examples/mcp23017_soil_moisture.py b/extensions/extension_board/mcp23017_soil_moisture.py similarity index 100% rename from examples/mcp23017_soil_moisture.py rename to extensions/extension_board/mcp23017_soil_moisture.py diff --git a/examples/valve_pcf8574.py b/extensions/valve_extension_board/valve_pcf8574.py similarity index 100% rename from examples/valve_pcf8574.py rename to extensions/valve_extension_board/valve_pcf8574.py diff --git a/qa/eduponics_mini_qa.py b/qa/eduponics_mini_qa.py new file mode 100644 index 0000000..082d34f --- /dev/null +++ b/qa/eduponics_mini_qa.py @@ -0,0 +1,248 @@ +""" +MicroPython QA file to test all the sensors on the Eduponics Mini +https://github.com/STEMinds/micropython-eduponics +MIT License +Copyright (c) 2020 STEMinds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import machine +from machine import I2C,ADC,Pin +from Eduponics import bme280,at24c02,ds1307,bh1750 +import neopixel +import time +import esp32 +import dht + +# configure wakeup deep sleep functionality +wake_up = Pin(36, mode = Pin.IN) +# level parameter can be: esp32.WAKEUP_ANY_HIGH or esp32.WAKEUP_ALL_LOW +esp32.wake_on_ext0(pin = wake_up, level = esp32.WAKEUP_ANY_HIGH) + +# configure neopixel +np = neopixel.NeoPixel(Pin(14), 1) + +# define water level sensor as INPUT on IO pin number 21 +water_level = Pin(21, Pin.IN) + +# define pump on pin IO23 as OUTPUT +pump = Pin(23, Pin.OUT) + +# initialize dht object, DHT11 coonected to IO19 +#d = dht.DHT11(Pin(19)) + +# setup I2C connection +i2c = I2C(scl=Pin(15), sda=Pin(4)) + +def test_eeprom(): + input("[-] Testing EEPROM ... press enter when ready ...") + print("") + eeprom = at24c02.AT24C32N(i2c) + print("[-] Reading 32 bytes ...") + print(eeprom.read(0, 32)) + print("[-] Writing 11 bytes - 'Hello World' ...") + eeprom.write(0, 'hello world') + print("[-] Reading 11 bytes - 'Hello World' ...") + print(eeprom.read(0, 11)) + # print space to make it pretty + print("") + +def test_dht11(): + input("[-] Testing DHT11 sensor 5 times ... press enter when ready ...") + print("") + # intialize counter + counter = 0 + while counter != 4: + # measure sensor data + d.measure() + # get temperature and humidity + temperature = d.temperature() + humidity = d.humidity() + # wait a second + time.sleep(1) + # update counter + counter = counter + 1 + # print space to make it pretty + print("") + +def test_ds1307(): + # initialize ds object + ds = ds1307.DS1307(i2c) + # activate ds1307 + ds.halt(False) + # set time + now = (2020, 9, 7, 6, 14, 28, 0, 0) + ds.datetime(now) + # set counter + counter = 0 + input("[-] Getting time 5 times, press enter when ready ...") + while counter != 4: + print(ds.datetime()) + time.sleep(1) + counter = counter + 1 + # print space to make it pretty + print("") + +def test_bh1750(): + # initialize sensor + sensor = bh1750.BH1750() + # initialize counter + counter = 0 + input("[-] Testing BH1750 sensor 5 times, press enter when ready ...") + print("") + while counter != 4: + # get light value + value = sensor.readLight() + print("[-] Light in the room: %slx" % value) + # sleep one second + time.sleep(1) + # increase counter + counter = counter + 1 + # print space to make it pretty + print("") + +def test_bme280(): + # Initialize BME280 object with default address 0x76 + sensor = bme280.BME280(i2c=i2c) + # setup counter + counter = 0 + input("[-] Reading BME280 values 5 times, press enter when ready ...") + print("") + while counter != 4: + # get the values from the BME280 library + values = sensor.values + altitude = sensor.altitude + dew_point = sensor.dew_point + sea_level = sensor.sealevel + # print the values every 1 second + print("------------------------") + print("Temperature: %s" % values[0]) + print("Humidity: %s" % values[1]) + print("Pressure: %s" % values[2]) + print("Altitude: %s" % altitude) + print("Dew point: %s" % dew_point) + print("Sea level: %s" % sea_level) + print("------------------------") + print("") + time.sleep(1) + counter = counter + 1 + # print space to make it pretty + print("") + +def test_pump(): + input("[-] Turn on/off the pump 3 times, press enter when ready ...") + print("") + counter = 0 + while counter != 3: + # turn on the pump + print("[-] Pump ON") + pump.value(1) + # wait one second + time.sleep(1) + # turn off the pump + print("[-] Pump OFF") + pump.value(0) + # wait one second + time.sleep(1) + # update counter + counter = counter + 1 + # print space to make it pretty + print("") + +def read_water_level(): + # set counter + counter = 1 + # read water level values + input("[-] Reading water sensor values 5 times, press enter when ready ...") + print("") + while counter != 5: + # will return 0 if container have no water and 1 if it has water + value = water_level.value() + print("[-] Water level: %s" % value) + time.sleep(1) + # update counter + counter = counter + 1 + # print space to make it pretty + print("") + +def test_rgb(): + input("[-] Testing RGB LED, press enter when ready ...") + print("") + print("[-] Setting RGB Red") + np[0] = (255, 0, 0) # set to red, full brightness + np.write() # save changes + time.sleep(1) + print("[-] Setting RGB Green") + np[0] = (0, 175, 0) # set to green, half brightness + np.write() # save changes + time.sleep(1) + print("[-] Setting RGB Blue") + np[0] = (0, 0, 85) # set to blue, quarter brightness + np.write() # save changes + time.sleep(1) + print("[-] Setting RGB OFF") + np[0] = (0, 0, 0) # empty the LED colors (wipe) + np.write() # save changes + # print space to make it pretty + print("") + +def read_soil_moisture(): + # set adc (analog to digital) on pin 35 + adc = ADC(Pin(35)) + # set 11dB input attenuation (voltage range roughly 0.0v - 3.6v) + adc.atten(ADC.ATTN_11DB) + # set counter + counter = 1 + # read soil moisture values + input("[-] Reading soli moisture values 5 times, press enter when ready ...") + print("") + while counter != 5: + # get sensor value + value = adc.read() + print("[-] Soil moisture: %s" % value) + time.sleep(1) + # update counter + counter = counter + 1 + # print space to make it pretty + print("") + +def main(): + # test RGB + test_rgb() + # test soil moisture + read_soil_moisture() + # test pump + test_pump() + # test water level + read_water_level() + # test bme280 + test_bme280() + # test bh1750 + test_bh1750() + # test ds1307 + test_ds1307() + # test EEPROM + test_eeprom() + # test DHT11 + #test_dht11() + # finally, go to deep sleep and test waking up + print('[-] Going to sleep now .. wake me up to test.') + machine.deepsleep() + +# start the main program +main()