Skip to content

Commit

Permalink
Merge pull request #191 from cgiesche/streamdeck-homeassistant-71
Browse files Browse the repository at this point in the history
Fixed reconnect bug(s)
  • Loading branch information
cgiesche committed Nov 27, 2023
2 parents 256513a + 0006edb commit 4f080d3
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 166 deletions.
331 changes: 166 additions & 165 deletions src/components/PluginComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
})
Expand All @@ -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)
}
})
Expand Down Expand Up @@ -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);
}
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)
}
}
const buttonShortPress = (context) => {
let settings = actionSettings.value[context];
callService(context, settings.button.serviceShortPress);
}
function updateState(stateMessage) {
if (!stateMessage.entity_id) {
console.log(`Missing entity_id in updated state: ${stateMessage}`)
return;
}
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);
}
}
let domain = stateMessage.entity_id.split('.')[0]
let changedContexts = Object.keys(actionSettings.value).filter(key => actionSettings.value[key].display.entityId === stateMessage.entity_id)
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);
}
}
}
}
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();
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)
}
updateContextState(context, domain, stateMessage);
} catch (e) {
console.error(e)
$SD.value.setImage(context, null);
$SD.value.showAlert(context);
}
})
}
const entitiyStatesChanged = (event) => {
event.forEach(updateState)
}
function updateContextState(currentContext, domain, stateObject) {
let contextSettings = actionSettings.value[currentContext]
let labelTemplates = null;
const entityStateChanged = (event) => {
if (event) {
let newState = event.data.new_state;
updateState(newState)
}
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);
}
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);
}
})
} else {
if (contextSettings.controllerType === 'Encoder') {
const buttonImage = touchScreenImageFactory.createButton(entityConfig);
setButtonSVG(buttonImage, currentContext)
} else {
const buttonImage = buttonImageFactory.createButton(entityConfig);
setButtonSVG(buttonImage, currentContext)
}
}
const updateContextState = (currentContext, domain, stateObject) => {
let contextSettings = actionSettings.value[currentContext]
let labelTemplates = null;
if (contextSettings.display.useCustomTitle) {
let state = stateObject.state;
let stateAttributes = stateObject.attributes;
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)
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);
}
}
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);
$HA.value.callService(serviceIdParts[1], serviceIdParts[0], serviceToCall.entityId, serviceData)
} catch (e) {
console.error(e)
$SD.value.showAlert(context);
}
}
}
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)
}
}
})
}
</script>
2 changes: 1 addition & 1 deletion src/components/ServiceCallConfiguration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 4f080d3

Please sign in to comment.