mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Merge remote-tracking branch 'origin/master' into updates
This commit is contained in:
commit
b7945d4aa7
8
.github/workflows/apt.yml
vendored
8
.github/workflows/apt.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
name: Setup APT build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set APT matrix
|
||||
id: apt-ppa
|
||||
run: |
|
||||
@ -54,7 +54,7 @@ jobs:
|
||||
matrix: ${{ fromJson(needs.setup.outputs.apt-matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }}
|
||||
submodules: true
|
||||
@ -107,12 +107,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.head_sha || github.event.client_payload.head_sha }}
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v5.3.0
|
||||
uses: crazy-max/ghaction-import-gpg@v6.0.0
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.APT_GPG }}
|
||||
|
||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
12
.github/workflows/nightly.yml
vendored
12
.github/workflows/nightly.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
if: github.repository_owner == 'hyperion-project'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
if: github.repository_owner == 'hyperion-project'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check if commit has changed
|
||||
id: build-necessary
|
||||
run: |
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
if: ${{ needs.check.outputs.build-nightly == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set nightly matrix
|
||||
id: nightly-ppa
|
||||
run: |
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
matrix: ${{ fromJson(needs.setup.outputs.nightly-matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -135,10 +135,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v5.3.0
|
||||
uses: crazy-max/ghaction-import-gpg@v6.0.0
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.APT_GPG }}
|
||||
|
||||
|
6
.github/workflows/pull-request.yml
vendored
6
.github/workflows/pull-request.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -76,7 +76,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -125,7 +125,7 @@ jobs:
|
||||
QT_VERSION: 5.15.2
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
8
.github/workflows/push-master.yml
vendored
8
.github/workflows/push-master.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
platform: amlogic
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -97,7 +97,7 @@ jobs:
|
||||
QT_VERSION: 5.15.2
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -162,7 +162,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Generate environment variables
|
||||
- name: Generate environment variables from .version and tag
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
# Dispatch event to build new HyperBian image
|
||||
- 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'}}
|
||||
with:
|
||||
repository: hyperion-project/HyperBian
|
||||
|
@ -10,13 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### 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
|
||||
|
||||
### Fixed
|
||||
- Fixed missing Include limits in QJsonSchemaChecker
|
||||
- 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
|
||||
|
||||
|
@ -46,6 +46,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<i class="fa fa-fw fa-save"></i><span data-i18n="wiz_identify">Identify/Test</span>
|
||||
</button>
|
||||
|
@ -86,6 +86,8 @@
|
||||
"conf_leds_layout_cl_bottomright": "Bottom Right (Corner)",
|
||||
"conf_leds_layout_cl_cornergap": "Corner 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_gappos": "gap position",
|
||||
"conf_leds_layout_cl_hleddepth": "Horizontal LED depth",
|
||||
@ -116,6 +118,8 @@
|
||||
"conf_leds_layout_cl_vleddepth": "Vertical LED depth",
|
||||
"conf_leds_layout_frame": "Classic Layout (LED Frame)",
|
||||
"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_ma_cabling": "Cabling",
|
||||
"conf_leds_layout_ma_direction": "Direction",
|
||||
@ -564,8 +568,8 @@
|
||||
"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_title": "HTTPS Port",
|
||||
"edt_dev_auth_key_title": "Authentication Token",
|
||||
"edt_dev_auth_key_title_info": "Authentication Token required to acccess the device",
|
||||
"edt_dev_auth_key_title": "Authorization Token",
|
||||
"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_warm_adjust": "Subtract warm white",
|
||||
"edt_dev_enum_subtract_minimum": "Subtract minimum",
|
||||
@ -618,7 +622,7 @@
|
||||
"edt_dev_spec_gpioBcm_title": "GPIO Pin",
|
||||
"edt_dev_spec_gpioMap_title": "GPIO mapping",
|
||||
"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_interpolation_title": "Interpolation",
|
||||
"edt_dev_spec_intervall_title": "Interval",
|
||||
@ -683,6 +687,7 @@
|
||||
"edt_dev_spec_transistionTime_title": "Transition time",
|
||||
"edt_dev_spec_uid_title": "UID",
|
||||
"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_useOrbSmoothing_title": "Use orb smoothing",
|
||||
"edt_dev_spec_useRgbwProtocol_title": "Use RGBW protocol",
|
||||
@ -755,6 +760,8 @@
|
||||
"edt_eff_ledlist": "LED List",
|
||||
"edt_eff_ledtest_header": "LED Test",
|
||||
"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_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.",
|
||||
@ -1087,7 +1094,7 @@
|
||||
"wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually",
|
||||
"wiz_cololight_title": "Cololight Wizard",
|
||||
"wiz_guideyou": "The $1 will guide you through the settings. Just press the button!",
|
||||
"wiz_hue_blinkblue": "Let ID $1 light up blue",
|
||||
"wiz_hue_blinkblue": "Let it light up",
|
||||
"wiz_hue_clientkey": "Clientkey",
|
||||
"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.",
|
||||
@ -1118,8 +1125,15 @@
|
||||
"wiz_identify": "Identify",
|
||||
"wiz_identify_tip": "Identify configured device by lighting it up",
|
||||
"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_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_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.",
|
||||
|
@ -327,7 +327,7 @@ $(document).ready(function () {
|
||||
var saveOptions = conf_editor_screen.getValue();
|
||||
|
||||
var instCaptOptions = window.serverConfig.instCapture;
|
||||
instCaptOptions.systemEnable = true;
|
||||
instCaptOptions.systemEnable = saveOptions.framegrabber.enable;
|
||||
saveOptions.instCapture = instCaptOptions;
|
||||
|
||||
requestWriteConfig(saveOptions);
|
||||
@ -679,7 +679,7 @@ $(document).ready(function () {
|
||||
var saveOptions = conf_editor_video.getValue();
|
||||
|
||||
var instCaptOptions = window.serverConfig.instCapture;
|
||||
instCaptOptions.v4lEnable = true;
|
||||
instCaptOptions.v4lEnable = saveOptions.grabberV4L2.enable;
|
||||
saveOptions.instCapture = instCaptOptions;
|
||||
|
||||
requestWriteConfig(saveOptions);
|
||||
@ -805,7 +805,7 @@ $(document).ready(function () {
|
||||
const saveOptions = conf_editor_audio.getValue();
|
||||
|
||||
const instCaptOptions = window.serverConfig.instCapture;
|
||||
instCaptOptions.audioEnable = true;
|
||||
instCaptOptions.audioEnable = saveOptions.grabberAudio.enable;
|
||||
saveOptions.instCapture = instCaptOptions;
|
||||
|
||||
requestWriteConfig(saveOptions);
|
||||
|
@ -1021,11 +1021,6 @@ $(document).ready(function () {
|
||||
var generalOptions = window.serverSchema.properties.device;
|
||||
|
||||
var ledType = $(this).val();
|
||||
|
||||
// philipshueentertainment backward fix
|
||||
if (ledType == "philipshueentertainment")
|
||||
ledType = "philipshue";
|
||||
|
||||
var specificOptions = window.serverSchema.properties.alldevices[ledType];
|
||||
|
||||
conf_editor = createJsonEditor('editor_container_leddevice', {
|
||||
@ -1055,18 +1050,22 @@ $(document).ready(function () {
|
||||
// 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);
|
||||
|
||||
// led controller sepecific wizards
|
||||
// LED controller specific wizards
|
||||
$('#btn_wiz_holder').html("");
|
||||
$('#btn_led_device_wiz').off();
|
||||
|
||||
if (ledType == "philipshue") {
|
||||
$('#root_specificOptions_useEntertainmentAPI').on("change", function () {
|
||||
var ledWizardType = (this.checked) ? "philipshueentertainment" : ledType;
|
||||
var data = { type: ledWizardType };
|
||||
var hue_title = (this.checked) ? 'wiz_hue_e_title' : 'wiz_hue_title';
|
||||
changeWizard(data, hue_title, startWizardPhilipsHue);
|
||||
});
|
||||
$("#root_specificOptions_useEntertainmentAPI").trigger("change");
|
||||
var ledWizardType = ledType;
|
||||
var data = { type: ledWizardType };
|
||||
var hue_title = 'wiz_hue_title';
|
||||
changeWizard(data, hue_title, startWizardPhilipsHue);
|
||||
}
|
||||
else if (ledType == "nanoleaf") {
|
||||
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") {
|
||||
var ledWizardType = (this.checked) ? "atmoorb" : ledType;
|
||||
@ -1092,6 +1091,7 @@ $(document).ready(function () {
|
||||
var colorOrderDefault = "rgb";
|
||||
var filter = {};
|
||||
|
||||
$('#btn_layout_controller').hide();
|
||||
$('#btn_test_controller').hide();
|
||||
|
||||
switch (ledType) {
|
||||
@ -1349,6 +1349,13 @@ $(document).ready(function () {
|
||||
|
||||
if (host === "") {
|
||||
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1);
|
||||
switch (ledType) {
|
||||
|
||||
case "nanoleaf":
|
||||
$('#btn_wiz_holder').hide();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
else {
|
||||
let params = {};
|
||||
@ -1360,6 +1367,8 @@ $(document).ready(function () {
|
||||
break;
|
||||
|
||||
case "nanoleaf":
|
||||
$('#btn_wiz_holder').show();
|
||||
|
||||
var token = conf_editor.getEditor("root.specificOptions.token").getValue();
|
||||
if (token === "") {
|
||||
return;
|
||||
@ -1676,6 +1685,33 @@ $(document).ready(function () {
|
||||
$("#leddevices").val(window.serverConfig.device.type);
|
||||
$("#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
|
||||
$("#btn_test_controller").off().on("click", function () {
|
||||
var ledType = $("#leddevices").val();
|
||||
@ -2155,6 +2191,7 @@ async function identify_device(type, params) {
|
||||
}
|
||||
|
||||
function updateElements(ledType, key) {
|
||||
var canLayout = false;
|
||||
if (devicesProperties[ledType][key]) {
|
||||
var hardwareLedCount = 1;
|
||||
switch (ledType) {
|
||||
@ -2173,18 +2210,11 @@ function updateElements(ledType, key) {
|
||||
case "nanoleaf":
|
||||
var ledProperties = devicesProperties[ledType][key];
|
||||
|
||||
if (ledProperties && ledProperties.panelLayout.layout) {
|
||||
//Identify non-LED type panels, e.g. Rhythm (1) and Shapes Controller (12)
|
||||
var nonLedNum = 0;
|
||||
for (const panel of ledProperties.panelLayout.layout.positionData) {
|
||||
if (panel.shapeType === 1 || panel.shapeType === 12) {
|
||||
nonLedNum++;
|
||||
}
|
||||
}
|
||||
hardwareLedCount = ledProperties.panelLayout.layout.numPanels - nonLedNum;
|
||||
if (ledProperties) {
|
||||
hardwareLedCount = ledProperties.ledCount;
|
||||
canLayout = true;
|
||||
}
|
||||
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
|
||||
|
||||
break;
|
||||
|
||||
case "udpraw":
|
||||
@ -2233,11 +2263,19 @@ function updateElements(ledType, key) {
|
||||
}
|
||||
|
||||
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) {
|
||||
$('#btn_submit_controller').attr('disabled', false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$('#btn_layout_controller').prop('disabled', true);
|
||||
$('#btn_submit_controller').attr('disabled', true);
|
||||
}
|
||||
}
|
||||
@ -2415,4 +2453,149 @@ function updateElementsWled(ledType, key) {
|
||||
}
|
||||
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
@ -95,7 +95,7 @@ else
|
||||
fi
|
||||
|
||||
# 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 """
|
||||
import json,sys
|
||||
@ -108,7 +108,7 @@ for i in data:
|
||||
""" 2>/dev/null)
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@ -124,7 +124,7 @@ for i in data:
|
||||
""" 2>/dev/null)
|
||||
|
||||
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 "---> https://github.com/hyperion-project/hyperion.ng/pull/$pr_number"
|
||||
exit 1
|
||||
@ -132,13 +132,13 @@ fi
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
# 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 """
|
||||
import json,sys
|
||||
data = json.load(sys.stdin)
|
||||
|
||||
for i in data['workflow_runs']:
|
||||
if i['head_sha'] == '"$head_sha"':
|
||||
if i['name'] == 'Hyperion PR Build':
|
||||
print(i['id'])
|
||||
break
|
||||
""" 2>/dev/null)
|
||||
|
11
effects/ledtest-seq.json
Normal file
11
effects/ledtest-seq.json
Normal 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
39
effects/ledtest-seq.py
Normal 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)
|
||||
|
56
effects/schema/ledtest-seq.schema.json
Normal file
56
effects/schema/ledtest-seq.schema.json
Normal 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
|
||||
}
|
@ -9,6 +9,15 @@
|
||||
// Constants
|
||||
namespace {
|
||||
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))
|
||||
@ -28,12 +37,12 @@ AudioGrabber::AudioGrabber()
|
||||
, _deviceProperties()
|
||||
, _device("none")
|
||||
, _hotColor(QColorConstants::Red)
|
||||
, _warnValue(80)
|
||||
, _warnValue(DEFAULT_WARNVALUE)
|
||||
, _warnColor(QColorConstants::Yellow)
|
||||
, _safeValue(45)
|
||||
, _safeValue(DEFAULT_SAFEVALUE)
|
||||
, _safeColor(QColorConstants::Green)
|
||||
, _multiplier(0)
|
||||
, _tolerance(20)
|
||||
, _multiplier(DEFAULT_MULTIPLIER)
|
||||
, _tolerance(DEFAULT_TOLERANCE)
|
||||
, _dynamicMultiplier(INT16_MAX)
|
||||
, _started(false)
|
||||
{
|
||||
@ -61,18 +70,27 @@ void AudioGrabber::setDevice(const QString& device)
|
||||
|
||||
void AudioGrabber::setConfiguration(const QJsonObject& config)
|
||||
{
|
||||
QJsonArray hotColorArray = config["hotColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(0), QVariant(0) })));
|
||||
QJsonArray warnColorArray = config["warnColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(255), QVariant(255), QVariant(0) })));
|
||||
QJsonArray safeColorArray = config["safeColor"].toArray(QJsonArray::fromVariantList(QList<QVariant>({ QVariant(0), QVariant(255), QVariant(0) })));
|
||||
QString audioEffect = config["audioEffect"].toString();
|
||||
QJsonObject audioEffectConfig = config[audioEffect].toObject();
|
||||
|
||||
_hotColor = QColor(hotColorArray.at(0).toInt(), hotColorArray.at(1).toInt(), hotColorArray.at(2).toInt());
|
||||
_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());
|
||||
if (audioEffect == "vuMeter")
|
||||
{
|
||||
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);
|
||||
_safeValue = config["safeValue"].toInt(45);
|
||||
_multiplier = config["multiplier"].toDouble(0);
|
||||
_tolerance = config["tolerance"].toInt(20);
|
||||
_hotColor = QColor(hotColorArray.at(0).toInt(), hotColorArray.at(1).toInt(), hotColorArray.at(2).toInt());
|
||||
_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());
|
||||
_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()
|
||||
|
@ -21,19 +21,19 @@ using namespace semver;
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
|
||||
const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
|
||||
} //End of constants
|
||||
|
||||
QJsonObject SettingsManager::schemaJson;
|
||||
|
||||
SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("SETTINGSMGR", "I"+QString::number(instance)))
|
||||
, _instance(instance)
|
||||
, _sTable(new SettingsTable(instance, this))
|
||||
, _configVersion(DEFAULT_VERSION)
|
||||
, _previousVersion(DEFAULT_VERSION)
|
||||
, _readonlyMode(readonlyMode)
|
||||
, _log(Logger::getInstance("SETTINGSMGR", "I" + QString::number(instance)))
|
||||
, _instance(instance)
|
||||
, _sTable(new SettingsTable(instance, this))
|
||||
, _configVersion(DEFAULT_VERSION)
|
||||
, _previousVersion(DEFAULT_VERSION)
|
||||
, _readonlyMode(readonlyMode)
|
||||
{
|
||||
_sTable->setReadonlyMode(_readonlyMode);
|
||||
// get schema
|
||||
@ -723,6 +723,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
|
||||
}
|
||||
|
||||
//Migration steps for versions <= 2.0.13
|
||||
_previousVersion = targetVersion;
|
||||
targetVersion.setVersion("2.0.13");
|
||||
if (_previousVersion <= targetVersion)
|
||||
{
|
||||
@ -738,12 +739,12 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
|
||||
{
|
||||
QString type = newDeviceConfig["type"].toString();
|
||||
|
||||
const QStringList serialDevices {"adalight", "dmx", "atmo", "sedu", "tpm2", "karate"};
|
||||
if ( serialDevices.contains(type ))
|
||||
const QStringList serialDevices{ "adalight", "dmx", "atmo", "sedu", "tpm2", "karate" };
|
||||
if (serialDevices.contains(type))
|
||||
{
|
||||
if (!newDeviceConfig.contains("rateList"))
|
||||
{
|
||||
newDeviceConfig["rateList"] = "CUSTOM";
|
||||
newDeviceConfig["rateList"] = "CUSTOM";
|
||||
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;
|
||||
|
@ -4,6 +4,7 @@
|
||||
//std includes
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
|
||||
// Qt includes
|
||||
#include <QNetworkReply>
|
||||
@ -21,88 +22,79 @@
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
const bool verbose = false;
|
||||
const bool verbose3 = false;
|
||||
const bool verbose = false;
|
||||
const bool verbose3 = false;
|
||||
|
||||
// Configuration settings
|
||||
const char CONFIG_HOST[] = "host";
|
||||
const char CONFIG_AUTH_TOKEN[] = "token";
|
||||
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
|
||||
const char CONFIG_BRIGHTNESS[] = "brightness";
|
||||
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
|
||||
// Configuration settings
|
||||
const char CONFIG_HOST[] = "host";
|
||||
const char CONFIG_AUTH_TOKEN[] = "token";
|
||||
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
|
||||
const char CONFIG_BRIGHTNESS[] = "brightness";
|
||||
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
|
||||
|
||||
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
|
||||
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
|
||||
const char CONFIG_PANEL_START_POS[] = "panelStartPos";
|
||||
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
|
||||
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
|
||||
|
||||
const bool DEFAULT_IS_RESTORE_STATE = true;
|
||||
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
|
||||
const int BRI_MAX = 100;
|
||||
const bool DEFAULT_IS_RESTORE_STATE = true;
|
||||
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
|
||||
const int BRI_MAX = 100;
|
||||
|
||||
// Panel configuration settings
|
||||
const char PANEL_LAYOUT[] = "layout";
|
||||
const char PANEL_NUM[] = "numPanels";
|
||||
const char PANEL_ID[] = "panelId";
|
||||
const char PANEL_POSITIONDATA[] = "positionData";
|
||||
const char PANEL_SHAPE_TYPE[] = "shapeType";
|
||||
const char PANEL_POS_X[] = "x";
|
||||
const char PANEL_POS_Y[] = "y";
|
||||
// Panel configuration settings
|
||||
const char PANEL_GLOBALORIENTATION[] = "globalOrientation";
|
||||
const char PANEL_GLOBALORIENTATION_VALUE[] = "value";
|
||||
const char PANEL_LAYOUT[] = "layout";
|
||||
const char PANEL_NUM[] = "numPanels";
|
||||
const char PANEL_ID[] = "panelId";
|
||||
const char PANEL_POSITIONDATA[] = "positionData";
|
||||
const char PANEL_SHAPE_TYPE[] = "shapeType";
|
||||
const char PANEL_POS_X[] = "x";
|
||||
const char PANEL_POS_Y[] = "y";
|
||||
|
||||
// List of State Information
|
||||
const char STATE_ON[] = "on";
|
||||
const char STATE_BRI[] = "brightness";
|
||||
const char STATE_HUE[] = "hue";
|
||||
const char STATE_SAT[] = "sat";
|
||||
const char STATE_CT[] = "ct";
|
||||
const char STATE_COLORMODE[] = "colorMode";
|
||||
const QStringList COLOR_MODES {"hs", "ct", "effect"};
|
||||
const char STATE_VALUE[] = "value";
|
||||
// List of State Information
|
||||
const char STATE_ON[] = "on";
|
||||
const char STATE_BRI[] = "brightness";
|
||||
const char STATE_HUE[] = "hue";
|
||||
const char STATE_SAT[] = "sat";
|
||||
const char STATE_CT[] = "ct";
|
||||
const char STATE_COLORMODE[] = "colorMode";
|
||||
const QStringList COLOR_MODES{ "hs", "ct", "effect" };
|
||||
const char STATE_VALUE[] = "value";
|
||||
|
||||
// Device Data elements
|
||||
const char DEV_DATA_NAME[] = "name";
|
||||
const char DEV_DATA_MODEL[] = "model";
|
||||
const char DEV_DATA_MANUFACTURER[] = "manufacturer";
|
||||
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
|
||||
// Device Data elements
|
||||
const char DEV_DATA_NAME[] = "name";
|
||||
const char DEV_DATA_MODEL[] = "model";
|
||||
const char DEV_DATA_MANUFACTURER[] = "manufacturer";
|
||||
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
|
||||
|
||||
// Nanoleaf Stream Control elements
|
||||
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
|
||||
// Nanoleaf Stream Control elements
|
||||
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
|
||||
|
||||
// Nanoleaf OpenAPI URLs
|
||||
const int API_DEFAULT_PORT = 16021;
|
||||
const char API_BASE_PATH[] = "/api/v1/%1/";
|
||||
const char API_ROOT[] = "";
|
||||
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
|
||||
const char API_STATE[] = "state";
|
||||
const char API_PANELLAYOUT[] = "panelLayout";
|
||||
const char API_EFFECT[] = "effects";
|
||||
// Nanoleaf OpenAPI URLs
|
||||
const int API_DEFAULT_PORT = 16021;
|
||||
const char API_BASE_PATH[] = "/api/v1/%1/";
|
||||
const char API_ROOT[] = "";
|
||||
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
|
||||
const char API_STATE[] = "state";
|
||||
const char API_PANELLAYOUT[] = "panelLayout";
|
||||
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
|
||||
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
|
||||
const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
|
||||
// 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";
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
enum EXTCONTROLVERSIONS {
|
||||
EXTCTRLVER_V1 = 1,
|
||||
@ -111,18 +103,16 @@ enum EXTCONTROLVERSIONS {
|
||||
|
||||
LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
|
||||
: ProviderUdp(deviceConfig)
|
||||
, _restApi(nullptr)
|
||||
, _apiPort(API_DEFAULT_PORT)
|
||||
, _topDown(true)
|
||||
, _leftRight(true)
|
||||
, _startPos(0)
|
||||
, _endPos(0)
|
||||
, _extControlVersion(EXTCTRLVER_V2)
|
||||
, _panelLedCount(0)
|
||||
, _restApi(nullptr)
|
||||
, _apiPort(API_DEFAULT_PORT)
|
||||
, _topDown(true)
|
||||
, _leftRight(true)
|
||||
, _extControlVersion(EXTCTRLVER_V2)
|
||||
, _panelLedCount(0)
|
||||
{
|
||||
#ifdef ENABLE_MDNS
|
||||
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
|
||||
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
|
||||
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -139,7 +129,7 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf()
|
||||
|
||||
bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
|
||||
{
|
||||
bool isInitOK {false};
|
||||
bool isInitOK{ false };
|
||||
|
||||
// Overwrite non supported/required features
|
||||
setLatchTime(0);
|
||||
@ -150,9 +140,9 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
|
||||
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
|
||||
_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);
|
||||
_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, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
|
||||
Debug(_log, "Set Brightness to : %d", _brightness);
|
||||
|
||||
// Read panel organisation configuration
|
||||
if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString())
|
||||
{
|
||||
_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);
|
||||
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString("top2down") == "top2down";
|
||||
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString("left2right") == "left2right";
|
||||
|
||||
isInitOK = true;
|
||||
}
|
||||
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 isInitOK = true;
|
||||
@ -206,7 +226,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
if (response.error())
|
||||
{
|
||||
QString errorReason = QString("Getting device details failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
this->setInError(errorReason);
|
||||
isInitOK = false;
|
||||
}
|
||||
else
|
||||
@ -225,37 +245,71 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
|
||||
// Get panel details from /panelLayout/layout
|
||||
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();
|
||||
|
||||
_panelLedCount = getHwLedCount(jsonLayout);
|
||||
_devConfig["hardwareLedCount"] = _panelLedCount;
|
||||
|
||||
int panelNum = jsonLayout[PANEL_NUM].toInt();
|
||||
const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
|
||||
|
||||
std::map<int, std::map<int, int>> panelMap;
|
||||
|
||||
// Loop over all children.
|
||||
for(const QJsonValue & value : positionData)
|
||||
for (const QJsonValue& value : positionData)
|
||||
{
|
||||
QJsonObject panelObj = value.toObject();
|
||||
|
||||
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 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);
|
||||
|
||||
// Skip Rhythm and Shapes controller panels
|
||||
if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
|
||||
int panelX;
|
||||
int panelY;
|
||||
if (isRotated)
|
||||
{
|
||||
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
|
||||
{ // 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
|
||||
_panelIds.clear();
|
||||
for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY)
|
||||
{
|
||||
// Sort panels left to right
|
||||
@ -263,7 +317,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -280,7 +334,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
// Sort panels right to left
|
||||
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)
|
||||
{
|
||||
@ -294,27 +348,22 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
this->_panelLedCount = _panelIds.size();
|
||||
_devConfig["hardwareLedCount"] = _panelLedCount;
|
||||
|
||||
Debug(_log, "PanelsNum : %d", panelNum);
|
||||
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.
|
||||
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)
|
||||
{
|
||||
QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!")
|
||||
.arg(_panelLedCount)
|
||||
.arg(configuredLedCount);
|
||||
this->setInError(errorReason);
|
||||
.arg(_panelLedCount)
|
||||
.arg(configuredLedCount);
|
||||
this->setInError(errorReason, false);
|
||||
isInitOK = false;
|
||||
}
|
||||
else
|
||||
@ -324,15 +373,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
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
|
||||
if (_endPos >= _panelLedCount)
|
||||
//Check that panel count matches working list created for processing
|
||||
if (_panelLedCount != _panelIds.size())
|
||||
{
|
||||
QString errorReason = QString("Start panel [%1] out of range. Start panel position can be max [%2] given [%3] panel available!")
|
||||
.arg(_startPos).arg(_panelLedCount - configuredLedCount).arg(_panelLedCount);
|
||||
|
||||
this->setInError(errorReason);
|
||||
QString errorReason = QString("Number of available panels [%1] do not match panel-ID look-up list [%2]!")
|
||||
.arg(_panelLedCount)
|
||||
.arg(_panelIds.size());
|
||||
this->setInError(errorReason, false);
|
||||
isInitOK = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return isInitOK;
|
||||
@ -340,7 +390,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
|
||||
bool LedDeviceNanoleaf::openRestAPI()
|
||||
{
|
||||
bool isInitOK {true};
|
||||
bool isInitOK{ true };
|
||||
|
||||
if (_restApi == nullptr)
|
||||
{
|
||||
@ -360,7 +410,7 @@ int LedDeviceNanoleaf::open()
|
||||
|
||||
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
|
||||
{
|
||||
if ( openRestAPI() )
|
||||
if (openRestAPI())
|
||||
{
|
||||
// Read LedDevice configuration and validate against device configuration
|
||||
if (initLedsConfiguration())
|
||||
@ -415,7 +465,7 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
|
||||
MdnsServiceRegister::getServiceType(_activeDeviceType),
|
||||
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
|
||||
DEFAULT_DISCOVER_TIMEOUT
|
||||
);
|
||||
);
|
||||
#else
|
||||
QString discoveryMethod("ssdp");
|
||||
deviceList = discover();
|
||||
@ -424,25 +474,25 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
|
||||
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
_hostName = params[CONFIG_HOST].toString("");
|
||||
_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 ( openRestAPI() )
|
||||
if (openRestAPI())
|
||||
{
|
||||
QString filter = params["filter"].toString("");
|
||||
_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()));
|
||||
}
|
||||
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());
|
||||
@ -463,21 +520,19 @@ QJsonObject LedDeviceNanoleaf::getProperties(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("");
|
||||
_apiPort = API_DEFAULT_PORT;if (NetUtils::resolveHostToAddress(_log, _hostName, _address))
|
||||
_authToken = params["token"].toString("");
|
||||
_apiPort = API_DEFAULT_PORT;
|
||||
_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 ( openRestAPI() )
|
||||
if (openRestAPI())
|
||||
{
|
||||
_restApi->setPath("identify");
|
||||
|
||||
// Perform request
|
||||
_restApi->setPath(API_IDENTIFY);
|
||||
httpResponse response = _restApi->put();
|
||||
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 on = false;
|
||||
@ -496,12 +581,12 @@ bool LedDeviceNanoleaf::powerOn()
|
||||
{
|
||||
QJsonObject newState;
|
||||
|
||||
QJsonObject onValue { {STATE_VALUE, true} };
|
||||
QJsonObject onValue{ {STATE_VALUE, true} };
|
||||
newState.insert(STATE_ON, onValue);
|
||||
|
||||
if ( _isBrightnessOverwrite)
|
||||
if (_isBrightnessOverwrite)
|
||||
{
|
||||
QJsonObject briValue { {STATE_VALUE, _brightness} };
|
||||
QJsonObject briValue{ {STATE_VALUE, _brightness} };
|
||||
newState.insert(STATE_BRI, briValue);
|
||||
}
|
||||
|
||||
@ -511,9 +596,10 @@ bool LedDeviceNanoleaf::powerOn()
|
||||
if (response.error())
|
||||
{
|
||||
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
this->setInError(errorReason);
|
||||
on = false;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
on = true;
|
||||
}
|
||||
|
||||
@ -529,7 +615,7 @@ bool LedDeviceNanoleaf::powerOff()
|
||||
{
|
||||
QJsonObject newState;
|
||||
|
||||
QJsonObject onValue { {STATE_VALUE, false} };
|
||||
QJsonObject onValue{ {STATE_VALUE, false} };
|
||||
newState.insert(STATE_ON, onValue);
|
||||
|
||||
//Power-off the Nanoleaf device physically
|
||||
@ -538,7 +624,7 @@ bool LedDeviceNanoleaf::powerOff()
|
||||
if (response.error())
|
||||
{
|
||||
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
this->setInError(errorReason);
|
||||
off = false;
|
||||
}
|
||||
}
|
||||
@ -549,12 +635,12 @@ bool LedDeviceNanoleaf::storeState()
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if ( _isRestoreOrigState )
|
||||
if (_isRestoreOrigState)
|
||||
{
|
||||
_restApi->setPath(API_STATE);
|
||||
|
||||
httpResponse response = _restApi->get();
|
||||
if ( response.error() )
|
||||
if (response.error())
|
||||
{
|
||||
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
|
||||
setInError(errorReason);
|
||||
@ -563,7 +649,7 @@ bool LedDeviceNanoleaf::storeState()
|
||||
else
|
||||
{
|
||||
_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();
|
||||
if (!isOn.isEmpty())
|
||||
@ -579,7 +665,7 @@ bool LedDeviceNanoleaf::storeState()
|
||||
|
||||
_originalColorMode = _originalStateProperties[STATE_COLORMODE].toString();
|
||||
|
||||
switch(COLOR_MODES.indexOf(_originalColorMode)) {
|
||||
switch (COLOR_MODES.indexOf(_originalColorMode)) {
|
||||
case 0:
|
||||
{
|
||||
// hs
|
||||
@ -611,7 +697,7 @@ bool LedDeviceNanoleaf::storeState()
|
||||
_restApi->setPath(API_EFFECT);
|
||||
|
||||
httpResponse responseEffects = _restApi->get();
|
||||
if ( responseEffects.error() )
|
||||
if (responseEffects.error())
|
||||
{
|
||||
QString errorReason = QString("Storing device state failed with error: '%1'").arg(responseEffects.getErrorReason());
|
||||
setInError(errorReason);
|
||||
@ -620,7 +706,7 @@ bool LedDeviceNanoleaf::storeState()
|
||||
else
|
||||
{
|
||||
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();
|
||||
_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
|
||||
}
|
||||
@ -641,21 +727,21 @@ bool LedDeviceNanoleaf::restoreState()
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if ( _isRestoreOrigState )
|
||||
if (_isRestoreOrigState)
|
||||
{
|
||||
QJsonObject newState;
|
||||
switch(COLOR_MODES.indexOf(_originalColorMode)) {
|
||||
switch (COLOR_MODES.indexOf(_originalColorMode)) {
|
||||
case 0:
|
||||
{ // hs
|
||||
QJsonObject hueValue { {STATE_VALUE, _originalHue} };
|
||||
QJsonObject hueValue{ {STATE_VALUE, _originalHue} };
|
||||
newState.insert(STATE_HUE, hueValue);
|
||||
QJsonObject satValue { {STATE_VALUE, _originalSat} };
|
||||
QJsonObject satValue{ {STATE_VALUE, _originalSat} };
|
||||
newState.insert(STATE_SAT, satValue);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{ // ct
|
||||
QJsonObject ctValue { {STATE_VALUE, _originalCt} };
|
||||
QJsonObject ctValue{ {STATE_VALUE, _originalCt} };
|
||||
newState.insert(STATE_CT, ctValue);
|
||||
break;
|
||||
}
|
||||
@ -667,37 +753,38 @@ bool LedDeviceNanoleaf::restoreState()
|
||||
newEffect[API_EFFECT_SELECT] = _originalEffect;
|
||||
_restApi->setPath(API_EFFECT);
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (!_originalIsDynEffect)
|
||||
{
|
||||
QJsonObject briValue { {STATE_VALUE, _originalBri} };
|
||||
QJsonObject briValue{ {STATE_VALUE, _originalBri} };
|
||||
newState.insert(STATE_BRI, briValue);
|
||||
}
|
||||
|
||||
QJsonObject onValue { {STATE_VALUE, _originalIsOn} };
|
||||
QJsonObject onValue{ {STATE_VALUE, _originalIsOn} };
|
||||
newState.insert(STATE_ON, onValue);
|
||||
|
||||
_restApi->setPath(API_STATE);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -722,7 +809,7 @@ bool LedDeviceNanoleaf::changeToExternalControlMode(QJsonDocument& resp)
|
||||
if (response.error())
|
||||
{
|
||||
QString errorReason = QString("Change to external control mode failed with error: '%1'").arg(response.getErrorReason());
|
||||
this->setInError ( errorReason );
|
||||
this->setInError(errorReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -758,29 +845,24 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
|
||||
|
||||
ColorRgb color;
|
||||
|
||||
//Maintain LED counter independent from PanelCounter
|
||||
int ledCounter = 0;
|
||||
for (int panelCounter = 0; panelCounter < _panelLedCount; panelCounter++)
|
||||
for (int panelCounter = 0; panelCounter < _panelLedCount; ++panelCounter)
|
||||
{
|
||||
// Set panelID
|
||||
int panelID = _panelIds[panelCounter];
|
||||
qToBigEndian<quint16>(static_cast<quint16>(panelID), udpbuffer.data() + i);
|
||||
i += 2;
|
||||
|
||||
// Set panels configured
|
||||
if (panelCounter >= _startPos && panelCounter <= _endPos) {
|
||||
color = static_cast<ColorRgb>(ledValues.at(ledCounter));
|
||||
++ledCounter;
|
||||
// Set panel's color LEDs
|
||||
if (panelCounter < this->getLedCount()) {
|
||||
color = static_cast<ColorRgb>(ledValues.at(panelCounter));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set panels not configured to 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.green);
|
||||
udpbuffer[i++] = static_cast<char>(color.blue);
|
||||
@ -799,7 +881,7 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
|
||||
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, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
|
||||
Debug(_log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
|
||||
}
|
||||
|
||||
retVal = writeBytes(udpbuffer);
|
||||
|
@ -87,6 +87,20 @@ public:
|
||||
///
|
||||
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:
|
||||
|
||||
///
|
||||
@ -147,6 +161,27 @@ protected:
|
||||
|
||||
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
|
||||
///
|
||||
@ -182,6 +217,20 @@ private:
|
||||
///
|
||||
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
|
||||
ProviderRestApi* _restApi;
|
||||
int _apiPort;
|
||||
@ -189,8 +238,6 @@ private:
|
||||
|
||||
bool _topDown;
|
||||
bool _leftRight;
|
||||
int _startPos;
|
||||
int _endPos;
|
||||
|
||||
//Nanoleaf device details
|
||||
QString _deviceModel;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,31 +16,6 @@
|
||||
#include "ProviderRestApi.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 0’s
|
||||
0x01, //xy Brightness
|
||||
0x00, // Reserved, write 0’s
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -145,12 +120,18 @@ public:
|
||||
/// Constructs the light.
|
||||
///
|
||||
/// @param log the logger
|
||||
/// @param bridge the bridge
|
||||
/// @param useApiV2 make use of Hue API version 2
|
||||
/// @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,
|
||||
int onBlackTimeToPowerOff,
|
||||
int onBlackTimeToPowerOn);
|
||||
PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, const QJsonObject& lightAttributes,
|
||||
int onBlackTimeToPowerOff,
|
||||
int onBlackTimeToPowerOn);
|
||||
|
||||
void setDeviceDetails(const QJsonObject& details);
|
||||
void setEntertainmentSrvDetails(const QJsonObject& details);
|
||||
|
||||
///
|
||||
/// @param on
|
||||
@ -167,7 +148,14 @@ public:
|
||||
///
|
||||
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;
|
||||
int getTransitionTime() const;
|
||||
@ -179,7 +167,7 @@ public:
|
||||
CiColorTriangle getColorSpace() const;
|
||||
|
||||
void saveOriginalState(const QJsonObject& values);
|
||||
QString getOriginalState() const;
|
||||
QJsonObject getOriginalState() const;
|
||||
|
||||
bool isBusy();
|
||||
bool isBlack(bool isBlack);
|
||||
@ -189,24 +177,30 @@ public:
|
||||
private:
|
||||
|
||||
Logger* _log;
|
||||
/// light id
|
||||
int _id;
|
||||
int _ledidx;
|
||||
bool _useApiV2;
|
||||
|
||||
QString _id;
|
||||
QString _deviceId;
|
||||
QString _product;
|
||||
QString _model;
|
||||
QString _name;
|
||||
QString _archeType;
|
||||
QString _gamutType;
|
||||
|
||||
int _maxSegments;
|
||||
|
||||
bool _on;
|
||||
int _transitionTime;
|
||||
CiColor _color;
|
||||
bool _hasColor;
|
||||
/// darkes blue color in hue lamp GAMUT = black
|
||||
CiColor _colorBlack;
|
||||
/// The model id of the hue lamp which is used to determine the color space.
|
||||
QString _modelId;
|
||||
QString _lightname;
|
||||
CiColorTriangle _colorSpace;
|
||||
|
||||
/// The json string of the original state.
|
||||
QJsonObject _originalStateJSON;
|
||||
|
||||
QString _originalState;
|
||||
QJsonObject _originalState;
|
||||
CiColor _originalColor;
|
||||
qint64 _lastSendColorTime;
|
||||
qint64 _lastBlackTime;
|
||||
@ -242,23 +236,40 @@ public:
|
||||
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 content the content of the POST request.
|
||||
/// @param routeElements the route's elements of the GET 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:
|
||||
|
||||
@ -289,7 +300,7 @@ protected:
|
||||
///
|
||||
/// @param[in] response from Hue-Bridge in JSON-format
|
||||
/// @param[in] suppressError Treat an error as a warning
|
||||
///
|
||||
///
|
||||
/// return True, Hue Bridge reports error
|
||||
///
|
||||
bool checkApiError(const QJsonDocument& response, bool supressError = false);
|
||||
@ -338,23 +349,41 @@ protected:
|
||||
///
|
||||
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
|
||||
ProviderRestApi* _restApi;
|
||||
int _apiPort;
|
||||
/// User name for the API ("newdeveloper")
|
||||
QString _authToken;
|
||||
QString _applicationID;
|
||||
|
||||
bool _useHueEntertainmentAPI;
|
||||
bool _useEntertainmentAPI;
|
||||
bool _useApiV2;
|
||||
bool _isAPIv2Ready;
|
||||
|
||||
QJsonDocument getGroupState( int groupId );
|
||||
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;
|
||||
bool _isDiyHue;
|
||||
|
||||
private:
|
||||
|
||||
@ -364,16 +393,25 @@ private:
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonArray discover();
|
||||
QJsonArray discoverSsdp();
|
||||
|
||||
QJsonDocument getAllBridgeInfos();
|
||||
void setBridgeConfig( const QJsonDocument &doc );
|
||||
QJsonDocument retrieveDeviceDetails(const QString& deviceId = "");
|
||||
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 setGroupMap( const QJsonDocument &doc );
|
||||
void setEntertainmentSrvMap( const QJsonDocument &doc );
|
||||
|
||||
//Philips Hue Bridge details
|
||||
QString _deviceName;
|
||||
QString _deviceBridgeId;
|
||||
QString _deviceModel;
|
||||
QString _deviceFirmwareVersion;
|
||||
int _deviceFirmwareVersion;
|
||||
QString _deviceAPIVersion;
|
||||
|
||||
uint _api_major;
|
||||
@ -382,8 +420,12 @@ private:
|
||||
|
||||
bool _isHueEntertainmentReady;
|
||||
|
||||
QMap<int,QJsonObject> _lightsMap;
|
||||
QMap<int,QJsonObject> _groupsMap;
|
||||
QMap<QString,QJsonObject> _devicesMap;
|
||||
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
|
||||
///
|
||||
unsigned int getLightsCount() const { return _lightsCount; }
|
||||
int getLightsCount() const { return _lightsCount; }
|
||||
|
||||
void setOnOffState(PhilipsHueLight& light, bool on, bool force = false);
|
||||
void setTransitionTime(PhilipsHueLight& light);
|
||||
@ -547,18 +589,18 @@ private:
|
||||
|
||||
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.
|
||||
///
|
||||
/// @rparam[in] Number of device's LEDs
|
||||
//
|
||||
void setLightsCount( unsigned int lightsCount);
|
||||
void setLightsCount(int lightsCount);
|
||||
|
||||
bool openStream();
|
||||
bool getStreamGroupState();
|
||||
@ -566,10 +608,8 @@ private:
|
||||
bool startStream();
|
||||
bool stopStream();
|
||||
|
||||
void writeStream(bool flush = false);
|
||||
int writeSingleLights(const std::vector<ColorRgb>& ledValues);
|
||||
|
||||
QByteArray prepareStreamData() const;
|
||||
int writeStreamData(const std::vector<ColorRgb>& ledValues, bool flush = false);
|
||||
|
||||
///
|
||||
bool _switchOffOnBlack;
|
||||
@ -582,12 +622,15 @@ private:
|
||||
bool _isInitLeds;
|
||||
|
||||
/// Array of the light ids.
|
||||
std::vector<int> _lightIds;
|
||||
QStringList _lightIds;
|
||||
/// Array to save the lamps.
|
||||
std::vector<PhilipsHueLight> _lights;
|
||||
|
||||
int _lightsCount;
|
||||
int _groupId;
|
||||
int _channelsCount;
|
||||
QString _groupId;
|
||||
QString _groupName;
|
||||
QString _streamOwner;
|
||||
|
||||
int _blackLightsTimeout;
|
||||
double _blackLevel;
|
||||
@ -595,15 +638,5 @@ private:
|
||||
int _onBlackTimeToPowerOn;
|
||||
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;
|
||||
};
|
||||
|
@ -2,11 +2,18 @@
|
||||
#include "ProviderRestApi.h"
|
||||
|
||||
// Qt includes
|
||||
#include <QObject>
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QFile>
|
||||
|
||||
#include <QSslSocket>
|
||||
|
||||
//std includes
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
@ -30,12 +37,12 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
|
||||
: _log(Logger::getInstance("LEDDEVICE"))
|
||||
, _networkManager(nullptr)
|
||||
, _requestTimeout(DEFAULT_REST_TIMEOUT)
|
||||
,_isSeflSignedCertificateAccpeted(false)
|
||||
{
|
||||
_networkManager = new QNetworkAccessManager();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
_networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
#endif
|
||||
|
||||
_apiUrl.setScheme(scheme);
|
||||
_apiUrl.setHost(host);
|
||||
_apiUrl.setPort(port);
|
||||
@ -46,7 +53,7 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
|
||||
: ProviderRestApi(scheme, host, port, "") {}
|
||||
|
||||
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(host, port, "") {}
|
||||
@ -59,18 +66,33 @@ ProviderRestApi::~ProviderRestApi()
|
||||
delete _networkManager;
|
||||
}
|
||||
|
||||
void ProviderRestApi::setScheme(const QString& scheme)
|
||||
{
|
||||
_apiUrl.setScheme(scheme);
|
||||
}
|
||||
|
||||
void ProviderRestApi::setUrl(const QUrl& url)
|
||||
{
|
||||
_apiUrl = url;
|
||||
_basePath = url.path();
|
||||
}
|
||||
|
||||
void ProviderRestApi::setBasePath(const QStringList& pathElements)
|
||||
{
|
||||
setBasePath(pathElements.join(ONE_SLASH));
|
||||
}
|
||||
|
||||
void ProviderRestApi::setBasePath(const QString& basePath)
|
||||
{
|
||||
_basePath.clear();
|
||||
appendPath(_basePath, basePath);
|
||||
}
|
||||
|
||||
void ProviderRestApi::clearBasePath()
|
||||
{
|
||||
_basePath.clear();
|
||||
}
|
||||
|
||||
void ProviderRestApi::setPath(const QStringList& pathElements)
|
||||
{
|
||||
_path.clear();
|
||||
@ -83,6 +105,11 @@ void ProviderRestApi::setPath(const QString& path)
|
||||
appendPath(_path, path);
|
||||
}
|
||||
|
||||
void ProviderRestApi::clearPath()
|
||||
{
|
||||
_path.clear();
|
||||
}
|
||||
|
||||
void ProviderRestApi::appendPath(const QString& path)
|
||||
{
|
||||
appendPath(_path, path);
|
||||
@ -204,6 +231,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
|
||||
QDateTime start = QDateTime::currentDateTime();
|
||||
QString opCode;
|
||||
QNetworkReply* reply;
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
opCode = "GET";
|
||||
@ -255,11 +283,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
|
||||
HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
response.setHttpStatusCode(httpStatusCode);
|
||||
response.setNetworkReplyError(reply->error());
|
||||
response.setHeaders(reply->rawHeaderPairs());
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
QByteArray replyData = reply->readAll();
|
||||
|
||||
if (!replyData.isEmpty())
|
||||
{
|
||||
QJsonParseError error;
|
||||
@ -284,40 +312,40 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
|
||||
else
|
||||
{
|
||||
QString errorReason;
|
||||
if (httpStatusCode > 0) {
|
||||
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
QString advise;
|
||||
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);
|
||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
{
|
||||
errorReason = "Network request timeout error";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
{
|
||||
errorReason = "Network request timeout error";
|
||||
if (httpStatusCode > 0) {
|
||||
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
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
|
||||
{
|
||||
errorReason = reply->errorString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
response.setError(true);
|
||||
response.setErrorReason(errorReason);
|
||||
}
|
||||
@ -344,3 +372,121 @@ void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray &
|
||||
{
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,13 @@
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include <QFile>
|
||||
#include <QBasicTimer>
|
||||
#include <QTimerEvent>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 1000 };
|
||||
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 2000 };
|
||||
|
||||
//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 };
|
||||
|
||||
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);
|
||||
if (reply && reply->isRunning()) {
|
||||
@ -87,6 +88,10 @@ public:
|
||||
QJsonDocument getBody() const { return _responseBody; }
|
||||
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; }
|
||||
void setErrorReason(const QString& errorReason) { _errorReason = errorReason; }
|
||||
|
||||
@ -99,6 +104,8 @@ public:
|
||||
private:
|
||||
|
||||
QJsonDocument _responseBody {};
|
||||
QHash<QByteArray, QByteArray> _responseHeaders;
|
||||
|
||||
bool _hasError = false;
|
||||
QString _errorReason;
|
||||
|
||||
@ -131,6 +138,7 @@ class ProviderRestApi : public QObject
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
/// @brief Constructor of the REST-API wrapper
|
||||
///
|
||||
ProviderRestApi();
|
||||
@ -176,6 +184,20 @@ public:
|
||||
///
|
||||
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
|
||||
///
|
||||
@ -190,6 +212,13 @@ public:
|
||||
///
|
||||
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
|
||||
///
|
||||
@ -204,6 +233,13 @@ public:
|
||||
///
|
||||
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)
|
||||
///
|
||||
@ -211,6 +247,11 @@ public:
|
||||
///
|
||||
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
|
||||
///
|
||||
@ -218,12 +259,18 @@ public:
|
||||
///
|
||||
void setPath(const QString& path);
|
||||
|
||||
///
|
||||
/// @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/"
|
||||
///
|
||||
void setPath(const QStringList& pathElements);
|
||||
|
||||
///
|
||||
/// @brief Clear an API's path
|
||||
///
|
||||
void clearPath();
|
||||
|
||||
///
|
||||
/// @brief Append an API's path element to path set before
|
||||
///
|
||||
@ -252,6 +299,10 @@ public:
|
||||
///
|
||||
void setQuery(const QUrlQuery& query);
|
||||
|
||||
|
||||
QString getBasePath() {return _basePath;}
|
||||
QString getPath() {return _path;}
|
||||
|
||||
///
|
||||
/// @brief Execute GET request
|
||||
///
|
||||
@ -359,6 +410,14 @@ public:
|
||||
/// @param[in] timeout in milliseconds.
|
||||
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.
|
||||
///
|
||||
@ -366,6 +425,10 @@ public:
|
||||
///
|
||||
void setLogger(Logger* log) { _log = log; }
|
||||
|
||||
protected slots:
|
||||
/// Handle the SSLErrors
|
||||
void onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors);
|
||||
|
||||
private:
|
||||
|
||||
///
|
||||
@ -379,9 +442,11 @@ private:
|
||||
|
||||
httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {});
|
||||
|
||||
bool checkServerIdentity(const QSslConfiguration& sslConfig) const;
|
||||
|
||||
Logger* _log;
|
||||
|
||||
// QNetworkAccessManager object for sending REST-requests.
|
||||
/// QNetworkAccessManager object for sending REST-requests.
|
||||
QNetworkAccessManager* _networkManager;
|
||||
std::chrono::milliseconds _requestTimeout;
|
||||
|
||||
@ -394,6 +459,9 @@ private:
|
||||
QUrlQuery _query;
|
||||
|
||||
QNetworkRequest _networkRequestHeaders;
|
||||
|
||||
QString _serverIdentity;
|
||||
bool _isSeflSignedCertificateAccpeted;
|
||||
};
|
||||
|
||||
#endif // PROVIDERRESTKAPI_H
|
||||
|
@ -150,6 +150,11 @@ const int *ProviderUdpSSL::getCiphersuites() const
|
||||
return mbedtls_ssl_list_ciphersuites();
|
||||
}
|
||||
|
||||
void ProviderUdpSSL::setPSKidentity(const QString& pskIdentity)
|
||||
{
|
||||
_psk_identity = pskIdentity;
|
||||
}
|
||||
|
||||
bool ProviderUdpSSL::initNetwork()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!_streamReady || _streamPaused)
|
||||
|
@ -100,6 +100,14 @@ protected:
|
||||
///
|
||||
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
|
||||
/// values are latched.
|
||||
@ -116,6 +124,8 @@ protected:
|
||||
///
|
||||
virtual const int * getCiphersuites() const;
|
||||
|
||||
void setPSKidentity(const QString& pskIdentity);
|
||||
|
||||
private:
|
||||
|
||||
bool initConnection();
|
||||
|
@ -72,41 +72,28 @@
|
||||
"propertyOrder": 7
|
||||
},
|
||||
"panelOrderTopDown": {
|
||||
"type": "integer",
|
||||
"type": "string",
|
||||
"title": "edt_dev_spec_order_top_down_title",
|
||||
"enum": [ 0, 1 ],
|
||||
"default": 0,
|
||||
"enum": [ "top2down", "bottom2up" ],
|
||||
"default": "top2down",
|
||||
"required": true,
|
||||
"options": {
|
||||
"enum_titles": [ "edt_conf_enum_top_down", "edt_conf_enum_bottom_up" ]
|
||||
},
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 8
|
||||
},
|
||||
"panelOrderLeftRight": {
|
||||
"type": "integer",
|
||||
"type": "string",
|
||||
"title": "edt_dev_spec_order_left_right_title",
|
||||
"enum": [ 0, 1 ],
|
||||
"default": 0,
|
||||
"enum": [ "left2right", "right2left" ],
|
||||
"default": "left2right",
|
||||
"required": true,
|
||||
"options": {
|
||||
"enum_titles": [ "edt_conf_enum_left_right", "edt_conf_enum_right_left" ]
|
||||
},
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 9
|
||||
},
|
||||
"panelStartPos": {
|
||||
"type": "integer",
|
||||
"title": "edt_dev_spec_panel_start_position",
|
||||
"step": 1,
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 10
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
@ -35,26 +35,45 @@
|
||||
},
|
||||
"propertyOrder": 4
|
||||
},
|
||||
"useAPIv2": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_useAPIv2_title",
|
||||
"default": false,
|
||||
"options": {
|
||||
"hidden": true
|
||||
},
|
||||
"access": "expert",
|
||||
"propertyOrder": 5
|
||||
},
|
||||
"useEntertainmentAPI": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_useEntertainmentAPI_title",
|
||||
"default": true,
|
||||
"propertyOrder": 5
|
||||
"options": {
|
||||
"hidden": true
|
||||
},
|
||||
"propertyOrder": 6
|
||||
},
|
||||
"switchOffOnBlack": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_switchOffOnBlack_title",
|
||||
"default": false,
|
||||
"propertyOrder": 6
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 7
|
||||
},
|
||||
"restoreOriginalState": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_restoreOriginalState_title",
|
||||
"default": false,
|
||||
"propertyOrder": 7
|
||||
"propertyOrder": 8
|
||||
},
|
||||
"blackLevel": {
|
||||
"type": "number",
|
||||
@ -64,7 +83,12 @@
|
||||
"step": 0.01,
|
||||
"minimum": 0.001,
|
||||
"maximum": 1.0,
|
||||
"propertyOrder": 8
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 9
|
||||
},
|
||||
"onBlackTimeToPowerOff": {
|
||||
"type": "integer",
|
||||
@ -76,7 +100,12 @@
|
||||
"maximum": 100000,
|
||||
"default": 600,
|
||||
"required": true,
|
||||
"propertyOrder": 9
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 10
|
||||
},
|
||||
"onBlackTimeToPowerOn": {
|
||||
"type": "integer",
|
||||
@ -88,14 +117,24 @@
|
||||
"maximum": 100000,
|
||||
"default": 300,
|
||||
"required": true,
|
||||
"propertyOrder": 9
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 11
|
||||
},
|
||||
"candyGamma": {
|
||||
"type": "boolean",
|
||||
"format": "checkbox",
|
||||
"title": "edt_dev_spec_candyGamma_title",
|
||||
"default": true,
|
||||
"propertyOrder": 10
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 12
|
||||
},
|
||||
"lightIds": {
|
||||
"type": "array",
|
||||
@ -112,20 +151,23 @@
|
||||
"useEntertainmentAPI": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 11
|
||||
"propertyOrder": 13
|
||||
},
|
||||
"groupId": {
|
||||
"type": "number",
|
||||
"format": "stepper",
|
||||
"step": 1,
|
||||
"type": "string",
|
||||
"title": "edt_dev_spec_groupId_title",
|
||||
"default": 0,
|
||||
"default": "",
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useEntertainmentAPI": true
|
||||
}
|
||||
},
|
||||
"propertyOrder": 12
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 14
|
||||
},
|
||||
"brightnessFactor": {
|
||||
"type": "number",
|
||||
@ -136,7 +178,12 @@
|
||||
"minimum": 0.5,
|
||||
"maximum": 10.0,
|
||||
"access": "advanced",
|
||||
"propertyOrder": 13
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 15
|
||||
},
|
||||
"handshakeTimeoutMin": {
|
||||
"type": "number",
|
||||
@ -154,7 +201,7 @@
|
||||
"useEntertainmentAPI": true
|
||||
}
|
||||
},
|
||||
"propertyOrder": 14
|
||||
"propertyOrder": 16
|
||||
},
|
||||
"handshakeTimeoutMax": {
|
||||
"type": "number",
|
||||
@ -172,7 +219,7 @@
|
||||
"useEntertainmentAPI": true
|
||||
}
|
||||
},
|
||||
"propertyOrder": 15
|
||||
"propertyOrder": 17
|
||||
},
|
||||
"verbose": {
|
||||
"type": "boolean",
|
||||
@ -180,7 +227,7 @@
|
||||
"title": "edt_dev_spec_verbose_title",
|
||||
"default": false,
|
||||
"access": "expert",
|
||||
"propertyOrder": 16
|
||||
"propertyOrder": 18
|
||||
},
|
||||
"transitiontime": {
|
||||
"type": "number",
|
||||
@ -195,24 +242,29 @@
|
||||
"useEntertainmentAPI": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 17
|
||||
"propertyOrder": 19
|
||||
},
|
||||
"blackLightsTimeout": {
|
||||
"type": "number",
|
||||
"title": "edt_dev_spec_blackLightsTimeout_title",
|
||||
"default": 5000,
|
||||
"options": {
|
||||
"hidden": true
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 18
|
||||
"propertyOrder": 20
|
||||
},
|
||||
"brightnessThreshold": {
|
||||
"type": "number",
|
||||
"title": "edt_dev_spec_brightnessThreshold_title",
|
||||
"default": 0.0001,
|
||||
"options": {
|
||||
"hidden": true
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 19
|
||||
"propertyOrder": 21
|
||||
},
|
||||
"brightnessMin": {
|
||||
"type": "number",
|
||||
@ -223,9 +275,11 @@
|
||||
"maximum": 1.0,
|
||||
"access": "advanced",
|
||||
"options": {
|
||||
"hidden": true
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 20
|
||||
"propertyOrder": 22
|
||||
},
|
||||
"brightnessMax": {
|
||||
"type": "number",
|
||||
@ -236,9 +290,11 @@
|
||||
"maximum": 1.0,
|
||||
"access": "advanced",
|
||||
"options": {
|
||||
"hidden": true
|
||||
"dependencies": {
|
||||
"useAPIv2": false
|
||||
}
|
||||
},
|
||||
"propertyOrder": 21
|
||||
"propertyOrder": 23
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
@ -182,6 +182,7 @@ bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddr
|
||||
}
|
||||
else
|
||||
{
|
||||
QObject::disconnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, nullptr, nullptr);
|
||||
Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname));
|
||||
}
|
||||
}
|
||||
|
14
resources/ssl/philips_hue_ca.pem
Normal file
14
resources/ssl/philips_hue_ca.pem
Normal 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-----
|
Loading…
x
Reference in New Issue
Block a user