Files
OpenRGB/Controllers/NanoleafController/NanoleafController.cpp

365 lines
13 KiB
C++

/*---------------------------------------------------------*\
| NanoleafController.cpp |
| |
| Driver for Nanoleaf |
| |
| Nikita Rushmanov 13 Jan 2022 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "NanoleafController.h"
#include "LogManager.h"
#include "httplib.h"
long APIRequest(std::string method, std::string location, std::string URI, json* request_data = nullptr, json* response_data = nullptr)
{
/*-------------------------------------------------------------*\
| Append http:// to the location field to create the URL |
\*-------------------------------------------------------------*/
const std::string url("http://" + location);
/*-------------------------------------------------------------*\
| Create httplib Client and variables to hold result |
\*-------------------------------------------------------------*/
httplib::Client client(url.c_str());
int status = 0;
std::string body = "";
/*-------------------------------------------------------------*\
| Perform the appropriate call for the given method |
\*-------------------------------------------------------------*/
if(method == "GET")
{
httplib::Result result = client.Get(URI.c_str());
if(httplib::Error::Success == result.error())
{
status = result->status;
body = result->body;
}
}
else if(method == "PUT")
{
if(request_data)
{
httplib::Result result = client.Put(URI.c_str(), request_data->dump(), "application/json");
if(httplib::Error::Success == result.error())
{
status = result->status;
body = result->body;
}
}
else
{
httplib::Result result = client.Put(URI.c_str());
if(httplib::Error::Success == result.error())
{
status = result->status;
body = result->body;
}
}
}
else if(method == "DELETE")
{
httplib::Result result = client.Delete(URI.c_str());
if(httplib::Error::Success == result.error())
{
status = result->status;
body = result->body;
}
}
else if(method == "POST")
{
httplib::Result result = client.Post(URI.c_str());
if(httplib::Error::Success == result.error())
{
status = result->status;
body = result->body;
}
}
/*-------------------------------------------------------------*\
| If status is in the 200 range the request was successful |
\*-------------------------------------------------------------*/
if((status / 100) == 2)
{
if(response_data)
{
*response_data = json::parse(body);
}
}
else
{
LOG_DEBUG("[Nanoleaf] HTTP %i:Could not %s from %s", status, method.c_str(), url.c_str());
}
return status;
}
NanoleafController::NanoleafController(std::string a_address, int a_port, std::string a_auth_token)
{
address = a_address;
port = a_port;
auth_token = a_auth_token;
location = address + ":" + std::to_string(port);
json data;
if(APIRequest("GET", location, "/api/v1/"+auth_token, nullptr, &data) == 200)
{
name = data["name"];
serial = data["serialNo"];
manufacturer = data["manufacturer"];
firmware_version = data["firmwareVersion"];
model = data["model"];
brightness = data["state"]["brightness"]["value"];
selectedEffect = data["effects"]["select"];
for(json::const_iterator it = data["effects"]["effectsList"].begin(); it != data["effects"]["effectsList"].end(); ++it)
{
effects.push_back(it.value());
}
for(json::const_iterator it = data["panelLayout"]["layout"]["positionData"].begin(); it != data["panelLayout"]["layout"]["positionData"].end(); ++it)
{
panel_ids.push_back(it.value()["panelId"].get<int>());
}
}
else
{
throw std::exception();
}
}
std::string NanoleafController::Pair(std::string address, int port)
{
const std::string location = address+":"+std::to_string(port);
json data;
if(APIRequest("POST", location, "/api/v1/new", nullptr, &data) == 200)
{
return data["auth_token"];
}
else
{
throw std::exception();
}
}
void NanoleafController::Unpair(std::string address, int port, std::string auth_token)
{
const std::string location = address+":"+std::to_string(port);
/*-------------------------------------------------------------*\
| We really don't care if this fails. |
\*-------------------------------------------------------------*/
APIRequest("DELETE", location, "/api/v1/"+auth_token, nullptr, nullptr);
}
void NanoleafController::UpdateLEDs(std::vector<RGBColor>& colors)
{
/*-------------------------------------------------------------*\
| Requires StartExternalControl() to have been called prior. |
\*-------------------------------------------------------------*/
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) |
\*---------------------------------------------------------*/
std::size_t size = panel_ids.size();
uint8_t* message = (uint8_t*)malloc((size * 7) + 1);
message[0] = (uint8_t)size; /* nPanels */
for(unsigned int i = 0; i < size; i++)
{
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((char*)message, ((int)size * 7) + 1);
free(message);
}
else if((model == NANOLEAF_CANVAS_MODEL)
|| (model == NANOLEAF_SHAPES_MODEL))
{
/*---------------------------------------------------------*\
| 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) |
\*---------------------------------------------------------*/
std::size_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(unsigned 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, ((int)size * 8) + 2);
free(message);
}
}
void NanoleafController::StartExternalControl()
{
json request;
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<std::string>().c_str(), std::to_string(response["streamControlPort"].get<int>()).c_str());
selectedEffect = NANOLEAF_DIRECT_MODE_EFFECT_NAME;
}
}
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";
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;
}
}
}
void NanoleafController::SelectEffect(std::string effect_name)
{
json request;
request["select"] = effect_name;
if((APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request) / 100) == 2)
{
selectedEffect = effect_name;
}
}
void NanoleafController::SetBrightness(int a_brightness)
{
json request;
request["brightness"]["value"] = a_brightness;
if((APIRequest("PUT", location, "/api/v1/"+auth_token+"/state", &request) / 100) == 2)
{
brightness = a_brightness;
}
}
std::string NanoleafController::GetAuthToken()
{
return auth_token;
};
std::string NanoleafController::GetName()
{
return name;
};
std::string NanoleafController::GetSerial()
{
return serial;
};
std::string NanoleafController::GetManufacturer()
{
return manufacturer;
};
std::string NanoleafController::GetFirmwareVersion()
{
return firmware_version;
};
std::string NanoleafController::GetModel()
{
return model;
};
std::vector<std::string>& NanoleafController::GetEffects()
{
return effects;
};
std::vector<int>& NanoleafController::GetPanelIds()
{
return panel_ids;
};
std::string NanoleafController::GetSelectedEffect()
{
return selectedEffect;
};
int NanoleafController::GetBrightness()
{
return brightness;
};