This commit is contained in:
Michal Courson
2025-08-28 21:39:35 -04:00
commit b1e155d08b
28 changed files with 7246 additions and 0 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]

13
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@ -0,0 +1,21 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
build/
# sdkconfig
# sdkconfig.old
managed_components/

23
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-14.2.0_20241119\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.espIdfPathWin": "C:\\Users\\mickl\\esp\\v5.5\\esp-idf",
"idf.pythonInstallPath": "C:\\Users\\mickl\\.espressif\\tools\\idf-python\\3.11.2\\python.exe",
"idf.openOcdConfigs": [
"board/esp32s3-builtin.cfg"
],
"idf.portWin": "COM4",
"idf.toolsPathWin": "C:\\Users\\mickl\\.espressif",
"idf.customExtraVars": {
"IDF_TARGET": "esp32s3"
},
"clangd.path": "C:\\Users\\mickl\\.espressif\\tools\\esp-clang\\esp-19.1.2_20250312\\esp-clang\\bin\\clangd.exe",
"clangd.arguments": [
"--background-index",
"--query-driver=C:\\Users\\mickl\\.espressif\\tools\\xtensa-esp-elf\\esp-14.2.0_20241119\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
"--compile-commands-dir=${workspaceFolder}\\build"
],
"idf.flashType": "UART",
"files.associations": {
"cstdint": "cpp",
"limits": "cpp",
"random": "cpp",
"fstream": "cpp"
}
}

10
CMakeLists.txt Normal file
View File

@ -0,0 +1,10 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
add_definitions(
-DARDUINO_USB_MODE
-DARDUINO_USB_CDC_ON_BOOT
)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(trackside)

483
dependencies.lock Normal file
View File

@ -0,0 +1,483 @@
dependencies:
bblanchon/arduinojson:
component_hash: b1c2f0b0bc26969af19809cee80b220fb882481d59cc75f8e79ebceecdc69f06
dependencies: []
source:
registry_url: https://components.espressif.com/
type: service
version: 7.4.2
chmorgan/esp-libhelix-mp3:
component_hash: cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53
dependencies:
- name: idf
require: private
version: '>=4.1.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.3
esp32async/asynctcp:
component_hash: 4d7e7bea969db5b06067152117cabf51510c4193063bb61327e55c63484b156d
dependencies: []
source:
registry_url: https://components.espressif.com
type: service
version: 3.4.5
esp32async/espasyncwebserver:
component_hash: c503fbebd1cb620f2ecd9a35e4d598f946f24ef18a22a533b0661932200efb72
dependencies:
- name: bblanchon/arduinojson
registry_url: https://components.espressif.com
require: private
version: ^7.4.2
- name: esp32async/asynctcp
registry_url: https://components.espressif.com
require: private
version: ^3.4.5
- name: espressif/arduino-esp32
registry_url: https://components.espressif.com
require: private
version: ^3.1.1
source:
registry_url: https://components.espressif.com/
type: service
version: 3.7.10
espressif/arduino-esp32:
component_hash: 12ddb10a6cc6b5bc3c700334627f2a1a7a3736bfad9f63a8115199142104ef6d
dependencies:
- name: chmorgan/esp-libhelix-mp3
registry_url: https://components.espressif.com
require: private
version: 1.0.3
- name: espressif/cbor
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 0.6.0~1
- name: espressif/esp-dsp
registry_url: https://components.espressif.com
require: private
rules:
- if: target != esp32c2
version: ^1.3.4
- name: espressif/esp-modbus
registry_url: https://components.espressif.com
require: private
version: ^1.0.15
- name: espressif/esp-sr
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32s3]
version: ^1.4.2
- name: espressif/esp-zboss-lib
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: ==1.6.4
- name: espressif/esp-zigbee-lib
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: ==1.6.5
- name: espressif/esp_diag_data_store
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 1.0.2
- name: espressif/esp_diagnostics
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 1.2.1
- name: espressif/esp_hosted
registry_url: https://components.espressif.com
require: private
rules:
- if: target == esp32p4
version: ^2.0.12
- name: espressif/esp_insights
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 1.2.2
- name: espressif/esp_modem
registry_url: https://components.espressif.com
require: private
version: ^1.1.0
- name: espressif/esp_rainmaker
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 1.5.2
- name: espressif/esp_wifi_remote
registry_url: https://components.espressif.com
require: private
rules:
- if: target == esp32p4
version: ^0.13.0
- name: espressif/libsodium
registry_url: https://components.espressif.com
require: private
version: ^1.0.20~1
- name: espressif/mdns
registry_url: https://components.espressif.com
require: private
version: ^1.2.3
- name: espressif/network_provisioning
registry_url: https://components.espressif.com
require: private
rules:
- if: target != esp32c2
version: 1.0.2
- name: espressif/qrcode
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 0.1.0~2
- name: espressif/rmaker_common
registry_url: https://components.espressif.com
require: private
rules:
- if: target not in [esp32c2, esp32p4]
version: 1.4.6
- name: idf
require: private
version: '>=5.3,<5.6'
- name: joltwallet/littlefs
registry_url: https://components.espressif.com
require: private
version: ^1.10.2
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32
- esp32s2
- esp32s3
- esp32c2
- esp32c3
- esp32c6
- esp32h2
- esp32p4
- esp32c5
version: 3.3.0~1
espressif/cbor:
component_hash: 440f4ee4504841cc9b4f3a8ef755776a612ac9dace355514c68b999868f990ff
dependencies:
- name: idf
require: private
version: '>=4.3'
source:
registry_url: https://components.espressif.com
type: service
version: 0.6.0~1
espressif/esp-dsp:
component_hash: 3e7bbd487f1357a1d4944d0c85966d049501ea281b8a4c7f93f7cfedd5b7f23d
dependencies:
- name: idf
require: private
version: '>=4.2'
source:
registry_url: https://components.espressif.com
type: service
version: 1.4.12
espressif/esp-modbus:
component_hash: 5d5e90b9e55721a8a194b301ad8102d4affb647f47b74cd413ff7d1ce2c1169c
dependencies:
- name: idf
require: private
version: '>=4.3'
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.18
espressif/esp-serial-flasher:
component_hash: dcc42a16712a1a636509cf0bf90e14032d7f2141784b533613b267b6aa318d52
dependencies: []
source:
registry_url: https://components.espressif.com
type: service
version: 0.0.11
espressif/esp-sr:
component_hash: 9b41fd5ae5960c393bfd3559cd6e5fa2a95c0bf833915cebafe57fb8c4e4c396
dependencies:
- name: espressif/esp-dsp
registry_url: https://components.espressif.com
require: private
version: <=1.5.0
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.9.5
espressif/esp-zboss-lib:
component_hash: 321883d142421f65009972408287441794250057668a11abbdfd8bec77c3309a
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.6.4
espressif/esp-zigbee-lib:
component_hash: 947ea40aa95a89b9da238e941bd2950952e7cd422074d0c02d6896e2ff07bad5
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.6.5
espressif/esp_diag_data_store:
component_hash: c1e5cf62f545d2b136db299f4df1b228b9840be5bc3410c9ad2d2a882b5c0d64
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.2
espressif/esp_diagnostics:
component_hash: 5ea8e8da8217ed9ed778db3973139e726e17cd27ef5cf6429c787d19226c79f3
dependencies:
- name: idf
require: private
version: '>=4.1'
- name: espressif/rmaker_common
registry_url: https://components.espressif.com
require: private
version: ~1.4.0
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.1
espressif/esp_insights:
component_hash: 4015c524b9955528f941268cf080174076b195800de910d061efc46113bc2e0c
dependencies:
- name: idf
require: private
version: '>=4.1'
- name: espressif/cbor
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=5.0
version: ~0.6
- name: espressif/esp_diag_data_store
registry_url: https://components.espressif.com
require: private
version: 1.0.2
- name: espressif/esp_diagnostics
registry_url: https://components.espressif.com
require: private
version: 1.2.1
- name: espressif/rmaker_common
registry_url: https://components.espressif.com
require: private
version: ~1.4.0
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.2
espressif/esp_modem:
component_hash: b541c3f5765d3ba062c0d004b9ca6da6d0af908293421c2dbe046c537fb2a011
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 1.4.0
espressif/esp_rainmaker:
component_hash: f6fe458fc7a0102ee2879f0247da4b41419e6c07de12031f66e5e9454d25baaa
dependencies:
- name: espressif/esp_rcp_update
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >= 5.1
version: ~1.2.0
- name: espressif/esp_schedule
registry_url: https://components.espressif.com
require: private
version: ~1.2.0
- name: espressif/esp_secure_cert_mgr
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=4.3
version: ^2.2.1
- name: espressif/json_generator
registry_url: https://components.espressif.com
require: private
version: ~1.1.1
- name: espressif/json_parser
registry_url: https://components.espressif.com
require: private
version: ~1.0.3
- name: espressif/mdns
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=5.0
version: ^1.2.0
- name: espressif/network_provisioning
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >= 5.1
version: ~1.0.0
- name: espressif/rmaker_common
registry_url: https://components.espressif.com
require: private
version: ~1.4.6
source:
registry_url: https://components.espressif.com
type: service
version: 1.5.2
espressif/esp_rcp_update:
component_hash: c10afbd54a17f27eed880e61262b161656e6d36ad63376c307f9273e99d0abcd
dependencies:
- name: espressif/esp-serial-flasher
registry_url: https://components.espressif.com
require: private
version: ~0.0.0
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.0
espressif/esp_schedule:
component_hash: e202a9c688f7f1ab601efb91d682e4bcfaebc508dcceee1a1e0a0d2d1ca75a26
dependencies:
- name: espressif/rmaker_common
registry_url: https://components.espressif.com
require: private
version: ~1.4.2
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.0
espressif/esp_secure_cert_mgr:
component_hash: 95b490db6944b2a6b32d7af172bb52e5045fe1d37ac8e00f9edfdd0535d64f72
dependencies:
- name: idf
require: private
version: '>=4.3'
source:
registry_url: https://components.espressif.com
type: service
version: 2.5.1
espressif/jsmn:
component_hash: d80350c41bbaa827c98a25b6072df00884e72f54885996fab4a4f0aebce6b6c3
dependencies:
- name: idf
require: private
version: '>=4.3'
source:
registry_url: https://components.espressif.com
type: service
version: 1.1.0
espressif/json_generator:
component_hash: 45033e1c199b13f1c8c1b544fb7d4e2df6a8e3071ebdcb1b22582b61a7974ff2
dependencies: []
source:
registry_url: https://components.espressif.com
type: service
version: 1.1.2
espressif/json_parser:
component_hash: d74b81729ad06ec11ff5eb5b1b0d7df1d00e6027fc11471f4b139c70dcf1b1e4
dependencies:
- name: espressif/jsmn
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=5.0
version: ~1.1
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.3
espressif/libsodium:
component_hash: 25b968723c584a2742ca36cebe5a7ef049c6767e059f7b1e1eec69946019025d
dependencies:
- name: idf
require: private
version: '>=4.2'
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.20~2
espressif/mdns:
component_hash: 3ec0af5f6bce310512e90f482388d21cc7c0e99668172d2f895356165fc6f7c5
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.8.2
espressif/network_provisioning:
component_hash: ef2e10182fd1861e68b821491916327c25416ca7ae70e5a6e43313dbc71fe993
dependencies:
- name: idf
require: private
version: '>=5.1'
source:
registry_url: https://components.espressif.com
type: service
version: 1.0.2
espressif/qrcode:
component_hash: 3b493771bc5d6ad30cbf87c25bf784aada8a08c941504355b55d6b75518ed7bc
dependencies: []
source:
registry_url: https://components.espressif.com
type: service
version: 0.1.0~2
espressif/rmaker_common:
component_hash: a3a1df881278d0351fc850b77792fe8a196ddd6dcacbea203d606329cc6a0239
dependencies: []
source:
registry_url: https://components.espressif.com
type: service
version: 1.4.6
idf:
source:
type: idf
version: 5.5.0
joltwallet/littlefs:
component_hash: 8e12955f47e27e6070b76715a96d6c75fc2b44f069e8c33679332d9bdd3120c4
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.20.1
direct_dependencies:
- bblanchon/arduinojson
- esp32async/espasyncwebserver
- espressif/arduino-esp32
- idf
manifest_hash: 720a2850c92f575db4f5eada42ebbed6314abacc2d6bd4f1d9107b6e0ec03056
target: esp32s3
version: 2.0.0

4
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "Ethernet.cpp" "Server.cpp" "Config.cpp" "main.cpp" "kartReciever.cpp" "Switch.cpp" "Ethernet.cpp"
INCLUDE_DIRS ""
)

61
main/Config.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "Config.hpp"
#include "FS.h"
#include "SPIFFS.h"
Config config;
void Config::Init() {
SPIFFS.begin(true);
File f = SPIFFS.open("/config.json", FILE_READ);
if (!f) {
SetDefaults();
return;
}
DeserializationError err = deserializeJson(conf_json, f);
if (err == DeserializationError::Ok) {
Load();
} else {
SetDefaults();
}
f.close();
}
void Config::SetDefaults() {
Serial.println("Setting default config");
// Set default MAC address
mac_address[0] = 0x34;
mac_address[1] = 0x85;
mac_address[2] = 0x18;
mac_address[3] = 0xa9;
mac_address[4] = 0x3d;
mac_address[5] = 0xfc;
// Set default beacon ID
beacon_id = esp_random();
// Save the defaults to SPIFFS
Save();
}
void Config::Save() {
conf_json.to<JsonObject>();
JsonArray arr = conf_json["mac"].to<JsonArray>();
for (int i = 0; i < 6; i++) {
arr.add<int>(mac_address[i]);
}
conf_json["beacon_id"] = beacon_id;
String out;
serializeJson(conf_json, out);
File f = SPIFFS.open("/config.json", FILE_WRITE, true);
f.print(out);
f.close();
Serial.printf("Config saved: %s\n", out.c_str());
}
void Config::Load() {
JsonArray arr = conf_json["mac"];
for (int i = 0; i < 6; i++) {
mac_address[i] = arr[i];
}
beacon_id = conf_json["beacon_id"];
}

22
main/Config.hpp Normal file
View File

@ -0,0 +1,22 @@
#ifndef CONFIG_HPP
#define CONFIG_HPP
#include <Arduino.h>
#include <ArduinoJson.h>
class Config {
public:
void Init();
void Save();
uint8_t mac_address[6] = {0x34, 0x85, 0x18, 0xa9, 0x3d, 0xfc};
uint32_t beacon_id = -1;
private:
void Load();
void SetDefaults();
JsonDocument conf_json;
};
extern Config config;
#endif

115
main/Ethernet.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "Ethernet.hpp"
#include "ETH.h"
#include "SPI.h"
#define ETH_PHY_TYPE ETH_PHY_W5500
#define ETH_PHY_ADDR 1
#define ETH_PHY_CS 8
#define ETH_PHY_IRQ 3
#define ETH_PHY_RST 5
#define ETH_SPI_SCK 11
#define ETH_SPI_MISO 10
#define ETH_SPI_MOSI 9
static bool eth_connected = false;
EthernetHandler eth;
void onEvent(arduino_event_id_t event, arduino_event_info_t info) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
Serial.println("ETH Started");
// set eth hostname here
ETH.setHostname("esp32-eth0");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
Serial.printf("ETH Got IP: '%s'\n", esp_netif_get_desc(info.got_ip.esp_netif));
Serial.println(ETH);
#if USE_TWO_ETH_PORTS
Serial.println(ETH1);
#endif
eth_connected = true;
break;
case ARDUINO_EVENT_ETH_LOST_IP:
Serial.println("ETH Lost IP");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
default:
break;
}
}
void EthernetHandler::init() {
// Initialization code here
Network.onEvent(onEvent);
SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI);
ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI);
}
void EthernetHandler::begin() {
// Code to start Ethernet communication
}
void EthernetHandler::end() {
// Code to end Ethernet communication
}
String EthernetHandler::post(const String& url, const String& payload) {
if (!eth_connected) {
Serial.println("Ethernet not connected. Cannot perform POST request.");
return "";
}
HTTPClient http;
http.begin(url);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(payload);
String response;
if (httpResponseCode > 0) {
response = http.getString();
Serial.printf("POST Response code: %d\n", httpResponseCode);
Serial.println("Response: " + response);
} else {
Serial.printf("Error on sending POST: %s\n", http.errorToString(httpResponseCode).c_str());
}
http.end();
return response;
}
String EthernetHandler::get(const String& url) {
if (!eth_connected) {
Serial.println("Ethernet not connected. Cannot perform GET request.");
return "";
}
HTTPClient http;
http.begin(url);
int httpResponseCode = http.GET();
String response;
if (httpResponseCode > 0) {
response = http.getString();
Serial.printf("GET Response code: %d\n", httpResponseCode);
Serial.println("Response: " + response);
} else {
Serial.printf("Error on sending GET: %s\n", http.errorToString(httpResponseCode).c_str());
}
http.end();
return response;
}

17
main/Ethernet.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifndef ETHERNET_HANDLER_HPP
#define ETHERNET_HANDLER_HPP
#include "Arduino.h"
#include "HTTPClient.h"
class EthernetHandler {
public:
void init();
void begin();
void end();
String post(const String& url, const String& payload);
String get(const String& url);
};
extern EthernetHandler eth;
#endif // ETHERNET_HANDLER_HPP

83
main/Server.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "Server.hpp"
#include <Arduino.h>
#include "Config.hpp"
#include "WiFi.h"
#include "index.hpp"
WebServer web_server;
void WebServer::Init() {
running = false;
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", index_html);
});
// Endpoint to get config
server.on("/config", HTTP_GET, [](AsyncWebServerRequest *request) {
JsonDocument doc;
JsonArray arr = doc["mac_address"].to<JsonArray>();
for (int i = 0; i < 6; i++) {
arr.add(config.mac_address[i]);
}
doc["beacon_id"] = config.beacon_id;
String out;
serializeJson(doc, out);
Serial.printf("Config requested: %s\n", out.c_str());
request->send(200, "application/json", out);
});
// Endpoint to set config
server.on("/config", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("mac_address", true)) {
String macStr = request->getParam("mac_address", true)->value();
int idx = 0;
int last = 0;
for (int i = 0; i < macStr.length() && idx < 6; i++) {
if (macStr[i] == ',') {
config.mac_address[idx++] = macStr.substring(last, i).toInt();
last = i + 1;
}
}
if (idx < 6) config.mac_address[idx] = macStr.substring(last).toInt();
}
if (request->hasParam("beacon_id", true)) {
config.beacon_id = request->getParam("beacon_id", true)->value().toInt();
}
config.Save();
request->send(200, "text/plain", "Config updated");
});
// Endpoint to restart system
server.on("/restart", HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Restarting...");
for (int i = 0; i < 10; i++) {
digitalWrite(LED_BUILTIN, i % 2);
delay(50);
};
ESP.restart();
});
}
void WebServer::Start() {
if (running) return;
WiFi.disconnect();
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.softAPConfig(local_ip, gateway, subnet);
WiFi.mode(WIFI_AP);
WiFi.softAP(String("beacon_") + String(config.beacon_id, HEX), "password");
running = true;
server.begin();
// Start server logic here
}
void WebServer::Stop() {
if (!running) return;
running = false;
server.end();
// Stop server logic here
}

21
main/Server.hpp Normal file
View File

@ -0,0 +1,21 @@
#ifndef SERVER_HPP
#define SERVER_HPP
#include "ESPAsyncWebServer.h"
class WebServer {
public:
WebServer() : server(80) {}
void Init();
void Start();
void Stop();
bool isRunning() const { return running; }
private:
bool running = false;
AsyncWebServer server;
};
extern WebServer web_server;
#endif

42
main/Switch.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "Switch.hpp"
void Switch::Init(int pin,
float update_rate,
Type t,
Polarity pol,
uint8_t mode) {
last_update_ = millis();
updated_ = false;
state_ = 0x00;
t_ = t;
// Flip may seem opposite to logical direction,
// but here 1 is pressed, 0 is not.
flip_ = pol == POLARITY_INVERTED ? true : false;
pinMode(pin, mode);
pin_ = pin;
}
void Switch::Init(int pin, float update_rate) {
Init(pin,
update_rate,
TYPE_MOMENTARY,
POLARITY_INVERTED,
INPUT_PULLUP);
}
void Switch::Debounce() {
// update no faster than 1kHz
uint32_t now = millis();
updated_ = false;
if (now - last_update_ >= 1) {
last_update_ = now;
updated_ = true;
// shift over, and introduce new state.
const bool new_val = digitalRead(pin_);
state_ = (state_ << 1) | (flip_ ? !new_val : new_val);
// Set time at which button was pressed
if (state_ == 0x7f)
rising_edge_time_ = millis();
}
}

87
main/Switch.hpp Normal file
View File

@ -0,0 +1,87 @@
#ifndef SWITCH_HPP
#define SWITCH_HPP
#include <Arduino.h>
class Switch {
public:
/** Specifies the expected behavior of the switch */
enum Type {
TYPE_TOGGLE, /**< & */
TYPE_MOMENTARY, /**< & */
};
/** Specifies whether the pressed is HIGH or LOW. */
enum Polarity {
POLARITY_NORMAL, /**< & */
POLARITY_INVERTED, /**< & */
};
Switch() {}
~Switch() {}
/**
Initializes the switch object with a given port/pin combo.
\param pin port/pin object to tell the switch which hardware pin to use.
\param update_rate Does nothing. Backwards compatibility until next breaking update.
\param t switch type -- Default: TYPE_MOMENTARY
\param pol switch polarity -- Default: POLARITY_INVERTED
\param pu switch pull up/down -- Default: PULL_UP
*/
void Init(int pin,
float update_rate,
Type t,
Polarity pol,
uint8_t mode = INPUT_PULLUP);
/**
Simplified Init.
\param pin port/pin object to tell the switch which hardware pin to use.
\param update_rate Left for backwards compatibility until next breaking change.
*/
void Init(int pin, float update_rate = 0.f);
/**
Called at update_rate to debounce and handle timing for the switch.
In order for events not to be missed, its important that the Edge/Pressed checks
be made at the same rate as the debounce function is being called.
*/
void Debounce();
/** \return true if a button was just pressed. */
inline bool RisingEdge() const { return updated_ ? state_ == 0x7f : false; }
/** \return true if the button was just released */
inline bool FallingEdge() const {
return updated_ ? state_ == 0x80 : false;
}
/** \return true if the button is held down (or if the toggle is on) */
inline bool Pressed() const { return state_ == 0xff; }
/** \return true if the button is held down, without debouncing */
inline bool RawState() {
const bool raw = digitalRead(pin_);
return flip_ ? !raw : raw;
}
/** \return the time in milliseconds that the button has been held (or toggle has been on) */
inline float TimeHeldMs() const {
return Pressed() ? millis() - rising_edge_time_ : 0;
}
/** Left for backwards compatability until next breaking change
* \param update_rate Doesn't do anything
*/
inline void SetUpdateRate(float update_rate) {}
private:
uint32_t last_update_;
bool updated_;
Type t_;
uint8_t state_;
bool flip_;
float rising_edge_time_;
int pin_;
};
#endif

37
main/TimeSync.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef TIMESYNC_HPP
#define TIMESYNC_HPP
#include <sys/time.h>
#include "Arduino.h"
#include "WiFi.h"
#include "time.h"
class TimeSync {
public:
static void Sync() {
const char* ssid = "ConnectonRefused";
const char* password = "retryconnection";
const char* ntpServer = "pool.ntp.org";
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
configTime(0, 0, ntpServer);
delay(2000);
WiFi.disconnect();
}
static uint64_t GetTime() {
struct timeval tv;
gettimeofday(&tv, NULL); // gets time in seconds and microseconds
uint64_t epochMillis = (uint64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
return epochMillis;
}
};
#endif

19
main/idf_component.yml Normal file
View File

@ -0,0 +1,19 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/arduino-esp32: ^3.3.0
bblanchon/arduinojson: ^7.4.2
esp32async/espasyncwebserver: ^3.7.10

84
main/index.hpp Normal file
View File

@ -0,0 +1,84 @@
#ifndef INDEX_HPP
#define INDEX_HPP
#include <Arduino.h>
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
.input-group {margin: 20px 0;}
label {font-size: 1.2rem;}
input[type='text'] {font-size: 1.2rem; padding: 5px;}
button {font-size: 1.2rem; padding: 10px 20px; margin: 10px;}
</style>
</head>
<body>
<h2>Beacon Config</h2>
<div id="config">
<h3>Device Config</h3>
<div class="input-group">
<label for="mac_address">MAC Address (hex, colon separated):</label>
<input type="text" id="mac_address" value="" placeholder="34:85:18:A9:3D:FC">
</div>
<div class="input-group">
<label for="beacon_id">Beacon ID (hex):</label>
<input type="text" id="beacon_id" value="" placeholder="AABBCCDD">
</div>
<button onclick="saveConfig()">Save Config</button>
<button onclick="restartSystem()">Restart System</button>
<p id="status"></p>
</div>
<script>
function toHex(num, len=2) {
return num.toString(16).toUpperCase().padStart(len, '0');
}
function fetchConfig() {
fetch('/config').then(r => r.json()).then(cfg => {
document.getElementById('mac_address').value = cfg.mac_address.map(x => toHex(x)).join(':');
document.getElementById('beacon_id').value = toHex(cfg.beacon_id, 8);
});
}
function saveConfig() {
const mac = document.getElementById('mac_address').value;
const beacon = document.getElementById('beacon_id').value;
// Convert MAC from hex string to decimal array for backend
const macArr = mac.split(':').map(x => parseInt(x, 16));
const macDec = macArr.join(',');
const beaconDec = parseInt(beacon, 16);
const params = new URLSearchParams();
params.append('mac_address', macDec);
params.append('beacon_id', beaconDec);
fetch('/config', {
method: 'POST',
body: params
}).then(r => r.text()).then(txt => {
document.getElementById('status').innerText = txt;
fetchConfig();
});
}
function restartSystem() {
fetch('/restart', {method: 'POST'}).then(r => r.text()).then(txt => {
document.getElementById('status').innerText = txt;
});
}
window.onload = fetchConfig;
</script>
</body>
</html>
)rawliteral";
#endif

10
main/kartMessage.hpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef KART_MESSAGE_HPP
#define KART_MESSAGE_HPP
#include "Arduino.h"
typedef struct {
uint32_t kart_id;
uint32_t transmision;
} kart_msg;
#endif

43
main/kartReciever.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "kartReciever.hpp"
#include <esp_now.h>
#include <esp_wifi.h>
#include "WiFi.h"
kartReceiver kart_receiver;
static kart_msg rcv_msg;
static void (*onReceiveCallback)(kart_msg *message) = nullptr;
static void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len) {
// todo rssi filtering
memcpy(&rcv_msg, data, sizeof(kart_msg));
// Serial.print("Bytes received: ");
// Serial.println(data_len);
if (onReceiveCallback) {
onReceiveCallback(&rcv_msg);
}
}
void kartReceiver::init() {
}
bool kartReceiver::begin() {
WiFi.disconnect();
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return false;
}
esp_now_register_recv_cb(OnDataRecv);
run_status = true;
return true;
}
void kartReceiver::end() {
esp_now_deinit();
run_status = false;
}
bool kartReceiver::isBegin() {
return run_status;
}
void kartReceiver::registerCallback(void (*onReceiveCallback_)(kart_msg *message)) {
onReceiveCallback = onReceiveCallback_;
}

20
main/kartReciever.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef KART_REC_HPP
#define KART_REC_HPP
#include "kartMessage.hpp"
class kartReceiver {
public:
void init();
bool begin();
void end();
bool isBegin();
void registerCallback(void (*onReceiveCallback)(kart_msg* message));
private:
bool run_status;
};
extern kartReceiver kart_receiver;
#endif

115
main/main.cpp Normal file
View File

@ -0,0 +1,115 @@
#include <esp_now.h>
#include <esp_wifi.h>
#include "Arduino.h"
#include "Config.hpp"
#include "Ethernet.hpp"
#include "SPIFFS.h"
#include "Server.hpp"
#include "Switch.hpp"
#include "TimeSync.hpp"
#include "WiFi.h"
#include "esp32-hal-ledc.h"
#include "kartReciever.hpp"
Switch web_entry_switch;
// 34:85:18:A9:3D:FC
uint8_t trackside_mac[] = {0x34, 0x85, 0x18, 0xa9, 0x3d, 0xfc};
typedef struct struct_message {
char a[32];
int b;
float c;
bool d;
} struct_message;
// Create a struct_message called myData
struct_message myData;
bool tp_state = false;
// callback function that will be executed when data is received
void OnKartMsg(kart_msg* msg) {
tp_state = !tp_state;
digitalWrite(21, tp_state);
Serial.printf("Message\n id: %lu\nTransmission: %lu\n\n", msg->kart_id, msg->transmision);
digitalWrite(RGB_BUILTIN, 1);
delay(100);
digitalWrite(RGB_BUILTIN, 0);
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
randomSeed(analogRead(5));
config.Init();
pinMode(21, OUTPUT);
digitalWrite(21, tp_state);
ledcAttach(1, 256000, 2);
ledcWrite(1, 2); // Sets
pinMode(LED_BUILTIN, OUTPUT);
// TimeSync::Sync()
eth.init();
eth.begin();
kart_receiver.begin();
kart_receiver.registerCallback(OnKartMsg);
web_server.Init();
web_entry_switch.Init(0);
// // Init ESP-NOW
// if (esp_now_init() != ESP_OK) {
// Serial.println("Error initializing ESP-NOW");
// return;
// }
// // Once ESPNow is successfully Init, we will register for recv CB to
// // get recv packer info
// esp_now_register_recv_cb(OnDataRecv);
}
uint8_t led_state = 0;
uint32_t last_led_change = 0;
String speed_in = "";
void loop() {
// Serial.println("loop");
while (Serial.available()) {
char c = (char)Serial.read();
speed_in += c;
if (c == '\n') {
// Process the complete line
Serial.println("Received speed input: " + speed_in);
if (speed_in.toInt() >= 0) {
ledcChangeFrequency(1, speed_in.toInt(), 2);
}
speed_in = "";
}
}
web_entry_switch.Debounce();
if (web_entry_switch.RisingEdge()) {
String response = eth.get("http://example.com");
Serial.println("GET Response: " + response);
}
if (web_server.isRunning()) {
if (millis() - last_led_change > 500) {
led_state ^= 1;
digitalWrite(RGB_BUILTIN, led_state);
last_led_change = millis();
}
if (web_entry_switch.RisingEdge()) {
Serial.println("Web server toggled");
web_server.Stop();
kart_receiver.begin();
digitalWrite(RGB_BUILTIN, 0);
}
} else {
if (web_entry_switch.Pressed() && web_entry_switch.TimeHeldMs() > 3000) {
Serial.println("Web server started");
kart_receiver.end();
web_server.Start();
}
// digitalWrite(LED_BUILTIN, 0);
}
delay(1);
}

7
partitions.csv Normal file
View File

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,
app1, app, ota_1, 0x340000,0x330000,
spiffs, data, spiffs, 0x670000,0x180000,
coredump, data, coredump,0x7F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x330000
5 app1 app ota_1 0x340000 0x330000
6 spiffs data spiffs 0x670000 0x180000
7 coredump data coredump 0x7F0000 0x10000

2932
sdkconfig Normal file

File diff suppressed because it is too large Load Diff

2929
sdkconfig.old Normal file

File diff suppressed because it is too large Load Diff