Skip to content

Commit

Permalink
Add Home Assistant Lights support (#1763)
Browse files Browse the repository at this point in the history
* New HomeAssistant LEDDevice

* Fix typos

* Ping Qt for Windows to 6.7 until aqtinstaller is fixed

* Fix HA default port handling

* HA - Update default latchtime and range

* Add HA Wizard and light selection

* Naming consistency

* Fix "Selected Hyperion instance is not running"

* CodeQL findings

* HA - allow to overwrite brightness by HA yes or no

* HA - Support switch off on black

* HA - Add transition time
  • Loading branch information
Lord-Grey committed Aug 25, 2024
1 parent df2b2b2 commit 4f1b95e
Show file tree
Hide file tree
Showing 19 changed files with 1,020 additions and 76 deletions.
1 change: 0 additions & 1 deletion .github/workflows/qt5_6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ jobs:
version: ${{ inputs.qt_version == '6' && '6.7' || '5.15.*' }}
target: 'desktop'
modules: ${{ inputs.qt_version == '6' && 'qtserialport' || '' }}
arch: 'win64_msvc2019_64'
cache: 'true'
cache-key-prefix: 'cache-qt-windows'

Expand Down
13 changes: 9 additions & 4 deletions assets/webconfig/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"conf_leds_layout_cl_bottomleft": "Bottom Left (Corner)",
"conf_leds_layout_cl_bottomright": "Bottom Right (Corner)",
"conf_leds_layout_cl_cornergap": "Corner Gap",
"conf_leds_layout_cl_disabled": "Deactivated",
"conf_leds_layout_cl_edgegap": "Edge Gap",
"conf_leds_layout_cl_entertainment": "Entertainment Area",
"conf_leds_layout_cl_entertainment_center": "Entertainment Area Center",
Expand All @@ -103,6 +104,7 @@
"conf_leds_layout_cl_lightPosBottomLeft112": "Bottom: 0 - 50% from Left",
"conf_leds_layout_cl_lightPosBottomLeft121": "Bottom: 50 - 100% from Left",
"conf_leds_layout_cl_lightPosBottomLeftNewMid": "Bottom: 25 - 75% from Left",
"conf_leds_layout_cl_lightPosEntire": "Whole picture",
"conf_leds_layout_cl_lightPosTopLeft112": "Top: 0 - 50% from Left",
"conf_leds_layout_cl_lightPosTopLeft121": "Top: 50 - 100% from Left",
"conf_leds_layout_cl_lightPosTopLeftNewMid": "Top: 25 - 75% from Left",
Expand Down Expand Up @@ -661,13 +663,14 @@
"edt_dev_spec_colorComponent_title": "Colour component",
"edt_dev_spec_debugLevel_title": "Debug Level",
"edt_dev_spec_delayAfterConnect_title": "Delay after connect",
"edt_dev_spec_devices_discovered_none": "No Devices Discovered",
"edt_dev_spec_devices_discovered_title": "Devices Discovered",
"edt_dev_spec_devices_discovered_none": "No Devices discovered",
"edt_dev_spec_devices_discovered_title": "Devices discovered",
"edt_dev_spec_devices_discovered_title_info": "Select your LED-Device discovered",
"edt_dev_spec_devices_discovered_title_info_custom": "Select your LED-Device discovered or configure a custome one",
"edt_dev_spec_devices_discovery_inprogress": "Discovery in progress",
"edt_dev_spec_dithering_title": "Dithering",
"edt_dev_spec_dmaNumber_title": "DMA channel",
"edt_dev_spec_fullBrightnessAtStart_title": "Full brightness at start",
"edt_dev_spec_gamma_title": "Gamma",
"edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level",
"edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold",
Expand All @@ -685,6 +688,7 @@
"edt_dev_spec_ledType_title": "LED Type",
"edt_dev_spec_lightid_itemtitle": "ID",
"edt_dev_spec_lightid_title": "Light ID(s)",
"edt_dev_spec_lights_discovered_none": "No Lights discovered",
"edt_dev_spec_lights_itemtitle": "Light",
"edt_dev_spec_lights_name": "Name",
"edt_dev_spec_lights_title": "Light(s)",
Expand Down Expand Up @@ -1184,9 +1188,10 @@
"wiz_identify_tip": "Identify configured device by lighting it up",
"wiz_identify_light": "Identify $1",
"wiz_layout": "Generate Layout",
"wiz_layout_led_position_title": "LED position",
"wiz_layout_led_positions_title": "LED position layout wizard",
"wiz_layout_led_positions_expl": "Select the LED position for the $1 controller lights.",
"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",
Expand Down
2 changes: 1 addition & 1 deletion assets/webconfig/js/content_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ $(document).ready(function () {
removeStorage("loginToken");
requestRequiresDefaultPasswortChange();
}
else if (event.reason == "Selected Hyperion instance isn't running") {
else if (event.reason == "Selected Hyperion instance is not running") {
//Switch to default instance
instanceSwitch(0);
} else {
Expand Down
123 changes: 119 additions & 4 deletions assets/webconfig/js/content_leds.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var devSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk68
var devFTDI = ['apa102_ftdi', 'sk6812_ftdi', 'ws2812_ftdi'];
var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster'];
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'homeassistant', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
var devSerial = ['adalight', 'dmx', 'atmo', 'sedu', 'tpm2', 'karate'];
var devHID = ['hyperionusbasp', 'lightpack', 'paintpack', 'rawhid'];

Expand Down Expand Up @@ -1100,6 +1100,7 @@ $(document).ready(function () {
switch (ledType) {
case "wled":
case "cololight":
case "homeassistant":
case "nanoleaf":
showAllDeviceInputOptions("hostList", false);
case "apa102":
Expand Down Expand Up @@ -1279,7 +1280,21 @@ $(document).ready(function () {
if (hostList !== "SELECT") {
const host = conf_editor.getEditor("root.specificOptions.host").getValue();
const token = conf_editor.getEditor("root.specificOptions.token").getValue();
if (host !== "" && token !== "") {
if (host !== "" && token !== "" && entityIds) {
canIdentify = true;
canSave = true;
}
}
}
break;

case "homeassistant": {
const hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
if (hostList !== "SELECT") {
const host = conf_editor.getEditor("root.specificOptions.host").getValue();
const token = conf_editor.getEditor("root.specificOptions.token").getValue();
const entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
if (host !== "" && token !== "" && entityIds) {
canIdentify = true;
canSave = true;
}
Expand Down Expand Up @@ -1387,6 +1402,16 @@ $(document).ready(function () {
getProperties_device(ledType, host, params);
break;

case "homeassistant":
var token = conf_editor.getEditor("root.specificOptions.token").getValue();
if (token === "") {
return;
}

params = { host: host, token: token, filter: "states" };
getProperties_device(ledType, host, params);
break;

case "nanoleaf":
$('#btn_wiz_holder').show();

Expand Down Expand Up @@ -1552,6 +1577,14 @@ $(document).ready(function () {

var host = "";
switch (ledType) {
case "homeassistant":
host = conf_editor.getEditor("root.specificOptions.host").getValue();
if (host === "") {
return
}
params = { host: host, token: token, filter: "states" };
break;

case "nanoleaf":
host = conf_editor.getEditor("root.specificOptions.host").getValue();
if (host === "") {
Expand Down Expand Up @@ -1654,6 +1687,16 @@ $(document).ready(function () {
default:
}
});

conf_editor.watch('root.specificOptions.entityIds', () => {
var entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
if (entityIds.length > 0) {
$('#btn_test_controller').prop('disabled', false);
} else {
$('#btn_test_controller').prop('disabled', true);
}
});

});

//philipshueentertainment backward fix
Expand Down Expand Up @@ -1684,7 +1727,7 @@ $(document).ready(function () {
else if ($.inArray(ledDevices[idx], devHID) != -1)
optArr[4].push(ledDevices[idx]);
else if (ledDevices[idx].endsWith("_ftdi")) {
var title = ledDevices[idx].replace('_ftdi','');
var title = ledDevices[idx].replace('_ftdi', '');
optArr[5].push(ledDevices[idx] + ":" + title);
}
else
Expand Down Expand Up @@ -1744,6 +1787,13 @@ $(document).ready(function () {
params = { host: host };
break;

case "homeassistant":
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var token = conf_editor.getEditor("root.specificOptions.token").getValue();
const entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
params = { host: host, token: token, entity_id: entityIds };
break;

case "nanoleaf":
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var token = conf_editor.getEditor("root.specificOptions.token").getValue();
Expand Down Expand Up @@ -1878,6 +1928,7 @@ function saveLedConfig(genDefLayout = false) {
}
break;

case "homeassistant":
case "nanoleaf":
case "wled":
case "yeelight":
Expand Down Expand Up @@ -2311,6 +2362,12 @@ function updateElements(ledType, key) {
}
break;

case "homeassistant":
updateElementsHomeAssistant(ledType, key);
hardwareLedCount = 1;
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break;

case "atmo":
case "karate":
var ledProperties = devicesProperties[ledType][key];
Expand Down Expand Up @@ -2438,6 +2495,63 @@ function validateWledLedCount(hardwareLedCount) {
}
}

function updateElementsHomeAssistant(ledType, key) {

// Get configured device's details
var configuredDeviceType = window.serverConfig.device.type;
var configuredHost = window.serverConfig.device.host;
var host = conf_editor.getEditor("root.specificOptions.host").getValue();

// New light selection list values
var enumVals = [];
var enumTitleVals = [];
var enumDefaultVal = [];

if (devicesProperties[ledType] && devicesProperties[ledType][key]) {
var ledDeviceProperties = devicesProperties[ledType][key];

if (!jQuery.isEmptyObject(ledDeviceProperties)) {
if (ledDeviceProperties && ledDeviceProperties.lightEntities) {


for (const light of ledDeviceProperties.lightEntities) {
enumVals.push(light.entity_id);
enumTitleVals.push(light.attributes.friendly_name);
}

}
}
}

// Select configured device
if (configuredDeviceType == ledType && configuredHost == host) {
let configuredEntityIds = window.serverConfig.device.entityIds;
for (const light of configuredEntityIds) {
if ($.inArray(enumVals, light) != -1) {
enumVals.push(light);
}
enumDefaultVal.push(light);
}
}

if (enumVals.length < 1) {
enumVals.push("NONE");
enumTitleVals.push($.i18n('edt_dev_spec_lights_discovered_none'));
}
else {
$('#btn_wiz_holder').show();
}


let addSchemaElements = {
"uniqueItems": true,
"minItems": 1,
"required": true
};

updateJsonEditorMultiSelection(conf_editor, 'root.specificOptions', 'entityIds', addSchemaElements, enumVals, enumTitleVals, enumDefaultVal);
}

function updateElementsWled(ledType, key) {

// Get configured device's details
Expand Down Expand Up @@ -2533,6 +2647,7 @@ 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
Expand Down Expand Up @@ -2591,7 +2706,7 @@ function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRi
29: { name: "4DLightstrip", led: true, sideLengthX: 50, sideLengthY: 50 },
30: { name: "Skylight Panel", led: true, sideLengthX: 180, sideLengthY: 180 },
31: { name: "SkylightControllerPrimary", led: true, sideLengthX: 180, sideLengthY: 180 },
32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },
32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },
999: { name: "Unknown", led: true, sideLengthX: 100, sideLengthY: 100 }
};

Expand Down
30 changes: 15 additions & 15 deletions assets/webconfig/js/ui_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ function showInfoDialog(type, header, message) {
$(document).on('click', '[data-dismiss-modal]', function () {
var target = $(this).data('dismiss-modal');
$($.find(target)).modal('hide');
});
});
}

function createHintH(type, text, container) {
Expand Down Expand Up @@ -478,7 +478,7 @@ function createJsonEditor(container, schema, setconfig, usePanel, arrayre) {
return editor;
}

function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVals, newTitelVals, newDefaultVal, addSelect, addCustom, addCustomAsFirst, customText) {
function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVals, newTitleVals, newDefaultVal, addSelect, addCustom, addCustomAsFirst, customText) {
var editor = rootEditor.getEditor(path);
var orginalProperties = editor.schema.properties[key];

Expand Down Expand Up @@ -516,8 +516,8 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa

if (addCustom) {

if (newTitelVals.length === 0) {
newTitelVals = [...newEnumVals];
if (newTitleVals.length === 0) {
newTitleVals = [...newEnumVals];
}

if (!!!customText) {
Expand All @@ -526,10 +526,10 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa

if (addCustomAsFirst) {
newEnumVals.unshift("CUSTOM");
newTitelVals.unshift(customText);
newTitleVals.unshift(customText);
} else {
newEnumVals.push("CUSTOM");
newTitelVals.push(customText);
newTitleVals.push(customText);
}

if (newSchema[key].options.infoText) {
Expand All @@ -540,16 +540,16 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa

if (addSelect) {
newEnumVals.unshift("SELECT");
newTitelVals.unshift("edt_conf_enum_please_select");
newTitleVals.unshift("edt_conf_enum_please_select");
newDefaultVal = "SELECT";
}

if (newEnumVals) {
newSchema[key]["enum"] = newEnumVals;
}

if (newTitelVals) {
newSchema[key]["options"]["enum_titles"] = newTitelVals;
if (newTitleVals) {
newSchema[key]["options"]["enum_titles"] = newTitleVals;
}
if (newDefaultVal) {
newSchema[key]["default"] = newDefaultVal;
Expand All @@ -572,7 +572,7 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
rootEditor.notifyWatchers(path + "." + key);
}

function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newEnumVals, newTitelVals, newDefaultVal) {
function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newEnumVals, newTitleVals, newDefaultVal) {
var editor = rootEditor.getEditor(path);
var orginalProperties = editor.schema.properties[key];

Expand Down Expand Up @@ -617,8 +617,8 @@ function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newE
newSchema[key]["items"]["enum"] = newEnumVals;
}

if (newTitelVals) {
newSchema[key]["items"]["options"]["enum_titles"] = newTitelVals;
if (newTitleVals) {
newSchema[key]["items"]["options"]["enum_titles"] = newTitleVals;
}

if (newDefaultVal) {
Expand Down Expand Up @@ -923,8 +923,8 @@ function createTableRow(list, head, align) {
el.style.verticalAlign = "middle";

var purifyConfig = {
ADD_TAGS: ['button'],
ADD_ATTR: ['onclick']
ADD_TAGS: ['button'],
ADD_ATTR: ['onclick']
};
el.innerHTML = DOMPurify.sanitize(list[i], purifyConfig);
row.appendChild(el);
Expand Down Expand Up @@ -1403,7 +1403,7 @@ function loadScript(src, callback, ...params) {
if (isScriptLoaded(src)) {
debugMessage('Script ' + src + ' already loaded');
if (callback && typeof callback === 'function') {
callback( ...params);
callback(...params);
}
return;
}
Expand Down
Loading

0 comments on commit 4f1b95e

Please sign in to comment.