Added SmartThings plugin [#148]

This commit is contained in:
Fabio Manganiello 2021-03-05 02:23:28 +01:00
parent 210cefc1a4
commit 4ada1c663d
25 changed files with 628 additions and 41 deletions

View file

@ -263,6 +263,8 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
'RPi.GPIO', 'RPi.GPIO',
'RPLCD', 'RPLCD',
'imapclient', 'imapclient',
'pysmartthings',
'aiohttp',
] ]
sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../..'))

View file

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

View file

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

View file

@ -27,6 +27,7 @@ Plugins
platypush/plugins/camera.pi.rst platypush/plugins/camera.pi.rst
platypush/plugins/chat.telegram.rst platypush/plugins/chat.telegram.rst
platypush/plugins/clipboard.rst platypush/plugins/clipboard.rst
platypush/plugins/config.rst
platypush/plugins/covid19.rst platypush/plugins/covid19.rst
platypush/plugins/csv.rst platypush/plugins/csv.rst
platypush/plugins/db.rst platypush/plugins/db.rst
@ -110,6 +111,7 @@ Plugins
platypush/plugins/sensor.rst platypush/plugins/sensor.rst
platypush/plugins/serial.rst platypush/plugins/serial.rst
platypush/plugins/shell.rst platypush/plugins/shell.rst
platypush/plugins/smartthings.rst
platypush/plugins/sound.rst platypush/plugins/sound.rst
platypush/plugins/ssh.rst platypush/plugins/ssh.rst
platypush/plugins/stt.rst platypush/plugins/stt.rst

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 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-31bc5041"],{6341:function(e,n,t){"use strict";t.r(n);t("b64b");var c=t("7a23"),r=Object(c["K"])("data-v-eac2ea44");Object(c["u"])("data-v-eac2ea44");var i={class:"switches-container"},s={class:"switch-plugins"},u={key:0,class:"no-content"},a={key:0,class:"refresh col-2"},o=Object(c["h"])("i",{class:"fa fa-sync"},null,-1),l={class:"refresh-button"},b=Object(c["h"])("i",{class:"fa fa-sync"},null,-1);Object(c["s"])();var d=r((function(e,n,t,r,d,h){var f=Object(c["z"])("Loading");return Object(c["r"])(),Object(c["e"])("div",i,[d.loading?(Object(c["r"])(),Object(c["e"])(f,{key:0})):Object(c["f"])("",!0),Object(c["h"])("div",s,[Object.keys(d.plugins).length?Object(c["f"])("",!0):(Object(c["r"])(),Object(c["e"])("div",u,"No switch plugins configured")),(Object(c["r"])(!0),Object(c["e"])(c["a"],null,Object(c["x"])(Object.keys(d.plugins),(function(e){return Object(c["r"])(),Object(c["e"])("div",{class:"switch-plugin",key:e,onClick:function(n){return d.selectedPlugin=d.selectedPlugin===e?null:e}},[Object(c["h"])("div",{class:["header",{selected:d.selectedPlugin===e}]},[Object(c["h"])("div",{class:"name col-10",textContent:Object(c["C"])(e)},null,8,["textContent"]),d.selectedPlugin===e?(Object(c["r"])(),Object(c["e"])("div",a,[Object(c["h"])("button",{onClick:Object(c["J"])((function(n){return d.bus.emit("refresh",e)}),["stop"]),title:"Refresh plugin",disabled:d.loading},[o],8,["onClick","disabled"])])):Object(c["f"])("",!0)],2),Object(c["h"])("div",{class:["body",{hidden:d.selectedPlugin!==e}]},[(Object(c["r"])(),Object(c["e"])(Object(c["A"])(d.components[e]),{config:d.plugins[e],"plugin-name":e,selected:d.selectedPlugin===e,bus:d.bus},null,8,["config","plugin-name","selected","bus"]))],2)],8,["onClick"])})),128))]),Object(c["h"])("div",l,[Object(c["h"])("button",{onClick:n[1]||(n[1]=function(){return h.refresh.apply(h,arguments)}),disabled:d.loading,title:"Refresh plugins"},[b],8,["disabled"])])])})),h=(t("4160"),t("a15b"),t("d81d"),t("fb6a"),t("d3b7"),t("ac1f"),t("1276"),t("159b"),t("96cf"),t("1da1")),f=t("3a5e"),p=t("3e54"),O=t("14b7"),j={name:"Switches",components:{Loading:f["a"]},mixins:[p["a"]],data:function(){return{loading:!1,plugins:{},components:{},selectedPlugin:null,bus:Object(O["a"])()}},methods:{initPanels:function(){var e=this;this.components={},Object.keys(this.plugins).forEach(function(){var n=Object(h["a"])(regeneratorRuntime.mark((function n(r){var i,s,u;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return i=r.split(".").map((function(e){return e[0].toUpperCase()+e.slice(1)})).join(""),s=null,n.prev=2,n.next=5,t("c1da")("./".concat(i,"/Index"));case 5:s=n.sent,n.next=11;break;case 8:return n.prev=8,n.t0=n["catch"](2),n.abrupt("return");case 11:u=Object(c["i"])(Object(h["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.abrupt("return",s);case 1:case"end":return e.stop()}}),e)})))),e.$options.components[r]=u,e.components[r]=u;case 14:case"end":return n.stop()}}),n,null,[[2,8]])})));return function(e){return n.apply(this,arguments)}}())},refresh:function(){var e=this;return Object(h["a"])(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return e.loading=!0,n.prev=1,n.next=4,e.request("utils.get_switch_plugins");case 4:e.plugins=n.sent,e.initPanels();case 6:return n.prev=6,e.loading=!1,n.finish(6);case 9:case"end":return n.stop()}}),n,null,[[1,,6,9]])})))()}},mounted:function(){this.refresh()}};t("84aa");j.render=d,j.__scopeId="data-v-eac2ea44";n["default"]=j},"7ac9":function(e,n,t){},"84aa":function(e,n,t){"use strict";t("7ac9")},c1da:function(e,n,t){var c={"./LightHue/Index":["0219","chunk-9f884670","chunk-4b03f49b","chunk-e017dc3e"],"./SwitchSwitchbot/Index":["5083","chunk-9f884670","chunk-4b03f49b","chunk-0021f7ee"],"./SwitchTplink/Index":["d11f","chunk-9f884670","chunk-4b03f49b","chunk-c4aee99e"],"./SwitchWemo/Index":["bedd","chunk-9f884670","chunk-4b03f49b","chunk-60dbbc82"],"./ZigbeeMqtt/Index":["65d6","chunk-9f884670","chunk-4b03f49b","chunk-69041365"]};function r(e){if(!t.o(c,e))return Promise.resolve().then((function(){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}));var n=c[e],r=n[0];return Promise.all(n.slice(1).map(t.e)).then((function(){return t(r)}))}r.keys=function(){return Object.keys(c)},r.id="c1da",e.exports=r}}]); (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-31bc5041"],{6341:function(e,n,t){"use strict";t.r(n);t("b64b");var c=t("7a23"),r=Object(c["K"])("data-v-eac2ea44");Object(c["u"])("data-v-eac2ea44");var i={class:"switches-container"},s={class:"switch-plugins"},u={key:0,class:"no-content"},a={key:0,class:"refresh col-2"},o=Object(c["h"])("i",{class:"fa fa-sync"},null,-1),l={class:"refresh-button"},b=Object(c["h"])("i",{class:"fa fa-sync"},null,-1);Object(c["s"])();var d=r((function(e,n,t,r,d,h){var f=Object(c["z"])("Loading");return Object(c["r"])(),Object(c["e"])("div",i,[d.loading?(Object(c["r"])(),Object(c["e"])(f,{key:0})):Object(c["f"])("",!0),Object(c["h"])("div",s,[Object.keys(d.plugins).length?Object(c["f"])("",!0):(Object(c["r"])(),Object(c["e"])("div",u,"No switch plugins configured")),(Object(c["r"])(!0),Object(c["e"])(c["a"],null,Object(c["x"])(Object.keys(d.plugins),(function(e){return Object(c["r"])(),Object(c["e"])("div",{class:"switch-plugin",key:e,onClick:function(n){return d.selectedPlugin=d.selectedPlugin===e?null:e}},[Object(c["h"])("div",{class:["header",{selected:d.selectedPlugin===e}]},[Object(c["h"])("div",{class:"name col-10",textContent:Object(c["C"])(e)},null,8,["textContent"]),d.selectedPlugin===e?(Object(c["r"])(),Object(c["e"])("div",a,[Object(c["h"])("button",{onClick:Object(c["J"])((function(n){return d.bus.emit("refresh",e)}),["stop"]),title:"Refresh plugin",disabled:d.loading},[o],8,["onClick","disabled"])])):Object(c["f"])("",!0)],2),Object(c["h"])("div",{class:["body",{hidden:d.selectedPlugin!==e}]},[(Object(c["r"])(),Object(c["e"])(Object(c["A"])(d.components[e]),{config:d.plugins[e],"plugin-name":e,selected:d.selectedPlugin===e,bus:d.bus},null,8,["config","plugin-name","selected","bus"]))],2)],8,["onClick"])})),128))]),Object(c["h"])("div",l,[Object(c["h"])("button",{onClick:n[1]||(n[1]=function(){return h.refresh.apply(h,arguments)}),disabled:d.loading,title:"Refresh plugins"},[b],8,["disabled"])])])})),h=(t("4160"),t("a15b"),t("d81d"),t("fb6a"),t("d3b7"),t("ac1f"),t("1276"),t("159b"),t("96cf"),t("1da1")),f=t("3a5e"),p=t("3e54"),O=t("14b7"),j={name:"Switches",components:{Loading:f["a"]},mixins:[p["a"]],data:function(){return{loading:!1,plugins:{},components:{},selectedPlugin:null,bus:Object(O["a"])()}},methods:{initPanels:function(){var e=this;this.components={},Object.keys(this.plugins).forEach(function(){var n=Object(h["a"])(regeneratorRuntime.mark((function n(r){var i,s,u;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return i=r.split(".").map((function(e){return e[0].toUpperCase()+e.slice(1)})).join(""),s=null,n.prev=2,n.next=5,t("c1da")("./".concat(i,"/Index"));case 5:s=n.sent,n.next=11;break;case 8:return n.prev=8,n.t0=n["catch"](2),n.abrupt("return");case 11:u=Object(c["i"])(Object(h["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.abrupt("return",s);case 1:case"end":return e.stop()}}),e)})))),e.$options.components[r]=u,e.components[r]=u;case 14:case"end":return n.stop()}}),n,null,[[2,8]])})));return function(e){return n.apply(this,arguments)}}())},refresh:function(){var e=this;return Object(h["a"])(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return e.loading=!0,n.prev=1,n.next=4,e.request("utils.get_switch_plugins");case 4:e.plugins=n.sent,e.initPanels();case 6:return n.prev=6,e.loading=!1,n.finish(6);case 9:case"end":return n.stop()}}),n,null,[[1,,6,9]])})))()}},mounted:function(){this.refresh()}};t("84aa");j.render=d,j.__scopeId="data-v-eac2ea44";n["default"]=j},"7ac9":function(e,n,t){},"84aa":function(e,n,t){"use strict";t("7ac9")},c1da:function(e,n,t){var c={"./LightHue/Index":["0219","chunk-9f884670","chunk-4b03f49b","chunk-e017dc3e"],"./SwitchSwitchbot/Index":["5083","chunk-9f884670","chunk-4b03f49b","chunk-0021f7ee"],"./SwitchTplink/Index":["d11f","chunk-9f884670","chunk-4b03f49b","chunk-c4aee99e"],"./SwitchWemo/Index":["bedd","chunk-9f884670","chunk-4b03f49b","chunk-60dbbc82"],"./ZigbeeMqtt/Index":["65d6","chunk-9f884670","chunk-4b03f49b","chunk-5ddcb52e"]};function r(e){if(!t.o(c,e))return Promise.resolve().then((function(){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}));var n=c[e],r=n[0];return Promise.all(n.slice(1).map(t.e)).then((function(){return t(r)}))}r.keys=function(){return Object.keys(c)},r.id="c1da",e.exports=r}}]);
//# sourceMappingURL=chunk-31bc5041.9b5b2506.js.map //# sourceMappingURL=chunk-31bc5041.33b2d65a.js.map

View file

@ -1,2 +1,2 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4b03f49b"],{"17dc":function(e,t,n){"use strict";n("b0c0");var i=n("7a23"),o=Object(i["K"])("data-v-755a3c5f");Object(i["u"])("data-v-755a3c5f");var a={class:"name col-l-10 col-m-9 col-s-8"},c=Object(i["h"])("i",{class:"fa fa-info"},null,-1),r={class:"toggler col-l-2 col-m-3 col-s-4"};Object(i["s"])();var s=o((function(e,t,n,o,s,u){var l=Object(i["z"])("Loading"),f=Object(i["z"])("ToggleSwitch");return Object(i["r"])(),Object(i["e"])("div",{class:"switch",onClick:t[2]||(t[2]=Object(i["J"])((function(){return u.onToggle.apply(u,arguments)}),["stop"]))},[n.loading?(Object(i["r"])(),Object(i["e"])(l,{key:0})):Object(i["f"])("",!0),Object(i["h"])("div",a,[n.hasInfo?(Object(i["r"])(),Object(i["e"])("button",{key:0,onClick:t[1]||(t[1]=Object(i["J"])((function(){return u.onInfo.apply(u,arguments)}),["prevent"]))},[c])):Object(i["f"])("",!0),Object(i["h"])("span",{class:"name-content",textContent:Object(i["C"])(n.name)},null,8,["textContent"])]),Object(i["h"])("div",r,[Object(i["h"])(f,{disabled:n.loading,value:n.state,onInput:u.onToggle},null,8,["disabled","value","onInput"])])])})),u=n("0279"),l=n("3a5e"),f={name:"Switch",components:{Loading:l["a"],ToggleSwitch:u["a"]},emits:["toggle","info"],props:{name:{type:String,required:!0},state:{type:Boolean,default:!1},loading:{type:Boolean,default:!1},hasInfo:{type:Boolean,default:!1}},methods:{onInfo:function(e){return e.stopPropagation(),this.$emit("info"),!1},onToggle:function(e){return e.stopPropagation(),this.$emit("toggle"),!1}}};n("21ae");f.render=s,f.__scopeId="data-v-755a3c5f";t["a"]=f},"21ae":function(e,t,n){"use strict";n("3386")},3386:function(e,t,n){},"487b":function(e,t,n){"use strict";n("13d5"),n("b0c0"),n("96cf");var i=n("1da1"),o=n("3e54"),a={name:"SwitchesMixin",mixins:[o["a"]],props:{pluginName:{type:String,required:!0},bus:{type:Object,required:!0},config:{type:Object,default:function(){return{}}},selected:{type:Boolean,default:!1}},data:function(){return{loading:!1,initialized:!1,selectedDevice:null,devices:{}}},methods:{onRefreshEvent:function(e){e===this.pluginName&&this.refresh()},toggle:function(e){var t=this;return Object(i["a"])(regeneratorRuntime.mark((function n(){var i;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return n.next=2,t.request("".concat(t.pluginName,".toggle"),{device:e});case 2:i=n.sent,t.devices[e].on=i.on;case 4:case"end":return n.stop()}}),n)})))()},refresh:function(){var e=this;return Object(i["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return e.loading=!0,t.prev=1,t.next=4,e.request("".concat(e.pluginName,".status"));case 4:e.devices=t.sent.reduce((function(e,t){var n,i=(null===(n=t.name)||void 0===n?void 0:n.length)?t.name:t.id;return e[i]=t,e}),{});case 5:return t.prev=5,e.loading=!1,t.finish(5);case 8:case"end":return t.stop()}}),t,null,[[1,,5,8]])})))()}},mounted:function(){var e=this;this.$watch((function(){return e.selected}),(function(t){t&&!e.initialized&&(e.refresh(),e.initialized=!0)})),this.bus.on("refresh",this.onRefreshEvent)},unmounted:function(){this.bus.off("refresh",this.onRefreshEvent)}};t["a"]=a}}]); (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4b03f49b"],{"17dc":function(e,t,n){"use strict";n("b0c0");var i=n("7a23"),o=Object(i["K"])("data-v-755a3c5f");Object(i["u"])("data-v-755a3c5f");var a={class:"name col-l-10 col-m-9 col-s-8"},c=Object(i["h"])("i",{class:"fa fa-info"},null,-1),r={class:"toggler col-l-2 col-m-3 col-s-4"};Object(i["s"])();var s=o((function(e,t,n,o,s,u){var l=Object(i["z"])("Loading"),f=Object(i["z"])("ToggleSwitch");return Object(i["r"])(),Object(i["e"])("div",{class:"switch",onClick:t[2]||(t[2]=Object(i["J"])((function(){return u.onToggle.apply(u,arguments)}),["stop"]))},[n.loading?(Object(i["r"])(),Object(i["e"])(l,{key:0})):Object(i["f"])("",!0),Object(i["h"])("div",a,[n.hasInfo?(Object(i["r"])(),Object(i["e"])("button",{key:0,onClick:t[1]||(t[1]=Object(i["J"])((function(){return u.onInfo.apply(u,arguments)}),["prevent"]))},[c])):Object(i["f"])("",!0),Object(i["h"])("span",{class:"name-content",textContent:Object(i["C"])(n.name)},null,8,["textContent"])]),Object(i["h"])("div",r,[Object(i["h"])(f,{disabled:n.loading,value:n.state,onInput:u.onToggle},null,8,["disabled","value","onInput"])])])})),u=n("0279"),l=n("3a5e"),f={name:"Switch",components:{Loading:l["a"],ToggleSwitch:u["a"]},emits:["toggle","info"],props:{name:{type:String,required:!0},state:{type:Boolean,default:!1},loading:{type:Boolean,default:!1},hasInfo:{type:Boolean,default:!1}},methods:{onInfo:function(e){return e.stopPropagation(),this.$emit("info"),!1},onToggle:function(e){return e.stopPropagation(),this.$emit("toggle"),!1}}};n("21ae");f.render=s,f.__scopeId="data-v-755a3c5f";t["a"]=f},"21ae":function(e,t,n){"use strict";n("3386")},3386:function(e,t,n){},"487b":function(e,t,n){"use strict";n("13d5"),n("b0c0"),n("96cf");var i=n("1da1"),o=n("3e54"),a={name:"SwitchesMixin",mixins:[o["a"]],props:{pluginName:{type:String,required:!0},bus:{type:Object,required:!0},config:{type:Object,default:function(){return{}}},selected:{type:Boolean,default:!1}},data:function(){return{loading:!1,initialized:!1,selectedDevice:null,devices:{}}},methods:{onRefreshEvent:function(e){e===this.pluginName&&this.refresh()},toggle:function(e){var t=this;return Object(i["a"])(regeneratorRuntime.mark((function n(){var i;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return n.next=2,t.request("".concat(t.pluginName,".toggle"),{device:e});case 2:i=n.sent,t.devices[e].on=i.on;case 4:case"end":return n.stop()}}),n)})))()},refresh:function(){var e=this;return Object(i["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return e.loading=!0,t.prev=1,t.next=4,e.request("".concat(e.pluginName,".switch_status"));case 4:e.devices=t.sent.reduce((function(e,t){var n,i=(null===(n=t.name)||void 0===n?void 0:n.length)?t.name:t.id;return e[i]=t,e}),{});case 5:return t.prev=5,e.loading=!1,t.finish(5);case 8:case"end":return t.stop()}}),t,null,[[1,,5,8]])})))()}},mounted:function(){var e=this;this.$watch((function(){return e.selected}),(function(t){t&&!e.initialized&&(e.refresh(),e.initialized=!0)})),this.bus.on("refresh",this.onRefreshEvent)},unmounted:function(){this.bus.off("refresh",this.onRefreshEvent)}};t["a"]=a}}]);
//# sourceMappingURL=chunk-4b03f49b.528a6888.js.map //# sourceMappingURL=chunk-4b03f49b.2f384634.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5ddcb52e"],{"65d6":function(e,t,c){"use strict";c.r(t);c("b64b");var n=c("7a23"),o=Object(n["K"])("data-v-b96c79fe");Object(n["u"])("data-v-b96c79fe");var a={class:"switches zigbee-mqtt-switches"},i={key:1,class:"no-content"};Object(n["s"])();var s=o((function(e,t,c,o,s,b){var d=Object(n["z"])("Loading"),r=Object(n["z"])("Switch");return Object(n["r"])(),Object(n["e"])("div",a,[e.loading?(Object(n["r"])(),Object(n["e"])(d,{key:0})):Object.keys(e.devices).length?Object(n["f"])("",!0):(Object(n["r"])(),Object(n["e"])("div",i,"No Zigbee switches found.")),(Object(n["r"])(!0),Object(n["e"])(n["a"],null,Object(n["x"])(e.devices,(function(t,c){return Object(n["r"])(),Object(n["e"])(r,{loading:e.loading,name:c,state:t.on,onToggle:function(t){return e.toggle(c)},key:c,"has-info":!0,onInfo:function(t){e.selectedDevice=c,e.$refs.switchInfoModal.show()}},null,8,["loading","name","state","onToggle","onInfo"])})),128))])})),b=c("3a5e"),d=c("487b"),r=c("17dc"),u={name:"ZigbeeMqtt",components:{Switch:r["a"],Loading:b["a"]},mixins:[d["a"]]};c("c510");u.render=s,u.__scopeId="data-v-b96c79fe";t["default"]=u},a1d6:function(e,t,c){},c510:function(e,t,c){"use strict";c("a1d6")}}]);
//# sourceMappingURL=chunk-5ddcb52e.c1af216d.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue","webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue?384f","webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue?f1a7"],"names":["class","loading","Object","keys","devices","length","device","name","state","on","toggle","key","has-info","selectedDevice","$refs","switchInfoModal","show","components","Switch","Loading","mixins","render","__scopeId"],"mappings":"gOACOA,MAAM,iC,SAEJA,MAAM,c,mIAFb,eAOM,MAPN,EAOM,CANW,EAAAC,S,iBAAf,eAA0B,YACUC,OAAOC,KAAK,EAAAC,SAASC,O,wCAAzD,eAAgG,MAAhG,EAAiE,+B,mBAEjE,eAEsE,2BADrC,EAAAD,SAAO,SAAxBE,EAAQC,G,wBADxB,eAEsE,GAF7DN,QAAS,EAAAA,QAAUM,KAAMA,EAAOC,MAAOF,EAAOG,GAAK,SAAM,mBAAE,EAAAC,OAAOH,IAChCI,IAAKJ,EAAOK,YAAU,EACxD,OAAI,YAAE,EAAAC,eAAiBN,EAAM,EAAAO,MAAMC,gBAAgBC,S,yGASjD,GACbT,KAAM,aACNU,WAAY,CAACC,SAAA,KAAQC,UAAA,MACrBC,OAAQ,CAAC,S,UCdX,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ,gB,yDCRf","file":"static/js/chunk-5ddcb52e.c1af216d.js","sourcesContent":["<template>\n <div class=\"switches zigbee-mqtt-switches\">\n <Loading v-if=\"loading\" />\n <div class=\"no-content\" v-else-if=\"!Object.keys(devices).length\">No Zigbee switches found.</div>\n\n <Switch :loading=\"loading\" :name=\"name\" :state=\"device.on\" @toggle=\"toggle(name)\"\n v-for=\"(device, name) in devices\" :key=\"name\" :has-info=\"true\"\n @info=\"selectedDevice = name; $refs.switchInfoModal.show()\" />\n </div>\n</template>\n\n<script>\nimport Loading from \"@/components/Loading\";\nimport SwitchMixin from \"@/components/panels/Switches/Mixin\";\nimport Switch from \"@/components/panels/Switches/Switch\";\n\nexport default {\n name: \"ZigbeeMqtt\",\n components: {Switch, Loading},\n mixins: [SwitchMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../common\";\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=b96c79fe&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\n\nimport \"./Index.vue?vue&type=style&index=0&id=b96c79fe&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-b96c79fe\"\n\nexport default script","export * from \"-!../../../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../../../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../../../../node_modules/vue-loader-v16/dist/stylePostLoader.js!../../../../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../../../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../../node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Index.vue?vue&type=style&index=0&id=b96c79fe&lang=scss&scoped=true\""],"sourceRoot":""}

View file

@ -1,2 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-69041365"],{"65d6":function(e,t,c){"use strict";c.r(t);c("b64b");var n=c("7a23"),o=Object(n["K"])("data-v-616a9486");Object(n["u"])("data-v-616a9486");var a={class:"switches zigbee-mqtt-switches"},i={key:1,class:"no-content"},s={key:0,class:"switch-info"};Object(n["s"])();var b=o((function(e,t,c,b,f,d){var r=Object(n["z"])("Loading"),l=Object(n["z"])("Switch"),j=Object(n["z"])("Modal");return Object(n["r"])(),Object(n["e"])("div",a,[e.loading?(Object(n["r"])(),Object(n["e"])(r,{key:0})):Object.keys(e.devices).length?Object(n["f"])("",!0):(Object(n["r"])(),Object(n["e"])("div",i,"No Zigbee switches found.")),(Object(n["r"])(!0),Object(n["e"])(n["a"],null,Object(n["x"])(e.devices,(function(t,c){return Object(n["r"])(),Object(n["e"])(l,{loading:e.loading,name:c,state:t.on,onToggle:function(t){return e.toggle(c)},key:c,"has-info":!0,onInfo:function(t){e.selectedDevice=c,e.$refs.switchInfoModal.show()}},null,8,["loading","name","state","onToggle","onInfo"])})),128)),Object(n["h"])(j,{title:"Device Info",ref:"switchInfoModal"},{default:o((function(){return[e.selectedDevice?(Object(n["r"])(),Object(n["e"])("div",s)):Object(n["f"])("",!0)]})),_:1},512)])})),f=c("3a5e"),d=c("487b"),r=c("17dc"),l=c("714b"),j={name:"ZigbeeMqtt",components:{Modal:l["a"],Switch:r["a"],Loading:f["a"]},mixins:[d["a"]]};c("7eff");j.render=b,j.__scopeId="data-v-616a9486";t["default"]=j},"7eff":function(e,t,c){"use strict";c("7ff2")},"7ff2":function(e,t,c){}}]);
//# sourceMappingURL=chunk-69041365.2f7b07e4.js.map

View file

@ -1 +0,0 @@
{"version":3,"sources":["webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue","webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue?384f","webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue?842c"],"names":["class","loading","Object","keys","devices","length","device","name","state","on","toggle","key","has-info","selectedDevice","$refs","switchInfoModal","show","title","ref","components","Modal","Switch","Loading","mixins","render","__scopeId"],"mappings":"gOACOA,MAAM,iC,SAEJA,MAAM,c,SAOJA,MAAM,e,6JATf,eA0BM,MA1BN,EA0BM,CAzBW,EAAAC,S,iBAAf,eAA0B,YACUC,OAAOC,KAAK,EAAAC,SAASC,O,wCAAzD,eAAgG,MAAhG,EAAiE,+B,mBAEjE,eAEsE,2BADrC,EAAAD,SAAO,SAAxBE,EAAQC,G,wBADxB,eAEsE,GAF7DN,QAAS,EAAAA,QAAUM,KAAMA,EAAOC,MAAOF,EAAOG,GAAK,SAAM,mBAAE,EAAAC,OAAOH,IAChCI,IAAKJ,EAAOK,YAAU,EACxD,OAAI,YAAE,EAAAC,eAAiBN,EAAM,EAAAO,MAAMC,gBAAgBC,S,gEAE5D,eAiBQ,GAjBDC,MAAM,cAAcC,IAAI,mB,YAC7B,iBAeM,CAfyB,EAAAL,gB,iBAA/B,eAeM,MAfN,I,yFA0BS,GACbN,KAAM,aACNY,WAAY,CAACC,QAAA,KAAOC,SAAA,KAAQC,UAAA,MAC5BC,OAAQ,CAAC,S,UClCX,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ,gB,oCCRf,W","file":"static/js/chunk-69041365.2f7b07e4.js","sourcesContent":["<template>\n <div class=\"switches zigbee-mqtt-switches\">\n <Loading v-if=\"loading\" />\n <div class=\"no-content\" v-else-if=\"!Object.keys(devices).length\">No Zigbee switches found.</div>\n\n <Switch :loading=\"loading\" :name=\"name\" :state=\"device.on\" @toggle=\"toggle(name)\"\n v-for=\"(device, name) in devices\" :key=\"name\" :has-info=\"true\"\n @info=\"selectedDevice = name; $refs.switchInfoModal.show()\" />\n\n <Modal title=\"Device Info\" ref=\"switchInfoModal\">\n <div class=\"switch-info\" v-if=\"selectedDevice\">\n<!-- <div class=\"row\">-->\n<!-- <div class=\"name\">Name</div>-->\n<!-- <div class=\"value\" v-text=\"devices[selectedDevice].name\" />-->\n<!-- </div>-->\n\n<!-- <div class=\"row\">-->\n<!-- <div class=\"name\">On</div>-->\n<!-- <div class=\"value\" v-text=\"devices[selectedDevice].on\" />-->\n<!-- </div>-->\n\n<!-- <div class=\"row\">-->\n<!-- <div class=\"name\">Address</div>-->\n<!-- <div class=\"value\" v-text=\"devices[selectedDevice].address\" />-->\n<!-- </div>-->\n </div>\n </Modal>\n </div>\n</template>\n\n<script>\nimport Loading from \"@/components/Loading\";\nimport SwitchMixin from \"@/components/panels/Switches/Mixin\";\nimport Switch from \"@/components/panels/Switches/Switch\";\nimport Modal from \"@/components/Modal\";\n\nexport default {\n name: \"ZigbeeMqtt\",\n components: {Modal, Switch, Loading},\n mixins: [SwitchMixin],\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../common\";\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=616a9486&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\n\nimport \"./Index.vue?vue&type=style&index=0&id=616a9486&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-616a9486\"\n\nexport default script","export * from \"-!../../../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../../../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../../../../node_modules/vue-loader-v16/dist/stylePostLoader.js!../../../../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../../../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../../node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Index.vue?vue&type=style&index=0&id=616a9486&lang=scss&scoped=true\""],"sourceRoot":""}

View file

@ -52,7 +52,7 @@ export default {
async refresh() { async refresh() {
this.loading = true this.loading = true
try { try {
this.devices = (await this.request(`${this.pluginName}.status`)).reduce((obj, device) => { this.devices = (await this.request(`${this.pluginName}.switch_status`)).reduce((obj, device) => {
const name = device.name?.length ? device.name : device.id const name = device.name?.length ? device.name : device.id
obj[name] = device obj[name] = device
return obj return obj

View file

@ -6,25 +6,6 @@
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)" <Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
v-for="(device, name) in devices" :key="name" :has-info="true" v-for="(device, name) in devices" :key="name" :has-info="true"
@info="selectedDevice = name; $refs.switchInfoModal.show()" /> @info="selectedDevice = name; $refs.switchInfoModal.show()" />
<Modal title="Device Info" ref="switchInfoModal">
<div class="switch-info" v-if="selectedDevice">
<!-- <div class="row">-->
<!-- <div class="name">Name</div>-->
<!-- <div class="value" v-text="devices[selectedDevice].name" />-->
<!-- </div>-->
<!-- <div class="row">-->
<!-- <div class="name">On</div>-->
<!-- <div class="value" v-text="devices[selectedDevice].on" />-->
<!-- </div>-->
<!-- <div class="row">-->
<!-- <div class="name">Address</div>-->
<!-- <div class="value" v-text="devices[selectedDevice].address" />-->
<!-- </div>-->
</div>
</Modal>
</div> </div>
</template> </template>
@ -32,11 +13,10 @@
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import SwitchMixin from "@/components/panels/Switches/Mixin"; import SwitchMixin from "@/components/panels/Switches/Mixin";
import Switch from "@/components/panels/Switches/Switch"; import Switch from "@/components/panels/Switches/Switch";
import Modal from "@/components/Modal";
export default { export default {
name: "ZigbeeMqtt", name: "ZigbeeMqtt",
components: {Modal, Switch, Loading}, components: {Switch, Loading},
mixins: [SwitchMixin], mixins: [SwitchMixin],
} }
</script> </script>

View file

@ -0,0 +1,572 @@
import asyncio
import aiohttp
from threading import RLock
from typing import Optional, Dict, List, Union
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
class SmartthingsPlugin(SwitchPlugin):
"""
Plugin to interact with devices and locations registered to a Samsung SmartThings account.
Requires:
* **pysmartthings** (``pip install pysmartthings``)
"""
_timeout = aiohttp.ClientTimeout(total=20.)
def __init__(self, access_token: str, **kwargs):
"""
:param access_token: SmartThings API access token - you can get one at https://account.smartthings.com/tokens.
"""
super().__init__(**kwargs)
self._access_token = access_token
self._refresh_lock = RLock()
self._execute_lock = RLock()
self._locations = []
self._devices = []
self._rooms_by_location = {}
self._locations_by_id = {}
self._locations_by_name = {}
self._devices_by_id = {}
self._devices_by_name = {}
self._rooms_by_id = {}
self._rooms_by_location_and_id = {}
self._rooms_by_location_and_name = {}
async def _refresh_locations(self, api):
self._locations = await api.locations()
self._locations_by_id = {
loc.location_id: loc
for loc in self._locations
}
self._locations_by_name = {
loc.name: loc
for loc in self._locations
}
async def _refresh_devices(self, api):
self._devices = await api.devices()
self._devices_by_id = {
dev.device_id: dev
for dev in self._devices
}
self._devices_by_name = {
dev.label: dev
for dev in self._devices
}
async def _refresh_rooms(self, api, location_id: str):
self._rooms_by_location[location_id] = await api.rooms(location_id=location_id)
self._rooms_by_id.update(**{
room.room_id: room
for room in self._rooms_by_location[location_id]
})
self._rooms_by_location_and_id[location_id] = {
room.room_id: room
for room in self._rooms_by_location[location_id]
}
self._rooms_by_location_and_name[location_id] = {
room.name: room
for room in self._rooms_by_location[location_id]
}
async def _refresh_info(self):
import pysmartthings
async with aiohttp.ClientSession(timeout=self._timeout) as session:
api = pysmartthings.SmartThings(session, self._access_token)
tasks = [
asyncio.ensure_future(self._refresh_locations(api)),
asyncio.ensure_future(self._refresh_devices(api)),
]
await asyncio.gather(*tasks)
room_tasks = [
asyncio.ensure_future(self._refresh_rooms(api, location.location_id))
for location in self._locations
]
await asyncio.gather(*room_tasks)
def refresh_info(self):
with self._refresh_lock:
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
loop.run_until_complete(self._refresh_info())
finally:
loop.stop()
def _location_to_dict(self, location) -> Dict:
return {
'name': location.name,
'location_id': location.location_id,
'country_code': location.country_code,
'locale': location.locale,
'latitude': location.latitude,
'longitude': location.longitude,
'temperature_scale': location.temperature_scale,
'region_radius': location.region_radius,
'timezone_id': location.timezone_id,
'rooms': {
room.room_id: self._room_to_dict(room)
for room in self._rooms_by_location.get(location.location_id, {})
}
}
@staticmethod
def _device_to_dict(device) -> Dict:
return {
'capabilities': device.capabilities,
'name': device.label,
'device_id': device.device_id,
'location_id': device.location_id,
'room_id': device.room_id,
'device_type_id': device.device_type_id,
'device_type_name': device.device_type_name,
'device_type_network': device.device_type_network,
}
@staticmethod
def _room_to_dict(room) -> Dict:
return {
'name': room.name,
'background_image': room.background_image,
'room_id': room.room_id,
'location_id': room.location_id,
}
@action
def info(self) -> Dict[str, Dict[str, dict]]:
"""
Return the objects registered to the account, including locations and devices.
.. code-block:: json
{
"devices": {
"smart-tv-id": {
"capabilities": [
"ocf",
"switch",
"audioVolume",
"audioMute",
"tvChannel",
"mediaInputSource",
"mediaPlayback",
"mediaTrackControl",
"custom.error",
"custom.picturemode",
"custom.soundmode",
"custom.accessibility",
"custom.launchapp",
"custom.recording",
"custom.tvsearch",
"custom.disabledCapabilities",
"samsungvd.ambient",
"samsungvd.ambientContent",
"samsungvd.ambient18",
"samsungvd.mediaInputSource",
"refresh",
"execute",
"samsungvd.firmwareVersion",
"samsungvd.supportsPowerOnByOcf"
],
"device_id": "smart-tv-id",
"device_type_id": null,
"device_type_name": null,
"device_type_network": null,
"location_id": "location-id",
"name": "Samsung Smart TV",
"room_id": "room-1"
},
"tv-switch-id": {
"capabilities": [
"switch",
"refresh",
"healthCheck"
],
"device_id": "tv-switch-id",
"device_type_id": null,
"device_type_name": null,
"device_type_network": null,
"location_id": "location-id",
"name": "TV Smart Switch",
"room_id": "room-1"
},
"lights-switch-id": {
"capabilities": [
"switch",
"refresh",
"healthCheck"
],
"device_id": "lights-switch-id",
"device_type_id": null,
"device_type_name": null,
"device_type_network": null,
"location_id": "location-id",
"name": "Lights Switch",
"room_id": "room-2"
}
},
"locations": {
"location-id": {
"name": "My home",
"location_id": "location-id",
"country_code": "us",
"locale": "en-US",
"latitude": "latitude",
"longitude": "longitude",
"temperature_scale": null,
"region_radius": null,
"timezone_id": null,
"rooms": {
"room-1": {
"background_image": null,
"location_id": "location-1",
"name": "Living Room",
"room_id": "room-1"
},
"room-2": {
"background_image": null,
"location_id": "location-1",
"name": "Bedroom",
"room_id": "room-2"
}
}
}
}
}
"""
self.refresh_info()
return {
'locations': {loc.location_id: self._location_to_dict(loc) for loc in self._locations},
'devices': {dev.device_id: self._device_to_dict(dev) for dev in self._devices},
}
@action
def get_location(self, location_id: Optional[str] = None, name: Optional[str] = None) -> dict:
"""
Get the info of a location by ID or name.
.. code-block:: json
{
"name": "My home",
"location_id": "location-id",
"country_code": "us",
"locale": "en-US",
"latitude": "latitude",
"longitude": "longitude",
"temperature_scale": null,
"region_radius": null,
"timezone_id": null,
"rooms": {
"room-1": {
"background_image": null,
"location_id": "location-1",
"name": "Living Room",
"room_id": "room-1"
},
"room-2": {
"background_image": null,
"location_id": "location-1",
"name": "Bedroom",
"room_id": "room-2"
}
}
}
"""
assert location_id or name, 'Specify either location_id or name'
if location_id not in self._locations_by_id or name not in self._locations_by_name:
self.refresh_info()
location = self._locations_by_id.get(location_id, self._locations_by_name.get(name))
assert location, 'Location {} not found'.format(location_id or name)
return self._location_to_dict(location)
def _get_device(self, device: str):
if device not in self._devices_by_id or device not in self._devices_by_name:
self.refresh_info()
device = self._devices_by_id.get(device, self._devices_by_name.get(device))
assert device, 'Device {} not found'.format(device)
return device
@action
def get_device(self, device: str) -> dict:
"""
Get a device info by ID or name.
:param device: Device ID or name.
:return:
.. code-block:: json
"tv-switch-id": {
"capabilities": [
"switch",
"refresh",
"healthCheck"
],
"device_id": "tv-switch-id",
"device_type_id": null,
"device_type_name": null,
"device_type_network": null,
"location_id": "location-id",
"name": "TV Smart Switch",
"room_id": "room-1"
}
"""
device = self._get_device(device)
return self._device_to_dict(device)
async def _execute(self, device_id: str, capability: str, command, component_id: str, args: Optional[list]):
import pysmartthings
async with aiohttp.ClientSession(timeout=self._timeout) as session:
api = pysmartthings.SmartThings(session, self._access_token)
device = await api.device(device_id)
ret = await device.command(component_id=component_id, capability=capability, command=command, args=args)
assert ret, 'The command {capability}={command} failed on device {device}'.format(
capability=capability, command=command, device=device_id)
@action
def execute(self,
device: str,
capability: str,
command,
component_id: str = 'main',
args: Optional[list] = None):
"""
Execute a command on a device.
Example request to turn on a device with ``switch`` capability:
.. code-block:: json
{
"type": "request",
"action": "smartthings.execute",
"args": {
"device": "My Switch",
"capability": "switch",
"command": "on"
}
}
:param device: Device ID or name.
:param capability: Property to be read/written (see device ``capabilities`` returned from :meth:`.get_device`).
:param command: Command to execute on the ``capability``
(see https://smartthings.developer.samsung.com/docs/api-ref/capabilities.html).
:param component_id: ID of the component to execute the command on (default: ``main``, i.e. the device itself).
:param args: Command extra arguments, as a list.
"""
device = self._get_device(device)
with self._execute_lock:
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
loop.run_until_complete(self._execute(
device_id=device.device_id, capability=capability, command=command,
component_id=component_id, args=args))
finally:
loop.stop()
@staticmethod
async def _get_device_status(api, device_id: str) -> dict:
device = await api.device(device_id)
await device.status.refresh()
return {
'device_id': device_id,
'name': device.label,
**{
cap: getattr(device.status, cap)
for cap in device.capabilities
if hasattr(device.status, cap)
and not callable(getattr(device.status, cap))
}
}
async def _refresh_status(self, devices: List[str]) -> List[dict]:
import pysmartthings
device_ids = []
missing_device_ids = set()
def parse_device_id(device):
device_id = None
if device in self._devices_by_id:
device_id = device
device_ids.append(device_id)
elif device in self._devices_by_name:
device_id = self._devices_by_name[device].device_id
device_ids.append(device_id)
else:
missing_device_ids.add(device)
if device_id and device in missing_device_ids:
missing_device_ids.remove(device)
for dev in devices:
parse_device_id(dev)
# Fail if some devices haven't been found after refreshing
assert not missing_device_ids, 'Could not find the following devices: {}'.format(list(missing_device_ids))
async with aiohttp.ClientSession(timeout=self._timeout) as session:
api = pysmartthings.SmartThings(session, self._access_token)
status_tasks = [
asyncio.ensure_future(self._get_device_status(api, device_id))
for device_id in device_ids
]
# noinspection PyTypeChecker
return await asyncio.gather(*status_tasks)
@action
def status(self, device: Optional[Union[str, List[str]]] = None) -> List[dict]:
"""
Refresh and return the status of one or more devices.
:param device: Device or list of devices to refresh (default: all)
:return: A list containing on entry per device, and each entry containing the current device state. Example:
.. code-block:: json
[
{
"device_id": "switch-1",
"name": "Fan",
"switch": false
},
{
"device_id": "tv-1",
"name": "Samsung Smart TV",
"switch": true
}
]
"""
self.refresh_info()
if not device:
self.refresh_info()
devices = self._devices_by_id.keys()
elif isinstance(device, str):
devices = [device]
else:
devices = device
with self._refresh_lock:
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
return loop.run_until_complete(self._refresh_status(devices))
finally:
loop.stop()
@action
def on(self, device: str, *args, **kwargs):
"""
Turn on a device with ``switch`` capability.
:param device: Device name or ID.
"""
return self.execute(device, 'switch', 'on')
@action
def off(self, device: str, *args, **kwargs):
"""
Turn off a device with ``switch`` capability.
:param device: Device name or ID.
"""
return self.execute(device, 'switch', 'off')
@action
def toggle(self, device: str, *args, **kwargs):
"""
Toggle a device with ``switch`` capability.
:param device: Device name or ID.
"""
import pysmartthings
device = self._get_device(device)
device_id = device.device_id
async def _toggle():
async with aiohttp.ClientSession(timeout=self._timeout) as session:
api = pysmartthings.SmartThings(session, self._access_token)
dev = await api.device(device_id)
assert 'switch' in dev.capabilities, 'The device {} has no switch capability'.format(dev.label)
await dev.status.refresh()
state = 'off' if dev.status.switch else 'on'
ret = await dev.command(component_id='main', capability='switch', command=state, args=args)
assert ret, 'The command switch={state} failed on device {device}'.format(state=state, device=dev.label)
with self._refresh_lock:
loop = asyncio.new_event_loop()
loop.run_until_complete(_toggle())
@property
def switches(self) -> List[dict]:
"""
:return: List of switch devices statuses in :class:`platypush.plugins.switch.SwitchPlugin` compatible format.
Example:
.. code-block:: json
[
{
"id": "switch-1",
"name": "Fan",
"on": false
},
{
"id": "tv-1",
"name": "Samsung Smart TV",
"on": true
}
]
"""
# noinspection PyUnresolvedReferences
devices = self.status().output
return [
{
'name': device['name'],
'id': device['device_id'],
'on': device['switch'],
}
for device in devices
if 'switch' in device
]
# vim:sw=4:ts=4:et:

View file

@ -27,18 +27,26 @@ class SwitchPlugin(Plugin):
raise NotImplementedError() raise NotImplementedError()
@action @action
def status(self, device=None, *args, **kwargs): def switch_status(self, device=None):
""" Get the status of a specified device or of all the configured devices (default)""" """ Get the status of a specified device or of all the configured devices (default)"""
devices = self.switches devices = self.switches
if device: if device:
devices = [d for d in self.switches if d.get('id') == device or d.get('name') == device] devices = [d for d in self.switches if d.get('id') == device or d.get('name') == device]
if devices: if devices:
return self.switches.pop(0) return devices[0]
else: else:
return None return None
return devices return devices
@action
def status(self, device=None, *args, **kwargs):
"""
Status function - if not overridden it calls :meth:`.switch_status`. You may want to override it if your plugin
does not handle only switches.
"""
return self.switch_status(device)
@property @property
def switches(self) -> List[dict]: def switches(self) -> List[dict]:
""" """

View file

@ -611,9 +611,7 @@ class ZigbeeMqttPlugin(MqttPlugin, SwitchPlugin):
@action @action
def devices_get(self, devices: Optional[List[str]] = None, **kwargs) -> Dict[str, dict]: def devices_get(self, devices: Optional[List[str]] = None, **kwargs) -> Dict[str, dict]:
""" """
Get the properties of the devices connected to the network. *NOTE*: Use this function instead of :meth:`.status` Get the properties of the devices connected to the network.
if you want to retrieve the status of *all* the components associated to the network - :meth:`.status` only
returns the status of the devices with a writable ``ON``/``OFF`` ``state`` property.
:param devices: If set, then only the status of these devices (by friendly name) will be retrieved (default: :param devices: If set, then only the status of these devices (by friendly name) will be retrieved (default:
retrieve all). retrieve all).
@ -665,6 +663,15 @@ class ZigbeeMqttPlugin(MqttPlugin, SwitchPlugin):
return response return response
@action
def status(self, device: Optional[str] = None, *args, **kwargs):
"""
Get the status of a device (by friendly name) or of all the connected devices (it wraps :meth:`.devices_get`).
:param device: Device friendly name (default: get all devices).
"""
return self.devices_get([device], *args, **kwargs)
# noinspection PyShadowingBuiltins,DuplicatedCode # noinspection PyShadowingBuiltins,DuplicatedCode
@action @action
def device_set(self, device: str, property: str, value: Any, **kwargs): def device_set(self, device: str, property: str, value: Any, **kwargs):

View file

@ -307,3 +307,7 @@ croniter
# VLC integration # VLC integration
# python-vlc # python-vlc
# SmartThings integration
# pysmartthings
# aiohttp

View file

@ -245,5 +245,7 @@ setup(
'gstreamer': ['gst-python'], 'gstreamer': ['gst-python'],
# Support for VLC integration # Support for VLC integration
'vlc': ['python-vlc'], 'vlc': ['python-vlc'],
# Support for SmartThings integration
'smartthings': ['pysmartthings', 'aiohttp'],
}, },
) )