#pragma once #include "wled.h" /* * Usermod that implements BobLight "ambilight" protocol * * See the accompanying README.md file for more info. */ #ifndef BOB_PORT #define BOB_PORT 19333 // Default boblightd port #endif class BobLightUsermod : public Usermod { typedef struct _LIGHT { char lightname[5]; float hscan[2]; float vscan[2]; } light_t; private: unsigned long lastTime = 0; bool enabled = false; bool initDone = false; light_t *lights = nullptr; uint16_t numLights = 0; // 16 + 9 + 16 + 9 uint16_t top, bottom, left, right; // will be filled in readFromConfig() uint16_t pct; WiFiClient bobClient; WiFiServer *bob; uint16_t bobPort = BOB_PORT; static const char _name[]; static const char _enabled[]; /* # boblight # Copyright (C) Bob 2009 # # makeboblight.sh created by Adam Boeglin # # boblight is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # boblight is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . */ // fills the lights[] array with position & depth of scan for each LED void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { int lightcount = 0; int total = top+left+right+bottom; int bcount; if (total > strip.getLengthTotal()) { DEBUG_PRINTLN(F("BobLight: Too many lights.")); return; } // start left part of bottom strip (clockwise direction, 1st half) if (bottom > 0) { bcount = 1; float brange = 100.0/bottom; float bcurrent = 50.0; if (bottom < top) { int diff = top - bottom; brange = 100.0/top; bcurrent -= (diff/2)*brange; } while (bcount <= bottom/2) { float btop = bcurrent - brange; String name = "b"+String(bcount); strncpy(lights[lightcount].lightname, name.c_str(), 4); lights[lightcount].hscan[0] = btop; lights[lightcount].hscan[1] = bcurrent; lights[lightcount].vscan[0] = 100 - pct_scan; lights[lightcount].vscan[1] = 100; lightcount+=1; bcurrent = btop; bcount+=1; } } // left side if (left > 0) { int lcount = 1; float lrange = 100.0/left; float lcurrent = 100.0; while (lcount <= left) { float ltop = lcurrent - lrange; String name = "l"+String(lcount); strncpy(lights[lightcount].lightname, name.c_str(), 4); lights[lightcount].hscan[0] = 0; lights[lightcount].hscan[1] = pct_scan; lights[lightcount].vscan[0] = ltop; lights[lightcount].vscan[1] = lcurrent; lightcount+=1; lcurrent = ltop; lcount+=1; } } // top side if (top > 0) { int tcount = 1; float trange = 100.0/top; float tcurrent = 0; while (tcount <= top) { float ttop = tcurrent + trange; String name = "t"+String(tcount); strncpy(lights[lightcount].lightname, name.c_str(), 4); lights[lightcount].hscan[0] = tcurrent; lights[lightcount].hscan[1] = ttop; lights[lightcount].vscan[0] = 0; lights[lightcount].vscan[1] = pct_scan; lightcount+=1; tcurrent = ttop; tcount+=1; } } // right side if (right > 0) { int rcount = 1; float rrange = 100.0/right; float rcurrent = 0; while (rcount <= right) { float rtop = rcurrent + rrange; String name = "r"+String(rcount); strncpy(lights[lightcount].lightname, name.c_str(), 4); lights[lightcount].hscan[0] = 100-pct_scan; lights[lightcount].hscan[1] = 100; lights[lightcount].vscan[0] = rcurrent; lights[lightcount].vscan[1] = rtop; lightcount+=1; rcurrent = rtop; rcount+=1; } } // right side of bottom strip (2nd half) if (bottom > 0) { float brange = 100.0/bottom; float bcurrent = 100; if (bottom < top) { brange = 100.0/top; } while (bcount <= bottom) { float btop = bcurrent - brange; String name = "b"+String(bcount); strncpy(lights[lightcount].lightname, name.c_str(), 4); lights[lightcount].hscan[0] = btop; lights[lightcount].hscan[1] = bcurrent; lights[lightcount].vscan[0] = 100 - pct_scan; lights[lightcount].vscan[1] = 100; lightcount+=1; bcurrent = btop; bcount+=1; } } numLights = lightcount; #if WLED_DEBUG DEBUG_PRINTLN(F("Fill light data: ")); DEBUG_PRINTF(" lights %d\n", numLights); for (int i=0; i strip.getLengthTotal() ) { DEBUG_PRINTLN(F("BobLight: Too many lights.")); DEBUG_PRINTF("%d+%d+%d+%d>%d\n", bottom, left, top, right, strip.getLengthTotal()); totalLights = strip.getLengthTotal(); top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); } lights = new light_t[totalLights]; if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights else enable(false); initDone = true; } void connected() { // we can only start server when WiFi is connected if (!bob) bob = new WiFiServer(bobPort, 1); bob->begin(); bob->setNoDelay(true); } void loop() { if (!enabled || strip.isUpdating()) return; if (millis() - lastTime > 10) { lastTime = millis(); pollBob(); } } void enable(bool en) { enabled = en; } #ifndef WLED_DISABLE_MQTT /** * handling of MQTT message * topic only contains stripped topic (part after /wled/MAC) * topic should look like: /swipe with amessage of [up|down] */ bool onMqttMessage(char* topic, char* payload) { //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { // String action = payload; // if (action == "on") { // enable(true); // return true; // } else if (action == "off") { // enable(false); // return true; // } //} return false; } /** * subscribe to MQTT topic for controlling usermod */ void onMqttConnect(bool sessionPresent) { //char subuf[64]; //if (mqttDeviceTopic[0] != 0) { // strcpy(subuf, mqttDeviceTopic); // strcat_P(subuf, PSTR("/subtopic")); // mqtt->subscribe(subuf, 0); //} } #endif void addToJsonInfo(JsonObject& root) { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); JsonArray infoArr = user.createNestedArray(FPSTR(_name)); String uiDomString = F(""); infoArr.add(uiDomString); } /* * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ void addToJsonState(JsonObject& root) { } /* * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ void readFromJsonState(JsonObject& root) { if (!initDone) return; // prevent crash on boot applyPreset() bool en = enabled; JsonObject um = root[FPSTR(_name)]; if (!um.isNull()) { if (um[FPSTR(_enabled)].is()) { en = um[FPSTR(_enabled)].as(); } else { String str = um[FPSTR(_enabled)]; // checkbox -> off or on en = (bool)(str!="off"); // off is guaranteed to be present } if (en != enabled && lights) { enable(en); if (!enabled && bob && bob->hasClient()) { if (bobClient) bobClient.stop(); bobClient = bob->available(); BobClear(); exitRealtime(); } } } } void appendConfigData() { //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); //oappend(SET_F("addOption(dd,'1st value',0);")); //oappend(SET_F("addOption(dd,'2nd value',1);")); oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) { JsonObject umData = root.createNestedObject(FPSTR(_name)); umData[FPSTR(_enabled)] = enabled; umData[F("port")] = bobPort; umData[F("top")] = top; umData[F("bottom")] = bottom; umData[F("left")] = left; umData[F("right")] = right; umData[F("pct")] = pct; } bool readFromConfig(JsonObject& root) { JsonObject umData = root[FPSTR(_name)]; bool configComplete = !umData.isNull(); bool en = enabled; configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); enable(en); configComplete &= getJsonValue(umData[F("port")], bobPort); configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); configComplete &= getJsonValue(umData[F("top")], top, 16); configComplete &= getJsonValue(umData[F("left")], left, 9); configComplete &= getJsonValue(umData[F("right")], right, 9); configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] pct = MIN(50,MAX(1,pct)); uint16_t totalLights = bottom + left + top + right; if (initDone && numLights != totalLights) { if (lights) delete[] lights; setup(); } return configComplete; } /* * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. * Commonly used for custom clocks (Cronixie, 7 segment) */ void handleOverlayDraw() { //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black } uint16_t getId() { return USERMOD_ID_BOBLIGHT; } }; // strings to reduce flash memory usage (used more than twice) const char BobLightUsermod::_name[] PROGMEM = "BobLight"; const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; // main boblight handling (definition here prevents inlining) void BobLightUsermod::pollBob() { //check if there are any new clients if (bob && bob->hasClient()) { //find free/disconnected spot if (!bobClient || !bobClient.connected()) { if (bobClient) bobClient.stop(); bobClient = bob->available(); DEBUG_PRINTLN(F("Boblight: Client connected.")); } //no free/disconnected spot so reject WiFiClient bobClientTmp = bob->available(); bobClientTmp.stop(); BobClear(); exitRealtime(); } //check clients for data if (bobClient && bobClient.connected()) { realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected //get data from the client while (bobClient.available()) { String input = bobClient.readStringUntil('\n'); // DEBUG_PRINT("Client: "); DEBUG_PRINTLN(input); // may be to stressful on Serial if (input.startsWith(F("hello"))) { DEBUG_PRINTLN(F("hello")); bobClient.print(F("hello\n")); } else if (input.startsWith(F("ping"))) { DEBUG_PRINTLN(F("ping 1")); bobClient.print(F("ping 1\n")); } else if (input.startsWith(F("get version"))) { DEBUG_PRINTLN(F("version 5")); bobClient.print(F("version 5\n")); } else if (input.startsWith(F("get lights"))) { char tmp[64]; String answer = ""; sprintf_P(tmp, PSTR("lights %d\n"), numLights); DEBUG_PRINT(tmp); answer.concat(tmp); for (int i=0; i ... input.remove(0,10); String tmp = input.substring(0,input.indexOf(' ')); int light_id = -1; for (uint16_t i=0; iavailable(); BobClear(); } } } }