From 61e54a2557d7859b3942211f7c1340b04cf4c696 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sun, 17 Nov 2019 01:16:46 +0100 Subject: [PATCH] Initial commit: SK6812 LED Fader, HTTP Server --- .gitignore | 7 ++ data/index.html | 10 ++ include/HTTPServer.h | 35 ++++++ include/WLAN_Logins.h.example | 13 +++ platformio.ini | 22 ++++ src/Fader.cpp | 101 +++++++++++++++++ src/Fader.h | 68 ++++++++++++ src/HTTPServer.cpp | 135 +++++++++++++++++++++++ src/main.cpp | 198 ++++++++++++++++++++++++++++++++++ 9 files changed, 589 insertions(+) create mode 100644 .gitignore create mode 100644 data/index.html create mode 100644 include/HTTPServer.h create mode 100644 include/WLAN_Logins.h.example create mode 100644 platformio.ini create mode 100644 src/Fader.cpp create mode 100644 src/Fader.h create mode 100644 src/HTTPServer.cpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e17e9a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch + +include/WLAN_Logins.h diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..59a8195 --- /dev/null +++ b/data/index.html @@ -0,0 +1,10 @@ + + + + Lampensteuerung + + +

Lampensteuerung

+

It works!

+ + \ No newline at end of file diff --git a/include/HTTPServer.h b/include/HTTPServer.h new file mode 100644 index 0000000..1f8162f --- /dev/null +++ b/include/HTTPServer.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +class Fader; + +class HTTPServer +{ + public: + bool start(void); + + void setFader(Fader *fader) { m_fader = fader;} + + static HTTPServer &instance(void) + { + static HTTPServer theInstance; + return theInstance; + } + + private: + httpsserver::HTTPServer *m_server; + + Fader *m_fader; + + HTTPServer(void); + + static void serverTask(void *arg); + + // handlers + static void handleRoot(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res); + static void handleColor(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res); + static void handle404(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res); +}; \ No newline at end of file diff --git a/include/WLAN_Logins.h.example b/include/WLAN_Logins.h.example new file mode 100644 index 0000000..c48d3b4 --- /dev/null +++ b/include/WLAN_Logins.h.example @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct NetInfo { + const char *ssid; + const char *password; +}; + +const std::array NETWORKS { + NetInfo{"SomeNetwork", "ThePassword"}, + NetInfo{"SomeOtherNetwork", "TheOtherPassword"} +}; diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..cc8d280 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,22 @@ +;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:nodemcu-32s] +platform = espressif32 +board = nodemcu-32s +framework = arduino + +monitor_speed = 115200 + +lib_deps = + ESP32 Digital RGB LED Drivers + esp32_https_server + +build_flags = -std=c++11 \ No newline at end of file diff --git a/src/Fader.cpp b/src/Fader.cpp new file mode 100644 index 0000000..e865fee --- /dev/null +++ b/src/Fader.cpp @@ -0,0 +1,101 @@ +#include "Fader.h" + +Fader::Fader(std::size_t nmodules, uint8_t fadestep) + : m_fadestep(fadestep), + m_curColor(nmodules), + m_targetColor(nmodules) +{} + +void Fader::set_color(uint32_t module, const Fader::Color &color) +{ + m_targetColor[module] = m_curColor[module] = color; + + m_somethingChanged = true; +} + +void Fader::add_color(uint32_t module, const Fader::Color &color) +{ + m_curColor[module] += color; + m_curColor[module].normalize(); + m_targetColor[module] += color; + m_targetColor[module].normalize(); + + m_somethingChanged = true; +} + +void Fader::fade_color(uint32_t module, const Fader::Color &color) +{ + m_targetColor[module] = color; + + m_somethingChanged = true; +} + +void Fader::set_color(const Fader::Color &color) +{ + for(std::size_t module = 0; module < m_curColor.size(); module++) { + m_targetColor[module] = m_curColor[module] = color; + } + + m_somethingChanged = true; +} + +void Fader::add_color(const Fader::Color &color) +{ + for (std::size_t module = 0; module < m_curColor.size(); module++) { + m_curColor[module] += color; + m_curColor[module].normalize(); + m_targetColor[module] += color; + m_targetColor[module].normalize(); + } + + m_somethingChanged = true; +} + +void Fader::fade_color(const Fader::Color &color) +{ + for (std::size_t module = 0; module < m_curColor.size(); module++) { + m_targetColor[module] = color; + } +} + +void Fader::set_fadestep(uint8_t newFadestep) +{ + m_fadestep = newFadestep; +} + +/* internal function */ +bool Fader::update_fade(int16_t *cur, const int16_t *target) +{ + int16_t diff; + if(*cur > *target) { + diff = *cur - *target; + if(diff < m_fadestep) { + *cur = *target; + } else { + *cur -= m_fadestep; + } + + return true; + } else if(*cur < *target) { + diff = *target - *cur; + if(diff < m_fadestep) { + *cur = *target; + } else { + *cur += m_fadestep; + } + + return true; + } + + return false; +} + +void Fader::update(void) +{ + for(std::size_t i = 0; i < m_curColor.size(); i++) { + m_somethingChanged = update_fade(&(m_curColor[i].r), &(m_targetColor[i].r)) || m_somethingChanged; + m_somethingChanged = update_fade(&(m_curColor[i].g), &(m_targetColor[i].g)) || m_somethingChanged; + m_somethingChanged = update_fade(&(m_curColor[i].b), &(m_targetColor[i].b)) || m_somethingChanged; + m_somethingChanged = update_fade(&(m_curColor[i].w), &(m_targetColor[i].w)) || m_somethingChanged; + } +} diff --git a/src/Fader.h b/src/Fader.h new file mode 100644 index 0000000..3892bf0 --- /dev/null +++ b/src/Fader.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +class Fader +{ + public: + struct Color { + int16_t r, g, b, w; + + Color(int16_t ir = 0, int16_t ig = 0, int16_t ib = 0, int16_t iw = 0) : r(ir), g(ig), b(ib), w(iw) {} + + void operator += (const Color &color) + { + this->r += color.r; + this->g += color.g; + this->b += color.b; + this->w += color.w; + } + + void normalize(void) + { + if(r > 255) { r = 255; } else if (r < 0) { r = 0; }; + if(g > 255) { g = 255; } else if (g < 0) { g = 0; }; + if(b > 255) { b = 255; } else if (b < 0) { b = 0; }; + if(w > 255) { w = 255; } else if (w < 0) { w = 0; }; + } + }; + + + Fader(std::size_t nmodules, uint8_t fadestep = 1); + + void set_color(uint32_t module, const Color &color); + void fade_color(uint32_t module, const Color &color); + void add_color(uint32_t module, const Color &color); + void set_color(const Color &color); // for all LEDs + void fade_color(const Color &color); // for all LEDs + void add_color(const Color &color); // for all LEDs + void set_fadestep(uint8_t newFadestep); + + void update(void); + + /*! + * \returns Whether any color value has changed since the last call to getColorValues(). + */ + bool something_changed(void) const {return m_somethingChanged; }; + + const std::vector& get_color_values(void) { + m_somethingChanged = false; + return m_curColor; + } + + private: + /*! + * Fade the colour value in cur towards target. + * + * \param cur The colour value to update. + * \param target The target value that should be reached. + * \returns Whether the value was actually changed. + */ + bool update_fade(int16_t *cur, const int16_t *target); + + uint8_t m_fadestep; + bool m_somethingChanged; + + std::vector m_curColor; + std::vector m_targetColor; +}; \ No newline at end of file diff --git a/src/HTTPServer.cpp b/src/HTTPServer.cpp new file mode 100644 index 0000000..dbedffe --- /dev/null +++ b/src/HTTPServer.cpp @@ -0,0 +1,135 @@ +/* HTTP Server setup and handler functions */ + +#include "HTTPServer.h" + +#include "Fader.h" + +HTTPServer::HTTPServer(void) + : m_fader(NULL) +{ + m_server = new httpsserver::HTTPServer(); +} + +void HTTPServer::handleRoot(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res) +{ + res->setHeader("Content-Type", "text/plain"); + + res->println("Hello World!"); + res->print("Uptime: "); + res->print(millis()); + res->println(" ms"); +} + +static void error400(httpsserver::HTTPResponse *res, const std::string &reason) +{ + res->println("Error 400: Bad Request"); + res->println(reason.c_str()); + + res->setStatusCode(400); +} + +void HTTPServer::handleColor(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res) +{ + res->setHeader("Content-Type", "text/plain"); + + httpsserver::ResourceParameters * params = req->getParams(); + + if(!params->isRequestParameterSet("color")) { + error400(res, "Parameter 'color' not set."); + return; + } + + std::string colorstr = params->getRequestParameter("color"); + + if(colorstr.length() != 8) { + error400(res, "Wrong length of color string (expected 8 hex digits)."); + return; + } + + uint32_t color = 0; + + for(size_t i = 0; i < 8; i++) { + color <<= 4; + + char c = colorstr[i]; + if(c >= 'A' && c <= 'F') { + color |= c - 'A' + 10; + } else if(c >= 'a' && c <= 'f') { + color |= c - 'a' + 10; + } else if(c >= '0' && c <= '9') { + color |= c - '0'; + } else { + error400(res, "Invalid character in color string (allowed: 0-9, a-f, A-F)"); + return; + } + } + + uint8_t r = (color & 0xFF000000) >> 24; + uint8_t g = (color & 0x00FF0000) >> 16; + uint8_t b = (color & 0x0000FF00) >> 8; + uint8_t w = (color & 0x000000FF) >> 0; + + res->print("Color changed to r:"); + res->print(r); + res->print(" g:"); + res->print(g); + res->print(" b:"); + res->print(b); + res->print(" w:"); + res->println(w); + + HTTPServer::instance().m_fader->fade_color({r,g,b,w}); +} + +void HTTPServer::handle404(httpsserver::HTTPRequest *req, httpsserver::HTTPResponse *res) +{ + req->discardRequestBody(); + + res->setStatusCode(404); + res->setHeader("Content-Type", "text/plain"); + + res->println("Error 404: Not found"); +} + +void HTTPServer::serverTask(void *arg) +{ + httpsserver::ResourceNode *nodeRoot = new httpsserver::ResourceNode("/", "GET", + HTTPServer::handleRoot); + httpsserver::ResourceNode *nodeColor = new httpsserver::ResourceNode("/color", "GET", + HTTPServer::handleColor); + httpsserver::ResourceNode *node404 = new httpsserver::ResourceNode("", "GET", + HTTPServer::handle404); + + HTTPServer &server = HTTPServer::instance(); + + server.m_server->registerNode(nodeRoot); + server.m_server->registerNode(nodeColor); + server.m_server->setDefaultNode(node404); + + Serial.println("[server] Starting HTTP Server..."); + server.m_server->start(); + if (server.m_server->isRunning()) + { + Serial.println("[server] Server ready."); + + while (true) + { + server.m_server->loop(); + } + } + + Serial.println("[server] Server died?! This should never happen."); +} + +bool HTTPServer::start(void) +{ + xTaskCreate( + HTTPServer::serverTask, /* Task function. */ + "HTTP Server Task", /* name of task. */ + 6144, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL); /* Task handle to keep track of created task */ + + return true; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7cddb0d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,198 @@ +#include + +#include +#include +#include + +#include "WLAN_Logins.h" +#include "HTTPServer.h" +#include "Fader.h" + +#include +#include + +const uint32_t FRAME_INTERVAL_US = 16666; +const uint32_t NUM_LEDS = 138; + +std::array STRANDS { // Avoid using any of the strapping pins on the ESP32, anything >=32, 16, 17... not much left. + strand_t {.rmtChannel = 0, .gpioNum = 4, .ledType = LED_SK6812W_V1, .brightLimit = 32, .numPixels = NUM_LEDS}, +}; + +bool led_on = false; + +size_t led_idx; +size_t frame; + +WiFiMulti WiFiMulti; + +Fader ledFader(NUM_LEDS); + +bool initLEDs() +{ + /**************************************************************************** + If you have multiple strands connected, but not all are in use, the + GPIO power-on defaults for the unused strand data lines will typically be + high-impedance. Unless you are pulling the data lines high or low via a + resistor, this will lead to noise on those unused but connected channels + and unwanted driving of those unallocated strands. + This optional gpioSetup() code helps avoid that problem programmatically. + ****************************************************************************/ + + digitalLeds_initDriver(); + + for (int i = 0; i < STRANDS.size(); i++) { + gpioSetup(STRANDS[i].gpioNum, OUTPUT, LOW); + } + + strand_t *strands[STRANDS.size()]; + for (int i = 0; i < STRANDS.size(); i++) + { + strands[i] = &STRANDS[i]; + } + int rc = digitalLeds_addStrands(strands, STRANDS.size()); + if (rc) + { + Serial.print("Init rc = "); + Serial.println(rc); + return false; + } + + for (int i = 0; i < STRANDS.size(); i++) + { + strand_t *pStrand = strands[i]; + Serial.print("Strand "); + Serial.print(i); + Serial.print(" = "); + Serial.print((uint32_t)(pStrand->pixels), HEX); + Serial.println(); +#if DEBUG_ESP32_DIGITAL_LED_LIB + dumpDebugBuffer(-2, digitalLeds_debugBuffer); +#endif + } + + led_idx = 0; + frame = 0; + + return true; +} + +static void ledTask( void * parameter ) +{ + /* loop forever */ + for(;;){ + + uint32_t start_time = micros(); + + led_idx++; + if(led_idx >= STRANDS[0].numPixels) { + led_idx = 0; + } + + strand_t *strands[STRANDS.size()]; + for (int i = 0; i < STRANDS.size(); i++) + { + strands[i] = &STRANDS[i]; + } + + ledFader.update(); + + if(ledFader.something_changed()) { + const std::vector &colors = ledFader.get_color_values(); + + for (size_t i = 0; i < STRANDS[0].numPixels; i++) { + const Fader::Color &c = colors[i]; + STRANDS[0].pixels[i] = pixelFromRGBW(c.r, c.g, c.b, c.w); + } + } + + digitalLeds_drawPixels(strands, 1); + + frame++; + + uint32_t now = micros(); + uint32_t duration = now - start_time; + + if(frame % 50 == 0) { + uint32_t load = 100 * duration / FRAME_INTERVAL_US; + + Serial.print("CPU Load: "); + Serial.print(load); + Serial.println(" %"); + } + + if(duration < FRAME_INTERVAL_US) { + delayMicroseconds(FRAME_INTERVAL_US - duration); + } + } + /* delete a task when finish, + this will never happen because this is infinity loop */ + vTaskDelete( NULL ); +} + +void setup() +{ + pinMode(2, OUTPUT); // On-Board LED + + Serial.begin(115200); + delay(10); + + if(!initLEDs()) { + Serial.println("LED setup failed!"); + while(true) { + delay(100); + } + } + + // We start by connecting to a WiFi network + + digitalWrite(2, HIGH); + + Serial.println(); + Serial.println(); + + for (auto &net : NETWORKS) + { + Serial.print("Adding network "); + Serial.println(net.ssid); + WiFiMulti.addAP(net.ssid, net.password); + } + + while (WiFiMulti.run() != WL_CONNECTED) + { + led_on = !led_on; + digitalWrite(2, led_on); + delay(250); + + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + HTTPServer::instance().setFader(&ledFader); + + ledFader.set_color(Fader::Color{64,0,0,0}); + ledFader.fade_color(Fader::Color{0,64,0,0}); + + xTaskCreate( + ledTask, /* Task function. */ + "LED Task", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 2, /* priority of the task */ + NULL); /* Task handle to keep track of created task */ + + HTTPServer::instance().start(); +} + +void loop() { + led_on = !led_on; + digitalWrite(2, led_on); + delay(500); + //Serial.print("RSSI: "); + //Serial.println(WiFi.RSSI()); + + //server.loop(); +} \ No newline at end of file