This commit is contained in:
2025-11-13 11:36:45 +01:00
commit fc6dc3ce77
7 changed files with 380 additions and 0 deletions

23
README.md Normal file
View File

@@ -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

4
config.py Normal file
View File

@@ -0,0 +1,4 @@
# === Configuration ===
RESET_DAILY_KM = True
RESET_MAX_SPEED = True
RESET_AVG_SPEED = True

23
database.py Normal file
View File

@@ -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")

4
gui.py Normal file
View File

@@ -0,0 +1,4 @@
# === GUI Functions ===
def create_screens():
# Already in main.py
pass

316
main.py Normal file
View File

@@ -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())

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
lvgl
onewire
ds18x20
ujson

6
sensors.py Normal file
View File

@@ -0,0 +1,6 @@
# === Sensors (optional, for modularity) ===
def get_speed():
return 65.0 # Dummy
def get_oil_temp():
return 85.0 # Dummy