From 8f736bc647ef893884b5cf0f2843fdf832ed6786 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Fri, 10 Mar 2023 15:43:12 +0100 Subject: [PATCH] Display status with details --- src/apps/home_assistant.py | 188 +++++++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 58 deletions(-) diff --git a/src/apps/home_assistant.py b/src/apps/home_assistant.py index e7f9ece..f2c5724 100644 --- a/src/apps/home_assistant.py +++ b/src/apps/home_assistant.py @@ -1,15 +1,21 @@ -import badger2040w as badger2040 -from badger2040w import WIDTH +from badger2040w import WIDTH, Badger2040W import urequests + +import secrets from secrets import HA_BASE_URL, HA_ACCESS_TOKEN -display = badger2040.Badger2040W() +display = Badger2040W() display.led(128) display.set_update_speed(2) +BLACK = 0 +WHITE = 15 display.connect() +LINE_START_OFFSET = 3 +STATUS_VALUE_OFFSET = 25 + class HAError(Exception): pass @@ -19,66 +25,132 @@ class HAFetchStateError(HAError): pass -class HAPlant: - def __init__(self, entity_id): - self.entity_id = entity_id - self.state = None - self._last_fetched = None +def fetch_state(entity: str) -> dict: + headers = { + "Authorization": f"Bearer {HA_ACCESS_TOKEN}", + "content-type": "application/json", + } + url = f"{HA_BASE_URL}/states/{entity}" + print("Fetching state from", url) + res = urequests.get(url, headers=headers) + if res.status_code != 200: + msg = f"Error fetching state for {entity}: {res.text}" + raise HAFetchStateError(msg) + data = res.json() + res.close() + del data["context"] + print(data) + return data - def fetch_state(self) -> None: - """Fetch state and store in self.state.""" - headers = { - "Authorization": f"Bearer {HA_ACCESS_TOKEN}", - "content-type": "application/json", - } - url = f"{HA_BASE_URL}/states/{self.entity_id}" - print("Fetching state from", url) - res = urequests.get(url, headers=headers) - if res.status_code != 200: - msg = f"Error fetching state for {self.entity_id}: {res.text}" - raise HAFetchStateError(msg) - data = res.json() - self.state = data - res.close() + +class HAPlant: + def __init__(self): + self._last_fetched = None + self.plant_state = None + # Example state + # { + # "entity_id": "plant.aloe_vera", + # "state": "problem", + # "attributes": { + # "species": "Aloe vera", + # "moisture_status": "ok", + # "temperature_status": "ok", + # "conductivity_status": "Low", + # "illuminance_status": "ok", + # "humidity_status": null, + # "dli_status": "Low", + # "species_original": "aloe vera", + # "device_class": "plant", + # "entity_picture": "https://opb-img.plantbook.io/aloe%20vera.jpg", + # "friendly_name": "Aloe vera" + # }, + # "last_changed": "2023-03-10T12:51:29.103630+00:00", + # "last_updated": "2023-03-10T12:52:47.188669+00:00", + # } + self.details = {} + # Example illuminance state + # { + # "entity_id": "sensor.aloe_vera_illuminance", + # "state": "245", + # "attributes": { + # "state_class": "measurement", + # "unit_of_measurement": "lx", + # "device_class": "illuminance", + # "friendly_name": "Aloe vera Illuminance" + # }, + # "last_changed": "2023-03-10T13:58:08.838316+00:00", + # "last_updated": "2023-03-10T13:58:08.838316+00:00", + # } + + def get_plant_attribute(self, attribute): + return self.plant_state.get("attributes", {}).get(attribute, None) + + def get_detailed_state(self, attribute): + return self.details.get(attribute, {}).get("state", None) + + def get_detailed_attribute(self, attribute, attribute_name): + return ( + self.details.get(attribute, {}) + .get("attributes", {}) + .get(attribute_name, None) + ) + + def get_plant_status(self, attribute): + status = self.get_plant_attribute(f"{attribute}_status") + if status is None: + return "N/A" + status = status.upper() + if status == "OK": + return "OK" + detailed_state = self.get_detailed_state( + attribute + ) + self.get_detailed_attribute(attribute, "unit_of_measurement") + if status == "LOW": + return f"Bas ({detailed_state})" + if status == "HIGH": + return f"Haut ({detailed_state})" + return status + + def fetch_states(self) -> None: + self.plant_state = fetch_state(secrets.HA_PLANT_ID) + self.details["moisture"] = fetch_state(secrets.HA_PLANT_MOISTURE_SENSOR) + self.details["illuminance"] = fetch_state(secrets.HA_PLANT_ILLUMINANCE_SENSOR) + self.details["temperature"] = fetch_state(secrets.HA_PLANT_TEMPERATURE_SENSOR) + self.details["conductivity"] = fetch_state(secrets.HA_PLANT_CONDUCTIVITY_SENSOR) + print(self.details) def display_state(self): - print(self.state) + # Clear the display + display.set_pen(WHITE) + display.clear() + + # Draw the page header + display.set_pen(BLACK) + display.rectangle(0, 0, WIDTH, 20) + + # Write text in header + display.set_font("bitmap6") + display.set_pen(WHITE) + display.text(self.get_plant_attribute("friendly_name"), 3, 4) + + display.set_pen(BLACK) + display.text("H", LINE_START_OFFSET, 30) + display.text("T", LINE_START_OFFSET, 55) + display.text("C", LINE_START_OFFSET, 80) + display.text("L", LINE_START_OFFSET, 105) + + display.set_font("bitmap8") + display.text(self.get_plant_status("moisture"), STATUS_VALUE_OFFSET, 30) + display.text(self.get_plant_status("temperature"), STATUS_VALUE_OFFSET, 55) + display.text(self.get_plant_status("conductivity"), STATUS_VALUE_OFFSET, 80) + display.text(self.get_plant_status("illuminance"), STATUS_VALUE_OFFSET, 105) + + display.update() -def draw_page(plant): - print("Drawing page...") - # Clear the display - display.set_pen(15) - display.clear() - display.set_pen(0) - - # Draw the page header - display.set_font("bitmap6") - display.set_pen(0) - display.rectangle(0, 0, WIDTH, 20) - display.set_pen(15) - display.text("Weather", 3, 4) - display.set_pen(0) - - display.set_font("bitmap8") - display.set_pen(0) - display.rectangle(0, 60, WIDTH, 25) - display.set_pen(15) - display.text( - "Found state, check logs", - 5, - 65, - WIDTH, - 1, - ) - - display.update() - plant.display_state() - - -plant = HAPlant("plant.aloe_vera") -plant.fetch_state() -draw_page(plant) +plant = HAPlant() +plant.fetch_states() +plant.display_state() # Call halt in a loop, on battery this switches off power. # On USB, the app will exit when A+C is pressed because the launcher picks that up.