316 lines
8.5 KiB
Python
316 lines
8.5 KiB
Python
# === 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()) |