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