diff --git a/README.md b/README.md deleted file mode 100644 index f90de4d..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# GrowSystem - diff --git a/REDME.md b/REDME.md deleted file mode 100644 index bc52edf..0000000 --- a/REDME.md +++ /dev/null @@ -1,3 +0,0 @@ -# Show output on terminal # -`minicom -b 115200 -o -D /dev/cu.usbmodem3301` - diff --git a/device_info.py b/device_info.py deleted file mode 100644 index 89c0474..0000000 --- a/device_info.py +++ /dev/null @@ -1,34 +0,0 @@ -import network - - -class DeviceInfo: - - name = "Dev Device 1" - - token = "PC]-0Bmp83h7F5#U!D6KJ(A&" - - wlan = network.WLAN(network.STA_IF) - - def get_macaddress(self): - return self._format_mac(self.wlan.config('mac').hex()) - - def get_ipaddress(self): - return self.wlan.ifconfig()[0] - - def get_all_device_infos(self): - return { - 'name': self.name, - 'mac_address': self.get_macaddress(), - 'ip_address': self.get_ipaddress(), - 'token': self.token} - - def _format_mac(self, mac): - # Split the MAC address into pairs of two characters each - pairs = [mac[i:i+2] for i in range(0, len(mac), 2)] - # Join the pairs with colons to create the formatted MAC address - formatted_mac = ":".join(pairs) - return formatted_mac - - - - \ No newline at end of file diff --git a/grow_system.py b/grow_system.py deleted file mode 100644 index f553d9f..0000000 --- a/grow_system.py +++ /dev/null @@ -1,58 +0,0 @@ -import time -from moisture_sensor import MoistureSensor -from dht22 import TemperatureHumiditySensor -from sensor_data_manager import SensorDataManager -from grow_system_api import GrowSystemApi - - -class GrowSystem: - - grow_system_api = GrowSystemApi() - - moisture_sensor = None - temperature_humidity_sensor = None - - most_recent_values = [] - - sensor_data_manager = None - - device_id = None - - def __init__(self, settings): - if not self.moisture_sensor: - self.moisture_sensor = MoistureSensor(settings['moisture_sensor']) - - if not self.temperature_humidity_sensor: - self.temperature_humidity_sensor = TemperatureHumiditySensor(settings['temperature_humidity_sensor']) - - def start(self): - print("Say the server hello...") - result = self.grow_system_api.say_hello() - message = result['message'] - - if message != 'OK': - print("Device not activated. Stopping") - return - - self.device_id = result['data']['device_id'] - self.sensor_data_manager = SensorDataManager(self.device_id) - - print("Start reading sensors ...") - while True: - # Reset data - self.most_recent_values = [] - # Moisture Sensor - self.moisture_sensor.read() - self.most_recent_values = self.most_recent_values + self.moisture_sensor.most_recent_value - - # Temperature and Humidity Sensor - self.temperature_humidity_sensor.read() - self.most_recent_values = self.most_recent_values + self.temperature_humidity_sensor.most_recent_values - - print("Most recent bla") - print(self.most_recent_values) - - self.sensor_data_manager.handleData(self.most_recent_values) - - time.sleep(5) - diff --git a/grow_system_api.py b/grow_system_api.py deleted file mode 100644 index dbd4aff..0000000 --- a/grow_system_api.py +++ /dev/null @@ -1,29 +0,0 @@ -from http_client import HTTPClient -from device_info import DeviceInfo -import json - - -class GrowSystemApi: - - http_client = HTTPClient() - - device_info = DeviceInfo() - - base_url = 'api.growsystem.muellerdev.kozow.com' - - def say_hello(self): - data = self._get_device_data() - response = self.http_client.post(self.base_url + "/api/device", data) - jsonResult = json.loads(response.text) - print(jsonResult) - return jsonResult; - - def send_measurements(self, device_id, data): - url = self.base_url + "/api/device/" + str(device_id) + "/sensor-log" - print(url) - response = self.http_client.post(url, data) - return json.loads(response.text) - - def _get_device_data(self): - return self.device_info.get_all_device_infos() - 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/http_request.py b/gs/classes/http_request.py new file mode 100644 index 0000000..c85063c --- /dev/null +++ b/gs/classes/http_request.py @@ -0,0 +1,72 @@ +import json +import ure as re + + +class HttpRequest: + + METHOD = { + "GET": "GET", + "POST": "POST" + } + + method = '' + path = '' + raw_content = '' + headers = {} + + def __init__(self, request): + self.original_request = request + # self.method = method + # self.path = path + # self.headers = headers + # self.raw_content = content + print("http request initialized") + self.parse_request(request) + + def parse_request(self, request): + # Split the request into lines + lines = request.decode().split('\r\n') + + # Extract method, path, and HTTP version from the first line + self.method, self.path, _ = lines[0].split() + + # Parse headers + for line in lines[1:]: + if not line: + break # Empty line indicates end of headers + key, value = line.split(': ', 1) + self.headers[key] = value + + # Content is assumed to be in the last line + self.raw_content = lines[-1] + + def get_content_json(self): + # Parse the POST request content into a dictionary + parsed_content = {} + pairs = self.raw_content.split('&') + for pair in pairs: + key, value = pair.split('=') + parsed_content[key] = value + + # Encode the values in the dictionary + encoded_content = {} + for key, value in parsed_content.items(): + encoded_value = re.sub(r'%([0-9A-Fa-f]{2})', lambda m: chr(int(m.group(1), 16)), value) + encoded_content[key] = encoded_value + + return encoded_content + + def __str__(self): + return { + "method": self.method, + "headers": self.headers, + "content": self.raw_content, + "content_json": self.get_content_json() + } + + + + + + + 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/dht22.py b/gs/classes/sensors/dht22.py similarity index 53% rename from dht22.py rename to gs/classes/sensors/dht22.py index 9f6a22a..464b31d 100644 --- a/dht22.py +++ b/gs/classes/sensors/dht22.py @@ -1,35 +1,37 @@ +from gs.classes.a_sensors import Sensors from dht import DHT22 from machine import ADC, Pin -class TemperatureHumiditySensor: - dht22_sensor_pin_int = -1 +class TemperatureHumiditySensor(Sensors): - dht22_sensor = None + sensor = None most_recent_values = [] def __init__(self, settings): - print("Hello from dht22 sensor class") + super().__init__(settings) + print("Initialize dht22 sensor. Sensor pin is: " + str(settings['pin_int'])) print(settings) - self.dht22_sensor_pin_int = settings['pin_int'] - self.dht22_sensor = DHT22(Pin(self.dht22_sensor_pin_int, Pin.IN, Pin.PULL_UP)) + 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.dht22_sensor.measure() + self.sensor.measure() self.most_recent_values = [ { 'type': 'temperature', - 'value': self.dht22_sensor.temperature(), + 'value': self.sensor.temperature(), 'unit': 'C' }, { 'type': 'humidity', - 'value': self.dht22_sensor.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 new file mode 100644 index 0000000..f066dbd --- /dev/null +++ b/gs/config/app.py @@ -0,0 +1,2 @@ +api_url = 'api.growsystem.muellerdev.kozow.com' +read_secs = 1 * 60 diff --git a/gs/config/examples/device_config.json b/gs/config/examples/device_config.json new file mode 100644 index 0000000..901d060 --- /dev/null +++ b/gs/config/examples/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/examples/initial_config.py b/gs/config/examples/initial_config.py new file mode 100644 index 0000000..9cd4fe6 --- /dev/null +++ b/gs/config/examples/initial_config.py @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..93f89aa --- /dev/null +++ b/gs/device_info.py @@ -0,0 +1,66 @@ +import network +import json + + +class DeviceInfo: + + wlan = network.WLAN(network.STA_IF) + + app_version = "1.0.0" + + def get_macaddress(self): + return self._format_mac(self.wlan.config('mac').hex()) + + def get_ipaddress(self): + return self.wlan.ifconfig()[0] + + def get_all_device_infos(self): + return { + 'name': self.get_name(), + 'mac_address': self.get_macaddress(), + 'ip_address': self.get_ipaddress(), + 'token': self.get_token()} + + 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 + pairs = [mac[i:i+2] for i in range(0, len(mac), 2)] + # Join the pairs with colons to create the formatted MAC address + formatted_mac = ":".join(pairs) + return formatted_mac + + 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) + 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 new file mode 100644 index 0000000..2c85799 --- /dev/null +++ b/gs/grow_system_api.py @@ -0,0 +1,64 @@ +from gs.http_client import HTTPClient +from gs.device_info import DeviceInfo +from gs.wlan_client import WlanClient +import json +import gs.config.initial_config as ic + + +class GrowSystemApi: + + http_client = HTTPClient() + + device_info = DeviceInfo() + + base_url = '' + + def __init__(self): + self.base_url = self.device_info.server_url() + self.connect_wifi(ic.config['ssid'], ic.config['password']) + + # config = self.device_info.config() + # print("Config:", config) + # print("Test", config['test']) + + def activate(self, config): + print("ACtivate config:", config) + data = self._get_device_data() + data.update({ + 'user_id': 1, + 'pin': config['pin'] + }) + 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): + response = self.http_client.post(self.base_url + "/api/device", self._get_device_data()) + print(response.text) + jsonResult = json.loads(response.text) + print(jsonResult) + return jsonResult; + + 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) + 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() + + def _get_json_encoded(self, text): + return json.loads(text) + + def connect_wifi(self, ssid, password): + print("Connect WLAN") + self.wlan_client = WlanClient(ssid, password) + self.wlan_client.connect() + diff --git a/gs/growsystem.py b/gs/growsystem.py new file mode 100644 index 0000000..6046be1 --- /dev/null +++ b/gs/growsystem.py @@ -0,0 +1,129 @@ +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: + + version = "1.0" + + initial_config = None + + device_info = DeviceInfo() + + sensors = [] + + def __init__(self): + print("Initialize Growsystem", self.version) + + if not self._is_initial_config_existing(): + print("No config existing. Start setup ...") + self._setup() + return + + from gs.grow_system_api import GrowSystemApi as GSA + self.gsapi = GSA() + + 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!") + import gs.config.initial_config as ic + self.initial_config = ic.config + device_config = self.gsapi.activate(self.initial_config) + #print("Device Config:", device_config['data']) + + 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/device_config.json') + + def _is_initial_config_existing(self): + return self._is_file_existing('/gs/config/initial_config.py') + + def _is_file_existing(self, filepath): + try: + f = open(filepath, "r") + f.close() + # continue with the file. + return True + 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 new file mode 100644 index 0000000..b819ff8 --- /dev/null +++ b/gs/http_client.py @@ -0,0 +1,36 @@ +import urequests +import json + +class HTTPClient: + def __init__(self): + pass + + def get(self, url): + url = 'https://' + url + try: + # headers = {'Content-Type': 'application/json'} + response = urequests.get(url) + if response.status_code == 200: + print("Data sent, got response") + return response + else: + print("Failed to get data. Status code:", response.status_code) + except Exception as e: + print("Exception occurred:", e) + + def post(self, url, data): + url = 'https://' + url + try: + headers = {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain, */*'} + json_data = json.dumps(data) + print("Send post request to: " + url) + response = urequests.post(url, data=json_data, headers=headers) + if response.status_code == 200: + print("Request OK (200)") + else: + print("Failed to send data.", response.status_code, response.text) + return response + except Exception as e: + print("Exception raised:", e) + return None + diff --git a/gs/little_apache.py b/gs/little_apache.py new file mode 100644 index 0000000..774a383 --- /dev/null +++ b/gs/little_apache.py @@ -0,0 +1,111 @@ +import socket +from gs.classes.http_request import HttpRequest + + +class LittleApache(): + + available_wifis = [] + keep_webserver_alive = True + + + def __init__(self, net): + self.net = net + addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] + self.s = socket.socket() + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.s.bind(addr) + self.s.listen() + # print('Listening on', addr) + + def start(self): + print("Webserver started. Connect to: " + self.net.ifconfig()[0]) + print(self.net.ifconfig()) + + while self.keep_webserver_alive: + try: + conn, addr = self.s.accept() + #print('Got a connection from', addr) + + # Receive and parse the request + request = conn.recv(1024) + # print("Request (RAW)", request) + http_request = HttpRequest(request) + self.http_request = http_request + request = str(request) + # print('Request content = %s' % request) + #try: + # request = request.split()[1] + # print('Request:', request) + #except IndexError: + # pass + response = self.response(http_request) + # Send the HTTP response and close the connection + conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') + conn.send(response) + conn.close() + except OSError as e: + conn.close() + print('Connection closed') + return self + + def response(self, request: HttpRequest): + #print("Webpage: ", request) + print("Request method: ", request.method, "Request path: ", request.path) + + header = f""" + + +
+