diff --git a/.gitignore b/.gitignore index d111e43..0c30bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ config.json /deploy/upload/ /deploy/last_upload/ /deploy/actual_upload/ +__pycache__ diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 72e46f2..f9351e1 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -10,7 +10,7 @@ import json # Import hardware-specific modules if available (for ESP32/ESP8266) try: - from umqtt.simple import MQTTClient + from umqtt.robust import MQTTClient SIMULATION = False except ImportError: diff --git a/src/esp_sensors/oled_display.py b/src/esp_sensors/oled_display.py index 4b233ba..5fe0d44 100644 --- a/src/esp_sensors/oled_display.py +++ b/src/esp_sensors/oled_display.py @@ -1,6 +1,11 @@ """ OLED display module for ESP32 using SSD1306 controller. """ +LINE_HEIGHT = 8 # Height of each line in pixels + +HEADER_LINE = 0 +STATUS_LINE = 1 +VALUE_LINES_START = 2 try: from machine import Pin, I2C @@ -18,16 +23,16 @@ class OLEDDisplay(Sensor): """SSD1306 OLED display implementation.""" def __init__( - self, - name: str = None, - scl_pin: int = None, - sda_pin: int = None, - width: int = None, - height: int = None, - address: int | str = None, - interval: int = None, - on_time: int = None, - display_config = None, + self, + name: str = None, + scl_pin: int = None, + sda_pin: int = None, + width: int = None, + height: int = None, + address: int | str = None, + interval: int = None, + on_time: int = None, + display_config=None, ): """ Initialize a new OLED display. @@ -84,7 +89,7 @@ class OLEDDisplay(Sensor): if not SIMULATION: try: print("Initializing OLED display...") - print(f" SCL pin: {self.scl_pin }, SDA pin: {self.sda_pin}") + print(f" SCL pin: {self.scl_pin}, SDA pin: {self.sda_pin}") # print('initializing scl pin', type(self.scl_pin), self.scl_pin) scl = Pin(self.scl_pin) # print('initializing sda pin', type(self.sda_pin), self.sda_pin) @@ -106,6 +111,7 @@ class OLEDDisplay(Sensor): print(f"Simulated OLED display initialized: {width}x{height}") self._display = None + # region basic display methods def clear(self): """ Clear the display. @@ -134,6 +140,23 @@ class OLEDDisplay(Sensor): self._display.text(text, x, y, color) self._display.show() + def set_line_text(self, i, value): + if SIMULATION: + print(f"Simulated OLED display line {i}: {value}") + else: + if self._display: + y = i * LINE_HEIGHT + if y < self.height: # Make sure we don't go off the screen + x = 0 + self._display.fill_rect(x, y, self.width, LINE_HEIGHT, 0) # Clear the line + self._display.text(str(value), x, y, 1) + else: + print(f"Line {i} exceeds display height, skipping") + + # endregion + + # region easy setter methods + def display_values(self, values: list): """ Display a list of values on the OLED screen. @@ -149,15 +172,38 @@ class OLEDDisplay(Sensor): print(f" Line {i}: {value}") else: if self._display: - self._display.fill(0) # Clear the display - + # self._display.fill(0) # Clear the display + x = 0 + y = VALUE_LINES_START * LINE_HEIGHT + self._display.fill_rect(x, y, self.width, self.height-y, 0) # Clear the line # Display each value on a new line (8 pixels per line) for i, value in enumerate(values): - if i * 10 < self.height: # Make sure we don't go off the screen - self._display.text(str(value), 0, i * 10, 1) + self.set_line_text(VALUE_LINES_START + i, value) self._display.show() + def set_header(self, value): + """ + Display a header on the OLED screen. + + Args: + value: The header to display + """ + self.set_line_text(HEADER_LINE, value) + + def set_status(self, status: str): + """ + Display a status message on the OLED screen. + + Args: + status: The status message to display + """ + self.set_line_text(STATUS_LINE, status) + self._display.show() + + # endregion + + # region Sensor interface methods def read(self) -> float: """ Update the display (placeholder to satisfy Sensor interface). @@ -185,3 +231,4 @@ class OLEDDisplay(Sensor): metadata["type"] = "SSD1306" metadata["values_count"] = len(self._values) return metadata + # endregion diff --git a/src/main.py b/src/main.py index e4720b8..542041a 100644 --- a/src/main.py +++ b/src/main.py @@ -98,19 +98,13 @@ def main(): # Initialize an OLED display using configuration display = OLEDDisplay(display_config=display_config) - display.clear() - display.display_text("Initializing...") + # display.clear() + name_str = f"N: {dht_sensor.name}" + display.set_header(name_str) + display.set_status("Initializing...") - # Initialize wifi connection - display.clear() - display.display_text("Connecting to WiFi...") - connect_wifi(network_config) - - # Set up MQTT client if enabled - display.clear() - display.display_text("Setting up MQTT...") - mqtt_client = setup_mqtt(mqtt_config) # mqtt_client = None + mqtt_enabled = mqtt_config.get("enabled", False) mqtt_publish_interval = mqtt_config.get("publish_interval", 60) # # Set up button using configuration @@ -124,50 +118,57 @@ def main(): # Main loop - sleep until button press, then read and display sensor data try: # while True: - print('sleeping for 5 seconds for debugging') - display.clear() - display.display_text('debug sleeping') - time.sleep(5) + # print('sleeping for 5 seconds for debugging') + # display.set_status('debug sleeping') + # time.sleep(5) # Read sensor values - display.clear() - display.display_text("Reading sensor values...") + display.set_status("Reading sensor values...") temperature = dht_sensor.read_temperature() humidity = dht_sensor.read_humidity() - # Publish to MQTT - display.clear() - display.display_values(['Publishing to MQTT...', '', mqtt_client.server, mqtt_client.port]) - publish_sensor_data(mqtt_client, mqtt_config, dht_sensor, temperature, humidity) - if mqtt_client: - try: - mqtt_client.disconnect() - print("MQTT client disconnected") - except Exception as e: - print(f"Error disconnecting MQTT client: {e}") - # # Format values for display - name_str = f"Sensor: {dht_sensor.name}" temp_str = f"Temp: {temperature:.1f} C" hum_str = f"Humidity: {humidity:.1f}%" time_str = f"Time: {time.time():.0f}" - # Display values - ## TODO: only display values, if the button has been clicked - display.clear() - display.display_values( - [name_str, '', temp_str, hum_str, time_str, "Press button again"] - ) - time.sleep(display.on_time) - # Print to console print('='*20) print(f"{temp_str}, {hum_str}") print('='*20) + # Display values + ## TODO: only display values, if the button has been clicked + display.display_values( + [temp_str, hum_str, time_str] + ) + + # Publish to MQTT + if mqtt_enabled: + # Initialize wifi connection + display.set_status("Connecting WiFi...") + connect_wifi(network_config) + + # Set up MQTT client if enabled + display.set_status("Setting up MQTT...") + mqtt_client = setup_mqtt(mqtt_config) + display.set_status("Publishing to MQTT...") + # display.display_values([mqtt_client.server, mqtt_client.port]) + publish_sensor_data(mqtt_client, mqtt_config, dht_sensor, temperature, humidity) + try: + if mqtt_client: + mqtt_client.disconnect() + print("MQTT client disconnected") + except Exception as e: + print(f"Error disconnecting MQTT client: {e}") + + display.set_status("...") + # sleep, to be able to do something, before going into deepsleep + time.sleep(display.on_time) time_until_next_read = mqtt_publish_interval - (time.time() - last_read_time) + display.set_status(f"Sleeping {time_until_next_read}s") print('sleeping for', time_until_next_read, 'seconds') if not SIMULATION: deepsleep(time_until_next_read * 1000) @@ -176,7 +177,6 @@ def main(): print(f"Simulated deep sleep for {time_until_next_read:.1f} seconds") time.sleep(time_until_next_read) - except KeyboardInterrupt: # Clean up on exit display.clear() @@ -222,4 +222,11 @@ def connect_wifi(network_config: dict): if __name__ == "__main__": - main() + try: + main() + except Exception as e: + print(f"An error occurred: {e}") + time.sleep(5) # give time to read the error message and respond + deepsleep(1) # dummy deepsleep to basically reset the system + +