#include "led_strip.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include #include #include #include "common/led_animation.h" #include "common/LEDStrip.h" #include "global.h" #include "common/neo_colors.h" #include "common/HSVTable.h" #include "JsonConstrain.h" #include "my_board.h" #include "luma_master.h" #include "common/luma-stiks.h" #define STRIP1_PIN RGBLED1_Pin #define STRIP2_PIN RGBLED2_Pin #define POWERUP_TIME 3000 static const char* tag = "led_strip"; ANIMATION_PROPS animProps; LEDSTRIP *strip1; LEDSTRIP *strip2; TaskHandle_t Strip1_Task_Handle; TaskHandle_t Strip2_Task_Handle; TaskHandle_t FrontLight_Task_Handle; QueueHandle_t eventQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) ); QueueHandle_t whiteQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) ); ANIMATION_EVENT lastNormalEventMsg; ANIMATION_EVENT eventMsg; LUMA_PACKET TempLumaPacket; int CurrentRunningEventIndex = -1; void Init_LED_Devices(BOARD_PINS boardPins) { ESP_LOGD(tag, "Strips Initialization entered..."); File file = LittleFS.open("/cfg/led-devices.json"); if(!file){ ESP_LOGE(tag,"Error opening led-devices.json!"); file.close(); }else{ JsonDocument doc; DeserializationError error = deserializeJson(doc, file); file.close(); if(error){ ESP_LOGE(tag, "led-devices.json deserialize error!.."); return;} // ********** STRIP1 ******************************** JsonObject stripJson = doc["strip1"]; if(jsonConstrainBool(tag, stripJson, "en", false)){ int port = jsonConstrain(tag, stripJson, "i2s-ch", 0, 1, 0); int size = jsonConstrain(tag, stripJson, "size", 1, 200, 10); //int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED1_Pin); LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() ); int shift = jsonConstrain(tag, stripJson, "shift", -size, size, 0); int offset = jsonConstrain(tag, stripJson, "offset", -size, size, 0); int core = jsonConstrain(tag, stripJson, "core", 0, 1, 0); int powerDiv = jsonConstrain(tag, stripJson, "power-div", 0, 3, 0); strip1 = new LEDSTRIP(port, size, boardPins.rgb1, order, shift, offset); strip1->setPowerDiv(powerDiv); ESP_LOGI(tag,"Strip1 initialized."); ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset); ESP_LOGD(tag," Strip1 Task Creating...Core: %d", core); xTaskCreatePinnedToCore(Strip1_Control_Task, "LED_Strip1_Task", 1024*10, NULL, 1, &Strip1_Task_Handle, core); } // ********** STRIP2 ******************************** stripJson.clear(); stripJson = doc["strip2"]; if(jsonConstrainBool(tag, stripJson, "en", false)){ int port = jsonConstrain(tag, stripJson, "i2s-ch", 0, 1, 0); int size = jsonConstrain(tag, stripJson, "size", 1, 200, 10); //int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED2_Pin); LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() ); int shift = jsonConstrain(tag, stripJson, "shift", -size, size, 0); int offset = jsonConstrain(tag, stripJson, "offset", -size, size, 0); int core2 = jsonConstrain(tag, stripJson, "core", 0, 1, 0); int powerDiv = jsonConstrain(tag, stripJson, "power-div", 0, 3, 0); strip2 = new LEDSTRIP(port, size, boardPins.rgb2, order, shift, offset); strip2->setPowerDiv(powerDiv); ESP_LOGI(tag,"Strip2 initialized."); ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset); ESP_LOGD(tag,"Strip2 Task Creating...Core: %d", core2); xTaskCreatePinnedToCore(Strip2_Control_Task, "LED_Strip2_Task", 1024*8, NULL, 1, &Strip2_Task_Handle, core2); }else{ ESP_LOGE(tag,"Strip2 disabled"); } JsonObject flightJson = doc["front-light"]; animProps.frontLight.enabled = jsonConstrainBool(tag, flightJson, "en", false); if(animProps.frontLight.enabled){ animProps.frontLight.relayIndex = jsonConstrain(tag, flightJson, "relay", 0, 3, 0); animProps.frontLight.core = jsonConstrain(tag, flightJson, "core", 0, 1, 1); animProps.frontLight.style = (FRONT_LIGHT_STYLE)(flightJson, "style", 0, 1, 0); ESP_LOGI(tag, "Front Light initialized.."); Init_FrontLight(); } JsonObject rlightJson = doc["rear-light"]; animProps.rearLight.enabled = jsonConstrainBool(tag, rlightJson, "en", false); if(animProps.rearLight.enabled){ animProps.rearLight.relayIndex = jsonConstrain(tag, rlightJson, "relay", 0, 3, 1); animProps.rearLight.buttonIndex = jsonConstrain(tag, rlightJson, "button", 0, 2, 2); animProps.rearLight.rampTime = jsonConstrain(tag, rlightJson, "ramp", 1000, 20000, 3000); animProps.rearLight.rampSteps = jsonConstrain(tag, rlightJson, "steps", 2, 20, 8); animProps.rearLight.min = jsonConstrain(tag, rlightJson, "min", 0.0f, 50.0f, 0.0f); animProps.rearLight.max = jsonConstrain(tag, rlightJson, "max", animProps.rearLight.min, 100.0f, 100.0f); ESP_LOGD(tag, "relay: %d, btn: %d, ramp: %d, steps: %d, min: %.2f, max: %.2f", animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps, animProps.rearLight.min, animProps.rearLight.max); Initialize_Rear_Control(animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps , animProps.rearLight.min, animProps.rearLight.max); ESP_LOGI(tag, "Rear Light initialized.."); } } } void Init_FrontLight(void) { xTaskCreatePinnedToCore(FrontLight_Task, "FrontLight_Task", 5000, NULL, 1, &FrontLight_Task_Handle, animProps.frontLight.core); } void Init_RearLight(void) { //if(jsonOb.isNull()){ Serial.println(" rear_light json is Null.."); return; } } void Test_Animations(void){ ANIMATION_EVENT event; strip1->fill({0,0,0}, 0, strip1->effSize); strip1->show(true); vTaskDelay(100); event.hue = RGBtoHUE(col_blue); event.hueRange = 180; event.param1 = 11; event.speed = 70; LEDStrip_FireInit(*strip1); while(1){ event.hue = 0; event.hueRange = 359; event.param1 = 22; event.speed = 70; //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); event.param1 = 30; //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); event.param1 = 40; //Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2); event.param1 = 50; Animation_Sectors(*strip1, event, DT_BOTH, 2); event.param1 = 50; Animation_Dashes(*strip1, event, DT_BOTH, 3); event.param1 = 50; Animation_Dashes(*strip1, event, DT_BOTH, 3); /* Animation_Fire(*strip1, event, RED_FIRE, 5000); Animation_Fire(*strip1, event, GREEN_FIRE, 5000); Animation_Fire(*strip1, event, BLUE_FIRE, 5000); event.col1 = col_blue; event.col2 = {255,136,0}; event.density = 11; event.speed = 100; Animation_Snakes(*strip1, event, false, DT_FWD, 2, 1); event.col1 = HUEtoRGB(359); event.col2 = HUEtoRGB(0); event.density = 22; event.speed = 70; Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4); event.col1 = HUEtoRGB(359); event.col2 = HUEtoRGB(0); event.density = 56; event.speed = 50; Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4); */ } } void Strip1_Control_Task(void *parameters) { vTaskDelay(1000); // small start delay ESP_LOGD(tag, "Strip1 Task Entered...."); //Test_Animations(); LEDStrip_FireInit(*strip1); // must be initialized before calling fire animation // Set default event 1 animation base on profile values ANIMATION_EVENT ev; // empty event ev.animIndex = 0; ev.countDown = 6000; ev.param2 = 75; ev.type = EV_NON_INJECT; PostNewEvent(ev); // Start Animation (White Fill) animProps.event[1].type = EV_NORMAL; PostNewEvent(animProps.event[1]); // default attraction animation while(true){ xQueueReceive( eventQueue, &eventMsg, portMAX_DELAY ); ESP_LOGD(tag, "Queue waiting: %d", uxQueueMessagesWaiting(eventQueue)); if(eventMsg.type == EV_NORMAL ) { // EV_NORMAL ESP_LOGD(tag, "Running event: type: EV_NORMAL, event: %d, anim: %d", eventMsg.selfIndex, eventMsg.animIndex); lastNormalEventMsg = eventMsg; //save last Normal EventMsg if( eventMsg.selfIndex >= 0 ) { CurrentRunningEventIndex = eventMsg.selfIndex; } if(Luma_PeerCount() > 0){ TempLumaPacket.type = LPT_ANIM; memcpy(&TempLumaPacket.data, &eventMsg, sizeof(ANIMATION_EVENT)); Luma_Master_Broadcast(TempLumaPacket); } if( eventMsg.selfIndex == 0 ){ RunWhitefillAnimation( eventMsg ); }else{ RunAnimation( eventMsg ); } } else if( eventMsg.type == EV_INJECT ){ // insert and return to previous animation ESP_LOGD(tag, "EV_INJECT"); xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event RunPopUpAnimations( eventMsg ); } else if( eventMsg.type == EV_NON_INJECT ){ ESP_LOGD(tag, "EV_NON_INJECT"); RunPopUpAnimations( eventMsg ); } else if( eventMsg.type == EV_TEST ){ ESP_LOGD(tag, "EV_TEST"); animStatus.EventTestCountdown = EVENT_TESTMODE_TIMEOUT; // start timeout countdown // Should not re-post last event from here in case repeated tests are started. // that would cause a backlog of the same previous event. if( eventMsg.selfIndex == 0 ){ RunWhitefillAnimation( eventMsg); }else{ RunAnimation( eventMsg ); } } } } void PostLastNormalEvent(){ xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event ESP_LOGD(tag, "RePosted Last Normal Event Index: %d", lastNormalEventMsg.selfIndex); } // Used to cycle to the next event primarity when usinging buttons int IncrementEventIndex(){ int x = (CurrentRunningEventIndex + 1) % animStatus.EventsCount; ESP_LOGD(tag, "Increment Event Index from %d to %d", CurrentRunningEventIndex, x); animProps.event[x].type = EV_NORMAL; PostNewEvent(animProps.event[x]); return animStatus.EventsIndex; } //EVENT_MSG newEvent; void PostNewEvent( ANIMATION_EVENT &animEvent) { //newEvent.type = type; //newEvent.event = animEvent; xQueueSend(eventQueue, &animEvent, 100); // queue new event // if(type == EV_NORMAL){ // If whitefill index ( could be restarted ) // if(animEvent.animIndex == ANIM_WHITEFILL_INDEX){ //if( countDown == 0 ) { animStatus.countDown = 1000; } // TODO Countdown calc maybe wrong...double check //if( animStatus.busy ) { animStatus.repeat = true; } // if( FrontLight_Task_Handle ) { xTaskNotifyGive( FrontLight_Task_Handle ); } // } // } if( Strip1_Task_Handle ){ xTaskNotifyGive( Strip1_Task_Handle ); } } void Strip2_Control_Task(void *parameters) { vTaskDelay(100); ESP_LOGD(tag, "Strip2 Task Entered....\n"); for(;;){ vTaskDelay(10000); } } void FrontLight_Task(void *parameters){ animStatus.busy = false; ANIMATION_EVENT whiteMsg; for(;;){ xQueueReceive( whiteQueue, &whiteMsg, portMAX_DELAY ); int msFillTime = 0; if(whiteMsg.countDown > 0){ // use countdown if available msFillTime = whiteMsg.countDown; }else{ msFillTime = map(whiteMsg.param1, 0, 100, 0, 10000); if(msFillTime < 1000){ msFillTime = 1000; } } float delayFactor = (float)map(whiteMsg.param2, 0, 100, 0, 100) / 100.0; Const_White_Fill(whiteStatus, animProps.frontLight, delayFactor, msFillTime, 50); } } void load_animation_profile(void){ File file = LittleFS.open("/cfg/anim-profile-common.json"); if(!file){ ESP_LOGE(tag, "Error opening anim-profile-common.json..."); file.close(); }else{ JsonDocument doc; DeserializationError error = deserializeJson(doc, file); file.close(); if(error){ ESP_LOGE(tag, "anim-profile-common.json deserialize error!.."); return;} JsonObject frontJson = doc["countdown"]; animProps.frontLight.minDuty = jsonConstrain(tag, frontJson, "min", 0.0f, 99.0f, 20.0f); animProps.frontLight.maxDuty = jsonConstrain(tag, frontJson, "max", 1.0f, 100.0f, 99.0f); animProps.frontLight.holdTime = jsonConstrain(tag, frontJson, "hold", 100, 5000, 1000); animProps.frontLight.rampTime = jsonConstrain(tag, frontJson, "ramp", 100, 5000, 500); animProps.profileIndex = jsonConstrain(tag, doc.as(), "profile-index", 0, 7, 0); ESP_LOGI(tag, "anim-profile-common.json settings loaded."); } } void load_animation_profileByIndex(int index){ //for(int index = 1; index <= 8; index++){ String strFileName = "/cfg/anim-profile" + String(index + 1) + ".json"; File file = LittleFS.open(strFileName); if(!file){ ESP_LOGE(tag, "%s", "Error opening " + strFileName); file.close(); }else{ JsonDocument doc; DeserializationError error = deserializeJson(doc, file); file.close(); if(error){ ESP_LOGE(tag, "%s deserialize error!..", strFileName.c_str()); return;} JsonObject profJson = doc.as(); animProps.profileName = jsonConstrainString(tag, profJson, "name", "").c_str(); ESP_LOGD(tag, "Profile Index: %d, Name: %s", index, animProps.profileName); // JsonArray eventsJson = profJson["events"]; for(int i = 0; i < animStatus.EventsCount; i++){ animProps.event[i].selfIndex = i; // Self Index if(i == 0){ animProps.event[i].animIndex = jsonConstrain(tag, eventsJson[i], "anim", 0, animProps.whitefillsCount-1, 0); }else{ animProps.event[i].animIndex = jsonConstrain(tag, eventsJson[i], "anim", 0, animProps.animationsCount-1, 0); } animProps.event[i].hue = jsonConstrain(tag, eventsJson[i], "hue", -2, 360, 0); animProps.event[i].hueRange = jsonConstrain(tag, eventsJson[i], "hue-range", 0, 360, 1); animProps.event[i].speed = jsonConstrain(tag, eventsJson[i], "speed", 0, 100, 50); animProps.event[i].param1 = jsonConstrain(tag, eventsJson[i], "param1", 0, 100, 50); animProps.event[i].param2 = jsonConstrain(tag, eventsJson[i], "param2", 0, 100, 50); animProps.event[i].check1 = jsonConstrainBool(tag, eventsJson[i], "check1", false); animProps.event[i].check2 = jsonConstrainBool(tag, eventsJson[i], "check2", false); animProps.event[i].check3 = jsonConstrainBool(tag, eventsJson[i], "check3", false); animProps.event[i].check4 = jsonConstrainBool(tag, eventsJson[i], "check4", false); ESP_LOGD(tag, " self: %d, anim: %d, param1: %d, speed: %d", animProps.event[i].selfIndex, animProps.event[i].animIndex, animProps.event[i].param1, animProps.event[i].speed); } ESP_LOGI(tag, "%s settings loaded.", strFileName.c_str()); } //} } void RunWhitefillAnimation(ANIMATION_EVENT &event){ //Start Constant Light Task if available if( FrontLight_Task_Handle ){ xQueueSend(whiteQueue, &event, 100); // queue new event xTaskNotifyGive( FrontLight_Task_Handle ); } int ret; switch(event.animIndex){ case 0: // Bottom/Up Fill ESP_LOGD(tag, " Running WhiteFill: %d , bottom/up", event.animIndex); ret = Animation_White_Fill_Mirrored(*strip1, event); break; case 1: // Snake Fill ESP_LOGD(tag, " Running WhiteFill: %d , Snake fill", event.animIndex); ret = Animation_White_Fill_Mirrored(*strip1, event); break; case 2: // Tick Fill ESP_LOGD(tag, " Running WhiteFill: %d , Tick fill", event.animIndex); ret = Animation_White_Fill_Mirrored(*strip1, event); break; case 3: // Smooth Brighten ESP_LOGD(tag, " Running WhiteFill: %d , Smooth Brighten", event.animIndex); ret = Animation_White_Fill_Mirrored(*strip1, event); break; case 4: // Cycle All ESP_LOGD(tag, " Running WhiteFill: %d , Cycle All", event.animIndex); ret = Animation_White_Fill_Mirrored(*strip1, event); break; default: // Random Select ESP_LOGD(tag, " Running WhiteFill: default , bottom/up"); ret = Animation_White_Fill_Mirrored(*strip1, event); break; } // if exited loop naturally... not from a notification so keep waiting if(ret == EXIT_FINISHED || ret == EXIT_TIMEOUT){ ESP_LOGD(tag, "Whitefill normal exit... waiting"); ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); } } void RunAnimation(ANIMATION_EVENT &event){ if(event.animIndex < 80){ switch(event.animIndex){ // AnimationEventIndex (0-X) case 0: // Rainbow ESP_LOGD(tag, " Running Anim: %d , Rainbow", event.animIndex); Animation_Rainbow(*strip1, event, INFINITE_LOOP); break; case 1: // Hue Spectrum Mirrored ESP_LOGD(tag, " Running Anim: %d , Hue Spectrum Mirrored", event.animIndex); Animation_Hue_Spectrum_Mirrored(*strip1, event, INFINITE_LOOP); break; case 2: // Color Pulse Cycle ESP_LOGD(tag, " Running Anim: %d , Color Pulse Cycle", event.animIndex); Animation_Pulse_Color_Cycling(*strip1, event); break; case 3: // Comets Mixed Colors ESP_LOGD(tag, " Running Anim: %d , Comets", event.animIndex); Animation_Comets(*strip1, event, DT_BOTH, INFINITE_LOOP); break; case 4: // Dashes Single and Mixed Colors ESP_LOGD(tag, " Running Anim: %d , Dashes", event.animIndex); Animation_Dashes(*strip1, event, DT_BOTH, INFINITE_LOOP); break; case 5: // Snakes Single and Mixed Colors ESP_LOGD(tag, " Running Anim: %d , Snakes", event.animIndex); Animation_Snakes(*strip1, event, DT_BOTH, INFINITE_LOOP); break; case 6: // Sectors Mixed Colors (No Sigle Colors because pointless) ESP_LOGD(tag, " Running Anim: %d , Sectors", event.animIndex); Animation_Sectors(*strip1, event, DT_BOTH, INFINITE_LOOP); break; case 7: // Fire (Red) ESP_LOGD(tag, " Running Anim: %d , Fire", event.animIndex); Animation_Fire(*strip1, event); break; case 8: // break; case 9: // Color Strobing break; case 10: // Random Color Twinkle break; case 11: // Stacking break; case 12: // Rain break; case 13: // Stacking break; case 14: // RED/WHITE/BLU Flag (density selects color pallet) (sector/comets,dashes, snakes) break; case 15: break; default: break; } }else{ // Non Looping Functions switch(event.animIndex){ // AnimationEventIndex (0-X) case 80: // clear strip strip1->fill(col_black, 0, strip1->effSize); strip1->show(true); ESP_LOGD(tag, "Animation Strip1 Clear"); ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); break; case 81: // set pixel strip1->setPixel(event.param1, GetColorFromHue(event.hue)); strip1->show(true); ESP_LOGD(tag, "Animation Set Pixel: %d", event.param1); ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); break; case 82: // break; default: break; } } } // These animations should be quick and not infinite void RunPopUpAnimations(ANIMATION_EVENT &event){ switch(event.animIndex){ // AnimationEventIndex (0-X) case 0: // Start/Boot/Power Up Sequence ANIMATION_EVENT ev; ev.animIndex = 0; ev.hue = -1; //{200, 200, 200}; //col_white; ev.hueRange = 1; //col_black; ev.countDown = 6000; // 6 seconds ev.param2 = 75; if( FrontLight_Task_Handle ){ xQueueSend(whiteQueue, &ev, 100); // queue new event xTaskNotifyGive( FrontLight_Task_Handle ); } Animation_White_Fill_Mirrored(*strip1, ev); break; case 1: // BLE Connected break; case 2: // BLE Disconnected break; case 3: // WiFi Connected break; case 4: // WiFi Disconnected break; case 5: // Luma Stick Connected break; default: break; } } // json color to rgbPixel conversion // Unused now rgbpixel_t hexToRGB(const char* hexColor) { long hexValue = strtol(hexColor + 1, nullptr, 16); // Skip the '#' character rgbpixel_t pix; pix.red = (hexValue >> 16) & 0xFF; pix.grn = (hexValue >> 8) & 0xFF; pix.blu = hexValue & 0xFF; return pix; }