Merge remote-tracking branch 'origin/master' into updates

This commit is contained in:
LordGrey 2023-10-15 18:31:00 +02:00
commit b7945d4aa7
30 changed files with 3421 additions and 1175 deletions

View File

@ -36,7 +36,7 @@ jobs:
name: Setup APT build name: Setup APT build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set APT matrix - name: Set APT matrix
id: apt-ppa id: apt-ppa
run: | run: |
@ -54,7 +54,7 @@ jobs:
matrix: ${{ fromJson(needs.setup.outputs.apt-matrix) }} matrix: ${{ fromJson(needs.setup.outputs.apt-matrix) }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }} ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }}
submodules: true submodules: true
@ -107,12 +107,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }} ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }}
- name: Import GPG key - name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v5.3.0 uses: crazy-max/ghaction-import-gpg@v6.0.0
with: with:
gpg_private_key: ${{ secrets.APT_GPG }} gpg_private_key: ${{ secrets.APT_GPG }}

View File

@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive

View File

@ -13,7 +13,7 @@ jobs:
if: github.repository_owner == 'hyperion-project' if: github.repository_owner == 'hyperion-project'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
@ -48,7 +48,7 @@ jobs:
if: github.repository_owner == 'hyperion-project' if: github.repository_owner == 'hyperion-project'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Check if commit has changed - name: Check if commit has changed
id: build-necessary id: build-necessary
run: | run: |
@ -66,7 +66,7 @@ jobs:
if: ${{ needs.check.outputs.build-nightly == 'true' }} if: ${{ needs.check.outputs.build-nightly == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set nightly matrix - name: Set nightly matrix
id: nightly-ppa id: nightly-ppa
run: | run: |
@ -84,7 +84,7 @@ jobs:
matrix: ${{ fromJson(needs.setup.outputs.nightly-matrix) }} matrix: ${{ fromJson(needs.setup.outputs.nightly-matrix) }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -135,10 +135,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Import GPG key - name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v5.3.0 uses: crazy-max/ghaction-import-gpg@v6.0.0
with: with:
gpg_private_key: ${{ secrets.APT_GPG }} gpg_private_key: ${{ secrets.APT_GPG }}

View File

@ -32,7 +32,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -76,7 +76,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -125,7 +125,7 @@ jobs:
QT_VERSION: 5.15.2 QT_VERSION: 5.15.2
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive

View File

@ -33,7 +33,7 @@ jobs:
platform: amlogic platform: amlogic
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -62,7 +62,7 @@ jobs:
name: macOS name: macOS
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -97,7 +97,7 @@ jobs:
QT_VERSION: 5.15.2 QT_VERSION: 5.15.2
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
@ -162,7 +162,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
# Generate environment variables # Generate environment variables
- name: Generate environment variables from .version and tag - name: Generate environment variables from .version and tag

View File

@ -10,7 +10,7 @@ jobs:
steps: steps:
# Dispatch event to build new HyperBian image # Dispatch event to build new HyperBian image
- name: Dispatch HyperBian build - name: Dispatch HyperBian build
uses: peter-evans/repository-dispatch@v2.1.1 uses: peter-evans/repository-dispatch@v2.1.2
if: ${{ github.repository_owner == 'hyperion-project'}} if: ${{ github.repository_owner == 'hyperion-project'}}
with: with:
repository: hyperion-project/HyperBian repository: hyperion-project/HyperBian

View File

@ -10,13 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Nanoleaf: Wizard to generate user authorization token allowing users to configure the device via a single window
- Nanoleaf: Generation of a default layout per device's configuration, including orientation
### Changed ### Changed
### Fixed ### Fixed
- Fixed missing Include limits in QJsonSchemaChecker - Fixed missing Include limits in QJsonSchemaChecker
- Fixed dependencies for deb packages in Debian Bookworm - Fixed dependencies for deb packages in Debian Bookworm
- Nanoleaf: "Panel numbering sequence" was not configurable any longer
- Nanoleaf: Number of panels increased during retries (#1643)
## Removed ### Removed
- Nanoleaf: Removed "Start Position" in favour of the general Blacklist feature provided
## [2.0.15](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.15) - 2023-02 ## [2.0.15](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.15) - 2023-02

View File

@ -46,6 +46,9 @@
</div> </div>
</div> </div>
<div class="panel-footer" style="text-align:right"> <div class="panel-footer" style="text-align:right">
<button id='btn_layout_controller' class="btn btn-primary" disabled data-toggle="tooltip" data-placement="top" title="Generate a layout for the configured device">
<i class="fa fa-fw fa-save"></i><span data-i18n="wiz_layout">Generate Layout</span>
</button>
<button id='btn_test_controller' class="btn btn-primary" disabled data-toggle="tooltip" data-placement="top" title="Identify configured device by lighting it up"> <button id='btn_test_controller' class="btn btn-primary" disabled data-toggle="tooltip" data-placement="top" title="Identify configured device by lighting it up">
<i class="fa fa-fw fa-save"></i><span data-i18n="wiz_identify">Identify/Test</span> <i class="fa fa-fw fa-save"></i><span data-i18n="wiz_identify">Identify/Test</span>
</button> </button>

View File

@ -86,6 +86,8 @@
"conf_leds_layout_cl_bottomright": "Bottom Right (Corner)", "conf_leds_layout_cl_bottomright": "Bottom Right (Corner)",
"conf_leds_layout_cl_cornergap": "Corner Gap", "conf_leds_layout_cl_cornergap": "Corner Gap",
"conf_leds_layout_cl_edgegap": "Edge Gap", "conf_leds_layout_cl_edgegap": "Edge Gap",
"conf_leds_layout_cl_entertainment": "Entertainment Area",
"conf_leds_layout_cl_entertainment_center": "Entertainment Area Center",
"conf_leds_layout_cl_gaglength": "Gap length", "conf_leds_layout_cl_gaglength": "Gap length",
"conf_leds_layout_cl_gappos": "gap position", "conf_leds_layout_cl_gappos": "gap position",
"conf_leds_layout_cl_hleddepth": "Horizontal LED depth", "conf_leds_layout_cl_hleddepth": "Horizontal LED depth",
@ -116,6 +118,8 @@
"conf_leds_layout_cl_vleddepth": "Vertical LED depth", "conf_leds_layout_cl_vleddepth": "Vertical LED depth",
"conf_leds_layout_frame": "Classic Layout (LED Frame)", "conf_leds_layout_frame": "Classic Layout (LED Frame)",
"conf_leds_layout_generatedconf": "Generated/Current LED Configuration", "conf_leds_layout_generatedconf": "Generated/Current LED Configuration",
"conf_leds_layout_generation_success": "LED Layout generated sucessfully",
"conf_leds_layout_generation_error": "LED Layout was not generated",
"conf_leds_layout_intro": "You also need an LED layout, which reflects your LED positions. The classic layout is the usually used TV frame, but we also support LED matrix (LED walls) creation. The view on this layout is ALWAYS from the FRONT of your TV.", "conf_leds_layout_intro": "You also need an LED layout, which reflects your LED positions. The classic layout is the usually used TV frame, but we also support LED matrix (LED walls) creation. The view on this layout is ALWAYS from the FRONT of your TV.",
"conf_leds_layout_ma_cabling": "Cabling", "conf_leds_layout_ma_cabling": "Cabling",
"conf_leds_layout_ma_direction": "Direction", "conf_leds_layout_ma_direction": "Direction",
@ -564,8 +568,8 @@
"edt_conf_webc_port_title": "HTTP Port", "edt_conf_webc_port_title": "HTTP Port",
"edt_conf_webc_sslport_expl": "Port for the WebServer, RPC and WebSocket HTTPS connections", "edt_conf_webc_sslport_expl": "Port for the WebServer, RPC and WebSocket HTTPS connections",
"edt_conf_webc_sslport_title": "HTTPS Port", "edt_conf_webc_sslport_title": "HTTPS Port",
"edt_dev_auth_key_title": "Authentication Token", "edt_dev_auth_key_title": "Authorization Token",
"edt_dev_auth_key_title_info": "Authentication Token required to acccess the device", "edt_dev_auth_key_title_info": "Authorization Token required to acccess the device",
"edt_dev_enum_sub_min_cool_adjust": "Subtract cool white", "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white",
"edt_dev_enum_sub_min_warm_adjust": "Subtract warm white", "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white",
"edt_dev_enum_subtract_minimum": "Subtract minimum", "edt_dev_enum_subtract_minimum": "Subtract minimum",
@ -618,7 +622,7 @@
"edt_dev_spec_gpioBcm_title": "GPIO Pin", "edt_dev_spec_gpioBcm_title": "GPIO Pin",
"edt_dev_spec_gpioMap_title": "GPIO mapping", "edt_dev_spec_gpioMap_title": "GPIO mapping",
"edt_dev_spec_gpioNumber_title": "GPIO number", "edt_dev_spec_gpioNumber_title": "GPIO number",
"edt_dev_spec_groupId_title": "Group ID", "edt_dev_spec_groupId_title": "Group",
"edt_dev_spec_header_title": "Specific Settings", "edt_dev_spec_header_title": "Specific Settings",
"edt_dev_spec_interpolation_title": "Interpolation", "edt_dev_spec_interpolation_title": "Interpolation",
"edt_dev_spec_intervall_title": "Interval", "edt_dev_spec_intervall_title": "Interval",
@ -683,6 +687,7 @@
"edt_dev_spec_transistionTime_title": "Transition time", "edt_dev_spec_transistionTime_title": "Transition time",
"edt_dev_spec_uid_title": "UID", "edt_dev_spec_uid_title": "UID",
"edt_dev_spec_universe_title": "Universe", "edt_dev_spec_universe_title": "Universe",
"edt_dev_spec_useAPIv2_title": "Use API v2",
"edt_dev_spec_useEntertainmentAPI_title": "Use Hue Entertainment API", "edt_dev_spec_useEntertainmentAPI_title": "Use Hue Entertainment API",
"edt_dev_spec_useOrbSmoothing_title": "Use orb smoothing", "edt_dev_spec_useOrbSmoothing_title": "Use orb smoothing",
"edt_dev_spec_useRgbwProtocol_title": "Use RGBW protocol", "edt_dev_spec_useRgbwProtocol_title": "Use RGBW protocol",
@ -755,6 +760,8 @@
"edt_eff_ledlist": "LED List", "edt_eff_ledlist": "LED List",
"edt_eff_ledtest_header": "LED Test", "edt_eff_ledtest_header": "LED Test",
"edt_eff_ledtest_header_desc": "Rotating output: Red, Green, Blue, White, Black", "edt_eff_ledtest_header_desc": "Rotating output: Red, Green, Blue, White, Black",
"edt_eff_ledtest_seq_header": "LED Test - Sequence",
"edt_eff_ledtest_seq_header_desc": "Light up the LEDs in sequence",
"edt_eff_length": "Length", "edt_eff_length": "Length",
"edt_eff_lightclock_header": "Light Clock", "edt_eff_lightclock_header": "Light Clock",
"edt_eff_lightclock_header_desc": "A real clock as light! Adjust the colors of hours, minute, seconds. A optional 3/6/9/12 o'clock marker is also available. In case the clock is wrong, you need to check your system clock.", "edt_eff_lightclock_header_desc": "A real clock as light! Adjust the colors of hours, minute, seconds. A optional 3/6/9/12 o'clock marker is also available. In case the clock is wrong, you need to check your system clock.",
@ -1087,7 +1094,7 @@
"wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually", "wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually",
"wiz_cololight_title": "Cololight Wizard", "wiz_cololight_title": "Cololight Wizard",
"wiz_guideyou": "The $1 will guide you through the settings. Just press the button!", "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_blinkblue": "Let it light up",
"wiz_hue_clientkey": "Clientkey", "wiz_hue_clientkey": "Clientkey",
"wiz_hue_create_user": "Create new User", "wiz_hue_create_user": "Create new User",
"wiz_hue_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button. <br> 2. Provide a user ID, if you do not have one create a new one.", "wiz_hue_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button. <br> 2. Provide a user ID, if you do not have one create a new one.",
@ -1118,8 +1125,15 @@
"wiz_identify": "Identify", "wiz_identify": "Identify",
"wiz_identify_tip": "Identify configured device by lighting it up", "wiz_identify_tip": "Identify configured device by lighting it up",
"wiz_identify_light": "Identify $1", "wiz_identify_light": "Identify $1",
"wiz_layout": "Generate Layout",
"wiz_layout_tip": "Generate a layout for the configured device",
"wiz_ids_disabled": "Deactivated", "wiz_ids_disabled": "Deactivated",
"wiz_ids_entire": "Whole picture", "wiz_ids_entire": "Whole picture",
"wiz_nanoleaf_failure_auth_token": "Please press the Nanoleaf Power On/Off button within 30 seconds",
"wiz_nanoleaf_failure_auth_token_t": "User authorization token generating timeout",
"wiz_nanoleaf_press_onoff_button": "Please press the Power On/Off button on your Nanoleaf device for 5-7 seconds",
"wiz_nanoleaf_user_auth_intro": "The wizard supports you in generating a user authorization token required to allowing Hyperion to access the device.",
"wiz_nanoleaf_user_auth_title": "Authorization Token Generating Wizard",
"wiz_noLights": "No $1 found! Please get the lights connected to the network or configure them manually.", "wiz_noLights": "No $1 found! Please get the lights connected to the network or configure them manually.",
"wiz_pos": "Position/State", "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_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.",

View File

@ -327,7 +327,7 @@ $(document).ready(function () {
var saveOptions = conf_editor_screen.getValue(); var saveOptions = conf_editor_screen.getValue();
var instCaptOptions = window.serverConfig.instCapture; var instCaptOptions = window.serverConfig.instCapture;
instCaptOptions.systemEnable = true; instCaptOptions.systemEnable = saveOptions.framegrabber.enable;
saveOptions.instCapture = instCaptOptions; saveOptions.instCapture = instCaptOptions;
requestWriteConfig(saveOptions); requestWriteConfig(saveOptions);
@ -679,7 +679,7 @@ $(document).ready(function () {
var saveOptions = conf_editor_video.getValue(); var saveOptions = conf_editor_video.getValue();
var instCaptOptions = window.serverConfig.instCapture; var instCaptOptions = window.serverConfig.instCapture;
instCaptOptions.v4lEnable = true; instCaptOptions.v4lEnable = saveOptions.grabberV4L2.enable;
saveOptions.instCapture = instCaptOptions; saveOptions.instCapture = instCaptOptions;
requestWriteConfig(saveOptions); requestWriteConfig(saveOptions);
@ -805,7 +805,7 @@ $(document).ready(function () {
const saveOptions = conf_editor_audio.getValue(); const saveOptions = conf_editor_audio.getValue();
const instCaptOptions = window.serverConfig.instCapture; const instCaptOptions = window.serverConfig.instCapture;
instCaptOptions.audioEnable = true; instCaptOptions.audioEnable = saveOptions.grabberAudio.enable;
saveOptions.instCapture = instCaptOptions; saveOptions.instCapture = instCaptOptions;
requestWriteConfig(saveOptions); requestWriteConfig(saveOptions);

View File

@ -1021,11 +1021,6 @@ $(document).ready(function () {
var generalOptions = window.serverSchema.properties.device; var generalOptions = window.serverSchema.properties.device;
var ledType = $(this).val(); var ledType = $(this).val();
// philipshueentertainment backward fix
if (ledType == "philipshueentertainment")
ledType = "philipshue";
var specificOptions = window.serverSchema.properties.alldevices[ledType]; var specificOptions = window.serverSchema.properties.alldevices[ledType];
conf_editor = createJsonEditor('editor_container_leddevice', { conf_editor = createJsonEditor('editor_container_leddevice', {
@ -1055,18 +1050,22 @@ $(document).ready(function () {
// change save button state based on validation result // change save button state based on validation result
conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').prop('disabled', true) : $('#btn_submit_controller').prop('disabled', false); conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').prop('disabled', true) : $('#btn_submit_controller').prop('disabled', false);
// led controller sepecific wizards // LED controller specific wizards
$('#btn_wiz_holder').html(""); $('#btn_wiz_holder').html("");
$('#btn_led_device_wiz').off(); $('#btn_led_device_wiz').off();
if (ledType == "philipshue") { if (ledType == "philipshue") {
$('#root_specificOptions_useEntertainmentAPI').on("change", function () { var ledWizardType = ledType;
var ledWizardType = (this.checked) ? "philipshueentertainment" : ledType; var data = { type: ledWizardType };
var data = { type: ledWizardType }; var hue_title = 'wiz_hue_title';
var hue_title = (this.checked) ? 'wiz_hue_e_title' : 'wiz_hue_title'; changeWizard(data, hue_title, startWizardPhilipsHue);
changeWizard(data, hue_title, startWizardPhilipsHue); }
}); else if (ledType == "nanoleaf") {
$("#root_specificOptions_useEntertainmentAPI").trigger("change"); var ledWizardType = ledType;
var data = { type: ledWizardType };
var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
changeWizard(data, nanoleaf_user_auth_title, startWizardNanoleafUserAuth);
$('#btn_wiz_holder').hide();
} }
else if (ledType == "atmoorb") { else if (ledType == "atmoorb") {
var ledWizardType = (this.checked) ? "atmoorb" : ledType; var ledWizardType = (this.checked) ? "atmoorb" : ledType;
@ -1092,6 +1091,7 @@ $(document).ready(function () {
var colorOrderDefault = "rgb"; var colorOrderDefault = "rgb";
var filter = {}; var filter = {};
$('#btn_layout_controller').hide();
$('#btn_test_controller').hide(); $('#btn_test_controller').hide();
switch (ledType) { switch (ledType) {
@ -1349,6 +1349,13 @@ $(document).ready(function () {
if (host === "") { if (host === "") {
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1); conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1);
switch (ledType) {
case "nanoleaf":
$('#btn_wiz_holder').hide();
break;
default:
}
} }
else { else {
let params = {}; let params = {};
@ -1360,6 +1367,8 @@ $(document).ready(function () {
break; break;
case "nanoleaf": case "nanoleaf":
$('#btn_wiz_holder').show();
var token = conf_editor.getEditor("root.specificOptions.token").getValue(); var token = conf_editor.getEditor("root.specificOptions.token").getValue();
if (token === "") { if (token === "") {
return; return;
@ -1676,6 +1685,33 @@ $(document).ready(function () {
$("#leddevices").val(window.serverConfig.device.type); $("#leddevices").val(window.serverConfig.device.type);
$("#leddevices").trigger("change"); $("#leddevices").trigger("change");
// Generate layout for LED-Device
$("#btn_layout_controller").off().on("click", function () {
var ledType = $("#leddevices").val();
var isGenerated = false;
switch (ledType) {
case "nanoleaf":
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var ledDeviceProperties = devicesProperties[ledType][host];
if (ledDeviceProperties) {
var panelOrderTopDown = conf_editor.getEditor("root.specificOptions.panelOrderTopDown").getValue() === "top2down";
var panelOrderLeftRight = conf_editor.getEditor("root.specificOptions.panelOrderLeftRight").getValue() === "left2right";
var ledArray = nanoleafGeneratelayout(ledDeviceProperties.panelLayout, panelOrderTopDown, panelOrderLeftRight);
aceEdt.set(ledArray);
isGenerated = true;
}
break;
default:
}
if (isGenerated) {
showInfoDialog('success', "", $.i18n('conf_leds_layout_generation_success'));
} else {
showInfoDialog('error', "", $.i18n('conf_leds_layout_generation_error'));
}
});
// Identify/ Test LED-Device // Identify/ Test LED-Device
$("#btn_test_controller").off().on("click", function () { $("#btn_test_controller").off().on("click", function () {
var ledType = $("#leddevices").val(); var ledType = $("#leddevices").val();
@ -2155,6 +2191,7 @@ async function identify_device(type, params) {
} }
function updateElements(ledType, key) { function updateElements(ledType, key) {
var canLayout = false;
if (devicesProperties[ledType][key]) { if (devicesProperties[ledType][key]) {
var hardwareLedCount = 1; var hardwareLedCount = 1;
switch (ledType) { switch (ledType) {
@ -2173,18 +2210,11 @@ function updateElements(ledType, key) {
case "nanoleaf": case "nanoleaf":
var ledProperties = devicesProperties[ledType][key]; var ledProperties = devicesProperties[ledType][key];
if (ledProperties && ledProperties.panelLayout.layout) { if (ledProperties) {
//Identify non-LED type panels, e.g. Rhythm (1) and Shapes Controller (12) hardwareLedCount = ledProperties.ledCount;
var nonLedNum = 0; canLayout = true;
for (const panel of ledProperties.panelLayout.layout.positionData) {
if (panel.shapeType === 1 || panel.shapeType === 12) {
nonLedNum++;
}
}
hardwareLedCount = ledProperties.panelLayout.layout.numPanels - nonLedNum;
} }
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break; break;
case "udpraw": case "udpraw":
@ -2233,11 +2263,19 @@ function updateElements(ledType, key) {
} }
if (!conf_editor.validate().length) { if (!conf_editor.validate().length) {
if (canLayout) {
$("#btn_layout_controller").show();
$('#btn_layout_controller').prop('disabled', false);
} else {
$('#btn_layout_controller').hide();
}
if (!window.readOnlyMode) { if (!window.readOnlyMode) {
$('#btn_submit_controller').attr('disabled', false); $('#btn_submit_controller').attr('disabled', false);
} }
} }
else { else {
$('#btn_layout_controller').prop('disabled', true);
$('#btn_submit_controller').attr('disabled', true); $('#btn_submit_controller').attr('disabled', true);
} }
} }
@ -2415,4 +2453,149 @@ function updateElementsWled(ledType, key) {
} }
showInputOptionForItem(conf_editor, "root.specificOptions.segments", "switchOffOtherSegments", showAdditionalOptions); showInputOptionForItem(conf_editor, "root.specificOptions.segments", "switchOffOtherSegments", showAdditionalOptions);
} }
function sortByPanelCoordinates(arr, topToBottom, leftToRight) {
arr.sort((a, b) => {
//Nanoleaf corodinates start at bottom left, therefore reverse topToBottom
if (!topToBottom) {
if (a.y === b.y) {
if (leftToRight) {
return a.x - b.x;
} else {
return b.x - a.x;
}
} else {
return a.y - b.y;
}
}
else {
if (a.y === b.y) {
if (leftToRight) {
return a.x - b.x;
} else {
return b.x - a.x;
}
} else {
return b.y - a.y;
}
}
});
}
function rotateCoordinates(x, y, radians) {
var rotatedX = x * Math.cos(radians) - y * Math.sin(radians);
var rotatedY = x * Math.sin(radians) + y * Math.cos(radians);
return { x: rotatedX, y: rotatedY };
}
function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRight) {
// Dictionary for Nanoleaf shape types
let shapeTypes = {
0: { name: "LightsTriangle", led: true, sideLengthX: 150, sideLengthY: 150 },
1: { name: "LightsRythm", led: false, sideLengthX: 0, sideLengthY: 0 },
2: { name: "Square", led: true, sideLengthX: 100, sideLengthY: 100 },
3: { name: "SquareControllerMaster", led: true, sideLengthX: 100, sideLengthY: 100 },
4: { name: "SquareControllerPassive", led: true, sideLengthX: 100, sideLengthY: 100 },
5: { name: "PowerSupply", led: true, sideLengthX: 100, sideLengthY: 100 },
7: { name: "ShapesHexagon", led: true, sideLengthX: 67, sideLengthY: 67 },
8: { name: "ShapesTriangle", led: true, sideLengthX: 134, sideLengthY: 134 },
9: { name: "ShapesMiniTriangle", led: true, sideLengthX: 67, sideLengthY: 67 },
12: { name: "ShapesController", led: false, sideLengthX: 0, sideLengthY: 0 },
14: { name: "ElementsHexagon", led: true, sideLengthX: 134, sideLengthY: 134 },
15: { name: "ElementsHexagonCorner", led: true, sideLengthX: 33.5, sideLengthY: 58 },
16: { name: "LinesConnector", led: false, sideLengthX: 11, sideLengthY: 11 },
17: { name: "LightLines", led: true, sideLengthX: 154, sideLengthY: 154 },
18: { name: "LightLinesSingleZone", led: true, sideLengthX: 77, sideLengthY: 77 },
19: { name: "ControllerCap", led: false, sideLengthX: 11, sideLengthY: 11 },
20: { name: "PowerConnector", led: false, sideLengthX: 11, sideLengthY: 11 },
999: { name: "Unknown", led: true, sideLengthX: 100, sideLengthY: 100 }
};
let { globalOrientation, layout } = panelLayout;
var degreesToRotate = 0;
if (globalOrientation) {
degreesToRotate = globalOrientation.value;
}
//Align rotation degree to 15 degree steps
const degreeSteps = 15;
var degreeRounded = ((Math.round(degreesToRotate / degreeSteps) * degreeSteps) + 360) % 360;
//Nanoleaf orientation is counter-clockwise
degreeRounded *= -1;
// Convert degrees to radians
const radians = (degreeRounded * Math.PI) / 180;
//Reduce the capture area
const areaSizeFactor = 0.5;
var panelDataXY = [...layout.positionData];
panelDataXY.forEach(panel => {
if (shapeTypes[panel.shapeType] == undefined) {
panel.shapeType = 999;
}
panel.shapeName = shapeTypes[panel.shapeType].name;
panel.led = shapeTypes[panel.shapeType].led;
panel.areaWidth = shapeTypes[panel.shapeType].sideLengthX * areaSizeFactor;
panel.areaHeight = shapeTypes[panel.shapeType].sideLengthY * areaSizeFactor;
if (radians !== 0) {
var rotatedXY = rotateCoordinates(panel.x, panel.y, radians);
panel.x = Math.round(rotatedXY.x);
panel.y = Math.round(rotatedXY.y);
}
panel.maxX = panel.x + panel.areaWidth;
panel.maxY = panel.y + panel.areaHeight;
});
var minX = panelDataXY[0].x;
var maxX = panelDataXY[0].x;
var minY = panelDataXY[0].y;
var maxY = panelDataXY[0].y;
panelDataXY.forEach(panel => {
if (panel.maxX > maxX) {
maxX = panel.maxX;
}
if (panel.x < minX) {
minX = panel.x;
}
if (panel.maxY > maxY) {
maxY = panel.maxY;
}
if (panel.y < minY) {
minY = panel.y;
}
});
const width = Math.abs(maxX - minX);
const height = Math.abs(maxY - minY);
const scaleX = 1 / width;
const scaleY = 1 / height;
var layoutObjects = [];
var i = 0;
sortByPanelCoordinates(panelDataXY, panelOrderTopDown, panelOrderLeftRight);
panelDataXY.forEach(panel => {
if (panel.led) {
let layoutObject = {
name: i + "-" + panel.panelId,
hmin: Math.min(1, Math.max(0, (panel.x - minX) * scaleX)),
hmax: Math.min(1, Math.max(0, (panel.x - minX + panel.areaWidth) * scaleX)),
//Nanoleaf corodinates start at bottom left, therefore reverse vertical positioning
vmax: (1 - Math.min(1, Math.max(0, (panel.y - minY) * scaleY))),
vmin: (1 - Math.min(1, Math.max(0, (panel.y - minY + panel.areaHeight) * scaleY)))
};
layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
++i;
}
});
return layoutObjects;
}

File diff suppressed because it is too large Load Diff

View File

@ -95,7 +95,7 @@ else
fi fi
# Determine if PR number exists # Determine if PR number exists
pulls=$(request_call "$api_url/pulls") pulls=$(request_call "$api_url/pulls?state=open")
pr_exists=$(echo "$pulls" | tr '\r\n' ' ' | ${pythonCmd} -c """ pr_exists=$(echo "$pulls" | tr '\r\n' ' ' | ${pythonCmd} -c """
import json,sys import json,sys
@ -108,7 +108,7 @@ for i in data:
""" 2>/dev/null) """ 2>/dev/null)
if [ "$pr_exists" != "exists" ]; then if [ "$pr_exists" != "exists" ]; then
echo "---> Pull Request $pr_number not found -> abort" echo "---> Pull Request $pr_number not found as open PR -> abort"
exit 1 exit 1
fi fi
@ -124,7 +124,7 @@ for i in data:
""" 2>/dev/null) """ 2>/dev/null)
if [ -z "$head_sha" ]; then if [ -z "$head_sha" ]; then
echo "---> The specified PR #$pr_number has no longer any artifacts." echo "---> The specified PR #$pr_number has no longer any artifacts or has been closed."
echo "---> It may be older than 14 days. Ask the PR creator to recreate the artifacts at the following URL:" echo "---> It may be older than 14 days. Ask the PR creator to recreate the artifacts at the following URL:"
echo "---> https://github.com/hyperion-project/hyperion.ng/pull/$pr_number" echo "---> https://github.com/hyperion-project/hyperion.ng/pull/$pr_number"
exit 1 exit 1
@ -132,13 +132,13 @@ fi
if [ -z "$run_id" ]; then if [ -z "$run_id" ]; then
# Determine run_id from head_sha # Determine run_id from head_sha
runs=$(request_call "$api_url/actions/runs") runs=$(request_call "$api_url/actions/runs?head_sha=$head_sha")
run_id=$(echo "$runs" | tr '\r\n' ' ' | ${pythonCmd} -c """ run_id=$(echo "$runs" | tr '\r\n' ' ' | ${pythonCmd} -c """
import json,sys import json,sys
data = json.load(sys.stdin) data = json.load(sys.stdin)
for i in data['workflow_runs']: for i in data['workflow_runs']:
if i['head_sha'] == '"$head_sha"': if i['name'] == 'Hyperion PR Build':
print(i['id']) print(i['id'])
break break
""" 2>/dev/null) """ 2>/dev/null)

11
effects/ledtest-seq.json Normal file
View File

@ -0,0 +1,11 @@
{
"name" : "Led Test - Sequence",
"script" : "ledtest-seq.py",
"args" :
{
"sleepTime" : 0.5,
"smoothing-custom-settings" : false,
"smoothing-time_ms" : 500,
"smoothing-updateFrequency" : 20.0
}
}

39
effects/ledtest-seq.py Normal file
View File

@ -0,0 +1,39 @@
import hyperion
import time
# Get parameters
sleepTime = float(hyperion.args.get('sleepTime', 0.5))
def TestRgb( iteration ):
switcher = {
0: (255, 0, 0),
1: (0, 255, 0),
2: (0, 0, 255),
}
return switcher.get(iteration, (127,127,127) )
ledData = bytearray(hyperion.ledCount * (0,0,0) )
i = 0
while not hyperion.abort():
if i < hyperion.ledCount:
j = i % 3
rgb = TestRgb( j )
ledData[3*i+0] = rgb[0]
ledData[3*i+1] = rgb[1]
ledData[3*i+2] = rgb[2]
i += 1
else:
if i == hyperion.ledCount:
ledData = bytearray(hyperion.ledCount * (0,0,0) )
i += 1
else:
i = 0
hyperion.setColor (ledData)
time.sleep(sleepTime)

View File

@ -0,0 +1,56 @@
{
"type":"object",
"script" : "ledtest-seq.py",
"title":"edt_eff_ledtest_seq_header",
"required":true,
"properties":{
"sleepTime": {
"type": "number",
"title":"edt_eff_sleeptime",
"default": 0.5,
"minimum" : 0.01,
"maximum": 1,
"step": 0.01,
"append" : "edt_append_s",
"propertyOrder" : 1
},
"smoothing-custom-settings" :
{
"type" : "boolean",
"title" : "edt_eff_smooth_custom",
"default" : false,
"propertyOrder" : 2
},
"smoothing-time_ms" :
{
"type" : "integer",
"title" : "edt_eff_smooth_time_ms",
"minimum" : 25,
"maximum": 600,
"default" : 200,
"append" : "edt_append_ms",
"options": {
"dependencies": {
"smoothing-custom-settings": true
}
},
"propertyOrder" : 3
},
"smoothing-updateFrequency" :
{
"type" : "number",
"title" : "edt_eff_smooth_updateFrequency",
"minimum" : 1.0,
"maximum" : 100.0,
"default" : 25.0,
"append" : "edt_append_hz",
"options": {
"dependencies": {
"smoothing-custom-settings": true
}
},
"propertyOrder" : 4
}
},
"additionalProperties": false
}

View File

@ -9,6 +9,15 @@
// Constants // Constants
namespace { namespace {
const uint16_t RESOLUTION = 255; const uint16_t RESOLUTION = 255;
//Constants vuMeter
const QJsonArray DEFAULT_HOTCOLOR { 255,0,0 };
const QJsonArray DEFAULT_WARNCOLOR { 255,255,0 };
const QJsonArray DEFAULT_SAFECOLOR { 0,255,0 };
const int DEFAULT_WARNVALUE { 80 };
const int DEFAULT_SAFEVALUE { 45 };
const int DEFAULT_MULTIPLIER { 0 };
const int DEFAULT_TOLERANCE { 20 };
} }
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
@ -28,12 +37,12 @@ AudioGrabber::AudioGrabber()
, _deviceProperties() , _deviceProperties()
, _device("none") , _device("none")
, _hotColor(QColorConstants::Red) , _hotColor(QColorConstants::Red)
, _warnValue(80) , _warnValue(DEFAULT_WARNVALUE)
, _warnColor(QColorConstants::Yellow) , _warnColor(QColorConstants::Yellow)
, _safeValue(45) , _safeValue(DEFAULT_SAFEVALUE)
, _safeColor(QColorConstants::Green) , _safeColor(QColorConstants::Green)
, _multiplier(0) , _multiplier(DEFAULT_MULTIPLIER)
, _tolerance(20) , _tolerance(DEFAULT_TOLERANCE)
, _dynamicMultiplier(INT16_MAX) , _dynamicMultiplier(INT16_MAX)
, _started(false) , _started(false)
{ {
@ -61,18 +70,27 @@ void AudioGrabber::setDevice(const QString& device)
void AudioGrabber::setConfiguration(const QJsonObject& config) void AudioGrabber::setConfiguration(const QJsonObject& config)
{ {
QJsonArray hotColorArray = config["hotColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(0), QVariant(0) }))); QString audioEffect = config["audioEffect"].toString();
QJsonArray warnColorArray = config["warnColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(255), QVariant(0) }))); QJsonObject audioEffectConfig = config[audioEffect].toObject();
QJsonArray safeColorArray = config["safeColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(0), QVariant(255), QVariant(0) })));
_hotColor = QColor(hotColorArray.at(0).toInt(), hotColorArray.at(1).toInt(), hotColorArray.at(2).toInt()); if (audioEffect == "vuMeter")
_warnColor = QColor(warnColorArray.at(0).toInt(), warnColorArray.at(1).toInt(), warnColorArray.at(2).toInt()); {
_safeColor = QColor(safeColorArray.at(0).toInt(), safeColorArray.at(1).toInt(), safeColorArray.at(2).toInt()); QJsonArray hotColorArray = audioEffectConfig.value("hotColor").toArray(DEFAULT_HOTCOLOR);
QJsonArray warnColorArray = audioEffectConfig.value("warnColor").toArray(DEFAULT_WARNCOLOR);
QJsonArray safeColorArray = audioEffectConfig.value("safeColor").toArray(DEFAULT_SAFECOLOR);
_warnValue = config["warnValue"].toInt(80); _hotColor = QColor(hotColorArray.at(0).toInt(), hotColorArray.at(1).toInt(), hotColorArray.at(2).toInt());
_safeValue = config["safeValue"].toInt(45); _warnColor = QColor(warnColorArray.at(0).toInt(), warnColorArray.at(1).toInt(), warnColorArray.at(2).toInt());
_multiplier = config["multiplier"].toDouble(0); _safeColor = QColor(safeColorArray.at(0).toInt(), safeColorArray.at(1).toInt(), safeColorArray.at(2).toInt());
_tolerance = config["tolerance"].toInt(20); _warnValue = audioEffectConfig["warnValue"].toInt(DEFAULT_WARNVALUE);
_safeValue = audioEffectConfig["safeValue"].toInt(DEFAULT_SAFEVALUE);
_multiplier = audioEffectConfig["multiplier"].toDouble(DEFAULT_MULTIPLIER);
_tolerance = audioEffectConfig["tolerance"].toInt(DEFAULT_MULTIPLIER);
}
else
{
Error(_log, "Unknow Audio-Effect: \"%s\" configured", QSTRING_CSTR(audioEffect));
}
} }
void AudioGrabber::resetMultiplier() void AudioGrabber::resetMultiplier()

View File

@ -21,19 +21,19 @@ using namespace semver;
// Constants // Constants
namespace { namespace {
const char DEFAULT_VERSION[] = "2.0.0-alpha.8"; const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
} //End of constants } //End of constants
QJsonObject SettingsManager::schemaJson; QJsonObject SettingsManager::schemaJson;
SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode) SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode)
: QObject(parent) : QObject(parent)
, _log(Logger::getInstance("SETTINGSMGR", "I"+QString::number(instance))) , _log(Logger::getInstance("SETTINGSMGR", "I" + QString::number(instance)))
, _instance(instance) , _instance(instance)
, _sTable(new SettingsTable(instance, this)) , _sTable(new SettingsTable(instance, this))
, _configVersion(DEFAULT_VERSION) , _configVersion(DEFAULT_VERSION)
, _previousVersion(DEFAULT_VERSION) , _previousVersion(DEFAULT_VERSION)
, _readonlyMode(readonlyMode) , _readonlyMode(readonlyMode)
{ {
_sTable->setReadonlyMode(_readonlyMode); _sTable->setReadonlyMode(_readonlyMode);
// get schema // get schema
@ -723,6 +723,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
} }
//Migration steps for versions <= 2.0.13 //Migration steps for versions <= 2.0.13
_previousVersion = targetVersion;
targetVersion.setVersion("2.0.13"); targetVersion.setVersion("2.0.13");
if (_previousVersion <= targetVersion) if (_previousVersion <= targetVersion)
{ {
@ -738,12 +739,12 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
{ {
QString type = newDeviceConfig["type"].toString(); QString type = newDeviceConfig["type"].toString();
const QStringList serialDevices {"adalight", "dmx", "atmo", "sedu", "tpm2", "karate"}; const QStringList serialDevices{ "adalight", "dmx", "atmo", "sedu", "tpm2", "karate" };
if ( serialDevices.contains(type )) if (serialDevices.contains(type))
{ {
if (!newDeviceConfig.contains("rateList")) if (!newDeviceConfig.contains("rateList"))
{ {
newDeviceConfig["rateList"] = "CUSTOM"; newDeviceConfig["rateList"] = "CUSTOM";
migrated = true; migrated = true;
} }
} }
@ -774,6 +775,126 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
} }
} }
} }
//Migration steps for versions <= 2.0.16
_previousVersion = targetVersion;
targetVersion.setVersion("2.0.16");
if (_previousVersion <= targetVersion)
{
Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
// Have Hostname/IP-address separate from port for LED-Devices
if (config.contains("device"))
{
QJsonObject newDeviceConfig = config["device"].toObject();
if (newDeviceConfig.contains("type"))
{
QString type = newDeviceConfig["type"].toString();
if (type == "philipshue")
{
if (newDeviceConfig.contains("groupId"))
{
if (newDeviceConfig["groupId"].isDouble())
{
int groupID = newDeviceConfig["groupId"].toInt();
newDeviceConfig["groupId"] = QString::number(groupID);
migrated = true;
}
}
if (newDeviceConfig.contains("lightIds"))
{
QJsonArray lightIds = newDeviceConfig.value("lightIds").toArray();
// Iterate through the JSON array and update integer values to strings
for (int i = 0; i < lightIds.size(); ++i) {
QJsonValue value = lightIds.at(i);
if (value.isDouble())
{
int lightId = value.toInt();
lightIds.replace(i, QString::number(lightId));
migrated = true;
}
}
newDeviceConfig["lightIds"] = lightIds;
}
}
if (type == "nanoleaf")
{
if (newDeviceConfig.contains("panelStartPos"))
{
newDeviceConfig.remove("panelStartPos");
migrated = true;
}
if (newDeviceConfig.contains("panelOrderTopDown"))
{
int panelOrderTopDown;
if (newDeviceConfig["panelOrderTopDown"].isDouble())
{
panelOrderTopDown = newDeviceConfig["panelOrderTopDown"].toInt();
}
else
{
panelOrderTopDown = newDeviceConfig["panelOrderTopDown"].toString().toInt();
}
newDeviceConfig.remove("panelOrderTopDown");
if (panelOrderTopDown == 0)
{
newDeviceConfig["panelOrderTopDown"] = "top2down";
migrated = true;
}
else
{
if (panelOrderTopDown == 1)
{
newDeviceConfig["panelOrderTopDown"] = "bottom2up";
migrated = true;
}
}
}
if (newDeviceConfig.contains("panelOrderLeftRight"))
{
int panelOrderLeftRight;
if (newDeviceConfig["panelOrderLeftRight"].isDouble())
{
panelOrderLeftRight = newDeviceConfig["panelOrderLeftRight"].toInt();
}
else
{
panelOrderLeftRight = newDeviceConfig["panelOrderLeftRight"].toString().toInt();
}
newDeviceConfig.remove("panelOrderLeftRight");
if (panelOrderLeftRight == 0)
{
newDeviceConfig["panelOrderLeftRight"] = "left2right";
migrated = true;
}
else
{
if (panelOrderLeftRight == 1)
{
newDeviceConfig["panelOrderLeftRight"] = "right2left";
migrated = true;
}
}
}
}
}
if (migrated)
{
config["device"] = newDeviceConfig;
Debug(_log, "LED-Device records migrated");
}
}
}
} }
} }
return migrated; return migrated;

View File

@ -4,6 +4,7 @@
//std includes //std includes
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <cmath>
// Qt includes // Qt includes
#include <QNetworkReply> #include <QNetworkReply>
@ -21,88 +22,79 @@
// Constants // Constants
namespace { namespace {
const bool verbose = false; const bool verbose = false;
const bool verbose3 = false; const bool verbose3 = false;
// Configuration settings // Configuration settings
const char CONFIG_HOST[] = "host"; const char CONFIG_HOST[] = "host";
const char CONFIG_AUTH_TOKEN[] = "token"; const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness"; const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness"; const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown"; const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight"; const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
const char CONFIG_PANEL_START_POS[] = "panelStartPos";
const bool DEFAULT_IS_RESTORE_STATE = true; const bool DEFAULT_IS_RESTORE_STATE = true;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true; const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 100; const int BRI_MAX = 100;
// Panel configuration settings // Panel configuration settings
const char PANEL_LAYOUT[] = "layout"; const char PANEL_GLOBALORIENTATION[] = "globalOrientation";
const char PANEL_NUM[] = "numPanels"; const char PANEL_GLOBALORIENTATION_VALUE[] = "value";
const char PANEL_ID[] = "panelId"; const char PANEL_LAYOUT[] = "layout";
const char PANEL_POSITIONDATA[] = "positionData"; const char PANEL_NUM[] = "numPanels";
const char PANEL_SHAPE_TYPE[] = "shapeType"; const char PANEL_ID[] = "panelId";
const char PANEL_POS_X[] = "x"; const char PANEL_POSITIONDATA[] = "positionData";
const char PANEL_POS_Y[] = "y"; const char PANEL_SHAPE_TYPE[] = "shapeType";
const char PANEL_POS_X[] = "x";
const char PANEL_POS_Y[] = "y";
// List of State Information // List of State Information
const char STATE_ON[] = "on"; const char STATE_ON[] = "on";
const char STATE_BRI[] = "brightness"; const char STATE_BRI[] = "brightness";
const char STATE_HUE[] = "hue"; const char STATE_HUE[] = "hue";
const char STATE_SAT[] = "sat"; const char STATE_SAT[] = "sat";
const char STATE_CT[] = "ct"; const char STATE_CT[] = "ct";
const char STATE_COLORMODE[] = "colorMode"; const char STATE_COLORMODE[] = "colorMode";
const QStringList COLOR_MODES {"hs", "ct", "effect"}; const QStringList COLOR_MODES{ "hs", "ct", "effect" };
const char STATE_VALUE[] = "value"; const char STATE_VALUE[] = "value";
// Device Data elements // Device Data elements
const char DEV_DATA_NAME[] = "name"; const char DEV_DATA_NAME[] = "name";
const char DEV_DATA_MODEL[] = "model"; const char DEV_DATA_MODEL[] = "model";
const char DEV_DATA_MANUFACTURER[] = "manufacturer"; const char DEV_DATA_MANUFACTURER[] = "manufacturer";
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion"; const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
// Nanoleaf Stream Control elements // Nanoleaf Stream Control elements
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222; const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
// Nanoleaf OpenAPI URLs // Nanoleaf OpenAPI URLs
const int API_DEFAULT_PORT = 16021; const int API_DEFAULT_PORT = 16021;
const char API_BASE_PATH[] = "/api/v1/%1/"; const char API_BASE_PATH[] = "/api/v1/%1/";
const char API_ROOT[] = ""; const char API_ROOT[] = "";
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}"; const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
const char API_STATE[] = "state"; const char API_STATE[] = "state";
const char API_PANELLAYOUT[] = "panelLayout"; const char API_PANELLAYOUT[] = "panelLayout";
const char API_EFFECT[] = "effects"; const char API_EFFECT[] = "effects";
const char API_IDENTIFY[] = "identify";
const char API_ADD_USER[] = "new";
const char API_EFFECT_SELECT[] = "select";
const char API_EFFECT_SELECT[] = "select"; //Nanoleaf Control data stream
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
//Nanoleaf Control data stream // Nanoleaf ssdp services
const int STREAM_FRAME_PANEL_NUM_SIZE = 2; const char SSDP_ID[] = "ssdp:all";
const int STREAM_FRAME_PANEL_INFO_SIZE = 8; const char SSDP_FILTER_HEADER[] = "ST";
const char SSDP_NANOLEAF[] = "nanoleaf:nl*";
const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
const double ROTATION_STEPS_DEGREE = 15.0;
// Nanoleaf ssdp services
const char SSDP_ID[] = "ssdp:all";
const char SSDP_FILTER_HEADER[] = "ST";
const char SSDP_NANOLEAF[] = "nanoleaf:nl*";
const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
} //End of constants } //End of constants
// Nanoleaf Panel Shapetypes
enum SHAPETYPES {
TRIANGLE = 0,
RHYTM = 1,
SQUARE = 2,
CONTROL_SQUARE_PRIMARY = 3,
CONTROL_SQUARE_PASSIVE = 4,
POWER_SUPPLY= 5,
HEXAGON_SHAPES = 7,
TRIANGE_SHAPES = 8,
MINI_TRIANGE_SHAPES = 8,
SHAPES_CONTROLLER = 12
};
// Nanoleaf external control versions // Nanoleaf external control versions
enum EXTCONTROLVERSIONS { enum EXTCONTROLVERSIONS {
EXTCTRLVER_V1 = 1, EXTCTRLVER_V1 = 1,
@ -111,18 +103,16 @@ enum EXTCONTROLVERSIONS {
LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig) LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
: ProviderUdp(deviceConfig) : ProviderUdp(deviceConfig)
, _restApi(nullptr) , _restApi(nullptr)
, _apiPort(API_DEFAULT_PORT) , _apiPort(API_DEFAULT_PORT)
, _topDown(true) , _topDown(true)
, _leftRight(true) , _leftRight(true)
, _startPos(0) , _extControlVersion(EXTCTRLVER_V2)
, _endPos(0) , _panelLedCount(0)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
{ {
#ifdef ENABLE_MDNS #ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif #endif
} }
@ -139,7 +129,7 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf()
bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
{ {
bool isInitOK {false}; bool isInitOK{ false };
// Overwrite non supported/required features // Overwrite non supported/required features
setLatchTime(0); setLatchTime(0);
@ -150,9 +140,9 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored."); Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored.");
} }
DebugIf(verbose,_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData()); DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
if ( ProviderUdp::init(deviceConfig) ) if (ProviderUdp::init(deviceConfig))
{ {
//Set hostname as per configuration and default port //Set hostname as per configuration and default port
_hostName = deviceConfig[CONFIG_HOST].toString(); _hostName = deviceConfig[CONFIG_HOST].toString();
@ -164,36 +154,66 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); _isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX); _brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) ); Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName));
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState); Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite); Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness); Debug(_log, "Set Brightness to : %d", _brightness);
// Read panel organisation configuration // Read panel organisation configuration
if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString()) _topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString("top2down") == "top2down";
{ _leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString("left2right") == "left2right";
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString().toInt() == 0;
}
else
{
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toInt() == 0;
}
if (deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].isString())
{
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString().toInt() == 0;
}
else
{
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0;
}
_startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0);
isInitOK = true; isInitOK = true;
} }
return isInitOK; return isInitOK;
} }
int LedDeviceNanoleaf::getHwLedCount(const QJsonObject& jsonLayout) const
{
int hwLedCount{ 0 };
const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
for (const QJsonValue& value : positionData)
{
QJsonObject panelObj = value.toObject();
int panelId = panelObj[PANEL_ID].toInt();
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
DebugIf(verbose, _log, "Panel [%d] - Type: [%d]", panelId, panelshapeType);
if (hasLEDs(static_cast<SHAPETYPES>(panelshapeType)))
{
++hwLedCount;
}
else
{
DebugIf(verbose, _log, "Rhythm/Shape/Lines Controller panel skipped.");
}
}
return hwLedCount;
}
bool LedDeviceNanoleaf::hasLEDs(const SHAPETYPES& panelshapeType) const
{
bool hasLED {true};
// Skip non LED panel types
switch (panelshapeType)
{
case SHAPES_CONTROLLER:
case LINES_CONECTOR:
case CONTROLLER_CAP:
case POWER_CONNECTOR:
case RHYTM:
DebugIf(verbose, _log, "Rhythm/Shape/Lines Controller panel skipped.");
hasLED = false;
break;
default:
break;
}
return hasLED;
}
bool LedDeviceNanoleaf::initLedsConfiguration() bool LedDeviceNanoleaf::initLedsConfiguration()
{ {
bool isInitOK = true; bool isInitOK = true;
@ -206,7 +226,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
if (response.error()) if (response.error())
{ {
QString errorReason = QString("Getting device details failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Getting device details failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason ); this->setInError(errorReason);
isInitOK = false; isInitOK = false;
} }
else else
@ -225,37 +245,71 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
// Get panel details from /panelLayout/layout // Get panel details from /panelLayout/layout
QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject(); QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject();
const QJsonObject globalOrientation = jsonPanelLayout[PANEL_GLOBALORIENTATION].toObject();
int orientation = globalOrientation[PANEL_GLOBALORIENTATION_VALUE].toInt();
int degreesToRotate {orientation};
bool isRotated {false};
if (degreesToRotate > 0)
{
isRotated = true;
int degreeRounded = static_cast<int>(round(degreesToRotate / ROTATION_STEPS_DEGREE) * ROTATION_STEPS_DEGREE);
degreesToRotate = (degreeRounded +360) % 360;
}
//Nanoleaf orientation is counter-clockwise
degreesToRotate *= -1;
double radians = (degreesToRotate * std::acos(-1)) / 180;
DebugIf(verbose, _log, "globalOrientation: %d, degreesToRotate: %d, radians: %0.2f", orientation, degreesToRotate, radians);
QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject(); QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject();
_panelLedCount = getHwLedCount(jsonLayout);
_devConfig["hardwareLedCount"] = _panelLedCount;
int panelNum = jsonLayout[PANEL_NUM].toInt(); int panelNum = jsonLayout[PANEL_NUM].toInt();
const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray(); const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
std::map<int, std::map<int, int>> panelMap; std::map<int, std::map<int, int>> panelMap;
// Loop over all children. // Loop over all children.
for(const QJsonValue & value : positionData) for (const QJsonValue& value : positionData)
{ {
QJsonObject panelObj = value.toObject(); QJsonObject panelObj = value.toObject();
int panelId = panelObj[PANEL_ID].toInt(); int panelId = panelObj[PANEL_ID].toInt();
int panelX = panelObj[PANEL_POS_X].toInt();
int panelY = panelObj[PANEL_POS_Y].toInt();
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt(); int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
int posX = panelObj[PANEL_POS_X].toInt();
int posY = panelObj[PANEL_POS_Y].toInt();
DebugIf(verbose,_log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType); int panelX;
int panelY;
// Skip Rhythm and Shapes controller panels if (isRotated)
if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
{ {
panelMap[panelY][panelX] = panelId; panelX = static_cast<int>(round(posX * cos(radians) - posY * sin(radians)));
panelY = static_cast<int>(round(posX * sin(radians) + posY * cos(radians)));
} }
else else
{ // Reset non support/required features {
Info(_log, "Rhythm/Shape Controller panel skipped."); panelX = posX;
panelY = posY;
}
if (hasLEDs(static_cast<SHAPETYPES>(panelshapeType)))
{
panelMap[panelY][panelX] = panelId;
DebugIf(verbose, _log, "Use Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
}
else
{
DebugIf(verbose, _log, "Skip Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
} }
} }
// Travers panels top down // Travers panels top down
_panelIds.clear();
for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY) for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY)
{ {
// Sort panels left to right // Sort panels left to right
@ -263,7 +317,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
{ {
for (auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX) for (auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX)
{ {
DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second); DebugIf(verbose, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second);
if (_topDown) if (_topDown)
{ {
@ -280,7 +334,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
// Sort panels right to left // Sort panels right to left
for (auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX) for (auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX)
{ {
DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second); DebugIf(verbose, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second);
if (_topDown) if (_topDown)
{ {
@ -294,27 +348,22 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
} }
} }
this->_panelLedCount = _panelIds.size();
_devConfig["hardwareLedCount"] = _panelLedCount;
Debug(_log, "PanelsNum : %d", panelNum); Debug(_log, "PanelsNum : %d", panelNum);
Debug(_log, "PanelLedCount : %d", _panelLedCount); Debug(_log, "PanelLedCount : %d", _panelLedCount);
Debug(_log, "Sort Top>Down : %d", _topDown);
Debug(_log, "Sort Left>Right: %d", _leftRight);
DebugIf(verbose, _log, "PanelMap size : %d", panelMap.size());
DebugIf(verbose, _log, "PanelIds count : %d", _panelIds.size());
// Check. if enough panels were found. // Check. if enough panels were found.
int configuredLedCount = this->getLedCount(); int configuredLedCount = this->getLedCount();
_endPos = _startPos + configuredLedCount - 1;
Debug(_log, "Sort Top>Down : %d", _topDown);
Debug(_log, "Sort Left>Right: %d", _leftRight);
Debug(_log, "Start Panel Pos: %d", _startPos);
Debug(_log, "End Panel Pos : %d", _endPos);
if (_panelLedCount < configuredLedCount) if (_panelLedCount < configuredLedCount)
{ {
QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!") QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!")
.arg(_panelLedCount) .arg(_panelLedCount)
.arg(configuredLedCount); .arg(configuredLedCount);
this->setInError(errorReason); this->setInError(errorReason, false);
isInitOK = false; isInitOK = false;
} }
else else
@ -324,15 +373,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
Info(_log, "%s: More panels [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount); Info(_log, "%s: More panels [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount);
} }
// Check, if start position + number of configured LEDs is greater than number of panels available //Check that panel count matches working list created for processing
if (_endPos >= _panelLedCount) if (_panelLedCount != _panelIds.size())
{ {
QString errorReason = QString("Start panel [%1] out of range. Start panel position can be max [%2] given [%3] panel available!") QString errorReason = QString("Number of available panels [%1] do not match panel-ID look-up list [%2]!")
.arg(_startPos).arg(_panelLedCount - configuredLedCount).arg(_panelLedCount); .arg(_panelLedCount)
.arg(_panelIds.size());
this->setInError(errorReason); this->setInError(errorReason, false);
isInitOK = false; isInitOK = false;
} }
} }
} }
return isInitOK; return isInitOK;
@ -340,7 +390,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
bool LedDeviceNanoleaf::openRestAPI() bool LedDeviceNanoleaf::openRestAPI()
{ {
bool isInitOK {true}; bool isInitOK{ true };
if (_restApi == nullptr) if (_restApi == nullptr)
{ {
@ -360,7 +410,7 @@ int LedDeviceNanoleaf::open()
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{ {
if ( openRestAPI() ) if (openRestAPI())
{ {
// Read LedDevice configuration and validate against device configuration // Read LedDevice configuration and validate against device configuration
if (initLedsConfiguration()) if (initLedsConfiguration())
@ -415,7 +465,7 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
MdnsServiceRegister::getServiceType(_activeDeviceType), MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT DEFAULT_DISCOVER_TIMEOUT
); );
#else #else
QString discoveryMethod("ssdp"); QString discoveryMethod("ssdp");
deviceList = discover(); deviceList = discover();
@ -424,25 +474,25 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList); devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered; return devicesDiscovered;
} }
QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
{ {
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties; QJsonObject properties;
_hostName = params[CONFIG_HOST].toString(""); _hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT; _apiPort = API_DEFAULT_PORT;
_authToken = params["token"].toString(""); _authToken = params[CONFIG_AUTH_TOKEN].toString("");
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{ {
if ( openRestAPI() ) if (openRestAPI())
{ {
QString filter = params["filter"].toString(""); QString filter = params["filter"].toString("");
_restApi->setPath(filter); _restApi->setPath(filter);
@ -453,7 +503,14 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
{ {
Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
} }
properties.insert("properties", response.getBody().object()); QJsonObject propertiesDetails = response.getBody().object();
if (!propertiesDetails.isEmpty())
{
QJsonObject jsonLayout = propertiesDetails.value(API_PANELLAYOUT).toObject().value(PANEL_LAYOUT).toObject();
_panelLedCount = getHwLedCount(jsonLayout);
propertiesDetails.insert("ledCount", getHwLedCount(jsonLayout));
}
properties.insert("properties", propertiesDetails);
} }
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
@ -463,21 +520,19 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
void LedDeviceNanoleaf::identify(const QJsonObject& params) void LedDeviceNanoleaf::identify(const QJsonObject& params)
{ {
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
_hostName = params[CONFIG_HOST].toString(""); _hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) _apiPort = API_DEFAULT_PORT;
_authToken = params["token"].toString(""); _authToken = params[CONFIG_AUTH_TOKEN].toString("");
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{ {
if ( openRestAPI() ) if (openRestAPI())
{ {
_restApi->setPath("identify"); _restApi->setPath(API_IDENTIFY);
// Perform request
httpResponse response = _restApi->put(); httpResponse response = _restApi->put();
if (response.error()) if (response.error())
{ {
@ -487,6 +542,36 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
} }
} }
QJsonObject LedDeviceNanoleaf::addAuthorization(const QJsonObject& params)
{
Debug(_log, "params: [%s]", QJsonDocument(params).toJson(QJsonDocument::Compact).constData());
QJsonObject responseBody;
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
Info(_log, "Generate user authorization token for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
if (openRestAPI())
{
_restApi->setBasePath(QString(API_BASE_PATH).arg(API_ADD_USER));
httpResponse response = _restApi->post();
if (response.error())
{
Warning(_log, "%s generating user authorization token failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
else
{
Debug(_log, "Generated user authorization token: \"%s\"", QSTRING_CSTR(response.getBody().object().value("auth_token").toString()));
responseBody = response.getBody().object();
}
}
}
return responseBody;
}
bool LedDeviceNanoleaf::powerOn() bool LedDeviceNanoleaf::powerOn()
{ {
bool on = false; bool on = false;
@ -496,12 +581,12 @@ bool LedDeviceNanoleaf::powerOn()
{ {
QJsonObject newState; QJsonObject newState;
QJsonObject onValue { {STATE_VALUE, true} }; QJsonObject onValue{ {STATE_VALUE, true} };
newState.insert(STATE_ON, onValue); newState.insert(STATE_ON, onValue);
if ( _isBrightnessOverwrite) if (_isBrightnessOverwrite)
{ {
QJsonObject briValue { {STATE_VALUE, _brightness} }; QJsonObject briValue{ {STATE_VALUE, _brightness} };
newState.insert(STATE_BRI, briValue); newState.insert(STATE_BRI, briValue);
} }
@ -511,9 +596,10 @@ bool LedDeviceNanoleaf::powerOn()
if (response.error()) if (response.error())
{ {
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason ); this->setInError(errorReason);
on = false; on = false;
} else { }
else {
on = true; on = true;
} }
@ -529,7 +615,7 @@ bool LedDeviceNanoleaf::powerOff()
{ {
QJsonObject newState; QJsonObject newState;
QJsonObject onValue { {STATE_VALUE, false} }; QJsonObject onValue{ {STATE_VALUE, false} };
newState.insert(STATE_ON, onValue); newState.insert(STATE_ON, onValue);
//Power-off the Nanoleaf device physically //Power-off the Nanoleaf device physically
@ -538,7 +624,7 @@ bool LedDeviceNanoleaf::powerOff()
if (response.error()) if (response.error())
{ {
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason ); this->setInError(errorReason);
off = false; off = false;
} }
} }
@ -549,12 +635,12 @@ bool LedDeviceNanoleaf::storeState()
{ {
bool rc = true; bool rc = true;
if ( _isRestoreOrigState ) if (_isRestoreOrigState)
{ {
_restApi->setPath(API_STATE); _restApi->setPath(API_STATE);
httpResponse response = _restApi->get(); httpResponse response = _restApi->get();
if ( response.error() ) if (response.error())
{ {
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
setInError(errorReason); setInError(errorReason);
@ -563,7 +649,7 @@ bool LedDeviceNanoleaf::storeState()
else else
{ {
_originalStateProperties = response.getBody().object(); _originalStateProperties = response.getBody().object();
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject isOn = _originalStateProperties.value(STATE_ON).toObject(); QJsonObject isOn = _originalStateProperties.value(STATE_ON).toObject();
if (!isOn.isEmpty()) if (!isOn.isEmpty())
@ -579,7 +665,7 @@ bool LedDeviceNanoleaf::storeState()
_originalColorMode = _originalStateProperties[STATE_COLORMODE].toString(); _originalColorMode = _originalStateProperties[STATE_COLORMODE].toString();
switch(COLOR_MODES.indexOf(_originalColorMode)) { switch (COLOR_MODES.indexOf(_originalColorMode)) {
case 0: case 0:
{ {
// hs // hs
@ -611,7 +697,7 @@ bool LedDeviceNanoleaf::storeState()
_restApi->setPath(API_EFFECT); _restApi->setPath(API_EFFECT);
httpResponse responseEffects = _restApi->get(); httpResponse responseEffects = _restApi->get();
if ( responseEffects.error() ) if (responseEffects.error())
{ {
QString errorReason = QString("Storing device state failed with error: '%1'").arg(responseEffects.getErrorReason()); QString errorReason = QString("Storing device state failed with error: '%1'").arg(responseEffects.getErrorReason());
setInError(errorReason); setInError(errorReason);
@ -620,7 +706,7 @@ bool LedDeviceNanoleaf::storeState()
else else
{ {
QJsonObject effects = responseEffects.getBody().object(); QJsonObject effects = responseEffects.getBody().object();
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
_originalEffect = effects[API_EFFECT_SELECT].toString(); _originalEffect = effects[API_EFFECT_SELECT].toString();
_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*"; _originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
} }
@ -641,21 +727,21 @@ bool LedDeviceNanoleaf::restoreState()
{ {
bool rc = true; bool rc = true;
if ( _isRestoreOrigState ) if (_isRestoreOrigState)
{ {
QJsonObject newState; QJsonObject newState;
switch(COLOR_MODES.indexOf(_originalColorMode)) { switch (COLOR_MODES.indexOf(_originalColorMode)) {
case 0: case 0:
{ // hs { // hs
QJsonObject hueValue { {STATE_VALUE, _originalHue} }; QJsonObject hueValue{ {STATE_VALUE, _originalHue} };
newState.insert(STATE_HUE, hueValue); newState.insert(STATE_HUE, hueValue);
QJsonObject satValue { {STATE_VALUE, _originalSat} }; QJsonObject satValue{ {STATE_VALUE, _originalSat} };
newState.insert(STATE_SAT, satValue); newState.insert(STATE_SAT, satValue);
break; break;
} }
case 1: case 1:
{ // ct { // ct
QJsonObject ctValue { {STATE_VALUE, _originalCt} }; QJsonObject ctValue{ {STATE_VALUE, _originalCt} };
newState.insert(STATE_CT, ctValue); newState.insert(STATE_CT, ctValue);
break; break;
} }
@ -667,37 +753,38 @@ bool LedDeviceNanoleaf::restoreState()
newEffect[API_EFFECT_SELECT] = _originalEffect; newEffect[API_EFFECT_SELECT] = _originalEffect;
_restApi->setPath(API_EFFECT); _restApi->setPath(API_EFFECT);
httpResponse response = _restApi->put(newEffect); httpResponse response = _restApi->put(newEffect);
if ( response.error() ) if (response.error())
{ {
Warning (_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); Warning(_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
} }
} else { }
Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType)); else {
Warning(_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false; _originalIsOn = false;
} }
break; break;
} }
default: default:
Warning (_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType)); Warning(_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType));
rc = false; rc = false;
} }
if (!_originalIsDynEffect) if (!_originalIsDynEffect)
{ {
QJsonObject briValue { {STATE_VALUE, _originalBri} }; QJsonObject briValue{ {STATE_VALUE, _originalBri} };
newState.insert(STATE_BRI, briValue); newState.insert(STATE_BRI, briValue);
} }
QJsonObject onValue { {STATE_VALUE, _originalIsOn} }; QJsonObject onValue{ {STATE_VALUE, _originalIsOn} };
newState.insert(STATE_ON, onValue); newState.insert(STATE_ON, onValue);
_restApi->setPath(API_STATE); _restApi->setPath(API_STATE);
httpResponse response = _restApi->put(newState); httpResponse response = _restApi->put(newState);
if ( response.error() ) if (response.error())
{ {
Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); Warning(_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
rc = false; rc = false;
} }
} }
@ -722,7 +809,7 @@ bool LedDeviceNanoleaf::changeToExternalControlMode(QJsonDocument& resp)
if (response.error()) if (response.error())
{ {
QString errorReason = QString("Change to external control mode failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Change to external control mode failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason ); this->setInError(errorReason);
} }
else else
{ {
@ -758,29 +845,24 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
ColorRgb color; ColorRgb color;
//Maintain LED counter independent from PanelCounter for (int panelCounter = 0; panelCounter < _panelLedCount; ++panelCounter)
int ledCounter = 0;
for (int panelCounter = 0; panelCounter < _panelLedCount; panelCounter++)
{ {
// Set panelID
int panelID = _panelIds[panelCounter]; int panelID = _panelIds[panelCounter];
qToBigEndian<quint16>(static_cast<quint16>(panelID), udpbuffer.data() + i);
i += 2;
// Set panels configured // Set panel's color LEDs
if (panelCounter >= _startPos && panelCounter <= _endPos) { if (panelCounter < this->getLedCount()) {
color = static_cast<ColorRgb>(ledValues.at(ledCounter)); color = static_cast<ColorRgb>(ledValues.at(panelCounter));
++ledCounter;
} }
else else
{ {
// Set panels not configured to black // Set panels not configured to black
color = ColorRgb::BLACK; color = ColorRgb::BLACK;
DebugIf(verbose3, _log, "[%d] >= panelLedCount [%d] => Set to BLACK", panelCounter, _panelLedCount); DebugIf(verbose3, _log, "[%u] >= panelLedCount [%u] => Set to BLACK", panelCounter, _panelLedCount);
} }
// Set panelID
qToBigEndian<quint16>(static_cast<quint16>(panelID), udpbuffer.data() + i);
i += 2;
// Set panel's color LEDs
udpbuffer[i++] = static_cast<char>(color.red); udpbuffer[i++] = static_cast<char>(color.red);
udpbuffer[i++] = static_cast<char>(color.green); udpbuffer[i++] = static_cast<char>(color.green);
udpbuffer[i++] = static_cast<char>(color.blue); udpbuffer[i++] = static_cast<char>(color.blue);
@ -799,7 +881,7 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
if (verbose3) if (verbose3)
{ {
Debug(_log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%d], Bytes to send [%d]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i); Debug(_log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%d], Bytes to send [%d]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i);
Debug( _log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64))); Debug(_log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
} }
retVal = writeBytes(udpbuffer); retVal = writeBytes(udpbuffer);

View File

@ -87,6 +87,20 @@ public:
/// ///
void identify(const QJsonObject& params) override; void identify(const QJsonObject& params) override;
/// @brief Add an API-token to the Nanoleaf device
///
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP",
/// }
///@endcode
///
/// @param[in] params Parameters to query device
/// @return A JSON structure holding the authorization keys
///
QJsonObject addAuthorization(const QJsonObject& params) override;
protected: protected:
/// ///
@ -147,6 +161,27 @@ protected:
private: private:
// Nanoleaf Panel Shapetypes
enum SHAPETYPES {
TRIANGLE = 0,
RHYTM = 1,
SQUARE = 2,
CONTROL_SQUARE_PRIMARY = 3,
CONTROL_SQUARE_PASSIVE = 4,
POWER_SUPPLY = 5,
HEXAGON_SHAPES = 7,
TRIANGE_SHAPES = 8,
MINI_TRIANGE_SHAPES = 9,
SHAPES_CONTROLLER = 12,
ELEMENTS_HEXAGONS = 14,
ELEMENTS_HEXAGONS_CORNER = 15,
LINES_CONECTOR = 16,
LIGHT_LINES = 17,
LIGHT_LINES_SINGLZONE = 18,
CONTROLLER_CAP = 19,
POWER_CONNECTOR = 20
};
/// ///
/// @brief Initialise the access to the REST-API wrapper /// @brief Initialise the access to the REST-API wrapper
/// ///
@ -182,6 +217,20 @@ private:
/// ///
QJsonArray discover(); QJsonArray discover();
///
/// @brief Get number of panels that can be used as LEds.
///
/// @return Number of usable LED panels
///
int getHwLedCount(const QJsonObject& jsonLayout) const;
///
/// @brief Check, if panelshape type has LEDs
///
/// @return True, if panel shape type has LEDs
///
bool hasLEDs(const SHAPETYPES& panelshapeType) const;
///REST-API wrapper ///REST-API wrapper
ProviderRestApi* _restApi; ProviderRestApi* _restApi;
int _apiPort; int _apiPort;
@ -189,8 +238,6 @@ private:
bool _topDown; bool _topDown;
bool _leftRight; bool _leftRight;
int _startPos;
int _endPos;
//Nanoleaf device details //Nanoleaf device details
QString _deviceModel; QString _deviceModel;

File diff suppressed because it is too large Load Diff

View File

@ -16,31 +16,6 @@
#include "ProviderRestApi.h" #include "ProviderRestApi.h"
#include "ProviderUdpSSL.h" #include "ProviderUdpSSL.h"
//Streaming message header and payload definition
const uint8_t HEADER[] =
{
'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol
0x01, 0x00, //version 1.0
0x01, //sequence number 1
0x00, 0x00, //Reserved write 0s
0x01, //xy Brightness
0x00, // Reserved, write 0s
};
const uint8_t PAYLOAD_PER_LIGHT[] =
{
0x01, 0x00, 0x06, //light ID
//color: 16 bpc
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
/*
(message.R >> 8) & 0xff, message.R & 0xff,
(message.G >> 8) & 0xff, message.G & 0xff,
(message.B >> 8) & 0xff, message.B & 0xff
*/
};
/** /**
* A XY color point in the color space of the hue system without brightness. * A XY color point in the color space of the hue system without brightness.
*/ */
@ -145,12 +120,18 @@ public:
/// Constructs the light. /// Constructs the light.
/// ///
/// @param log the logger /// @param log the logger
/// @param bridge the bridge /// @param useApiV2 make use of Hue API version 2
/// @param id the light id /// @param id the light id
/// @param lightAttributes the light's attributes as provied by the Hue Bridge
/// @param onBlackTimeToPowerOff Timeframe of Black output that triggers powering off the light
/// @param onBlackTimeToPowerOn Timeframe of non Black output that triggers powering on the light
/// ///
PhilipsHueLight(Logger* log, int id, QJsonObject values, int ledidx, PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, const QJsonObject& lightAttributes,
int onBlackTimeToPowerOff, int onBlackTimeToPowerOff,
int onBlackTimeToPowerOn); int onBlackTimeToPowerOn);
void setDeviceDetails(const QJsonObject& details);
void setEntertainmentSrvDetails(const QJsonObject& details);
/// ///
/// @param on /// @param on
@ -167,7 +148,14 @@ public:
/// ///
void setColor(const CiColor& color); void setColor(const CiColor& color);
int getId() const; QString getId() const;
QString getdeviceId() const;
QString getProduct() const;
QString getModel() const;
QString getName() const;
QString getArcheType() const;
int getMaxSegments() const;
bool getOnOffState() const; bool getOnOffState() const;
int getTransitionTime() const; int getTransitionTime() const;
@ -179,7 +167,7 @@ public:
CiColorTriangle getColorSpace() const; CiColorTriangle getColorSpace() const;
void saveOriginalState(const QJsonObject& values); void saveOriginalState(const QJsonObject& values);
QString getOriginalState() const; QJsonObject getOriginalState() const;
bool isBusy(); bool isBusy();
bool isBlack(bool isBlack); bool isBlack(bool isBlack);
@ -189,24 +177,30 @@ public:
private: private:
Logger* _log; Logger* _log;
/// light id bool _useApiV2;
int _id;
int _ledidx; QString _id;
QString _deviceId;
QString _product;
QString _model;
QString _name;
QString _archeType;
QString _gamutType;
int _maxSegments;
bool _on; bool _on;
int _transitionTime; int _transitionTime;
CiColor _color; CiColor _color;
bool _hasColor; bool _hasColor;
/// darkes blue color in hue lamp GAMUT = black /// darkes blue color in hue lamp GAMUT = black
CiColor _colorBlack; CiColor _colorBlack;
/// The model id of the hue lamp which is used to determine the color space.
QString _modelId;
QString _lightname;
CiColorTriangle _colorSpace; CiColorTriangle _colorSpace;
/// The json string of the original state. /// The json string of the original state.
QJsonObject _originalStateJSON; QJsonObject _originalStateJSON;
QString _originalState; QJsonObject _originalState;
CiColor _originalColor; CiColor _originalColor;
qint64 _lastSendColorTime; qint64 _lastSendColorTime;
qint64 _lastBlackTime; qint64 _lastBlackTime;
@ -242,23 +236,40 @@ public:
QJsonDocument get(const QString& route); QJsonDocument get(const QString& route);
/// ///
/// @brief Perform a REST-API POST /// @brief Perform a REST-API GET
/// ///
/// @param route the route of the POST request. /// @param routeElements the route's elements of the GET request.
/// @param content the content of the POST request.
/// ///
QJsonDocument put(const QString& route, const QString& content, bool supressError = false); /// @return the content of the GET request.
///
QJsonDocument get(const QStringList& routeElements);
QJsonDocument getLightState( int lightId); ///
void setLightState( int lightId = 0, const QString &state = ""); /// @brief Perform a REST-API PUT
///
/// @param routeElements the route's elements of the PUT request.
/// @param content the content of the PUT request.
/// @param supressError Treat an error as a warning
///
/// @return the content of the PUT request.
///
QJsonDocument put(const QStringList& routeElements, const QJsonObject& content, bool supressError = false);
QMap<int,QJsonObject> getLightMap() const; QJsonDocument retrieveBridgeDetails();
QJsonObject getDeviceDetails(const QString& deviceId);
QJsonObject getEntertainmentSrvDetails(const QString& deviceId);
QMap<int,QJsonObject> getGroupMap() const; QJsonObject getLightDetails(const QString& lightId);
QJsonDocument setLightState(const QString& lightId, const QJsonObject& state);
QString getGroupName(int groupId = 0) const; QMap<QString,QJsonObject> getDevicesMap() const;
QMap<QString,QJsonObject> getLightMap() const;
QMap<QString,QJsonObject> getGroupMap() const;
QMap<QString,QJsonObject> getEntertainmentMap() const;
QJsonArray getGroupLights(int groupId = 0) const; QString getGroupName(const QString& groupId) const;
QStringList getGroupLights(const QString& groupId) const;
int getGroupChannelsCount(const QString& groupId) const;
protected: protected:
@ -289,7 +300,7 @@ protected:
/// ///
/// @param[in] response from Hue-Bridge in JSON-format /// @param[in] response from Hue-Bridge in JSON-format
/// @param[in] suppressError Treat an error as a warning /// @param[in] suppressError Treat an error as a warning
/// ///
/// return True, Hue Bridge reports error /// return True, Hue Bridge reports error
/// ///
bool checkApiError(const QJsonDocument& response, bool supressError = false); bool checkApiError(const QJsonDocument& response, bool supressError = false);
@ -338,23 +349,41 @@ protected:
/// ///
QJsonObject addAuthorization(const QJsonObject& params) override; QJsonObject addAuthorization(const QJsonObject& params) override;
bool isApiEntertainmentReady(const QString& apiVersion);
bool isAPIv2Ready (int swVersion);
int getFirmwareVerion() { return _deviceFirmwareVersion; }
void setBridgeDetails( const QJsonDocument &doc, bool isLogging = false );
void setBaseApiEnvironment(bool apiV2 = true, const QString& path = "");
QJsonDocument getGroupDetails( const QString& groupId );
QJsonDocument setGroupState( const QString& groupId, bool state);
bool isStreamOwner(const QString &streamOwner) const;
bool initDevicesMap();
bool initLightsMap();
bool initGroupsMap();
bool initEntertainmentSrvsMap();
void log(const char* msg, const char* type, ...) const;
bool configureSsl();
const int * getCiphersuites() const override;
///REST-API wrapper ///REST-API wrapper
ProviderRestApi* _restApi; ProviderRestApi* _restApi;
int _apiPort; int _apiPort;
/// User name for the API ("newdeveloper") /// User name for the API ("newdeveloper")
QString _authToken; QString _authToken;
QString _applicationID;
bool _useHueEntertainmentAPI; bool _useEntertainmentAPI;
bool _useApiV2;
bool _isAPIv2Ready;
QJsonDocument getGroupState( int groupId ); bool _isDiyHue;
QJsonDocument setGroupState( int groupId, bool state);
bool isStreamOwner(const QString &streamOwner) const;
bool initMaps();
void log(const char* msg, const char* type, ...) const;
const int * getCiphersuites() const override;
private: private:
@ -364,16 +393,25 @@ private:
/// ///
/// @return A JSON structure holding a list of devices found /// @return A JSON structure holding a list of devices found
/// ///
QJsonArray discover(); QJsonArray discoverSsdp();
QJsonDocument getAllBridgeInfos(); QJsonDocument retrieveDeviceDetails(const QString& deviceId = "");
void setBridgeConfig( const QJsonDocument &doc ); QJsonDocument retrieveLightDetails(const QString& lightId = "");
QJsonDocument retrieveGroupDetails(const QString& groupId = "");
QJsonDocument retrieveEntertainmentSrvDetails(const QString& deviceId = "");
bool retrieveApplicationId();
void setDevicesMap( const QJsonDocument &doc );
void setLightsMap( const QJsonDocument &doc ); void setLightsMap( const QJsonDocument &doc );
void setGroupMap( const QJsonDocument &doc ); void setGroupMap( const QJsonDocument &doc );
void setEntertainmentSrvMap( const QJsonDocument &doc );
//Philips Hue Bridge details //Philips Hue Bridge details
QString _deviceName;
QString _deviceBridgeId;
QString _deviceModel; QString _deviceModel;
QString _deviceFirmwareVersion; int _deviceFirmwareVersion;
QString _deviceAPIVersion; QString _deviceAPIVersion;
uint _api_major; uint _api_major;
@ -382,8 +420,12 @@ private:
bool _isHueEntertainmentReady; bool _isHueEntertainmentReady;
QMap<int,QJsonObject> _lightsMap; QMap<QString,QJsonObject> _devicesMap;
QMap<int,QJsonObject> _groupsMap; QMap<QString,QJsonObject> _lightsMap;
QMap<QString,QJsonObject> _groupsMap;
QMap<QString,QJsonObject> _entertainmentMap;
int _lightsCount;
}; };
/** /**
@ -440,7 +482,7 @@ public:
/// ///
/// @return Number of device's LEDs /// @return Number of device's LEDs
/// ///
unsigned int getLightsCount() const { return _lightsCount; } int getLightsCount() const { return _lightsCount; }
void setOnOffState(PhilipsHueLight& light, bool on, bool force = false); void setOnOffState(PhilipsHueLight& light, bool on, bool force = false);
void setTransitionTime(PhilipsHueLight& light); void setTransitionTime(PhilipsHueLight& light);
@ -547,18 +589,18 @@ private:
bool setLights(); bool setLights();
/// creates new PhilipsHueLight(s) based on user lightid with bridge feedback /// creates new PhilipsHueLight(s) based on user lightId with bridge feedback
/// ///
/// @param map Map of lightid/value pairs of bridge /// @param map Map of lightId/value pairs of bridge
/// ///
bool updateLights(const QMap<int, QJsonObject> &map); bool updateLights(const QMap<QString, QJsonObject> &map);
/// ///
/// @brief Set the number of LEDs supported by the device. /// @brief Set the number of LEDs supported by the device.
/// ///
/// @rparam[in] Number of device's LEDs /// @rparam[in] Number of device's LEDs
// //
void setLightsCount( unsigned int lightsCount); void setLightsCount(int lightsCount);
bool openStream(); bool openStream();
bool getStreamGroupState(); bool getStreamGroupState();
@ -566,10 +608,8 @@ private:
bool startStream(); bool startStream();
bool stopStream(); bool stopStream();
void writeStream(bool flush = false);
int writeSingleLights(const std::vector<ColorRgb>& ledValues); int writeSingleLights(const std::vector<ColorRgb>& ledValues);
int writeStreamData(const std::vector<ColorRgb>& ledValues, bool flush = false);
QByteArray prepareStreamData() const;
/// ///
bool _switchOffOnBlack; bool _switchOffOnBlack;
@ -582,12 +622,15 @@ private:
bool _isInitLeds; bool _isInitLeds;
/// Array of the light ids. /// Array of the light ids.
std::vector<int> _lightIds; QStringList _lightIds;
/// Array to save the lamps. /// Array to save the lamps.
std::vector<PhilipsHueLight> _lights; std::vector<PhilipsHueLight> _lights;
int _lightsCount; int _lightsCount;
int _groupId; int _channelsCount;
QString _groupId;
QString _groupName;
QString _streamOwner;
int _blackLightsTimeout; int _blackLightsTimeout;
double _blackLevel; double _blackLevel;
@ -595,15 +638,5 @@ private:
int _onBlackTimeToPowerOn; int _onBlackTimeToPowerOn;
bool _candyGamma; bool _candyGamma;
// TODO: Check what is the correct class
uint32_t _handshake_timeout_min;
uint32_t _handshake_timeout_max;
bool _stopConnection;
QString _groupName;
QString _streamOwner;
qint64 _lastConfirm;
int _lastId;
bool _groupStreamState; bool _groupStreamState;
}; };

View File

@ -2,11 +2,18 @@
#include "ProviderRestApi.h" #include "ProviderRestApi.h"
// Qt includes // Qt includes
#include <QObject>
#include <QEventLoop> #include <QEventLoop>
#include <QNetworkReply> #include <QNetworkReply>
#include <QByteArray> #include <QByteArray>
#include <QJsonObject> #include <QJsonObject>
#include <QList>
#include <QHash>
#include <QFile>
#include <QSslSocket>
//std includes //std includes
#include <iostream> #include <iostream>
#include <chrono> #include <chrono>
@ -30,12 +37,12 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
: _log(Logger::getInstance("LEDDEVICE")) : _log(Logger::getInstance("LEDDEVICE"))
, _networkManager(nullptr) , _networkManager(nullptr)
, _requestTimeout(DEFAULT_REST_TIMEOUT) , _requestTimeout(DEFAULT_REST_TIMEOUT)
,_isSeflSignedCertificateAccpeted(false)
{ {
_networkManager = new QNetworkAccessManager(); _networkManager = new QNetworkAccessManager();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
_networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); _networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
#endif #endif
_apiUrl.setScheme(scheme); _apiUrl.setScheme(scheme);
_apiUrl.setHost(host); _apiUrl.setHost(host);
_apiUrl.setPort(port); _apiUrl.setPort(port);
@ -46,7 +53,7 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
: ProviderRestApi(scheme, host, port, "") {} : ProviderRestApi(scheme, host, port, "") {}
ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath) ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath)
: ProviderRestApi("http", host, port, basePath) {} : ProviderRestApi((port == 443) ? "https" : "http", host, port, basePath) {}
ProviderRestApi::ProviderRestApi(const QString& host, int port) ProviderRestApi::ProviderRestApi(const QString& host, int port)
: ProviderRestApi(host, port, "") {} : ProviderRestApi(host, port, "") {}
@ -59,18 +66,33 @@ ProviderRestApi::~ProviderRestApi()
delete _networkManager; delete _networkManager;
} }
void ProviderRestApi::setScheme(const QString& scheme)
{
_apiUrl.setScheme(scheme);
}
void ProviderRestApi::setUrl(const QUrl& url) void ProviderRestApi::setUrl(const QUrl& url)
{ {
_apiUrl = url; _apiUrl = url;
_basePath = url.path(); _basePath = url.path();
} }
void ProviderRestApi::setBasePath(const QStringList& pathElements)
{
setBasePath(pathElements.join(ONE_SLASH));
}
void ProviderRestApi::setBasePath(const QString& basePath) void ProviderRestApi::setBasePath(const QString& basePath)
{ {
_basePath.clear(); _basePath.clear();
appendPath(_basePath, basePath); appendPath(_basePath, basePath);
} }
void ProviderRestApi::clearBasePath()
{
_basePath.clear();
}
void ProviderRestApi::setPath(const QStringList& pathElements) void ProviderRestApi::setPath(const QStringList& pathElements)
{ {
_path.clear(); _path.clear();
@ -83,6 +105,11 @@ void ProviderRestApi::setPath(const QString& path)
appendPath(_path, path); appendPath(_path, path);
} }
void ProviderRestApi::clearPath()
{
_path.clear();
}
void ProviderRestApi::appendPath(const QString& path) void ProviderRestApi::appendPath(const QString& path)
{ {
appendPath(_path, path); appendPath(_path, path);
@ -204,6 +231,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
QDateTime start = QDateTime::currentDateTime(); QDateTime start = QDateTime::currentDateTime();
QString opCode; QString opCode;
QNetworkReply* reply; QNetworkReply* reply;
switch (operation) { switch (operation) {
case QNetworkAccessManager::GetOperation: case QNetworkAccessManager::GetOperation:
opCode = "GET"; opCode = "GET";
@ -255,11 +283,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
response.setHttpStatusCode(httpStatusCode); response.setHttpStatusCode(httpStatusCode);
response.setNetworkReplyError(reply->error()); response.setNetworkReplyError(reply->error());
response.setHeaders(reply->rawHeaderPairs());
if (reply->error() == QNetworkReply::NoError) if (reply->error() == QNetworkReply::NoError)
{ {
QByteArray replyData = reply->readAll(); QByteArray replyData = reply->readAll();
if (!replyData.isEmpty()) if (!replyData.isEmpty())
{ {
QJsonParseError error; QJsonParseError error;
@ -284,40 +312,40 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
else else
{ {
QString errorReason; QString errorReason;
if (httpStatusCode > 0) { if (reply->error() == QNetworkReply::OperationCanceledError)
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); {
QString advise; errorReason = "Network request timeout error";
switch ( httpStatusCode ) {
case HttpStatusCode::BadRequest:
advise = "Check Request Body";
break;
case HttpStatusCode::UnAuthorized:
advise = "Check Authentication Token (API Key)";
break;
case HttpStatusCode::Forbidden:
advise = "No permission to access the given resource";
break;
case HttpStatusCode::NotFound:
advise = "Check Resource given";
break;
default:
advise = httpReason;
break;
}
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
} }
else else
{ {
if (reply->error() == QNetworkReply::OperationCanceledError) if (httpStatusCode > 0) {
{ QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
errorReason = "Network request timeout error"; QString advise;
switch ( httpStatusCode ) {
case HttpStatusCode::BadRequest:
advise = "Check Request Body";
break;
case HttpStatusCode::UnAuthorized:
advise = "Check Authorization Token (API Key)";
break;
case HttpStatusCode::Forbidden:
advise = "No permission to access the given resource";
break;
case HttpStatusCode::NotFound:
advise = "Check Resource given";
break;
default:
advise = httpReason;
break;
}
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
} }
else else
{ {
errorReason = reply->errorString(); errorReason = reply->errorString();
} }
} }
response.setError(true); response.setError(true);
response.setErrorReason(errorReason); response.setErrorReason(errorReason);
} }
@ -344,3 +372,121 @@ void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray &
{ {
_networkRequestHeaders.setRawHeader(headerName, headerValue); _networkRequestHeaders.setRawHeader(headerName, headerValue);
} }
void httpResponse::setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs)
{
_responseHeaders.clear();
for (const auto &item: pairs)
{
_responseHeaders[item.first] = item.second;
}
}
QByteArray httpResponse::getHeader(const QByteArray header) const
{
return _responseHeaders.value(header);
}
bool ProviderRestApi::setCaCertificate(const QString& caFileName)
{
bool rc {false};
/// Add our own CA to the default SSL configuration
QSslConfiguration configuration = QSslConfiguration::defaultConfiguration();
QFile caFile (caFileName);
if (!caFile.open(QIODevice::ReadOnly))
{
Error(_log,"Unable to open CA-Certificate file: %s", QSTRING_CSTR(caFileName));
return false;
}
QSslCertificate cert (&caFile);
caFile.close();
QList<QSslCertificate> allowedCAs;
allowedCAs << cert;
configuration.setCaCertificates(allowedCAs);
QSslConfiguration::setDefaultConfiguration(configuration);
#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
{
QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors, Qt::UniqueConnection );
_networkManager->connectToHostEncrypted(_apiUrl.host(), _apiUrl.port(), configuration);
rc = true;
}
#endif
return rc;
}
void ProviderRestApi::acceptSelfSignedCertificates(bool isAccepted)
{
_isSeflSignedCertificateAccpeted = isAccepted;
}
void ProviderRestApi::setAlternateServerIdentity(const QString& serverIdentity)
{
_serverIdentity = serverIdentity;
}
QString ProviderRestApi::getAlternateServerIdentity() const
{
return _serverIdentity;
}
bool ProviderRestApi::checkServerIdentity(const QSslConfiguration& sslConfig) const
{
bool isServerIdentified {false};
// Perform common name validation
QSslCertificate serverCertificate = sslConfig.peerCertificate();
QStringList commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName);
if ( commonName.contains(getAlternateServerIdentity(), Qt::CaseInsensitive) )
{
isServerIdentified = true;
}
return isServerIdentified;
}
void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
{
int ignoredErrorCount {0};
for (const QSslError &error : errors)
{
bool ignoreSslError{false};
switch (error.error()) {
case QSslError::HostNameMismatch :
if (checkServerIdentity(reply->sslConfiguration()) )
{
ignoreSslError = true;
}
break;
case QSslError::SelfSignedCertificate :
if (_isSeflSignedCertificateAccpeted)
{
ignoreSslError = true;
}
break;
default:
break;
}
if (ignoreSslError)
{
++ignoredErrorCount;
}
else
{
Debug (_log,"SSL Error occured: [%d] %s ",error.error(), QSTRING_CSTR(error.errorString()));
}
}
if (ignoredErrorCount == errors.size())
{
reply->ignoreSslErrors();
}
}

View File

@ -10,12 +10,13 @@
#include <QUrlQuery> #include <QUrlQuery>
#include <QJsonDocument> #include <QJsonDocument>
#include <QFile>
#include <QBasicTimer> #include <QBasicTimer>
#include <QTimerEvent> #include <QTimerEvent>
#include <chrono> #include <chrono>
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 1000 }; constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 2000 };
//Set QNetworkReply timeout without external timer //Set QNetworkReply timeout without external timer
//https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer //https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer
@ -28,7 +29,7 @@ public:
enum HandleMethod { Abort, Close }; enum HandleMethod { Abort, Close };
ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) : ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) :
QObject(reply), m_method(method), m_timedout(false) QObject(reply), m_method(method), m_timedout(false)
{ {
Q_ASSERT(reply); Q_ASSERT(reply);
if (reply && reply->isRunning()) { if (reply && reply->isRunning()) {
@ -87,6 +88,10 @@ public:
QJsonDocument getBody() const { return _responseBody; } QJsonDocument getBody() const { return _responseBody; }
void setBody(const QJsonDocument& body) { _responseBody = body; } void setBody(const QJsonDocument& body) { _responseBody = body; }
QByteArray getHeader(const QByteArray header) const;
void setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs);
QString getErrorReason() const { return _errorReason; } QString getErrorReason() const { return _errorReason; }
void setErrorReason(const QString& errorReason) { _errorReason = errorReason; } void setErrorReason(const QString& errorReason) { _errorReason = errorReason; }
@ -99,6 +104,8 @@ public:
private: private:
QJsonDocument _responseBody {}; QJsonDocument _responseBody {};
QHash<QByteArray, QByteArray> _responseHeaders;
bool _hasError = false; bool _hasError = false;
QString _errorReason; QString _errorReason;
@ -131,6 +138,7 @@ class ProviderRestApi : public QObject
public: public:
///
/// @brief Constructor of the REST-API wrapper /// @brief Constructor of the REST-API wrapper
/// ///
ProviderRestApi(); ProviderRestApi();
@ -176,6 +184,20 @@ public:
/// ///
virtual ~ProviderRestApi() override; virtual ~ProviderRestApi() override;
///
/// @brief Set the API's scheme
///
/// @param[in] scheme
///
void setScheme(const QString& scheme);
///
/// @brief Get the API's scheme
///
/// return schme
///
QString getScheme() { return _apiUrl.scheme(); }
/// ///
/// @brief Set an API's host /// @brief Set an API's host
/// ///
@ -190,6 +212,13 @@ public:
/// ///
void setPort(const int port) { _apiUrl.setPort(port); } void setPort(const int port) { _apiUrl.setPort(port); }
///
/// @brief Get the API's port
///
/// return port
///
int getPort() { return _apiUrl.port(); }
/// ///
/// @brief Set an API's url /// @brief Set an API's url
/// ///
@ -204,6 +233,13 @@ public:
/// ///
QUrl getUrl() const; QUrl getUrl() const;
///
/// @brief Set an API's base path (the stable path element before addressing resources)
///
/// @param[in] pathElements to form a path, e.g. (clip,v2,resource) results in "/clip/v2/resource"
///
void setBasePath(const QStringList& pathElements);
/// ///
/// @brief Set an API's base path (the stable path element before addressing resources) /// @brief Set an API's base path (the stable path element before addressing resources)
/// ///
@ -211,6 +247,11 @@ public:
/// ///
void setBasePath(const QString& basePath); void setBasePath(const QString& basePath);
///
/// @brief Clear an API's base path (the stable path element before addressing resources)
///
void clearBasePath();
/// ///
/// @brief Set an API's path to address resources /// @brief Set an API's path to address resources
/// ///
@ -218,12 +259,18 @@ public:
/// ///
void setPath(const QString& path); void setPath(const QString& path);
///
/// @brief Set an API's path to address resources /// @brief Set an API's path to address resources
/// ///
/// @param[in] pathElements to form a path, e.g. (lights,1,state) results in "/lights/1/state/" /// @param[in] pathElements to form a path, e.g. (lights,1,state) results in "/lights/1/state/"
/// ///
void setPath(const QStringList& pathElements); void setPath(const QStringList& pathElements);
///
/// @brief Clear an API's path
///
void clearPath();
/// ///
/// @brief Append an API's path element to path set before /// @brief Append an API's path element to path set before
/// ///
@ -252,6 +299,10 @@ public:
/// ///
void setQuery(const QUrlQuery& query); void setQuery(const QUrlQuery& query);
QString getBasePath() {return _basePath;}
QString getPath() {return _path;}
/// ///
/// @brief Execute GET request /// @brief Execute GET request
/// ///
@ -359,6 +410,14 @@ public:
/// @param[in] timeout in milliseconds. /// @param[in] timeout in milliseconds.
void setTransferTimeout(std::chrono::milliseconds timeout = DEFAULT_REST_TIMEOUT) { _requestTimeout = timeout; } void setTransferTimeout(std::chrono::milliseconds timeout = DEFAULT_REST_TIMEOUT) { _requestTimeout = timeout; }
bool setCaCertificate(const QString& caFileName);
void acceptSelfSignedCertificates(bool accept);
void setAlternateServerIdentity(const QString& serverIdentity);
QString getAlternateServerIdentity() const;
/// ///
/// @brief Set the common logger for LED-devices. /// @brief Set the common logger for LED-devices.
/// ///
@ -366,6 +425,10 @@ public:
/// ///
void setLogger(Logger* log) { _log = log; } void setLogger(Logger* log) { _log = log; }
protected slots:
/// Handle the SSLErrors
void onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors);
private: private:
/// ///
@ -379,9 +442,11 @@ private:
httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {}); httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {});
bool checkServerIdentity(const QSslConfiguration& sslConfig) const;
Logger* _log; Logger* _log;
// QNetworkAccessManager object for sending REST-requests. /// QNetworkAccessManager object for sending REST-requests.
QNetworkAccessManager* _networkManager; QNetworkAccessManager* _networkManager;
std::chrono::milliseconds _requestTimeout; std::chrono::milliseconds _requestTimeout;
@ -394,6 +459,9 @@ private:
QUrlQuery _query; QUrlQuery _query;
QNetworkRequest _networkRequestHeaders; QNetworkRequest _networkRequestHeaders;
QString _serverIdentity;
bool _isSeflSignedCertificateAccpeted;
}; };
#endif // PROVIDERRESTKAPI_H #endif // PROVIDERRESTKAPI_H

View File

@ -150,6 +150,11 @@ const int *ProviderUdpSSL::getCiphersuites() const
return mbedtls_ssl_list_ciphersuites(); return mbedtls_ssl_list_ciphersuites();
} }
void ProviderUdpSSL::setPSKidentity(const QString& pskIdentity)
{
_psk_identity = pskIdentity;
}
bool ProviderUdpSSL::initNetwork() bool ProviderUdpSSL::initNetwork()
{ {
if ((!_isDeviceReady || _streamPaused) && _streamReady) if ((!_isDeviceReady || _streamPaused) && _streamReady)
@ -334,6 +339,11 @@ void ProviderUdpSSL::freeSSLConnection()
} }
} }
void ProviderUdpSSL::writeBytes(QByteArray data, bool flush)
{
writeBytes(static_cast<uint>(data.size()), reinterpret_cast<unsigned char*>(data.data()), flush);
}
void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data, bool flush) void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data, bool flush)
{ {
if (!_streamReady || _streamPaused) if (!_streamReady || _streamPaused)

View File

@ -100,6 +100,14 @@ protected:
/// ///
void stopConnection(); void stopConnection();
///
/// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched.
///
/// @param[in] data The data
///
void writeBytes(QByteArray data, bool flush = false);
/// ///
/// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the /// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched. /// values are latched.
@ -116,6 +124,8 @@ protected:
/// ///
virtual const int * getCiphersuites() const; virtual const int * getCiphersuites() const;
void setPSKidentity(const QString& pskIdentity);
private: private:
bool initConnection(); bool initConnection();

View File

@ -72,41 +72,28 @@
"propertyOrder": 7 "propertyOrder": 7
}, },
"panelOrderTopDown": { "panelOrderTopDown": {
"type": "integer", "type": "string",
"title": "edt_dev_spec_order_top_down_title", "title": "edt_dev_spec_order_top_down_title",
"enum": [ 0, 1 ], "enum": [ "top2down", "bottom2up" ],
"default": 0, "default": "top2down",
"required": true, "required": true,
"options": { "options": {
"enum_titles": [ "edt_conf_enum_top_down", "edt_conf_enum_bottom_up" ] "enum_titles": [ "edt_conf_enum_top_down", "edt_conf_enum_bottom_up" ]
}, },
"minimum": 0,
"maximum": 1,
"access": "advanced", "access": "advanced",
"propertyOrder": 8 "propertyOrder": 8
}, },
"panelOrderLeftRight": { "panelOrderLeftRight": {
"type": "integer", "type": "string",
"title": "edt_dev_spec_order_left_right_title", "title": "edt_dev_spec_order_left_right_title",
"enum": [ 0, 1 ], "enum": [ "left2right", "right2left" ],
"default": 0, "default": "left2right",
"required": true, "required": true,
"options": { "options": {
"enum_titles": [ "edt_conf_enum_left_right", "edt_conf_enum_right_left" ] "enum_titles": [ "edt_conf_enum_left_right", "edt_conf_enum_right_left" ]
}, },
"minimum": 0,
"maximum": 1,
"access": "advanced", "access": "advanced",
"propertyOrder": 9 "propertyOrder": 9
},
"panelStartPos": {
"type": "integer",
"title": "edt_dev_spec_panel_start_position",
"step": 1,
"minimum": 0,
"default": 0,
"access": "advanced",
"propertyOrder": 10
} }
}, },
"additionalProperties": true "additionalProperties": true

View File

@ -35,26 +35,45 @@
}, },
"propertyOrder": 4 "propertyOrder": 4
}, },
"useAPIv2": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_useAPIv2_title",
"default": false,
"options": {
"hidden": true
},
"access": "expert",
"propertyOrder": 5
},
"useEntertainmentAPI": { "useEntertainmentAPI": {
"type": "boolean", "type": "boolean",
"format": "checkbox", "format": "checkbox",
"title": "edt_dev_spec_useEntertainmentAPI_title", "title": "edt_dev_spec_useEntertainmentAPI_title",
"default": true, "default": true,
"propertyOrder": 5 "options": {
"hidden": true
},
"propertyOrder": 6
}, },
"switchOffOnBlack": { "switchOffOnBlack": {
"type": "boolean", "type": "boolean",
"format": "checkbox", "format": "checkbox",
"title": "edt_dev_spec_switchOffOnBlack_title", "title": "edt_dev_spec_switchOffOnBlack_title",
"default": false, "default": false,
"propertyOrder": 6 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 7
}, },
"restoreOriginalState": { "restoreOriginalState": {
"type": "boolean", "type": "boolean",
"format": "checkbox", "format": "checkbox",
"title": "edt_dev_spec_restoreOriginalState_title", "title": "edt_dev_spec_restoreOriginalState_title",
"default": false, "default": false,
"propertyOrder": 7 "propertyOrder": 8
}, },
"blackLevel": { "blackLevel": {
"type": "number", "type": "number",
@ -64,7 +83,12 @@
"step": 0.01, "step": 0.01,
"minimum": 0.001, "minimum": 0.001,
"maximum": 1.0, "maximum": 1.0,
"propertyOrder": 8 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 9
}, },
"onBlackTimeToPowerOff": { "onBlackTimeToPowerOff": {
"type": "integer", "type": "integer",
@ -76,7 +100,12 @@
"maximum": 100000, "maximum": 100000,
"default": 600, "default": 600,
"required": true, "required": true,
"propertyOrder": 9 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 10
}, },
"onBlackTimeToPowerOn": { "onBlackTimeToPowerOn": {
"type": "integer", "type": "integer",
@ -88,14 +117,24 @@
"maximum": 100000, "maximum": 100000,
"default": 300, "default": 300,
"required": true, "required": true,
"propertyOrder": 9 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 11
}, },
"candyGamma": { "candyGamma": {
"type": "boolean", "type": "boolean",
"format": "checkbox", "format": "checkbox",
"title": "edt_dev_spec_candyGamma_title", "title": "edt_dev_spec_candyGamma_title",
"default": true, "default": true,
"propertyOrder": 10 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 12
}, },
"lightIds": { "lightIds": {
"type": "array", "type": "array",
@ -112,20 +151,23 @@
"useEntertainmentAPI": false "useEntertainmentAPI": false
} }
}, },
"propertyOrder": 11 "propertyOrder": 13
}, },
"groupId": { "groupId": {
"type": "number", "type": "string",
"format": "stepper",
"step": 1,
"title": "edt_dev_spec_groupId_title", "title": "edt_dev_spec_groupId_title",
"default": 0, "default": "",
"options": { "options": {
"dependencies": { "dependencies": {
"useEntertainmentAPI": true "useEntertainmentAPI": true
} }
}, },
"propertyOrder": 12 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 14
}, },
"brightnessFactor": { "brightnessFactor": {
"type": "number", "type": "number",
@ -136,7 +178,12 @@
"minimum": 0.5, "minimum": 0.5,
"maximum": 10.0, "maximum": 10.0,
"access": "advanced", "access": "advanced",
"propertyOrder": 13 "options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 15
}, },
"handshakeTimeoutMin": { "handshakeTimeoutMin": {
"type": "number", "type": "number",
@ -154,7 +201,7 @@
"useEntertainmentAPI": true "useEntertainmentAPI": true
} }
}, },
"propertyOrder": 14 "propertyOrder": 16
}, },
"handshakeTimeoutMax": { "handshakeTimeoutMax": {
"type": "number", "type": "number",
@ -172,7 +219,7 @@
"useEntertainmentAPI": true "useEntertainmentAPI": true
} }
}, },
"propertyOrder": 15 "propertyOrder": 17
}, },
"verbose": { "verbose": {
"type": "boolean", "type": "boolean",
@ -180,7 +227,7 @@
"title": "edt_dev_spec_verbose_title", "title": "edt_dev_spec_verbose_title",
"default": false, "default": false,
"access": "expert", "access": "expert",
"propertyOrder": 16 "propertyOrder": 18
}, },
"transitiontime": { "transitiontime": {
"type": "number", "type": "number",
@ -195,24 +242,29 @@
"useEntertainmentAPI": false "useEntertainmentAPI": false
} }
}, },
"propertyOrder": 17 "propertyOrder": 19
}, },
"blackLightsTimeout": { "blackLightsTimeout": {
"type": "number", "type": "number",
"title": "edt_dev_spec_blackLightsTimeout_title",
"default": 5000, "default": 5000,
"options": { "options": {
"hidden": true "dependencies": {
"useAPIv2": false
}
}, },
"propertyOrder": 18 "propertyOrder": 20
}, },
"brightnessThreshold": { "brightnessThreshold": {
"type": "number", "type": "number",
"title": "edt_dev_spec_brightnessThreshold_title", "title": "edt_dev_spec_brightnessThreshold_title",
"default": 0.0001, "default": 0.0001,
"options": { "options": {
"hidden": true "dependencies": {
"useAPIv2": false
}
}, },
"propertyOrder": 19 "propertyOrder": 21
}, },
"brightnessMin": { "brightnessMin": {
"type": "number", "type": "number",
@ -223,9 +275,11 @@
"maximum": 1.0, "maximum": 1.0,
"access": "advanced", "access": "advanced",
"options": { "options": {
"hidden": true "dependencies": {
"useAPIv2": false
}
}, },
"propertyOrder": 20 "propertyOrder": 22
}, },
"brightnessMax": { "brightnessMax": {
"type": "number", "type": "number",
@ -236,9 +290,11 @@
"maximum": 1.0, "maximum": 1.0,
"access": "advanced", "access": "advanced",
"options": { "options": {
"hidden": true "dependencies": {
"useAPIv2": false
}
}, },
"propertyOrder": 21 "propertyOrder": 23
} }
}, },
"additionalProperties": true "additionalProperties": true

View File

@ -182,6 +182,7 @@ bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddr
} }
else else
{ {
QObject::disconnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, nullptr, nullptr);
Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname)); Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname));
} }
} }

View File

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICMjCCAdigAwIBAgIUO7FSLbaxikuXAljzVaurLXWmFw4wCgYIKoZIzj0EAwIw
OTELMAkGA1UEBhMCTkwxFDASBgNVBAoMC1BoaWxpcHMgSHVlMRQwEgYDVQQDDAty
b290LWJyaWRnZTAiGA8yMDE3MDEwMTAwMDAwMFoYDzIwMzgwMTE5MDMxNDA3WjA5
MQswCQYDVQQGEwJOTDEUMBIGA1UECgwLUGhpbGlwcyBIdWUxFDASBgNVBAMMC3Jv
b3QtYnJpZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjNw2tx2AplOf9x86
aTdvEcL1FU65QDxziKvBpW9XXSIcibAeQiKxegpq8Exbr9v6LBnYbna2VcaK0G22
jOKkTqOBuTCBtjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNV
HQ4EFgQUZ2ONTFrDT6o8ItRnKfqWKnHFGmQwdAYDVR0jBG0wa4AUZ2ONTFrDT6o8
ItRnKfqWKnHFGmShPaQ7MDkxCzAJBgNVBAYTAk5MMRQwEgYDVQQKDAtQaGlsaXBz
IEh1ZTEUMBIGA1UEAwwLcm9vdC1icmlkZ2WCFDuxUi22sYpLlwJY81Wrqy11phcO
MAoGCCqGSM49BAMCA0gAMEUCIEBYYEOsa07TH7E5MJnGw557lVkORgit2Rm1h3B2
sFgDAiEA1Fj/C3AN5psFMjo0//mrQebo0eKd3aWRx+pQY08mk48=
-----END CERTIFICATE-----