mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
New LED Device - Cololight (#1070)
This commit is contained in:
parent
bb652ade36
commit
83455441fa
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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]=[];
|
||||
|
@ -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
|
||||
//****************************
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
772
libsrc/leddevice/dev_net/LedDeviceCololight.cpp
Normal file
772
libsrc/leddevice/dev_net/LedDeviceCololight.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
246
libsrc/leddevice/dev_net/LedDeviceCololight.h
Normal file
246
libsrc/leddevice/dev_net/LedDeviceCololight.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
22
libsrc/leddevice/schemas/schema-cololight.json
Normal file
22
libsrc/leddevice/schemas/schema-cololight.json
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user