From 3c8b59f709f0c2ac088249505471a58be29c194f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maik=20Mu=CC=88ller?= Date: Tue, 7 May 2024 13:23:05 +0200 Subject: [PATCH] code ready --- gs/classes/a_sensors.py | 13 ++++ gs/classes/sensors.py | 13 ++++ gs/classes/sensors/ambilight_sensor.py | 35 ++++++++++ gs/classes/sensors/dht22.py | 37 +++++++++++ gs/classes/sensors/moisture_sensor.py | 39 +++++++++++ gs/config/app.py | 3 +- gs/config/device_config.json | 1 + gs/config/device_config.py | 1 - gs/config/initial_config.py | 2 +- gs/device_info.py | 82 ++++++++++------------- gs/exceptions/not_subscriptable_error.py | 2 + gs/grow_system_api.py | 12 +++- gs/growsystem.py | 83 +++++++++++++++++++++--- gs/http_client.py | 9 +-- gs/little_apache.py | 5 +- gs/sensor_data_manager.py | 24 +++++++ gs/setup.py | 5 +- lib/bh1750.py | 62 ++++++++++++++++++ main.py | 3 +- 19 files changed, 362 insertions(+), 69 deletions(-) create mode 100644 gs/classes/a_sensors.py create mode 100644 gs/classes/sensors.py create mode 100644 gs/classes/sensors/ambilight_sensor.py create mode 100644 gs/classes/sensors/dht22.py create mode 100644 gs/classes/sensors/moisture_sensor.py create mode 100644 gs/config/device_config.json delete mode 100644 gs/config/device_config.py create mode 100644 gs/exceptions/not_subscriptable_error.py create mode 100644 gs/sensor_data_manager.py create mode 100644 lib/bh1750.py diff --git a/gs/classes/a_sensors.py b/gs/classes/a_sensors.py new file mode 100644 index 0000000..af4244e --- /dev/null +++ b/gs/classes/a_sensors.py @@ -0,0 +1,13 @@ +class Sensors: + # this is a parent class for the Sensor classes + sensor_pin_int = -1 + + sensor = None + + type = "unset" + + def __init__(self, settings): + self.type = settings['type'] + print("Initialize " + self.type + " sensor. Sensor pin is: " + str(settings['pin_int'])) + + diff --git a/gs/classes/sensors.py b/gs/classes/sensors.py new file mode 100644 index 0000000..af4244e --- /dev/null +++ b/gs/classes/sensors.py @@ -0,0 +1,13 @@ +class Sensors: + # this is a parent class for the Sensor classes + sensor_pin_int = -1 + + sensor = None + + type = "unset" + + def __init__(self, settings): + self.type = settings['type'] + print("Initialize " + self.type + " sensor. Sensor pin is: " + str(settings['pin_int'])) + + diff --git a/gs/classes/sensors/ambilight_sensor.py b/gs/classes/sensors/ambilight_sensor.py new file mode 100644 index 0000000..df1d966 --- /dev/null +++ b/gs/classes/sensors/ambilight_sensor.py @@ -0,0 +1,35 @@ +from gs.classes.a_sensors import Sensors +from machine import Pin, I2C +from utime import sleep +from lib.bh1750 import BH1750 + + +class AmbilightSensor(Sensors): + + + most_recent_values = [] + + def __init__(self, settings): + super().__init__(settings) + print(settings) # TODO remove + self.sensor_pin_int = settings['pin_int'] + # self.sensor = DHT22(Pin(self.sensor_pin_int, Pin.IN, Pin.PULL_UP)) + self.sensor = BH1750(I2C(0, sda=Pin(settings['pin_int_sda']), scl=Pin(settings['pin_int_scl']))) + + def read(self): + try: + measurement = self.sensor.luminance(BH1750.ONCE_HIRES_1) + print("ambilight ..") + print(measurement) + self.most_recent_values = [ + { + 'type': 'ambilight', + 'value': measurement, + 'unit': '-' + }, + ] + except OSError: + print('Ambilight Error reading temperature/humidity. Check wires') + print() + + diff --git a/gs/classes/sensors/dht22.py b/gs/classes/sensors/dht22.py new file mode 100644 index 0000000..464b31d --- /dev/null +++ b/gs/classes/sensors/dht22.py @@ -0,0 +1,37 @@ +from gs.classes.a_sensors import Sensors +from dht import DHT22 +from machine import ADC, Pin + + +class TemperatureHumiditySensor(Sensors): + + sensor = None + + most_recent_values = [] + + def __init__(self, settings): + super().__init__(settings) + print("Initialize dht22 sensor. Sensor pin is: " + str(settings['pin_int'])) + print(settings) + self.sensor_pin_int = settings['pin_int'] + self.sensor = DHT22(Pin(self.sensor_pin_int, Pin.IN, Pin.PULL_UP)) + + def read(self): + try: + self.sensor.measure() + self.most_recent_values = [ + { + 'type': 'temperature', + 'value': self.sensor.temperature(), + 'unit': 'C' + }, + { + 'type': 'humidity', + 'value': self.sensor.humidity(), + 'unit': '%' + } + ] + except OSError: + print('DHT22 Error reading temperature/humidity. Check wires') + print() + diff --git a/gs/classes/sensors/moisture_sensor.py b/gs/classes/sensors/moisture_sensor.py new file mode 100644 index 0000000..145cf9a --- /dev/null +++ b/gs/classes/sensors/moisture_sensor.py @@ -0,0 +1,39 @@ +# Moisture Sensor Class +from gs.classes.a_sensors import Sensors +from machine import ADC, Pin + + +class MoistureSensor(Sensors): + + sensor = None + + most_recent_values = [] + + min_raw_value = None + max_raw_value = None + + def __init__(self, sensor_data, min_raw_value=300, max_raw_value=65535): + super().__init__(sensor_data) + print("Initialize moisture sensor. Sensor pin is: " + str(sensor_data['pin_int'])) + self.sensor_pin_int = sensor_data['pin_int'] + self.min_raw_value = min_raw_value + self.max_raw_value = max_raw_value + self.sensor = ADC(Pin(self.sensor_pin_int)) + + def read(self): + self.most_recent_values = [ + { + 'type': 'moisture', + 'value': self.convert_to_moisture_percentage(self.sensor.read_u16()), + 'unit': '%' + }, + ] + + def normalize_sensor_value(self, raw_value): + return (raw_value - self.min_raw_value) / (self.max_raw_value - self.min_raw_value) + + def convert_to_moisture_percentage(self, raw_value): + normalized_value = self.normalize_sensor_value(raw_value) + return round(100 - normalized_value * 100, 1) + + diff --git a/gs/config/app.py b/gs/config/app.py index 8a5b5e3..f066dbd 100644 --- a/gs/config/app.py +++ b/gs/config/app.py @@ -1 +1,2 @@ -api_url = 'https://growsystem.muellerdev.kozow.com/api/' +api_url = 'api.growsystem.muellerdev.kozow.com' +read_secs = 1 * 60 diff --git a/gs/config/device_config.json b/gs/config/device_config.json new file mode 100644 index 0000000..901d060 --- /dev/null +++ b/gs/config/device_config.json @@ -0,0 +1 @@ +{"sensors": [{"pin_int": 15, "type": "dht22"}, {"pin_int": 26, "type": "moisture"}], "device_id": 9, "name": "Fulltest1", "token": "uStIrOgScrpbUr0Y", "user_id": 1} \ No newline at end of file diff --git a/gs/config/device_config.py b/gs/config/device_config.py deleted file mode 100644 index 4111c92..0000000 --- a/gs/config/device_config.py +++ /dev/null @@ -1 +0,0 @@ -sensors = [{"pin_int_sda": 8, "type": "ambilight", "pin_int": 8, "pin_int_scl": 9}, {"pin_int": 15, "type": "dht22"}, {"pin_int": 26, "type": "moisture"}] \ No newline at end of file diff --git a/gs/config/initial_config.py b/gs/config/initial_config.py index 9a3b791..9cd4fe6 100644 --- a/gs/config/initial_config.py +++ b/gs/config/initial_config.py @@ -1 +1 @@ -config = {"pin": "1234", "password": "95%04-MM", "ssid": "Oppa-95.lan"} \ No newline at end of file +config = {"ssid": "Oppa-95.lan", "user_id": "1", "password": "95%04-MM", "pin": 9534} \ No newline at end of file diff --git a/gs/device_info.py b/gs/device_info.py index eb4c1da..93f89aa 100644 --- a/gs/device_info.py +++ b/gs/device_info.py @@ -4,47 +4,9 @@ import json class DeviceInfo: - # Device Infos - name = "Dev Device 1" - - token = "PC]-0Bmp83h7F5#U!D6KJ(A&" - - #server_url = 'api.growsystem.muelleronline.org' - server_url = 'api.growsystem.muellerdev.kozow.com' - - wlan_ssid = 'Oppa-95.lan' - wlan_pw = '95%04-MM' - - sensors = [ - { - 'type': 'moisture', - 'pin_int': 26 - }, - { - 'type': 'ambilight', - 'pin_int': 8, # for compatibility only - 'pin_int_sda': 8, - 'pin_int_scl': 9 - }, - - - # { - # 'type': 'dht22', - # 'pin_int': 15 - # } - ] - - engines = [ - { - 'type': 'pump', - 'pins': [15] - } - ] - - read_secs = 5 - # Device Infos End - wlan = network.WLAN(network.STA_IF) + + app_version = "1.0.0" def get_macaddress(self): return self._format_mac(self.wlan.config('mac').hex()) @@ -54,13 +16,29 @@ class DeviceInfo: def get_all_device_infos(self): return { - 'name': self.name, + 'name': self.get_name(), 'mac_address': self.get_macaddress(), 'ip_address': self.get_ipaddress(), - 'token': self.token} + 'token': self.get_token()} - def config(self): - return self._loadConfig() + def config(self, filepath=None): + return self._loadJsonConfig(filepath) + + def app_config(self): + import gs.config.app as app_config + return app_config + + def server_url(self): + return self.app_config().api_url + + def get_token(self): + return self.config()['token'] if self.config() and self.config()['token'] else '' + + def get_name(self): + return self.config()['name'] if self.config() and self.config()['name'] else '' + + def get_device_id(self): + return self.config()['device_id'] if self.config() and self.config()['device_id'] else '' def _format_mac(self, mac): # Split the MAC address into pairs of two characters each @@ -69,10 +47,20 @@ class DeviceInfo: formatted_mac = ":".join(pairs) return formatted_mac - def _loadConfig(self): - with open('/gs/config/sensors.py', 'r') as file: + def _loadJsonConfig(self, filepath=None): + filepath = filepath if filepath else '/gs/config/device_config.json' + try: + file = open(filepath, "r") json_content = file.read() - return json.loads(json_content) + return json.loads(json_content) + except OSError: # open failed + print("File not found: ", filepath) + return None + + #with open(filepath, 'r') as file: + # json_content = file.read() + #return json.loads(json_content) + \ No newline at end of file diff --git a/gs/exceptions/not_subscriptable_error.py b/gs/exceptions/not_subscriptable_error.py new file mode 100644 index 0000000..19fa115 --- /dev/null +++ b/gs/exceptions/not_subscriptable_error.py @@ -0,0 +1,2 @@ +class NotSubscriptableError(Exception): + pass diff --git a/gs/grow_system_api.py b/gs/grow_system_api.py index fa7ab66..2c85799 100644 --- a/gs/grow_system_api.py +++ b/gs/grow_system_api.py @@ -14,7 +14,7 @@ class GrowSystemApi: base_url = '' def __init__(self): - self.base_url = self.device_info.server_url + self.base_url = self.device_info.server_url() self.connect_wifi(ic.config['ssid'], ic.config['password']) # config = self.device_info.config() @@ -22,6 +22,7 @@ class GrowSystemApi: # print("Test", config['test']) def activate(self, config): + print("ACtivate config:", config) data = self._get_device_data() data.update({ 'user_id': 1, @@ -29,6 +30,7 @@ class GrowSystemApi: }) print("activate ...", data) response = self.http_client.post(self.base_url + "/api/device/activate", data) + print("REsponse ...", response) return self._get_json_encoded(response.text) def say_hello(self): @@ -41,7 +43,13 @@ class GrowSystemApi: def send_measurements(self, device_id, data): url = self.base_url + "/api/device/" + str(device_id) + "/sensor-log" response = self.http_client.post(url, data) - return json.loads(response.text) + try: + return json.loads(response.text) + except ValueError as e: + print("JSON Value error raised after sending measurements") + except Exception as e: + print("Exception raised while sending measurements", e) + return response def _get_device_data(self): return self.device_info.get_all_device_infos() diff --git a/gs/growsystem.py b/gs/growsystem.py index 26885ad..6046be1 100644 --- a/gs/growsystem.py +++ b/gs/growsystem.py @@ -1,6 +1,13 @@ from gs.setup import Setup +from gs.device_info import DeviceInfo +from gs.sensor_data_manager import SensorDataManager import os import ujson +import machine +import time +from gs.classes.sensors.ambilight_sensor import AmbilightSensor +from gs.classes.sensors.dht22 import TemperatureHumiditySensor +from gs.classes.sensors.moisture_sensor import MoistureSensor class GrowSystem: @@ -9,6 +16,10 @@ class GrowSystem: initial_config = None + device_info = DeviceInfo() + + sensors = [] + def __init__(self): print("Initialize Growsystem", self.version) @@ -22,13 +33,37 @@ class GrowSystem: if self._is_config_existing(): print("Skip Setup. Config existing.") + self._initialize_sensors() elif self._is_initial_config_existing(): print("Initial config only existing (no base config). Start activation ...") self._activate() + + def start(self): + self.sensor_data_manager = SensorDataManager(self.device_info.get_device_id()) + + print("Start reading sensors ...") + + read_secs = self.device_info.app_config().read_secs + + while True: + # Reset data + self.most_recent_values = [] + + for sensor in self.sensors: + print("Read sensor of type " + sensor.type + " at pin " + str(sensor.sensor_pin_int)) + sensor.read() + for measurement in sensor.most_recent_values: + print(f"Got {measurement['value']} {measurement['unit']} ({measurement['type']})") + self.most_recent_values = self.most_recent_values + sensor.most_recent_values + + self.sensor_data_manager.handleData(self.most_recent_values) + + time.sleep(read_secs) def _setup(self): setup = Setup() setup.setup_pico() + machine.reset def _activate(self): print("Start activation!") @@ -36,16 +71,27 @@ class GrowSystem: self.initial_config = ic.config device_config = self.gsapi.activate(self.initial_config) #print("Device Config:", device_config['data']) - sensors = device_config['data']['sensors'] - sensor_configs = [] - for sensor in sensors: - sensor_configs.append(sensor['config']) - print(sensor['config']) - with open("/gs/config/device_config.py", "w") as f: - f.write("sensors = " + ujson.dumps(sensor_configs)) + + if True: + sensors = device_config['data']['sensors'] + sensor_configs = [] + for sensor in sensors: + sensor_configs.append(sensor['config']) + print(sensor['config']) + device_configs = { + 'name': device_config['data']['name'], + 'device_id': device_config['data']['id'], + 'token': device_config['data']['token'], + 'user_id': device_config['data']['user_id'], + 'sensors': sensor_configs + } + with open("/gs/config/device_config.json", "w") as f: + f.write(ujson.dumps(device_configs)) + if self._is_config_existing(): + machine.reset def _is_config_existing(self): - return self._is_file_existing('/gs/config/config.py') + return self._is_file_existing('/gs/config/device_config.json') def _is_initial_config_existing(self): return self._is_file_existing('/gs/config/initial_config.py') @@ -59,4 +105,25 @@ class GrowSystem: except OSError: # open failed # handle the file open cas return False + + def _initialize_sensors(self): + # Init sensors + sensors = self.device_info.config()['sensors'] + for sensor in sensors: + print("--------------------------------------") + print("Initialize sensor:") + print(sensor) + sensor_type = sensor['type'] + if sensor_type == 'moisture': + print("Found sensor of type moisture") + self.sensors.append(MoistureSensor(sensor)) + elif sensor_type == 'dht22': + print("Found sensor of type DHT22") + self.sensors.append(TemperatureHumiditySensor(sensor)) + elif sensor_type == 'ambilight': + print("Found sensor of type GY302/BH1750") + self.sensors.append(AmbilightSensor(sensor)) + else: + print("No sensor type configured for: " + sensor['type']) + diff --git a/gs/http_client.py b/gs/http_client.py index fd197ed..b819ff8 100644 --- a/gs/http_client.py +++ b/gs/http_client.py @@ -26,10 +26,11 @@ class HTTPClient: print("Send post request to: " + url) response = urequests.post(url, data=json_data, headers=headers) if response.status_code == 200: - return response + print("Request OK (200)") else: - print("Failed to send data. Status code:", response.status_code) - print(response.text) + print("Failed to send data.", response.status_code, response.text) + return response except Exception as e: - print("Exception occurred:", e) + print("Exception raised:", e) + return None diff --git a/gs/little_apache.py b/gs/little_apache.py index 841c96c..774a383 100644 --- a/gs/little_apache.py +++ b/gs/little_apache.py @@ -81,8 +81,9 @@ class LittleApache():

Setup Pico

SSID:
- Password:
- PIN:
+ Password:

+ ID:
+ PIN:
""" diff --git a/gs/sensor_data_manager.py b/gs/sensor_data_manager.py new file mode 100644 index 0000000..806870e --- /dev/null +++ b/gs/sensor_data_manager.py @@ -0,0 +1,24 @@ +from gs.grow_system_api import GrowSystemApi +# from device_info import DeviceInfo +from gs.exceptions.not_subscriptable_error import NotSubscriptableError + +class SensorDataManager: + + device_info = None + + grow_system_api = None + + device_id = None + + def __init__(self, device_id): + self.grow_system_api = GrowSystemApi() + # self.device_info = DeviceInfo() + self.device_id = device_id + + def handleData(self, data): + json_response = self.grow_system_api.send_measurements(self.device_id, data) + try: + print("Response message: " + json_response['message']) + except TypeError as e: + print("The response is no json data", e, json_response) + diff --git a/gs/setup.py b/gs/setup.py index 5b89e9d..fd5fea9 100644 --- a/gs/setup.py +++ b/gs/setup.py @@ -57,9 +57,10 @@ class Setup: config = { "ssid": config['ssid'], "password": config['password'], - "pin": config['pin'] + "pin": config['pin'], + "user_id": config['user_id'] } - print("Save config:", config) + print("Save initial config:", config) with open("/gs/config/initial_config.py", "w") as f: f.write("config = " + ujson.dumps(config)) diff --git a/lib/bh1750.py b/lib/bh1750.py new file mode 100644 index 0000000..e155a09 --- /dev/null +++ b/lib/bh1750.py @@ -0,0 +1,62 @@ +""" +Micropython BH1750 ambient light sensor driver. +* https://github.com/PinkInk/upylib/tree/master/bh1750 +""" + +from utime import sleep_ms + + +class BH1750(): + """Micropython BH1750 ambient light sensor driver.""" + + PWR_OFF = 0x00 + PWR_ON = 0x01 + RESET = 0x07 + + # modes + CONT_LOWRES = 0x13 + CONT_HIRES_1 = 0x10 + CONT_HIRES_2 = 0x11 + ONCE_HIRES_1 = 0x20 + ONCE_HIRES_2 = 0x21 + ONCE_LOWRES = 0x23 + + # default addr=0x23 if addr pin floating or pulled to ground + # addr=0x5c if addr pin pulled high + def __init__(self, bus, addr=0x23): + self.bus = bus + self.addr = addr + self.off() + self.reset() + + def off(self): + """Turn sensor off.""" + self.set_mode(self.PWR_OFF) + + def on(self): + """Turn sensor on.""" + self.set_mode(self.PWR_ON) + + def reset(self): + """Reset sensor, turn on first if required.""" + self.on() + self.set_mode(self.RESET) + + def set_mode(self, mode): + """Set sensor mode.""" + self.mode = mode + self.bus.writeto(self.addr, bytes([self.mode])) + + def luminance(self, mode): + """Sample luminance (in lux), using specified sensor mode.""" + # continuous modes + if mode & 0x10 and mode != self.mode: + self.set_mode(mode) + # one shot modes + if mode & 0x20: + self.set_mode(mode) + # earlier measurements return previous reading + sleep_ms(24 if mode in (0x13, 0x23) else 180) + data = self.bus.readfrom(self.addr, 2) + factor = 2.0 if mode in (0x11, 0x21) else 1.0 + return (data[0]<<8 | data[1]) / (1.2 * factor) diff --git a/main.py b/main.py index 71b2e28..ae2ca15 100644 --- a/main.py +++ b/main.py @@ -5,8 +5,9 @@ from time import sleep if __name__ == '__main__': gs = GrowSystem() + gs.start() while True: print("Keep running in main.py") sleep(5) - + \ No newline at end of file