From e61b6cd3d70e150a3e7d15e23c4cad4fd1655e5a Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Tue, 3 May 2022 00:17:22 -0500 Subject: [PATCH] Implement direct control for v2 protocol (Canvas and Shapes), clean up some formatting with v1 to match. --- .../NanoleafController/NanoleafController.cpp | 106 ++++++++++++++---- .../NanoleafController/NanoleafController.h | 1 + .../RGBController_Nanoleaf.cpp | 46 ++++---- 3 files changed, 107 insertions(+), 46 deletions(-) diff --git a/Controllers/NanoleafController/NanoleafController.cpp b/Controllers/NanoleafController/NanoleafController.cpp index b14bd755..cbcec22d 100644 --- a/Controllers/NanoleafController/NanoleafController.cpp +++ b/Controllers/NanoleafController/NanoleafController.cpp @@ -159,30 +159,77 @@ void NanoleafController::UpdateLEDs(std::vector& colors) if(model == NANOLEAF_LIGHT_PANELS_MODEL) { + /*---------------------------------------------------------*\ + | Protocol V1 - https://forum.nanoleaf.me/docs | + | | + | Size Description | + | --------------------------------------------------------- | + | 1 nPanels Number of panels | + | | + | 1 panelId ID of panel | + | 1 nFrames Number of frames (always 1) | + | 1 R Red channel | + | 1 G Green channel | + | 1 B Blue channel | + | 1 W White channel (ignored) | + | 1 transitionTime Transition time (x 100ms) | + \*---------------------------------------------------------*/ uint8_t size = panel_ids.size(); - uint8_t* message = (uint8_t*)malloc(size*7+6+1); + uint8_t* message = (uint8_t*)malloc((size * 7) + 1); - message[0] = (uint8_t)size; + message[0] = (uint8_t)size; /* nPanels */ for(int i = 0; i < size; i++) { - message[( 7 * i) + 0 + 1] = (uint8_t)panel_ids[i]; - message[( 7 * i) + 1 + 1] = (uint8_t)1; - message[( 7 * i) + 2 + 1] = (uint8_t)RGBGetRValue(colors[i]); - message[( 7 * i) + 3 + 1] = (uint8_t)RGBGetGValue(colors[i]); - message[( 7 * i) + 4 + 1] = (uint8_t)RGBGetBValue(colors[i]); - message[( 7 * i) + 5 + 1] = (uint8_t)0; - message[( 7 * i) + 6 + 1] = (uint8_t)0; + message[(7 * i) + 0 + 1] = (uint8_t)panel_ids[i]; /* panelId */ + message[(7 * i) + 1 + 1] = (uint8_t)1; /* nFrames */ + message[(7 * i) + 2 + 1] = (uint8_t)RGBGetRValue(colors[i]); /* R */ + message[(7 * i) + 3 + 1] = (uint8_t)RGBGetGValue(colors[i]); /* G */ + message[(7 * i) + 4 + 1] = (uint8_t)RGBGetBValue(colors[i]); /* B */ + message[(7 * i) + 5 + 1] = (uint8_t)0; /* W */ + message[(7 * i) + 6 + 1] = (uint8_t)0; /* transitionTime */ } - external_control_socket.udp_write(reinterpret_cast(message), size*7+6+1); + external_control_socket.udp_write((char*)message, (size * 7) + 1); } - else if(model == NANOLEAF_CANVAS_MODEL) + else if((model == NANOLEAF_CANVAS_MODEL) + || (model == NANOLEAF_SHAPES_MODEL)) { /*---------------------------------------------------------*\ - | Insert V2 protocol implementation here. | + | Protocol V2 - https://forum.nanoleaf.me/docs | + | | + | Size Description | + | --------------------------------------------------------- | + | 2 nPanels Number of panels | + | | + | 2 panelId ID of panel | + | 1 R Red channel | + | 1 G Green channel | + | 1 B Blue channel | + | 1 W White channel (ignored) | + | 2 transitionTime Transition time (x 100ms) | \*---------------------------------------------------------*/ + uint8_t size = panel_ids.size(); + + uint8_t* message = (uint8_t*)malloc((size * 8) + 2); + + message[0] = (uint8_t)(size >> 8); /* nPanels H */ + message[1] = (uint8_t)(size & 0xFF); /* nPanels L */ + + for(int i = 0; i < size; i++) + { + message[(8 * i) + 0 + 2] = (uint8_t)(panel_ids[i] >> 8); /* panelId H */ + message[(8 * i) + 1 + 2] = (uint8_t)(panel_ids[i] & 0xFF); /* panelId L */ + message[(8 * i) + 2 + 2] = (uint8_t)RGBGetRValue(colors[i]); /* R */ + message[(8 * i) + 3 + 2] = (uint8_t)RGBGetGValue(colors[i]); /* G */ + message[(8 * i) + 4 + 2] = (uint8_t)RGBGetBValue(colors[i]); /* B */ + message[(8 * i) + 5 + 2] = (uint8_t)0; /* W */ + message[(8 * i) + 6 + 2] = (uint8_t)0; /* transitionTime H */ + message[(8 * i) + 7 + 2] = (uint8_t)0; /* transitionTime L */ + } + + external_control_socket.udp_write((char *)message, (size * 8) + 2); } } @@ -192,21 +239,40 @@ void NanoleafController::StartExternalControl() request["write"]["command"] = "display"; request["write"]["animType"] = "extControl"; + /*-------------------------------------------------------------*\ + | Determine whether to use v1 or v2 extControl protocol based | + | on model string | + \*-------------------------------------------------------------*/ if(model == NANOLEAF_LIGHT_PANELS_MODEL) { + /*---------------------------------------------------------*\ + | Protocol v1 returns IP and port for UDP communication | + \*---------------------------------------------------------*/ request["write"]["extControlVersion"] = "v1"; + + json response; + if((APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request, &response) / 100) == 2) + { + external_control_socket.udp_client(response["streamControlIpAddr"].get().c_str(), std::to_string(response["streamControlPort"].get()).c_str()); + + selectedEffect = NANOLEAF_DIRECT_MODE_EFFECT_NAME; + } } - else if(model == NANOLEAF_CANVAS_MODEL) + else if((model == NANOLEAF_CANVAS_MODEL) + || (model == NANOLEAF_SHAPES_MODEL)) { + /*---------------------------------------------------------*\ + | Protocol v2 does not return anything, use device IP and | + | port 60222 | + \*---------------------------------------------------------*/ request["write"]["extControlVersion"] = "v2"; - } - json response; - if((APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request, &response) / 100) == 2) - { - external_control_socket.udp_client(response["streamControlIpAddr"].get().c_str(), std::to_string(response["streamControlPort"].get()).c_str()); + if((APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request) / 100) == 2) + { + external_control_socket.udp_client(address.c_str(), "60222"); - selectedEffect = NANOLEAF_DIRECT_MODE_EFFECT_NAME; + selectedEffect = NANOLEAF_DIRECT_MODE_EFFECT_NAME; + } } } @@ -280,4 +346,4 @@ std::string NanoleafController::GetSelectedEffect() int NanoleafController::GetBrightness() { return brightness; -}; \ No newline at end of file +}; diff --git a/Controllers/NanoleafController/NanoleafController.h b/Controllers/NanoleafController/NanoleafController.h index 35f6372c..6e8fd91a 100644 --- a/Controllers/NanoleafController/NanoleafController.h +++ b/Controllers/NanoleafController/NanoleafController.h @@ -14,6 +14,7 @@ #define NANOLEAF_DIRECT_MODE_EFFECT_NAME "*Dynamic*" #define NANOLEAF_LIGHT_PANELS_MODEL "NL22" #define NANOLEAF_CANVAS_MODEL "NL29" +#define NANOLEAF_SHAPES_MODEL "NL42" class NanoleafController { diff --git a/Controllers/NanoleafController/RGBController_Nanoleaf.cpp b/Controllers/NanoleafController/RGBController_Nanoleaf.cpp index 1e16ce03..3bf4245b 100644 --- a/Controllers/NanoleafController/RGBController_Nanoleaf.cpp +++ b/Controllers/NanoleafController/RGBController_Nanoleaf.cpp @@ -25,28 +25,25 @@ RGBController_Nanoleaf::RGBController_Nanoleaf(std::string a_address, int a_port type = DEVICE_TYPE_LIGHT; /*-------------------------------------------------------------*\ - | Direct mode currently only supported for Nanoleaf Panels. | + | Direct mode uses external control protocol. | \*-------------------------------------------------------------*/ - if(controller.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL) - { - mode Direct; - Direct.name = "Direct"; - Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; - Direct.color_mode = MODE_COLORS_PER_LED; - modes.push_back(Direct); + mode Direct; + Direct.name = "Direct"; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); - /*---------------------------------------------------------*\ - | Set this effect as current if the name is selected. | - \*---------------------------------------------------------*/ - if(controller.GetSelectedEffect() == NANOLEAF_DIRECT_MODE_EFFECT_NAME) - { - /*-----------------------------------------------------*\ - | If the direct mode is active, we need to call this | - | method to open the socket. | - \*-----------------------------------------------------*/ - controller.StartExternalControl(); - active_mode = 0; - } + /*---------------------------------------------------------*\ + | Set this effect as current if the name is selected. | + \*---------------------------------------------------------*/ + if(controller.GetSelectedEffect() == NANOLEAF_DIRECT_MODE_EFFECT_NAME) + { + /*-----------------------------------------------------*\ + | If the direct mode is active, we need to call this | + | method to open the socket. | + \*-----------------------------------------------------*/ + controller.StartExternalControl(); + active_mode = 0; } /*-------------------------------------------------------------*\ @@ -89,7 +86,7 @@ void RGBController_Nanoleaf::SetupZones() for(std::vector::const_iterator it = controller.GetPanelIds().begin(); it != controller.GetPanelIds().end(); ++it) { led new_led; - new_led.name = std::to_string(*it); + new_led.name = std::to_string(*it); leds.push_back(new_led); } @@ -107,10 +104,7 @@ void RGBController_Nanoleaf::ResizeZone(int /*zone*/, int /*new_size*/) void RGBController_Nanoleaf::DeviceUpdateLEDs() { - if(controller.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL) - { - controller.UpdateLEDs(colors); - } + controller.UpdateLEDs(colors); } void RGBController_Nanoleaf::UpdateZoneLEDs(int /*zone*/) @@ -133,7 +127,7 @@ void RGBController_Nanoleaf::DeviceUpdateMode() /*---------------------------------------------------------*\ | Mode 0 is reserved for Direct mode | \*---------------------------------------------------------*/ - if(active_mode == 0 && controller.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL) + if(active_mode == 0) { controller.StartExternalControl(); }