From 0b40474c229f8c5f90800342f90c845efd272dc3 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 4 Oct 2025 21:11:15 +0200 Subject: [PATCH] running version of Runner Game Controller --- .gitignore | 5 + .vscode/extensions.json | 10 ++ include/README | 37 ++++++ lib/README | 46 +++++++ platformio.ini | 19 +++ src/main.cpp | 279 ++++++++++++++++++++++++++++++++++++++++ test/README | 11 ++ 7 files changed, 407 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/main.cpp create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..186212c --- /dev/null +++ b/platformio.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_speed = 115200 +;lib_deps = +;build_flags=-DTELEGRAM_DEBUG +upload_port = COM7 +monitor_port = COM7 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c060c92 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,279 @@ +#include +#include +#include + +// ===== Konfiguration ===== +#define BUTTON_PIN_1 GPIO_NUM_33 +#define BUTTON_PIN_2 GPIO_NUM_32 +#define BUTTON_PIN_3 GPIO_NUM_19 +#define BUTTON_PIN_4 GPIO_NUM_18 + +// MAC-Adresse des Empfängers (Game-ESP) +// WICHTIG: Durch die echte MAC-Adresse ersetzen! +uint8_t receiverMAC[] = {0x3C, 0x61, 0x05, 0x3E, 0xC7, 0x74}; +esp_now_peer_info_t peerInfo = {}; +int16_t tryChannel = 0; +int16_t ChannelNr = -1; + +// ===== Nachrichtenstruktur ===== +#define MAX_EVENTS_PER_PACKET 20 + +struct ButtonPacket +{ + uint8_t eventCount; // Anzahl Events in diesem Paket + uint8_t playerIds[MAX_EVENTS_PER_PACKET]; // Welcher Spieler (1-4) + uint32_t timestamps[MAX_EVENTS_PER_PACKET]; // Zeitstempel (optional für Debugging) +}; + +// ===== Globale Variablen ===== +ButtonPacket currentPacket; +unsigned long lastSendTime = 0; +unsigned long lastPingTime = 0; +const unsigned long SEND_INTERVAL_MS = 100; // Sende alle 10ms wenn Events vorhanden + +// Debounce +struct ButtonState +{ + bool currentState; + bool debounceState; + bool lastDebouncedState; + unsigned long lastDebounceTime; + unsigned long debounceDelay; +}; + +#define DEBOUNCE_TIME 15 + +ButtonState buttons[4] = { + {true, true, true, 0, DEBOUNCE_TIME}, + {true, true, true, 0, DEBOUNCE_TIME}, + {true, true, true, 0, DEBOUNCE_TIME}, + {true, true, true, 0, DEBOUNCE_TIME}}; + +// ===== ESP-NOW Callback ===== +void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) +{ + if (status == ESP_NOW_SEND_SUCCESS) + { + Serial.println("Send OK"); + } + else + { + Serial.println("Send FAIL"); + } +} +void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) +{ + if ((len == 4) && (incomingData[0] == 'P') && (incomingData[0] == 'O') && (incomingData[0] == 'N') && (incomingData[0] == 'G')) + { + ChannelNr = tryChannel; + Serial.print("PONG on Channel "); + Serial.print(tryChannel); + } +} + +// ===== Event zum Paket hinzufügen ===== +void sendPacket(); + +void addEventToPacket(uint8_t playerId) +{ + if (currentPacket.eventCount < MAX_EVENTS_PER_PACKET) + { + currentPacket.playerIds[currentPacket.eventCount] = playerId; + currentPacket.timestamps[currentPacket.eventCount] = millis(); + currentPacket.eventCount++; + + Serial.print("Added P"); + Serial.print(playerId); + Serial.print(" ("); + Serial.print(currentPacket.eventCount); + Serial.println(" events buffered)"); + } + else + { + // Paket voll - sofort senden + sendPacket(); + addEventToPacket(playerId); // Rekursiv für das neue Event + } +} + +// void changeChannel() +// { +// esp_now_deinit(); +// WiFi.disconnect(); + +// peerInfo.channel += 1; +// if (peerInfo.channel > 14) peerInfo.channel = 1; + +// WiFi.begin("GEHEIM","GEHEIM", peerInfo.channel); +// if (esp_now_init() != ESP_OK) +// { +// Serial.println("ESP-NOW init failed"); +// return; +// } + +// if (esp_now_add_peer(&peerInfo) != ESP_OK) +// { +// Serial.println("Failed to add peer"); +// return; +// } + +// } + +// ===== Paket senden ===== +void sendPacket() +{ + // if (ChannelNr < 0) + // { + // if ((millis() - lastPingTime) > 500) + // { + // changeChannel(); + + // esp_now_send(receiverMAC, (uint8_t *)"PING", 4); + + // Serial.print("PING on channel "); + // Serial.print(peerInfo.channel); + // Serial.println(" events"); + // } + + // return; + // } + + if (currentPacket.eventCount == 0) + return; + + esp_err_t result = esp_now_send(receiverMAC, (uint8_t *)¤tPacket, sizeof(ButtonPacket)); + + if (result == ESP_OK) + { + Serial.print("Sent "); + Serial.print(currentPacket.eventCount); + Serial.println(" events"); + } + else + { + Serial.println("Send error"); + } + + // Paket zurücksetzen + currentPacket.eventCount = 0; + lastSendTime = millis(); +} + +// ===== Button-Polling mit Debounce ===== +void checkButtons() +{ + const uint8_t buttonPins[] = {BUTTON_PIN_1, BUTTON_PIN_2, BUTTON_PIN_3, BUTTON_PIN_4}; + + static uint32_t timestamp = 0; + + timestamp = millis(); + + for (int i = 0; i < 4; i++) + { + buttons[i].currentState = digitalRead(buttonPins[i]); + } + + for (int i = 0; i < 4; i++) + { + // Serial.print(i + 1); + // Serial.print(" = "); + // Serial.println(reading); + + // Bei State-Änderung Debounce-Timer starten + if (buttons[i].currentState != buttons[i].debounceState) + { + buttons[i].lastDebounceTime = timestamp; + buttons[i].debounceState = buttons[i].currentState; + // Serial.print("Reading"); + // Serial.print(i + 1); + // Serial.print(" = "); + // Serial.print(buttons[i].currentState); + // Serial.print(" ==> "); + // Serial.println(buttons[i].lastDebounceTime); + } + + // Nach Debounce-Zeit prüfen + if ((buttons[i].lastDebouncedState != buttons[i].debounceState) && ((timestamp - buttons[i].lastDebounceTime) > buttons[i].debounceDelay)) + { + // Serial.print("Button"); + // Serial.print(i + 1); + // Serial.print(" debounced "); + // Serial.println(buttons[i].debounceState); + + // Fallende Flanke = Button gedrückt (Pull-up aktiv) + if (buttons[i].debounceState == LOW && buttons[i].lastDebouncedState == HIGH) + { + addEventToPacket(i + 1); // Spieler 1-4 + // Serial.println(i + 1); + } + + buttons[i].lastDebouncedState = buttons[i].debounceState; + } + } +} + +// ===== Setup ===== +void setup() +{ + Serial.begin(115200); + Serial.println("ESP-NOW Button Sender"); + + // Button-Pins konfigurieren (mit internem Pull-up) + pinMode(BUTTON_PIN_1, INPUT_PULLUP); + pinMode(BUTTON_PIN_2, INPUT_PULLUP); + pinMode(BUTTON_PIN_3, INPUT_PULLUP); + pinMode(BUTTON_PIN_4, INPUT_PULLUP); + + // WiFi im Station-Mode + WiFi.mode(WIFI_STA); + Serial.print("MAC Address: "); + Serial.println(WiFi.macAddress()); + + Serial.print("Channel: "); + Serial.println(WiFi.channel()); + + // ESP-NOW initialisieren + if (esp_now_init() != ESP_OK) + { + Serial.println("ESP-NOW init failed"); + return; + } + + // Callback registrieren + esp_now_register_send_cb(OnDataSent); + + // Peer hinzufügen + memcpy(peerInfo.peer_addr, receiverMAC, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + + if (esp_now_add_peer(&peerInfo) != ESP_OK) + { + Serial.println("Failed to add peer"); + return; + } + + Serial.println("Setup complete"); + + // Paket initialisieren + currentPacket.eventCount = 0; +} + +// ===== Main Loop ===== +void loop() +{ + unsigned long currentTime = millis(); + + // Buttons überprüfen + checkButtons(); + + // Paket senden wenn: + // 1. Events vorhanden sind UND + // 2. Sendeintervall abgelaufen ist + if ((currentTime - lastSendTime) >= SEND_INTERVAL_MS) + { + sendPacket(); + } + + delay(1); // Kurze Pause +} \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html