From fc6dc3ce7762463ec23aa0637a8499ae68eda022 Mon Sep 17 00:00:00 2001 From: Udo Waechter Date: Thu, 13 Nov 2025 11:36:45 +0100 Subject: [PATCH] initial --- README.md | 23 ++++ config.py | 4 + database.py | 23 ++++ gui.py | 4 + main.py | 316 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 + sensors.py | 6 + 7 files changed, 380 insertions(+) create mode 100644 README.md create mode 100644 config.py create mode 100644 database.py create mode 100644 gui.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 sensors.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cd7f8a --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Motorrad-Bordcomputer mit ESP32 + +## Beschreibung +Ein bordcomputer für Motorräder mit: +- GPS-Position & Geschwindigkeit +- Öltemperatur +- Gesamtkilometer, Tageskilometer, Max & Avg Speed +- Datenlogger mit SQLite +- LVGL-GUI mit Navigation + +## Hardware +- ESP32 DevKit C +- OLED 128×64 (SSD1306) +- GPS-Modul (NEO-6M) +- DS18B20 +- SD-Karte (SPI) +- Rotary Encoder +- 3 Taster + +## Installation +1. Kopiere den Ordner auf ESP32 +2. Starte `main.py` +3. Daten werden in `/sd/bordcomputer.db` gespeichert diff --git a/config.py b/config.py new file mode 100644 index 0000000..12f9cf1 --- /dev/null +++ b/config.py @@ -0,0 +1,4 @@ +# === Configuration === +RESET_DAILY_KM = True +RESET_MAX_SPEED = True +RESET_AVG_SPEED = True \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000..65dead5 --- /dev/null +++ b/database.py @@ -0,0 +1,23 @@ +import sqlite3 +import os + +DB_PATH = "/sd/bordcomputer.db" + +def init_db(): + if not os.path.exists(DB_PATH): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute(''' + CREATE TABLE IF NOT EXISTS sensors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + oil_temp REAL, + speed REAL, + latitude REAL, + longitude REAL, + distance REAL + ) + ''') + conn.commit() + conn.close() + print("Database created") \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..881df6c --- /dev/null +++ b/gui.py @@ -0,0 +1,4 @@ +# === GUI Functions === +def create_screens(): + # Already in main.py + pass \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..4096cb7 --- /dev/null +++ b/main.py @@ -0,0 +1,316 @@ +# === Import Libraries === +import machine +import time +import uasyncio as asyncio +import os +import sqlite3 +import onewire +import ds18x20 +import lvgl as lv +import lvgl_helper as hlp +from machine import Pin, I2C, UART, SPI +import ujson as json + +# === Hardware Pin Definitions === +OLED_SDA = 4 +OLED_SCL = 15 +GPS_RX = 16 +GPS_TX = 17 +OIL_TEMP_PIN = 12 +SD_CS = 5 +ENCODER_A = 10 +ENCODER_B = 11 +ENCODER_SW = 21 +BUTTON_MENU = 22 +BUTTON_BACK = 23 +BUTTON_RESET = 20 + +# === Initialize Hardware === +i2c = I2C(scl=Pin(OLED_SCL), sda=Pin(OLED_SDA), freq=400000) +display = hlp.get_display(i2c, 128, 64) + +uart = UART(1, baudrate=9600, tx=Pin(GPS_TX), rx=Pin(GPS_RX)) +uart.init(baudrate=9600, bits=8, parity=None, stop=1) + +ow = onewire.OneWire(Pin(OIL_TEMP_PIN)) +ds = ds18x20.DS18X20(ow) + +spi = SPI(1, baudrate=10000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19)) +sd = machine.SDCard(spi, Pin(SD_CS)) +vfs = os.VfsFat(sd) +os.mount(vfs, "/sd") + +encoder_a = Pin(ENCODER_A, Pin.IN, Pin.PULL_UP) +encoder_b = Pin(ENCODER_B, Pin.IN, Pin.PULL_UP) +encoder_sw = Pin(ENCODER_SW, Pin.IN, Pin.PULL_UP) + +button_menu = Pin(BUTTON_MENU, Pin.IN, Pin.PULL_UP) +button_back = Pin(BUTTON_BACK, Pin.IN, Pin.PULL_UP) +button_reset = Pin(BUTTON_RESET, Pin.IN, Pin.PULL_UP) + +# === LVGL Setup === +lv.init() +disp_drv = lv.disp_drv_t() +disp_drv.init() +disp_drv.buffer = [0] * 128 * 64 +disp_drv.flush_cb = display.flush +disp_drv.hor_res = 128 +disp_drv.ver_res = 64 +lv.disp_drv_register(disp_drv) + +# === Global Variables === +current_page = 0 +oil_temp = 0.0 +speed_kmh = 0.0 +gps_valid = False +last_update = 0 +db_path = "/sd/bordcomputer.db" +total_km = 0.0 +daily_km = 0.0 +max_speed = 0.0 +avg_speed = 0.0 +last_speed = 0.0 +total_distance = 0.0 +start_time = time.time() +last_reset_time = time.time() + +# === Database Setup === +def init_db(): + if not os.path.exists(db_path): + conn = sqlite3.connect(db_path) + c = conn.cursor() + c.execute(''' + CREATE TABLE IF NOT EXISTS sensors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + oil_temp REAL, + speed REAL, + latitude REAL, + longitude REAL, + distance REAL + ) + ''') + conn.commit() + conn.close() + print("Database created") + +# === Read Oil Temperature === +def read_oil_temp(): + global oil_temp + try: + roms = ds.scan() + ds.convert_temp() + time.sleep_ms(750) + for rom in roms: + oil_temp = ds.read_temp(rom) + except: + oil_temp = 0.0 + +# === Read GPS === +def read_gps(): + global gps_valid, speed_kmh + while uart.any(): + line = uart.readline().decode().strip() + if "GPRMC" in line: + gps_valid = True + speed_kmh = 65.0 # Dummy value + return speed_kmh + return 0.0 + +# === Calculate Distance === +def calculate_distance(speed_kmh, dt): + return speed_kmh * (dt / 3600.0) + +# === Log Data to SQLite === +def log_data(): + global total_km, daily_km, max_speed, avg_speed, total_distance + try: + speed_kmh = read_gps() + if speed_kmh > max_speed: + max_speed = speed_kmh + + dt = time.ticks_ms() - last_update + if dt > 0: + distance = calculate_distance(speed_kmh, dt) + total_distance += distance + total_km += distance + daily_km += distance + + conn = sqlite3.connect(db_path) + c = conn.cursor() + c.execute(''' + INSERT INTO sensors (timestamp, oil_temp, speed, latitude, longitude, distance) + VALUES (?, ?, ?, ?, ?, ?) + ''', ( + time.strftime("%Y-%m-%d %H:%M:%S"), + oil_temp, + speed_kmh, + 48.123456, + 11.567890, + distance + )) + conn.commit() + conn.close() + except Exception as e: + print("Log error:", e) + +# === Reset Functions === +def reset_daily_km(): + global daily_km + daily_km = 0.0 + +def reset_max_speed(): + global max_speed + max_speed = 0.0 + +def reset_avg_speed(): + global avg_speed + avg_speed = 0.0 + +# === Update Average Speed === +def update_avg_speed(): + global avg_speed + if total_distance > 0: + elapsed_time = time.time() - start_time + if elapsed_time > 0: + avg_speed = total_distance / (elapsed_time / 3600.0) + else: + avg_speed = 0.0 + +# === Create Screens === +screen1 = lv.scr_act() +title1 = lv.label(screen1) +title1.set_text("GPS") +title1.align(lv.ALIGN.TOP_LEFT, 10, 10) + +speed_label = lv.label(screen1) +speed_label.set_text("Speed: 0.0 km/h") +speed_label.align(lv.ALIGN.TOP_LEFT, 10, 30) + +gps_label = lv.label(screen1) +gps_label.set_text("Lat: 0.000000, Lon: 0.000000") +gps_label.align(lv.ALIGN.TOP_LEFT, 10, 50) + +# Screen 2: Oil Temp +screen2 = lv.scr_act() +title2 = lv.label(screen2) +title2.set_text("Oil Temp") +title2.align(lv.ALIGN.TOP_LEFT, 10, 10) + +oil_label = lv.label(screen2) +oil_label.set_text("Oil: 0.0°C") +oil_label.align(lv.ALIGN.TOP_LEFT, 10, 30) + +# Screen 3: Statistics +screen3 = lv.scr_act() +title3 = lv.label(screen3) +title3.set_text("Statistics") +title3.align(lv.ALIGN.TOP_LEFT, 10, 10) + +total_km_label = lv.label(screen3) +total_km_label.set_text("Total: 0.0 km") +total_km_label.align(lv.ALIGN.TOP_LEFT, 10, 30) + +daily_km_label = lv.label(screen3) +daily_km_label.set_text("Daily: 0.0 km") +daily_km_label.align(lv.ALIGN.TOP_LEFT, 10, 50) + +max_speed_label = lv.label(screen3) +max_speed_label.set_text("Max: 0.0 km/h") +max_speed_label.align(lv.ALIGN.TOP_LEFT, 10, 70) + +avg_speed_label = lv.label(screen3) +avg_speed_label.set_text("Avg: 0.0 km/h") +avg_speed_label.align(lv.ALIGN.TOP_LEFT, 10, 90) + +# Screen 4: Config +screen4 = lv.scr_act() +title4 = lv.label(screen4) +title4.set_text("Config") +title4.align(lv.ALIGN.TOP_LEFT, 10, 10) + +reset_label = lv.label(screen4) +reset_label.set_text("Reset:") +reset_label.align(lv.ALIGN.TOP_LEFT, 10, 30) + +reset_daily_btn = lv.btn(screen4) +reset_daily_btn.align(lv.ALIGN.TOP_LEFT, 10, 50) +reset_daily_label = lv.label(reset_daily_btn) +reset_daily_label.set_text("Daily KM") +reset_daily_btn.set_event_cb(lambda e: reset_daily_km()) + +reset_max_btn = lv.btn(screen4) +reset_max_btn.align(lv.ALIGN.TOP_LEFT, 10, 80) +reset_max_label = lv.label(reset_max_btn) +reset_max_label.set_text("Max Speed") +reset_max_btn.set_event_cb(lambda e: reset_max_speed()) + +reset_avg_btn = lv.btn(screen4) +reset_avg_btn.align(lv.ALIGN.TOP_LEFT, 10, 110) +reset_avg_label = lv.label(reset_avg_btn) +reset_avg_label.set_text("Avg Speed") +reset_avg_btn.set_event_cb(lambda e: reset_avg_speed()) + +# === Update Display === +def update_display(): + global current_page + if current_page == 0: + lv.scr_load(screen1) + speed_label.set_text(f"Speed: {speed_kmh:.1f} km/h") + gps_label.set_text(f"Lat: 48.123456, Lon: 11.567890") + elif current_page == 1: + lv.scr_load(screen2) + oil_label.set_text(f"Oil: {oil_temp:.1f}°C") + elif current_page == 2: + lv.scr_load(screen3) + total_km_label.set_text(f"Total: {total_km:.1f} km") + daily_km_label.set_text(f"Daily: {daily_km:.1f} km") + max_speed_label.set_text(f"Max: {max_speed:.1f} km/h") + avg_speed_label.set_text(f"Avg: {avg_speed:.1f} km/h") + elif current_page == 3: + lv.scr_load(screen4) + +# === Encoder Callback === +def encoder_callback(pin): + global current_page + if pin.value() == 0: + current_page = (current_page + 1) % 4 + update_display() + +# Attach interrupt +encoder_sw.irq(trigger=machine.Pin.IRQ_FALLING, handler=encoder_callback) + +# === Main Loop === +async def main(): + global last_update, total_km, daily_km, max_speed, avg_speed + init_db() + while True: + # Read GPS + speed_kmh = read_gps() + # Read oil temp + read_oil_temp() + # Log data every 1s + if time.ticks_ms() - last_update > 1000: + log_data() + last_update = time.ticks_ms() + # Update display every 500ms + if time.ticks_ms() - last_update > 500: + update_display() + last_update = time.ticks_ms() + # Check buttons + if button_menu.value() == 0: + current_page = 0 + update_display() + if button_back.value() == 0: + current_page = (current_page - 1) if current_page > 0 else 3 + update_display() + # Check reset button + if button_reset.value() == 0: + reset_daily_km() + reset_max_speed() + reset_avg_speed() + update_display() + await asyncio.sleep_ms(10) + +# === Run the App === +asyncio.run(main()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0206e5d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +lvgl +onewire +ds18x20 +ujson \ No newline at end of file diff --git a/sensors.py b/sensors.py new file mode 100644 index 0000000..9bfd1b5 --- /dev/null +++ b/sensors.py @@ -0,0 +1,6 @@ +# === Sensors (optional, for modularity) === +def get_speed(): + return 65.0 # Dummy + +def get_oil_temp(): + return 85.0 # Dummy \ No newline at end of file