Merge mqtt backend and plugin #320

Merged
blacklight merged 21 commits from 315/merge-mqtt-backend-and-plugin into master 2023-09-17 02:51:48 +02:00
108 changed files with 2365 additions and 4407 deletions

View File

@ -56,5 +56,4 @@ Backends
platypush/backend/weather.darksky.rst platypush/backend/weather.darksky.rst
platypush/backend/weather.openweathermap.rst platypush/backend/weather.openweathermap.rst
platypush/backend/wiimote.rst platypush/backend/wiimote.rst
platypush/backend/zwave.rst
platypush/backend/zwave.mqtt.rst platypush/backend/zwave.mqtt.rst

View File

@ -1,5 +0,0 @@
``zwave``
===========================
.. automodule:: platypush.backend.zwave
:members:

View File

@ -1,5 +0,0 @@
``zwave``
===========================
.. automodule:: platypush.plugins.zwave
:members:

View File

@ -149,5 +149,4 @@ Plugins
platypush/plugins/xmpp.rst platypush/plugins/xmpp.rst
platypush/plugins/zeroconf.rst platypush/plugins/zeroconf.rst
platypush/plugins/zigbee.mqtt.rst platypush/plugins/zigbee.mqtt.rst
platypush/plugins/zwave.rst
platypush/plugins/zwave.mqtt.rst platypush/plugins/zwave.mqtt.rst

View File

@ -4,9 +4,17 @@ from typing import Type, Optional, Union, List
from platypush.backend import Backend from platypush.backend import Backend
from platypush.context import get_plugin from platypush.context import get_plugin
from platypush.message.event.chat.telegram import MessageEvent, CommandMessageEvent, TextMessageEvent, \ from platypush.message.event.chat.telegram import (
PhotoMessageEvent, VideoMessageEvent, ContactMessageEvent, DocumentMessageEvent, LocationMessageEvent, \ MessageEvent,
GroupChatCreatedEvent CommandMessageEvent,
TextMessageEvent,
PhotoMessageEvent,
VideoMessageEvent,
ContactMessageEvent,
DocumentMessageEvent,
LocationMessageEvent,
GroupChatCreatedEvent,
)
from platypush.plugins.chat.telegram import ChatTelegramPlugin 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.ContactMessageEvent` when a contact is received.
* :class:`platypush.message.event.chat.telegram.DocumentMessageEvent` when a document 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.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: 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 :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. the bot. If nothing is specified then no restrictions are applied.
@ -39,40 +49,52 @@ class ChatTelegramBackend(Backend):
super().__init__(**kwargs) super().__init__(**kwargs)
self.authorized_chat_ids = set(authorized_chat_ids or []) 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): def _authorize(self, msg):
if not self.authorized_chat_ids: if not self.authorized_chat_ids:
return return
if msg.chat.type == 'private' and msg.chat.id not in self.authorized_chat_ids: 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.logger.info(
self._plugin.send_message(chat_id=msg.chat.id, text='You are not allowed to send messages to this bot') '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 raise PermissionError
def _msg_hook(self, cls: Type[MessageEvent]): def _msg_hook(self, cls: Type[MessageEvent]):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def hook(update, context): def hook(update, _):
msg = update.effective_message msg = update.effective_message
try: try:
self._authorize(msg) self._authorize(msg)
self.bus.post(cls(chat_id=update.effective_chat.id, self.bus.post(
message=self._plugin.parse_msg(msg).output, cls(
user=self._plugin.parse_user(update.effective_user).output)) chat_id=update.effective_chat.id,
message=self._plugin.parse_msg(msg).output,
user=self._plugin.parse_user(update.effective_user).output,
)
)
except PermissionError: except PermissionError:
pass pass
return hook return hook
def _group_hook(self): def _group_hook(self):
# noinspection PyUnusedLocal
def hook(update, context): def hook(update, context):
msg = update.effective_message msg = update.effective_message
if msg.group_chat_created: if msg.group_chat_created:
self.bus.post(GroupChatCreatedEvent(chat_id=update.effective_chat.id, self.bus.post(
message=self._plugin.parse_msg(msg).output, GroupChatCreatedEvent(
user=self._plugin.parse_user(update.effective_user).output)) 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: elif msg.photo:
self._msg_hook(PhotoMessageEvent)(update, context) self._msg_hook(PhotoMessageEvent)(update, context)
elif msg.video: elif msg.video:
@ -92,27 +114,33 @@ class ChatTelegramBackend(Backend):
return hook return hook
def _command_hook(self): def _command_hook(self):
# noinspection PyUnusedLocal def hook(update, _):
def hook(update, context):
msg = update.effective_message 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() 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: try:
self._authorize(msg) self._authorize(msg)
self.bus.post(CommandMessageEvent(chat_id=update.effective_chat.id, self.bus.post(
command=cmd, CommandMessageEvent(
cmdargs=args, chat_id=update.effective_chat.id,
message=self._plugin.parse_msg(msg).output, command=cmd,
user=self._plugin.parse_user(update.effective_user).output)) cmdargs=args,
message=self._plugin.parse_msg(msg).output,
user=self._plugin.parse_user(update.effective_user).output,
)
)
except PermissionError: except PermissionError:
pass pass
return hook return hook
def run(self): def run(self):
# noinspection PyPackageRequirements
from telegram.ext import MessageHandler, Filters from telegram.ext import MessageHandler, Filters
super().run() super().run()
@ -120,12 +148,24 @@ class ChatTelegramBackend(Backend):
dispatcher = telegram.dispatcher dispatcher = telegram.dispatcher
dispatcher.add_handler(MessageHandler(Filters.group, self._group_hook())) dispatcher.add_handler(MessageHandler(Filters.group, self._group_hook()))
dispatcher.add_handler(MessageHandler(Filters.text, self._msg_hook(TextMessageEvent))) dispatcher.add_handler(
dispatcher.add_handler(MessageHandler(Filters.photo, self._msg_hook(PhotoMessageEvent))) MessageHandler(Filters.text, self._msg_hook(TextMessageEvent))
dispatcher.add_handler(MessageHandler(Filters.video, self._msg_hook(VideoMessageEvent))) )
dispatcher.add_handler(MessageHandler(Filters.contact, self._msg_hook(ContactMessageEvent))) dispatcher.add_handler(
dispatcher.add_handler(MessageHandler(Filters.location, self._msg_hook(LocationMessageEvent))) MessageHandler(Filters.photo, self._msg_hook(PhotoMessageEvent))
dispatcher.add_handler(MessageHandler(Filters.document, self._msg_hook(DocumentMessageEvent))) )
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())) dispatcher.add_handler(MessageHandler(Filters.command, self._command_hook()))
self.logger.info('Initialized Telegram backend') self.logger.info('Initialized Telegram backend')

View File

@ -5,7 +5,7 @@ manifest:
platypush.message.event.chat.telegram.ContactMessageEvent: when a contact is received. platypush.message.event.chat.telegram.ContactMessageEvent: when a contact is received.
platypush.message.event.chat.telegram.DocumentMessageEvent: when a document is platypush.message.event.chat.telegram.DocumentMessageEvent: when a document is
received. 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. to a new group.
platypush.message.event.chat.telegram.LocationMessageEvent: when a location is platypush.message.event.chat.telegram.LocationMessageEvent: when a location is
received. received.

View File

@ -4,7 +4,7 @@ from .auth import (
authenticate_user_pass, authenticate_user_pass,
get_auth_status, 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 .logger import logger
from .routes import ( from .routes import (
get_http_port, get_http_port,
@ -25,7 +25,6 @@ __all__ = [
'get_http_port', 'get_http_port',
'get_ip_or_hostname', 'get_ip_or_hostname',
'get_local_base_url', 'get_local_base_url',
'get_message_response',
'get_remote_base_url', 'get_remote_base_url',
'get_routes', 'get_routes',
'get_streaming_routes', 'get_streaming_routes',

View File

@ -1,11 +1,9 @@
from redis import Redis
from platypush.bus.redis import RedisBus from platypush.bus.redis import RedisBus
from platypush.config import Config from platypush.config import Config
from platypush.context import get_backend from platypush.context import get_backend
from platypush.message import Message from platypush.message import Message
from platypush.message.request import Request 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 from .logger import logger
@ -67,24 +65,3 @@ def send_request(action, wait_for_response=True, **kwargs):
msg['args'] = kwargs msg['args'] = kwargs
return send_message(msg, wait_for_response=wait_for_response) 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

View File

@ -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

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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}}]); "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

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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}}]); "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

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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}}]); "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

View File

@ -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=_}}]); "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

View File

@ -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}}]); "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

View File

@ -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":""}

View File

@ -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=$}}]); "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

View File

@ -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}}]); "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

View File

@ -4,7 +4,7 @@ import Utils from "@/Utils"
export default { export default {
name: "EntityMixin", name: "EntityMixin",
mixins: [Utils], mixins: [Utils],
emits: ['input'], emits: ['input', 'loading'],
props: { props: {
loading: { loading: {
type: Boolean, type: Boolean,

View File

@ -168,10 +168,11 @@ export default {
methods: { methods: {
addEntity(entity) { addEntity(entity) {
this.entities[entity.id] = entity
if (entity.parent_id != null) if (entity.parent_id != null)
return // Only group entities that have no parent return // Only group entities that have no parent
this.entities[entity.id] = entity;
['id', 'type', 'category', 'plugin'].forEach((attr) => { ['id', 'type', 'category', 'plugin'].forEach((attr) => {
if (entity[attr] == null) if (entity[attr] == null)
return return

View File

@ -96,7 +96,7 @@ $collapse-toggler-width: 2em;
.value { .value {
font-size: 1.1em; font-size: 1.1em;
font-weight: bold; font-weight: bold;
word-break: break-all; word-break: break-word;
opacity: 0.8; opacity: 0.8;
} }

View File

@ -6,8 +6,12 @@ from websocket import WebSocketApp
from platypush.backend import Backend from platypush.backend import Backend
from platypush.context import get_plugin from platypush.context import get_plugin
from platypush.message.event.trello import MoveCardEvent, NewCardEvent, ArchivedCardEvent, \ from platypush.message.event.trello import (
UnarchivedCardEvent MoveCardEvent,
NewCardEvent,
ArchivedCardEvent,
UnarchivedCardEvent,
)
from platypush.plugins.trello import TrelloPlugin from platypush.plugins.trello import TrelloPlugin
@ -33,9 +37,9 @@ class TrelloBackend(Backend):
Triggers: Triggers:
* :class:`platypush.message.event.trello.NewCardEvent` when a card is created. * :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.trello.MoveCardEvent` when a card is moved.
* :class:`platypush.message.event.ArchivedCardEvent` when a card is archived/closed. * :class:`platypush.message.event.trello.ArchivedCardEvent` when a card is archived/closed.
* :class:`platypush.message.event.UnarchivedCardEvent` when a card is un-archived/opened. * :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): def _initialize_connection(self, ws: WebSocketApp):
for board_id in self._boards_by_id.keys(): for board_id in self._boards_by_id.keys():
self._send(ws, { self._send(
'type': 'subscribe', ws,
'modelType': 'Board', {
'idModel': board_id, 'type': 'subscribe',
'tags': ['clientActions', 'updates'], 'modelType': 'Board',
'invitationTokens': [], 'idModel': board_id,
}) 'tags': ['clientActions', 'updates'],
'invitationTokens': [],
},
)
self.logger.info('Trello boards subscribed') self.logger.info('Trello boards subscribed')
def _on_msg(self): def _on_msg(self):
def hndl(*args): def hndl(*args):
if len(args) < 2: if len(args) < 2:
self.logger.warning('Missing websocket argument - make sure that you are using ' self.logger.warning(
'a version of websocket-client < 0.53.0 or >= 0.58.0') 'Missing websocket argument - make sure that you are using '
'a version of websocket-client < 0.53.0 or >= 0.58.0'
)
return return
ws, msg = args[:2] ws, msg = args[:2]
@ -96,7 +105,9 @@ class TrelloBackend(Backend):
try: try:
msg = json.loads(msg) msg = json.loads(msg)
except Exception as e: 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 return
if 'error' in msg: if 'error' in msg:
@ -119,8 +130,12 @@ class TrelloBackend(Backend):
args = { args = {
'card_id': delta['data']['card']['id'], 'card_id': delta['data']['card']['id'],
'card_name': delta['data']['card']['name'], 'card_name': delta['data']['card']['name'],
'list_id': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('id'), 'list_id': (
'list_name': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('name'), 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_id': delta['data']['board']['id'],
'board_name': delta['data']['board']['name'], 'board_name': delta['data']['board']['name'],
'closed': delta.get('closed'), 'closed': delta.get('closed'),
@ -134,14 +149,20 @@ class TrelloBackend(Backend):
self.bus.post(NewCardEvent(**args)) self.bus.post(NewCardEvent(**args))
elif delta.get('type') == 'updateCard': elif delta.get('type') == 'updateCard':
if 'listBefore' in delta['data']: if 'listBefore' in delta['data']:
args.update({ args.update(
'old_list_id': delta['data']['listBefore']['id'], {
'old_list_name': delta['data']['listBefore']['name'], 'old_list_id': delta['data']['listBefore']['id'],
}) 'old_list_name': delta['data']['listBefore']['name'],
}
)
self.bus.post(MoveCardEvent(**args)) self.bus.post(MoveCardEvent(**args))
elif 'closed' in delta['data'].get('old', {}): 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)) self.bus.post(cls(**args))
return hndl return hndl
@ -185,11 +206,13 @@ class TrelloBackend(Backend):
self._req_id += 1 self._req_id += 1
def _connect(self) -> WebSocketApp: def _connect(self) -> WebSocketApp:
return WebSocketApp(self.url, return WebSocketApp(
on_open=self._on_open(), self.url,
on_message=self._on_msg(), on_open=self._on_open(),
on_error=self._on_error(), on_message=self._on_msg(),
on_close=self._on_close()) on_error=self._on_error(),
on_close=self._on_close(),
)
def run(self): def run(self):
super().run() super().run()

View File

@ -1,8 +1,8 @@
manifest: manifest:
events: events:
platypush.message.event.ArchivedCardEvent: when a card is archived/closed. platypush.message.event.trello.ArchivedCardEvent: when a card is archived/closed.
platypush.message.event.MoveCardEvent: when a card is moved. platypush.message.event.trello.MoveCardEvent: when a card is moved.
platypush.message.event.UnarchivedCardEvent: when a card is un-archived/opened. platypush.message.event.trello.UnarchivedCardEvent: when a card is un-archived/opened.
platypush.message.event.trello.NewCardEvent: when a card is created. platypush.message.event.trello.NewCardEvent: when a card is created.
install: install:
pip: [] pip: []

View File

@ -15,7 +15,7 @@ class WiimoteBackend(Backend):
Triggers: Triggers:
* :class:`platypush.message.event.Wiimote.WiimoteEvent` \ * :class:`platypush.message.event.wiimote.WiimoteEvent` \
when the state of the Wiimote (battery, buttons, acceleration etc.) changes when the state of the Wiimote (battery, buttons, acceleration etc.) changes
Requires: Requires:

View File

@ -1,6 +1,6 @@
manifest: manifest:
events: 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 buttons, acceleration etc.) changes
install: install:
pip: [] pip: []

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -1,15 +1,34 @@
from collections import defaultdict
from dataclasses import dataclass, field
import logging import logging
import threading import threading
import time import time
from queue import Queue, Empty 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 from platypush.message.event import Event
logger = logging.getLogger('platypush:bus') 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: class Bus:
""" """
Main local bus where the daemon will listen for new messages. Main local bus where the daemon will listen for new messages.
@ -21,7 +40,10 @@ class Bus:
self.bus = Queue() self.bus = Queue()
self.on_message = on_message self.on_message = on_message
self.thread_id = threading.get_ident() 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() self._should_stop = threading.Event()
def post(self, msg): def post(self, msg):
@ -38,26 +60,24 @@ class Bus:
def stop(self): def stop(self):
self._should_stop.set() 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 _msg_executor(self, msg):
def event_handler(event: Event, handler: Callable[[Event], None]): def event_handler(event: Event, handler: Callable[[Event], None]):
logger.info('Triggering event handler %s', handler.__name__) logger.info('Triggering event handler %s', handler.__name__)
handler(event) handler(event)
def executor(): def executor():
if isinstance(msg, Event): for hndl in self._get_matching_handlers(msg):
handlers = self.event_handlers.get( threading.Thread(target=event_handler, args=(msg, hndl)).start()
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))
try: try:
if self.on_message: if self.on_message:
@ -100,27 +120,25 @@ class Bus:
logger.info('Bus service stopped') logger.info('Bus service stopped')
def register_handler( def register_handler(
self, event_type: Type[Event], handler: Callable[[Event], None] self, type: Type[Message], handler: Callable[[Message], None], **kwargs
) -> Callable[[], None]: ) -> 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 type: Type of the message to subscribe to (event inheritance also works).
:param handler: Event handler - a function that takes an Event object as parameter. :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). :return: A function that can be called to remove the handler (no parameters required).
""" """
if event_type not in self.event_handlers: self.handlers[type][handler] = MessageHandler(type, handler, kwargs)
self.event_handlers[event_type] = set()
self.event_handlers[event_type].add(handler)
def unregister(): def unregister():
self.unregister_handler(event_type, handler) self.unregister_handler(type, handler)
return unregister return unregister
def unregister_handler( def unregister_handler(
self, event_type: Type[Event], handler: Callable[[Event], None] self, type: Type[Message], handler: Callable[[Message], None]
) -> None: ) -> None:
""" """
Remove an event handler. Remove an event handler.
@ -128,14 +146,12 @@ class Bus:
:param event_type: Event type. :param event_type: Event type.
:param handler: Existing event handler. :param handler: Existing event handler.
""" """
if event_type not in self.event_handlers: if type not in self.handlers:
return return
if handler in self.event_handlers[event_type]: self.handlers[type].pop(handler, None)
self.event_handlers[event_type].remove(handler) if len(self.handlers[type]) == 0:
del self.handlers[type]
if len(self.event_handlers[event_type]) == 0:
del self.event_handlers[event_type]
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View File

@ -106,10 +106,11 @@ class Config:
if cfgfile is None: if cfgfile is None:
cfgfile = self._get_default_cfgfile() cfgfile = self._get_default_cfgfile()
cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
if cfgfile is None or not os.path.exists(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): def _init_logging(self):
logging_config = { logging_config = {
@ -211,21 +212,24 @@ class Config:
'variable': {}, '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__)) 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 if not cfgfile:
cfgfile = ( # Use /etc/platypush/config.yaml if the user is running as root,
( # otherwise ~/.config/platypush/config.yaml
os.path.join(os.environ['XDG_CONFIG_HOME'], 'config.yaml') cfgfile = (
if os.environ.get('XDG_CONFIG_HOME') (
else os.path.join( os.path.join(os.environ['XDG_CONFIG_HOME'], 'config.yaml')
os.path.expanduser('~'), '.config', 'platypush', '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 = pathlib.Path(cfgfile).parent
cfgdir.mkdir(parents=True, exist_ok=True) cfgdir.mkdir(parents=True, exist_ok=True)
@ -526,6 +530,7 @@ class Config:
Get a config value or the whole configuration object. Get a config value or the whole configuration object.
:param key: Configuration entry to get (default: all entries). :param key: Configuration entry to get (default: all entries).
:param default: Default value to return if the key is missing.
""" """
# pylint: disable=protected-access # pylint: disable=protected-access
config = cls._get_instance()._config.copy() config = cls._get_instance()._config.copy()

View File

@ -26,6 +26,7 @@
### Include directives ### Include directives
### ------------------ ### ------------------
###
# # You can split your configuration over multiple files and use the include # # You can split your configuration over multiple files and use the include
# # directive to import other files into your configuration. # # directive to import other files into your configuration.
# #
@ -37,11 +38,13 @@
# - logging.yaml # - logging.yaml
# - media.yaml # - media.yaml
# - sensors.yaml # - sensors.yaml
###
### ----------------- ### -----------------
### Working directory ### Working directory
### ----------------- ### -----------------
###
# # Working directory of the application. This is where the main database will be # # 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 # # stored by default (if the default SQLite configuration is used), and it's
# # where the integrations will store their state. # # where the integrations will store their state.
@ -55,11 +58,13 @@
# # - $HOME/.local/share/platypush otherwise. # # - $HOME/.local/share/platypush otherwise.
# #
# workdir: ~/.local/share/platypush # workdir: ~/.local/share/platypush
###
### ---------------------- ### ----------------------
### Database configuration ### Database configuration
### ---------------------- ### ----------------------
###
# # By default Platypush will use a SQLite database named `main.db` under the # # 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 # # `workdir`. You can specify any other engine string here - the application has
# # been tested against SQLite, Postgres and MariaDB/MySQL >= 8. # # been tested against SQLite, Postgres and MariaDB/MySQL >= 8.
@ -73,11 +78,13 @@
# engine: sqlite:///home/user/.local/share/platypush/main.db # engine: sqlite:///home/user/.local/share/platypush/main.db
# # OR, if you want to use e.g. Postgres with the pg8000 driver: # # OR, if you want to use e.g. Postgres with the pg8000 driver:
# engine: postgresql+pg8000://dbuser:dbpass@dbhost/dbname # engine: postgresql+pg8000://dbuser:dbpass@dbhost/dbname
###
### --------------------- ### ---------------------
### Logging configuration ### Logging configuration
### --------------------- ### ---------------------
###
# # Platypush logs on stdout by default. You can use the logging section to # # Platypush logs on stdout by default. You can use the logging section to
# # specify an alternative file or change the logging level. # # specify an alternative file or change the logging level.
# #
@ -87,11 +94,13 @@
# logging: # logging:
# filename: ~/.local/log/platypush/platypush.log # filename: ~/.local/log/platypush/platypush.log
# level: INFO # level: INFO
###
### ----------------------- ### -----------------------
### device_id configuration ### device_id configuration
### ----------------------- ### -----------------------
###
# # The device_id is used by many components of Platypush and it should uniquely # # 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 # # identify a device in your network. If nothing is specified then the hostname
# # will be used. # # will be used.
@ -100,11 +109,13 @@
# # -d/--device-id option. # # -d/--device-id option.
# #
# device_id: my_device # device_id: my_device
###
### ------------------- ### -------------------
### Redis configuration ### Redis configuration
### ------------------- ### -------------------
###
# # Platypush needs a Redis instance for inter-process communication. # # Platypush needs a Redis instance for inter-process communication.
# # # #
# # By default, the application will try and connect to a Redis server listening # # By default, the application will try and connect to a Redis server listening
@ -123,11 +134,13 @@
# port: 6379 # port: 6379
# username: user # username: user
# password: secret # password: secret
###
### ------------------------ ### ------------------------
### Web server configuration ### Web server configuration
### ------------------------ ### ------------------------
###
# Platypush comes with a versatile Web server that is used to: # Platypush comes with a versatile Web server that is used to:
# #
# - Serve the main UI and the UIs for the plugins that provide one. # - Serve the main UI and the UIs for the plugins that provide one.
@ -225,6 +238,30 @@ backend.http:
# poll_interval: 20 # 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 # # Example configuration of music.mpd plugin, a plugin to interact with MPD and
# # Mopidy music server instances. See # # Mopidy music server instances. See
@ -244,17 +281,6 @@ backend.http:
# clipboard: # 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 # # Enable the system plugin if you want your device to periodically report
# # system statistics (CPU load, disk usage, memory usage etc.) # # 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 # # 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 # # be triggered with the new data, and you can subscribe a hook to these events
# # to run your custom logic. # # to run your custom logic.
# #
# system: # system:
# # How often we should poll for new data # # How often we should poll for new data
# poll_interval: 60 # poll_interval: 60
@ -311,7 +337,7 @@ backend.http:
### ###
# # Example configuration of a weather plugin # # Example configuration of a weather plugin
# #
# weather.openweathermap: # weather.openweathermap:
# token: secret # token: secret
# lat: lat # lat: lat
@ -328,7 +354,7 @@ backend.http:
# # using Web hooks (i.e. event hooks that subscribe to # # using Web hooks (i.e. event hooks that subscribe to
# # `platypush.message.event.http.hook.WebhookEvent` events), provided that the # # `platypush.message.event.http.hook.WebhookEvent` events), provided that the
# # Web server is listening on a publicly accessible address. # # Web server is listening on a publicly accessible address.
# #
# ifttt: # ifttt:
# ifttt_key: SECRET # ifttt_key: SECRET
### ###
@ -348,14 +374,14 @@ backend.http:
# # build automation routines on. You can also use Platypush to control your # # build automation routines on. You can also use Platypush to control your
# # Zigbee devices, either through the Web interface or programmatically through # # Zigbee devices, either through the Web interface or programmatically through
# # the available plugin actions. # # the available plugin actions.
# #
# zigbee.mqtt: # zigbee.mqtt:
# # Host of the MQTT broker # # Host of the MQTT broker
# host: riemann # host: my-mqtt-broker
# # Listen port of the MQTT broker # # Listen port of the MQTT broker
# port: 1883 # port: 1883
# # Base topic, as specified in `<zigbee2mqtt_dir>/data/configuration.yaml` # # 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. # # automation routines on.
# # You can also use Platypush to control your Z-Wave devices, either through the # # You can also use Platypush to control your Z-Wave devices, either through the
# # Web interface or programmatically through the available plugin actions. # # Web interface or programmatically through the available plugin actions.
# #
# zwave.mqtt: # zwave.mqtt:
# # Host of the MQTT broker # # Host of the MQTT broker
# host: riemann # host: my-mqtt-broker
# # Listen port of the MQTT broker # # Listen port of the MQTT broker
# port: 1883 # port: 1883
# # Gateway name, usually configured in the ZWaveJS-UI through `Settings -> # # 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 # # You can also capture images by connecting to the
# # `/camera/<plugin>/photo[.extension]`, for example `/camera/ffmpeg/photo.jpg`. # # `/camera/<plugin>/photo[.extension]`, for example `/camera/ffmpeg/photo.jpg`.
# #
# camera.ffmpeg: # camera.ffmpeg:
# # Default video device to use # # Default video device to use
# device: /dev/video0 # device: /dev/video0
@ -543,7 +569,7 @@ backend.http:
# # `platypush.message.event.sensor.SensorDataChangeEvent` events will be # # `platypush.message.event.sensor.SensorDataChangeEvent` events will be
# # triggered when the data changes - you can subscribe to them in your custom # # triggered when the data changes - you can subscribe to them in your custom
# # hooks. # # hooks.
# #
# serial: # serial:
# # The path to the USB interface with e.g. an Arduino or ESP microcontroller # # The path to the USB interface with e.g. an Arduino or ESP microcontroller
# # connected. # # connected.
@ -579,7 +605,7 @@ backend.http:
# temperature: 0.5 # temperature: 0.5
# humidity: 0.75 # humidity: 0.75
# luminosity: 5 # luminosity: 5
# #
# # If a threshold is defined for a sensor, and the value of that sensor goes # # If a threshold is defined for a sensor, and the value of that sensor goes
# # below/above that temperature between two reads, then a # # below/above that temperature between two reads, then a
# # `SensorDataBelowThresholdEvent` or a `SensorDataAboveThresholdEvent` will # # `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 # # Note that the interface of this plugin is basically the same as the serial
# # plugin, and any other plugin that extends `SensorPlugin` in general. # # plugin, and any other plugin that extends `SensorPlugin` in general.
# # Therefore, poll_interval, tolerance and thresholds are supported here too. # # Therefore, poll_interval, tolerance and thresholds are supported here too.
# #
# arduino: # arduino:
# board: /dev/ttyUSB0 # board: /dev/ttyUSB0
# # name -> PIN number mapping (similar for digital_pins). # # name -> PIN number mapping (similar for digital_pins).
@ -607,10 +633,10 @@ backend.http:
# # the forwarded events. # # the forwarded events.
# analog_pins: # analog_pins:
# temperature: 7 # temperature: 7
# #
# tolerance: # tolerance:
# temperature: 0.5 # temperature: 0.5
# #
# thresholds: # thresholds:
# temperature: 25.0 # temperature: 25.0
### ###
@ -619,13 +645,13 @@ backend.http:
# # Another example: the LTR559 is a common sensor for proximity and luminosity # # 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 # # 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. # # interface. It exposes the same base interface as all other sensor plugins.
# #
# sensor.ltr559: # sensor.ltr559:
# poll_interval: 1.0 # poll_interval: 1.0
# tolerance: # tolerance:
# light: 7.0 # light: 7.0
# proximity: 5.0 # proximity: 5.0
# #
# thresholds: # thresholds:
# proximity: 10.0 # proximity: 10.0
### ###
@ -637,7 +663,7 @@ backend.http:
### ###
# # `tts` is the simplest TTS integration. It leverages the Google Translate open # # `tts` is the simplest TTS integration. It leverages the Google Translate open
# # "say" endpoint to render text as audio speech. # # "say" endpoint to render text as audio speech.
# #
# tts: # tts:
# # The media plugin that should be used to play the audio response # # The media plugin that should be used to play the audio response
# media_plugin: media.vlc # media_plugin: media.vlc
@ -655,7 +681,7 @@ backend.http:
# # Google developers console, create an API key, and follow the instruction # # 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 # # logged on the next restart to give your app the required permissions to your
# # account. # # account.
# #
# tts.google: # tts.google:
# # The media plugin that should be used to play the audio response # # The media plugin that should be used to play the audio response
# media_plugin: media.vlc # media_plugin: media.vlc
@ -674,7 +700,7 @@ backend.http:
# # Follow the instructions at # # Follow the instructions at
# # https://docs.platypush.tech/platypush/plugins/tts.mimic3.html to quickly # # https://docs.platypush.tech/platypush/plugins/tts.mimic3.html to quickly
# # bootstrap a mimic3 server. # # bootstrap a mimic3 server.
# #
# tts.mimic3: # tts.mimic3:
# # The base URL of the mimic3 server # # The base URL of the mimic3 server
# server_url: http://riemann:59125 # 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 # # 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, # # Android device that detects when your phone enters or exits a certain area,
# # and sends the appropriate request to your Platypush server. # # and sends the appropriate request to your Platypush server.
# #
# procedure.at_home: # procedure.at_home:
# # Set the db variable AT_HOME to 1. # # Set the db variable AT_HOME to 1.
# # Variables are flexible entities with a name and a value that will be # # Variables are flexible entities with a name and a value that will be
@ -741,11 +767,11 @@ backend.http:
# - action: variable.set # - action: variable.set
# args: # args:
# AT_HOME: 1 # AT_HOME: 1
# #
# # Check the luminosity level from e.g. a connected LTR559 sensor. # # Check the luminosity level from e.g. a connected LTR559 sensor.
# # It could also be a Bluetooth, Zigbee, Z-Wave, serial etc. sensor. # # It could also be a Bluetooth, Zigbee, Z-Wave, serial etc. sensor.
# - action: sensor.ltr559.get_measurement # - action: sensor.ltr559.get_measurement
# #
# # If it's below a certain threshold, turn on the lights. # # If it's below a certain threshold, turn on the lights.
# # In this case, `light` is a parameter returned by the previous response, # # In this case, `light` is a parameter returned by the previous response,
# # so we can directly access it here through the `${}` context operator. # # so we can directly access it here through the `${}` context operator.
@ -753,12 +779,12 @@ backend.http:
# # ${output["light"]}. # # ${output["light"]}.
# - if ${int(light or 0) < 110}: # - if ${int(light or 0) < 110}:
# - action: light.hue.on # - action: light.hue.on
# #
# # Say a welcome home message # # Say a welcome home message
# - action: tts.mimic3.say # - action: tts.mimic3.say
# args: # args:
# text: Welcome home # text: Welcome home
# #
# # Start the music # # Start the music
# - action: music.mpd.play # - action: music.mpd.play
### ###
@ -771,10 +797,10 @@ backend.http:
# - action: variable.unset # - action: variable.unset
# args: # args:
# name: AT_HOME # name: AT_HOME
# #
# # Stop the music # # Stop the music
# - action: music.mpd.stop # - action: music.mpd.stop
# #
# # Turn off the lights # # Turn off the lights
# - action: light.hue.off # - action: light.hue.off
### ###
@ -789,12 +815,12 @@ backend.http:
# # # #
# # See the event hook section below for a sample hook that listens for messages # # See the event hook section below for a sample hook that listens for messages
# # sent by other clients using this procedure. # # sent by other clients using this procedure.
# #
# procedure.send_sensor_data(name, value): # procedure.send_sensor_data(name, value):
# - action: mqtt.send_message # - action: mqtt.send_message
# args: # args:
# topic: platypush/sensors # topic: platypush/sensors
# host: mqtt-server # host: my-mqtt-broker
# port: 1883 # port: 1883
# msg: # msg:
# name: ${name} # name: ${name}
@ -807,7 +833,7 @@ backend.http:
## ------------------- ## -------------------
# Event hooks are procedures that are run when a certain condition is met. # 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 # Check the documentation of your configured backends and plugins to see which
# events they can trigger, and check https://docs.platypush.tech/events.html # events they can trigger, and check https://docs.platypush.tech/events.html
# for the full list of available events with their schemas. # 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 # # Note that, for this event to be triggered, the application must first
# # subscribe to the `platypush/sensor` topic - e.g. by adding `platypush/sensor` # # subscribe to the `platypush/sensor` topic - e.g. by adding `platypush/sensor`
# # to the active subscriptions in the `mqtt` configurations. # # to the active subscriptions in the `mqtt` configurations.
# #
# event.hook.OnSensorDataReceived: # event.hook.OnSensorDataReceived:
# if: # if:
# type: platypush.message.event.mqtt.MQTTMessageEvent # 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 # # The example below plays the music on mpd/mopidy when your voice assistant
# # triggers a speech recognized event with "play the music" content. # # triggers a speech recognized event with "play the music" content.
# #
# event.hook.PlayMusicAssistantCommand: # event.hook.PlayMusicAssistantCommand:
# if: # if:
# type: platypush.message.event.assistant.SpeechRecognizedEvent # 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" # # This will turn on the lights when you say "turn on the lights"
# #
# event.hook.TurnOnLightsCommand: # event.hook.TurnOnLightsCommand:
# if: # if:
# type: platypush.message.event.assistant.SpeechRecognizedEvent # 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 # # 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 # # advised to create your custom passphrase and checks the request's headers or
# # query string for it - preferably one passphrase per endpoint. # # query string for it - preferably one passphrase per endpoint.
# #
# event.hook.WebhookExample: # event.hook.WebhookExample:
# if: # if:
# type: platypush.message.event.http.hook.WebhookEvent # type: platypush.message.event.http.hook.WebhookEvent
@ -910,7 +936,7 @@ backend.http:
# # Standard UNIX cron syntax is supported, plus an optional 6th indicator # # Standard UNIX cron syntax is supported, plus an optional 6th indicator
# # at the end of the expression to run jobs with second granularity. # # at the end of the expression to run jobs with second granularity.
# # The example below executes a script at intervals of 1 minute. # # The example below executes a script at intervals of 1 minute.
# #
# cron.TestCron: # cron.TestCron:
# cron_expression: '* * * * *' # cron_expression: '* * * * *'
# actions: # actions:

View File

@ -25,8 +25,8 @@ class EntitiesEngine(Thread):
together (preventing excessive writes and throttling events), and together (preventing excessive writes and throttling events), and
prevents race conditions when SQLite is used. prevents race conditions when SQLite is used.
2. Merge any existing entities with their newer representations. 2. Merge any existing entities with their newer representations.
3. Update the entities taxonomy. 3. Update the entities' taxonomy.
4. Persist the new state to the entities database. 4. Persist the new state to the entities' database.
5. Trigger events for the updated entities. 5. Trigger events for the updated entities.
""" """

View File

@ -32,7 +32,10 @@ def action(f: Callable[..., Any]) -> Callable[..., Response]:
response = Response() response = Response()
try: try:
result = f(*args, **kwargs) result = f(*args, **kwargs)
except TypeError as e: except Exception as e:
if isinstance(e, KeyboardInterrupt):
return response
_logger.exception(e) _logger.exception(e)
result = Response(errors=[str(e)]) result = Response(errors=[str(e)])

View File

@ -12,6 +12,7 @@ from typing import Callable, Dict, Generator, Optional, Type, Union
from platypush.backend import Backend from platypush.backend import Backend
from platypush.config import Config from platypush.config import Config
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action
from platypush.message import Message
from platypush.message.event import Event from platypush.message.event import Event
from platypush.message.response import Response from platypush.message.response import Response
from platypush.utils import ( from platypush.utils import (
@ -314,7 +315,8 @@ class InspectPlugin(Plugin):
{ {
get_plugin_name_by_class(cls): dict(plugin) get_plugin_name_by_class(cls): dict(plugin)
for cls, plugin in self._components_cache.get(Plugin, {}).items() for cls, plugin in self._components_cache.get(Plugin, {}).items()
} },
cls=Message.Encoder,
) )
@action @action

View File

@ -1,16 +1,25 @@
from collections import defaultdict
import hashlib
import io import io
import json import json
import os
import threading 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.config import Config
from platypush.context import get_bus
from platypush.message import Message 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 This plugin allows you to send custom message to a message queue compatible
with the MQTT protocol, see https://mqtt.org/ with the MQTT protocol, see https://mqtt.org/
@ -19,253 +28,131 @@ class MqttPlugin(Plugin):
* **paho-mqtt** (``pip install paho-mqtt``) * **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__( def __init__(
self, self,
host=None, host: Optional[str] = None,
port=1883, port: int = 1883,
tls_cafile=None, topics: Optional[Iterable[str]] = None,
tls_certfile=None, tls_cafile: Optional[str] = None,
tls_keyfile=None, tls_certfile: Optional[str] = None,
tls_version=None, tls_keyfile: Optional[str] = None,
tls_ciphers=None, tls_version: Optional[str] = None,
tls_insecure=False, tls_ciphers: Optional[str] = None,
username=None, tls_insecure: bool = False,
password=None, username: Optional[str] = None,
client_id=None, password: Optional[str] = None,
timeout=None, client_id: Optional[str] = None,
timeout: Optional[int] = DEFAULT_TIMEOUT,
run_topic_prefix: Optional[str] = None,
listeners: Optional[Iterable[dict]] = None,
**kwargs, **kwargs,
): ):
""" """
:param host: If set, MQTT messages will by default routed to this host :param host: If set, MQTT messages will by default routed to this host
unless overridden in `send_message` (default: None) unless overridden in `send_message` (default: None)
:type host: str
:param port: If a default host is set, specify the listen port :param port: If a default host is set, specify the listen port
(default: 1883) (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, :param tls_cafile: If a default host is set and requires TLS/SSL,
specify the certificate authority file (default: None) specify the certificate authority file (default: None)
:type tls_cafile: str
:param tls_certfile: If a default host is set and requires TLS/SSL, :param tls_certfile: If a default host is set and requires TLS/SSL,
specify the certificate file (default: None) specify the certificate file (default: None)
:type tls_certfile: str
:param tls_keyfile: If a default host is set and requires TLS/SSL, :param tls_keyfile: If a default host is set and requires TLS/SSL,
specify the key file (default: None) specify the key file (default: None)
:type tls_keyfile: str
:param tls_version: If TLS/SSL is enabled on the MQTT server and it :param tls_version: If TLS/SSL is enabled on the MQTT server and it
requires a certain TLS version, specify it here (default: None). requires a certain TLS version, specify it here (default: None).
Supported versions: ``tls`` (automatic), ``tlsv1``, ``tlsv1.1``, Supported versions: ``tls`` (automatic), ``tlsv1``, ``tlsv1.1``,
``tlsv1.2``. ``tlsv1.2``.
:type tls_version: str
:param tls_ciphers: If a default host is set and requires TLS/SSL, :param tls_ciphers: If a default host is set and requires TLS/SSL,
specify the supported ciphers (default: None) specify the supported ciphers (default: None)
:type tls_ciphers: str
:param tls_insecure: Set to True to ignore TLS insecure warnings :param tls_insecure: Set to True to ignore TLS insecure warnings
(default: False). (default: False).
:type tls_insecure: bool
:param username: If a default host is set and requires user :param username: If a default host is set and requires user
authentication, specify the username ciphers (default: None) authentication, specify the username ciphers (default: None)
:type username: str
:param password: If a default host is set and requires user :param password: If a default host is set and requires user
authentication, specify the password ciphers (default: None) authentication, specify the password ciphers (default: None)
:type password: str
:param client_id: ID used to identify the client on the MQTT server :param client_id: ID used to identify the client on the MQTT server
(default: None). If None is specified then (default: None). If None is specified then
``Config.get('device_id')`` will be used. ``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) super().__init__(**kwargs)
self.host = host self.client_id = client_id or str(Config.get('device_id'))
self.port = port self.run_topic = (
self.username = username f'{run_topic_prefix}/{Config.get("device_id")}'
self.password = password if type(self) == MqttPlugin and run_topic_prefix
self.client_id = client_id or Config.get('device_id') else None
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._listeners_lock = defaultdict(threading.RLock)
self.tls_version = self.get_tls_version(tls_version) self.listeners: Dict[str, MqttClient] = {} # client_id -> MqttClient map
self.tls_insecure = tls_insecure
self.tls_ciphers = tls_ciphers
self.timeout = timeout self.timeout = timeout
self.default_listener = (
@staticmethod self._get_client(
def get_tls_version(version: Optional[str] = None): host=host,
import ssl port=port,
topics=(
if not version: (tuple(topics) if topics else ())
return None + ((self.run_topic,) if self.run_topic else ())
),
if isinstance(version, type(ssl.PROTOCOL_TLS)): on_message=self.on_mqtt_message(),
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(
tls_cafile=tls_cafile, tls_cafile=tls_cafile,
tls_certfile=tls_certfile, tls_certfile=tls_certfile,
tls_keyfile=tls_keyfile, tls_keyfile=tls_keyfile,
@ -274,11 +161,258 @@ class MqttPlugin(Plugin):
tls_insecure=tls_insecure, tls_insecure=tls_insecure,
username=username, username=username,
password=password, 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() 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: if reply_topic:
client.on_message = self._response_callback( client.on_message = self._response_callback(
reply_topic=reply_topic, reply_topic=reply_topic,
@ -289,12 +423,13 @@ class MqttPlugin(Plugin):
client.publish(topic, str(msg), qos=qos) client.publish(topic, str(msg), qos=qos)
if not reply_topic: if not reply_topic:
return return None
client.loop_start() client.loop_start()
ok = response_received.wait(timeout=timeout) ok = response_received.wait(timeout=client.timeout)
if not ok: if not ok:
raise TimeoutError('Response timed out') raise TimeoutError('Response timed out')
return response_buffer.getvalue() return response_buffer.getvalue()
finally: finally:
response_buffer.close() response_buffer.close()
@ -303,12 +438,50 @@ class MqttPlugin(Plugin):
try: try:
client.loop_stop() client.loop_stop()
except Exception as e: 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() 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 @staticmethod
def _response_callback(reply_topic: str, event: threading.Event, buffer: IO[bytes]): 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): def on_message(client, _, msg):
if msg.topic != reply_topic: if msg.topic != reply_topic:
return return
@ -322,9 +495,40 @@ class MqttPlugin(Plugin):
@action @action
def send_message(self, *args, **kwargs): 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) 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: # vim:sw=4:ts=4:et:

View 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:

View File

@ -1,5 +1,6 @@
manifest: manifest:
events: {} events:
- platypush.message.event.mqtt.MQTTMessageEvent
install: install:
apk: apk:
- py3-paho-mqtt - py3-paho-mqtt

File diff suppressed because it is too large Load Diff

View File

@ -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