adds support for covers
remove HA event listeners on shutdown
This commit is contained in:
parent
95fd422f78
commit
5458ee0654
@ -17,7 +17,23 @@
|
|||||||
|
|
||||||
#MMM-HomeAssistant-Touch .ha-entity.ha-switch.on,
|
#MMM-HomeAssistant-Touch .ha-entity.ha-switch.on,
|
||||||
#MMM-HomeAssistant-Touch .ha-entity.ha-light.on {
|
#MMM-HomeAssistant-Touch .ha-entity.ha-light.on {
|
||||||
background-color: #2AF20261;
|
background-color: #2af20261;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ha-slider {
|
||||||
|
width: 3rem;
|
||||||
|
height: 200px;
|
||||||
|
border: 0.1rem solid grey;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ha-slider .ha-slider-fill {
|
||||||
|
background-color: #2af20261;
|
||||||
|
width: 3rem;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ Module.register("MMM-HomeAssistant-Touch", {
|
|||||||
this.file("./UIClasses/Base.js"),
|
this.file("./UIClasses/Base.js"),
|
||||||
this.file("./UIClasses/Light.js"),
|
this.file("./UIClasses/Light.js"),
|
||||||
this.file("./UIClasses/Switch.js"),
|
this.file("./UIClasses/Switch.js"),
|
||||||
|
this.file("./UIClasses/Cover.js"),
|
||||||
this.file("./UIClasses/Unsupported.js"),
|
this.file("./UIClasses/Unsupported.js"),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,37 +1,39 @@
|
|||||||
class Base {
|
class Base {
|
||||||
constructor(id, mm) {
|
constructor(id, mm) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = id.split('.')[0]
|
this.type = id.split(".")[0];
|
||||||
this.name = id;
|
this.name = id;
|
||||||
this.mm = mm;
|
this.mm = mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState(state) {
|
updateState(state) {
|
||||||
this.name = (state.attributes || {}).friendly_name || this.id;
|
this.name = (state.attributes || {}).friendly_name || this.id;
|
||||||
this.state = state.state;
|
this.state = state.state;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
getContainer() {
|
getContainer() {
|
||||||
const entity = document.createElement("div");
|
const entity = document.createElement("div");
|
||||||
entity.classList.add("ha-entity");
|
entity.classList.add("ha-entity");
|
||||||
entity.classList.add(`ha-${this.type}`)
|
entity.classList.add(`ha-${this.type}`);
|
||||||
entity.id = this.id;
|
entity.id = this.id;
|
||||||
entity.innerHTML = "Loading...";
|
entity.innerHTML = "Loading...";
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const container = document.getElementById(this.id);
|
const container = document.getElementById(this.id);
|
||||||
container.className = ""
|
if (container) {
|
||||||
container.classList.add("ha-entity");
|
container.className = "";
|
||||||
container.classList.add(`ha-${this.type}`)
|
container.classList.add("ha-entity");
|
||||||
|
container.classList.add(`ha-${this.type}`);
|
||||||
|
|
||||||
const title = document.createElement("span");
|
const title = document.createElement("span");
|
||||||
title.className = "title";
|
title.className = "title";
|
||||||
title.innerHTML = this.name;
|
title.innerHTML = this.name;
|
||||||
|
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
container.appendChild(title);
|
container.appendChild(title);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
85
UIClasses/Cover.js
Normal file
85
UIClasses/Cover.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
class Cover extends Base {
|
||||||
|
constructor(...params) {
|
||||||
|
super(...params);
|
||||||
|
this.onSliderMove = this.onSliderMove.bind(this);
|
||||||
|
this.removeSlider = this.removeSlider.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContainer() {
|
||||||
|
const entity = super.getContainer();
|
||||||
|
entity.onmousedown = (event) => {
|
||||||
|
this.addSlider(event.x, event.y);
|
||||||
|
};
|
||||||
|
entity.onmouseup = () => {
|
||||||
|
this.removeSlider();
|
||||||
|
};
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState(state) {
|
||||||
|
this.name = (state.attributes || {}).friendly_name || this.id;
|
||||||
|
this.state = state.attributes.current_position;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
super.render();
|
||||||
|
const container = document.getElementById(this.id);
|
||||||
|
if (container) {
|
||||||
|
container.classList.add(this.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSlider(offsetX, offsetY) {
|
||||||
|
const slider = document.createElement("div");
|
||||||
|
slider.id = `slider-${this.id}`;
|
||||||
|
slider.classList.add("ha-slider");
|
||||||
|
|
||||||
|
// click location minus height minus margins
|
||||||
|
slider.style.top = `calc(${offsetY}px - 60px - 200px + calc(2px * ${this.state}))`;
|
||||||
|
// click location minus width/2 minus margins
|
||||||
|
slider.style.left = `calc(${offsetX}px - 60px - 1.5rem)`;
|
||||||
|
|
||||||
|
const sliderFill = document.createElement("div");
|
||||||
|
sliderFill.id = `slider-fill-${this.id}`;
|
||||||
|
sliderFill.classList.add("ha-slider-fill");
|
||||||
|
sliderFill.style.height = `${this.state}%`;
|
||||||
|
sliderFill.innerHTML = this.state;
|
||||||
|
|
||||||
|
slider.appendChild(sliderFill);
|
||||||
|
document.body.appendChild(slider);
|
||||||
|
|
||||||
|
document.body.addEventListener("mouseup", this.removeSlider);
|
||||||
|
document.body.addEventListener("mousemove", this.onSliderMove);
|
||||||
|
|
||||||
|
this.sliderStartY = offsetY;
|
||||||
|
this.sliderState = this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSliderMove(event) {
|
||||||
|
const sliderFill = document.getElementById(`slider-fill-${this.id}`);
|
||||||
|
if (sliderFill) {
|
||||||
|
const offset = this.sliderStartY - event.y;
|
||||||
|
this.sliderState = this.state + Math.round((100 / 200) * offset);
|
||||||
|
sliderFill.style.height = `${this.sliderState}%`;
|
||||||
|
sliderFill.innerHTML = this.sliderState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSlider() {
|
||||||
|
const slider = document.getElementById(`slider-${this.id}`);
|
||||||
|
if (slider) {
|
||||||
|
slider.remove();
|
||||||
|
}
|
||||||
|
document.body.removeEventListener("mouseup", this.removeSlider);
|
||||||
|
document.body.removeEventListener("mousemove", this.onSliderMove);
|
||||||
|
this.sendNewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNewState() {
|
||||||
|
this.mm.sendSocketNotification("SET_COVER_POSITION", {
|
||||||
|
entity: this.id,
|
||||||
|
position: this.sliderState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,8 +14,8 @@ class Light extends Base {
|
|||||||
render() {
|
render() {
|
||||||
super.render();
|
super.render();
|
||||||
const container = document.getElementById(this.id);
|
const container = document.getElementById(this.id);
|
||||||
container.classList.add(this.state);
|
if (container) {
|
||||||
|
container.classList.add(this.state);
|
||||||
container.appendChild(statusCheckbox);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class Switch extends Base {
|
|||||||
};
|
};
|
||||||
entity.ontouchend = () => {
|
entity.ontouchend = () => {
|
||||||
this.mm.sendSocketNotification("TOGGLE_STATE", { entity: this.id });
|
this.mm.sendSocketNotification("TOGGLE_STATE", { entity: this.id });
|
||||||
}
|
};
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@ class Switch extends Base {
|
|||||||
render() {
|
render() {
|
||||||
super.render();
|
super.render();
|
||||||
const container = document.getElementById(this.id);
|
const container = document.getElementById(this.id);
|
||||||
container.classList.add(this.state)
|
if (container) {
|
||||||
|
container.classList.add(this.state);
|
||||||
container.appendChild(statusCheckbox);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ class UIClassFactory {
|
|||||||
return Light;
|
return Light;
|
||||||
case "switch":
|
case "switch":
|
||||||
return Switch;
|
return Switch;
|
||||||
|
case "cover":
|
||||||
|
return Cover;
|
||||||
default:
|
default:
|
||||||
return Unsupported;
|
return Unsupported;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,24 +3,36 @@ const HomeAssistant = require("homeassistant");
|
|||||||
const HomeAssistantWS = require("homeassistant-ws");
|
const HomeAssistantWS = require("homeassistant-ws");
|
||||||
const Logger = require("./helpers/Logger");
|
const Logger = require("./helpers/Logger");
|
||||||
|
|
||||||
const connections = {};
|
|
||||||
|
|
||||||
module.exports = NodeHelper.create({
|
module.exports = NodeHelper.create({
|
||||||
start,
|
start,
|
||||||
|
stop,
|
||||||
socketNotificationReceived,
|
socketNotificationReceived,
|
||||||
connect,
|
connect,
|
||||||
getState,
|
getState,
|
||||||
toggleState,
|
toggleState,
|
||||||
|
setCoverPosition,
|
||||||
onStateChangedEvent,
|
onStateChangedEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
this.logger = new Logger(this.name);
|
this.logger = new Logger(this.name);
|
||||||
|
this.connections = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
for (const connection in this.connections) {
|
||||||
|
this.connections[connection].websocket.unsubscribeFromEvent(
|
||||||
|
"state_changed"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketNotificationReceived(notification, payload) {
|
function socketNotificationReceived(notification, payload) {
|
||||||
this.logger.debug(`Recieved notification ${notification}`, payload);
|
this.logger.debug(`Recieved notification ${notification}`, payload);
|
||||||
if (notification !== 'CONNECT' && (!payload.identifier || !connections[payload.identifier])) {
|
if (
|
||||||
|
notification !== "CONNECT" &&
|
||||||
|
(!payload.identifier || !this.connections[payload.identifier])
|
||||||
|
) {
|
||||||
this.logger.error(`No connection for ${payload.identifier} found`);
|
this.logger.error(`No connection for ${payload.identifier} found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -35,6 +47,9 @@ function socketNotificationReceived(notification, payload) {
|
|||||||
case "TOGGLE_STATE":
|
case "TOGGLE_STATE":
|
||||||
this.toggleState(payload);
|
this.toggleState(payload);
|
||||||
break;
|
break;
|
||||||
|
case "SET_COVER_POSITION":
|
||||||
|
this.setCoverPosition(payload);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +62,7 @@ async function connect(payload) {
|
|||||||
};
|
};
|
||||||
const hass = new HomeAssistant(connectionConfig);
|
const hass = new HomeAssistant(connectionConfig);
|
||||||
this.logger.info(`HomeAssistant connected for ${payload.identifier}`);
|
this.logger.info(`HomeAssistant connected for ${payload.identifier}`);
|
||||||
connections[payload.identifier] = {
|
this.connections[payload.identifier] = {
|
||||||
hass,
|
hass,
|
||||||
entities: [],
|
entities: [],
|
||||||
};
|
};
|
||||||
@ -58,8 +73,8 @@ async function connect(payload) {
|
|||||||
host: new URL(connectionConfig.host).host,
|
host: new URL(connectionConfig.host).host,
|
||||||
})
|
})
|
||||||
.then((hassWs) => {
|
.then((hassWs) => {
|
||||||
connections[payload.identifier].websocket = hassWs;
|
this.connections[payload.identifier].websocket = hassWs;
|
||||||
hassWs.onStateChanged(onStateChangedEvent.bind(self));
|
hassWs.onEvent("state_changed", onStateChangedEvent.bind(self));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
@ -70,8 +85,8 @@ async function connect(payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getState(payload) {
|
async function getState(payload) {
|
||||||
this.logger.debug(`Getting state for ${payload.entity}`);
|
this.logger.debug(`Getting state for ${payload.entity}`);
|
||||||
const hass = connections[payload.identifier].hass;
|
const hass = this.connections[payload.identifier].hass;
|
||||||
const [domain, entity] = payload.entity.split(".");
|
const [domain, entity] = payload.entity.split(".");
|
||||||
const response = await hass.states.get(domain, entity);
|
const response = await hass.states.get(domain, entity);
|
||||||
this.logger.debug(`Got state for ${payload.entity}`);
|
this.logger.debug(`Got state for ${payload.entity}`);
|
||||||
@ -80,24 +95,33 @@ async function getState(payload) {
|
|||||||
data: response,
|
data: response,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!connections[payload.identifier].entities.includes(payload.entity)) {
|
if (!this.connections[payload.identifier].entities.includes(payload.entity)) {
|
||||||
connections[payload.identifier].entities.push(payload.entity);
|
this.connections[payload.identifier].entities.push(payload.entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleState(payload) {
|
async function toggleState(payload) {
|
||||||
this.logger.debug(`Toggling state for ${payload.entity}`);
|
this.logger.debug(`Toggling state for ${payload.entity}`);
|
||||||
const hass = connections[payload.identifier].hass;
|
const hass = this.connections[payload.identifier].hass;
|
||||||
const [domain, entity] = payload.entity.split(".");
|
const [domain, entity] = payload.entity.split(".");
|
||||||
const response = await hass.services.call('toggle', domain, entity)
|
const response = await hass.services.call("toggle", domain, entity);
|
||||||
this.logger.debug(`Response for toggling state of ${payload.entity}`, response)
|
this.getState(payload);
|
||||||
this.getState(payload)
|
}
|
||||||
|
|
||||||
|
async function setCoverPosition(payload) {
|
||||||
|
this.logger.debug(`Setting position for cover ${payload.entity} to ${payload.position}`)
|
||||||
|
const hass = this.connections[payload.identifier].hass;
|
||||||
|
const response = await hass.services.call("set_cover_position", 'cover', {
|
||||||
|
entity_id: payload.entity,
|
||||||
|
position: payload.position
|
||||||
|
});
|
||||||
|
this.getState(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStateChangedEvent(event) {
|
function onStateChangedEvent(event) {
|
||||||
//this.logger.debug(`Got state change for ${event.data.entity_id}`);
|
//this.logger.debug(`Got state change for ${event.data.entity_id}`);
|
||||||
for (const connection in connections) {
|
for (const connection in this.connections) {
|
||||||
if (connections[connection].entities.includes(event.data.entity_id)) {
|
if (this.connections[connection].entities.includes(event.data.entity_id)) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Found listening connection (${connection}) for entity ${event.data.entity_id}`
|
`Found listening connection (${connection}) for entity ${event.data.entity_id}`
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user