forked from platypush/platypush
Merge pull request 'Merge mqtt
backend and plugin' (#320) from 315/merge-mqtt-backend-and-plugin into master
Reviewed-on: platypush/platypush#320
This commit is contained in:
commit
9e7b95583b
108 changed files with 2365 additions and 4407 deletions
|
@ -56,5 +56,4 @@ Backends
|
|||
platypush/backend/weather.darksky.rst
|
||||
platypush/backend/weather.openweathermap.rst
|
||||
platypush/backend/wiimote.rst
|
||||
platypush/backend/zwave.rst
|
||||
platypush/backend/zwave.mqtt.rst
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
``zwave``
|
||||
===========================
|
||||
|
||||
.. automodule:: platypush.backend.zwave
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``zwave``
|
||||
===========================
|
||||
|
||||
.. automodule:: platypush.plugins.zwave
|
||||
:members:
|
|
@ -149,5 +149,4 @@ Plugins
|
|||
platypush/plugins/xmpp.rst
|
||||
platypush/plugins/zeroconf.rst
|
||||
platypush/plugins/zigbee.mqtt.rst
|
||||
platypush/plugins/zwave.rst
|
||||
platypush/plugins/zwave.mqtt.rst
|
||||
|
|
|
@ -4,9 +4,17 @@ from typing import Type, Optional, Union, List
|
|||
|
||||
from platypush.backend import Backend
|
||||
from platypush.context import get_plugin
|
||||
from platypush.message.event.chat.telegram import MessageEvent, CommandMessageEvent, TextMessageEvent, \
|
||||
PhotoMessageEvent, VideoMessageEvent, ContactMessageEvent, DocumentMessageEvent, LocationMessageEvent, \
|
||||
GroupChatCreatedEvent
|
||||
from platypush.message.event.chat.telegram import (
|
||||
MessageEvent,
|
||||
CommandMessageEvent,
|
||||
TextMessageEvent,
|
||||
PhotoMessageEvent,
|
||||
VideoMessageEvent,
|
||||
ContactMessageEvent,
|
||||
DocumentMessageEvent,
|
||||
LocationMessageEvent,
|
||||
GroupChatCreatedEvent,
|
||||
)
|
||||
from platypush.plugins.chat.telegram import ChatTelegramPlugin
|
||||
|
||||
|
||||
|
@ -23,7 +31,7 @@ class ChatTelegramBackend(Backend):
|
|||
* :class:`platypush.message.event.chat.telegram.ContactMessageEvent` when a contact is received.
|
||||
* :class:`platypush.message.event.chat.telegram.DocumentMessageEvent` when a document is received.
|
||||
* :class:`platypush.message.event.chat.telegram.CommandMessageEvent` when a command message is received.
|
||||
* :class:`platypush.message.event.chat.telegram.GroupCreatedEvent` when the bot is invited to a new group.
|
||||
* :class:`platypush.message.event.chat.telegram.GroupChatCreatedEvent` when the bot is invited to a new group.
|
||||
|
||||
Requires:
|
||||
|
||||
|
@ -31,7 +39,9 @@ class ChatTelegramBackend(Backend):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, authorized_chat_ids: Optional[List[Union[str, int]]] = None, **kwargs):
|
||||
def __init__(
|
||||
self, authorized_chat_ids: Optional[List[Union[str, int]]] = None, **kwargs
|
||||
):
|
||||
"""
|
||||
:param authorized_chat_ids: Optional list of chat_id/user_id which are authorized to send messages to
|
||||
the bot. If nothing is specified then no restrictions are applied.
|
||||
|
@ -39,40 +49,52 @@ class ChatTelegramBackend(Backend):
|
|||
|
||||
super().__init__(**kwargs)
|
||||
self.authorized_chat_ids = set(authorized_chat_ids or [])
|
||||
self._plugin: ChatTelegramPlugin = get_plugin('chat.telegram')
|
||||
self._plugin: ChatTelegramPlugin = get_plugin('chat.telegram') # type: ignore
|
||||
|
||||
def _authorize(self, msg):
|
||||
if not self.authorized_chat_ids:
|
||||
return
|
||||
|
||||
if msg.chat.type == 'private' and msg.chat.id not in self.authorized_chat_ids:
|
||||
self.logger.info('Received message from unauthorized chat_id {}'.format(msg.chat.id))
|
||||
self._plugin.send_message(chat_id=msg.chat.id, text='You are not allowed to send messages to this bot')
|
||||
self.logger.info(
|
||||
'Received message from unauthorized chat_id %s', msg.chat.id
|
||||
)
|
||||
self._plugin.send_message(
|
||||
chat_id=msg.chat.id,
|
||||
text='You are not allowed to send messages to this bot',
|
||||
)
|
||||
raise PermissionError
|
||||
|
||||
def _msg_hook(self, cls: Type[MessageEvent]):
|
||||
# noinspection PyUnusedLocal
|
||||
def hook(update, context):
|
||||
def hook(update, _):
|
||||
msg = update.effective_message
|
||||
|
||||
try:
|
||||
self._authorize(msg)
|
||||
self.bus.post(cls(chat_id=update.effective_chat.id,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output))
|
||||
self.bus.post(
|
||||
cls(
|
||||
chat_id=update.effective_chat.id,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output,
|
||||
)
|
||||
)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
return hook
|
||||
|
||||
def _group_hook(self):
|
||||
# noinspection PyUnusedLocal
|
||||
def hook(update, context):
|
||||
msg = update.effective_message
|
||||
if msg.group_chat_created:
|
||||
self.bus.post(GroupChatCreatedEvent(chat_id=update.effective_chat.id,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output))
|
||||
self.bus.post(
|
||||
GroupChatCreatedEvent(
|
||||
chat_id=update.effective_chat.id,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output,
|
||||
)
|
||||
)
|
||||
elif msg.photo:
|
||||
self._msg_hook(PhotoMessageEvent)(update, context)
|
||||
elif msg.video:
|
||||
|
@ -92,27 +114,33 @@ class ChatTelegramBackend(Backend):
|
|||
return hook
|
||||
|
||||
def _command_hook(self):
|
||||
# noinspection PyUnusedLocal
|
||||
def hook(update, context):
|
||||
def hook(update, _):
|
||||
msg = update.effective_message
|
||||
m = re.match('\s*/([0-9a-zA-Z_-]+)\s*(.*)', msg.text)
|
||||
m = re.match(r'\s*/([0-9a-zA-Z_-]+)\s*(.*)', msg.text)
|
||||
if not m:
|
||||
self.logger.warning('Invalid command: %s', msg.text)
|
||||
return
|
||||
|
||||
cmd = m.group(1).lower()
|
||||
args = [arg for arg in re.split('\s+', m.group(2)) if len(arg)]
|
||||
args = [arg for arg in re.split(r'\s+', m.group(2)) if len(arg)]
|
||||
|
||||
try:
|
||||
self._authorize(msg)
|
||||
self.bus.post(CommandMessageEvent(chat_id=update.effective_chat.id,
|
||||
command=cmd,
|
||||
cmdargs=args,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output))
|
||||
self.bus.post(
|
||||
CommandMessageEvent(
|
||||
chat_id=update.effective_chat.id,
|
||||
command=cmd,
|
||||
cmdargs=args,
|
||||
message=self._plugin.parse_msg(msg).output,
|
||||
user=self._plugin.parse_user(update.effective_user).output,
|
||||
)
|
||||
)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
return hook
|
||||
|
||||
def run(self):
|
||||
# noinspection PyPackageRequirements
|
||||
from telegram.ext import MessageHandler, Filters
|
||||
|
||||
super().run()
|
||||
|
@ -120,12 +148,24 @@ class ChatTelegramBackend(Backend):
|
|||
dispatcher = telegram.dispatcher
|
||||
|
||||
dispatcher.add_handler(MessageHandler(Filters.group, self._group_hook()))
|
||||
dispatcher.add_handler(MessageHandler(Filters.text, self._msg_hook(TextMessageEvent)))
|
||||
dispatcher.add_handler(MessageHandler(Filters.photo, self._msg_hook(PhotoMessageEvent)))
|
||||
dispatcher.add_handler(MessageHandler(Filters.video, self._msg_hook(VideoMessageEvent)))
|
||||
dispatcher.add_handler(MessageHandler(Filters.contact, self._msg_hook(ContactMessageEvent)))
|
||||
dispatcher.add_handler(MessageHandler(Filters.location, self._msg_hook(LocationMessageEvent)))
|
||||
dispatcher.add_handler(MessageHandler(Filters.document, self._msg_hook(DocumentMessageEvent)))
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.text, self._msg_hook(TextMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.photo, self._msg_hook(PhotoMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.video, self._msg_hook(VideoMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.contact, self._msg_hook(ContactMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.location, self._msg_hook(LocationMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(
|
||||
MessageHandler(Filters.document, self._msg_hook(DocumentMessageEvent))
|
||||
)
|
||||
dispatcher.add_handler(MessageHandler(Filters.command, self._command_hook()))
|
||||
|
||||
self.logger.info('Initialized Telegram backend')
|
||||
|
|
|
@ -5,7 +5,7 @@ manifest:
|
|||
platypush.message.event.chat.telegram.ContactMessageEvent: when a contact is received.
|
||||
platypush.message.event.chat.telegram.DocumentMessageEvent: when a document is
|
||||
received.
|
||||
platypush.message.event.chat.telegram.GroupCreatedEvent: when the bot is invited
|
||||
platypush.message.event.chat.telegram.GroupChatCreatedEvent: when the bot is invited
|
||||
to a new group.
|
||||
platypush.message.event.chat.telegram.LocationMessageEvent: when a location is
|
||||
received.
|
||||
|
|
|
@ -4,7 +4,7 @@ from .auth import (
|
|||
authenticate_user_pass,
|
||||
get_auth_status,
|
||||
)
|
||||
from .bus import bus, get_message_response, send_message, send_request
|
||||
from .bus import bus, send_message, send_request
|
||||
from .logger import logger
|
||||
from .routes import (
|
||||
get_http_port,
|
||||
|
@ -25,7 +25,6 @@ __all__ = [
|
|||
'get_http_port',
|
||||
'get_ip_or_hostname',
|
||||
'get_local_base_url',
|
||||
'get_message_response',
|
||||
'get_remote_base_url',
|
||||
'get_routes',
|
||||
'get_streaming_routes',
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
from redis import Redis
|
||||
|
||||
from platypush.bus.redis import RedisBus
|
||||
from platypush.config import Config
|
||||
from platypush.context import get_backend
|
||||
from platypush.message import Message
|
||||
from platypush.message.request import Request
|
||||
from platypush.utils import get_redis_conf, get_redis_queue_name_by_message
|
||||
from platypush.utils import get_redis_conf, get_message_response
|
||||
|
||||
from .logger import logger
|
||||
|
||||
|
@ -67,24 +65,3 @@ def send_request(action, wait_for_response=True, **kwargs):
|
|||
msg['args'] = kwargs
|
||||
|
||||
return send_message(msg, wait_for_response=wait_for_response)
|
||||
|
||||
|
||||
def get_message_response(msg):
|
||||
"""
|
||||
Get the response to the given message.
|
||||
|
||||
:param msg: The message to get the response for.
|
||||
:return: The response to the given message.
|
||||
"""
|
||||
redis = Redis(**bus().redis_args)
|
||||
redis_queue = get_redis_queue_name_by_message(msg)
|
||||
if not redis_queue:
|
||||
return None
|
||||
|
||||
response = redis.blpop(redis_queue, timeout=60)
|
||||
if response and len(response) > 1:
|
||||
response = Message.build(response[1])
|
||||
else:
|
||||
response = None
|
||||
|
||||
return response
|
||||
|
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.d5110853.js"></script><script defer="defer" src="/static/js/app.2b500cfd.js"></script><link href="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><link href="/static/css/app.86fe5b1c.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.d5110853.js"></script><script defer="defer" src="/static/js/app.3c7ccc2c.js"></script><link href="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><link href="/static/css/app.86fe5b1c.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[169],{169:function(t,e,n){n.r(e),n.d(e,{default:function(){return x}});var l=n(6252),a=n(3577);const i={class:"entity sensor-container"},s={class:"head"},u={class:"icon"},o={class:"label"},r=["textContent"],c=["textContent"];function d(t,e,n,d,v,p){const f=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",i,[(0,l._)("div",s,[(0,l._)("div",u,[(0,l.Wm)(f,{entity:t.value,loading:t.loading,error:t.error},null,8,["entity","loading","error"])]),(0,l._)("div",o,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(t.value.name)},null,8,r)]),(0,l._)("div",{class:"value-container",textContent:(0,a.zw)(p.displayValue)},null,8,c)])])}var v=n(847),p=n(4967),f={name:"PercentSensor",components:{EntityIcon:p["default"]},mixins:[v["default"]],computed:{displayValue(){if(null==this.value.value)return null;let t=100*this.value.value;return(t.toString()==t.toFixed(0)?t.toFixed(0):t.toFixed(1))+"%"}}},y=n(3744);const h=(0,y.Z)(f,[["render",d],["__scopeId","data-v-1b6c81c2"]]);var x=h}}]);
|
||||
//# sourceMappingURL=169.ebdd7044.js.map
|
||||
//# sourceMappingURL=169.02caaaba.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/169.ebdd7044.js","mappings":"8LACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,6GANfC,EAAAA,EAAAA,IAYM,MAZNC,EAYM,EAXJC,EAAAA,EAAAA,GAUM,MAVNC,EAUM,EATJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAAqD,OAAhDH,MAAM,kB,aAAkBc,EAAAA,EAAAA,IAAQE,EAAaC,e,qCASxD,GACEF,KAAM,gBACNG,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YACTC,SAAU,CACRL,YAAAA,GACE,GAAwB,MAApBM,KAAKb,MAAMA,MACb,OAAO,KAGT,IAAIc,EAAY,IAAMD,KAAKb,MAAMA,MACjC,OACEc,EAAUC,YAAcD,EAAUE,QAAQ,GACxCF,EAAUE,QAAQ,GAAKF,EAAUE,QAAQ,IACzC,GACN,I,UC5BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/PercentSensor.vue","webpack://platypush/./src/components/panels/Entities/PercentSensor.vue?1f84"],"sourcesContent":["<template>\n <div class=\"entity sensor-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\" v-text=\"displayValue\" />\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'PercentSensor',\n components: {EntityIcon},\n mixins: [EntityMixin],\n computed: {\n displayValue() {\n if (this.value.value == null) {\n return null\n }\n\n let normValue = 100 * this.value.value\n return (\n normValue.toString() == normValue.toFixed(0)\n ? normValue.toFixed(0) : normValue.toFixed(1)\n ) + '%'\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./PercentSensor.vue?vue&type=template&id=1b6c81c2&scoped=true\"\nimport script from \"./PercentSensor.vue?vue&type=script&lang=js\"\nexport * from \"./PercentSensor.vue?vue&type=script&lang=js\"\n\nimport \"./PercentSensor.vue?vue&type=style&index=0&id=1b6c81c2&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1b6c81c2\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","$options","displayValue","components","EntityIcon","mixins","EntityMixin","computed","this","normValue","toString","toFixed","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/169.02caaaba.js","mappings":"8LACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,6GANfC,EAAAA,EAAAA,IAYM,MAZNC,EAYM,EAXJC,EAAAA,EAAAA,GAUM,MAVNC,EAUM,EATJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAAqD,OAAhDH,MAAM,kB,aAAkBc,EAAAA,EAAAA,IAAQE,EAAaC,e,qCASxD,GACEF,KAAM,gBACNG,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YACTC,SAAU,CACRL,YAAAA,GACE,GAAwB,MAApBM,KAAKb,MAAMA,MACb,OAAO,KAGT,IAAIc,EAAY,IAAMD,KAAKb,MAAMA,MACjC,OACEc,EAAUC,YAAcD,EAAUE,QAAQ,GACxCF,EAAUE,QAAQ,GAAKF,EAAUE,QAAQ,IACzC,GACN,I,UC5BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/PercentSensor.vue","webpack://platypush/./src/components/panels/Entities/PercentSensor.vue?1f84"],"sourcesContent":["<template>\n <div class=\"entity sensor-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\" v-text=\"displayValue\" />\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'PercentSensor',\n components: {EntityIcon},\n mixins: [EntityMixin],\n computed: {\n displayValue() {\n if (this.value.value == null) {\n return null\n }\n\n let normValue = 100 * this.value.value\n return (\n normValue.toString() == normValue.toFixed(0)\n ? normValue.toFixed(0) : normValue.toFixed(1)\n ) + '%'\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./PercentSensor.vue?vue&type=template&id=1b6c81c2&scoped=true\"\nimport script from \"./PercentSensor.vue?vue&type=script&lang=js\"\nexport * from \"./PercentSensor.vue?vue&type=script&lang=js\"\n\nimport \"./PercentSensor.vue?vue&type=style&index=0&id=1b6c81c2&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1b6c81c2\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","$options","displayValue","components","EntityIcon","mixins","EntityMixin","computed","this","normValue","toString","toFixed","__exports__","render"],"sourceRoot":""}
|
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[2217],{2217:function(n,t,e){e.r(t),e.d(t,{default:function(){return y}});var a=e(6252),l=e(3577);const s={class:"entity cpu-times-container"},i={class:"head"},c={class:"col-1 icon"},o={class:"col-11 label"},r=["textContent"];function u(n,t,e,u,d,p){const v=(0,a.up)("EntityIcon");return(0,a.wg)(),(0,a.iD)("div",s,[(0,a._)("div",i,[(0,a._)("div",c,[(0,a.Wm)(v,{entity:n.value,loading:n.loading,error:n.error},null,8,["entity","loading","error"])]),(0,a._)("div",o,[(0,a._)("div",{class:"name",textContent:(0,l.zw)(n.value.name)},null,8,r)])])])}var d=e(847),p=e(4967),v={name:"CpuTimes",components:{EntityIcon:p["default"]},mixins:[d["default"]]},f=e(3744);const m=(0,f.Z)(v,[["render",u],["__scopeId","data-v-4667e342"]]);var y=m}}]);
|
||||
//# sourceMappingURL=2217.6b927594.js.map
|
||||
//# sourceMappingURL=2217.9116c837.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/2217.6b927594.js","mappings":"gMACOA,MAAM,8B,GACJA,MAAM,Q,GACJA,MAAM,c,GAONA,MAAM,gB,2FATfC,EAAAA,EAAAA,IAaM,MAbNC,EAaM,EAZJC,EAAAA,EAAAA,GAWM,MAXNC,EAWM,EAVJD,EAAAA,EAAAA,GAKM,MALNE,EAKM,EAJJC,EAAAA,EAAAA,IAGmBC,EAAA,CAFhBC,OAAQC,EAAAC,MACRC,QAASF,EAAAE,QACTC,MAAOH,EAAAG,O,wCAGZT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,uCAUxC,GACEA,KAAM,WACNC,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UCjBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/CpuTimes.vue","webpack://platypush/./src/components/panels/Entities/CpuTimes.vue?1fa2"],"sourcesContent":["<template>\n <div class=\"entity cpu-times-container\">\n <div class=\"head\">\n <div class=\"col-1 icon\">\n <EntityIcon\n :entity=\"value\"\n :loading=\"loading\"\n :error=\"error\" />\n </div>\n\n <div class=\"col-11 label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'CpuTimes',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./CpuTimes.vue?vue&type=template&id=4667e342&scoped=true\"\nimport script from \"./CpuTimes.vue?vue&type=script&lang=js\"\nexport * from \"./CpuTimes.vue?vue&type=script&lang=js\"\n\nimport \"./CpuTimes.vue?vue&type=style&index=0&id=4667e342&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4667e342\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/2217.9116c837.js","mappings":"gMACOA,MAAM,8B,GACJA,MAAM,Q,GACJA,MAAM,c,GAONA,MAAM,gB,2FATfC,EAAAA,EAAAA,IAaM,MAbNC,EAaM,EAZJC,EAAAA,EAAAA,GAWM,MAXNC,EAWM,EAVJD,EAAAA,EAAAA,GAKM,MALNE,EAKM,EAJJC,EAAAA,EAAAA,IAGmBC,EAAA,CAFhBC,OAAQC,EAAAC,MACRC,QAASF,EAAAE,QACTC,MAAOH,EAAAG,O,wCAGZT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,uCAUxC,GACEA,KAAM,WACNC,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UCjBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/CpuTimes.vue","webpack://platypush/./src/components/panels/Entities/CpuTimes.vue?1fa2"],"sourcesContent":["<template>\n <div class=\"entity cpu-times-container\">\n <div class=\"head\">\n <div class=\"col-1 icon\">\n <EntityIcon\n :entity=\"value\"\n :loading=\"loading\"\n :error=\"error\" />\n </div>\n\n <div class=\"col-11 label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'CpuTimes',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./CpuTimes.vue?vue&type=template&id=4667e342&scoped=true\"\nimport script from \"./CpuTimes.vue?vue&type=script&lang=js\"\nexport * from \"./CpuTimes.vue?vue&type=script&lang=js\"\n\nimport \"./CpuTimes.vue?vue&type=style&index=0&id=4667e342&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4667e342\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[2460],{2460:function(n,t,e){e.r(t),e.d(t,{default:function(){return C}});var a=e(6252),l=e(3577);const c={class:"entity cpu-container"},s={class:"head"},o={class:"col-1 icon"},u={class:"label"},i=["textContent"],r={class:"value-container"},d=["textContent"];function v(n,t,e,v,p,f){const _=(0,a.up)("EntityIcon");return(0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",s,[(0,a._)("div",o,[(0,a.Wm)(_,{entity:n.value,loading:n.loading,error:n.error},null,8,["entity","loading","error"])]),(0,a._)("div",u,[(0,a._)("div",{class:"name",textContent:(0,l.zw)(n.value.name)},null,8,i)]),(0,a._)("div",r,[(0,a._)("div",{class:"value",textContent:(0,l.zw)(Math.round(100*n.value.percent,1)+"%")},null,8,d)])])])}var p=e(847),f=e(4967),_={name:"Cpu",components:{EntityIcon:f["default"]},mixins:[p["default"]]},h=e(3744);const y=(0,h.Z)(_,[["render",v],["__scopeId","data-v-d3cf6cca"]]);var C=y}}]);
|
||||
//# sourceMappingURL=2460.567e73f6.js.map
|
||||
//# sourceMappingURL=2460.6a8718df.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/2460.567e73f6.js","mappings":"gMACOA,MAAM,wB,GACJA,MAAM,Q,GACJA,MAAM,c,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAcM,MAdNC,EAcM,EAbJC,EAAAA,EAAAA,GAYM,MAZNC,EAYM,EAXJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAEM,MAFNa,EAEM,EADJb,EAAAA,EAAAA,GAAuE,OAAlEH,MAAM,Q,aAAQc,EAAAA,EAAAA,IAAQG,KAAKC,MAAsB,IAAhBT,EAAAC,MAAMS,QAAe,GAAK,M,uCAUxE,GACEJ,KAAM,MACNK,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UClBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Cpu.vue","webpack://platypush/./src/components/panels/Entities/Cpu.vue?2542"],"sourcesContent":["<template>\n <div class=\"entity cpu-container\">\n <div class=\"head\">\n <div class=\"col-1 icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <div class=\"value\" v-text=\"Math.round(value.percent * 100, 1) + '%'\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Cpu',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Cpu.vue?vue&type=template&id=d3cf6cca&scoped=true\"\nimport script from \"./Cpu.vue?vue&type=script&lang=js\"\nexport * from \"./Cpu.vue?vue&type=script&lang=js\"\n\nimport \"./Cpu.vue?vue&type=style&index=0&id=d3cf6cca&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-d3cf6cca\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","Math","round","percent","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/2460.6a8718df.js","mappings":"gMACOA,MAAM,wB,GACJA,MAAM,Q,GACJA,MAAM,c,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAcM,MAdNC,EAcM,EAbJC,EAAAA,EAAAA,GAYM,MAZNC,EAYM,EAXJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAEM,MAFNa,EAEM,EADJb,EAAAA,EAAAA,GAAuE,OAAlEH,MAAM,Q,aAAQc,EAAAA,EAAAA,IAAQG,KAAKC,MAAsB,IAAhBT,EAAAC,MAAMS,QAAe,GAAK,M,uCAUxE,GACEJ,KAAM,MACNK,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UClBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Cpu.vue","webpack://platypush/./src/components/panels/Entities/Cpu.vue?2542"],"sourcesContent":["<template>\n <div class=\"entity cpu-container\">\n <div class=\"head\">\n <div class=\"col-1 icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <div class=\"value\" v-text=\"Math.round(value.percent * 100, 1) + '%'\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Cpu',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Cpu.vue?vue&type=template&id=d3cf6cca&scoped=true\"\nimport script from \"./Cpu.vue?vue&type=script&lang=js\"\nexport * from \"./Cpu.vue?vue&type=script&lang=js\"\n\nimport \"./Cpu.vue?vue&type=style&index=0&id=d3cf6cca&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-d3cf6cca\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","Math","round","percent","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[2893,6362],{2893:function(e,t,n){n.r(t),n.d(t,{default:function(){return w}});var l=n(6252),a=n(3577);const u={class:"entity sensor-container"},s={class:"head"},i={class:"icon"},o={class:"label"},c=["textContent"],r={key:0,class:"value-container"},v=["textContent"],d=["textContent"];function p(e,t,n,p,y,m){const f=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",u,[(0,l._)("div",s,[(0,l._)("div",i,[(0,l.Wm)(f,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,l._)("div",o,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(e.value.name)},null,8,c)]),null!=e.value.value?((0,l.wg)(),(0,l.iD)("div",r,[null!=e.value.unit?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"unit",textContent:(0,a.zw)(e.value.unit)},null,8,v)):(0,l.kq)("",!0),(0,l._)("span",{class:"value",textContent:(0,a.zw)(m.displayValue(e.value.value))},null,8,d)])):(0,l.kq)("",!0)])])}var y=n(4967),m=n(6362),f={name:"EnumSensor",components:{EntityIcon:y["default"]},mixins:[m["default"]],methods:{displayValue(e){return this.value?.values&&"object"===typeof this.value.values&&this.value.values[e]||e}}},_=n(3744);const h=(0,_.Z)(f,[["render",p],["__scopeId","data-v-159d46fc"]]);var w=h},6362:function(e,t,n){n.r(t),n.d(t,{default:function(){return w}});var l=n(6252),a=n(3577);const u={class:"entity sensor-container"},s={class:"head"},i={class:"icon"},o={class:"label"},c=["textContent"],r={key:0,class:"value-container"},v=["textContent"],d=["textContent"];function p(e,t,n,p,y,m){const f=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",u,[(0,l._)("div",s,[(0,l._)("div",i,[(0,l.Wm)(f,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,l._)("div",o,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(e.value.name)},null,8,c)]),null!=m.computedValue?((0,l.wg)(),(0,l.iD)("div",r,[(0,l._)("span",{class:"value",textContent:(0,a.zw)(m.computedValue)},null,8,v),null!=e.value.unit?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"unit",textContent:(0,a.zw)(e.value.unit)},null,8,d)):(0,l.kq)("",!0)])):(0,l.kq)("",!0)])])}var y=n(847),m=n(4967),f={name:"Sensor",components:{EntityIcon:m["default"]},mixins:[y["default"]],computed:{computedValue(){return null!=this.value.value?this.value.value:this.value._value}}},_=n(3744);const h=(0,_.Z)(f,[["render",p],["__scopeId","data-v-3b38610c"]]);var w=h}}]);
|
||||
//# sourceMappingURL=2893.519a1554.js.map
|
||||
//# sourceMappingURL=2893.55e3bcf7.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[3368],{3368:function(e,l,t){t.r(l),t.d(l,{default:function(){return b}});var a=t(6252),s=t(3577),n=t(9963);const i={class:"entity switch-container"},u={class:"icon"},o={class:"label"},c=["textContent"],v={class:"value-container"},d=["textContent"],r={class:"row"},p={class:"input"},h=["disabled"],y={key:0,value:"",selected:""},g=["value","selected","textContent"];function w(e,l,t,w,f,_){const k=(0,a.up)("EntityIcon");return(0,a.wg)(),(0,a.iD)("div",i,[(0,a._)("div",{class:(0,s.C_)(["head",{collapsed:e.collapsed}])},[(0,a._)("div",u,[(0,a.Wm)(k,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,a._)("div",o,[(0,a._)("div",{class:"name",textContent:(0,s.zw)(e.value.name)},null,8,c)]),(0,a._)("div",v,[null!=e.value?.value?((0,a.wg)(),(0,a.iD)("span",{key:0,class:"value",textContent:(0,s.zw)(e.value.values[e.value.value]||e.value.value)},null,8,d)):(0,a.kq)("",!0),_.hasValues?((0,a.wg)(),(0,a.iD)("button",{key:1,onClick:l[0]||(l[0]=(0,n.iM)((l=>e.collapsed=!e.collapsed),["stop"]))},[(0,a._)("i",{class:(0,s.C_)(["fas",{"fa-angle-up":!e.collapsed,"fa-angle-down":e.collapsed}])},null,2)])):(0,a.kq)("",!0)])],2),e.collapsed?(0,a.kq)("",!0):((0,a.wg)(),(0,a.iD)("div",{key:0,class:"body",onClick:l[2]||(l[2]=(0,n.iM)(((...e)=>_.prevent&&_.prevent(...e)),["stop"]))},[(0,a._)("div",r,[(0,a._)("div",p,[(0,a._)("select",{onInput:l[1]||(l[1]=(...e)=>_.setValue&&_.setValue(...e)),ref:"values",disabled:e.loading},[e.value.is_write_only?((0,a.wg)(),(0,a.iD)("option",y,"--")):(0,a.kq)("",!0),((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)(_.displayValues,((l,t)=>((0,a.wg)(),(0,a.iD)("option",{value:t,selected:t==e.value.value,key:t,textContent:(0,s.zw)(l)},null,8,g)))),128))],40,h)])])]))])}var f=t(847),_=t(4967),k={name:"EnumSwitch",components:{EntityIcon:_["default"]},mixins:[f["default"]],computed:{hasValues(){return!!Object.values(this?.value?.values||{}).length},displayValues(){return this.value?.values instanceof Array?this.value.values.reduce(((e,l)=>(e[l]=l,e)),{}):this.value?.values||{}}},methods:{prevent(e){return e.stopPropagation(),!1},async setValue(e){if(e.target.value?.length){if(this.$emit("loading",!0),this.value.is_write_only){const e=this;setTimeout((()=>{e.$refs.values.value=""}),1e3)}try{await this.request("entities.execute",{id:this.value.id,action:"set",value:e.target.value})}finally{this.$emit("loading",!1)}}}}},m=t(3744);const C=(0,m.Z)(k,[["render",w],["__scopeId","data-v-043593ec"]]);var b=C}}]);
|
||||
//# sourceMappingURL=3368.cb04738a.js.map
|
||||
//# sourceMappingURL=3368.eda50aa5.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[3559],{3559:function(e,n,t){t.r(n),t.d(n,{default:function(){return k}});var l=t(6252),a=t(3577);const u={class:"entity link-quality-container"},i={class:"head"},s={class:"icon"},r={class:"label"},c=["textContent"],o={class:"value-container"},v=["textContent"];function d(e,n,t,d,p,f){const h=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",u,[(0,l._)("div",i,[(0,l._)("div",s,[(0,l.Wm)(h,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,l._)("div",r,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(e.value.name)},null,8,c)]),(0,l._)("div",o,[null!=f.valuePercent?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"value",textContent:(0,a.zw)(f.valuePercent+"%")},null,8,v)):(0,l.kq)("",!0)])])])}var p=t(847),f=t(4967),h={name:"LinkQuality",components:{EntityIcon:f["default"]},mixins:[p["default"]],computed:{valuePercent(){if(null==this.value?.value)return null;const e=this.value.min||0,n=this.value.max||100;return(100*this.value.value/(n-e)).toFixed(0)}}},y=t(3744);const m=(0,y.Z)(h,[["render",d],["__scopeId","data-v-66f207d9"]]);var k=m}}]);
|
||||
//# sourceMappingURL=3559.df95d103.js.map
|
||||
//# sourceMappingURL=3559.c2592048.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/3559.df95d103.js","mappings":"gMACOA,MAAM,iC,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAgBM,MAhBNC,EAgBM,EAfJC,EAAAA,EAAAA,GAcM,MAdNC,EAcM,EAbJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAIM,MAJNa,EAIM,CADoB,MAAhBC,EAAAC,eAAY,WAFpBjB,EAAAA,EAAAA,IAEgC,Q,MAF1BD,MAAM,Q,aACVc,EAAAA,EAAAA,IAAQG,EAAmBC,aAAJ,M,wDAWjC,GACEH,KAAM,cACNI,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRL,YAAAA,GACE,GAAyB,MAArBM,KAAKd,OAAOA,MACd,OAAO,KAET,MAAMe,EAAMD,KAAKd,MAAMe,KAAO,EACxBC,EAAMF,KAAKd,MAAMgB,KAAO,IAC9B,OAAS,IAAMF,KAAKd,MAAMA,OAAUgB,EAAMD,IAAME,QAAQ,EAC1D,I,UC9BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/LinkQuality.vue","webpack://platypush/./src/components/panels/Entities/LinkQuality.vue?19d2"],"sourcesContent":["<template>\n <div class=\"entity link-quality-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <span class=\"value\"\n v-text=\"valuePercent + '%'\"\n v-if=\"valuePercent != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'LinkQuality',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n valuePercent() {\n if (this.value?.value == null)\n return null\n\n const min = this.value.min || 0\n const max = this.value.max || 100\n return ((100 * this.value.value) / (max - min)).toFixed(0)\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./LinkQuality.vue?vue&type=template&id=66f207d9&scoped=true\"\nimport script from \"./LinkQuality.vue?vue&type=script&lang=js\"\nexport * from \"./LinkQuality.vue?vue&type=script&lang=js\"\n\nimport \"./LinkQuality.vue?vue&type=style&index=0&id=66f207d9&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-66f207d9\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","$options","valuePercent","components","EntityIcon","mixins","EntityMixin","computed","this","min","max","toFixed","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/3559.c2592048.js","mappings":"gMACOA,MAAM,iC,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAgBM,MAhBNC,EAgBM,EAfJC,EAAAA,EAAAA,GAcM,MAdNC,EAcM,EAbJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,aAGlCZ,EAAAA,EAAAA,GAIM,MAJNa,EAIM,CADoB,MAAhBC,EAAAC,eAAY,WAFpBjB,EAAAA,EAAAA,IAEgC,Q,MAF1BD,MAAM,Q,aACVc,EAAAA,EAAAA,IAAQG,EAAmBC,aAAJ,M,wDAWjC,GACEH,KAAM,cACNI,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRL,YAAAA,GACE,GAAyB,MAArBM,KAAKd,OAAOA,MACd,OAAO,KAET,MAAMe,EAAMD,KAAKd,MAAMe,KAAO,EACxBC,EAAMF,KAAKd,MAAMgB,KAAO,IAC9B,OAAS,IAAMF,KAAKd,MAAMA,OAAUgB,EAAMD,IAAME,QAAQ,EAC1D,I,UC9BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/LinkQuality.vue","webpack://platypush/./src/components/panels/Entities/LinkQuality.vue?19d2"],"sourcesContent":["<template>\n <div class=\"entity link-quality-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <span class=\"value\"\n v-text=\"valuePercent + '%'\"\n v-if=\"valuePercent != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'LinkQuality',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n valuePercent() {\n if (this.value?.value == null)\n return null\n\n const min = this.value.min || 0\n const max = this.value.max || 100\n return ((100 * this.value.value) / (max - min)).toFixed(0)\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./LinkQuality.vue?vue&type=template&id=66f207d9&scoped=true\"\nimport script from \"./LinkQuality.vue?vue&type=script&lang=js\"\nexport * from \"./LinkQuality.vue?vue&type=script&lang=js\"\n\nimport \"./LinkQuality.vue?vue&type=style&index=0&id=66f207d9&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-66f207d9\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","$options","valuePercent","components","EntityIcon","mixins","EntityMixin","computed","this","min","max","toFixed","__exports__","render"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[3835],{3405:function(e,t,n){n.d(t,{Z:function(){return h}});var a=n(6252),i=n(3577),l=n(9963);const o=e=>((0,a.dD)("data-v-a6396ae8"),e=e(),(0,a.Cn)(),e),s=["checked"],c=o((()=>(0,a._)("div",{class:"switch"},[(0,a._)("div",{class:"dot"})],-1))),d={class:"label"};function u(e,t,n,o,u,r){return(0,a.wg)(),(0,a.iD)("div",{class:(0,i.C_)(["power-switch",{disabled:n.disabled}]),onClick:t[0]||(t[0]=(0,l.iM)(((...e)=>r.onInput&&r.onInput(...e)),["stop"]))},[(0,a._)("input",{type:"checkbox",checked:n.value},null,8,s),(0,a._)("label",null,[c,(0,a._)("span",d,[(0,a.WI)(e.$slots,"default",{},void 0,!0)])])],2)}var r={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput(e){if(this.disabled)return!1;this.$emit("input",e)}}},v=n(3744);const p=(0,v.Z)(r,[["render",u],["__scopeId","data-v-a6396ae8"]]);var h=p},3835:function(e,t,n){n.r(t),n.d(t,{default:function(){return m}});var a=n(6252),i=n(3577),l=n(9963);const o={class:"entity device-container"},s={class:"head"},c={class:"icon"},d={class:"label"},u=["textContent"];function r(e,t,n,r,v,p){const h=(0,a.up)("EntityIcon"),f=(0,a.up)("ToggleSwitch");return(0,a.wg)(),(0,a.iD)("div",o,[(0,a._)("div",s,[(0,a._)("div",c,[(0,a.Wm)(h,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,a._)("div",d,[(0,a._)("div",{class:"name",textContent:(0,i.zw)(e.value.name)},null,8,u)]),(0,a._)("div",{class:(0,i.C_)(["value-container",{"with-children":e.value?.children_ids?.length}])},[(0,a.Wm)(f,{value:e.value.connected,disabled:e.loading,onInput:p.connect,onClick:t[0]||(t[0]=(0,l.iM)((()=>{}),["stop"]))},null,8,["value","disabled","onInput"])],2)])])}var v=n(847),p=n(4967),h=n(3405),f={name:"BluetoothDevice",components:{EntityIcon:p["default"],ToggleSwitch:h.Z},mixins:[v["default"]],methods:{async connect(e){e.stopPropagation(),this.$emit("loading",!0);const t="bluetooth."+(this.value.connected?"disconnect":"connect");try{await this.request(t,{device:this.value.address})}finally{this.$emit("loading",!1)}}}},_=n(3744);const g=(0,_.Z)(f,[["render",r],["__scopeId","data-v-6aff1eff"]]);var m=g}}]);
|
||||
//# sourceMappingURL=3835.11129165.js.map
|
||||
//# sourceMappingURL=3835.667ba911.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/4558.a23333b6.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/4558.a23333b6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/4558.a23333b6.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/4558.a23333b6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[5329],{5329:function(e,l,a){a.r(l),a.d(l,{default:function(){return le}});var s=a(6252),t=a(3577),n=a(9963);const i=e=>((0,s.dD)("data-v-d7813182"),e=e(),(0,s.Cn)(),e),v={class:"icon"},c={class:"label"},d=["textContent"],u={class:"value-and-toggler"},o=["textContent"],r={key:0,class:"body children attributes fade-in"},_={key:0,class:"child"},C=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Total")],-1))),m={class:"value"},k=["textContent"],h={key:1,class:"child"},p=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Available")],-1))),w={class:"value"},x=["textContent"],f={key:2,class:"child"},y=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Used")],-1))),z={class:"value"},b=["textContent"],g={key:3,class:"child"},D=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Free")],-1))),S={class:"value"},q=["textContent"],I={key:4,class:"child"},M=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Active")],-1))),A={class:"value"},E=["textContent"],B={key:5,class:"child"},F=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Inactive")],-1))),T={class:"value"},U=["textContent"],W={key:6,class:"child"},Z=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Buffers")],-1))),j={class:"value"},G=["textContent"],H={key:7,class:"child"},J=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Cached")],-1))),K={class:"value"},L=["textContent"],N={key:8,class:"child"},O=i((()=>(0,s._)("div",{class:"label"},[(0,s._)("div",{class:"name"},"Shared")],-1))),P={class:"value"},Q=["textContent"];function R(e,l,a,i,R,V){const X=(0,s.up)("EntityIcon");return(0,s.wg)(),(0,s.iD)("div",{class:(0,t.C_)(["entity memory-stats-container",{expanded:!R.isCollapsed}])},[(0,s._)("div",{class:"head",onClick:l[1]||(l[1]=(0,n.iM)((e=>R.isCollapsed=!R.isCollapsed),["stop"]))},[(0,s._)("div",v,[(0,s.Wm)(X,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,s._)("div",c,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.value.name)},null,8,d)]),(0,s._)("div",u,[(0,s._)("div",{class:"value",textContent:(0,t.zw)(Math.round(100*e.value.percent,1)+"%")},null,8,o),(0,s._)("div",{class:"collapse-toggler",onClick:l[0]||(l[0]=(0,n.iM)((e=>R.isCollapsed=!R.isCollapsed),["stop"]))},[(0,s._)("i",{class:(0,t.C_)(["fas",{"fa-chevron-down":R.isCollapsed,"fa-chevron-up":!R.isCollapsed}])},null,2)])])]),R.isCollapsed?(0,s.kq)("",!0):((0,s.wg)(),(0,s.iD)("div",r,[null!=e.value.total?((0,s.wg)(),(0,s.iD)("div",_,[C,(0,s._)("div",m,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.total))},null,8,k)])])):(0,s.kq)("",!0),null!=e.value.available?((0,s.wg)(),(0,s.iD)("div",h,[p,(0,s._)("div",w,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.available))},null,8,x)])])):(0,s.kq)("",!0),null!=e.value.used?((0,s.wg)(),(0,s.iD)("div",f,[y,(0,s._)("div",z,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.used))},null,8,b)])])):(0,s.kq)("",!0),null!=e.value.free?((0,s.wg)(),(0,s.iD)("div",g,[D,(0,s._)("div",S,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.free))},null,8,q)])])):(0,s.kq)("",!0),null!=e.value.active?((0,s.wg)(),(0,s.iD)("div",I,[M,(0,s._)("div",A,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.active))},null,8,E)])])):(0,s.kq)("",!0),null!=e.value.inactive?((0,s.wg)(),(0,s.iD)("div",B,[F,(0,s._)("div",T,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.inactive))},null,8,U)])])):(0,s.kq)("",!0),null!=e.value.buffers?((0,s.wg)(),(0,s.iD)("div",W,[Z,(0,s._)("div",j,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.buffers))},null,8,G)])])):(0,s.kq)("",!0),null!=e.value.cached?((0,s.wg)(),(0,s.iD)("div",H,[J,(0,s._)("div",K,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.cached))},null,8,L)])])):(0,s.kq)("",!0),null!=e.value.shared?((0,s.wg)(),(0,s.iD)("div",N,[O,(0,s._)("div",P,[(0,s._)("div",{class:"name",textContent:(0,t.zw)(e.convertSize(e.value.shared))},null,8,Q)])])):(0,s.kq)("",!0)]))],2)}var V=a(847),X=a(4967),Y={name:"MemoryStats",components:{EntityIcon:X["default"]},mixins:[V["default"]],data(){return{isCollapsed:!0}}},$=a(3744);const ee=(0,$.Z)(Y,[["render",R],["__scopeId","data-v-d7813182"]]);var le=ee}}]);
|
||||
//# sourceMappingURL=5329.444a9cf1.js.map
|
||||
//# sourceMappingURL=5329.114966f2.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[6362],{6362:function(t,e,n){n.r(e),n.d(e,{default:function(){return w}});var l=n(6252),a=n(3577);const u={class:"entity sensor-container"},s={class:"head"},i={class:"icon"},o={class:"label"},c=["textContent"],r={key:0,class:"value-container"},v=["textContent"],d=["textContent"];function p(t,e,n,p,m,h){const y=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",u,[(0,l._)("div",s,[(0,l._)("div",i,[(0,l.Wm)(y,{entity:t.value,loading:t.loading,error:t.error},null,8,["entity","loading","error"])]),(0,l._)("div",o,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(t.value.name)},null,8,c)]),null!=h.computedValue?((0,l.wg)(),(0,l.iD)("div",r,[(0,l._)("span",{class:"value",textContent:(0,a.zw)(h.computedValue)},null,8,v),null!=t.value.unit?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"unit",textContent:(0,a.zw)(t.value.unit)},null,8,d)):(0,l.kq)("",!0)])):(0,l.kq)("",!0)])])}var m=n(847),h=n(4967),y={name:"Sensor",components:{EntityIcon:h["default"]},mixins:[m["default"]],computed:{computedValue(){return null!=this.value.value?this.value.value:this.value._value}}},f=n(3744);const k=(0,f.Z)(y,[["render",p],["__scopeId","data-v-3b38610c"]]);var w=k}}]);
|
||||
//# sourceMappingURL=6362.95da0eb4.js.map
|
||||
//# sourceMappingURL=6362.c4de72d9.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/6362.95da0eb4.js","mappings":"gMACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,2BAINA,MAAM,mB,6GAVfC,EAAAA,EAAAA,IAiBM,MAjBNC,EAiBM,EAhBJC,EAAAA,EAAAA,GAeM,MAfNC,EAeM,EAdJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,YAIP,MAAjBC,EAAAC,gBAAa,WADvBhB,EAAAA,EAAAA,IAKM,MALNiB,EAKM,EAHJf,EAAAA,EAAAA,GAA6C,QAAvCH,MAAM,Q,aAAQc,EAAAA,EAAAA,IAAQE,EAAcC,gB,UAEpB,MAAdR,EAAAC,MAAMS,OAAI,WADlBlB,EAAAA,EAAAA,IAC8B,Q,MADxBD,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALS,O,yEAWzC,GACEJ,KAAM,SACNK,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRP,aAAAA,GACE,OAAwB,MAApBQ,KAAKf,MAAMA,MACNe,KAAKf,MAAMA,MACbe,KAAKf,MAAMgB,MACpB,I,UC5BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Sensor.vue","webpack://platypush/./src/components/panels/Entities/Sensor.vue?60a5"],"sourcesContent":["<template>\n <div class=\"entity sensor-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\"\n v-if=\"computedValue != null\">\n <span class=\"value\" v-text=\"computedValue\" />\n <span class=\"unit\" v-text=\"value.unit\"\n v-if=\"value.unit != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Sensor',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n computedValue() {\n if (this.value.value != null)\n return this.value.value\n return this.value._value\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Sensor.vue?vue&type=template&id=3b38610c&scoped=true\"\nimport script from \"./Sensor.vue?vue&type=script&lang=js\"\nexport * from \"./Sensor.vue?vue&type=script&lang=js\"\n\nimport \"./Sensor.vue?vue&type=style&index=0&id=3b38610c&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-3b38610c\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","$options","computedValue","_hoisted_6","unit","components","EntityIcon","mixins","EntityMixin","computed","this","_value","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/6362.c4de72d9.js","mappings":"gMACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,2BAINA,MAAM,mB,6GAVfC,EAAAA,EAAAA,IAiBM,MAjBNC,EAiBM,EAhBJC,EAAAA,EAAAA,GAeM,MAfNC,EAeM,EAdJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAAgEC,EAAA,CAAnDC,OAAQC,EAAAC,MAAQC,QAASF,EAAAE,QAAUC,MAAOH,EAAAG,O,wCAGzDT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,YAIP,MAAjBC,EAAAC,gBAAa,WADvBhB,EAAAA,EAAAA,IAKM,MALNiB,EAKM,EAHJf,EAAAA,EAAAA,GAA6C,QAAvCH,MAAM,Q,aAAQc,EAAAA,EAAAA,IAAQE,EAAcC,gB,UAEpB,MAAdR,EAAAC,MAAMS,OAAI,WADlBlB,EAAAA,EAAAA,IAC8B,Q,MADxBD,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALS,O,yEAWzC,GACEJ,KAAM,SACNK,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRP,aAAAA,GACE,OAAwB,MAApBQ,KAAKf,MAAMA,MACNe,KAAKf,MAAMA,MACbe,KAAKf,MAAMgB,MACpB,I,UC5BJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Sensor.vue","webpack://platypush/./src/components/panels/Entities/Sensor.vue?60a5"],"sourcesContent":["<template>\n <div class=\"entity sensor-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\"\n v-if=\"computedValue != null\">\n <span class=\"value\" v-text=\"computedValue\" />\n <span class=\"unit\" v-text=\"value.unit\"\n v-if=\"value.unit != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Sensor',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n computedValue() {\n if (this.value.value != null)\n return this.value.value\n return this.value._value\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Sensor.vue?vue&type=template&id=3b38610c&scoped=true\"\nimport script from \"./Sensor.vue?vue&type=script&lang=js\"\nexport * from \"./Sensor.vue?vue&type=script&lang=js\"\n\nimport \"./Sensor.vue?vue&type=style&index=0&id=3b38610c&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-3b38610c\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","$options","computedValue","_hoisted_6","unit","components","EntityIcon","mixins","EntityMixin","computed","this","_value","__exports__","render"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[7523],{4358:function(e,t,a){a.d(t,{Z:function(){return w}});var l=a(6252),n=a(3577),s=a(9963);const i={class:"slider-wrapper"},u=["textContent"],r=["textContent"],o={class:"slider-container"},d=["min","max","step","disabled","value"],p={class:"track-inner",ref:"track"},c={class:"thumb",ref:"thumb"},v=["textContent"];function h(e,t,a,h,g,f){return(0,l.wg)(),(0,l.iD)("label",i,[a.withRange?((0,l.wg)(),(0,l.iD)("span",{key:0,class:(0,n.C_)(["range-labels",{"with-label":a.withLabel}])},[a.withRange?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"label left",textContent:(0,n.zw)(a.range[0])},null,8,u)):(0,l.kq)("",!0),a.withRange?((0,l.wg)(),(0,l.iD)("span",{key:1,class:"label right",textContent:(0,n.zw)(a.range[1])},null,8,r)):(0,l.kq)("",!0)],2)):(0,l.kq)("",!0),(0,l._)("span",o,[(0,l._)("input",{class:(0,n.C_)(["slider",{"with-label":a.withLabel}]),type:"range",min:a.range[0],max:a.range[1],step:a.step,disabled:a.disabled,value:a.value,ref:"range",onInput:t[0]||(t[0]=(0,s.iM)(((...e)=>f.onUpdate&&f.onUpdate(...e)),["stop"])),onChange:t[1]||(t[1]=(0,s.iM)(((...e)=>f.onUpdate&&f.onUpdate(...e)),["stop"]))},null,42,d),(0,l._)("div",{class:(0,n.C_)(["track",{"with-label":a.withLabel}])},[(0,l._)("div",p,null,512)],2),(0,l._)("div",c,null,512),a.withLabel?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"label",textContent:(0,n.zw)(a.value),ref:"label"},null,8,v)):(0,l.kq)("",!0)])])}var g={name:"Slider",emits:["input","change","mouseup","mousedown","touchstart","touchend","keyup","keydown"],props:{value:{type:Number},disabled:{type:Boolean,default:!1},range:{type:Array,default:()=>[0,100]},step:{type:Number,default:1},withLabel:{type:Boolean,default:!1},withRange:{type:Boolean,default:!1}},methods:{onUpdate(e){this.update(e.target.value),this.$emit(e.type,{...e,target:{...e.target,value:this.$refs.range.value}})},update(e){const t=this.$refs.range.clientWidth,a=(e-this.range[0])/(this.range[1]-this.range[0]),l=a*t,n=this.$refs.thumb;n.style.left=l-n.clientWidth/2+"px",this.$refs.thumb.style.transform=`translate(-${a}%, -50%)`,this.$refs.track.style.width=`${l}px`}},mounted(){null!=this.value&&this.update(this.value),this.$watch((()=>this.value),(e=>this.update(e)))}},f=a(3744);const m=(0,f.Z)(g,[["render",h],["__scopeId","data-v-4b38623f"]]);var w=m},7523:function(e,t,a){a.r(t),a.d(t,{default:function(){return V}});var l=a(6252),n=a(3577),s=a(9963);const i={class:"entity dimmer-container"},u={class:"icon"},r={class:"label"},o=["textContent"],d={class:"value-container pull-right"},p=["textContent"],c={class:"row"},v={key:0,class:"input"},h={class:"col-10"},g={class:"col-2 value"},f=["value"],m={key:1,class:"input"},w={class:"col-12 value"},y=["value"];function b(e,t,a,b,_,k){const C=(0,l.up)("EntityIcon"),x=(0,l.up)("Slider");return(0,l.wg)(),(0,l.iD)("div",i,[(0,l._)("div",{class:(0,n.C_)(["head",{collapsed:e.collapsed}])},[(0,l._)("div",u,[(0,l.Wm)(C,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,l._)("div",r,[(0,l._)("div",{class:"name",textContent:(0,n.zw)(e.value.name)},null,8,o)]),(0,l._)("div",d,[null!=k.parsedValue?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"value",textContent:(0,n.zw)(k.parsedValue)},null,8,p)):(0,l.kq)("",!0),(0,l._)("button",{onClick:t[0]||(t[0]=(0,s.iM)((t=>e.collapsed=!e.collapsed),["stop"]))},[(0,l._)("i",{class:(0,n.C_)(["fas",{"fa-angle-up":!e.collapsed,"fa-angle-down":e.collapsed}])},null,2)])])],2),e.collapsed?(0,l.kq)("",!0):((0,l.wg)(),(0,l.iD)("div",{key:0,class:"body",onClick:t[3]||(t[3]=(0,s.iM)(((...e)=>k.prevent&&k.prevent(...e)),["stop"]))},[(0,l._)("div",c,[null!=e.value?.min&&null!=e.value?.max?((0,l.wg)(),(0,l.iD)("div",v,[(0,l._)("div",h,[(0,l.Wm)(x,{range:[e.value.min,e.value.max],"with-range":"",value:e.value.value,onInput:k.setValue},null,8,["range","value","onInput"])]),(0,l._)("div",g,[(0,l._)("input",{type:"number",value:e.value.value,onChange:t[1]||(t[1]=(...e)=>k.setValue&&k.setValue(...e))},null,40,f)])])):((0,l.wg)(),(0,l.iD)("div",m,[(0,l._)("div",w,[(0,l._)("input",{type:"number",value:e.value.value,onChange:t[2]||(t[2]=(...e)=>k.setValue&&k.setValue(...e))},null,40,y)])]))])]))])}var _=a(4358),k=a(847),C=a(4967),x={name:"Dimmer",components:{Slider:_.Z,EntityIcon:C["default"]},mixins:[k["default"]],computed:{parsedValue(){if(this.value?.is_write_only||null==this.value?.value)return null;let e=this.value.value;return this.value.unit&&(e=`${e} ${this.value.unit}`),e}},methods:{prevent(e){return e.stopPropagation(),!1},async setValue(e){if(e.target.value?.length){this.$emit("loading",!0);try{await this.request("entities.execute",{id:this.value.id,action:"set",value:+e.target.value})}finally{this.$emit("loading",!1)}}}}},$=a(3744);const D=(0,$.Z)(x,[["render",b],["__scopeId","data-v-3affff53"]]);var V=D}}]);
|
||||
//# sourceMappingURL=7523.367c2045.js.map
|
||||
//# sourceMappingURL=7523.5fed230e.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[7590],{7590:function(e,t,n){n.r(t),n.d(t,{default:function(){return b}});var l=n(6252),a=n(3577);const o={class:"entity battery-container"},s={class:"head"},r={class:"icon"},c={class:"label"},u=["textContent"],i={class:"value-container"},v=["textContent"];function d(e,t,n,d,f,p){const C=(0,l.up)("EntityIcon");return(0,l.wg)(),(0,l.iD)("div",o,[(0,l._)("div",s,[(0,l._)("div",r,[(0,l.Wm)(C,{entity:e.value,icon:p.icon,loading:e.loading,error:e.error},null,8,["entity","icon","loading","error"])]),(0,l._)("div",c,[(0,l._)("div",{class:"name",textContent:(0,a.zw)(e.value.name)},null,8,u)]),(0,l._)("div",i,[null!=p.valuePercent?((0,l.wg)(),(0,l.iD)("span",{key:0,class:"value",textContent:(0,a.zw)(p.valuePercent+"%")},null,8,v)):(0,l.kq)("",!0)])])])}var f=n(847),p=n(4967);const C=[{iconClass:"full",color:"#157145",value:.9},{iconClass:"three-quarters",color:"#94C595",value:.825},{iconClass:"half",color:"#F0B67F",value:.625},{iconClass:"quarter",color:"#FE5F55",value:.375},{iconClass:"low",color:"#CC444B",value:.15},{iconClass:"empty",color:"#EC0B43",value:.05}];var h={name:"Battery",components:{EntityIcon:p["default"]},mixins:[f["default"]],computed:{valuePercent(){if(null==this.value?.value)return null;const e=this.value.min||0,t=this.value.max||100;return(100*this.value.value/(t-e)).toFixed(0)},icon(){const e={...this.value.meta?.icon||{}};let t=this.valuePercent,n=C[0];if(null!=t){t=parseFloat(t)/100;for(const e of C){if(t>e.value)break;n=e}}return e["class"]=`fas fa-battery-${n.iconClass}`,e["color"]=n.color,e}},methods:{prevent(e){return e.stopPropagation(),!1}}},m=n(3744);const y=(0,m.Z)(h,[["render",d],["__scopeId","data-v-4b2ced66"]]);var b=y}}]);
|
||||
//# sourceMappingURL=7590.6cda174b.js.map
|
||||
//# sourceMappingURL=7590.ebe62444.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/7590.6cda174b.js","mappings":"gMACOA,MAAM,4B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAcM,MAdNC,EAcM,EAbJC,EAAAA,EAAAA,GAYM,MAZNC,EAYM,EAXJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAA6EC,EAAA,CAAhEC,OAAQC,EAAAC,MAAQC,KAAMC,EAAAD,KAAOE,QAASJ,EAAAI,QAAUC,MAAOL,EAAAK,O,+CAGtEX,EAAAA,EAAAA,GAEM,MAFNY,EAEM,EADJZ,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOgB,EAAAA,EAAAA,IAAQP,EAAWC,MAALO,O,aAGlCd,EAAAA,EAAAA,GAEM,MAFNe,EAEM,CADkE,MAAhBN,EAAAO,eAAY,WAAlElB,EAAAA,EAAAA,IAA8E,Q,MAAxED,MAAM,Q,aAAQgB,EAAAA,EAAAA,IAAQJ,EAAmBO,aAAJ,M,wDAUnD,MAAMC,EAAa,CACjB,CACEC,UAAW,OACXC,MAAO,UACPZ,MAAO,IAET,CACEW,UAAW,iBACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,OACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,UACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,MACXC,MAAO,UACPZ,MAAO,KAET,CACEW,UAAW,QACXC,MAAO,UACPZ,MAAO,MAIX,OACEO,KAAM,UACNM,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRR,YAAAA,GACE,GAAyB,MAArBS,KAAKlB,OAAOA,MACd,OAAO,KAET,MAAMmB,EAAMD,KAAKlB,MAAMmB,KAAO,EACxBC,EAAMF,KAAKlB,MAAMoB,KAAO,IAC9B,OAAS,IAAMF,KAAKlB,MAAMA,OAAUoB,EAAMD,IAAME,QAAQ,EAC1D,EAEApB,IAAAA,GACE,MAAMA,EAAO,IAAKiB,KAAKlB,MAAMsB,MAAMrB,MAAQ,CAAC,GAC5C,IAAID,EAAQkB,KAAKT,aACbc,EAAYb,EAAW,GAE3B,GAAa,MAATV,EAAe,CACjBA,EAAQwB,WAAWxB,GAAS,IAC5B,IAAK,MAAMyB,KAAKf,EAAY,CAC1B,GAAIV,EAAQyB,EAAEzB,MACZ,MACFuB,EAAYE,CACd,CACF,CAIA,OAFAxB,EAAK,SAAY,kBAAiBsB,EAAUZ,YAC5CV,EAAK,SAAWsB,EAAUX,MACnBX,CACT,GAGFyB,QAAS,CACPC,OAAAA,CAAQC,GAEN,OADAA,EAAMC,mBACC,CACT,I,UCvFJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Battery.vue","webpack://platypush/./src/components/panels/Entities/Battery.vue?1b53"],"sourcesContent":["<template>\n <div class=\"entity battery-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :icon=\"icon\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <span class=\"value\" v-text=\"valuePercent + '%'\" v-if=\"valuePercent != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nconst thresholds = [\n {\n iconClass: 'full',\n color: '#157145',\n value: 0.9,\n },\n {\n iconClass: 'three-quarters',\n color: '#94C595',\n value: 0.825,\n },\n {\n iconClass: 'half',\n color: '#F0B67F',\n value: 0.625,\n },\n {\n iconClass: 'quarter',\n color: '#FE5F55',\n value: 0.375,\n },\n {\n iconClass: 'low',\n color: '#CC444B',\n value: 0.15,\n },\n {\n iconClass: 'empty',\n color: '#EC0B43',\n value: 0.05,\n },\n]\n\nexport default {\n name: 'Battery',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n valuePercent() {\n if (this.value?.value == null)\n return null\n\n const min = this.value.min || 0\n const max = this.value.max || 100\n return ((100 * this.value.value) / (max - min)).toFixed(0)\n },\n\n icon() {\n const icon = {...(this.value.meta?.icon || {})}\n let value = this.valuePercent\n let threshold = thresholds[0]\n\n if (value != null) {\n value = parseFloat(value) / 100\n for (const t of thresholds) {\n if (value > t.value)\n break\n threshold = t\n }\n }\n\n icon['class'] = `fas fa-battery-${threshold.iconClass}`\n icon['color'] = threshold.color\n return icon\n },\n },\n\n methods: {\n prevent(event) {\n event.stopPropagation()\n return false\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Battery.vue?vue&type=template&id=4b2ced66&scoped=true\"\nimport script from \"./Battery.vue?vue&type=script&lang=js\"\nexport * from \"./Battery.vue?vue&type=script&lang=js\"\n\nimport \"./Battery.vue?vue&type=style&index=0&id=4b2ced66&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4b2ced66\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","icon","$options","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","valuePercent","thresholds","iconClass","color","components","EntityIcon","mixins","EntityMixin","computed","this","min","max","toFixed","meta","threshold","parseFloat","t","methods","prevent","event","stopPropagation","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/7590.ebe62444.js","mappings":"gMACOA,MAAM,4B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAINA,MAAM,S,qBAINA,MAAM,mB,2FAVfC,EAAAA,EAAAA,IAcM,MAdNC,EAcM,EAbJC,EAAAA,EAAAA,GAYM,MAZNC,EAYM,EAXJD,EAAAA,EAAAA,GAEM,MAFNE,EAEM,EADJC,EAAAA,EAAAA,IAA6EC,EAAA,CAAhEC,OAAQC,EAAAC,MAAQC,KAAMC,EAAAD,KAAOE,QAASJ,EAAAI,QAAUC,MAAOL,EAAAK,O,+CAGtEX,EAAAA,EAAAA,GAEM,MAFNY,EAEM,EADJZ,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOgB,EAAAA,EAAAA,IAAQP,EAAWC,MAALO,O,aAGlCd,EAAAA,EAAAA,GAEM,MAFNe,EAEM,CADkE,MAAhBN,EAAAO,eAAY,WAAlElB,EAAAA,EAAAA,IAA8E,Q,MAAxED,MAAM,Q,aAAQgB,EAAAA,EAAAA,IAAQJ,EAAmBO,aAAJ,M,wDAUnD,MAAMC,EAAa,CACjB,CACEC,UAAW,OACXC,MAAO,UACPZ,MAAO,IAET,CACEW,UAAW,iBACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,OACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,UACXC,MAAO,UACPZ,MAAO,MAET,CACEW,UAAW,MACXC,MAAO,UACPZ,MAAO,KAET,CACEW,UAAW,QACXC,MAAO,UACPZ,MAAO,MAIX,OACEO,KAAM,UACNM,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,YAETC,SAAU,CACRR,YAAAA,GACE,GAAyB,MAArBS,KAAKlB,OAAOA,MACd,OAAO,KAET,MAAMmB,EAAMD,KAAKlB,MAAMmB,KAAO,EACxBC,EAAMF,KAAKlB,MAAMoB,KAAO,IAC9B,OAAS,IAAMF,KAAKlB,MAAMA,OAAUoB,EAAMD,IAAME,QAAQ,EAC1D,EAEApB,IAAAA,GACE,MAAMA,EAAO,IAAKiB,KAAKlB,MAAMsB,MAAMrB,MAAQ,CAAC,GAC5C,IAAID,EAAQkB,KAAKT,aACbc,EAAYb,EAAW,GAE3B,GAAa,MAATV,EAAe,CACjBA,EAAQwB,WAAWxB,GAAS,IAC5B,IAAK,MAAMyB,KAAKf,EAAY,CAC1B,GAAIV,EAAQyB,EAAEzB,MACZ,MACFuB,EAAYE,CACd,CACF,CAIA,OAFAxB,EAAK,SAAY,kBAAiBsB,EAAUZ,YAC5CV,EAAK,SAAWsB,EAAUX,MACnBX,CACT,GAGFyB,QAAS,CACPC,OAAAA,CAAQC,GAEN,OADAA,EAAMC,mBACC,CACT,I,UCvFJ,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Battery.vue","webpack://platypush/./src/components/panels/Entities/Battery.vue?1b53"],"sourcesContent":["<template>\n <div class=\"entity battery-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon :entity=\"value\" :icon=\"icon\" :loading=\"loading\" :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n\n <div class=\"value-container\">\n <span class=\"value\" v-text=\"valuePercent + '%'\" v-if=\"valuePercent != null\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nconst thresholds = [\n {\n iconClass: 'full',\n color: '#157145',\n value: 0.9,\n },\n {\n iconClass: 'three-quarters',\n color: '#94C595',\n value: 0.825,\n },\n {\n iconClass: 'half',\n color: '#F0B67F',\n value: 0.625,\n },\n {\n iconClass: 'quarter',\n color: '#FE5F55',\n value: 0.375,\n },\n {\n iconClass: 'low',\n color: '#CC444B',\n value: 0.15,\n },\n {\n iconClass: 'empty',\n color: '#EC0B43',\n value: 0.05,\n },\n]\n\nexport default {\n name: 'Battery',\n components: {EntityIcon},\n mixins: [EntityMixin],\n\n computed: {\n valuePercent() {\n if (this.value?.value == null)\n return null\n\n const min = this.value.min || 0\n const max = this.value.max || 100\n return ((100 * this.value.value) / (max - min)).toFixed(0)\n },\n\n icon() {\n const icon = {...(this.value.meta?.icon || {})}\n let value = this.valuePercent\n let threshold = thresholds[0]\n\n if (value != null) {\n value = parseFloat(value) / 100\n for (const t of thresholds) {\n if (value > t.value)\n break\n threshold = t\n }\n }\n\n icon['class'] = `fas fa-battery-${threshold.iconClass}`\n icon['color'] = threshold.color\n return icon\n },\n },\n\n methods: {\n prevent(event) {\n event.stopPropagation()\n return false\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Battery.vue?vue&type=template&id=4b2ced66&scoped=true\"\nimport script from \"./Battery.vue?vue&type=script&lang=js\"\nexport * from \"./Battery.vue?vue&type=script&lang=js\"\n\nimport \"./Battery.vue?vue&type=style&index=0&id=4b2ced66&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4b2ced66\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","icon","$options","loading","error","_hoisted_4","_toDisplayString","name","_hoisted_6","valuePercent","thresholds","iconClass","color","components","EntityIcon","mixins","EntityMixin","computed","this","min","max","toFixed","meta","threshold","parseFloat","t","methods","prevent","event","stopPropagation","__exports__","render"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[8391],{3405:function(t,e,a){a.d(e,{Z:function(){return h}});var n=a(6252),i=a(3577),l=a(9963);const s=t=>((0,n.dD)("data-v-a6396ae8"),t=t(),(0,n.Cn)(),t),o=["checked"],u=s((()=>(0,n._)("div",{class:"switch"},[(0,n._)("div",{class:"dot"})],-1))),c={class:"label"};function d(t,e,a,s,d,r){return(0,n.wg)(),(0,n.iD)("div",{class:(0,i.C_)(["power-switch",{disabled:a.disabled}]),onClick:e[0]||(e[0]=(0,l.iM)(((...t)=>r.onInput&&r.onInput(...t)),["stop"]))},[(0,n._)("input",{type:"checkbox",checked:a.value},null,8,o),(0,n._)("label",null,[u,(0,n._)("span",c,[(0,n.WI)(t.$slots,"default",{},void 0,!0)])])],2)}var r={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput(t){if(this.disabled)return!1;this.$emit("input",t)}}},p=a(3744);const v=(0,p.Z)(r,[["render",d],["__scopeId","data-v-a6396ae8"]]);var h=v},8391:function(t,e,a){a.r(e),a.d(e,{default:function(){return y}});var n=a(6252),i=a(3577),l=a(9963);const s={class:"entity switch-container"},o={class:"head"},u={class:"col-1 icon"},c={class:"col-9 label"},d=["textContent"],r={class:"col-2 switch pull-right"};function p(t,e,a,p,v,h){const g=(0,n.up)("EntityIcon"),_=(0,n.up)("ToggleSwitch");return(0,n.wg)(),(0,n.iD)("div",s,[(0,n._)("div",o,[(0,n._)("div",u,[(0,n.Wm)(g,{entity:t.value,loading:t.loading,error:t.error},null,8,["entity","loading","error"])]),(0,n._)("div",c,[(0,n._)("div",{class:"name",textContent:(0,i.zw)(t.value.name)},null,8,d)]),(0,n._)("div",r,[(0,n.Wm)(_,{value:!t.value.is_write_only&&t.value.state,disabled:t.loading||t.value.is_read_only,onInput:h.toggle,onClick:e[0]||(e[0]=(0,l.iM)((()=>{}),["stop"]))},null,8,["value","disabled","onInput"])])])])}var v=a(3405),h=a(4967),g=a(847),_={name:"Switch",components:{ToggleSwitch:v.Z,EntityIcon:h["default"]},mixins:[g["default"]],methods:{async toggle(t){t.stopPropagation(),this.$emit("loading",!0);try{if(await this.request("entities.execute",{id:this.value.id,action:"toggle"}),this.value.is_write_only){const t=this;t.value.state=!0,setTimeout((()=>t.value.state=!1),250)}}finally{this.$emit("loading",!1)}}}},f=a(3744);const w=(0,f.Z)(_,[["render",p],["__scopeId","data-v-2aaabd26"]]);var y=w}}]);
|
||||
//# sourceMappingURL=8391.119357c7.js.map
|
||||
//# sourceMappingURL=8391.16e30eb1.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[8621],{3405:function(e,n,t){t.d(n,{Z:function(){return h}});var a=t(6252),l=t(3577),s=t(9963);const i=e=>((0,a.dD)("data-v-a6396ae8"),e=e(),(0,a.Cn)(),e),o=["checked"],u=i((()=>(0,a._)("div",{class:"switch"},[(0,a._)("div",{class:"dot"})],-1))),c={class:"label"};function d(e,n,t,i,d,r){return(0,a.wg)(),(0,a.iD)("div",{class:(0,l.C_)(["power-switch",{disabled:t.disabled}]),onClick:n[0]||(n[0]=(0,s.iM)(((...e)=>r.onInput&&r.onInput(...e)),["stop"]))},[(0,a._)("input",{type:"checkbox",checked:t.value},null,8,o),(0,a._)("label",null,[u,(0,a._)("span",c,[(0,a.WI)(e.$slots,"default",{},void 0,!0)])])],2)}var r={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput(e){if(this.disabled)return!1;this.$emit("input",e)}}},v=t(3744);const p=(0,v.Z)(r,[["render",d],["__scopeId","data-v-a6396ae8"]]);var h=p},8621:function(e,n,t){t.r(n),t.d(n,{default:function(){return g}});var a=t(6252),l=t(3577);const s={class:"entity sensor-container"},i={class:"head"},o={class:"icon"},u={class:"label"},c=["textContent"],d={key:0,class:"value-container"};function r(e,n,t,r,v,p){const h=(0,a.up)("EntityIcon"),f=(0,a.up)("ToggleSwitch");return(0,a.wg)(),(0,a.iD)("div",s,[(0,a._)("div",i,[(0,a._)("div",o,[(0,a.Wm)(h,{entity:e.value,loading:e.loading,error:e.error},null,8,["entity","loading","error"])]),(0,a._)("div",u,[(0,a._)("div",{class:"name",textContent:(0,l.zw)(e.value.name)},null,8,c)]),null!=e.value.value?((0,a.wg)(),(0,a.iD)("div",d,[(0,a.Wm)(f,{value:e.value.value,disabled:!0},null,8,["value"])])):(0,a.kq)("",!0)])])}var v=t(847),p=t(4967),h=t(3405),f={name:"BinarySensor",components:{EntityIcon:p["default"],ToggleSwitch:h.Z},mixins:[v["default"]]},b=t(3744);const _=(0,b.Z)(f,[["render",r],["__scopeId","data-v-8baaebb4"]]);var g=_}}]);
|
||||
//# sourceMappingURL=8621.0aa03df1.js.map
|
||||
//# sourceMappingURL=8621.33df9b41.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[8769],{8769:function(n,e,t){t.r(e),t.d(e,{default:function(){return h}});var a=t(6252),i=t(3577);const c={class:"entity device-container"},l={class:"head"},s={class:"icon"},o={class:"label"},r=["textContent"];function u(n,e,t,u,d,v){const p=(0,a.up)("EntityIcon");return(0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",l,[(0,a._)("div",s,[(0,a.Wm)(p,{entity:n.value,loading:n.loading,error:n.error},null,8,["entity","loading","error"])]),(0,a._)("div",o,[(0,a._)("div",{class:"name",textContent:(0,i.zw)(n.value.name)},null,8,r)])])])}var d=t(847),v=t(4967),p={name:"Device",components:{EntityIcon:v["default"]},mixins:[d["default"]]},f=t(3744);const y=(0,f.Z)(p,[["render",u],["__scopeId","data-v-07323f6c"]]);var h=y}}]);
|
||||
//# sourceMappingURL=8769.5ea5c0cb.js.map
|
||||
//# sourceMappingURL=8769.02eed3a9.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/js/8769.5ea5c0cb.js","mappings":"gMACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAONA,MAAM,S,2FATfC,EAAAA,EAAAA,IAaM,MAbNC,EAaM,EAZJC,EAAAA,EAAAA,GAWM,MAXNC,EAWM,EAVJD,EAAAA,EAAAA,GAKM,MALNE,EAKM,EAJJC,EAAAA,EAAAA,IAGmBC,EAAA,CAFhBC,OAAQC,EAAAC,MACRC,QAASF,EAAAE,QACTC,MAAOH,EAAAG,O,wCAGZT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,uCAUxC,GACEA,KAAM,SACNC,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UCjBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Device.vue","webpack://platypush/./src/components/panels/Entities/Device.vue?1785"],"sourcesContent":["<template>\n <div class=\"entity device-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon\n :entity=\"value\"\n :loading=\"loading\"\n :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Device',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Device.vue?vue&type=template&id=07323f6c&scoped=true\"\nimport script from \"./Device.vue?vue&type=script&lang=js\"\nexport * from \"./Device.vue?vue&type=script&lang=js\"\n\nimport \"./Device.vue?vue&type=style&index=0&id=07323f6c&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-07323f6c\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/js/8769.02eed3a9.js","mappings":"gMACOA,MAAM,2B,GACJA,MAAM,Q,GACJA,MAAM,Q,GAONA,MAAM,S,2FATfC,EAAAA,EAAAA,IAaM,MAbNC,EAaM,EAZJC,EAAAA,EAAAA,GAWM,MAXNC,EAWM,EAVJD,EAAAA,EAAAA,GAKM,MALNE,EAKM,EAJJC,EAAAA,EAAAA,IAGmBC,EAAA,CAFhBC,OAAQC,EAAAC,MACRC,QAASF,EAAAE,QACTC,MAAOH,EAAAG,O,wCAGZT,EAAAA,EAAAA,GAEM,MAFNU,EAEM,EADJV,EAAAA,EAAAA,GAAwC,OAAnCH,MAAM,O,aAAOc,EAAAA,EAAAA,IAAQL,EAAWC,MAALK,O,uCAUxC,GACEA,KAAM,SACNC,WAAY,CAACC,WAAUA,EAAAA,YACvBC,OAAQ,CAACC,EAAAA,a,UCjBX,MAAMC,GAA2B,OAAgB,EAAQ,CAAC,CAAC,SAASC,GAAQ,CAAC,YAAY,qBAEzF,O","sources":["webpack://platypush/./src/components/panels/Entities/Device.vue","webpack://platypush/./src/components/panels/Entities/Device.vue?1785"],"sourcesContent":["<template>\n <div class=\"entity device-container\">\n <div class=\"head\">\n <div class=\"icon\">\n <EntityIcon\n :entity=\"value\"\n :loading=\"loading\"\n :error=\"error\" />\n </div>\n\n <div class=\"label\">\n <div class=\"name\" v-text=\"value.name\" />\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport EntityMixin from \"./EntityMixin\"\nimport EntityIcon from \"./EntityIcon\"\n\nexport default {\n name: 'Device',\n components: {EntityIcon},\n mixins: [EntityMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"common\";\n</style>\n","import { render } from \"./Device.vue?vue&type=template&id=07323f6c&scoped=true\"\nimport script from \"./Device.vue?vue&type=script&lang=js\"\nexport * from \"./Device.vue?vue&type=script&lang=js\"\n\nimport \"./Device.vue?vue&type=style&index=0&id=07323f6c&lang=scss&scoped=true\"\n\nimport exportComponent from \"../../../../node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-07323f6c\"]])\n\nexport default __exports__"],"names":["class","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_component_EntityIcon","entity","_ctx","value","loading","error","_hoisted_4","_toDisplayString","name","components","EntityIcon","mixins","EntityMixin","__exports__","render"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[9624],{9624:function(l,e,a){a.r(e),a.d(e,{default:function(){return D}});var t=a(6252),s=a(3577),i=a(9963);const n=l=>((0,t.dD)("data-v-c4b6a144"),l=l(),(0,t.Cn)(),l),o={key:0,class:"entity variable-container"},u={class:"icon"},d={class:"label"},c=["textContent"],r=["textContent"],v={class:"row"},p={class:"row"},h={class:"col-9"},_=["disabled"],b={class:"col-3 pull-right"},f=["disabled"],m=n((()=>(0,t._)("i",{class:"fas fa-trash"},null,-1))),y=[m],g=["disabled"],k=n((()=>(0,t._)("i",{class:"fas fa-check"},null,-1))),w=[k];function C(l,e,a,n,m,k){const C=(0,t.up)("EntityIcon");return null!=l.value.value?((0,t.wg)(),(0,t.iD)("div",o,[(0,t._)("div",{class:(0,s.C_)(["head",{collapsed:l.collapsed}])},[(0,t._)("div",u,[(0,t.Wm)(C,{entity:l.value,loading:l.loading,error:l.error},null,8,["entity","loading","error"])]),(0,t._)("div",d,[(0,t._)("div",{class:"name",textContent:(0,s.zw)(l.value.name)},null,8,c)]),(0,t._)("div",{class:"value-and-toggler",onClick:e[1]||(e[1]=(0,i.iM)((e=>l.collapsed=!l.collapsed),["stop"]))},[null!=l.value?.value?((0,t.wg)(),(0,t.iD)("div",{key:0,class:"value",textContent:(0,s.zw)(l.value.value)},null,8,r)):(0,t.kq)("",!0),(0,t._)("div",{class:"collapse-toggler",onClick:e[0]||(e[0]=(0,i.iM)((e=>l.collapsed=!l.collapsed),["stop"]))},[(0,t._)("i",{class:(0,s.C_)(["fas",{"fa-chevron-down":l.collapsed,"fa-chevron-up":!l.collapsed}])},null,2)])])],2),l.collapsed?(0,t.kq)("",!0):((0,t.wg)(),(0,t.iD)("div",{key:0,class:"body",onClick:e[5]||(e[5]=(0,i.iM)(((...e)=>l.prevent&&l.prevent(...e)),["stop"]))},[(0,t._)("div",v,[(0,t._)("form",{onSubmit:e[4]||(e[4]=(0,i.iM)(((...l)=>k.setValue&&k.setValue(...l)),["prevent"]))},[(0,t._)("div",p,[(0,t._)("div",h,[(0,t.wy)((0,t._)("input",{type:"text","onUpdate:modelValue":e[2]||(e[2]=e=>l.value_=e),placeholder:"Variable value",disabled:l.loading,ref:"text"},null,8,_),[[i.nr,l.value_]])]),(0,t._)("div",b,[(0,t._)("button",{type:"button",title:"Clear",onClick:e[3]||(e[3]=(0,i.iM)(((...l)=>k.clearValue&&k.clearValue(...l)),["stop"])),disabled:l.loading},y,8,f),(0,t._)("button",{type:"submit",title:"Edit",disabled:l.loading},w,8,g)])])],32)])]))])):(0,t.kq)("",!0)}var V=a(847),x=a(4967),q={name:"Variable",components:{EntityIcon:x["default"]},mixins:[V["default"]],emits:["loading"],data:function(){return{collapsed:!0,value_:null}},computed:{isCollapsed(){return this.collapsed}},methods:{async clearValue(){this.$emit("loading",!0);try{await this.request("variable.unset",{name:this.value.name})}finally{this.$emit("loading",!1)}},async setValue(){const l=this.value_;if(!l?.length)return await this.clearValue();this.$emit("loading",!0);try{const e={};e[this.value.name]=l,await this.request("variable.set",e)}finally{this.$emit("loading",!1)}}},mounted(){this.value_=this.value.value,this.$watch((()=>this.value.value),(l=>{this.value_=l}))}},M=a(3744);const $=(0,M.Z)(q,[["render",C],["__scopeId","data-v-c4b6a144"]]);var D=$}}]);
|
||||
//# sourceMappingURL=9624.5124c411.js.map
|
||||
//# sourceMappingURL=9624.e590eb03.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
"use strict";(self["webpackChunkplatypush"]=self["webpackChunkplatypush"]||[]).push([[984],{3405:function(t,e,n){n.d(e,{Z:function(){return v}});var i=n(6252),a=n(3577),o=n(9963);const s=t=>((0,i.dD)("data-v-a6396ae8"),t=t(),(0,i.Cn)(),t),l=["checked"],c=s((()=>(0,i._)("div",{class:"switch"},[(0,i._)("div",{class:"dot"})],-1))),d={class:"label"};function u(t,e,n,s,u,r){return(0,i.wg)(),(0,i.iD)("div",{class:(0,a.C_)(["power-switch",{disabled:n.disabled}]),onClick:e[0]||(e[0]=(0,o.iM)(((...t)=>r.onInput&&r.onInput(...t)),["stop"]))},[(0,i._)("input",{type:"checkbox",checked:n.value},null,8,l),(0,i._)("label",null,[c,(0,i._)("span",d,[(0,i.WI)(t.$slots,"default",{},void 0,!0)])])],2)}var r={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput(t){if(this.disabled)return!1;this.$emit("input",t)}}},p=n(3744);const h=(0,p.Z)(r,[["render",u],["__scopeId","data-v-a6396ae8"]]);var v=h},984:function(t,e,n){n.r(e),n.d(e,{default:function(){return _}});var i=n(6252),a=n(3577),o=n(9963);const s={class:"entity bluetooth-service-container"},l={class:"head"},c={class:"col-1 icon"},d={class:"col-9 label"},u=["textContent"],r={class:"col-2 connector pull-right"};function p(t,e,n,p,h,v){const g=(0,i.up)("EntityIcon"),f=(0,i.up)("ToggleSwitch");return(0,i.wg)(),(0,i.iD)("div",s,[(0,i._)("div",l,[(0,i._)("div",c,[(0,i.Wm)(g,{entity:t.value,loading:t.loading,error:t.error},null,8,["entity","loading","error"])]),(0,i._)("div",d,[(0,i._)("div",{class:"name",textContent:(0,a.zw)(t.value.name)},null,8,u)]),(0,i._)("div",r,[(0,i.Wm)(f,{value:t.value.connected,disabled:t.loading,onInput:v.connect,onClick:e[0]||(e[0]=(0,o.iM)((()=>{}),["stop"]))},null,8,["value","disabled","onInput"])])])])}var h=n(3405),v=n(4967),g=n(847),f={name:"BluetoothService",components:{ToggleSwitch:h.Z,EntityIcon:v["default"]},mixins:[g["default"]],methods:{async connect(t){t.stopPropagation(),this.$emit("loading",!0);const e="bluetooth."+(this.value.connected?"disconnect":"connect");try{await this.request(e,{device:this.parent.address,service_uuid:this.uuid})}finally{this.$emit("loading",!1)}},async disconnect(t){t.stopPropagation(),this.$emit("loading",!0);try{await this.request("bluetooth.disconnect",{device:this.parent.address})}finally{this.$emit("loading",!1)}}}},m=n(3744);const y=(0,m.Z)(f,[["render",p],["__scopeId","data-v-5c801a06"]]);var _=y}}]);
|
||||
//# sourceMappingURL=984.ae424e7e.js.map
|
||||
//# sourceMappingURL=984.b15beee9.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,7 @@ import Utils from "@/Utils"
|
|||
export default {
|
||||
name: "EntityMixin",
|
||||
mixins: [Utils],
|
||||
emits: ['input'],
|
||||
emits: ['input', 'loading'],
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -168,10 +168,11 @@ export default {
|
|||
|
||||
methods: {
|
||||
addEntity(entity) {
|
||||
this.entities[entity.id] = entity
|
||||
|
||||
if (entity.parent_id != null)
|
||||
return // Only group entities that have no parent
|
||||
|
||||
this.entities[entity.id] = entity;
|
||||
['id', 'type', 'category', 'plugin'].forEach((attr) => {
|
||||
if (entity[attr] == null)
|
||||
return
|
||||
|
|
|
@ -96,7 +96,7 @@ $collapse-toggler-width: 2em;
|
|||
.value {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,12 @@ from websocket import WebSocketApp
|
|||
|
||||
from platypush.backend import Backend
|
||||
from platypush.context import get_plugin
|
||||
from platypush.message.event.trello import MoveCardEvent, NewCardEvent, ArchivedCardEvent, \
|
||||
UnarchivedCardEvent
|
||||
from platypush.message.event.trello import (
|
||||
MoveCardEvent,
|
||||
NewCardEvent,
|
||||
ArchivedCardEvent,
|
||||
UnarchivedCardEvent,
|
||||
)
|
||||
|
||||
from platypush.plugins.trello import TrelloPlugin
|
||||
|
||||
|
@ -33,9 +37,9 @@ class TrelloBackend(Backend):
|
|||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.trello.NewCardEvent` when a card is created.
|
||||
* :class:`platypush.message.event.MoveCardEvent` when a card is moved.
|
||||
* :class:`platypush.message.event.ArchivedCardEvent` when a card is archived/closed.
|
||||
* :class:`platypush.message.event.UnarchivedCardEvent` when a card is un-archived/opened.
|
||||
* :class:`platypush.message.event.trello.MoveCardEvent` when a card is moved.
|
||||
* :class:`platypush.message.event.trello.ArchivedCardEvent` when a card is archived/closed.
|
||||
* :class:`platypush.message.event.trello.UnarchivedCardEvent` when a card is un-archived/opened.
|
||||
|
||||
"""
|
||||
|
||||
|
@ -69,21 +73,26 @@ class TrelloBackend(Backend):
|
|||
|
||||
def _initialize_connection(self, ws: WebSocketApp):
|
||||
for board_id in self._boards_by_id.keys():
|
||||
self._send(ws, {
|
||||
'type': 'subscribe',
|
||||
'modelType': 'Board',
|
||||
'idModel': board_id,
|
||||
'tags': ['clientActions', 'updates'],
|
||||
'invitationTokens': [],
|
||||
})
|
||||
self._send(
|
||||
ws,
|
||||
{
|
||||
'type': 'subscribe',
|
||||
'modelType': 'Board',
|
||||
'idModel': board_id,
|
||||
'tags': ['clientActions', 'updates'],
|
||||
'invitationTokens': [],
|
||||
},
|
||||
)
|
||||
|
||||
self.logger.info('Trello boards subscribed')
|
||||
|
||||
def _on_msg(self):
|
||||
def hndl(*args):
|
||||
if len(args) < 2:
|
||||
self.logger.warning('Missing websocket argument - make sure that you are using '
|
||||
'a version of websocket-client < 0.53.0 or >= 0.58.0')
|
||||
self.logger.warning(
|
||||
'Missing websocket argument - make sure that you are using '
|
||||
'a version of websocket-client < 0.53.0 or >= 0.58.0'
|
||||
)
|
||||
return
|
||||
|
||||
ws, msg = args[:2]
|
||||
|
@ -96,7 +105,9 @@ class TrelloBackend(Backend):
|
|||
try:
|
||||
msg = json.loads(msg)
|
||||
except Exception as e:
|
||||
self.logger.warning('Received invalid JSON message from Trello: {}: {}'.format(msg, e))
|
||||
self.logger.warning(
|
||||
'Received invalid JSON message from Trello: {}: {}'.format(msg, e)
|
||||
)
|
||||
return
|
||||
|
||||
if 'error' in msg:
|
||||
|
@ -119,8 +130,12 @@ class TrelloBackend(Backend):
|
|||
args = {
|
||||
'card_id': delta['data']['card']['id'],
|
||||
'card_name': delta['data']['card']['name'],
|
||||
'list_id': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('id'),
|
||||
'list_name': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('name'),
|
||||
'list_id': (
|
||||
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
||||
).get('id'),
|
||||
'list_name': (
|
||||
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
||||
).get('name'),
|
||||
'board_id': delta['data']['board']['id'],
|
||||
'board_name': delta['data']['board']['name'],
|
||||
'closed': delta.get('closed'),
|
||||
|
@ -134,14 +149,20 @@ class TrelloBackend(Backend):
|
|||
self.bus.post(NewCardEvent(**args))
|
||||
elif delta.get('type') == 'updateCard':
|
||||
if 'listBefore' in delta['data']:
|
||||
args.update({
|
||||
'old_list_id': delta['data']['listBefore']['id'],
|
||||
'old_list_name': delta['data']['listBefore']['name'],
|
||||
})
|
||||
args.update(
|
||||
{
|
||||
'old_list_id': delta['data']['listBefore']['id'],
|
||||
'old_list_name': delta['data']['listBefore']['name'],
|
||||
}
|
||||
)
|
||||
|
||||
self.bus.post(MoveCardEvent(**args))
|
||||
elif 'closed' in delta['data'].get('old', {}):
|
||||
cls = UnarchivedCardEvent if delta['data']['old']['closed'] else ArchivedCardEvent
|
||||
cls = (
|
||||
UnarchivedCardEvent
|
||||
if delta['data']['old']['closed']
|
||||
else ArchivedCardEvent
|
||||
)
|
||||
self.bus.post(cls(**args))
|
||||
|
||||
return hndl
|
||||
|
@ -185,11 +206,13 @@ class TrelloBackend(Backend):
|
|||
self._req_id += 1
|
||||
|
||||
def _connect(self) -> WebSocketApp:
|
||||
return WebSocketApp(self.url,
|
||||
on_open=self._on_open(),
|
||||
on_message=self._on_msg(),
|
||||
on_error=self._on_error(),
|
||||
on_close=self._on_close())
|
||||
return WebSocketApp(
|
||||
self.url,
|
||||
on_open=self._on_open(),
|
||||
on_message=self._on_msg(),
|
||||
on_error=self._on_error(),
|
||||
on_close=self._on_close(),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
manifest:
|
||||
events:
|
||||
platypush.message.event.ArchivedCardEvent: when a card is archived/closed.
|
||||
platypush.message.event.MoveCardEvent: when a card is moved.
|
||||
platypush.message.event.UnarchivedCardEvent: when a card is un-archived/opened.
|
||||
platypush.message.event.trello.ArchivedCardEvent: when a card is archived/closed.
|
||||
platypush.message.event.trello.MoveCardEvent: when a card is moved.
|
||||
platypush.message.event.trello.UnarchivedCardEvent: when a card is un-archived/opened.
|
||||
platypush.message.event.trello.NewCardEvent: when a card is created.
|
||||
install:
|
||||
pip: []
|
||||
|
|
|
@ -15,7 +15,7 @@ class WiimoteBackend(Backend):
|
|||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.Wiimote.WiimoteEvent` \
|
||||
* :class:`platypush.message.event.wiimote.WiimoteEvent` \
|
||||
when the state of the Wiimote (battery, buttons, acceleration etc.) changes
|
||||
|
||||
Requires:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
manifest:
|
||||
events:
|
||||
platypush.message.event.Wiimote.WiimoteEvent: when the state of the Wiimote (battery,
|
||||
platypush.message.event.wiimote.WiimoteEvent: when the state of the Wiimote (battery,
|
||||
buttons, acceleration etc.) changes
|
||||
install:
|
||||
pip: []
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import warnings
|
||||
|
||||
from platypush.backend import Backend
|
||||
|
||||
|
||||
class ZigbeeMqttBackend(Backend):
|
||||
"""
|
||||
Listen for events on a zigbee2mqtt service.
|
||||
|
||||
**WARNING**: This backend is **DEPRECATED** and it will be removed in a
|
||||
future version.
|
||||
|
||||
It has been merged with
|
||||
:class:`platypush.plugins.zigbee.mqtt.ZigbeeMqttPlugin`.
|
||||
|
||||
Now you can simply configure the `zigbee.mqtt` plugin in order to enable
|
||||
the Zigbee integration - no need to enable both the plugin and the backend.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
warnings.warn(
|
||||
'''
|
||||
The zigbee.mqtt backend has been merged into the zigbee.mqtt
|
||||
plugin. It is now deprecated and it will be removed in a future
|
||||
version.
|
||||
Please remove any references to it from your configuration.
|
||||
''',
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
self.wait_stop()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -1,350 +0,0 @@
|
|||
import inspect
|
||||
import logging
|
||||
import queue
|
||||
import os
|
||||
import threading
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from platypush.backend import Backend
|
||||
from platypush.config import Config
|
||||
from platypush.message.event.zwave import (
|
||||
ZwaveNetworkReadyEvent,
|
||||
ZwaveNetworkStoppedEvent,
|
||||
ZwaveEvent,
|
||||
ZwaveNodeAddedEvent,
|
||||
ZwaveValueAddedEvent,
|
||||
ZwaveNodeQueryCompletedEvent,
|
||||
ZwaveValueChangedEvent,
|
||||
ZwaveValueRefreshedEvent,
|
||||
ZwaveValueRemovedEvent,
|
||||
ZwaveNetworkResetEvent,
|
||||
ZwaveCommandEvent,
|
||||
ZwaveCommandWaitingEvent,
|
||||
ZwaveNodeRemovedEvent,
|
||||
ZwaveNodeRenamedEvent,
|
||||
ZwaveNodeReadyEvent,
|
||||
ZwaveButtonRemovedEvent,
|
||||
ZwaveButtonCreatedEvent,
|
||||
ZwaveButtonOnEvent,
|
||||
ZwaveButtonOffEvent,
|
||||
ZwaveNetworkErrorEvent,
|
||||
ZwaveNodeGroupEvent,
|
||||
ZwaveNodePollingEnabledEvent,
|
||||
ZwaveNodePollingDisabledEvent,
|
||||
ZwaveNodeSceneEvent,
|
||||
ZwaveNodeEvent,
|
||||
)
|
||||
|
||||
event_queue = queue.Queue()
|
||||
network_ready = threading.Event()
|
||||
|
||||
|
||||
class _ZWEvent:
|
||||
def __init__(self, signal: str, sender: str, network=None, **kwargs):
|
||||
self.signal = signal
|
||||
self.sender = sender
|
||||
self.network = network
|
||||
self.args = kwargs
|
||||
|
||||
|
||||
def _zwcallback(signal, sender, network, **kwargs):
|
||||
if signal == network.SIGNAL_NETWORK_AWAKED:
|
||||
network_ready.set()
|
||||
event_queue.put(_ZWEvent(signal=signal, sender=sender, network=network, **kwargs))
|
||||
|
||||
|
||||
class ZwaveBackend(Backend):
|
||||
"""
|
||||
Start and manage a Z-Wave network.
|
||||
|
||||
If you are using a USB adapter and want a consistent naming for the device paths, you can use udev.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# Get the vendorID and productID of your device through lsusb.
|
||||
# Then add a udev rule for it to link it e.g. to /dev/zwave.
|
||||
|
||||
cat <<EOF > /etc/udev/rules.d/92-zwave.rules
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0658", ATTRS{idProduct}=="0200", SYMLINK+="zwave"
|
||||
EOF
|
||||
|
||||
# Restart the udev service
|
||||
systemctl restart systemd-udevd.service
|
||||
|
||||
.. note::
|
||||
|
||||
This backend is deprecated, since the underlying ``python-openzwave`` is
|
||||
quite buggy and largely unmaintained.
|
||||
|
||||
Use the `zwave.mqtt` backend instead
|
||||
(:class:`platypush.backend.zwave.mqtt.ZwaveMqttBackend`).
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.zwave.ZwaveNetworkReadyEvent` when the network is up and running.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNetworkStoppedEvent` when the network goes down.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNetworkResetEvent` when the network is reset.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNetworkErrorEvent` when an error occurs on the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeQueryCompletedEvent` when all the nodes on the network
|
||||
have been queried.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeEvent` when a node attribute changes.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeAddedEvent` when a node is added to the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeRemovedEvent` when a node is removed from the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeRenamedEvent` when a node is renamed.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeReadyEvent` when a node is ready.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeGroupEvent` when a node is associated/de-associated to a
|
||||
group.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodeSceneEvent` when a scene is set on a node.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodePollingEnabledEvent` when the polling is successfully turned
|
||||
on a node.
|
||||
* :class:`platypush.message.event.zwave.ZwaveNodePollingDisabledEvent` when the polling is successfully turned
|
||||
off a node.
|
||||
* :class:`platypush.message.event.zwave.ZwaveButtonCreatedEvent` when a button is added to the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveButtonRemovedEvent` when a button is removed from the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveButtonOnEvent` when a button is pressed.
|
||||
* :class:`platypush.message.event.zwave.ZwaveButtonOffEvent` when a button is released.
|
||||
* :class:`platypush.message.event.zwave.ZwaveValueAddedEvent` when a value is added to a node on the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveValueChangedEvent` when the value of a node on the network
|
||||
changes.
|
||||
* :class:`platypush.message.event.zwave.ZwaveValueRefreshedEvent` when the value of a node on the network
|
||||
is refreshed.
|
||||
* :class:`platypush.message.event.zwave.ZwaveValueRemovedEvent` when the value of a node on the network
|
||||
is removed.
|
||||
* :class:`platypush.message.event.zwave.ZwaveCommandEvent` when a command is received on the network.
|
||||
* :class:`platypush.message.event.zwave.ZwaveCommandWaitingEvent` when a command is waiting for a message
|
||||
to complete.
|
||||
|
||||
Requires:
|
||||
|
||||
* **python-openzwave** (``pip install python-openzwave``)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: str,
|
||||
config_path: Optional[str] = None,
|
||||
user_path: Optional[str] = None,
|
||||
ready_timeout: float = 10.0,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
:param device: Path to the Z-Wave adapter (e.g. /dev/ttyUSB0 or /dev/ttyACM0).
|
||||
:param config_path: Z-Wave configuration path (default: ``<OPENZWAVE_PATH>/ozw_config``).
|
||||
:param user_path: Z-Wave user path where runtime and configuration files will be stored
|
||||
(default: ``<PLATYPUSH_WORKDIR>/zwave``).
|
||||
:param ready_timeout: Network ready timeout in seconds (default: 60).
|
||||
"""
|
||||
import python_openzwave
|
||||
from openzwave.network import ZWaveNetwork
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self.device = device
|
||||
|
||||
if not config_path:
|
||||
# noinspection PyTypeChecker
|
||||
config_path = os.path.join(
|
||||
os.path.dirname(inspect.getfile(python_openzwave)), 'ozw_config'
|
||||
)
|
||||
if not user_path:
|
||||
user_path = os.path.join(Config.get('workdir'), 'zwave')
|
||||
os.makedirs(user_path, mode=0o770, exist_ok=True)
|
||||
|
||||
self.config_path = config_path
|
||||
self.user_path = user_path
|
||||
self.ready_timeout = ready_timeout
|
||||
self.network: Optional[ZWaveNetwork] = None
|
||||
|
||||
def start_network(self):
|
||||
if self.network and self.network.state >= self.network.STATE_AWAKED:
|
||||
self.logger.info('Z-Wave network already started')
|
||||
return
|
||||
|
||||
from openzwave.network import ZWaveNetwork, dispatcher
|
||||
from openzwave.option import ZWaveOption
|
||||
|
||||
network_ready.clear()
|
||||
logging.getLogger('openzwave').addHandler(self.logger)
|
||||
opts = ZWaveOption(
|
||||
self.device, config_path=self.config_path, user_path=self.user_path
|
||||
)
|
||||
opts.set_console_output(False)
|
||||
opts.lock()
|
||||
|
||||
self.network = ZWaveNetwork(opts, log=None)
|
||||
dispatcher.connect(_zwcallback)
|
||||
ready = network_ready.wait(self.ready_timeout)
|
||||
|
||||
if not ready:
|
||||
self.logger.warning(
|
||||
'Driver not ready after {} seconds: continuing anyway'.format(
|
||||
self.ready_timeout
|
||||
)
|
||||
)
|
||||
|
||||
def stop_network(self):
|
||||
if self.network:
|
||||
self.network.stop()
|
||||
network_ready.clear()
|
||||
self.network = None
|
||||
|
||||
def _process_event(self, event: _ZWEvent):
|
||||
from platypush.plugins.zwave import ZwavePlugin
|
||||
|
||||
network = (
|
||||
event.network
|
||||
if hasattr(event, 'network') and event.network
|
||||
else self.network
|
||||
)
|
||||
|
||||
if (
|
||||
event.signal == network.SIGNAL_NETWORK_STOPPED
|
||||
or event.signal == network.SIGNAL_DRIVER_REMOVED
|
||||
):
|
||||
event = ZwaveNetworkStoppedEvent(device=self.device)
|
||||
elif (
|
||||
event.signal == network.SIGNAL_ALL_NODES_QUERIED
|
||||
or event.signal == network.SIGNAL_ALL_NODES_QUERIED_SOME_DEAD
|
||||
):
|
||||
event = ZwaveNodeQueryCompletedEvent(device=self.device)
|
||||
elif event.signal == network.SIGNAL_NETWORK_FAILED:
|
||||
event = ZwaveNetworkErrorEvent(device=self.device)
|
||||
self.logger.warning('Z-Wave network error')
|
||||
elif (
|
||||
event.signal == network.SIGNAL_NETWORK_RESETTED
|
||||
or event.signal == network.SIGNAL_DRIVER_RESET
|
||||
):
|
||||
event = ZwaveNetworkResetEvent(device=self.device)
|
||||
elif event.signal == network.SIGNAL_BUTTON_ON:
|
||||
event = ZwaveButtonOnEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_BUTTON_OFF:
|
||||
event = ZwaveButtonOffEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_CONTROLLER_COMMAND:
|
||||
event = ZwaveCommandEvent(
|
||||
device=self.device,
|
||||
state=event.args['state'],
|
||||
state_description=event.args['state_full'],
|
||||
error=event.args['error'] if event.args['error_int'] else None,
|
||||
error_description=event.args['error_full']
|
||||
if event.args['error_int']
|
||||
else None,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
if event.args['node']
|
||||
else None,
|
||||
)
|
||||
elif event.signal == network.SIGNAL_CONTROLLER_WAITING:
|
||||
event = ZwaveCommandWaitingEvent(
|
||||
device=self.device,
|
||||
state=event.args['state'],
|
||||
state_description=event.args['state_full'],
|
||||
)
|
||||
elif event.signal == network.SIGNAL_CREATE_BUTTON:
|
||||
event = ZwaveButtonCreatedEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_DELETE_BUTTON:
|
||||
event = ZwaveButtonRemovedEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_GROUP:
|
||||
event = ZwaveNodeGroupEvent(
|
||||
device=self.device,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
group_index=event.args['groupidx'],
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NETWORK_AWAKED:
|
||||
event = ZwaveNetworkReadyEvent(
|
||||
device=self.device,
|
||||
ozw_library_version=self.network.controller.ozw_library_version,
|
||||
python_library_version=self.network.controller.python_library_version,
|
||||
zwave_library=self.network.controller.library_description,
|
||||
home_id=self.network.controller.home_id,
|
||||
node_id=self.network.controller.node_id,
|
||||
node_version=self.network.controller.node.version,
|
||||
nodes_count=self.network.nodes_count,
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NODE_EVENT:
|
||||
event = ZwaveNodeEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NODE_ADDED:
|
||||
event = ZwaveNodeAddedEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NODE_NAMING:
|
||||
event = ZwaveNodeRenamedEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NODE_READY:
|
||||
event = ZwaveNodeReadyEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_NODE_REMOVED:
|
||||
event = ZwaveNodeRemovedEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_POLLING_DISABLED:
|
||||
event = ZwaveNodePollingEnabledEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_POLLING_ENABLED:
|
||||
event = ZwaveNodePollingDisabledEvent(
|
||||
device=self.device, node=ZwavePlugin.node_to_dict(event.args['node'])
|
||||
)
|
||||
elif event.signal == network.SIGNAL_SCENE_EVENT:
|
||||
event = ZwaveNodeSceneEvent(
|
||||
device=self.device,
|
||||
scene_id=event.args['scene_id'],
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
)
|
||||
elif event.signal == network.SIGNAL_VALUE_ADDED:
|
||||
event = ZwaveValueAddedEvent(
|
||||
device=self.device,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
value=ZwavePlugin.value_to_dict(event.args['value']),
|
||||
)
|
||||
elif event.signal == network.SIGNAL_VALUE_CHANGED:
|
||||
event = ZwaveValueChangedEvent(
|
||||
device=self.device,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
value=ZwavePlugin.value_to_dict(event.args['value']),
|
||||
)
|
||||
elif event.signal == network.SIGNAL_VALUE_REFRESHED:
|
||||
event = ZwaveValueRefreshedEvent(
|
||||
device=self.device,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
value=ZwavePlugin.value_to_dict(event.args['value']),
|
||||
)
|
||||
elif event.signal == network.SIGNAL_VALUE_REMOVED:
|
||||
event = ZwaveValueRemovedEvent(
|
||||
device=self.device,
|
||||
node=ZwavePlugin.node_to_dict(event.args['node']),
|
||||
value=ZwavePlugin.value_to_dict(event.args['value']),
|
||||
)
|
||||
else:
|
||||
self.logger.info('Received unhandled ZWave event: {}'.format(event))
|
||||
|
||||
if isinstance(event, ZwaveEvent):
|
||||
self.bus.post(event)
|
||||
|
||||
def __enter__(self):
|
||||
self.start_network()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.stop_network()
|
||||
|
||||
def loop(self):
|
||||
try:
|
||||
event = event_queue.get(block=True, timeout=1.0)
|
||||
self._process_event(event)
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -1,48 +0,0 @@
|
|||
manifest:
|
||||
events:
|
||||
platypush.message.event.zwave.ZwaveButtonCreatedEvent: when a button is added
|
||||
to the network.
|
||||
platypush.message.event.zwave.ZwaveButtonOffEvent: when a button is released.
|
||||
platypush.message.event.zwave.ZwaveButtonOnEvent: when a button is pressed.
|
||||
platypush.message.event.zwave.ZwaveButtonRemovedEvent: when a button is removed
|
||||
from the network.
|
||||
platypush.message.event.zwave.ZwaveCommandEvent: when a command is received on
|
||||
the network.
|
||||
platypush.message.event.zwave.ZwaveCommandWaitingEvent: when a command is waiting
|
||||
for a messageto complete.
|
||||
platypush.message.event.zwave.ZwaveNetworkErrorEvent: when an error occurs on
|
||||
the network.
|
||||
platypush.message.event.zwave.ZwaveNetworkReadyEvent: when the network is up and
|
||||
running.
|
||||
platypush.message.event.zwave.ZwaveNetworkResetEvent: when the network is reset.
|
||||
platypush.message.event.zwave.ZwaveNetworkStoppedEvent: when the network goes
|
||||
down.
|
||||
platypush.message.event.zwave.ZwaveNodeAddedEvent: when a node is added to the
|
||||
network.
|
||||
platypush.message.event.zwave.ZwaveNodeEvent: when a node attribute changes.
|
||||
platypush.message.event.zwave.ZwaveNodeGroupEvent: when a node is associated/de-associated
|
||||
to agroup.
|
||||
platypush.message.event.zwave.ZwaveNodePollingDisabledEvent: when the polling
|
||||
is successfully turnedoff a node.
|
||||
platypush.message.event.zwave.ZwaveNodePollingEnabledEvent: when the polling is
|
||||
successfully turnedon a node.
|
||||
platypush.message.event.zwave.ZwaveNodeQueryCompletedEvent: when all the nodes
|
||||
on the networkhave been queried.
|
||||
platypush.message.event.zwave.ZwaveNodeReadyEvent: when a node is ready.
|
||||
platypush.message.event.zwave.ZwaveNodeRemovedEvent: when a node is removed from
|
||||
the network.
|
||||
platypush.message.event.zwave.ZwaveNodeRenamedEvent: when a node is renamed.
|
||||
platypush.message.event.zwave.ZwaveNodeSceneEvent: when a scene is set on a node.
|
||||
platypush.message.event.zwave.ZwaveValueAddedEvent: when a value is added to a
|
||||
node on the network.
|
||||
platypush.message.event.zwave.ZwaveValueChangedEvent: when the value of a node
|
||||
on the networkchanges.
|
||||
platypush.message.event.zwave.ZwaveValueRefreshedEvent: when the value of a node
|
||||
on the networkis refreshed.
|
||||
platypush.message.event.zwave.ZwaveValueRemovedEvent: when the value of a node
|
||||
on the networkis removed.
|
||||
install:
|
||||
pip:
|
||||
- python-openzwave
|
||||
package: platypush.backend.zwave
|
||||
type: backend
|
|
@ -1,15 +1,34 @@
|
|||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
from queue import Queue, Empty
|
||||
from typing import Callable, Type
|
||||
from typing import Callable, Dict, Iterable, Type
|
||||
|
||||
from platypush.message import Message
|
||||
from platypush.message.event import Event
|
||||
|
||||
logger = logging.getLogger('platypush:bus')
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageHandler:
|
||||
"""
|
||||
Wrapper for a message callback handler.
|
||||
"""
|
||||
|
||||
msg_type: Type[Message]
|
||||
callback: Callable[[Message], None]
|
||||
kwargs: dict = field(default_factory=dict)
|
||||
|
||||
def match(self, msg: Message) -> bool:
|
||||
return isinstance(msg, self.msg_type) and all(
|
||||
getattr(msg, k, None) == v for k, v in self.kwargs.items()
|
||||
)
|
||||
|
||||
|
||||
class Bus:
|
||||
"""
|
||||
Main local bus where the daemon will listen for new messages.
|
||||
|
@ -21,7 +40,10 @@ class Bus:
|
|||
self.bus = Queue()
|
||||
self.on_message = on_message
|
||||
self.thread_id = threading.get_ident()
|
||||
self.event_handlers = {}
|
||||
self.handlers: Dict[
|
||||
Type[Message], Dict[Callable[[Message], None], MessageHandler]
|
||||
] = defaultdict(dict)
|
||||
|
||||
self._should_stop = threading.Event()
|
||||
|
||||
def post(self, msg):
|
||||
|
@ -38,26 +60,24 @@ class Bus:
|
|||
def stop(self):
|
||||
self._should_stop.set()
|
||||
|
||||
def _get_matching_handlers(
|
||||
self, msg: Message
|
||||
) -> Iterable[Callable[[Message], None]]:
|
||||
return [
|
||||
hndl.callback
|
||||
for cls in type(msg).__mro__
|
||||
for hndl in self.handlers.get(cls, [])
|
||||
if hndl.match(msg)
|
||||
]
|
||||
|
||||
def _msg_executor(self, msg):
|
||||
def event_handler(event: Event, handler: Callable[[Event], None]):
|
||||
logger.info('Triggering event handler %s', handler.__name__)
|
||||
handler(event)
|
||||
|
||||
def executor():
|
||||
if isinstance(msg, Event):
|
||||
handlers = self.event_handlers.get(
|
||||
type(msg),
|
||||
{
|
||||
*[
|
||||
hndl
|
||||
for event_type, hndl in self.event_handlers.items()
|
||||
if isinstance(msg, event_type)
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
for hndl in handlers:
|
||||
threading.Thread(target=event_handler, args=(msg, hndl))
|
||||
for hndl in self._get_matching_handlers(msg):
|
||||
threading.Thread(target=event_handler, args=(msg, hndl)).start()
|
||||
|
||||
try:
|
||||
if self.on_message:
|
||||
|
@ -100,27 +120,25 @@ class Bus:
|
|||
logger.info('Bus service stopped')
|
||||
|
||||
def register_handler(
|
||||
self, event_type: Type[Event], handler: Callable[[Event], None]
|
||||
self, type: Type[Message], handler: Callable[[Message], None], **kwargs
|
||||
) -> Callable[[], None]:
|
||||
"""
|
||||
Register an event handler to the bus.
|
||||
Register a generic handler to the bus.
|
||||
|
||||
:param event_type: Event type to subscribe (event inheritance also works).
|
||||
:param handler: Event handler - a function that takes an Event object as parameter.
|
||||
:param type: Type of the message to subscribe to (event inheritance also works).
|
||||
:param handler: Event handler - a function that takes a Message object as parameter.
|
||||
:param kwargs: Extra filter on the message values.
|
||||
:return: A function that can be called to remove the handler (no parameters required).
|
||||
"""
|
||||
if event_type not in self.event_handlers:
|
||||
self.event_handlers[event_type] = set()
|
||||
|
||||
self.event_handlers[event_type].add(handler)
|
||||
self.handlers[type][handler] = MessageHandler(type, handler, kwargs)
|
||||
|
||||
def unregister():
|
||||
self.unregister_handler(event_type, handler)
|
||||
self.unregister_handler(type, handler)
|
||||
|
||||
return unregister
|
||||
|
||||
def unregister_handler(
|
||||
self, event_type: Type[Event], handler: Callable[[Event], None]
|
||||
self, type: Type[Message], handler: Callable[[Message], None]
|
||||
) -> None:
|
||||
"""
|
||||
Remove an event handler.
|
||||
|
@ -128,14 +146,12 @@ class Bus:
|
|||
:param event_type: Event type.
|
||||
:param handler: Existing event handler.
|
||||
"""
|
||||
if event_type not in self.event_handlers:
|
||||
if type not in self.handlers:
|
||||
return
|
||||
|
||||
if handler in self.event_handlers[event_type]:
|
||||
self.event_handlers[event_type].remove(handler)
|
||||
|
||||
if len(self.event_handlers[event_type]) == 0:
|
||||
del self.event_handlers[event_type]
|
||||
self.handlers[type].pop(handler, None)
|
||||
if len(self.handlers[type]) == 0:
|
||||
del self.handlers[type]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -106,10 +106,11 @@ class Config:
|
|||
if cfgfile is None:
|
||||
cfgfile = self._get_default_cfgfile()
|
||||
|
||||
cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
|
||||
if cfgfile is None or not os.path.exists(cfgfile):
|
||||
cfgfile = self._create_default_config()
|
||||
cfgfile = self._create_default_config(cfgfile)
|
||||
|
||||
self.config_file = os.path.abspath(os.path.expanduser(cfgfile))
|
||||
self.config_file = cfgfile
|
||||
|
||||
def _init_logging(self):
|
||||
logging_config = {
|
||||
|
@ -211,21 +212,24 @@ class Config:
|
|||
'variable': {},
|
||||
}
|
||||
|
||||
def _create_default_config(self):
|
||||
@staticmethod
|
||||
def _create_default_config(cfgfile: Optional[str] = None):
|
||||
cfg_mod_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Use /etc/platypush/config.yaml if the user is running as root,
|
||||
# otherwise ~/.config/platypush/config.yaml
|
||||
cfgfile = (
|
||||
(
|
||||
os.path.join(os.environ['XDG_CONFIG_HOME'], 'config.yaml')
|
||||
if os.environ.get('XDG_CONFIG_HOME')
|
||||
else os.path.join(
|
||||
os.path.expanduser('~'), '.config', 'platypush', 'config.yaml'
|
||||
|
||||
if not cfgfile:
|
||||
# Use /etc/platypush/config.yaml if the user is running as root,
|
||||
# otherwise ~/.config/platypush/config.yaml
|
||||
cfgfile = (
|
||||
(
|
||||
os.path.join(os.environ['XDG_CONFIG_HOME'], 'config.yaml')
|
||||
if os.environ.get('XDG_CONFIG_HOME')
|
||||
else os.path.join(
|
||||
os.path.expanduser('~'), '.config', 'platypush', 'config.yaml'
|
||||
)
|
||||
)
|
||||
if not is_root()
|
||||
else os.path.join(os.sep, 'etc', 'platypush', 'config.yaml')
|
||||
)
|
||||
if not is_root()
|
||||
else os.path.join(os.sep, 'etc', 'platypush', 'config.yaml')
|
||||
)
|
||||
|
||||
cfgdir = pathlib.Path(cfgfile).parent
|
||||
cfgdir.mkdir(parents=True, exist_ok=True)
|
||||
|
@ -526,6 +530,7 @@ class Config:
|
|||
Get a config value or the whole configuration object.
|
||||
|
||||
:param key: Configuration entry to get (default: all entries).
|
||||
:param default: Default value to return if the key is missing.
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
config = cls._get_instance()._config.copy()
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
### Include directives
|
||||
### ------------------
|
||||
|
||||
###
|
||||
# # You can split your configuration over multiple files and use the include
|
||||
# # directive to import other files into your configuration.
|
||||
#
|
||||
|
@ -37,11 +38,13 @@
|
|||
# - logging.yaml
|
||||
# - media.yaml
|
||||
# - sensors.yaml
|
||||
###
|
||||
|
||||
### -----------------
|
||||
### Working directory
|
||||
### -----------------
|
||||
|
||||
###
|
||||
# # Working directory of the application. This is where the main database will be
|
||||
# # stored by default (if the default SQLite configuration is used), and it's
|
||||
# # where the integrations will store their state.
|
||||
|
@ -55,11 +58,13 @@
|
|||
# # - $HOME/.local/share/platypush otherwise.
|
||||
#
|
||||
# workdir: ~/.local/share/platypush
|
||||
###
|
||||
|
||||
### ----------------------
|
||||
### Database configuration
|
||||
### ----------------------
|
||||
|
||||
###
|
||||
# # By default Platypush will use a SQLite database named `main.db` under the
|
||||
# # `workdir`. You can specify any other engine string here - the application has
|
||||
# # been tested against SQLite, Postgres and MariaDB/MySQL >= 8.
|
||||
|
@ -73,11 +78,13 @@
|
|||
# engine: sqlite:///home/user/.local/share/platypush/main.db
|
||||
# # OR, if you want to use e.g. Postgres with the pg8000 driver:
|
||||
# engine: postgresql+pg8000://dbuser:dbpass@dbhost/dbname
|
||||
###
|
||||
|
||||
### ---------------------
|
||||
### Logging configuration
|
||||
### ---------------------
|
||||
|
||||
###
|
||||
# # Platypush logs on stdout by default. You can use the logging section to
|
||||
# # specify an alternative file or change the logging level.
|
||||
#
|
||||
|
@ -87,11 +94,13 @@
|
|||
# logging:
|
||||
# filename: ~/.local/log/platypush/platypush.log
|
||||
# level: INFO
|
||||
###
|
||||
|
||||
### -----------------------
|
||||
### device_id configuration
|
||||
### -----------------------
|
||||
|
||||
###
|
||||
# # The device_id is used by many components of Platypush and it should uniquely
|
||||
# # identify a device in your network. If nothing is specified then the hostname
|
||||
# # will be used.
|
||||
|
@ -100,11 +109,13 @@
|
|||
# # -d/--device-id option.
|
||||
#
|
||||
# device_id: my_device
|
||||
###
|
||||
|
||||
### -------------------
|
||||
### Redis configuration
|
||||
### -------------------
|
||||
|
||||
###
|
||||
# # Platypush needs a Redis instance for inter-process communication.
|
||||
# #
|
||||
# # By default, the application will try and connect to a Redis server listening
|
||||
|
@ -123,11 +134,13 @@
|
|||
# port: 6379
|
||||
# username: user
|
||||
# password: secret
|
||||
###
|
||||
|
||||
### ------------------------
|
||||
### Web server configuration
|
||||
### ------------------------
|
||||
|
||||
###
|
||||
# Platypush comes with a versatile Web server that is used to:
|
||||
#
|
||||
# - Serve the main UI and the UIs for the plugins that provide one.
|
||||
|
@ -225,6 +238,30 @@ backend.http:
|
|||
# poll_interval: 20
|
||||
###
|
||||
|
||||
###
|
||||
# # Example configuration of the MQTT plugin.
|
||||
# # This plugin allows you to subscribe to MQTT topics and trigger `platypush.message.event.mqtt.MQTTMessageEvent`
|
||||
# # events that you can hook on when new messages are received.
|
||||
# # You can also publish messages to MQTT topics through the `mqtt.publish` action.
|
||||
#
|
||||
# mqtt:
|
||||
# # Host and port of the MQTT broker
|
||||
# host: my-mqtt-broker
|
||||
# port: 1883
|
||||
# # Topic to subscribe to. Messages received on these topics will trigger `MQTTMessageEvent` events.
|
||||
# topics:
|
||||
# - platypush/sensors
|
||||
#
|
||||
# # Extra listeners. You can use them to subscribe to multiple brokers at the same time.
|
||||
# listeners:
|
||||
# - host: another-mqtt-broker
|
||||
# port: 1883
|
||||
# username: user
|
||||
# password: secret
|
||||
# topics:
|
||||
# - platypush/tests
|
||||
###
|
||||
|
||||
###
|
||||
# # Example configuration of music.mpd plugin, a plugin to interact with MPD and
|
||||
# # Mopidy music server instances. See
|
||||
|
@ -244,17 +281,6 @@ backend.http:
|
|||
# clipboard:
|
||||
###
|
||||
|
||||
###
|
||||
# # Example configuration of the MQTT plugin. This specifies a server that the
|
||||
# # application will use by default (if not specified on the request body).
|
||||
#
|
||||
# mqtt:
|
||||
# host: 192.168.1.2
|
||||
# port: 1883
|
||||
# username: user
|
||||
# password: secret
|
||||
###
|
||||
|
||||
###
|
||||
# # Enable the system plugin if you want your device to periodically report
|
||||
# # system statistics (CPU load, disk usage, memory usage etc.)
|
||||
|
@ -262,7 +288,7 @@ backend.http:
|
|||
# # When new data is gathered, an `EntityUpdateEvent` with `plugin='system'` will
|
||||
# # be triggered with the new data, and you can subscribe a hook to these events
|
||||
# # to run your custom logic.
|
||||
#
|
||||
#
|
||||
# system:
|
||||
# # How often we should poll for new data
|
||||
# poll_interval: 60
|
||||
|
@ -311,7 +337,7 @@ backend.http:
|
|||
|
||||
###
|
||||
# # Example configuration of a weather plugin
|
||||
#
|
||||
#
|
||||
# weather.openweathermap:
|
||||
# token: secret
|
||||
# lat: lat
|
||||
|
@ -328,7 +354,7 @@ backend.http:
|
|||
# # using Web hooks (i.e. event hooks that subscribe to
|
||||
# # `platypush.message.event.http.hook.WebhookEvent` events), provided that the
|
||||
# # Web server is listening on a publicly accessible address.
|
||||
#
|
||||
#
|
||||
# ifttt:
|
||||
# ifttt_key: SECRET
|
||||
###
|
||||
|
@ -348,14 +374,14 @@ backend.http:
|
|||
# # build automation routines on. You can also use Platypush to control your
|
||||
# # Zigbee devices, either through the Web interface or programmatically through
|
||||
# # the available plugin actions.
|
||||
#
|
||||
#
|
||||
# zigbee.mqtt:
|
||||
# # Host of the MQTT broker
|
||||
# host: riemann
|
||||
# host: my-mqtt-broker
|
||||
# # Listen port of the MQTT broker
|
||||
# port: 1883
|
||||
# # Base topic, as specified in `<zigbee2mqtt_dir>/data/configuration.yaml`
|
||||
# base_topic: zigbee2mqtt
|
||||
# topic_prefix: zigbee2mqtt
|
||||
###
|
||||
|
||||
###
|
||||
|
@ -366,10 +392,10 @@ backend.http:
|
|||
# # automation routines on.
|
||||
# # You can also use Platypush to control your Z-Wave devices, either through the
|
||||
# # Web interface or programmatically through the available plugin actions.
|
||||
#
|
||||
#
|
||||
# zwave.mqtt:
|
||||
# # Host of the MQTT broker
|
||||
# host: riemann
|
||||
# host: my-mqtt-broker
|
||||
# # Listen port of the MQTT broker
|
||||
# port: 1883
|
||||
# # Gateway name, usually configured in the ZWaveJS-UI through `Settings ->
|
||||
|
@ -402,7 +428,7 @@ backend.http:
|
|||
#
|
||||
# # You can also capture images by connecting to the
|
||||
# # `/camera/<plugin>/photo[.extension]`, for example `/camera/ffmpeg/photo.jpg`.
|
||||
#
|
||||
#
|
||||
# camera.ffmpeg:
|
||||
# # Default video device to use
|
||||
# device: /dev/video0
|
||||
|
@ -543,7 +569,7 @@ backend.http:
|
|||
# # `platypush.message.event.sensor.SensorDataChangeEvent` events will be
|
||||
# # triggered when the data changes - you can subscribe to them in your custom
|
||||
# # hooks.
|
||||
#
|
||||
#
|
||||
# serial:
|
||||
# # The path to the USB interface with e.g. an Arduino or ESP microcontroller
|
||||
# # connected.
|
||||
|
@ -579,7 +605,7 @@ backend.http:
|
|||
# temperature: 0.5
|
||||
# humidity: 0.75
|
||||
# luminosity: 5
|
||||
#
|
||||
#
|
||||
# # If a threshold is defined for a sensor, and the value of that sensor goes
|
||||
# # below/above that temperature between two reads, then a
|
||||
# # `SensorDataBelowThresholdEvent` or a `SensorDataAboveThresholdEvent` will
|
||||
|
@ -599,7 +625,7 @@ backend.http:
|
|||
# # Note that the interface of this plugin is basically the same as the serial
|
||||
# # plugin, and any other plugin that extends `SensorPlugin` in general.
|
||||
# # Therefore, poll_interval, tolerance and thresholds are supported here too.
|
||||
#
|
||||
#
|
||||
# arduino:
|
||||
# board: /dev/ttyUSB0
|
||||
# # name -> PIN number mapping (similar for digital_pins).
|
||||
|
@ -607,10 +633,10 @@ backend.http:
|
|||
# # the forwarded events.
|
||||
# analog_pins:
|
||||
# temperature: 7
|
||||
#
|
||||
#
|
||||
# tolerance:
|
||||
# temperature: 0.5
|
||||
#
|
||||
#
|
||||
# thresholds:
|
||||
# temperature: 25.0
|
||||
###
|
||||
|
@ -619,13 +645,13 @@ backend.http:
|
|||
# # Another example: the LTR559 is a common sensor for proximity and luminosity
|
||||
# # that can be wired to a Raspberry Pi or similar devices over SPI or I2C
|
||||
# # interface. It exposes the same base interface as all other sensor plugins.
|
||||
#
|
||||
#
|
||||
# sensor.ltr559:
|
||||
# poll_interval: 1.0
|
||||
# tolerance:
|
||||
# light: 7.0
|
||||
# proximity: 5.0
|
||||
#
|
||||
#
|
||||
# thresholds:
|
||||
# proximity: 10.0
|
||||
###
|
||||
|
@ -637,7 +663,7 @@ backend.http:
|
|||
###
|
||||
# # `tts` is the simplest TTS integration. It leverages the Google Translate open
|
||||
# # "say" endpoint to render text as audio speech.
|
||||
#
|
||||
#
|
||||
# tts:
|
||||
# # The media plugin that should be used to play the audio response
|
||||
# media_plugin: media.vlc
|
||||
|
@ -655,7 +681,7 @@ backend.http:
|
|||
# # Google developers console, create an API key, and follow the instruction
|
||||
# # logged on the next restart to give your app the required permissions to your
|
||||
# # account.
|
||||
#
|
||||
#
|
||||
# tts.google:
|
||||
# # The media plugin that should be used to play the audio response
|
||||
# media_plugin: media.vlc
|
||||
|
@ -674,7 +700,7 @@ backend.http:
|
|||
# # Follow the instructions at
|
||||
# # https://docs.platypush.tech/platypush/plugins/tts.mimic3.html to quickly
|
||||
# # bootstrap a mimic3 server.
|
||||
#
|
||||
#
|
||||
# tts.mimic3:
|
||||
# # The base URL of the mimic3 server
|
||||
# server_url: http://riemann:59125
|
||||
|
@ -731,7 +757,7 @@ backend.http:
|
|||
# # A use-case can be the one where you have a Tasker automation running on your
|
||||
# # Android device that detects when your phone enters or exits a certain area,
|
||||
# # and sends the appropriate request to your Platypush server.
|
||||
#
|
||||
#
|
||||
# procedure.at_home:
|
||||
# # Set the db variable AT_HOME to 1.
|
||||
# # Variables are flexible entities with a name and a value that will be
|
||||
|
@ -741,11 +767,11 @@ backend.http:
|
|||
# - action: variable.set
|
||||
# args:
|
||||
# AT_HOME: 1
|
||||
#
|
||||
#
|
||||
# # Check the luminosity level from e.g. a connected LTR559 sensor.
|
||||
# # It could also be a Bluetooth, Zigbee, Z-Wave, serial etc. sensor.
|
||||
# - action: sensor.ltr559.get_measurement
|
||||
#
|
||||
#
|
||||
# # If it's below a certain threshold, turn on the lights.
|
||||
# # In this case, `light` is a parameter returned by the previous response,
|
||||
# # so we can directly access it here through the `${}` context operator.
|
||||
|
@ -753,12 +779,12 @@ backend.http:
|
|||
# # ${output["light"]}.
|
||||
# - if ${int(light or 0) < 110}:
|
||||
# - action: light.hue.on
|
||||
#
|
||||
#
|
||||
# # Say a welcome home message
|
||||
# - action: tts.mimic3.say
|
||||
# args:
|
||||
# text: Welcome home
|
||||
#
|
||||
#
|
||||
# # Start the music
|
||||
# - action: music.mpd.play
|
||||
###
|
||||
|
@ -771,10 +797,10 @@ backend.http:
|
|||
# - action: variable.unset
|
||||
# args:
|
||||
# name: AT_HOME
|
||||
#
|
||||
#
|
||||
# # Stop the music
|
||||
# - action: music.mpd.stop
|
||||
#
|
||||
#
|
||||
# # Turn off the lights
|
||||
# - action: light.hue.off
|
||||
###
|
||||
|
@ -789,12 +815,12 @@ backend.http:
|
|||
# #
|
||||
# # See the event hook section below for a sample hook that listens for messages
|
||||
# # sent by other clients using this procedure.
|
||||
#
|
||||
#
|
||||
# procedure.send_sensor_data(name, value):
|
||||
# - action: mqtt.send_message
|
||||
# args:
|
||||
# topic: platypush/sensors
|
||||
# host: mqtt-server
|
||||
# host: my-mqtt-broker
|
||||
# port: 1883
|
||||
# msg:
|
||||
# name: ${name}
|
||||
|
@ -807,7 +833,7 @@ backend.http:
|
|||
## -------------------
|
||||
|
||||
# Event hooks are procedures that are run when a certain condition is met.
|
||||
#
|
||||
#
|
||||
# Check the documentation of your configured backends and plugins to see which
|
||||
# events they can trigger, and check https://docs.platypush.tech/events.html
|
||||
# for the full list of available events with their schemas.
|
||||
|
@ -830,7 +856,7 @@ backend.http:
|
|||
# # Note that, for this event to be triggered, the application must first
|
||||
# # subscribe to the `platypush/sensor` topic - e.g. by adding `platypush/sensor`
|
||||
# # to the active subscriptions in the `mqtt` configurations.
|
||||
#
|
||||
#
|
||||
# event.hook.OnSensorDataReceived:
|
||||
# if:
|
||||
# type: platypush.message.event.mqtt.MQTTMessageEvent
|
||||
|
@ -849,7 +875,7 @@ backend.http:
|
|||
###
|
||||
# # The example below plays the music on mpd/mopidy when your voice assistant
|
||||
# # triggers a speech recognized event with "play the music" content.
|
||||
#
|
||||
#
|
||||
# event.hook.PlayMusicAssistantCommand:
|
||||
# if:
|
||||
# type: platypush.message.event.assistant.SpeechRecognizedEvent
|
||||
|
@ -863,7 +889,7 @@ backend.http:
|
|||
|
||||
###
|
||||
# # This will turn on the lights when you say "turn on the lights"
|
||||
#
|
||||
#
|
||||
# event.hook.TurnOnLightsCommand:
|
||||
# if:
|
||||
# type: platypush.message.event.assistant.SpeechRecognizedEvent
|
||||
|
@ -887,7 +913,7 @@ backend.http:
|
|||
# # By default they don't have an authentication layer at all. You are however
|
||||
# # advised to create your custom passphrase and checks the request's headers or
|
||||
# # query string for it - preferably one passphrase per endpoint.
|
||||
#
|
||||
#
|
||||
# event.hook.WebhookExample:
|
||||
# if:
|
||||
# type: platypush.message.event.http.hook.WebhookEvent
|
||||
|
@ -910,7 +936,7 @@ backend.http:
|
|||
# # Standard UNIX cron syntax is supported, plus an optional 6th indicator
|
||||
# # at the end of the expression to run jobs with second granularity.
|
||||
# # The example below executes a script at intervals of 1 minute.
|
||||
#
|
||||
#
|
||||
# cron.TestCron:
|
||||
# cron_expression: '* * * * *'
|
||||
# actions:
|
||||
|
|
|
@ -25,8 +25,8 @@ class EntitiesEngine(Thread):
|
|||
together (preventing excessive writes and throttling events), and
|
||||
prevents race conditions when SQLite is used.
|
||||
2. Merge any existing entities with their newer representations.
|
||||
3. Update the entities taxonomy.
|
||||
4. Persist the new state to the entities database.
|
||||
3. Update the entities' taxonomy.
|
||||
4. Persist the new state to the entities' database.
|
||||
5. Trigger events for the updated entities.
|
||||
|
||||
"""
|
||||
|
|
|
@ -32,7 +32,10 @@ def action(f: Callable[..., Any]) -> Callable[..., Response]:
|
|||
response = Response()
|
||||
try:
|
||||
result = f(*args, **kwargs)
|
||||
except TypeError as e:
|
||||
except Exception as e:
|
||||
if isinstance(e, KeyboardInterrupt):
|
||||
return response
|
||||
|
||||
_logger.exception(e)
|
||||
result = Response(errors=[str(e)])
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ from typing import Callable, Dict, Generator, Optional, Type, Union
|
|||
from platypush.backend import Backend
|
||||
from platypush.config import Config
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.message import Message
|
||||
from platypush.message.event import Event
|
||||
from platypush.message.response import Response
|
||||
from platypush.utils import (
|
||||
|
@ -314,7 +315,8 @@ class InspectPlugin(Plugin):
|
|||
{
|
||||
get_plugin_name_by_class(cls): dict(plugin)
|
||||
for cls, plugin in self._components_cache.get(Plugin, {}).items()
|
||||
}
|
||||
},
|
||||
cls=Message.Encoder,
|
||||
)
|
||||
|
||||
@action
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
from collections import defaultdict
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from typing import Any, Dict, Iterable, Optional, IO
|
||||
from typing_extensions import override
|
||||
|
||||
from typing import Any, Optional, IO
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from platypush.config import Config
|
||||
from platypush.context import get_bus
|
||||
from platypush.message import Message
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.message.event.mqtt import MQTTMessageEvent
|
||||
from platypush.message.request import Request
|
||||
from platypush.plugins import RunnablePlugin, action
|
||||
from platypush.utils import get_message_response
|
||||
|
||||
from ._client import DEFAULT_TIMEOUT, MqttCallback, MqttClient
|
||||
|
||||
|
||||
class MqttPlugin(Plugin):
|
||||
class MqttPlugin(RunnablePlugin):
|
||||
"""
|
||||
This plugin allows you to send custom message to a message queue compatible
|
||||
with the MQTT protocol, see https://mqtt.org/
|
||||
|
@ -19,253 +28,131 @@ class MqttPlugin(Plugin):
|
|||
|
||||
* **paho-mqtt** (``pip install paho-mqtt``)
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.mqtt.MQTTMessageEvent` when a new
|
||||
message is received on a subscribed topic.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host=None,
|
||||
port=1883,
|
||||
tls_cafile=None,
|
||||
tls_certfile=None,
|
||||
tls_keyfile=None,
|
||||
tls_version=None,
|
||||
tls_ciphers=None,
|
||||
tls_insecure=False,
|
||||
username=None,
|
||||
password=None,
|
||||
client_id=None,
|
||||
timeout=None,
|
||||
host: Optional[str] = None,
|
||||
port: int = 1883,
|
||||
topics: Optional[Iterable[str]] = None,
|
||||
tls_cafile: Optional[str] = None,
|
||||
tls_certfile: Optional[str] = None,
|
||||
tls_keyfile: Optional[str] = None,
|
||||
tls_version: Optional[str] = None,
|
||||
tls_ciphers: Optional[str] = None,
|
||||
tls_insecure: bool = False,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
client_id: Optional[str] = None,
|
||||
timeout: Optional[int] = DEFAULT_TIMEOUT,
|
||||
run_topic_prefix: Optional[str] = None,
|
||||
listeners: Optional[Iterable[dict]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:param host: If set, MQTT messages will by default routed to this host
|
||||
unless overridden in `send_message` (default: None)
|
||||
:type host: str
|
||||
|
||||
:param port: If a default host is set, specify the listen port
|
||||
(default: 1883)
|
||||
:type port: int
|
||||
|
||||
:param topics: If a default ``host`` is specified, then this list will
|
||||
include a default list of topics that should be subscribed on that
|
||||
broker at startup.
|
||||
:param tls_cafile: If a default host is set and requires TLS/SSL,
|
||||
specify the certificate authority file (default: None)
|
||||
:type tls_cafile: str
|
||||
|
||||
:param tls_certfile: If a default host is set and requires TLS/SSL,
|
||||
specify the certificate file (default: None)
|
||||
:type tls_certfile: str
|
||||
|
||||
:param tls_keyfile: If a default host is set and requires TLS/SSL,
|
||||
specify the key file (default: None)
|
||||
:type tls_keyfile: str
|
||||
|
||||
:param tls_version: If TLS/SSL is enabled on the MQTT server and it
|
||||
requires a certain TLS version, specify it here (default: None).
|
||||
Supported versions: ``tls`` (automatic), ``tlsv1``, ``tlsv1.1``,
|
||||
``tlsv1.2``.
|
||||
:type tls_version: str
|
||||
|
||||
:param tls_ciphers: If a default host is set and requires TLS/SSL,
|
||||
specify the supported ciphers (default: None)
|
||||
:type tls_ciphers: str
|
||||
|
||||
:param tls_insecure: Set to True to ignore TLS insecure warnings
|
||||
(default: False).
|
||||
:type tls_insecure: bool
|
||||
|
||||
:param username: If a default host is set and requires user
|
||||
authentication, specify the username ciphers (default: None)
|
||||
:type username: str
|
||||
|
||||
:param password: If a default host is set and requires user
|
||||
authentication, specify the password ciphers (default: None)
|
||||
:type password: str
|
||||
|
||||
:param client_id: ID used to identify the client on the MQTT server
|
||||
(default: None). If None is specified then
|
||||
``Config.get('device_id')`` will be used.
|
||||
:type client_id: str
|
||||
:param timeout: Client timeout in seconds (default: 30 seconds).
|
||||
:param run_topic_prefix: If specified, the MQTT plugin will listen for
|
||||
messages on a topic in the format `{run_topic_prefix}/{device_id}.
|
||||
When a message is received, it will interpret it as a JSON request
|
||||
to execute, in the format
|
||||
``{"type": "request", "action": "plugin.action", "args": {...}}``.
|
||||
|
||||
.. warning:: This parameter is mostly kept for backwards
|
||||
compatibility, but you should avoid it - unless the MQTT broker
|
||||
is on a personal safe network that you own, or it requires
|
||||
user authentication and it uses SSL. The reason is that the
|
||||
messages received on this topic won't be subject to token
|
||||
verification, allowing unauthenticated arbitrary command
|
||||
execution on the target host. If you still want the ability of
|
||||
running commands remotely over an MQTT broker, then you may
|
||||
consider creating a dedicated topic listener with an attached
|
||||
event hook on
|
||||
:class:`platypush.message.event.mqtt.MQTTMessageEvent`. The
|
||||
hook can implement whichever authentication logic you like.
|
||||
|
||||
:param listeners: If specified, the MQTT plugin will listen for
|
||||
messages on these topics. Use this parameter if you also want to
|
||||
listen on other MQTT brokers other than the primary one. This
|
||||
parameter supports a list of maps, where each item supports the
|
||||
same arguments passed to the main configuration (host, port, topic,
|
||||
password etc.). If host/port are omitted, then the host/port value
|
||||
from the plugin configuration will be used. If any of the other
|
||||
fields are omitted, then their default value will be used (usually
|
||||
null). Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
listeners:
|
||||
# This listener use the default configured host/port
|
||||
- topics:
|
||||
- topic1
|
||||
- topic2
|
||||
- topic3
|
||||
|
||||
# This will use a custom MQTT broker host
|
||||
- host: sensors
|
||||
port: 11883
|
||||
username: myuser
|
||||
password: secret
|
||||
topics:
|
||||
- topic4
|
||||
- topic5
|
||||
|
||||
:param timeout: Client timeout in seconds (default: None).
|
||||
:type timeout: int
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.client_id = client_id or Config.get('device_id')
|
||||
self.tls_cafile = self._expandpath(tls_cafile) if tls_cafile else None
|
||||
self.tls_certfile = self._expandpath(tls_certfile) if tls_certfile else None
|
||||
self.tls_keyfile = self._expandpath(tls_keyfile) if tls_keyfile else None
|
||||
self.tls_version = self.get_tls_version(tls_version)
|
||||
self.tls_insecure = tls_insecure
|
||||
self.tls_ciphers = tls_ciphers
|
||||
self.client_id = client_id or str(Config.get('device_id'))
|
||||
self.run_topic = (
|
||||
f'{run_topic_prefix}/{Config.get("device_id")}'
|
||||
if type(self) == MqttPlugin and run_topic_prefix
|
||||
else None
|
||||
)
|
||||
|
||||
self._listeners_lock = defaultdict(threading.RLock)
|
||||
self.listeners: Dict[str, MqttClient] = {} # client_id -> MqttClient map
|
||||
self.timeout = timeout
|
||||
|
||||
@staticmethod
|
||||
def get_tls_version(version: Optional[str] = None):
|
||||
import ssl
|
||||
|
||||
if not version:
|
||||
return None
|
||||
|
||||
if isinstance(version, type(ssl.PROTOCOL_TLS)):
|
||||
return version
|
||||
|
||||
if isinstance(version, str):
|
||||
version = version.lower()
|
||||
|
||||
if version == 'tls':
|
||||
return ssl.PROTOCOL_TLS
|
||||
if version == 'tlsv1':
|
||||
return ssl.PROTOCOL_TLSv1
|
||||
if version == 'tlsv1.1':
|
||||
return ssl.PROTOCOL_TLSv1_1
|
||||
if version == 'tlsv1.2':
|
||||
return ssl.PROTOCOL_TLSv1_2
|
||||
|
||||
assert f'Unrecognized TLS version: {version}'
|
||||
|
||||
def _mqtt_args(self, **kwargs):
|
||||
return {
|
||||
'host': kwargs.get('host', self.host),
|
||||
'port': kwargs.get('port', self.port),
|
||||
'timeout': kwargs.get('timeout', self.timeout),
|
||||
'tls_certfile': kwargs.get('tls_certfile', self.tls_certfile),
|
||||
'tls_keyfile': kwargs.get('tls_keyfile', self.tls_keyfile),
|
||||
'tls_version': kwargs.get('tls_version', self.tls_version),
|
||||
'tls_ciphers': kwargs.get('tls_ciphers', self.tls_ciphers),
|
||||
'username': kwargs.get('username', self.username),
|
||||
'password': kwargs.get('password', self.password),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _expandpath(path: Optional[str] = None) -> Optional[str]:
|
||||
return os.path.abspath(os.path.expanduser(path)) if path else None
|
||||
|
||||
def _get_client(
|
||||
self,
|
||||
tls_cafile: Optional[str] = None,
|
||||
tls_certfile: Optional[str] = None,
|
||||
tls_keyfile: Optional[str] = None,
|
||||
tls_version: Optional[str] = None,
|
||||
tls_ciphers: Optional[str] = None,
|
||||
tls_insecure: Optional[bool] = None,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
):
|
||||
from paho.mqtt.client import Client
|
||||
|
||||
tls_cafile = self._expandpath(tls_cafile or self.tls_cafile)
|
||||
tls_certfile = self._expandpath(tls_certfile or self.tls_certfile)
|
||||
tls_keyfile = self._expandpath(tls_keyfile or self.tls_keyfile)
|
||||
tls_ciphers = tls_ciphers or self.tls_ciphers
|
||||
username = username or self.username
|
||||
password = password or self.password
|
||||
|
||||
tls_version = tls_version or self.tls_version # type: ignore[reportGeneralTypeIssues]
|
||||
if tls_version:
|
||||
tls_version = self.get_tls_version(tls_version) # type: ignore[reportGeneralTypeIssues]
|
||||
if tls_insecure is None:
|
||||
tls_insecure = self.tls_insecure
|
||||
|
||||
client = Client()
|
||||
|
||||
if username and password:
|
||||
client.username_pw_set(username, password)
|
||||
if tls_cafile:
|
||||
client.tls_set(
|
||||
ca_certs=tls_cafile,
|
||||
certfile=tls_certfile,
|
||||
keyfile=tls_keyfile,
|
||||
tls_version=tls_version, # type: ignore[reportGeneralTypeIssues]
|
||||
ciphers=tls_ciphers,
|
||||
)
|
||||
|
||||
client.tls_insecure_set(tls_insecure)
|
||||
|
||||
return client
|
||||
|
||||
@action
|
||||
def publish(
|
||||
self,
|
||||
topic: str,
|
||||
msg: Any,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
reply_topic: Optional[str] = None,
|
||||
timeout: int = 60,
|
||||
tls_cafile: Optional[str] = None,
|
||||
tls_certfile: Optional[str] = None,
|
||||
tls_keyfile: Optional[str] = None,
|
||||
tls_version: Optional[str] = None,
|
||||
tls_ciphers: Optional[str] = None,
|
||||
tls_insecure: Optional[bool] = None,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
qos: int = 0,
|
||||
):
|
||||
"""
|
||||
Sends a message to a topic.
|
||||
|
||||
:param topic: Topic/channel where the message will be delivered
|
||||
:param msg: Message to be sent. It can be a list, a dict, or a Message
|
||||
object.
|
||||
:param host: MQTT broker hostname/IP (default: default host configured
|
||||
on the plugin).
|
||||
:param port: MQTT broker port (default: default port configured on the
|
||||
plugin).
|
||||
:param reply_topic: If a ``reply_topic`` is specified, then the action
|
||||
will wait for a response on this topic.
|
||||
:param timeout: If ``reply_topic`` is set, use this parameter to
|
||||
specify the maximum amount of time to wait for a response (default:
|
||||
60 seconds).
|
||||
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the
|
||||
certificate requires a certificate authority to authenticate it,
|
||||
`ssl_cafile` will point to the provided ca.crt file (default:
|
||||
None).
|
||||
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a
|
||||
client certificate it required, specify it here (default: None).
|
||||
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a
|
||||
client certificate key it required, specify it here (default:
|
||||
None).
|
||||
:param tls_version: If TLS/SSL is enabled on the MQTT server and it
|
||||
requires a certain TLS version, specify it here (default: None).
|
||||
Supported versions: ``tls`` (automatic), ``tlsv1``, ``tlsv1.1``,
|
||||
``tlsv1.2``.
|
||||
:param tls_insecure: Set to True to ignore TLS insecure warnings
|
||||
(default: False).
|
||||
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an
|
||||
explicit list of supported ciphers is required, specify it here
|
||||
(default: None).
|
||||
:param username: Specify it if the MQTT server requires authentication
|
||||
(default: None).
|
||||
:param password: Specify it if the MQTT server requires authentication
|
||||
(default: None).
|
||||
:param qos: Quality of Service (_QoS_) for the message - see `MQTT QoS
|
||||
<https://assetwolf.com/learn/mqtt-qos-understanding-quality-of-service>`_
|
||||
(default: 0).
|
||||
"""
|
||||
response_buffer = io.BytesIO()
|
||||
client = None
|
||||
|
||||
try:
|
||||
# Try to parse it as a platypush message or dump it to JSON from a dict/list
|
||||
if isinstance(msg, (dict, list)):
|
||||
msg = json.dumps(msg)
|
||||
|
||||
try:
|
||||
msg = Message.build(json.loads(msg))
|
||||
except Exception as e:
|
||||
self.logger.debug('Not a valid JSON: %s', e)
|
||||
|
||||
host = host or self.host
|
||||
port = port or self.port or 1883
|
||||
assert host, 'No host specified'
|
||||
|
||||
client = self._get_client(
|
||||
self.default_listener = (
|
||||
self._get_client(
|
||||
host=host,
|
||||
port=port,
|
||||
topics=(
|
||||
(tuple(topics) if topics else ())
|
||||
+ ((self.run_topic,) if self.run_topic else ())
|
||||
),
|
||||
on_message=self.on_mqtt_message(),
|
||||
tls_cafile=tls_cafile,
|
||||
tls_certfile=tls_certfile,
|
||||
tls_keyfile=tls_keyfile,
|
||||
|
@ -274,11 +161,258 @@ class MqttPlugin(Plugin):
|
|||
tls_insecure=tls_insecure,
|
||||
username=username,
|
||||
password=password,
|
||||
client_id=client_id,
|
||||
timeout=timeout,
|
||||
)
|
||||
if host
|
||||
else None
|
||||
)
|
||||
|
||||
for listener in listeners or []:
|
||||
self._get_client(
|
||||
**self._mqtt_args(on_message=self.on_mqtt_message(), **listener)
|
||||
)
|
||||
|
||||
client.connect(host, port, keepalive=timeout)
|
||||
def _get_client_id(
|
||||
self,
|
||||
host: str,
|
||||
port: int,
|
||||
client_id: Optional[str] = None,
|
||||
on_message: Optional[MqttCallback] = None,
|
||||
topics: Iterable[str] = (),
|
||||
**_,
|
||||
) -> str:
|
||||
"""
|
||||
Calculates a unique client ID given an MQTT configuration.
|
||||
"""
|
||||
client_id = client_id or self.client_id
|
||||
client_hash = hashlib.sha1(
|
||||
'|'.join(
|
||||
[
|
||||
self.__class__.__name__,
|
||||
host,
|
||||
str(port),
|
||||
json.dumps(sorted(topics)),
|
||||
str(id(on_message)),
|
||||
]
|
||||
).encode()
|
||||
).hexdigest()
|
||||
|
||||
return f'{client_id}-{client_hash}'
|
||||
|
||||
def _mqtt_args(
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: int = 1883,
|
||||
timeout: Optional[int] = DEFAULT_TIMEOUT,
|
||||
topics: Iterable[str] = (),
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:return: An MQTT configuration mapping that uses either the specified
|
||||
arguments (if host is specified), or falls back to the default
|
||||
configurated arguments.
|
||||
"""
|
||||
default_conf = (
|
||||
self.default_listener.configuration if self.default_listener else {}
|
||||
)
|
||||
|
||||
if not host:
|
||||
assert (
|
||||
self.default_listener
|
||||
), 'No host specified and no configured default host'
|
||||
|
||||
return {
|
||||
**default_conf,
|
||||
'topics': (*self.default_listener.topics, *topics),
|
||||
}
|
||||
|
||||
return {
|
||||
'host': host,
|
||||
'port': port,
|
||||
'timeout': timeout or default_conf.get('timeout'),
|
||||
'topics': topics,
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
def on_mqtt_message(self) -> MqttCallback:
|
||||
"""
|
||||
Default MQTT message handler. It forwards a
|
||||
:class:`platypush.message.event.mqtt.MQTTMessageEvent` event to the
|
||||
bus.
|
||||
"""
|
||||
|
||||
def handler(client: MqttClient, _, msg: mqtt.MQTTMessage):
|
||||
data = msg.payload
|
||||
try:
|
||||
data = data.decode('utf-8')
|
||||
data = json.loads(data)
|
||||
except (TypeError, AttributeError, ValueError):
|
||||
# Not a serialized JSON
|
||||
pass
|
||||
|
||||
if self.default_listener and msg.topic == self.run_topic:
|
||||
try:
|
||||
app_msg = Message.build(data)
|
||||
self.on_exec_message(client, app_msg)
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
'Message execution error: %s: %s', type(e).__name__, str(e)
|
||||
)
|
||||
else:
|
||||
get_bus().post(
|
||||
MQTTMessageEvent(
|
||||
host=client.host, port=client.port, topic=msg.topic, msg=data
|
||||
)
|
||||
)
|
||||
|
||||
return handler
|
||||
|
||||
def on_exec_message(self, client: MqttClient, msg):
|
||||
"""
|
||||
Message handler for (legacy) application requests over MQTT.
|
||||
"""
|
||||
|
||||
def response_thread(req: Request):
|
||||
"""
|
||||
A separate thread to handle the response to a request.
|
||||
"""
|
||||
if not self.run_topic:
|
||||
return
|
||||
|
||||
response = get_message_response(req)
|
||||
if not response:
|
||||
return
|
||||
|
||||
response_topic = f'{self.run_topic}/responses/{req.id}'
|
||||
self.logger.info(
|
||||
'Processing response on the MQTT topic %s: %s',
|
||||
response_topic,
|
||||
response,
|
||||
)
|
||||
|
||||
client.publish(payload=str(response), topic=response_topic)
|
||||
|
||||
self.logger.info('Received message on the MQTT backend: %s', msg)
|
||||
|
||||
try:
|
||||
get_bus().post(msg)
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
return
|
||||
|
||||
if isinstance(msg, Request):
|
||||
threading.Thread(
|
||||
target=response_thread,
|
||||
name='MQTTProcessorResponseThread',
|
||||
args=(msg,),
|
||||
).start()
|
||||
|
||||
def _get_client(
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: int = 1883,
|
||||
topics: Iterable[str] = (),
|
||||
client_id: Optional[str] = None,
|
||||
on_message: Optional[MqttCallback] = None,
|
||||
**kwargs,
|
||||
) -> MqttClient:
|
||||
"""
|
||||
:return: A :class:`platypush.message.event.mqtt.MqttClient` instance.
|
||||
It will return the existing client with the given inferred ID if it
|
||||
already exists, or it will register a new one.
|
||||
"""
|
||||
if host:
|
||||
kwargs['host'] = host
|
||||
kwargs['port'] = port
|
||||
else:
|
||||
assert (
|
||||
self.default_listener
|
||||
), 'No host specified and no configured default host'
|
||||
kwargs = self.default_listener.configuration
|
||||
|
||||
kwargs.update(
|
||||
{
|
||||
'topics': topics,
|
||||
'on_message': on_message,
|
||||
'client_id': client_id,
|
||||
}
|
||||
)
|
||||
|
||||
on_message = on_message or self.on_mqtt_message()
|
||||
client_id = self._get_client_id(
|
||||
host=kwargs['host'],
|
||||
port=kwargs['port'],
|
||||
client_id=client_id,
|
||||
on_message=on_message,
|
||||
topics=topics,
|
||||
)
|
||||
|
||||
kwargs['client_id'] = client_id
|
||||
with self._listeners_lock[client_id]:
|
||||
client = self.listeners.get(client_id)
|
||||
if not (client and client.is_alive()):
|
||||
client = self.listeners[
|
||||
client_id
|
||||
] = MqttClient( # pylint: disable=E1125
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if topics:
|
||||
client.subscribe(*topics)
|
||||
|
||||
return client
|
||||
|
||||
@action
|
||||
def publish(
|
||||
self,
|
||||
topic: str,
|
||||
msg: Any,
|
||||
qos: int = 0,
|
||||
reply_topic: Optional[str] = None,
|
||||
**mqtt_kwargs,
|
||||
):
|
||||
"""
|
||||
Sends a message to a topic.
|
||||
|
||||
:param topic: Topic/channel where the message will be delivered
|
||||
:param msg: Message to be sent. It can be a list, a dict, or a Message
|
||||
object.
|
||||
:param qos: Quality of Service (_QoS_) for the message - see `MQTT QoS
|
||||
<https://assetwolf.com/learn/mqtt-qos-understanding-quality-of-service>`_
|
||||
(default: 0).
|
||||
:param reply_topic: If a ``reply_topic`` is specified, then the action
|
||||
will wait for a response on this topic.
|
||||
:param mqtt_kwargs: MQTT broker configuration (host, port, username,
|
||||
password etc.). See :meth:`.__init__` parameters.
|
||||
"""
|
||||
response_buffer = io.BytesIO()
|
||||
client = None
|
||||
|
||||
try:
|
||||
# Try to parse it as a Platypush message or dump it to JSON from a dict/list
|
||||
if isinstance(msg, (dict, list)):
|
||||
msg = json.dumps(msg)
|
||||
|
||||
try:
|
||||
msg = Message.build(json.loads(msg))
|
||||
except (KeyError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
client = self._get_client(**mqtt_kwargs)
|
||||
client.connect()
|
||||
response_received = threading.Event()
|
||||
|
||||
# If it's a request, then wait for the response
|
||||
if (
|
||||
isinstance(msg, Request)
|
||||
and self.default_listener
|
||||
and client.host == self.default_listener.host
|
||||
and self.run_topic
|
||||
and topic == self.run_topic
|
||||
):
|
||||
reply_topic = f'{self.run_topic}/responses/{msg.id}'
|
||||
|
||||
if reply_topic:
|
||||
client.on_message = self._response_callback(
|
||||
reply_topic=reply_topic,
|
||||
|
@ -289,12 +423,13 @@ class MqttPlugin(Plugin):
|
|||
|
||||
client.publish(topic, str(msg), qos=qos)
|
||||
if not reply_topic:
|
||||
return
|
||||
return None
|
||||
|
||||
client.loop_start()
|
||||
ok = response_received.wait(timeout=timeout)
|
||||
ok = response_received.wait(timeout=client.timeout)
|
||||
if not ok:
|
||||
raise TimeoutError('Response timed out')
|
||||
|
||||
return response_buffer.getvalue()
|
||||
finally:
|
||||
response_buffer.close()
|
||||
|
@ -303,12 +438,50 @@ class MqttPlugin(Plugin):
|
|||
try:
|
||||
client.loop_stop()
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not stop client loop: %s', e)
|
||||
self.logger.warning(
|
||||
'Could not stop client loop: %s: %s', type(e).__name__, e
|
||||
)
|
||||
|
||||
client.disconnect()
|
||||
|
||||
@action
|
||||
def subscribe(self, topic: str, **mqtt_kwargs):
|
||||
"""
|
||||
Programmatically subscribe to a topic on an MQTT broker.
|
||||
|
||||
Messages received on this topic will trigger a
|
||||
:class:`platypush.message.event.mqtt.MQTTMessageEvent` event that you
|
||||
can subscribe to.
|
||||
|
||||
:param topic: Topic to subscribe to.
|
||||
:param mqtt_kwargs: MQTT broker configuration (host, port, username,
|
||||
password etc.). See :meth:`.__init__` parameters.
|
||||
"""
|
||||
client = self._get_client(
|
||||
topics=(topic,), on_message=self.on_mqtt_message(), **mqtt_kwargs
|
||||
)
|
||||
|
||||
if not client.is_alive():
|
||||
client.start()
|
||||
|
||||
@action
|
||||
def unsubscribe(self, topic: str, **mqtt_kwargs):
|
||||
"""
|
||||
Programmatically unsubscribe from a topic on an MQTT broker.
|
||||
|
||||
:param topic: Topic to unsubscribe from.
|
||||
:param mqtt_kwargs: MQTT broker configuration (host, port, username,
|
||||
password etc.). See :meth:`.__init__` parameters.
|
||||
"""
|
||||
self._get_client(**mqtt_kwargs).unsubscribe(topic)
|
||||
|
||||
@staticmethod
|
||||
def _response_callback(reply_topic: str, event: threading.Event, buffer: IO[bytes]):
|
||||
"""
|
||||
A response callback that writes the response to an IOBuffer and stops
|
||||
the client loop.
|
||||
"""
|
||||
|
||||
def on_message(client, _, msg):
|
||||
if msg.topic != reply_topic:
|
||||
return
|
||||
|
@ -322,9 +495,40 @@ class MqttPlugin(Plugin):
|
|||
@action
|
||||
def send_message(self, *args, **kwargs):
|
||||
"""
|
||||
Alias for :meth:`platypush.plugins.mqtt.MqttPlugin.publish`.
|
||||
Legacy alias for :meth:`platypush.plugins.mqtt.MqttPlugin.publish`.
|
||||
"""
|
||||
return self.publish(*args, **kwargs)
|
||||
|
||||
@override
|
||||
def main(self):
|
||||
if self.run_topic:
|
||||
self.logger.warning(
|
||||
'The MQTT integration is listening for commands on the topic %s.\n'
|
||||
'This approach is unsafe, as it allows any client to run unauthenticated requests.\n'
|
||||
'Please only enable it in test/trusted environments.',
|
||||
self.run_topic,
|
||||
)
|
||||
|
||||
for listener in self.listeners.values():
|
||||
listener.start()
|
||||
|
||||
self.wait_stop()
|
||||
|
||||
@override
|
||||
def stop(self):
|
||||
"""
|
||||
Disconnect all the clients upon plugin stop.
|
||||
"""
|
||||
for listener in self.listeners.values():
|
||||
listener.stop()
|
||||
|
||||
super().stop()
|
||||
|
||||
for listener in self.listeners.values():
|
||||
try:
|
||||
listener.join(timeout=1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
244
platypush/plugins/mqtt/_client.py
Normal file
244
platypush/plugins/mqtt/_client.py
Normal file
|
@ -0,0 +1,244 @@
|
|||
from enum import IntEnum
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import Any, Callable, Dict, Final, Iterable, Optional, Union
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from platypush.config import Config
|
||||
|
||||
MqttCallback = Callable[["MqttClient", Any, mqtt.MQTTMessage], Any]
|
||||
DEFAULT_TIMEOUT: Final[int] = 30
|
||||
|
||||
|
||||
class MqttClient(mqtt.Client, threading.Thread):
|
||||
"""
|
||||
Wrapper class for an MQTT client executed in a separate thread.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
host: str,
|
||||
port: int,
|
||||
client_id: str,
|
||||
topics: Iterable[str] = (),
|
||||
on_message: Optional[MqttCallback] = None,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
tls_cafile: Optional[str] = None,
|
||||
tls_certfile: Optional[str] = None,
|
||||
tls_keyfile: Optional[str] = None,
|
||||
tls_version: Optional[Union[str, IntEnum]] = None,
|
||||
tls_ciphers: Optional[str] = None,
|
||||
tls_insecure: bool = False,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
**kwargs,
|
||||
):
|
||||
self.client_id = client_id or str(Config.get('device_id'))
|
||||
mqtt.Client.__init__(self, *args, client_id=self.client_id, **kwargs)
|
||||
threading.Thread.__init__(self, name=f'MQTTClient:{self.client_id}')
|
||||
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.tls_cafile = self._expandpath(tls_cafile)
|
||||
self.tls_certfile = self._expandpath(tls_certfile)
|
||||
self.tls_keyfile = self._expandpath(tls_keyfile)
|
||||
self.tls_version = self._get_tls_version(tls_version)
|
||||
self.tls_ciphers = self._expandpath(tls_ciphers)
|
||||
self.tls_insecure = tls_insecure
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.topics = set(topics or [])
|
||||
self.timeout = timeout
|
||||
self.on_connect = self.connect_hndl()
|
||||
self.on_disconnect = self.disconnect_hndl()
|
||||
|
||||
if on_message:
|
||||
self.on_message = on_message # type: ignore
|
||||
|
||||
if username and password:
|
||||
self.username_pw_set(username, password)
|
||||
|
||||
if tls_cafile:
|
||||
self.tls_set(
|
||||
ca_certs=self.tls_cafile,
|
||||
certfile=self.tls_certfile,
|
||||
keyfile=self.tls_keyfile,
|
||||
tls_version=self.tls_version,
|
||||
ciphers=self.tls_ciphers,
|
||||
)
|
||||
|
||||
self.tls_insecure_set(self.tls_insecure)
|
||||
|
||||
self._running = False
|
||||
self._stop_scheduled = False
|
||||
|
||||
@staticmethod
|
||||
def _expandpath(path: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
Utility method to expand a path string.
|
||||
"""
|
||||
return os.path.abspath(os.path.expanduser(path)) if path else None
|
||||
|
||||
@staticmethod
|
||||
def _get_tls_version(version: Optional[Union[str, IntEnum]] = None):
|
||||
"""
|
||||
A utility method that normalizes an SSL version string or enum to a
|
||||
standard ``_SSLMethod`` enum.
|
||||
"""
|
||||
import ssl
|
||||
|
||||
if not version:
|
||||
return None
|
||||
|
||||
if isinstance(version, type(ssl.PROTOCOL_TLS)):
|
||||
return version
|
||||
|
||||
if isinstance(version, str):
|
||||
version = version.lower()
|
||||
|
||||
if version == 'tls':
|
||||
return ssl.PROTOCOL_TLS
|
||||
if version == 'tlsv1':
|
||||
return ssl.PROTOCOL_TLSv1
|
||||
if version == 'tlsv1.1':
|
||||
return ssl.PROTOCOL_TLSv1_1
|
||||
if version == 'tlsv1.2':
|
||||
return ssl.PROTOCOL_TLSv1_2
|
||||
|
||||
raise AssertionError(f'Unrecognized TLS version: {version}')
|
||||
|
||||
def connect(
|
||||
self,
|
||||
*args,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
keepalive: Optional[int] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Overrides the default connect method.
|
||||
"""
|
||||
if not self.is_connected():
|
||||
self.logger.debug(
|
||||
'Connecting to MQTT broker %s:%d, client_id=%s...',
|
||||
self.host,
|
||||
self.port,
|
||||
self.client_id,
|
||||
)
|
||||
|
||||
return super().connect(
|
||||
host=host or self.host,
|
||||
port=port or self.port,
|
||||
keepalive=keepalive or self.timeout,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def configuration(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:return: The configuration of the client.
|
||||
"""
|
||||
return {
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'topics': self.topics,
|
||||
'on_message': self.on_message,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'client_id': self.client_id,
|
||||
'tls_cafile': self.tls_cafile,
|
||||
'tls_certfile': self.tls_certfile,
|
||||
'tls_keyfile': self.tls_keyfile,
|
||||
'tls_version': self.tls_version,
|
||||
'tls_ciphers': self.tls_ciphers,
|
||||
'tls_insecure': self.tls_insecure,
|
||||
'timeout': self.timeout,
|
||||
}
|
||||
|
||||
def subscribe(self, *topics, **kwargs):
|
||||
"""
|
||||
Client subscription handler.
|
||||
"""
|
||||
if not topics:
|
||||
topics = self.topics
|
||||
|
||||
self.topics.update(topics)
|
||||
for topic in topics:
|
||||
super().subscribe(topic, **kwargs)
|
||||
|
||||
def unsubscribe(self, *topics, **kwargs):
|
||||
"""
|
||||
Client unsubscribe handler.
|
||||
"""
|
||||
if not topics:
|
||||
topics = self.topics
|
||||
|
||||
for topic in topics:
|
||||
if topic not in self.topics:
|
||||
self.logger.info('The topic %s is not subscribed', topic)
|
||||
continue
|
||||
|
||||
super().unsubscribe(topic, **kwargs)
|
||||
self.topics.remove(topic)
|
||||
|
||||
def connect_hndl(self):
|
||||
"""
|
||||
When the client connects, subscribe to all the registered topics.
|
||||
"""
|
||||
|
||||
def handler(*_, **__):
|
||||
self.logger.debug(
|
||||
'Connected to MQTT broker %s:%d, client_id=%s',
|
||||
self.host,
|
||||
self.port,
|
||||
self.client_id,
|
||||
)
|
||||
self.subscribe()
|
||||
|
||||
return handler
|
||||
|
||||
def disconnect_hndl(self):
|
||||
"""
|
||||
Notifies the client disconnection.
|
||||
"""
|
||||
|
||||
def handler(*_, **__):
|
||||
self.logger.debug(
|
||||
'Disconnected from MQTT broker %s:%d, client_id=%s',
|
||||
self.host,
|
||||
self.port,
|
||||
self.client_id,
|
||||
)
|
||||
|
||||
return handler
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Connects to the MQTT server, subscribes to all the registered topics
|
||||
and listens for messages.
|
||||
"""
|
||||
super().run()
|
||||
self.connect()
|
||||
self._running = True
|
||||
self.loop_forever()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
The stop method schedules the stop and disconnects the client.
|
||||
"""
|
||||
if not self.is_alive():
|
||||
return
|
||||
|
||||
self._stop_scheduled = True
|
||||
self.disconnect()
|
||||
self._running = False
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -1,5 +1,6 @@
|
|||
manifest:
|
||||
events: {}
|
||||
events:
|
||||
- platypush.message.event.mqtt.MQTTMessageEvent
|
||||
install:
|
||||
apk:
|
||||
- py3-paho-mqtt
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,269 +0,0 @@
|
|||
import contextlib
|
||||
import json
|
||||
from typing import Mapping
|
||||
|
||||
from platypush.backend.mqtt import MqttBackend
|
||||
from platypush.bus import Bus
|
||||
from platypush.context import get_bus, get_plugin
|
||||
from platypush.message.event.zigbee.mqtt import (
|
||||
ZigbeeMqttOnlineEvent,
|
||||
ZigbeeMqttOfflineEvent,
|
||||
ZigbeeMqttDevicePropertySetEvent,
|
||||
ZigbeeMqttDevicePairingEvent,
|
||||
ZigbeeMqttDeviceConnectedEvent,
|
||||
ZigbeeMqttDeviceBannedEvent,
|
||||
ZigbeeMqttDeviceRemovedEvent,
|
||||
ZigbeeMqttDeviceRemovedFailedEvent,
|
||||
ZigbeeMqttDeviceWhitelistedEvent,
|
||||
ZigbeeMqttDeviceRenamedEvent,
|
||||
ZigbeeMqttDeviceBindEvent,
|
||||
ZigbeeMqttDeviceUnbindEvent,
|
||||
ZigbeeMqttGroupAddedEvent,
|
||||
ZigbeeMqttGroupAddedFailedEvent,
|
||||
ZigbeeMqttGroupRemovedEvent,
|
||||
ZigbeeMqttGroupRemovedFailedEvent,
|
||||
ZigbeeMqttGroupRemoveAllEvent,
|
||||
ZigbeeMqttGroupRemoveAllFailedEvent,
|
||||
ZigbeeMqttErrorEvent,
|
||||
)
|
||||
from platypush.plugins.zigbee.mqtt import ZigbeeMqttPlugin
|
||||
|
||||
|
||||
class ZigbeeMqttListener(MqttBackend):
|
||||
"""
|
||||
Listener for zigbee2mqtt events.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
plugin = self._plugin
|
||||
self.base_topic = plugin.base_topic # type: ignore
|
||||
self._devices = {}
|
||||
self._devices_info = {}
|
||||
self._groups = {}
|
||||
self._last_state = None
|
||||
self.server_info = {
|
||||
'host': plugin.host, # type: ignore
|
||||
'port': plugin.port or self._default_mqtt_port, # type: ignore
|
||||
'tls_cafile': plugin.tls_cafile, # type: ignore
|
||||
'tls_certfile': plugin.tls_certfile, # type: ignore
|
||||
'tls_ciphers': plugin.tls_ciphers, # type: ignore
|
||||
'tls_keyfile': plugin.tls_keyfile, # type: ignore
|
||||
'tls_version': plugin.tls_version, # type: ignore
|
||||
'username': plugin.username, # type: ignore
|
||||
'password': plugin.password, # type: ignore
|
||||
}
|
||||
|
||||
listeners = [
|
||||
{
|
||||
**self.server_info,
|
||||
'topics': [
|
||||
self.base_topic + '/' + topic
|
||||
for topic in [
|
||||
'bridge/state',
|
||||
'bridge/log',
|
||||
'bridge/logging',
|
||||
'bridge/devices',
|
||||
'bridge/groups',
|
||||
]
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
subscribe_default_topic=False, listeners=listeners, **self.server_info
|
||||
)
|
||||
|
||||
assert self.client_id
|
||||
self.client_id += '-zigbee-mqtt'
|
||||
|
||||
def _process_state_message(self, client, msg):
|
||||
if msg == self._last_state:
|
||||
return
|
||||
|
||||
if msg == 'online':
|
||||
evt = ZigbeeMqttOnlineEvent
|
||||
elif msg == 'offline':
|
||||
evt = ZigbeeMqttOfflineEvent
|
||||
self.logger.warning('zigbee2mqtt service is offline')
|
||||
else:
|
||||
return
|
||||
|
||||
self._bus.post(evt(host=client._host, port=client._port))
|
||||
self._last_state = msg
|
||||
|
||||
def _process_log_message(self, client, msg):
|
||||
msg_type = msg.get('type')
|
||||
text = msg.get('message')
|
||||
args = {'host': client._host, 'port': client._port}
|
||||
|
||||
if msg_type == 'devices':
|
||||
devices = {}
|
||||
for dev in text or []:
|
||||
devices[dev['friendly_name']] = dev
|
||||
client.subscribe(self.base_topic + '/' + dev['friendly_name'])
|
||||
elif msg_type == 'pairing':
|
||||
self._bus.post(ZigbeeMqttDevicePairingEvent(device=text, **args))
|
||||
elif msg_type in ['device_ban', 'device_banned']:
|
||||
self._bus.post(ZigbeeMqttDeviceBannedEvent(device=text, **args))
|
||||
elif msg_type in ['device_removed_failed', 'device_force_removed_failed']:
|
||||
force = msg_type == 'device_force_removed_failed'
|
||||
self._bus.post(
|
||||
ZigbeeMqttDeviceRemovedFailedEvent(device=text, force=force, **args)
|
||||
)
|
||||
elif msg_type == 'device_whitelisted':
|
||||
self._bus.post(ZigbeeMqttDeviceWhitelistedEvent(device=text, **args))
|
||||
elif msg_type == 'device_renamed':
|
||||
self._bus.post(ZigbeeMqttDeviceRenamedEvent(device=text, **args))
|
||||
elif msg_type == 'device_bind':
|
||||
self._bus.post(ZigbeeMqttDeviceBindEvent(device=text, **args))
|
||||
elif msg_type == 'device_unbind':
|
||||
self._bus.post(ZigbeeMqttDeviceUnbindEvent(device=text, **args))
|
||||
elif msg_type == 'device_group_add':
|
||||
self._bus.post(ZigbeeMqttGroupAddedEvent(group=text, **args))
|
||||
elif msg_type == 'device_group_add_failed':
|
||||
self._bus.post(ZigbeeMqttGroupAddedFailedEvent(group=text, **args))
|
||||
elif msg_type == 'device_group_remove':
|
||||
self._bus.post(ZigbeeMqttGroupRemovedEvent(group=text, **args))
|
||||
elif msg_type == 'device_group_remove_failed':
|
||||
self._bus.post(ZigbeeMqttGroupRemovedFailedEvent(group=text, **args))
|
||||
elif msg_type == 'device_group_remove_all':
|
||||
self._bus.post(ZigbeeMqttGroupRemoveAllEvent(group=text, **args))
|
||||
elif msg_type == 'device_group_remove_all_failed':
|
||||
self._bus.post(ZigbeeMqttGroupRemoveAllFailedEvent(group=text, **args))
|
||||
elif msg_type == 'zigbee_publish_error':
|
||||
self.logger.error('zigbee2mqtt error: {}'.format(text))
|
||||
self._bus.post(ZigbeeMqttErrorEvent(error=text, **args))
|
||||
elif msg.get('level') in ['warning', 'error']:
|
||||
log = getattr(self.logger, msg['level'])
|
||||
log(
|
||||
'zigbee2mqtt {}: {}'.format(
|
||||
msg['level'], text or msg.get('error', msg.get('warning'))
|
||||
)
|
||||
)
|
||||
|
||||
def _process_devices(self, client, msg):
|
||||
devices_info = {
|
||||
device.get('friendly_name', device.get('ieee_address')): device
|
||||
for device in msg
|
||||
}
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
event_args = {'host': client._host, 'port': client._port}
|
||||
client.subscribe(
|
||||
*[self.base_topic + '/' + device for device in devices_info.keys()]
|
||||
)
|
||||
|
||||
for name, device in devices_info.items():
|
||||
if name not in self._devices:
|
||||
self._bus.post(
|
||||
ZigbeeMqttDeviceConnectedEvent(device=name, **event_args)
|
||||
)
|
||||
|
||||
exposes = (device.get('definition', {}) or {}).get('exposes', [])
|
||||
payload = self._plugin._build_device_get_request(exposes) # type: ignore
|
||||
if payload:
|
||||
client.publish(
|
||||
self.base_topic + '/' + name + '/get',
|
||||
json.dumps(payload),
|
||||
)
|
||||
|
||||
devices_copy = [*self._devices.keys()]
|
||||
for name in devices_copy:
|
||||
if name not in devices_info:
|
||||
self._bus.post(ZigbeeMqttDeviceRemovedEvent(device=name, **event_args))
|
||||
del self._devices[name]
|
||||
|
||||
self._devices = {device: {} for device in devices_info.keys()}
|
||||
self._devices_info = devices_info
|
||||
|
||||
def _process_groups(self, client, msg):
|
||||
# noinspection PyProtectedMember
|
||||
event_args = {'host': client._host, 'port': client._port}
|
||||
groups_info = {
|
||||
group.get('friendly_name', group.get('id')): group for group in msg
|
||||
}
|
||||
|
||||
for name in groups_info.keys():
|
||||
if name not in self._groups:
|
||||
self._bus.post(ZigbeeMqttGroupAddedEvent(group=name, **event_args))
|
||||
|
||||
groups_copy = [*self._groups.keys()]
|
||||
for name in groups_copy:
|
||||
if name not in groups_info:
|
||||
self._bus.post(ZigbeeMqttGroupRemovedEvent(group=name, **event_args))
|
||||
del self._groups[name]
|
||||
|
||||
self._groups = {group: {} for group in groups_info.keys()}
|
||||
|
||||
def on_mqtt_message(self):
|
||||
def handler(client, _, msg):
|
||||
topic = msg.topic[len(self.base_topic) + 1 :]
|
||||
data = msg.payload.decode()
|
||||
if not data:
|
||||
return
|
||||
|
||||
with contextlib.suppress(ValueError, TypeError):
|
||||
data = json.loads(data)
|
||||
|
||||
if topic == 'bridge/state':
|
||||
self._process_state_message(client, data)
|
||||
elif topic in ['bridge/log', 'bridge/logging']:
|
||||
self._process_log_message(client, data)
|
||||
elif topic == 'bridge/devices':
|
||||
self._process_devices(client, data)
|
||||
elif topic == 'bridge/groups':
|
||||
self._process_groups(client, data)
|
||||
else:
|
||||
suffix = topic.split('/')[-1]
|
||||
if suffix not in self._devices:
|
||||
return
|
||||
|
||||
name = suffix
|
||||
changed_props = {
|
||||
k: v for k, v in data.items() if v != self._devices[name].get(k)
|
||||
}
|
||||
|
||||
if changed_props:
|
||||
self._process_property_update(name, data)
|
||||
self._bus.post(
|
||||
ZigbeeMqttDevicePropertySetEvent(
|
||||
host=client._host,
|
||||
port=client._port,
|
||||
device=name,
|
||||
properties=changed_props,
|
||||
)
|
||||
)
|
||||
|
||||
self._devices[name].update(data)
|
||||
|
||||
return handler
|
||||
|
||||
@property
|
||||
def _plugin(self) -> ZigbeeMqttPlugin:
|
||||
plugin = get_plugin('zigbee.mqtt')
|
||||
assert plugin, 'The zigbee.mqtt plugin is not configured'
|
||||
return plugin
|
||||
|
||||
@property
|
||||
def _bus(self) -> Bus:
|
||||
return get_bus()
|
||||
|
||||
def _process_property_update(self, device_name: str, properties: Mapping):
|
||||
device_info = self._devices_info.get(device_name)
|
||||
if not (device_info and properties):
|
||||
return
|
||||
|
||||
self._plugin.publish_entities( # type: ignore
|
||||
[
|
||||
{
|
||||
**device_info,
|
||||
'state': properties,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue