mirror of
https://github.com/cgiesche/streamdeck-homeassistant.git
synced 2026-04-22 03:00:19 -04:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -19,6 +19,7 @@ const touchScreenImageFactory = new EntityButtonImageFactory({width: 200, height
|
||||
const $SD = ref(null)
|
||||
const $HA = ref(null)
|
||||
const $reconnectTimeout = ref({})
|
||||
const globalSettings = ref({})
|
||||
const actionSettings = ref([])
|
||||
const buttonLongpressTimeouts = ref(new Map()) //context, timeout
|
||||
|
||||
@@ -30,36 +31,13 @@ onMounted(() => {
|
||||
window.connectElgatoStreamDeckSocket = (inPort, inPluginUUID, inRegisterEvent, inInfo) => {
|
||||
$SD.value = new StreamDeck(inPort, inPluginUUID, inRegisterEvent, inInfo, "{}");
|
||||
|
||||
$SD.value.on("globalsettings", (globalSettings) => {
|
||||
$SD.value.on("globalsettings", (inGlobalSettings) => {
|
||||
console.log("Got global settings.")
|
||||
globalSettings.value = globalSettings;
|
||||
connectHomeAssistant(globalSettings);
|
||||
globalSettings.value = inGlobalSettings;
|
||||
connectHomeAssistant();
|
||||
}
|
||||
)
|
||||
|
||||
const onHAConnected = () => {
|
||||
$HA.value.getStates(entitiyStatesChanged)
|
||||
$HA.value.subscribeEvents(entityStateChanged)
|
||||
}
|
||||
|
||||
const onHAError = (msg) => {
|
||||
console.log(`Home Assistant connection error: ${msg}`)
|
||||
showAlert()
|
||||
window.clearTimeout($reconnectTimeout)
|
||||
$reconnectTimeout.value = window.setTimeout(connectHomeAssistant, 5000)
|
||||
}
|
||||
|
||||
const onHAClosed = (msg) => {
|
||||
console.log(`Home Assistant connection closed, trying to reopen connection: ${msg}`)
|
||||
showAlert()
|
||||
window.clearTimeout($reconnectTimeout)
|
||||
$reconnectTimeout.value = window.setTimeout(connectHomeAssistant, 5000)
|
||||
}
|
||||
|
||||
const showAlert = () => {
|
||||
Object.keys(actionSettings.value).forEach(key => $SD.value.showAlert(key))
|
||||
}
|
||||
|
||||
$SD.value.on("connected", () => {
|
||||
$SD.value.requestGlobalSettings();
|
||||
})
|
||||
@@ -78,7 +56,7 @@ onMounted(() => {
|
||||
rotationPercent[context] = 0;
|
||||
actionSettings.value[context] = Settings.parse(message.payload.settings)
|
||||
if ($HA.value) {
|
||||
$HA.value.getStates(entitiyStatesChanged)
|
||||
$HA.value.getStates(entityStatesChanged)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -141,163 +119,186 @@ onMounted(() => {
|
||||
rotationAmount[context] = 0;
|
||||
actionSettings.value[context] = Settings.parse(message.payload.settings)
|
||||
if ($HA.value) {
|
||||
$HA.value.getStates(entitiyStatesChanged)
|
||||
$HA.value.getStates(entityStatesChanged)
|
||||
}
|
||||
})
|
||||
|
||||
const buttonDown = (context) => {
|
||||
const timeout = setTimeout(buttonLongPress, 300, context);
|
||||
buttonLongpressTimeouts.value.set(context, timeout)
|
||||
}
|
||||
|
||||
const buttonUp = (context) => {
|
||||
// If "long press timeout" is still present, we perform a normal press
|
||||
const lpTimeout = buttonLongpressTimeouts.value.get(context);
|
||||
if (lpTimeout) {
|
||||
clearTimeout(lpTimeout);
|
||||
buttonLongpressTimeouts.value.delete(context)
|
||||
buttonShortPress(context);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonShortPress = (context) => {
|
||||
let settings = actionSettings.value[context];
|
||||
callService(context, settings.button.serviceShortPress);
|
||||
}
|
||||
|
||||
const buttonLongPress = (context) => {
|
||||
buttonLongpressTimeouts.value.delete(context);
|
||||
let settings = actionSettings.value[context];
|
||||
if (settings.button.serviceLongPress.serviceId) {
|
||||
callService(context, settings.button.serviceLongPress);
|
||||
} else {
|
||||
callService(context, settings.button.serviceShortPress);
|
||||
}
|
||||
}
|
||||
|
||||
const callService = (context, serviceToCall, serviceDataAttributes = {}) => {
|
||||
if ($HA.value) {
|
||||
if (serviceToCall["serviceId"]) {
|
||||
try {
|
||||
const serviceIdParts = serviceToCall.serviceId.split('.');
|
||||
|
||||
let serviceData = null;
|
||||
if (serviceToCall.serviceData) {
|
||||
let renderedServiceData = nunjucks.renderString(serviceToCall.serviceData, serviceDataAttributes)
|
||||
serviceData = JSON.parse(renderedServiceData);
|
||||
}
|
||||
|
||||
$HA.value.callService(serviceIdParts[1], serviceIdParts[0], serviceToCall.entityId, serviceData)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$SD.value.showAlert(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const connectHomeAssistant = (globalSettings) => {
|
||||
console.log("Connecting to Home Assistant")
|
||||
if (globalSettings.serverUrl && globalSettings.accessToken) {
|
||||
if ($HA.value) {
|
||||
$HA.value.close();
|
||||
}
|
||||
console.log("Connecting to Home Assistant " + globalSettings.serverUrl)
|
||||
$HA.value = new Homeassistant(globalSettings.serverUrl, globalSettings.accessToken, onHAConnected, onHAError, onHAClosed)
|
||||
}
|
||||
}
|
||||
|
||||
const entitiyStatesChanged = (event) => {
|
||||
event.forEach(updateState)
|
||||
}
|
||||
|
||||
const entityStateChanged = (event) => {
|
||||
if (event) {
|
||||
let newState = event.data.new_state;
|
||||
updateState(newState)
|
||||
}
|
||||
}
|
||||
|
||||
const updateState = (stateMessage) => {
|
||||
if (!stateMessage.entity_id) {
|
||||
console.log(`Missing entity_id in updated state: ${stateMessage}`)
|
||||
return;
|
||||
}
|
||||
|
||||
let domain = stateMessage.entity_id.split('.')[0]
|
||||
let changedContexts = Object.keys(actionSettings.value).filter(key => actionSettings.value[key].display.entityId === stateMessage.entity_id)
|
||||
|
||||
changedContexts.forEach(context => {
|
||||
try {
|
||||
if (stateMessage.last_updated != null) stateMessage.attributes["last_updated"] = new Date(stateMessage.last_updated).toLocaleTimeString();
|
||||
if (stateMessage.last_changed != null) stateMessage.attributes["last_changed"] = new Date(stateMessage.last_changed).toLocaleTimeString();
|
||||
|
||||
updateContextState(context, domain, stateMessage);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$SD.value.setImage(context, null);
|
||||
$SD.value.showAlert(context);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateContextState = (currentContext, domain, stateObject) => {
|
||||
let contextSettings = actionSettings.value[currentContext]
|
||||
let labelTemplates = null;
|
||||
|
||||
if (contextSettings.display.useCustomButtonLabels && contextSettings.display.buttonLabels) {
|
||||
labelTemplates = contextSettings.display.buttonLabels.split("\n");
|
||||
}
|
||||
let entityConfig = entityConfigFactory.determineConfig(domain, stateObject, labelTemplates)
|
||||
|
||||
entityConfig.isAction = contextSettings.button.serviceShortPress.serviceId && (contextSettings.display.enableServiceIndicator === undefined || contextSettings.display.enableServiceIndicator) // undefined = on by default
|
||||
entityConfig.isMultiAction = contextSettings.button.serviceLongPress.serviceId && (contextSettings.display.enableServiceIndicator === undefined || contextSettings.display.enableServiceIndicator) // undefined = on by default
|
||||
entityConfig.hideIcon = contextSettings.display.hideIcon
|
||||
|
||||
if (contextSettings.display.useStateImagesForOnOffStates) {
|
||||
switch (stateObject.state) {
|
||||
case "on":
|
||||
case "playing":
|
||||
case "open":
|
||||
case "opening":
|
||||
case "home":
|
||||
case "locked":
|
||||
case "active":
|
||||
console.log("Setting state of " + currentContext + " to 1")
|
||||
$SD.value.setState(currentContext, 1);
|
||||
break;
|
||||
default:
|
||||
console.log("Setting state of " + currentContext + " to 0")
|
||||
$SD.value.setState(currentContext, 0);
|
||||
}
|
||||
} else {
|
||||
if (contextSettings.controllerType === 'Encoder') {
|
||||
const buttonImage = touchScreenImageFactory.createButton(entityConfig);
|
||||
setButtonSVG(buttonImage, currentContext)
|
||||
} else {
|
||||
const buttonImage = buttonImageFactory.createButton(entityConfig);
|
||||
setButtonSVG(buttonImage, currentContext)
|
||||
}
|
||||
}
|
||||
|
||||
if (contextSettings.display.useCustomTitle) {
|
||||
let state = stateObject.state;
|
||||
let stateAttributes = stateObject.attributes;
|
||||
|
||||
const customTitle = nunjucks.renderString(contextSettings.display.buttonTitle, {...{state}, ...stateAttributes})
|
||||
$SD.value.setTitle(currentContext, customTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setButtonSVG = (svg, changedContext) => {
|
||||
const image = "data:image/svg+xml;charset=utf8," + svg;
|
||||
if (actionSettings.value[changedContext].controllerType === 'Encoder') {
|
||||
$SD.value.setFeedback(changedContext, {"full-canvas": image, "canvas": null, "title": ""})
|
||||
} else {
|
||||
$SD.value.setImage(changedContext, image)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function connectHomeAssistant() {
|
||||
console.log("Connecting to Home Assistant")
|
||||
if (globalSettings.value.serverUrl && globalSettings.value.accessToken) {
|
||||
if ($HA.value) {
|
||||
$HA.value.close();
|
||||
}
|
||||
console.log("Connecting to Home Assistant " + globalSettings.value.serverUrl)
|
||||
$HA.value = new Homeassistant(globalSettings.value.serverUrl, globalSettings.value.accessToken, onHAConnected, onHAError, onHAClosed)
|
||||
}
|
||||
}
|
||||
|
||||
const onHAConnected = () => {
|
||||
$HA.value.getStates(entityStatesChanged)
|
||||
$HA.value.subscribeEvents(entityStateChanged)
|
||||
}
|
||||
|
||||
function onHAError(msg) {
|
||||
showAlert()
|
||||
console.log(`Home Assistant connection error: ${msg}`)
|
||||
window.clearTimeout($reconnectTimeout)
|
||||
$reconnectTimeout.value = window.setTimeout(connectHomeAssistant, 5000)
|
||||
}
|
||||
|
||||
function onHAClosed(msg) {
|
||||
showAlert()
|
||||
console.log(`Home Assistant connection closed, trying to reopen connection: ${msg}`)
|
||||
window.clearTimeout($reconnectTimeout)
|
||||
$reconnectTimeout.value = window.setTimeout(connectHomeAssistant, 5000)
|
||||
}
|
||||
|
||||
function showAlert() {
|
||||
Object.keys(actionSettings.value).forEach(key => $SD.value.showAlert(key))
|
||||
}
|
||||
|
||||
function entityStatesChanged(event) {
|
||||
event.forEach(updateState)
|
||||
}
|
||||
|
||||
function entityStateChanged(event) {
|
||||
if (event) {
|
||||
let newState = event.data.new_state;
|
||||
updateState(newState)
|
||||
}
|
||||
}
|
||||
|
||||
function updateState(stateMessage) {
|
||||
if (!stateMessage.entity_id) {
|
||||
console.log(`Missing entity_id in updated state: ${stateMessage}`)
|
||||
return;
|
||||
}
|
||||
|
||||
let domain = stateMessage.entity_id.split('.')[0]
|
||||
let changedContexts = Object.keys(actionSettings.value).filter(key => actionSettings.value[key].display.entityId === stateMessage.entity_id)
|
||||
|
||||
changedContexts.forEach(context => {
|
||||
try {
|
||||
if (stateMessage.last_updated != null) stateMessage.attributes["last_updated"] = new Date(stateMessage.last_updated).toLocaleTimeString();
|
||||
if (stateMessage.last_changed != null) stateMessage.attributes["last_changed"] = new Date(stateMessage.last_changed).toLocaleTimeString();
|
||||
|
||||
updateContextState(context, domain, stateMessage);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$SD.value.setImage(context, null);
|
||||
$SD.value.showAlert(context);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateContextState(currentContext, domain, stateObject) {
|
||||
let contextSettings = actionSettings.value[currentContext]
|
||||
let labelTemplates = null;
|
||||
|
||||
if (contextSettings.display.useCustomButtonLabels && contextSettings.display.buttonLabels) {
|
||||
labelTemplates = contextSettings.display.buttonLabels.split("\n");
|
||||
}
|
||||
let entityConfig = entityConfigFactory.determineConfig(domain, stateObject, labelTemplates)
|
||||
|
||||
entityConfig.isAction = contextSettings.button.serviceShortPress.serviceId && (contextSettings.display.enableServiceIndicator === undefined || contextSettings.display.enableServiceIndicator) // undefined = on by default
|
||||
entityConfig.isMultiAction = contextSettings.button.serviceLongPress.serviceId && (contextSettings.display.enableServiceIndicator === undefined || contextSettings.display.enableServiceIndicator) // undefined = on by default
|
||||
entityConfig.hideIcon = contextSettings.display.hideIcon
|
||||
|
||||
if (contextSettings.display.useStateImagesForOnOffStates) {
|
||||
switch (stateObject.state) {
|
||||
case "on":
|
||||
case "playing":
|
||||
case "open":
|
||||
case "opening":
|
||||
case "home":
|
||||
case "locked":
|
||||
case "active":
|
||||
console.log("Setting state of " + currentContext + " to 1")
|
||||
$SD.value.setState(currentContext, 1);
|
||||
break;
|
||||
default:
|
||||
console.log("Setting state of " + currentContext + " to 0")
|
||||
$SD.value.setState(currentContext, 0);
|
||||
}
|
||||
} else {
|
||||
if (contextSettings.controllerType === 'Encoder') {
|
||||
const buttonImage = touchScreenImageFactory.createButton(entityConfig);
|
||||
setButtonSVG(buttonImage, currentContext)
|
||||
} else {
|
||||
const buttonImage = buttonImageFactory.createButton(entityConfig);
|
||||
setButtonSVG(buttonImage, currentContext)
|
||||
}
|
||||
}
|
||||
|
||||
if (contextSettings.display.useCustomTitle) {
|
||||
let state = stateObject.state;
|
||||
let stateAttributes = stateObject.attributes;
|
||||
|
||||
const customTitle = nunjucks.renderString(contextSettings.display.buttonTitle, {...{state}, ...stateAttributes})
|
||||
$SD.value.setTitle(currentContext, customTitle);
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonSVG(svg, changedContext) {
|
||||
const image = "data:image/svg+xml;charset=utf8," + svg;
|
||||
if (actionSettings.value[changedContext].controllerType === 'Encoder') {
|
||||
$SD.value.setFeedback(changedContext, {"full-canvas": image, "canvas": null, "title": ""})
|
||||
} else {
|
||||
$SD.value.setImage(changedContext, image)
|
||||
}
|
||||
}
|
||||
|
||||
function buttonDown(context) {
|
||||
const timeout = setTimeout(buttonLongPress, 300, context);
|
||||
buttonLongpressTimeouts.value.set(context, timeout)
|
||||
}
|
||||
|
||||
function buttonUp(context) {
|
||||
// If "long press timeout" is still present, we perform a normal press
|
||||
const lpTimeout = buttonLongpressTimeouts.value.get(context);
|
||||
if (lpTimeout) {
|
||||
clearTimeout(lpTimeout);
|
||||
buttonLongpressTimeouts.value.delete(context)
|
||||
buttonShortPress(context);
|
||||
}
|
||||
}
|
||||
|
||||
function buttonShortPress(context) {
|
||||
let settings = actionSettings.value[context];
|
||||
callService(context, settings.button.serviceShortPress);
|
||||
}
|
||||
|
||||
function buttonLongPress(context) {
|
||||
buttonLongpressTimeouts.value.delete(context);
|
||||
let settings = actionSettings.value[context];
|
||||
if (settings.button.serviceLongPress.serviceId) {
|
||||
callService(context, settings.button.serviceLongPress);
|
||||
} else {
|
||||
callService(context, settings.button.serviceShortPress);
|
||||
}
|
||||
}
|
||||
|
||||
function callService(context, serviceToCall, serviceDataAttributes = {}) {
|
||||
if ($HA.value) {
|
||||
if (serviceToCall["serviceId"]) {
|
||||
try {
|
||||
const serviceIdParts = serviceToCall.serviceId.split('.');
|
||||
|
||||
let serviceData = null;
|
||||
if (serviceToCall.serviceData) {
|
||||
let renderedServiceData = nunjucks.renderString(serviceToCall.serviceData, serviceDataAttributes)
|
||||
serviceData = JSON.parse(renderedServiceData);
|
||||
}
|
||||
|
||||
$HA.value.callService(serviceIdParts[1], serviceIdParts[0], serviceToCall.entityId, serviceData)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$SD.value.showAlert(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="domainEntities.length > 0" class="mb-3">
|
||||
<label class="form-label" for="entity">Entity (Optional)</label>
|
||||
<label class="form-label" for="entity">Entity</label>
|
||||
<div class="input-group">
|
||||
<select id="entity"
|
||||
:value="modelValue.entityId" class="form-select form-select-sm"
|
||||
|
||||
Reference in New Issue
Block a user