Skip to content

Commit

Permalink
Merge pull request #182 from cgiesche/streamdeck-homeassistant-117
Browse files Browse the repository at this point in the history
Streamdeck homeassistant 117
  • Loading branch information
cgiesche authored Nov 5, 2023
2 parents e8fd4a0 + 7d875f4 commit 2ae26ac
Show file tree
Hide file tree
Showing 12 changed files with 691 additions and 547 deletions.
7 changes: 6 additions & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
}
],
"SupportedInMultiActions": true,
"UUID": "de.perdoctus.streamdeck.homeassistant.entity"
"UUID": "de.perdoctus.streamdeck.homeassistant.entity",
"Controllers": ["Keypad", "Encoder"],
"Encoder": {
"layout": "$A0",
"StackColor": "#AABBCC"
}
},

{
Expand Down
85 changes: 67 additions & 18 deletions src/components/PiComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
</div>

<div v-if="haConnectionState === 'connected'" class="clearfix mb-3">
<h1>Button appearance</h1>
<h1>{{ controllerType }} appearance</h1>

<div class="mb-3">
<label class="form-label" for="accessToken">Domain</label>
Expand Down Expand Up @@ -115,19 +115,54 @@
<label class="form-check-label" for="chkHideIcon">Hide icon</label>
</div>

<h1>Button actions</h1>

<h6>Short Press</h6>
<ServiceCallConfiguration v-model="serviceShortPress"
:available-entities="availableEntities"
:available-services="availableServices"

></ServiceCallConfiguration>

<h6>Long Press</h6>
<ServiceCallConfiguration v-model="serviceLongPress"
:available-entities="availableEntities"
:available-services="availableServices"></ServiceCallConfiguration>
<h1>{{ controllerType }} actions</h1>

<AccordeonComponent id="presses" class="mb-2">
<AccordeonItem accordeon-id="presses" item-id="shortPress" title="Short Press">
<ServiceCallConfiguration v-model="serviceShortPress" :available-entities="availableEntities"
:available-services="availableServices"
class="mb-2"

></ServiceCallConfiguration>
</AccordeonItem>

<AccordeonItem accordeon-id="presses" item-id="longPress" title="Long Press">
<ServiceCallConfiguration v-model="serviceLongPress" :available-entities="availableEntities"
:available-services="availableServices"
class="mb-2"></ServiceCallConfiguration>

</AccordeonItem>

<template v-if="controllerType === 'Encoder'">
<AccordeonItem accordeon-id="presses" item-id="touch" title="Screen tap">
<ServiceCallConfiguration v-model="serviceTap" :available-entities="availableEntities"
:available-services="availableServices"
class="mb-2"></ServiceCallConfiguration>
</AccordeonItem>

<AccordeonItem accordeon-id="presses" item-id="dialRotate" title="Rotation">
<ServiceCallConfiguration v-model="serviceRotation"
:available-entities="availableEntities"
:available-services="availableServices"></ServiceCallConfiguration>
<details class="mb-2">
<summary>Available variables</summary>
<div class="form-text">
<span v-pre class="text-info font-monospace">{{ ticks }}</span> - The number of ticks the dial was
rotated (negative value for left turn, positive value for right turn).
</div>
<div class="form-text">
<span v-pre class="text-info font-monospace">{{ rotationPercent }}</span> - A number between 0 and 100
that represents the rotation percentage value of the dial.
</div>
<div class="form-text">
<span v-pre class="text-info font-monospace">{{ rotationAbsolute }}</span> - A number between 0 and 255
that represents the absolute rotation value of the dial.
</div>
</details>
</AccordeonItem>
</template>

</AccordeonComponent>

<button id="mybutton" :disabled="!domain" class="btn btn-sm btn-primary float-end"
v-on:click="saveSettings">
Expand All @@ -148,6 +183,8 @@ import {Service} from "@/modules/pi/service";
import {computed, onMounted, ref} from "vue";
import ServiceCallConfiguration from "@/components/ServiceCallConfiguration.vue";
import {ObjectUtils} from "@/modules/common/utils";
import AccordeonComponent from "@/components/accordeon/BootstrapAccordeon.vue";
import AccordeonItem from "@/components/accordeon/BootstrapAccordeonItem.vue";
let $HA = null;
let $SD = null;
Expand All @@ -156,8 +193,12 @@ const serverUrl = ref("")
const accessToken = ref("")
const domain = ref("")
const entity = ref("")
const serviceShortPress = ref({})
const serviceLongPress = ref({})
const serviceTap = ref({})
const serviceRotation = ref({})
const useCustomTitle = ref(false)
const buttonTitle = ref("{{friendly_name}}")
const useStateImagesForOnOffStates = ref(false) // determined by action ID (manifest)
Expand All @@ -173,16 +214,18 @@ const currentStates = ref([])
const haConnectionState = ref("disconnected") // disconnected, connecting, connected
const haError = ref("")
const controllerType = ref("")
onMounted(() => {
window.connectElgatoStreamDeckSocket = (inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) => {
$SD = new StreamDeck(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo);
// Dual State entity (custom icons for on/off)
const inActionInfoObject = JSON.parse(inActionInfo);
if (inActionInfoObject["action"] === "de.perdoctus.streamdeck.homeassistant.dual-state-entity") {
useStateImagesForOnOffStates.value = true;
}
useStateImagesForOnOffStates.value = inActionInfoObject["action"] === "de.perdoctus.streamdeck.homeassistant.dual-state-entity";
controllerType.value = inActionInfoObject.payload.controller
$SD.on("globalsettings", (globalSettings) => {
if (globalSettings) {
Expand Down Expand Up @@ -210,6 +253,8 @@ onMounted(() => {
buttonLabels.value = settings["display"]["buttonLabels"]
serviceShortPress.value = settings["button"]["serviceShortPress"]
serviceLongPress.value = settings["button"]["serviceLongPress"]
serviceTap.value = settings["button"]["serviceTap"]
serviceRotation.value = settings["button"]["serviceRotation"]
})
}
})
Expand Down Expand Up @@ -301,7 +346,9 @@ function saveGlobalSettings() {
function saveSettings() {
let settings = {
version: 3,
version: 4,
controllerType: controllerType.value,
display: {
domain: domain.value,
Expand All @@ -318,6 +365,8 @@ function saveSettings() {
button: {
serviceShortPress: serviceShortPress.value,
serviceLongPress: serviceLongPress.value,
serviceTap: serviceTap.value,
serviceRotation: serviceRotation.value
}
}
Expand Down
110 changes: 86 additions & 24 deletions src/components/PluginComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@
<script setup>
import {StreamDeck} from "@/modules/common/streamdeck";
import {Homeassistant} from "@/modules/homeassistant/homeassistant";
import {EntityButtonImageFactory, EntityConfigFactory} from "@/modules/plugin/entityButtonImageFactory";
import {EntityButtonImageFactory} from "@/modules/plugin/entityButtonImageFactory";
import nunjucks from "nunjucks"
import {Settings} from "@/modules/common/settings";
import {onMounted, ref} from "vue";
import {EntityConfigFactory} from "@/modules/plugin/entityConfigFactory";
const entityConfigFactory = new EntityConfigFactory()
const buttonImageFactory = new EntityButtonImageFactory()
const touchScreenImageFactory = new EntityButtonImageFactory({width: 200, height: 100})
const $SD = ref(null)
const $HA = ref(null)
const $reconnectTimeout = ref({})
// const useStateImagesForOnOffStates = ref(false)
const actionSettings = ref({})
const actionSettings = ref([])
const buttonLongpressTimeouts = ref(new Map()) //context, timeout
let rotationTimeout = [];
let rotationAmount = [];
let rotationPercent = [];
onMounted(() => {
window.connectElgatoStreamDeckSocket = (inPort, inPluginUUID, inRegisterEvent, inInfo) => {
$SD.value = new StreamDeck(inPort, inPluginUUID, inRegisterEvent, inInfo, "{}");
Expand All @@ -32,7 +37,6 @@ onMounted(() => {
}
)
const onHAConnected = () => {
$HA.value.getStates(entitiyStatesChanged)
$HA.value.subscribeEvents(entityStateChanged)
Expand Down Expand Up @@ -61,26 +65,17 @@ onMounted(() => {
})
$SD.value.on("keyDown", (message) => {
let context = message.context
const timeout = setTimeout(buttonLongPress, 300, context);
buttonLongpressTimeouts.value.set(context, timeout)
buttonDown(message.context);
})
$SD.value.on("keyUp", (message) => {
let context = message.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);
}
buttonUp(message.context);
})
$SD.value.on("willAppear", (message) => {
let context = message.context;
rotationAmount[context] = 0;
rotationPercent[context] = 0;
actionSettings.value[context] = Settings.parse(message.payload.settings)
if ($HA.value) {
$HA.value.getStates(entitiyStatesChanged)
Expand All @@ -92,36 +87,94 @@ onMounted(() => {
delete actionSettings.value[context]
})
$SD.value.on("dialRotate", (message) => {
let context = message.context;
let settings = actionSettings.value[context];
rotationAmount[context] += message.payload.ticks;
rotationPercent[context] += (message.payload.ticks * 2);
if (rotationPercent[context] < 0) {
rotationPercent[context] = 0;
} else if (rotationPercent[context] > 100) {
rotationPercent[context] = 100;
}
if (rotationTimeout[context])
return;
rotationTimeout[context] = setTimeout(() => {
callService(context, settings.button.serviceRotation, {ticks: rotationAmount[context], rotationPercent: rotationPercent[context], rotationAbsolute: 2.55 * rotationPercent[context]});
rotationAmount[context] = 0;
rotationTimeout[context] = null;
}, 300);
})
$SD.value.on("dialDown", (message) => {
buttonDown(message.context);
})
$SD.value.on("dialUp", (message) => {
buttonUp(message.context);
})
$SD.value.on("touchTap", (message) => {
let context = message.context;
let settings = actionSettings.value[context];
callService(context, settings.button.serviceTap);
})
$SD.value.on("didReceiveSettings", (message) => {
let context = message.context;
rotationAmount[context] = 0;
actionSettings.value[context] = Settings.parse(message.payload.settings)
if ($HA.value) {
$HA.value.getStates(entitiyStatesChanged)
}
})
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[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) => {
const callService = (context, serviceToCall, serviceDataAttributes = {}) => {
if ($HA.value) {
if (serviceToCall["serviceId"]) {
try {
const serviceIdParts = serviceToCall.serviceId.split('.');
const serviceData = serviceToCall.serviceData ? JSON.parse(serviceToCall.serviceData) : null;
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)
Expand Down Expand Up @@ -206,8 +259,13 @@ onMounted(() => {
$SD.value.setState(currentContext, 0);
}
} else {
const buttonImage = buttonImageFactory.createButton(entityConfig);
setButtonSVG(buttonImage, currentContext)
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) {
Expand All @@ -222,7 +280,11 @@ onMounted(() => {
const setButtonSVG = (svg, changedContext) => {
const image = "data:image/svg+xml;charset=utf8," + svg;
$SD.value.setImage(changedContext, image)
if (actionSettings.value[changedContext].controllerType === 'Encoder') {
$SD.value.setFeedback(changedContext, {"full-canvas": image, "canvas": null, "title": ""})
} else {
$SD.value.setImage(changedContext, image)
}
}
})
Expand Down
Loading

0 comments on commit 2ae26ac

Please sign in to comment.