initial
This commit is contained in:
23
README.md
Normal file
23
README.md
Normal 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
4
config.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# === Configuration ===
|
||||||
|
RESET_DAILY_KM = True
|
||||||
|
RESET_MAX_SPEED = True
|
||||||
|
RESET_AVG_SPEED = True
|
||||||
23
database.py
Normal file
23
database.py
Normal 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
4
gui.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# === GUI Functions ===
|
||||||
|
def create_screens():
|
||||||
|
# Already in main.py
|
||||||
|
pass
|
||||||
316
main.py
Normal file
316
main.py
Normal 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
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
lvgl
|
||||||
|
onewire
|
||||||
|
ds18x20
|
||||||
|
ujson
|
||||||
6
sensors.py
Normal file
6
sensors.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# === Sensors (optional, for modularity) ===
|
||||||
|
def get_speed():
|
||||||
|
return 65.0 # Dummy
|
||||||
|
|
||||||
|
def get_oil_temp():
|
||||||
|
return 85.0 # Dummy
|
||||||
Reference in New Issue
Block a user