New LED Device - Cololight (#1070)

This commit is contained in:
LordGrey 2020-11-01 21:56:19 +01:00 committed by GitHub
parent bb652ade36
commit 83455441fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1476 additions and 164 deletions

View File

@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking
### Added
- Cololight support (Cololight Plus & Strip) incl. configuration wizard
- Provide additional details on Hardware/CPU information
- Allow execution with option "--version", while another hyperion daemon is running
- New language support: Russian and Chinese (simplified) (#1005)
- added libcec to deb/rpm dependency list
- updated some language files
@ -21,8 +24,7 @@ Allow execution with option "--version", while another hyperion daemon is runnin
- DirectX9 Grabber (#1039)
- Added DirectX SDK to CompileHowto
- Hide Systray on exit & Install DirectX Redistributable
- Read-Only configuration database support
- Read-Only configuration database suppor
### Changed
- boblight: reduce cpu time spent on memcopy and parsing rgb values (#1016)
@ -33,6 +35,8 @@ Allow execution with option "--version", while another hyperion daemon is runnin
- Update LICENSE
- Change links from http to https (#1067)
- UI: Separate LED-Layout creation from UI code
### Fixed
- Properly save Hue light state between sessions (#1014)
- AVAHI included in Webserver (#996)

View File

@ -855,7 +855,6 @@
"update_versreminder": "Your version: $1",
"wiz_atmoorb_desc2": "Now choose which Orbs should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.",
"wiz_atmoorb_intro1": "This wizards configures Hyperion for AtmoOrbs. Features are the AtmoOrb auto detection, setting each light to a specific position on your picture or disable it and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_atmoorb_noLights": "No AtmoOrbs found! Please get the lights connected to the network or configure them manually.",
"wiz_atmoorb_title": "AtmoOrb Wizard",
"wiz_cc_adjustgamma": "Gamma: What you have to do is, adjust gamma levels of each channel until you have the same perceived amount of each channel. Hint: Neutral is 1.0! For example, if your Grey is a bit reddish it means that you have to increase red gamma to reduce the amount of red (the more gamma, the less amount of color).",
"wiz_cc_adjustit": "Adjust your \"$1\", until your are fine with it. Take notice: The more you adjust away from the default value the color spectrum will be limited (Also for all colors in between). Depending on TV/LED color spectrum the results will vary.",
@ -879,6 +878,10 @@
"wiz_cc_testintrok": "Push on a button below to start a test video.",
"wiz_cc_testintrowok": "Check out the following link to download test videos:",
"wiz_cc_title": "Colour calibration wizard",
"wiz_cololight_desc2": "Now choose which Cololights should be added. To identify single lights, press the button on the right.",
"wiz_cololight_intro1": "This wizards configures Hyperion for the Cololight system. Features are the Cololight auto detection and tune the Hyperion settings automatically! In short: All you need are some clicks and you are done!<br />Note: In case of Cololight Strip, you might need to manually correct the LED count and layout.",
"wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually",
"wiz_cololight_title": "Cololight Wizard",
"wiz_guideyou": "The $1 will guide you through the settings. Just press the button!",
"wiz_hue_blinkblue": "Let ID $1 light up blue",
"wiz_hue_clientkey": "Clientkey:",
@ -912,6 +915,7 @@
"wiz_identify_light": "Identify $1",
"wiz_ids_disabled": "Deactivated",
"wiz_ids_entire": "Whole picture",
"wiz_noLights": "No $1 found! Please get the lights connected to the network or configure them manually.",
"wiz_pos": "Position/State",
"wiz_rgb_expl": "The color dot switches every x seconds the color (red, green), at the same time your LEDs switch the color too. Answer the questions at the bottom to check/correct your byte order.",
"wiz_rgb_intro1": "This wizard will guide you through the finding process of the correct color order for your leds. Click on continue to begin.",
@ -924,7 +928,6 @@
"wiz_wizavail": "Wizard available",
"wiz_yeelight_desc2": "Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.",
"wiz_yeelight_intro1": "This wizards configures Hyperion for the Yeelight system. Features are the Yeelighs' auto detection, setting each light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_yeelight_noLights": "No Yeelights found! Please get the lights connected to the network or configure them manually.",
"wiz_yeelight_title": "Yeelight Wizard",
"wiz_yeelight_unsupported": "Unsupported"
}

View File

@ -59,40 +59,36 @@ function createLedPreview(leds, origin){
}
function createClassicLeds(){
//get values
var ledstop = parseInt($("#ip_cl_top").val());
var ledsbottom = parseInt($("#ip_cl_bottom").val());
var ledsleft = parseInt($("#ip_cl_left").val());
var ledsright = parseInt($("#ip_cl_right").val());
var ledsglength = parseInt($("#ip_cl_glength").val());
var ledsgpos = parseInt($("#ip_cl_gpos").val());
var position = parseInt($("#ip_cl_position").val());
var reverse = $("#ip_cl_reverse").is(":checked");
function createClassicLedLayoutSimple( ledstop,ledsleft,ledsright,ledsbottom,position,reverse ){
//advanced values
var ledsVDepth = parseInt($("#ip_cl_vdepth").val())/100;
var ledsHDepth = parseInt($("#ip_cl_hdepth").val())/100;
var edgeVGap = parseInt($("#ip_cl_edgegap").val())/100/2;
//var cornerVGap = parseInt($("#ip_cl_cornergap").val())/100/2;
var overlap = $("#ip_cl_overlap").val()/100;
let params = {
ledstop: 0, ledsleft: 0, ledsright: 0, ledsbottom: 0,
ledsglength: 0, ledsgpos: 0, position: 0,
ledsHDepth: 0.08, ledsVDepth: 0.05, overlap: 0,
edgeVGap: 0,
ptblh: 0, ptblv: 1, ptbrh: 1, ptbrv: 1,
pttlh: 0, pttlv: 0, pttrh: 1, pttrv: 0,
reverse:false
};
params.ledstop = ledstop;
params.ledsleft = ledsleft;
params.ledsright = ledsright;
params.ledsbottom = ledsbottom;
params.position = position;
params.reverse = reverse;
//trapezoid values % -> float
var ptblh = parseInt($("#ip_cl_pblh").val())/100;
var ptblv = parseInt($("#ip_cl_pblv").val())/100;
var ptbrh = parseInt($("#ip_cl_pbrh").val())/100;
var ptbrv = parseInt($("#ip_cl_pbrv").val())/100;
var pttlh = parseInt($("#ip_cl_ptlh").val())/100;
var pttlv = parseInt($("#ip_cl_ptlv").val())/100;
var pttrh = parseInt($("#ip_cl_ptrh").val())/100;
var pttrv = parseInt($("#ip_cl_ptrv").val())/100;
return createClassicLedLayout( params );
}
function createClassicLedLayout( params ){
//helper
var edgeHGap = edgeVGap/(16/9);
var edgeHGap = params.edgeVGap/(16/9);
var ledArray = [];
function createFinalArray(array){
finalLedArray = [];
var finalLedArray = [];
for(var i = 0; i<array.length; i++){
var hmin = array[i].hmin;
var hmax = array[i].hmax;
@ -100,7 +96,7 @@ function createClassicLeds(){
var vmax = array[i].vmax;
finalLedArray[i] = { "hmax": hmax, "hmin": hmin, "vmax": vmax, "vmin": vmin }
}
createLedPreview(finalLedArray, 'classic');
return finalLedArray;
}
function rotateArray(array, times){
@ -131,9 +127,9 @@ function createClassicLeds(){
function ovl(scan,val)
{
if(scan == "+")
return valScan(val += overlap);
return valScan(val += params.overlap);
else
return valScan(val -= overlap);
return valScan(val -= params.overlap);
}
function createLedArray(hmin, hmax, vmin, vmax){
@ -145,53 +141,53 @@ function createClassicLeds(){
}
function createTopLeds(){
var steph = (pttrh - pttlh - (2*edgeHGap))/ledstop;
var stepv = (pttrv - pttlv)/ledstop;
var steph = (params.pttrh - params.pttlh - (2*edgeHGap))/params.ledstop;
var stepv = (params.pttrv - params.pttlv)/params.ledstop;
for (var i = 0; i<ledstop; i++){
var hmin = ovl("-",pttlh+(steph*Number([i]))+edgeHGap);
var hmax = ovl("+",pttlh+(steph*Number([i+1]))+edgeHGap);
var vmin = pttlv+(stepv*Number([i]));
var vmax = vmin + ledsHDepth;
for (var i = 0; i<params.ledstop; i++){
var hmin = ovl("-",params.pttlh+(steph*Number([i]))+edgeHGap);
var hmax = ovl("+",params.pttlh+(steph*Number([i+1]))+edgeHGap);
var vmin = params.pttlv+(stepv*Number([i]));
var vmax = vmin + params.ledsHDepth;
createLedArray(hmin, hmax, vmin, vmax);
}
}
function createRightLeds(){
var steph = (ptbrh - pttrh)/ledsright;
var stepv = (ptbrv - pttrv - (2*edgeVGap))/ledsright;
var steph = (params.ptbrh - params.pttrh)/params.ledsright;
var stepv = (params.ptbrv - params.pttrv - (2*params.edgeVGap))/params.ledsright;
for (var i = 0; i<ledsright; i++){
var hmax = pttrh+(steph*Number([i+1]));
var hmin = hmax-ledsVDepth;
var vmin = ovl("-",pttrv+(stepv*Number([i]))+edgeVGap);
var vmax = ovl("+",pttrv+(stepv*Number([i+1]))+edgeVGap);
for (var i = 0; i<params.ledsright; i++){
var hmax = params.pttrh+(steph*Number([i+1]));
var hmin = hmax-params.ledsVDepth;
var vmin = ovl("-",params.pttrv+(stepv*Number([i]))+params.edgeVGap);
var vmax = ovl("+",params.pttrv+(stepv*Number([i+1]))+params.edgeVGap);
createLedArray(hmin, hmax, vmin, vmax);
}
}
function createBottomLeds(){
var steph = (ptbrh - ptblh - (2*edgeHGap))/ledsbottom;
var stepv = (ptbrv - ptblv)/ledsbottom;
var steph = (params.ptbrh - params.ptblh - (2*edgeHGap))/params.ledsbottom;
var stepv = (params.ptbrv - params.ptblv)/params.ledsbottom;
for (var i = ledsbottom-1; i>-1; i--){
var hmin = ovl("-",ptblh+(steph*Number([i]))+edgeHGap);
var hmax = ovl("+",ptblh+(steph*Number([i+1]))+edgeHGap);
var vmax= ptblv+(stepv*Number([i]));
var vmin = vmax-ledsHDepth;
for (var i = params.ledsbottom-1; i>-1; i--){
var hmin = ovl("-",params.ptblh+(steph*Number([i]))+edgeHGap);
var hmax = ovl("+",params.ptblh+(steph*Number([i+1]))+edgeHGap);
var vmax= params.ptblv+(stepv*Number([i]));
var vmin = vmax-params.ledsHDepth;
createLedArray(hmin, hmax, vmin, vmax);
}
}
function createLeftLeds(){
var steph = (ptblh - pttlh)/ledsleft;
var stepv = (ptblv - pttlv - (2*edgeVGap))/ledsleft;
var steph = (params.ptblh - params.pttlh)/params.ledsleft;
var stepv = (params.ptblv - params.pttlv - (2*params.edgeVGap))/params.ledsleft;
for (var i = ledsleft-1; i>-1; i--){
var hmin = pttlh+(steph*Number([i]));
var hmax = hmin+ledsVDepth;
var vmin = ovl("-",pttlv+(stepv*Number([i]))+edgeVGap);
var vmax = ovl("+",pttlv+(stepv*Number([i+1]))+edgeVGap);
for (var i = params.ledsleft-1; i>-1; i--){
var hmin = params.pttlh+(steph*Number([i]));
var hmax = hmin+params.ledsVDepth;
var vmin = ovl("-",params.pttlv+(stepv*Number([i]))+params.edgeVGap);
var vmax = ovl("+",params.pttlv+(stepv*Number([i+1]))+params.edgeVGap);
createLedArray(hmin, hmax, vmin, vmax);
}
@ -202,47 +198,87 @@ function createClassicLeds(){
createRightLeds();
createBottomLeds();
createLeftLeds();
//check led gap pos
if (ledsgpos+ledsglength > ledArray.length)
if (params.ledsgpos+params.ledsglength > ledArray.length)
{
var mpos = Math.max(0,ledArray.length-ledsglength);
$('#ip_cl_ledsgpos').val(mpos);
var mpos = Math.max(0,ledArray.length-params.ledsglength);
//$('#ip_cl_ledsgpos').val(mpos);
ledsgpos = mpos;
}
//check led gap length
if(ledsglength >= ledArray.length)
if(params.ledsglength >= ledArray.length)
{
$('#ip_cl_ledsglength').val(ledArray.length-1);
ledsglength = ledArray.length-ledsglength-1;
//$('#ip_cl_ledsglength').val(ledArray.length-1);
params.ledsglength = ledArray.length-params.ledsglength-1;
}
if(ledsglength != 0){
ledArray.splice(ledsgpos, ledsglength);
if(params.ledsglength != 0){
ledArray.splice(params.ledsgpos, params.ledsglength);
}
if (position != 0){
rotateArray(ledArray, position);
if (params.position != 0){
rotateArray(ledArray, params.position);
}
if (reverse)
if (params.reverse)
ledArray.reverse();
createFinalArray(ledArray);
return createFinalArray(ledArray);
}
function createMatrixLeds(){
function createClassicLeds(){
//get values
let params = {
ledstop : parseInt($("#ip_cl_top").val()),
ledsbottom : parseInt($("#ip_cl_bottom").val()),
ledsleft : parseInt($("#ip_cl_left").val()),
ledsright : parseInt($("#ip_cl_right").val()),
ledsglength : parseInt($("#ip_cl_glength").val()),
ledsgpos : parseInt($("#ip_cl_gpos").val()),
position : parseInt($("#ip_cl_position").val()),
reverse : $("#ip_cl_reverse").is(":checked"),
//advanced values
ledsVDepth : parseInt($("#ip_cl_vdepth").val())/100,
ledsHDepth : parseInt($("#ip_cl_hdepth").val())/100,
edgeVGap : parseInt($("#ip_cl_edgegap").val())/100/2,
//cornerVGap : parseInt($("#ip_cl_cornergap").val())/100/2,
overlap : $("#ip_cl_overlap").val()/100,
//trapezoid values % -> float
ptblh : parseInt($("#ip_cl_pblh").val())/100,
ptblv : parseInt($("#ip_cl_pblv").val())/100,
ptbrh : parseInt($("#ip_cl_pbrh").val())/100,
ptbrv : parseInt($("#ip_cl_pbrv").val())/100,
pttlh : parseInt($("#ip_cl_ptlh").val())/100,
pttlv : parseInt($("#ip_cl_ptlv").val())/100,
pttrh : parseInt($("#ip_cl_ptrh").val())/100,
pttrv : parseInt($("#ip_cl_ptrv").val())/100,
}
finalLedArray = createClassicLedLayout( params );
//check led gap pos
if (params.ledsgpos+params.ledsglength > finalLedArray.length) {
var mpos = Math.max(0,finalLedArray.length-params.ledsglength);
$('#ip_cl_ledsgpos').val(mpos);
}
//check led gap length
if(params.ledsglength >= finalLedArray.length) {
$('#ip_cl_ledsglength').val(finalLedArray.length-1);
}
createLedPreview(finalLedArray, 'classic');
}
function createMatrixLayout( ledshoriz, ledsvert, cabling, start){
// Big thank you to RanzQ (Juha Rantanen) from Github for this script
// https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js
//get values
var ledshoriz = parseInt($("#ip_ma_ledshoriz").val());
var ledsvert = parseInt($("#ip_ma_ledsvert").val());
var cabling = $("#ip_ma_cabling").val();
//var order = $("#ip_ma_order").val();
var start = $("#ip_ma_start").val();
var parallel = false
var leds = []
var hblock = 1.0 / ledshoriz
@ -298,9 +334,23 @@ function createMatrixLeds(){
endX = tmp
}
}
finalLedArray =[];
finalLedArray = leds
createLedPreview(leds, 'matrix');
return leds;
}
function createMatrixLeds(){
// Big thank you to RanzQ (Juha Rantanen) from Github for this script
// https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js
//get values
var ledshoriz = parseInt($("#ip_ma_ledshoriz").val());
var ledsvert = parseInt($("#ip_ma_ledsvert").val());
var cabling = $("#ip_ma_cabling").val();
var start = $("#ip_ma_start").val();
finalLedArray = createMatrixLayout(ledshoriz,ledsvert,cabling,start);
createLedPreview(finalLedArray, 'matrix');
}
function migrateLedConfig(slConfig){
@ -560,6 +610,12 @@ $(document).ready(function() {
var atmoorb_title = 'wiz_atmoorb_title';
changeWizard(data, atmoorb_title, startWizardAtmoOrb);
}
else if(ledType == "cololight") {
var ledWizardType = (this.checked) ? "cololight" : ledType;
var data = { type: ledWizardType };
var cololight_title = 'wiz_cololight_title';
changeWizard(data, cololight_title, startWizardCololight);
}
else if(ledType == "yeelight") {
var ledWizardType = (this.checked) ? "yeelight" : ledType;
var data = { type: ledWizardType };
@ -582,10 +638,11 @@ $(document).ready(function() {
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster'];
var devNET = ['atmoorb', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight'];
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight'];
var devUSB = ['adalight', 'dmx', 'atmo', 'hyperionusbasp', 'lightpack', 'paintpack', 'rawhid', 'sedu', 'tpm2', 'karate'];
var optArr = [[]];
var optArr = [[]];
optArr[1]=[];
optArr[2]=[];
optArr[3]=[];

View File

@ -152,9 +152,11 @@ function beginWizardRGB() {
$('#btn_wizard_byteorder').off().on('click', startWizardRGB);
//color calibration wizard
var kodiHost = document.location.hostname;
var kodiPort = 9090;
var kodiAddress = kodiHost;
var wiz_editor;
var colorLength;
var cobj;
@ -168,6 +170,7 @@ var picnr = 0;
var availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"];
if (getStorage("kodiAddress") != null) {
kodiAddress = getStorage("kodiAddress");
[kodiHost, kodiPort] = kodiAddress.split(":", 2);
@ -585,6 +588,30 @@ function assignLightPos(id, pos, name) {
return i;
}
function getHostInLights(hostname) {
return lights.filter(
function (lights) {
return lights.host === hostname
}
);
}
function getIpInLights(ip) {
return lights.filter(
function (lights) {
return lights.ip === ip
}
);
}
function getIdInLights(id) {
return lights.filter(
function (lights) {
return lights.id === id
}
);
}
//****************************
// Wizard Philips Hue
//****************************
@ -798,17 +825,8 @@ async function getProperties_hue_bridge(hostAddress, username, resourceFilter) {
}
function identify_hue_device(hostAddress, username, id) {
console.log("identify_hue_device");
let params = { host: hostAddress, user: username, lightId: id };
const res = requestLedDeviceIdentification("philipshue", params);
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
const r = res.info
console.log(r);
}
requestLedDeviceIdentification("philipshue", params);
}
function getHueIPs() {
@ -1271,14 +1289,7 @@ async function getProperties_wled(hostAddress, resourceFilter) {
function identify_wled(hostAddress) {
let params = { host: hostAddress };
const res = requestLedDeviceIdentification("wled", params);
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
const r = res.info
console.log(r);
}
requestLedDeviceIdentification("wled", params);
}
//****************************
@ -1390,14 +1401,6 @@ function beginWizardYeelight() {
$('#btn_wiz_abort').off().on('click', resetWizard);
}
function getHostInLights(hostname) {
return lights.filter(
function (lights) {
return lights.host === hostname
}
);
}
async function discover_yeelight_lights() {
var light = {};
// Get discovered lights
@ -1414,10 +1417,9 @@ async function discover_yeelight_lights() {
if (device.hostname !== "") {
if (getHostInLights(device.hostname).length === 0) {
light = {};
var light = {};
light.host = device.hostname;
light.port = device.port;
light.name = device.other.name;
light.model = device.other.model;
lights.push(light);
@ -1435,7 +1437,7 @@ async function discover_yeelight_lights() {
if (host !== "")
if (getHostInLights(host).length === 0) {
light = {};
var light = {};
light.host = host;
light.port = port;
light.name = configuredLights[keyConfig].name;
@ -1454,7 +1456,7 @@ function assign_yeelight_lights() {
// If records are left for configuration
if (Object.keys(lights).length > 0) {
$('#wh_topcontainer').toggle(false);
$('#yee_ids_t,#btn_wiz_save').toggle(true);
$('#yee_ids_t, #btn_wiz_save').toggle(true);
var lightOptions = [
"top", "topleft", "topright",
@ -1486,7 +1488,7 @@ function assign_yeelight_lights() {
options += '>' + $.i18n(txt + val) + '</option>';
}
var enabled = 'enabled'
var enabled = 'enabled';
if (!models.includes(lights[lightid].model)) {
var enabled = 'disabled';
options = '<option value=disabled>' + $.i18n('wiz_yeelight_unsupported') + '</option>';
@ -1514,7 +1516,7 @@ function assign_yeelight_lights() {
$('.yee_sel_watch').trigger('change');
}
else {
var noLightsTxt = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_yeelight_noLights') + '</p>';
var noLightsTxt = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_noLights','Yeelights') + '</p>';
$('#wizp2_body').append(noLightsTxt);
}
}
@ -1535,13 +1537,8 @@ async function getProperties_yeelight(hostname, port) {
}
function identify_yeelight_device(hostname, port) {
let params = { hostname: hostname, port: port };
const res = requestLedDeviceIdentification("yeelight", params);
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
//const r = res.info;
}
let params = { hostname: hostname, port: port
requestLedDeviceIdentification("yeelight", params);
}
//****************************
@ -1648,14 +1645,6 @@ function beginWizardAtmoOrb() {
$('#btn_wiz_abort').off().on('click', resetWizard);
}
function getIdInLights(id) {
return lights.filter(
function (lights) {
return lights.id === id
}
);
}
async function discover_atmoorb_lights(multiCastGroup, multiCastPort) {
var light = {};
@ -1673,13 +1662,13 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) {
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
const r = res.info
const r = res.info;
// Process devices returned by discovery
for (const device of r.devices) {
if (device.id !== "") {
if (getIdInLights(device.id).length === 0) {
light = {};
var light = {};
light.id = device.id;
light.ip = device.ip;
light.host = device.hostname;
@ -1692,7 +1681,7 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) {
for (const keyConfig in configuredLights) {
if (configuredLights[keyConfig] !== "" && !isNaN(configuredLights[keyConfig])) {
if (getIdInLights(configuredLights[keyConfig]).length === 0) {
light = {};
var light = {};
light.id = configuredLights[keyConfig];
light.ip = "";
light.host = "";
@ -1743,9 +1732,9 @@ function assign_atmoorb_lights() {
options += '>' + $.i18n(txt + val) + '</option>';
}
var enabled = 'enabled'
var enabled = 'enabled';
if (orbId < 1 || orbId > 255) {
enabled = 'disabled'
enabled = 'disabled';
options = '<option value=disabled>' + $.i18n('wiz_atmoorb_unsupported') + '</option>';
}
@ -1775,20 +1764,14 @@ function assign_atmoorb_lights() {
$('.orb_sel_watch').trigger('change');
}
else {
var noLightsTxt = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_atmoorb_noLights') + '</p>';
var noLightsTxt = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_noLights','AtmoOrbs') + '</p>';
$('#wizp2_body').append(noLightsTxt);
}
}
function identify_atmoorb_device(orbId) {
let params = { id: orbId };
const res = requestLedDeviceIdentification("atmoorb", params);
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
const r = res.info
}
requestLedDeviceIdentification("atmoorb", params);
}
//****************************
@ -1837,16 +1820,197 @@ async function getProperties_nanoleaf(hostAddress, authToken, resourceFilter) {
function identify_nanoleaf(hostAddress, authToken) {
let params = { host: hostAddress, token: authToken };
requestLedDeviceIdentification("nanoleaf", params);
}
//****************************
// Wizard Cololight
//****************************
var lights = null;
var selectedLightId = null;
function startWizardCololight(e) {
//create html
var cololight_title = 'wiz_cololight_title';
var cololight_intro1 = 'wiz_cololight_intro1';
$('#wiz_header').html('<i class="fa fa-magic fa-fw"></i>' + $.i18n(cololight_title));
$('#wizp1_body').html('<h4 style="font-weight:bold;text-transform:uppercase;">' + $.i18n(cololight_title) + '</h4><p>' + $.i18n(cololight_intro1) + '</p>');
$('#wizp1_footer').html('<button type="button" class="btn btn-primary" id="btn_wiz_cont"><i class="fa fa-fw fa-check"></i>' + $.i18n('general_btn_continue') + '</button><button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>' + $.i18n('general_btn_cancel') + '</button>');
$('#wizp2_body').html('<div id="wh_topcontainer"></div>');
$('#wh_topcontainer').append('<div class="form-group" id="usrcont" style="display:none"></div>');
$('#wizp2_body').append('<div id="colo_ids_t" style="display:none"><p style="font-weight:bold" id="colo_id_headline">' + $.i18n('wiz_cololight_desc2') + '</p></div>');
createTable("lidsh", "lidsb", "colo_ids_t");
$('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_identify')], true));
$('#wizp2_footer').html('<button type="button" class="btn btn-primary" id="btn_wiz_save" style="display:none"><i class="fa fa-fw fa-save"></i>'
+ $.i18n('general_btn_save') + '</button><buttowindow.serverConfig.device = d;n type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>'
+ $.i18n('general_btn_cancel') + '</button>');
//open modal
$("#wizard_modal").modal({
backdrop: "static",
keyboard: false,
show: true
});
//listen for continue
$('#btn_wiz_cont').off().on('click', function () {
beginWizardCololight();
$('#wizp1').toggle(false);
$('#wizp2').toggle(true);
});
}
function beginWizardCololight() {
lights = [];
discover_cololights();
$('#btn_wiz_save').off().on("click", function () {
//LED device config
//Start with a clean configuration
var d = {};
d.type = 'cololight';
//Cololight does not resolve into stable hostnames (as devices named the same), therefore use IP
if (!lights[selectedLightId].ip) {
d.host = lights[selectedLightId].host;
} else {
d.host = lights[selectedLightId].ip;
}
var coloLightProperties = lights[selectedLightId].props;
if (Object.keys(coloLightProperties).length === 0) {
alert($.i18n('wiz_cololight_noprops'));
d.hardwareLedCount = 1;
} else {
if (coloLightProperties.ledCount > 0) {
d.hardwareLedCount = coloLightProperties.ledCount;
} else if (coloLightProperties.modelType === "Strip")
d.hardwareLedCount = 120;
}
d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
window.serverConfig.device = d;
//LED layout - have initial layout prepared matching the LED-count
var coloLightLedConfig = [];
if (coloLightProperties.modelType === "Strip") {
coloLightLedConfig = createClassicLedLayoutSimple(d.hardwareLedCount / 2, d.hardwareLedCount / 4, d.hardwareLedCount / 4, 0, d.hardwareLedCount / 4 * 3, false);
} else {
coloLightLedConfig = createClassicLedLayoutSimple(0, 0, 0, d.hardwareLedCount, 0, true);
}
window.serverConfig.leds = coloLightLedConfig;
//smoothing off
window.serverConfig.smoothing.enable = false;
requestWriteConfig(window.serverConfig, true);
resetWizard();
});
$('#btn_wiz_abort').off().on('click', resetWizard);
}
async function discover_cololights() {
const res = await requestLedDeviceDiscovery('cololight');
const res = requestLedDeviceIdentification("nanoleaf", params);
// TODO: error case unhandled
// res can be: false (timeout) or res.error (not found)
if (res && !res.error) {
const r = res.info
console.log(r);
const r = res.info;
// Process devices returned by discovery
for (const device of r.devices) {
if (device.ip !== "") {
if (getIpInLights(device.ip).length === 0) {
var light = {};
light.ip = device.ip;
light.host = device.hostname;
light.name = device.name;
light.type = device.type;
lights.push(light);
}
}
}
assign_cololight_lights();
}
}
function assign_cololight_lights() {
// If records are left for configuration
if (Object.keys(lights).length > 0) {
$('#wh_topcontainer').toggle(false);
$('#colo_ids_t, #btn_wiz_save').toggle(true);
$('.lidsb').html("");
var options = "";
for (var lightid in lights) {
lights[lightid].id = lightid;
var lightHostname = lights[lightid].host;
var lightIP = lights[lightid].ip;
var val = lightHostname + " (" + lightIP + ")";
options += '<option value="' + lightid + '">' + val + '</option>';
}
var enabled = 'enabled';
$('.lidsb').append(createTableRow(['<select id="colo_select_id" ' + enabled + ' class="colo_sel_watch form-control">'
+ options
+ '</select>', '<button id="wiz_identify_btn" class="btn btn-sm btn-primary">'
+ $.i18n('wiz_identify') + '</button>']));
$('.colo_sel_watch').bind("change", function () {
selectedLightId = $('#colo_select_id').val();
var lightIP = lights[selectedLightId].ip;
$('#wiz_identify_btn').unbind().bind('click', function (event) { identify_cololight_device(lightIP); });
if (!lights[selectedLightId].props) {
getProperties_cololight(lightIP);
}
});
$('.colo_sel_watch').trigger('change');
}
else {
var noLightsTxt = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_noLights','Cololights') + '</p>';
$('#wizp2_body').append(noLightsTxt);
}
}
async function getProperties_cololight(ip) {
let params = { host: ip };
const res = await requestLedDeviceProperties('cololight', params);
if (res && !res.error) {
var coloLightProperties = res.info;
//Store properties along light with given IP-address
var id = getIpInLights(ip)[0].id;
lights[id].props = coloLightProperties;
}
}
function identify_cololight_device(hostAddress) {
let params = { host: hostAddress };
requestLedDeviceIdentification("cololight", params);
}
//****************************
// Wizard/Routines RS232-Devices
//****************************

View File

@ -60,7 +60,7 @@ public:
///
/// @brief Set the number of LEDs supported by the device.
///
/// @param[in] ledCount Number of device LEDs
/// @param[in] ledCount Number of device LEDs, 0 = unknown number
///
void setLedCount(unsigned int ledCount);
@ -191,7 +191,7 @@ public slots:
///
/// @brief Get the number of LEDs supported by the device.
///
/// @return Number of device's LEDs
/// @return Number of device's LEDs, 0 = unknown number
///
unsigned int getLedCount() const { return _ledCount; }
@ -350,6 +350,14 @@ protected:
/// @return array as string of hex values
QString uint8_t_to_hex_string(const uint8_t * data, const qint64 size, qint64 number = -1) const;
///
/// @brief Converts a ByteArray to hex string.
///
/// @param data ByteArray
/// @param number Number of array items to be converted.
/// @return array as string of hex values
QString toHex(const QByteArray& data, int number = -1) const;
/// Current device's type
QString _activeDeviceType;
@ -368,17 +376,17 @@ protected:
// Device configuration parameters
/// Number of hardware LEDs supported by device.
unsigned int _ledCount;
unsigned int _ledRGBCount;
unsigned int _ledRGBWCount;
/// Refresh interval in milliseconds
int _refreshTimerInterval_ms;
/// Time a device requires mandatorily between two writes (in milliseconds)
int _latchTime_ms;
/// Number of hardware LEDs supported by device.
uint _ledCount;
uint _ledRGBCount;
uint _ledRGBWCount;
/// Does the device allow restoring the original state?
bool _isRestoreOrigState;

View File

@ -24,6 +24,7 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
, _refreshTimer(nullptr)
, _refreshTimerInterval_ms(0)
, _latchTime_ms(0)
, _ledCount(0)
, _isRestoreOrigState(false)
, _isEnabled(false)
, _isDeviceInitialised(false)
@ -454,3 +455,17 @@ QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const qint64 size
return bytes.toHex();
#endif
}
QString LedDevice::toHex(const QByteArray& data, int number) const
{
if ( number <= 0 || number > data.size())
{
number = data.size();
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return data.left(number).toHex(':');
#else
return data.left(number).toHex();
#endif
}

View File

@ -35,5 +35,6 @@
<file alias="schema-nanoleaf">schemas/schema-nanoleaf.json</file>
<file alias="schema-wled">schemas/schema-wled.json</file>
<file alias="schema-yeelight">schemas/schema-yeelight.json</file>
<file alias="schema-cololight">schemas/schema-cololight.json</file>
</qresource>
</RCC>

View File

@ -0,0 +1,772 @@
#include "LedDeviceCololight.h"
#include <utils/QStringUtils.h>
#include <QUdpSocket>
#include <QHostInfo>
#include <QtEndian>
#include <QEventLoop>
#include <chrono>
// Constants
namespace {
const bool verbose = false;
const bool verbose3 = false;
// Configuration settings
const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
// Cololight discovery service
const int API_DEFAULT_PORT = 8900;
const char DISCOVERY_ADDRESS[] = "255.255.255.255";
const quint16 DISCOVERY_PORT = 12345;
const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 5000 };
constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
const char COLOLIGHT_MODEL[] = "mod";
const char COLOLIGHT_MODEL_TYPE[] = "subkey";
const char COLOLIGHT_MAC[] = "sn";
const char COLOLIGHT_NAME[] = "name";
const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
const int COLOLIGHT_BEADS_PER_MODULE = 19;
const int COLOLIGHT_MIN_STRIP_SEGMENT_SIZE = 30;
enum verbs {
GET = 0x03,
SET = 0x04,
SETEEPROM = 0x07,
SETVAR = 0x0b
};
enum commandTypes {
STATE_OFF = 0x80,
STATE_ON = 0x81,
BRIGTHNESS = 0xCF,
SETCOLOR = 0xFF
};
enum idxTypes {
BRIGTHNESS_CONTROL = 0x01,
COLOR_CONTROL = 0x02,
COLOR_DIRECT_CONTROL = 0x81,
READ_INFO_FROM_STORAGE = 0x86
};
enum bufferMode {
MONOCROME = 0x01,
LIGHTBEAD = 0x02,
};
enum ledLayout {
STRIP_LAYOUT,
MODLUE_LAYOUT
};
enum modelType {
STRIP,
PLUS
};
const uint8_t PACKET_HEADER[] =
{
'S', 'Z', // Tag "SZ"
0x30, 0x30, // Version "00"
0x00, 0x00, // AppID, 0x0000 = TL1 command mode
0x00, 0x00, 0x00, 0x00 // Size
};
const uint8_t PACKET_SECU[] =
{
0x00, 0x00, 0x00, 0x00, // Dict
0x00, 0x00, 0x00, 0x00, // Sum
0x00, 0x00, 0x00, 0x00, // Salt
0x00, 0x00, 0x00, 0x00 // SN
};
const uint8_t TL1_CMD_FIXED_PART[] =
{
0x00, 0x00, 0x00, 0x00, // DISTID
0x00, 0x00, 0x00, 0x00, // SRCID
0x00, // SECU
0x00, // VERB
0x00, // CTAG
0x00 // LENGTH
};
} //End of constants
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
: ProviderUdp(deviceConfig)
, _modelType(-1)
, _ledLayoutType(STRIP_LAYOUT)
, _ledBeadCount(0)
, _distance(0)
, _sequenceNumber(1)
{
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU));
}
LedDevice* LedDeviceCololight::construct(const QJsonObject& deviceConfig)
{
return new LedDeviceCololight(deviceConfig);
}
bool LedDeviceCololight::init(const QJsonObject& deviceConfig)
{
bool isInitOK = false;
_port = API_DEFAULT_PORT;
if (ProviderUdp::init(deviceConfig))
{
// Initialise LedDevice configuration and execution environment
Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder()));
Debug(_log, "LatchTime : %d", this->getLatchTime());
if (initLedsConfiguration())
{
initDirectColorCmdTemplate();
isInitOK = true;
}
}
return isInitOK;
}
bool LedDeviceCololight::initLedsConfiguration()
{
bool isInitOK = false;
if (!getInfo())
{
QString errorReason = QString("Cololight device (%1) not accessible to get additional properties!")
.arg(getAddress().toString());
setInError(errorReason);
}
else
{
QString modelTypeText;
switch (_modelType) {
case 0:
modelTypeText = "Strip";
_ledLayoutType = STRIP_LAYOUT;
break;
case 1:
_ledLayoutType = MODLUE_LAYOUT;
modelTypeText = "Plus";
break;
default:
_modelType = STRIP;
modelTypeText = "Strip";
_ledLayoutType = STRIP_LAYOUT;
Info(_log, "Model not identified, assuming Cololight %s", QSTRING_CSTR(modelTypeText));
break;
}
Debug(_log, "Model type : %s", QSTRING_CSTR(modelTypeText));
if (getLedCount() == 0)
{
setLedCount(static_cast<uint>(_devConfig[CONFIG_HW_LED_COUNT].toInt(0)));
}
if (_modelType == STRIP && (getLedCount() % COLOLIGHT_MIN_STRIP_SEGMENT_SIZE != 0))
{
QString errorReason = QString("Hardware LED count must be multiple of %1 for Cololight Strip!")
.arg(COLOLIGHT_MIN_STRIP_SEGMENT_SIZE);
this->setInError(errorReason);
}
else
{
Debug(_log, "LedCount : %d", getLedCount());
uint configuredLedCount = static_cast<uint>(_devConfig["currentLedCount"].toInt(1));
if (getLedCount() < configuredLedCount)
{
QString errorReason = QString("Not enough LEDs [%1] for configured LEDs in layout [%2] found!")
.arg(getLedCount())
.arg(configuredLedCount);
this->setInError(errorReason);
}
else
{
if (getLedCount() > configuredLedCount)
{
Info(_log, "%s: More LEDs [%u] than configured LEDs in layout [%u].", QSTRING_CSTR(this->getActiveDeviceType()), getLedCount(), configuredLedCount);
}
isInitOK = true;
}
}
}
return isInitOK;
}
void LedDeviceCololight::initDirectColorCmdTemplate()
{
int ledNumber = static_cast<int>(this->getLedCount());
_directColorCommandTemplate.clear();
//Packet
_directColorCommandTemplate.append(static_cast<char>(bufferMode::LIGHTBEAD)); // idx
int beads = 1;
if (_ledLayoutType == MODLUE_LAYOUT)
{
beads = COLOLIGHT_BEADS_PER_MODULE;
}
for (int i = 0; i < ledNumber; ++i)
{
_directColorCommandTemplate.append(static_cast<char>(i * beads + 1));
_directColorCommandTemplate.append(static_cast<char>(i * beads + beads));
_directColorCommandTemplate.append(3, static_cast<char>(0x00));
}
}
bool LedDeviceCololight::getInfo()
{
bool isCmdOK = false;
QByteArray command;
const quint8 packetSize = 2;
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
command.fill('\0');
command[fixPartsize - 3] = static_cast<char>(SETVAR); // verb
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
//Packet
command[fixPartsize] = static_cast<char>(READ_INFO_FROM_STORAGE); // idx
command[fixPartsize + 1] = static_cast<char>(0x01); // idx
if (sendRequest(TL1_CMD, command))
{
QByteArray response;
if (readResponse(response))
{
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
quint16 ledNum = qFromBigEndian<quint16>(response.data() + 1);
if (ledNum != 0xFFFF)
{
_ledBeadCount = ledNum;
if (ledNum % COLOLIGHT_BEADS_PER_MODULE == 0)
{
_modelType = MODLUE_LAYOUT;
_distance = ledNum / COLOLIGHT_BEADS_PER_MODULE;
setLedCount(static_cast<uint>(_distance));
}
}
else
{
_modelType = STRIP;
setLedCount(0);
}
Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance);
isCmdOK = true;
}
}
return isCmdOK;
}
bool LedDeviceCololight::setEffect(const effect effect)
{
return setColor(static_cast<uint32_t>(effect));
}
bool LedDeviceCololight::setColor(const ColorRgb colorRgb)
{
uint32_t color = colorRgb.blue | (colorRgb.green << 8) | (colorRgb.red << 16) | (0x00 << 24);
return setColor(color);
}
bool LedDeviceCololight::setColor(const uint32_t color)
{
bool isCmdOK = false;
QByteArray command;
const quint8 packetSize = 6;
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
command.fill('\0');
command[fixPartsize - 3] = static_cast<char>(SET); // verb
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
//Packet
command[fixPartsize] = static_cast<char>(0x02); // idx
command[fixPartsize + 1] = static_cast<char>(0xff); // set color or dynamic effect
qToBigEndian<quint32>(color, command.data() + fixPartsize + 2);
if (sendRequest(TL1_CMD, command))
{
QByteArray response;
if (readResponse(response))
{
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
isCmdOK = true;
}
}
return isCmdOK;
}
bool LedDeviceCololight::setState(bool isOn)
{
bool isCmdOK = false;
quint8 type = isOn ? STATE_ON : STATE_OFF;
QByteArray command;
const quint8 packetSize = 3;
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
command.fill('\0');
command[fixPartsize - 3] = static_cast<char>(SET); // verb
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
//Packet
command[fixPartsize] = static_cast<char>(BRIGTHNESS_CONTROL); // idx
command[fixPartsize + 1] = static_cast<char>(type); // type
command[fixPartsize + 2] = static_cast<char>(isOn); // value
if (sendRequest(TL1_CMD, command))
{
QByteArray response;
if (readResponse(response))
{
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
isCmdOK = true;
}
}
return isCmdOK;
}
bool LedDeviceCololight::setStateDirect(bool isOn)
{
bool isCmdOK = false;
QByteArray command;
//Packet
command.append(static_cast<char>(0x04)); // idx
command.append(static_cast<char>(isOn)); // idx
command.append(static_cast<char>(0xd7)); // idx
if (sendRequest(DIRECT_CONTROL, command))
{
QByteArray response;
if (readResponse(response))
{
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
isCmdOK = true;
}
}
return isCmdOK;
}
bool LedDeviceCololight::setColor(const std::vector<ColorRgb>& ledValues)
{
int ledNumber = static_cast<int>(ledValues.size());
QByteArray command = _directColorCommandTemplate;
//Update LED values, start from offset (mode + first start/stop pair) = 3
for (int i = 0; i < ledNumber; ++i)
{
command[3 + i * 5] = static_cast<char>(ledValues[i].red);
command[3 + i * 5 + 1] = static_cast<char>(ledValues[i].green);
command[3 + i * 5 + 2] = static_cast<char>(ledValues[i].blue);
}
bool isCmdOK = sendRequest(DIRECT_CONTROL, command);
return isCmdOK;
}
bool LedDeviceCololight::setTL1CommandMode(bool isOn)
{
bool isCmdOK = false;
quint8 type = isOn ? STATE_ON : STATE_OFF;
QByteArray command;
const quint8 packetSize = 2;
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
command.fill('\0');
command[fixPartsize - 3] = static_cast<char>(SETEEPROM); // verb
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
//Packet
command[fixPartsize] = static_cast<char>(COLOR_CONTROL); // idx
command[fixPartsize + 1] = static_cast<char>(type); // type
if (sendRequest(TL1_CMD, command))
{
QByteArray response;
if (readResponse(response))
{
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
isCmdOK = true;
}
}
return isCmdOK;
}
bool LedDeviceCololight::sendRequest(const appID appID, const QByteArray& command)
{
bool isSendOK = true;
QByteArray packet(_packetFixPart);
packet.append(static_cast<char>(_sequenceNumber));
packet.append(command);
quint32 size = sizeof(PACKET_SECU) + 1 + command.size();
qToBigEndian<quint16>(appID, packet.data() + 4);
qToBigEndian<quint32>(size, packet.data() + 6);
++_sequenceNumber;
DebugIf(verbose3, _log, "packet: ([0x%x], [%u])[%s]", size, size, QSTRING_CSTR(toHex(packet, 64)));
if (writeBytes(packet) < 0)
{
isSendOK = false;
}
return isSendOK;
}
bool LedDeviceCololight::readResponse()
{
QByteArray response;
return readResponse(response);
}
bool LedDeviceCololight::readResponse(QByteArray& response)
{
bool isRequestOK = false;
if (_udpSocket->waitForReadyRead(DEFAULT_READ_TIMEOUT.count()))
{
while (_udpSocket->waitForReadyRead(200))
{
QByteArray datagram;
while (_udpSocket->hasPendingDatagrams())
{
datagram.resize(static_cast<int>(_udpSocket->pendingDatagramSize()));
QHostAddress senderIP;
quint16 senderPort;
_udpSocket->readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort);
if (datagram.size() >= 10)
{
DebugIf(verbose3, _log, "response: [%s]", QSTRING_CSTR(toHex(datagram, 64)));
quint16 appID = qFromBigEndian<quint16>(datagram.mid(4, sizeof(appID)));
if (verbose && appID == 0x8000)
{
QString tagVersion = datagram.left(2);
quint32 packetSize = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) - sizeof(packetSize)));
Debug(_log, "Response HEADER: tagVersion [%s], appID: [0x%.2x][%u], packet size: [0x%.4x][%u]", QSTRING_CSTR(tagVersion), appID, appID, packetSize, packetSize);
quint32 dictionary = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER)));
quint32 checkSum = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary)));
quint32 salt = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum), sizeof(salt)));
quint32 sequenceNumber = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum) + sizeof(salt)));
Debug(_log, "Response SECU : Dict: [0x%.4x][%u], Sum: [0x%.4x][%u], Salt: [0x%.4x][%u], SN: [0x%.4x][%u]", dictionary, dictionary, checkSum, checkSum, salt, salt, sequenceNumber, sequenceNumber);
quint8 packetSN = static_cast<quint8>(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU)));
Debug(_log, "Response packSN: [0x%.4x][%u]", packetSN, packetSN);
}
quint8 errorCode = static_cast<quint8>(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + 1));
int dataPartStart = sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + sizeof(TL1_CMD_FIXED_PART);
if (errorCode != 0)
{
quint8 originalVerb = static_cast<quint8>(datagram.at(dataPartStart - 2) - 0x80);
quint8 originalRequestPacketSN = static_cast<quint8>(datagram.at(dataPartStart - 1));
if (errorCode == 16)
{
//TL1 Command failure
Error(_log, "Request [0x%x] failed =with error [%u], appID [%u], originalVerb [0x%x]", originalRequestPacketSN, errorCode, appID, originalVerb);
}
else
{
Error(_log, "Request [0x%x] failed with error [%u], appID [%u]", originalRequestPacketSN, errorCode, appID);
}
}
else
{
// TL1 Protocol
if (appID == 0x8000)
{
if (dataPartStart < datagram.size())
{
quint8 dataLength = static_cast<quint8>(datagram.at(dataPartStart));
response = datagram.mid(dataPartStart + 1, dataLength);
if (verbose)
{
quint8 originalVerb = static_cast<quint8>(datagram.at(dataPartStart - 2) - 0x80);
Debug(_log, "Cmd [0x%x], Data returned: [%s]", originalVerb, QSTRING_CSTR(toHex(response)));
}
}
else
{
DebugIf(verbose, _log, "No additional data returned");
}
}
isRequestOK = true;
}
}
}
}
}
return isRequestOK;
}
int LedDeviceCololight::write(const std::vector<ColorRgb>& ledValues)
{
int rc = -1;
if (setColor(ledValues))
{
rc = 0;
}
return rc;
}
bool LedDeviceCololight::powerOn()
{
bool on = true;
if (_isDeviceReady)
{
if (!setState(false) || !setTL1CommandMode(false))
{
on = false;
}
}
return on;
}
bool LedDeviceCololight::powerOff()
{
bool off = true;
if (_isDeviceReady)
{
writeBlack();
off = setStateDirect(false);
setTL1CommandMode(false);
}
return off;
}
QJsonObject LedDeviceCololight::discover()
{
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QJsonArray deviceList;
QUdpSocket udpSocket;
udpSocket.writeDatagram(QString(DISCOVERY_MESSAGE).toUtf8(), QHostAddress(DISCOVERY_ADDRESS), DISCOVERY_PORT);
if (udpSocket.waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count()))
{
while (udpSocket.waitForReadyRead(500))
{
QByteArray datagram;
while (udpSocket.hasPendingDatagrams())
{
datagram.resize(static_cast<int>(udpSocket.pendingDatagramSize()));
QHostAddress senderIP;
quint16 senderPort;
udpSocket.readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort);
QString data(datagram);
QMap<QString, QString> headers;
// parse request
QStringList entries = QStringUtils::split(data, "\n", QStringUtils::SplitBehavior::SkipEmptyParts);
for (auto entry : entries)
{
// split into key=value, be aware that value field may contain also a "="
entry = entry.simplified();
int pos = entry.indexOf("=");
if (pos == -1)
{
continue;
}
const QString key = entry.left(pos).trimmed().toLower();
const QString value = entry.mid(pos + 1).trimmed();
headers[key] = value;
}
if (headers.value("mod") == COLOLIGHT_MODEL_IDENTIFIER)
{
QString ipAddress = QHostAddress(senderIP.toIPv4Address()).toString();
_services.insert(ipAddress, headers);
Debug(_log, "Cololight discovered at [%s]", QSTRING_CSTR(ipAddress));
DebugIf(verbose3, _log, "_data: [%s]", QSTRING_CSTR(data));
}
}
}
}
QMap<QString, QMap <QString, QString>>::iterator i;
for (i = _services.begin(); i != _services.end(); ++i)
{
QJsonObject obj;
obj.insert("ip", i.key());
obj.insert("model", i.value().value(COLOLIGHT_MODEL));
obj.insert("type", i.value().value(COLOLIGHT_MODEL_TYPE));
obj.insert("mac", i.value().value(COLOLIGHT_MAC));
obj.insert("name", i.value().value(COLOLIGHT_NAME));
QHostInfo hostInfo = QHostInfo::fromName(i.key());
if (hostInfo.error() == QHostInfo::NoError)
{
QString hostname = hostInfo.hostName();
//Seems that for Windows no local domain name is resolved
if (!QHostInfo::localDomainName().isEmpty())
{
obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName()));
obj.insert("domain", QHostInfo::localDomainName());
}
else
{
int domainPos = hostname.indexOf('.');
obj.insert("hostname", hostname.left(domainPos));
obj.insert("domain", hostname.mid(domainPos + 1));
}
}
deviceList << obj;
}
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
QString apiHostname = params["host"].toString("");
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
if (!apiHostname.isEmpty())
{
QJsonObject deviceConfig;
deviceConfig.insert("host", apiHostname);
deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig))
{
if (getInfo())
{
QString modelTypeText;
switch (_modelType) {
case 1:
modelTypeText = "Plus";
break;
default:
modelTypeText = "Strip";
break;
}
properties.insert("modelType", modelTypeText);
properties.insert("ledCount", static_cast<int>(getLedCount()));
properties.insert("ledBeadCount", _ledBeadCount);
properties.insert("distance", _distance);
}
}
}
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
return properties;
}
void LedDeviceCololight::identify(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString apiHostname = params["host"].toString("");
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
if (!apiHostname.isEmpty())
{
QJsonObject deviceConfig;
deviceConfig.insert("host", apiHostname);
deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig))
{
if (setStateDirect(false) && setState(true))
{
setEffect(THE_CIRCUS);
QEventLoop loop;
QTimer::singleShot(DEFAULT_IDENTIFY_TIME.count(), &loop, &QEventLoop::quit);
loop.exec();
setColor(ColorRgb::BLACK);
}
}
}
}

View File

@ -0,0 +1,246 @@
#ifndef LEDEVICECOLOLIGHT_H
#define LEDEVICECOLOLIGHT_H
// LedDevice includes
#include <leddevice/LedDevice.h>
#include "ProviderUdp.h"
enum appID {
TL1_CMD = 0x00,
DIRECT_CONTROL = 0x01,
TRANSMIT_FILE = 0x02,
CLEAR_FILES = 0x03,
WRITE_FILE = 0x04,
READ_FILE = 0x05,
MODIFY_SECU = 0x06
};
enum effect : uint32_t {
SAVANNA = 0x04970400,
SUNRISE = 0x01c10a00,
UNICORNS = 0x049a0e00,
PENSIEVE = 0x04c40600,
THE_CIRCUS = 0x04810130,
INSTASHARE = 0x03bc0190,
EIGTHIES = 0x049a0000,
CHERRY_BLOS = 0x04940800,
RAINBOW = 0x05bd0690,
TEST = 0x03af0af0,
CHRISTMAS = 0x068b0900
};
///
/// Implementation of a Cololight LedDevice
///
class LedDeviceCololight : public ProviderUdp
{
public:
///
/// @brief Constructs a Cololight LED-device
///
/// @param deviceConfig Device's configuration as JSON-Object
///
explicit LedDeviceCololight(const QJsonObject& deviceConfig);
///
/// @brief Constructs the LED-device
///
/// @param[in] deviceConfig Device's configuration as JSON-Object
/// @return LedDevice constructed
///
static LedDevice* construct(const QJsonObject& deviceConfig);
///
/// @brief Discover Cololight devices available (for configuration).
///
/// @return A JSON structure holding a list of devices found
///
QJsonObject discover() override;
///
/// @brief Get a Cololight device's resource properties
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP",
/// }
///@endcode
///
/// @param[in] params Parameters to query device
/// @return A JSON structure holding the device's properties
///
QJsonObject getProperties(const QJsonObject& params) override;
///
/// @brief Send an update to the Cololight device to identify it.
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP",
/// }
///@endcode
///
/// @param[in] params Parameters to address device
///
void identify(const QJsonObject& params) override;
protected:
///
/// @brief Initialise the device's configuration
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
/// @param[in] ledValues The RGB-color per LED
/// @return Zero on success, else negative
///
int write(const std::vector<ColorRgb>& ledValues) override;
///
/// @brief Power-/turn on the Cololight device.
///
/// @return True if success
///
bool powerOn() override;
///
/// @brief Power-/turn off the Cololight device.
///
/// @return True if success
///
bool powerOff() override;
private:
bool initLedsConfiguration();
void initDirectColorCmdTemplate();
///
/// @brief Read additional information from Cololight
///
/// @return True if success
///
bool getInfo();
///
/// @brief Set a Cololight effect
///
/// @param[in] effect from effect list
///
/// @return True if success
///
bool setEffect(const effect effect);
///
/// @brief Set a color
///
/// @param[in] color in RGB
///
/// @return True if success
///
bool setColor(const ColorRgb colorRgb);
///
/// @brief Set a color (or effect)
///
/// @param[in] color in four bytes (red, green, blue, mode)
///
/// @return True if success
///
bool setColor(const uint32_t color);
///
/// @brief Set colors per LED as per given list
///
/// @param[in] list of color per LED
///
/// @return True if success
///
bool setColor(const std::vector<ColorRgb>& ledValues);
///
/// @brief Set the Cololight device in TL1 command mode
///
/// @param[in] isOn, Enable TL1 command mode = true
///
/// @return True if success
///
bool setTL1CommandMode(bool isOn);
///
/// @brief Set the Cololight device's state (on/off) in TL1 mode
///
/// @param[in] isOn, on=true
///
/// @return True if success
///
bool setState(bool isOn);
///
/// @brief Set the Cololight device's state (on/off) in Direct Mode
///
/// @param[in] isOn, on=true
///
/// @return True if success
///
bool setStateDirect(bool isOn);
///
/// @brief Send a request to the Cololight device for execution
///
/// @param[in] appID
/// @param[in] command
///
/// @return True if success
///
bool sendRequest(const appID appID, const QByteArray& command);
///
/// @brief Read response for a send request
///
/// @return True if success
///
bool readResponse();
///
/// @brief Read response for a send request
///
/// @param[out] response
///
/// @return True if success
///
bool readResponse(QByteArray& response);
// Cololight model, e.g. CololightPlus, CololightStrip
int _modelType;
// Defines how Cololight LED are organised (multiple light beads in a module or individual lights on a strip
int _ledLayoutType;
// Count of overall LEDs across all modules
int _ledBeadCount;
// Distance (in #modules) of the module farest away from the main controller
int _distance;
QByteArray _packetFixPart;
QByteArray _DataPart;
QByteArray _directColorCommandTemplate;
quint32 _sequenceNumber;
//Cololights discovered and their response message details
QMultiMap<QString, QMap <QString, QString>> _services;
};
#endif // LEDEVICECOLOLIGHT_H

View File

@ -22,7 +22,7 @@ ProviderUdp::ProviderUdp(const QJsonObject &deviceConfig)
, _port(1)
, _defaultHost("127.0.0.1")
{
_latchTime_ms = 1;
_latchTime_ms = 0;
}
ProviderUdp::~ProviderUdp()
@ -138,3 +138,13 @@ int ProviderUdp::writeBytes(const unsigned size, const uint8_t * data)
return retVal;
}
int ProviderUdp::writeBytes(const QByteArray &bytes)
{
qint64 retVal = _udpSocket->writeDatagram(bytes,_address,_port);
WarningIf((retVal<0), _log, "&s", QSTRING_CSTR(QString
("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString())));
return retVal;
}

View File

@ -28,6 +28,8 @@ public:
///
~ProviderUdp() override;
QHostAddress getAddress() const { return _address; }
protected:
///
@ -53,8 +55,7 @@ protected:
int close() override;
///
/// @brief Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched.
/// @brief Writes the given bytes to the UDP-device
///
/// @param[in] size The length of the data
/// @param[in] data The data
@ -63,6 +64,15 @@ protected:
///
int writeBytes(const unsigned size, const uint8_t *data);
///
/// @brief Writes the given bytes to the UDP-device
///
/// @param[in] data The data
///
/// @return Zero on success, else negative
///
int writeBytes(const QByteArray &bytes);
///
QUdpSocket * _udpSocket;
QHostAddress _address;

View File

@ -0,0 +1,22 @@
{
"type":"object",
"required":true,
"properties": {
"host" : {
"type": "string",
"title":"edt_dev_spec_targetIpHost_title",
"propertyOrder" : 1
},
"latchTime": {
"type": "integer",
"title":"edt_dev_spec_latchtime_title",
"default": 0,
"append" : "edt_append_ms",
"minimum": 0,
"maximum": 1000,
"access" : "expert",
"propertyOrder" : 2
}
},
"additionalProperties": true
}