Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed reconnect bug(s) #191

Merged
merged 1 commit into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading