forked from platypush/platypush
Added SmartThings plugin [#148]
This commit is contained in:
parent
210cefc1a4
commit
4ada1c663d
25 changed files with 628 additions and 41 deletions
|
@ -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('../..'))
|
||||||
|
|
5
docs/source/platypush/plugins/config.rst
Normal file
5
docs/source/platypush/plugins/config.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.config``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.config
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/smartthings.rst
Normal file
5
docs/source/platypush/plugins/smartthings.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.smartthings``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.smartthings
|
||||||
|
:members:
|
|
@ -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
1
platypush/backend/http/webapp/dist/static/css/chunk-5ddcb52e.9f35ac32.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/chunk-5ddcb52e.9f35ac32.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
||||||
(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
|
File diff suppressed because one or more lines are too long
|
@ -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
|
1
platypush/backend/http/webapp/dist/static/js/chunk-4b03f49b.2f384634.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-4b03f49b.2f384634.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/chunk-5ddcb52e.c1af216d.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/chunk-5ddcb52e.c1af216d.js
vendored
Normal 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
|
1
platypush/backend/http/webapp/dist/static/js/chunk-5ddcb52e.c1af216d.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-5ddcb52e.c1af216d.js.map
vendored
Normal 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":""}
|
|
@ -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
|
|
|
@ -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":""}
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
572
platypush/plugins/smartthings.py
Normal file
572
platypush/plugins/smartthings.py
Normal 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:
|
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -307,3 +307,7 @@ croniter
|
||||||
|
|
||||||
# VLC integration
|
# VLC integration
|
||||||
# python-vlc
|
# python-vlc
|
||||||
|
|
||||||
|
# SmartThings integration
|
||||||
|
# pysmartthings
|
||||||
|
# aiohttp
|
2
setup.py
2
setup.py
|
@ -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'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue