From 3c3724e4db2f7648996f2c007e8679dde77cef0e Mon Sep 17 00:00:00 2001 From: Jason Valdron Date: Fri, 20 May 2022 22:57:51 -0300 Subject: [PATCH] Improve interface --- .../OmgHeatTracker.ino | 601 +++++++++--------- remote.html | 206 +++++- remote.min.html | 2 +- 3 files changed, 493 insertions(+), 316 deletions(-) rename OmgHeatTracker.ino => OmgHeatTracker/OmgHeatTracker.ino (79%) diff --git a/OmgHeatTracker.ino b/OmgHeatTracker/OmgHeatTracker.ino similarity index 79% rename from OmgHeatTracker.ino rename to OmgHeatTracker/OmgHeatTracker.ino index 878c818..cf19e66 100644 --- a/OmgHeatTracker.ino +++ b/OmgHeatTracker/OmgHeatTracker.ino @@ -1,300 +1,301 @@ -/** - * OmgHeatTracker.ino - * Provides the application code for a Heat Tracker used by the OMG flyball - * club. - * - * Designed and tested for a 'LOLIN (WEMOS) D1 R2' plugged into a - * 'detachable-esp8266-4x-wire-to-board'. - * - * @author Jason Valdron - * @version 1.0 / 2019-09-16 - */ - -#include -#include -#include -#include -#include -#include - -// Defines the number of LEDs per Heat Tracker light. -#define NUM_LEDS 7 - -// Defines the total number of heats, matching the number of lights. -#define TOTAL_HEATS 5 - -// The SSID/password that is broadcast, the remote's config should match this. -#define SSID "omg-heat-tracker-" -#define PASSWORD "flyball123" - -// Application logic. -enum HeatResult { none, left, right, tie }; -const char* heatResultStringMapping[] = { - "none", - "left", - "right", - "tie" -}; -const CRGB heatResultColorMapping[] = { - CRGB::Black, - CRGB::Blue, - CRGB::Green, - CRGB::White -}; - -// Keeps track of the heats, and how we're flashing lights. -HeatResult heats[TOTAL_HEATS]; -int currentHeat = 0; -int flashRemaining = 0; -bool flash = false; - -// The global LEDs and HTTP server. -CRGB leds[TOTAL_HEATS][NUM_LEDS]; -ESP8266WebServer http(80); - -/** - * Main entrypoint of the application. This is where we setup the LEDs, using - * FastLED, where we setup the access point, the HTTP routes, setup the initial - * states for the race, then start the HTTP server. - */ -void setup() -{ - Serial.begin(115200); - Serial.println("Setting up..."); - - setupLeds(); - showStatus(0); - - setupAccessPoint(); - showStatus(1); - - setupHttpRoutes(); - showStatus(2); - - resetRace(); - showStatus(3); - - http.begin(); - Serial.println("HTTP: HTTP server started"); - showStatus(4); - - FastLED.clear(); -} - -/** - * The main loop method of the application. This is where the HTTP server - * handles the clients, and sets the states of the lights. - */ -void loop() -{ - http.handleClient(); - setLightsState(); -} - -/** - * Provides a method to show the status on the given light. This is simply to - * turn on all 5 lights in the setup method, in order to provide a visual - * indication that the Heat Tracker has been turned on and working as expected. - */ -void showStatus(int x) -{ - fill_solid(leds[x], NUM_LEDS, CRGB::Teal); - FastLED.show(); - FastLED.delay(500); -} - -/** - * Sets the state of the lights. This is the main method that turns on the - * lights given the heat status. - */ -void setLightsState() -{ - // We set the state of all lights. - for (int x = 0; x < TOTAL_HEATS; x++) - { - // This maps the heat's result (heats array) to the resultant color - // mapping (heatResultColorMapping). - CRGB color = heatResultColorMapping[heats[x]]; - - // If we're setting the light of the last heat, and it needs to flash, - // we flash here. - if (flash && x == currentHeat - 1) - { - color = CRGB::Black; - } - - // Set the LEDs colors. - fill_solid(leds[x], NUM_LEDS, color); - } - - // FastLED requires show to update the colors, all at once. - FastLED.show(); - FastLED.delay(250); - - // If we're still flashing, small logic to invert the flash state, and - // remove one flash remaining. - if (flashRemaining > 0) - { - flashRemaining--; - flash = !flash; - FastLED.delay(250); - } - else - { - flash = false; - } - -} - -/** - * Resets the race states, removes all heats, set back to the first heat and - * removes flashes. - */ -void resetRace() -{ - Serial.println("Race: Resetting all heats, starting new race"); - for (int i = 0; i < TOTAL_HEATS; i++) - { - heats[i] = none; - } - currentHeat = 0; - flashRemaining = 0; - flash = false; -} - -/** - * Used to setup all TOTAL_HEATS lights, current Heat Tracker has 5 lights, all - * from D1 through D5 on a D1 Mini. - */ -void setupLeds() -{ - FastLED.addLeds(leds[0], NUM_LEDS); - FastLED.addLeds(leds[1], NUM_LEDS); - FastLED.addLeds(leds[2], NUM_LEDS); - FastLED.addLeds(leds[3], NUM_LEDS); - FastLED.addLeds(leds[4], NUM_LEDS); -} - -/** - * Sets up the access point, so that the remote can connect to it, and that we - * can provide a small browser based remote. This sets up an AP to SSID + two - * digits from the MAC address of the current ESP. - */ -void setupAccessPoint() -{ - Serial.println("Wifi: Setting Wifi to AP mode"); - WiFi.mode(WIFI_AP); - - uint8_t mac[WL_MAC_ADDR_LENGTH]; - WiFi.softAPmacAddress(mac); - String id = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + - String(mac[WL_MAC_ADDR_LENGTH - 1], HEX); - id.toUpperCase(); - - String ap = SSID + id; - - char buffer[ap.length() + 1]; - ap.toCharArray(buffer, ap.length() - 1); - - Serial.print("Wifi: Using SSID "); - Serial.println(buffer); - - WiFi.softAP(buffer, PASSWORD); - - IPAddress ip = WiFi.softAPIP(); - Serial.print("Wifi: Ready with IP "); - Serial.println(ip); -} - -/** - * Sets up the browser based remote and the API. See README.md for API - * endpoints. - */ -void setupHttpRoutes() -{ - // GET: / - // Provides the browser based remote. This just sends the 'remote.min.html' - // file's content. - http.on("/", HTTP_GET, []() - { - http.send(200, "text/html", "
Heat #1: Loading...
Heat #2: Loading...
Heat #3: Loading...
Heat #4: Loading...
Heat #5: Loading...
"); - }); - - // GET: /heats - // Gets the current heats results. - http.on("/heats", HTTP_GET, []() - { - const size_t CAPACITY = JSON_ARRAY_SIZE(TOTAL_HEATS); - StaticJsonDocument doc; - JsonArray array = doc.to(); - - for (int i = 0; i < TOTAL_HEATS; i++) - { - array.add(heatResultStringMapping[heats[i]]); - } - - String json; - serializeJsonPretty(doc, json); - - http.send(200, "application/json", json); - }); - - // POST: /heat - // Sends the current heat's status. Takes in a JSON object with one - // property, 'result', which should match the HeatResult enum. - http.on("/heat", HTTP_POST, []() { - if (currentHeat == 5) - { - http.send(400, "application/json", "On last heat, must reset."); - } - else - { - StaticJsonDocument<40> doc; - DeserializationError error = deserializeJson(doc, http.arg("plain")); - - Serial.print("HTTP: Received "); - Serial.println(http.arg("plain")); - - if (error) - { - http.send(400, "application/json", "{ \"error\": \"Invalid JSON body.\" }"); - return; - } - - int result = doc["result"]; - - Serial.print("Race: Win "); - Serial.println(heatResultStringMapping[result]); - heats[currentHeat++] = static_cast(result); - flashRemaining = 8; - http.send(201); - } - }); - - // DELETE: /heats - // Resets all heats, starts a new race. - http.on("/heats", HTTP_DELETE, []() - { - Serial.println("Race: Resetting race"); - resetRace(); - http.send(200); - }); - - // DELETE: /heat/last - // Resets the last heat, in case it was done by mistake. - http.on("/heat/last", HTTP_DELETE, []() - { - if (currentHeat == 0) - { - http.send(400, "application/json", "{ \"error\": \"Cannot undo heat, currently on first heat.\" }"); - } - else - { - Serial.println("Race: Undoing last heat"); - heats[--currentHeat] = none; - flashRemaining = 0; - flash = false; - http.send(200); - } - }); -} \ No newline at end of file +/** + * OmgHeatTracker.ino + * Provides the application code for a Heat Tracker used by the OMG flyball + * club. + * + * Designed and tested for a 'LOLIN (WEMOS) D1 R2' plugged into a + * 'detachable-esp8266-4x-wire-to-board'. + * + * @author Jason Valdron + * @version 1.0 / 2019-09-16 + * @version 1.1 / 2022-05-20 / Better UI + */ + +#include +#include +#include +#include +#include +#include + +// Defines the number of LEDs per Heat Tracker light. +#define NUM_LEDS 7 + +// Defines the total number of heats, matching the number of lights. +#define TOTAL_HEATS 5 + +// The SSID/password that is broadcast, the remote's config should match this. +#define SSID "omg-heat-tracker-" +#define PASSWORD "flyball123" + +// Application logic. +enum HeatResult { none, left, right, tie }; +const char* heatResultStringMapping[] = { + "none", + "left", + "right", + "tie" +}; +const CRGB heatResultColorMapping[] = { + CRGB::Black, + CRGB::Blue, + CRGB::Green, + CRGB::White +}; + +// Keeps track of the heats, and how we're flashing lights. +HeatResult heats[TOTAL_HEATS]; +int currentHeat = 0; +int flashRemaining = 0; +bool flash = false; + +// The global LEDs and HTTP server. +CRGB leds[TOTAL_HEATS][NUM_LEDS]; +ESP8266WebServer http(80); + +/** + * Main entrypoint of the application. This is where we setup the LEDs, using + * FastLED, where we setup the access point, the HTTP routes, setup the initial + * states for the race, then start the HTTP server. + */ +void setup() +{ + Serial.begin(115200); + Serial.println("Setting up..."); + + setupLeds(); + showStatus(0); + + setupAccessPoint(); + showStatus(1); + + setupHttpRoutes(); + showStatus(2); + + resetRace(); + showStatus(3); + + http.begin(); + Serial.println("HTTP: HTTP server started"); + showStatus(4); + + FastLED.clear(); +} + +/** + * The main loop method of the application. This is where the HTTP server + * handles the clients, and sets the states of the lights. + */ +void loop() +{ + http.handleClient(); + setLightsState(); +} + +/** + * Provides a method to show the status on the given light. This is simply to + * turn on all 5 lights in the setup method, in order to provide a visual + * indication that the Heat Tracker has been turned on and working as expected. + */ +void showStatus(int x) +{ + fill_solid(leds[x], NUM_LEDS, CRGB::Teal); + FastLED.show(); + FastLED.delay(500); +} + +/** + * Sets the state of the lights. This is the main method that turns on the + * lights given the heat status. + */ +void setLightsState() +{ + // We set the state of all lights. + for (int x = 0; x < TOTAL_HEATS; x++) + { + // This maps the heat's result (heats array) to the resultant color + // mapping (heatResultColorMapping). + CRGB color = heatResultColorMapping[heats[x]]; + + // If we're setting the light of the last heat, and it needs to flash, + // we flash here. + if (flash && x == currentHeat - 1) + { + color = CRGB::Black; + } + + // Set the LEDs colors. + fill_solid(leds[x], NUM_LEDS, color); + } + + // FastLED requires show to update the colors, all at once. + FastLED.show(); + FastLED.delay(250); + + // If we're still flashing, small logic to invert the flash state, and + // remove one flash remaining. + if (flashRemaining > 0) + { + flashRemaining--; + flash = !flash; + FastLED.delay(250); + } + else + { + flash = false; + } + +} + +/** + * Resets the race states, removes all heats, set back to the first heat and + * removes flashes. + */ +void resetRace() +{ + Serial.println("Race: Resetting all heats, starting new race"); + for (int i = 0; i < TOTAL_HEATS; i++) + { + heats[i] = none; + } + currentHeat = 0; + flashRemaining = 0; + flash = false; +} + +/** + * Used to setup all TOTAL_HEATS lights, current Heat Tracker has 5 lights, all + * from D1 through D5 on a D1 Mini. + */ +void setupLeds() +{ + FastLED.addLeds(leds[0], NUM_LEDS); + FastLED.addLeds(leds[1], NUM_LEDS); + FastLED.addLeds(leds[2], NUM_LEDS); + FastLED.addLeds(leds[3], NUM_LEDS); + FastLED.addLeds(leds[4], NUM_LEDS); +} + +/** + * Sets up the access point, so that the remote can connect to it, and that we + * can provide a small browser based remote. This sets up an AP to SSID + two + * digits from the MAC address of the current ESP. + */ +void setupAccessPoint() +{ + Serial.println("Wifi: Setting Wifi to AP mode"); + WiFi.mode(WIFI_AP); + + uint8_t mac[WL_MAC_ADDR_LENGTH]; + WiFi.softAPmacAddress(mac); + String id = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + + String(mac[WL_MAC_ADDR_LENGTH - 1], HEX); + id.toUpperCase(); + + String ap = SSID + id; + + char buffer[ap.length() + 1]; + ap.toCharArray(buffer, ap.length() - 1); + + Serial.print("Wifi: Using SSID "); + Serial.println(buffer); + + WiFi.softAP(buffer, PASSWORD); + + IPAddress ip = WiFi.softAPIP(); + Serial.print("Wifi: Ready with IP "); + Serial.println(ip); +} + +/** + * Sets up the browser based remote and the API. See README.md for API + * endpoints. + */ +void setupHttpRoutes() +{ + // GET: / + // Provides the browser based remote. This just sends the 'remote.min.html' + // file's content. + http.on("/", HTTP_GET, []() + { + http.send(200, "text/html", "
"); + }); + + // GET: /heats + // Gets the current heats results. + http.on("/heats", HTTP_GET, []() + { + const size_t CAPACITY = JSON_ARRAY_SIZE(TOTAL_HEATS); + StaticJsonDocument doc; + JsonArray array = doc.to(); + + for (int i = 0; i < TOTAL_HEATS; i++) + { + array.add(heatResultStringMapping[heats[i]]); + } + + String json; + serializeJsonPretty(doc, json); + + http.send(200, "application/json", json); + }); + + // POST: /heat + // Sends the current heat's status. Takes in a JSON object with one + // property, 'result', which should match the HeatResult enum. + http.on("/heat", HTTP_POST, []() { + if (currentHeat == 5) + { + http.send(400, "application/json", "On last heat, must reset."); + } + else + { + StaticJsonDocument<40> doc; + DeserializationError error = deserializeJson(doc, http.arg("plain")); + + Serial.print("HTTP: Received "); + Serial.println(http.arg("plain")); + + if (error) + { + http.send(400, "application/json", "{ \"error\": \"Invalid JSON body.\" }"); + return; + } + + int result = doc["result"]; + + Serial.print("Race: Win "); + Serial.println(heatResultStringMapping[result]); + heats[currentHeat++] = static_cast(result); + flashRemaining = 8; + http.send(201); + } + }); + + // DELETE: /heats + // Resets all heats, starts a new race. + http.on("/heats", HTTP_DELETE, []() + { + Serial.println("Race: Resetting race"); + resetRace(); + http.send(200); + }); + + // DELETE: /heat/last + // Resets the last heat, in case it was done by mistake. + http.on("/heat/last", HTTP_DELETE, []() + { + if (currentHeat == 0) + { + http.send(400, "application/json", "{ \"error\": \"Cannot undo heat, currently on first heat.\" }"); + } + else + { + Serial.println("Race: Undoing last heat"); + heats[--currentHeat] = none; + flashRemaining = 0; + flash = false; + http.send(200); + } + }); +} diff --git a/remote.html b/remote.html index 0a696db..c959205 100644 --- a/remote.html +++ b/remote.html @@ -3,6 +3,8 @@ + - -
Heat #1: Loading...
-
Heat #2: Loading...
-
Heat #3: Loading...
-
Heat #4: Loading...
-
Heat #5: Loading...
-
- - - + +
+
+ +
+
+
+
+
+
+
+
+
-
- - +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
- \ No newline at end of file + diff --git a/remote.min.html b/remote.min.html index 2583bc3..f744090 100644 --- a/remote.min.html +++ b/remote.min.html @@ -1 +1 @@ -
Heat #1: Loading...
Heat #2: Loading...
Heat #3: Loading...
Heat #4: Loading...
Heat #5: Loading...
\ No newline at end of file +