move into firmware subfolder

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-08-20 10:27:03 +02:00
parent d316bb9f2c
commit 5a08c2e09d
117 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
#include "ui/LightMenu.h"
#include "led_manager.h"
#include "ui/LightSettingsMenu.h"
/**
* @namespace LightMenuItem
* @brief Constants for light menu item identifiers
*/
namespace LightMenuItem
{
constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item
} // namespace LightMenuItem
namespace LightMenuOptions
{
constexpr std::string LIGHT_ACTIVE = "light_active";
constexpr std::string LIGHT_MODE = "light_mode";
} // namespace LightMenuOptions
LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Add toggle for enabling/disabling the light system
bool active = false;
if (m_options && m_options->persistenceManager)
{
active = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_ACTIVE, active);
}
addToggle(LightMenuItem::ACTIVATE, "Einschalten", active);
// Create mode selection options (Day/Night modes)
std::vector<std::string> values;
values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); // Night mode
int mode_value = 0;
if (m_options && m_options->persistenceManager)
{
mode_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_MODE, mode_value);
}
addSelection(LightMenuItem::MODE, "Modus", values, mode_value);
// Add menu item for accessing LED settings submenu
addText(LightMenuItem::LED_SETTINGS, "Einstellungen");
}
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
std::shared_ptr<Widget> widget;
// Handle different menu items based on their ID
switch (menuItem.getId())
{
case LightMenuItem::ACTIVATE: {
// Toggle the light activation state when SELECT is pressed
if (button == ButtonType::SELECT)
{
toggle(menuItem);
if (getItem(menuItem.getId()).getValue() == "1")
{
led_event_data_t payload = {.value = 42};
send_event(EVENT_LED_ON, &payload);
}
else
{
led_event_data_t payload = {.value = 0};
send_event(EVENT_LED_OFF, &payload);
}
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getValue() == "1";
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_ACTIVE, value);
m_options->persistenceManager->Save();
}
}
break;
}
case LightMenuItem::MODE: {
// Switch between day/night modes using left/right buttons
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(item.getId()).getIndex();
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_MODE, value);
m_options->persistenceManager->Save();
}
}
break;
}
case LightMenuItem::LED_SETTINGS: {
// Open the LED settings submenu when SELECT is pressed
if (button == ButtonType::SELECT)
{
widget = std::make_shared<LightSettingsMenu>(m_options);
}
break;
}
default:
// Handle unknown menu items (no action required)
break;
}
// Push the new widget to the screen stack if one was created
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}

View File

@@ -0,0 +1,57 @@
#include "ui/LightSettingsMenu.h"
/**
* @namespace LightSettingsMenuItem
* @brief Constants for light settings menu item identifiers
*/
namespace LightSettingsMenuItem
{
constexpr uint8_t SECTION_COUNTER = 0; ///< ID for the section counter menu item
}
std::string LightSettingsMenu::CreateKey(const int index)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%d", index);
return key;
}
LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Create values vector for section counts (1-99)
std::vector<std::string> values;
for (size_t i = 1; i <= 99; i++)
{
values.emplace_back(std::to_string(i));
}
// Add section counter selection (allows choosing number of sections)
auto value = 7;
if (m_options && m_options->persistenceManager)
{
value = m_options->persistenceManager->GetValue(CreateKey(0), value);
}
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, value);
setItemSize(std::stoull(getItem(0).getValue()));
}
void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
// Handle value switching for the current menu item
switchValue(menuItem, button);
// Update the section list size based on the section counter value
if (menuItem.getId() == 0)
{
setItemSize(std::stoull(getItem(0).getValue()));
}
// Persist the changed section values if persistence is available
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getIndex();
m_options->persistenceManager->SetValue(CreateKey(menuItem.getId()), value);
}
}

View File

@@ -0,0 +1,44 @@
#include "ui/MainMenu.h"
#include "common/Widget.h"
#include "ui/LightMenu.h"
#include "ui/SettingsMenu.h"
namespace MainMenuItem
{
constexpr uint8_t LIGHT = 0;
constexpr uint8_t EXTERNAL_DEVICES = 1;
constexpr uint8_t SETTINGS = 2;
} // namespace MainMenuItem
MainMenu::MainMenu(menu_options_t *options) : Menu(options), m_options(options)
{
addText(MainMenuItem::LIGHT, "Lichtsteuerung");
addTextCounter(MainMenuItem::EXTERNAL_DEVICES, "ext. Geraete", 0);
addText(MainMenuItem::SETTINGS, "Einstellungen");
}
void MainMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
if (button == ButtonType::SELECT)
{
std::shared_ptr<Widget> widget;
switch (menuItem.getId())
{
case MainMenuItem::LIGHT:
widget = std::make_shared<LightMenu>(m_options);
break;
case MainMenuItem::SETTINGS:
widget = std::make_shared<SettingsMenu>(m_options);
break;
default:
break;
}
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}
}

View File

@@ -0,0 +1,329 @@
#include "ui/ScreenSaver.h"
#include "data/roads.h"
#include "data/vehicles.h"
#include <cstdlib>
ScreenSaver::ScreenSaver(menu_options_t *options)
: Widget(options->u8g2), m_options(options), m_animationCounter(0), m_lastSpawnTime(0), m_leftVehicleCount(0),
m_rightVehicleCount(0)
{
initVehicles();
}
void ScreenSaver::initVehicles()
{
m_vehicles.resize(MAX_VEHICLES);
for (auto &vehicle : m_vehicles)
{
vehicle.active = false;
}
}
void ScreenSaver::update(const uint64_t dt)
{
m_animationCounter += dt;
m_lastSpawnTime += dt;
m_sceneShiftTimer += dt;
// Shift entire scene every 30 seconds
if (m_sceneShiftTimer > 30000)
{
m_sceneOffsetX = (random() % 7) - 3; // -3 to +3 pixels
m_sceneOffsetY = (random() % 7) - 3; // -3 to +3 pixels
m_sceneShiftTimer = 0;
}
// Try to spawn a new vehicle every few seconds
if (m_lastSpawnTime > VEHICLE_SPAWN_DELAY)
{
trySpawnVehicle();
m_lastSpawnTime = 0;
}
// Update vehicle positions
if (m_animationCounter > 16) // ~60 FPS
{
m_animationCounter = 0;
for (auto &vehicle : m_vehicles)
{
if (!vehicle.active)
continue;
// Move vehicle
if (vehicle.direction == Direction::LEFT)
{
vehicle.x -= static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x <= -32) // Allow for largest vehicle width
{
vehicle.active = false;
m_leftVehicleCount--;
}
}
else // Direction::RIGHT
{
vehicle.x += static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x >= (u8g2->width + 32)) // Allow for largest vehicle width
{
vehicle.active = false;
m_rightVehicleCount--;
}
}
}
}
}
bool ScreenSaver::canSpawnInDirection(Direction direction) const
{
// Minimalen Abstand zwischen 48 und 64 Pixel zufällig wählen
int requiredDistance =
MIN_SAME_DIRECTION_DISTANCE + (random() % (MAX_SAME_DIRECTION_DISTANCE - MIN_SAME_DIRECTION_DISTANCE + 1));
for (const auto &vehicle : m_vehicles)
{
if (!vehicle.active || vehicle.direction != direction)
continue;
// Abstand zum nächsten Fahrzeug in gleicher Richtung prüfen
if (direction == Direction::LEFT)
{
// Fahrzeuge fahren von rechts nach links
// Neues Fahrzeug würde bei u8g2->width + 16 starten
int newVehicleX = u8g2->width + 16;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (newVehicleX - vehicle.x < requiredDistance)
return false;
}
else // Direction::RIGHT
{
// Fahrzeuge fahren von links nach rechts
// Neues Fahrzeug würde bei -32 starten
int newVehicleX = -32;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (vehicle.x - newVehicleX < requiredDistance)
return false;
}
}
return true;
}
void ScreenSaver::trySpawnVehicle()
{
// Check if we can spawn a new vehicle
int activeVehicles = 0;
int availableSlot = -1;
for (int i = 0; i < MAX_VEHICLES; i++)
{
if (m_vehicles[i].active)
{
activeVehicles++;
}
else if (availableSlot == -1)
{
availableSlot = i;
}
}
// Don't spawn if we're at max capacity or no slot available
if (activeVehicles >= MAX_VEHICLES || availableSlot == -1)
{
return;
}
Direction direction = getRandomDirection();
// Check direction constraints
if ((direction == Direction::LEFT && m_leftVehicleCount >= MAX_LEFT_VEHICLES) ||
(direction == Direction::RIGHT && m_rightVehicleCount >= MAX_RIGHT_VEHICLES))
{
return;
}
if (!canSpawnInDirection(direction))
{
return;
}
// Create new vehicle
Vehicle &newVehicle = m_vehicles[availableSlot];
newVehicle.type = getRandomVehicleType();
newVehicle.direction = direction;
newVehicle.speed = MIN_SPEED + (static_cast<float>(random()) / RAND_MAX) * (MAX_SPEED - MIN_SPEED);
// Set Y position based on a direction (simulate opposing traffic lanes)
const int halfHeight = u8g2->height / 2;
if (direction == Direction::RIGHT)
{
// Vehicles going LEFT use bottom half of screen
newVehicle.y = halfHeight + 8 + (random() % (halfHeight - 24));
m_rightVehicleCount++;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT use top half of screen
newVehicle.y = 8 + (random() % (halfHeight - 24));
m_leftVehicleCount++;
}
// Set the starting X position based on the direction
if (direction == Direction::LEFT)
{
// Vehicles going LEFT (from right to left) start from RIGHT side of screen
newVehicle.x = u8g2->width + 16;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT (from left to right) start from LEFT side of screen
newVehicle.x = -32; // Account for the largest vehicle width
}
newVehicle.active = true;
}
ScreenSaver::VehicleType ScreenSaver::getRandomVehicleType()
{
switch (random() % 5)
{
case 0:
return VehicleType::CAR;
case 1:
return VehicleType::CONVERTABLE;
case 2:
return VehicleType::SUV;
case 3:
return VehicleType::LORRY;
case 4:
return VehicleType::TRUCK;
default:
return VehicleType::CAR;
}
}
ScreenSaver::Direction ScreenSaver::getRandomDirection()
{
// Simple 50/50 chance for each direction
return (random() % 2 == 0) ? Direction::LEFT : Direction::RIGHT;
}
void ScreenSaver::render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
// Calculate offsets
const int roadOffset = (m_animationCounter / 100) % road_horizontal_width;
// Draw all active vehicles with a scene offset
for (const auto &vehicle : m_vehicles)
{
if (vehicle.active)
{
Vehicle offsetVehicle = vehicle;
offsetVehicle.x += m_sceneOffsetX;
offsetVehicle.y += m_sceneOffsetY;
drawVehicle(offsetVehicle);
}
}
// Draw road with offsets
const int y = u8g2->height / 2 - road_horizontal_height / 2 + m_sceneOffsetY;
for (int x = -road_horizontal_width + roadOffset + m_sceneOffsetX; x <= u8g2->width; x += road_horizontal_width)
{
drawTransparentBitmap(x, y, road_horizontal_width, road_horizontal_height, road_horizontal_bits);
}
}
void ScreenSaver::drawVehicle(const Vehicle &vehicle) const
{
int width, height;
if (const unsigned char *bitmap = getVehicleBitmap(vehicle.type, vehicle.direction, width, height))
{
drawTransparentBitmap(vehicle.x, vehicle.y, width, height, bitmap);
// u8g2_DrawXBM(u8g2, vehicle.x, vehicle.y, width, height, bitmap);
}
}
void ScreenSaver::drawTransparentBitmap(const int x, const int y, const int width, const int height,
const unsigned char *bitmap) const
{
for (int py = 0; py < height; py++)
{
for (int px = 0; px < width; px++)
{
// Calculate byte and a bit of position in bitmap
const int byteIndex = (py * ((width + 7) / 8)) + (px / 8);
// Check if the pixel is set (white)
if (const int bitIndex = px % 8; bitmap[byteIndex] & (1 << bitIndex))
{
// Only draw white pixels, skip black (transparent) pixels
const int screenX = x + px;
// Bounds checking
if (const int screenY = y + py;
screenX >= 0 && screenX < u8g2->width && screenY >= 0 && screenY < u8g2->height)
{
u8g2_DrawPixel(u8g2, screenX, screenY);
}
}
// Black pixels are simply not drawn (transparent)
}
}
}
const unsigned char *ScreenSaver::getVehicleBitmap(const VehicleType type, const Direction direction, int &width,
int &height)
{
switch (type)
{
case VehicleType::CAR:
width = car_width;
height = car_height;
return (direction == Direction::LEFT) ? car_left_bits : car_right_bits;
case VehicleType::CONVERTABLE:
width = convertable_width;
height = convertable_height;
return (direction == Direction::LEFT) ? convertable_left_bits : convertable_right_bits;
case VehicleType::SUV:
width = suv_width;
height = suv_height;
return (direction == Direction::LEFT) ? suv_left_bits : suv_right_bits;
case VehicleType::LORRY:
width = lorry_width;
height = lorry_height;
return (direction == Direction::LEFT) ? lorry_left_bits : lorry_right_bits;
case VehicleType::TRUCK:
width = truck_width;
height = truck_height;
return (direction == Direction::LEFT) ? truck_left_bits : truck_right_bits;
default:
width = car_width;
height = car_height;
return car_left_bits;
}
}
void ScreenSaver::onButtonClicked(ButtonType button)
{
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}

View File

@@ -0,0 +1,11 @@
#include "ui/SettingsMenu.h"
namespace SettingsMenuItem
{
constexpr uint8_t OTA_UPLOAD = 0;
}
SettingsMenu::SettingsMenu(menu_options_t *options) : Menu(options)
{
addText(SettingsMenuItem::OTA_UPLOAD, "OTA Einspielen");
}

View File

@@ -0,0 +1,39 @@
#include "ui/SplashScreen.h"
#include "ui/MainMenu.h"
#ifndef ESP32
#include <chrono>
#include <thread>
#endif
uint64_t counter = 0;
SplashScreen::SplashScreen(menu_options_t *options) : Widget(options->u8g2), m_options(options)
{
}
void SplashScreen::update(const uint64_t dt)
{
counter += dt;
if (counter >= 3000)
{
counter = 0;
if (m_options && m_options->setScreen)
{
m_options->setScreen(std::make_shared<MainMenu>(m_options));
}
}
#ifndef ESP32
std::this_thread::sleep_for(std::chrono::milliseconds(1));
#endif
}
void SplashScreen::render()
{
u8g2_SetFont(u8g2, u8g2_font_DigitalDisco_tr);
u8g2_DrawStr(u8g2, 28, u8g2->height / 2 - 10, "HO Anlage");
u8g2_DrawStr(u8g2, 30, u8g2->height / 2 + 5, "Axel Janz");
u8g2_SetFont(u8g2, u8g2_font_haxrcorp4089_tr);
u8g2_DrawStr(u8g2, 35, 50, "Initialisierung...");
}