Migrated switches web panel and refactored switch plugins to expose a more consistent interface
This commit is contained in:
parent
51de11da25
commit
56f8d85feb
59 changed files with 1167 additions and 65 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-01396ebc.920effd7.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-01396ebc.920effd7.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-194b2204.f893d85c.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-194b2204.f893d85c.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-22aa1dbc.5d45d8b9.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-22aa1dbc.5d45d8b9.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-2ce5c25d.8ffe0de8.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-2ce5c25d.8ffe0de8.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-4748c324.3e3aa652.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-4748c324.3e3aa652.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-58df877b.7ca464b4.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-58df877b.7ca464b4.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-cf32428c.a0014f36.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-cf32428c.a0014f36.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
platypush/backend/http/dist/static/fonts/lato-medium-italic.4d295621.woff2
vendored
Normal file
BIN
platypush/backend/http/dist/static/fonts/lato-medium-italic.4d295621.woff2
vendored
Normal file
Binary file not shown.
BIN
platypush/backend/http/dist/static/fonts/lato-medium-italic.aa1a7512.woff
vendored
Normal file
BIN
platypush/backend/http/dist/static/fonts/lato-medium-italic.aa1a7512.woff
vendored
Normal file
Binary file not shown.
BIN
platypush/backend/http/dist/static/fonts/lato-medium.0996d39c.woff2
vendored
Normal file
BIN
platypush/backend/http/dist/static/fonts/lato-medium.0996d39c.woff2
vendored
Normal file
Binary file not shown.
BIN
platypush/backend/http/dist/static/fonts/lato-medium.acbd6ecc.woff
vendored
Normal file
BIN
platypush/backend/http/dist/static/fonts/lato-medium.acbd6ecc.woff
vendored
Normal file
Binary file not shown.
2
platypush/backend/http/dist/static/js/app.8720e90c.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/app.8720e90c.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/app.8720e90c.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/app.8720e90c.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
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-01396ebc.c5c193f1.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-01396ebc.c5c193f1.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-01396ebc"],{b4ff:function(e,t,c){},bedd:function(e,t,c){"use strict";c.r(t);c("b0c0"),c("b64b");var n=c("7a23"),a=Object(n["K"])("data-v-5c936ba2");Object(n["u"])("data-v-5c936ba2");var s={class:"switches wemo-switches"},o={key:1,class:"no-content"},i={key:0,class:"switch-info"},b={class:"row"},d=Object(n["h"])("div",{class:"name"},"Name",-1),l={class:"row"},O=Object(n["h"])("div",{class:"name"},"On",-1),j={class:"row"},v=Object(n["h"])("div",{class:"name"},"IP",-1);Object(n["s"])();var r=a((function(e,t,c,r,u,f){var h=Object(n["z"])("Loading"),w=Object(n["z"])("Switch"),g=Object(n["z"])("Modal");return Object(n["r"])(),Object(n["e"])("div",s,[e.loading?(Object(n["r"])(),Object(n["e"])(h,{key:0})):Object.keys(e.devices).length?Object(n["f"])("",!0):(Object(n["r"])(),Object(n["e"])("div",o,"No WeMo 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"])(w,{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"])(g,{title:"Device Info",ref:"switchInfoModal"},{default:a((function(){return[e.selectedDevice?(Object(n["r"])(),Object(n["e"])("div",i,[Object(n["h"])("div",b,[d,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].name)},null,8,["textContent"])]),Object(n["h"])("div",l,[O,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].on)},null,8,["textContent"])]),Object(n["h"])("div",j,[v,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].ip)},null,8,["textContent"])])])):Object(n["f"])("",!0)]})),_:1},512)])})),u=c("3a5e"),f=c("487b"),h=c("17dc"),w=c("714b"),g={name:"SwitchWemo",components:{Modal:w["a"],Switch:h["a"],Loading:u["a"]},mixins:[f["a"]]};c("c319");g.render=r,g.__scopeId="data-v-5c936ba2";t["default"]=g},c319:function(e,t,c){"use strict";c("b4ff")}}]);
|
||||||
|
//# sourceMappingURL=chunk-01396ebc.c5c193f1.js.map
|
1
platypush/backend/http/dist/static/js/chunk-01396ebc.c5c193f1.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-01396ebc.c5c193f1.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["webpack:///./src/components/panels/Switches/SwitchWemo/Index.vue","webpack:///./src/components/panels/Switches/SwitchWemo/Index.vue?77ee","webpack:///./src/components/panels/Switches/SwitchWemo/Index.vue?7731"],"names":["class","loading","Object","keys","devices","length","device","name","state","on","toggle","key","has-info","selectedDevice","$refs","switchInfoModal","show","title","ref","ip","components","Modal","Switch","Loading","mixins","render","__scopeId"],"mappings":"+PACOA,MAAM,0B,SAEJA,MAAM,c,SAOJA,MAAM,e,GACJA,MAAM,O,EACT,eAA4B,OAAvBA,MAAM,QAAO,QAAI,G,GAInBA,MAAM,O,EACT,eAA0B,OAArBA,MAAM,QAAO,MAAE,G,GAIjBA,MAAM,O,EACT,eAA0B,OAArBA,MAAM,QAAO,MAAE,G,6JArB5B,eA0BM,MA1BN,EA0BM,CAzBW,EAAAC,S,iBAAf,eAA0B,YACUC,OAAOC,KAAK,EAAAC,SAASC,O,wCAAzD,eAA8F,MAA9F,EAAiE,6B,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,EAeM,CAdJ,eAGM,MAHN,EAGM,CAFJ,EACA,eAA2D,OAAtDb,MAAM,Q,YAAQ,eAAqC,EAAtB,QAAC,EAAAa,gBAAgBN,O,0BAGrD,eAGM,MAHN,EAGM,CAFJ,EACA,eAAyD,OAApDP,MAAM,Q,YAAQ,eAAmC,EAApB,QAAC,EAAAa,gBAAgBJ,K,0BAGrD,eAGM,MAHN,EAGM,CAFJ,EACA,eAAyD,OAApDT,MAAM,Q,YAAQ,eAAmC,EAApB,QAAC,EAAAa,gBAAgBM,K,sHAa9C,GACbZ,KAAM,aACNa,WAAY,CAACC,QAAA,KAAOC,SAAA,KAAQC,UAAA,MAC5BC,OAAQ,CAAC,S,UClCX,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ,gB,kCCRf","file":"static/js/chunk-01396ebc.c5c193f1.js","sourcesContent":["<template>\n <div class=\"switches wemo-switches\">\n <Loading v-if=\"loading\" />\n <div class=\"no-content\" v-else-if=\"!Object.keys(devices).length\">No WeMo 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\">IP</div>\n <div class=\"value\" v-text=\"devices[selectedDevice].ip\" />\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: \"SwitchWemo\",\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=5c936ba2&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=5c936ba2&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-5c936ba2\"\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/cli-service/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/cli-service/node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Index.vue?vue&type=style&index=0&id=5c936ba2&lang=scss&scoped=true\""],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-194b2204.ce93c763.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-194b2204.ce93c763.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-194b2204"],{"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,d,f){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)])})),d=c("3a5e"),f=c("487b"),r=c("17dc"),l=c("714b"),j={name:"ZigbeeMqtt",components:{Modal:l["a"],Switch:r["a"],Loading:d["a"]},mixins:[f["a"]]};c("7eff");j.render=b,j.__scopeId="data-v-616a9486";t["default"]=j},"7eff":function(e,t,c){"use strict";c("8d51")},"8d51":function(e,t,c){}}]);
|
||||||
|
//# sourceMappingURL=chunk-194b2204.ce93c763.js.map
|
1
platypush/backend/http/dist/static/js/chunk-194b2204.ce93c763.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-194b2204.ce93c763.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?fd1a","webpack:///./src/components/panels/Switches/ZigbeeMqtt/Index.vue?91d8"],"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-194b2204.ce93c763.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/cli-service/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/cli-service/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":""}
|
2
platypush/backend/http/dist/static/js/chunk-22aa1dbc.c2ddea8b.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-22aa1dbc.c2ddea8b.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-22aa1dbc"],{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"},d=Object(c["h"])("i",{class:"fa fa-sync"},null,-1);Object(c["s"])();var b=r((function(e,n,t,r,b,h){var f=Object(c["z"])("Loading");return Object(c["r"])(),Object(c["e"])("div",i,[b.loading?(Object(c["r"])(),Object(c["e"])(f,{key:0})):Object(c["f"])("",!0),Object(c["h"])("div",s,[Object.keys(b.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(b.plugins),(function(e){return Object(c["r"])(),Object(c["e"])("div",{class:"switch-plugin",key:e,onClick:function(n){return b.selectedPlugin=b.selectedPlugin===e?null:e}},[Object(c["h"])("div",{class:["header",{selected:b.selectedPlugin===e}]},[Object(c["h"])("div",{class:"name col-10",textContent:Object(c["C"])(e)},null,8,["textContent"]),b.selectedPlugin===e?(Object(c["r"])(),Object(c["e"])("div",a,[Object(c["h"])("button",{onClick:Object(c["J"])((function(n){return b.bus.emit("refresh",e)}),["stop"]),title:"Refresh plugin",disabled:b.loading},[o],8,["onClick","disabled"])])):Object(c["f"])("",!0)],2),Object(c["h"])("div",{class:["body",{hidden:b.selectedPlugin!==e}]},[(Object(c["r"])(),Object(c["e"])(Object(c["A"])(b.components[e]),{config:b.plugins[e],"plugin-name":e,selected:b.selectedPlugin===e,bus:b.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:b.loading,title:"Refresh plugins"},[d],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=b,j.__scopeId="data-v-eac2ea44";n["default"]=j},"84aa":function(e,n,t){"use strict";t("cd7e")},c1da:function(e,n,t){var c={"./LightHue/Index":["0219","chunk-2f304dee","chunk-cf32428c","chunk-58df877b"],"./SwitchSwitchbot/Index":["5083","chunk-2f304dee","chunk-cf32428c","chunk-4748c324"],"./SwitchTplink/Index":["d11f","chunk-2f304dee","chunk-cf32428c","chunk-2ce5c25d"],"./SwitchWemo/Index":["bedd","chunk-2f304dee","chunk-cf32428c","chunk-01396ebc"],"./ZigbeeMqtt/Index":["65d6","chunk-2f304dee","chunk-cf32428c","chunk-194b2204"]};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},cd7e:function(e,n,t){}}]);
|
||||||
|
//# sourceMappingURL=chunk-22aa1dbc.c2ddea8b.js.map
|
1
platypush/backend/http/dist/static/js/chunk-22aa1dbc.c2ddea8b.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-22aa1dbc.c2ddea8b.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-2ce5c25d.de32e4fb.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2ce5c25d.de32e4fb.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2ce5c25d"],{3340:function(e,t,c){"use strict";c("981d")},"981d":function(e,t,c){},d11f:function(e,t,c){"use strict";c.r(t);c("b0c0"),c("b64b");var n=c("7a23"),i=Object(n["K"])("data-v-e259fb36");Object(n["u"])("data-v-e259fb36");var s={class:"switches tplink-switches"},l={key:1,class:"no-content"},d={key:0,class:"switch-info"},o={class:"row"},v=Object(n["h"])("div",{class:"name"},"Name",-1),a={class:"row"},b=Object(n["h"])("div",{class:"name"},"On",-1),O={class:"row"},j=Object(n["h"])("div",{class:"name"},"IP",-1),r={key:0,class:"row"},u=Object(n["h"])("div",{class:"name"},"MAC",-1),h={key:1,class:"row"},w=Object(n["h"])("div",{class:"name"},"Current Consumption",-1),f={key:2,class:"row"},C=Object(n["h"])("div",{class:"name"},"Device Type",-1),m={key:3,class:"row"},D=Object(n["h"])("div",{class:"name"},"Firmware ID",-1),_={key:4,class:"row"},x=Object(n["h"])("div",{class:"name"},"Hardware ID",-1),k={key:5,class:"row"},p=Object(n["h"])("div",{class:"name"},"Hardware Version",-1),g={key:6,class:"row"},y=Object(n["h"])("div",{class:"name"},"Software Version",-1);Object(n["s"])();var I=i((function(e,t,c,I,M,T){var S=Object(n["z"])("Loading"),z=Object(n["z"])("Switch"),L=Object(n["z"])("Modal");return Object(n["r"])(),Object(n["e"])("div",s,[e.loading?(Object(n["r"])(),Object(n["e"])(S,{key:0})):Object.keys(e.devices).length?Object(n["f"])("",!0):(Object(n["r"])(),Object(n["e"])("div",l,"No TP-Link 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"])(z,{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"])(L,{title:"Device Info",ref:"switchInfoModal"},{default:i((function(){var t,c,i,s,l,I;return[e.selectedDevice?(Object(n["r"])(),Object(n["e"])("div",d,[Object(n["h"])("div",o,[v,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].name)},null,8,["textContent"])]),Object(n["h"])("div",a,[b,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].on)},null,8,["textContent"])]),Object(n["h"])("div",O,[j,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].ip)},null,8,["textContent"])]),(null===(t=e.devices[e.selectedDevice].hw_info)||void 0===t?void 0:t.mac)?(Object(n["r"])(),Object(n["e"])("div",r,[u,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.mac)},null,8,["textContent"])])):Object(n["f"])("",!0),null!=e.devices[e.selectedDevice].current_consumption?(Object(n["r"])(),Object(n["e"])("div",h,[w,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].current_consumption)},null,8,["textContent"])])):Object(n["f"])("",!0),(null===(c=e.devices[e.selectedDevice].hw_info)||void 0===c?void 0:c.dev_name)?(Object(n["r"])(),Object(n["e"])("div",f,[C,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.dev_name)},null,8,["textContent"])])):Object(n["f"])("",!0),(null===(i=e.devices[e.selectedDevice].hw_info)||void 0===i?void 0:i.fwId)?(Object(n["r"])(),Object(n["e"])("div",m,[D,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.fwId)},null,8,["textContent"])])):Object(n["f"])("",!0),(null===(s=e.devices[e.selectedDevice].hw_info)||void 0===s?void 0:s.hwId)?(Object(n["r"])(),Object(n["e"])("div",_,[x,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.hwId)},null,8,["textContent"])])):Object(n["f"])("",!0),(null===(l=e.devices[e.selectedDevice].hw_info)||void 0===l?void 0:l.hw_ver)?(Object(n["r"])(),Object(n["e"])("div",k,[p,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.hw_ver)},null,8,["textContent"])])):Object(n["f"])("",!0),(null===(I=e.devices[e.selectedDevice].hw_info)||void 0===I?void 0:I.sw_ver)?(Object(n["r"])(),Object(n["e"])("div",g,[y,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].hw_info.sw_ver)},null,8,["textContent"])])):Object(n["f"])("",!0)])):Object(n["f"])("",!0)]})),_:1},512)])})),M=c("3a5e"),T=c("487b"),S=c("17dc"),z=c("714b"),L={name:"SwitchTplink",components:{Modal:z["a"],Switch:S["a"],Loading:M["a"]},mixins:[T["a"]]};c("3340");L.render=I,L.__scopeId="data-v-e259fb36";t["default"]=L}}]);
|
||||||
|
//# sourceMappingURL=chunk-2ce5c25d.de32e4fb.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2ce5c25d.de32e4fb.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2ce5c25d.de32e4fb.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-4748c324.d98e70eb.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-4748c324.d98e70eb.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4748c324"],{"021c":function(e,t,c){"use strict";c("edec")},5083:function(e,t,c){"use strict";c.r(t);c("b0c0"),c("b64b");var n=c("7a23"),s=Object(n["K"])("data-v-7b062fcf");Object(n["u"])("data-v-7b062fcf");var a={class:"switches switchbot-switches"},i={key:1,class:"no-content"},o={key:0,class:"switch-info"},d={class:"row"},b=Object(n["h"])("div",{class:"name"},"Name",-1),l={class:"row"},O=Object(n["h"])("div",{class:"name"},"On",-1),j={class:"row"},v=Object(n["h"])("div",{class:"name"},"Address",-1);Object(n["s"])();var r=s((function(e,t,c,r,f,u){var h=Object(n["z"])("Loading"),w=Object(n["z"])("Switch"),g=Object(n["z"])("Modal");return Object(n["r"])(),Object(n["e"])("div",a,[e.loading?(Object(n["r"])(),Object(n["e"])(h,{key:0})):Object.keys(e.devices).length?Object(n["f"])("",!0):(Object(n["r"])(),Object(n["e"])("div",i,"No SwitchBot 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"])(w,{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"])(g,{title:"Device Info",ref:"switchInfoModal"},{default:s((function(){return[e.selectedDevice?(Object(n["r"])(),Object(n["e"])("div",o,[Object(n["h"])("div",d,[b,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].name)},null,8,["textContent"])]),Object(n["h"])("div",l,[O,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].on)},null,8,["textContent"])]),Object(n["h"])("div",j,[v,Object(n["h"])("div",{class:"value",textContent:Object(n["C"])(e.devices[e.selectedDevice].address)},null,8,["textContent"])])])):Object(n["f"])("",!0)]})),_:1},512)])})),f=c("3a5e"),u=c("487b"),h=c("17dc"),w=c("714b"),g={name:"SwitchSwitchbot",components:{Modal:w["a"],Switch:h["a"],Loading:f["a"]},mixins:[u["a"]]};c("021c");g.render=r,g.__scopeId="data-v-7b062fcf";t["default"]=g},edec:function(e,t,c){}}]);
|
||||||
|
//# sourceMappingURL=chunk-4748c324.d98e70eb.js.map
|
1
platypush/backend/http/dist/static/js/chunk-4748c324.d98e70eb.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-4748c324.d98e70eb.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["webpack:///./src/components/panels/Switches/SwitchSwitchbot/Index.vue?5a42","webpack:///./src/components/panels/Switches/SwitchSwitchbot/Index.vue","webpack:///./src/components/panels/Switches/SwitchSwitchbot/Index.vue?4da4"],"names":["class","loading","Object","keys","devices","length","device","name","state","on","toggle","key","has-info","selectedDevice","$refs","switchInfoModal","show","title","ref","address","components","Modal","Switch","Loading","mixins","render","__scopeId"],"mappings":"kHAAA,W,0JCCOA,MAAM,+B,SAEJA,MAAM,c,SAOJA,MAAM,e,GACJA,MAAM,O,EACT,eAA4B,OAAvBA,MAAM,QAAO,QAAI,G,GAInBA,MAAM,O,EACT,eAA0B,OAArBA,MAAM,QAAO,MAAE,G,GAIjBA,MAAM,O,EACT,eAA+B,OAA1BA,MAAM,QAAO,WAAO,G,6JArBjC,eA0BM,MA1BN,EA0BM,CAzBW,EAAAC,S,iBAAf,eAA0B,YACUC,OAAOC,KAAK,EAAAC,SAASC,O,wCAAzD,eAAmG,MAAnG,EAAiE,kC,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,EAeM,CAdJ,eAGM,MAHN,EAGM,CAFJ,EACA,eAA2D,OAAtDb,MAAM,Q,YAAQ,eAAqC,EAAtB,QAAC,EAAAa,gBAAgBN,O,0BAGrD,eAGM,MAHN,EAGM,CAFJ,EACA,eAAyD,OAApDP,MAAM,Q,YAAQ,eAAmC,EAApB,QAAC,EAAAa,gBAAgBJ,K,0BAGrD,eAGM,MAHN,EAGM,CAFJ,EACA,eAA8D,OAAzDT,MAAM,Q,YAAQ,eAAwC,EAAzB,QAAC,EAAAa,gBAAgBM,U,sHAa9C,GACbZ,KAAM,kBACNa,WAAY,CAACC,QAAA,KAAOC,SAAA,KAAQC,UAAA,MAC5BC,OAAQ,CAAC,S,UClCX,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ,gB","file":"static/js/chunk-4748c324.d98e70eb.js","sourcesContent":["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/cli-service/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/cli-service/node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Index.vue?vue&type=style&index=0&id=7b062fcf&lang=scss&scoped=true\"","<template>\n <div class=\"switches switchbot-switches\">\n <Loading v-if=\"loading\" />\n <div class=\"no-content\" v-else-if=\"!Object.keys(devices).length\">No SwitchBot 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: \"SwitchSwitchbot\",\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=7b062fcf&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=7b062fcf&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-7b062fcf\"\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-58df877b.24a532d6.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-58df877b.24a532d6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-58df877b.24a532d6.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-58df877b.24a532d6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-cf32428c.b3f244c0.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-cf32428c.b3f244c0.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-cf32428c"],{"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("24a4")},"24a4":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}}]);
|
||||||
|
//# sourceMappingURL=chunk-cf32428c.b3f244c0.js.map
|
1
platypush/backend/http/dist/static/js/chunk-cf32428c.b3f244c0.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-cf32428c.b3f244c0.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
File diff suppressed because one or more lines are too long
5
platypush/backend/http/webapp/package-lock.json
generated
5
platypush/backend/http/webapp/package-lock.json
generated
|
@ -7176,6 +7176,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
|
||||||
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA=="
|
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA=="
|
||||||
},
|
},
|
||||||
|
"lato-font": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lato-font/-/lato-font-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-kbg34jdLZo+3Mx1EyJTTei2fjhE="
|
||||||
|
},
|
||||||
"launch-editor": {
|
"launch-editor": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.0",
|
||||||
"bulma": "^0.9.1",
|
"bulma": "^0.9.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"lato-font": "^3.0.0",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"sass-loader": "^10.1.0",
|
"sass-loader": "^10.1.0",
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
"rtorrent": {
|
"rtorrent": {
|
||||||
"class": "fa fa-magnet"
|
"class": "fa fa-magnet"
|
||||||
},
|
},
|
||||||
|
"switches": {
|
||||||
|
"class": "fas fa-toggle-on"
|
||||||
|
},
|
||||||
"sound": {
|
"sound": {
|
||||||
"class": "fa fa-microphone"
|
"class": "fa fa-microphone"
|
||||||
},
|
},
|
||||||
|
|
|
@ -111,9 +111,7 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $tablet) {
|
@media screen and (min-width: $tablet) {
|
||||||
width: 20%;
|
width: calc(16em - 2vw);
|
||||||
min-width: 12.5em;
|
|
||||||
max-width: 25em;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: $nav-bg;
|
background: $nav-bg;
|
||||||
|
@ -122,6 +120,10 @@ nav {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $desktop) {
|
||||||
|
width: 16em;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
box-shadow: $nav-box-shadow-entry;
|
box-shadow: $nav-box-shadow-entry;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches-container">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
|
<div class="switch-plugins">
|
||||||
|
<div class="no-content" v-if="!Object.keys(plugins).length">No switch plugins configured</div>
|
||||||
|
|
||||||
|
<div class="switch-plugin" v-for="pluginName in Object.keys(plugins)" :key="pluginName"
|
||||||
|
@click="selectedPlugin = selectedPlugin === pluginName ? null : pluginName">
|
||||||
|
<div class="header" :class="{selected: selectedPlugin === pluginName}">
|
||||||
|
<div class="name col-10" v-text="pluginName" />
|
||||||
|
<div class="refresh col-2" v-if="selectedPlugin === pluginName">
|
||||||
|
<button @click.stop="bus.emit('refresh', pluginName)" title="Refresh plugin" :disabled="loading">
|
||||||
|
<i class="fa fa-sync" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body" :class="{hidden: selectedPlugin !== pluginName}">
|
||||||
|
<component :is="components[pluginName]" :config="plugins[pluginName]" :plugin-name="pluginName"
|
||||||
|
:selected="selectedPlugin === pluginName" :bus="bus" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="refresh-button">
|
||||||
|
<button @click="refresh" :disabled="loading" title="Refresh plugins">
|
||||||
|
<i class="fa fa-sync" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
import {defineAsyncComponent} from "vue";
|
||||||
|
import mitt from "mitt";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Switches",
|
||||||
|
components: {Loading},
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
plugins: {},
|
||||||
|
components: {},
|
||||||
|
selectedPlugin: null,
|
||||||
|
bus: mitt(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
initPanels() {
|
||||||
|
this.components = {}
|
||||||
|
|
||||||
|
Object.keys(this.plugins).forEach(async (pluginName) => {
|
||||||
|
const componentName = pluginName.split('.').map((token) => token[0].toUpperCase() + token.slice(1)).join('')
|
||||||
|
let comp = null
|
||||||
|
try {
|
||||||
|
comp = await import(`@/components/panels/Switches/${componentName}/Index`)
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = defineAsyncComponent(async () => { return comp })
|
||||||
|
this.$options.components[pluginName] = component
|
||||||
|
this.components[pluginName] = component
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.plugins = await this.request('utils.get_switch_plugins')
|
||||||
|
this.initPanels()
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "vars";
|
||||||
|
|
||||||
|
.switches-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.switch-plugins {
|
||||||
|
background: $background-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: $border-shadow-bottom-right;
|
||||||
|
|
||||||
|
@media screen and (max-width: calc(#{$tablet - 1px})) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
width: 90%;
|
||||||
|
border-radius: 1em;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $desktop) {
|
||||||
|
width: 500pt;
|
||||||
|
margin-top: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-content {
|
||||||
|
padding: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-plugin {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em 1.5em 1em .5em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .075em;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: $selected-bg;
|
||||||
|
box-shadow: $border-shadow-bottom-right;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
&:first-child {
|
||||||
|
.header {
|
||||||
|
border-radius: 1em 1em 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
.header {
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
border: $default-border-2;
|
||||||
|
border-bottom: 0;
|
||||||
|
box-shadow: $border-shadow-bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 1.5em;
|
||||||
|
right: 1.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 4em;
|
||||||
|
height: 4em;
|
||||||
|
border-radius: 2em;
|
||||||
|
background: $refresh-button-bg;
|
||||||
|
color: $refresh-button-fg;
|
||||||
|
border: none;
|
||||||
|
box-shadow: $border-shadow-bottom-right;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,119 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches switchbot-switches">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="no-content" v-else-if="!Object.keys(devices).length">No Hue lights found.</div>
|
||||||
|
|
||||||
|
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
|
||||||
|
v-for="(device, name) in devices" :key="name" :has-info="true"
|
||||||
|
@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" v-if="devices[selectedDevice].reachable != null">
|
||||||
|
<div class="name">Reachable</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].reachable" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].bri != null">
|
||||||
|
<div class="name">Brightness</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].bri" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].ct != null">
|
||||||
|
<div class="name">Color Temperature</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].ct" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hue != null">
|
||||||
|
<div class="name">Hue</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hue" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].sat != null">
|
||||||
|
<div class="name">Saturation</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].sat" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].xy != null">
|
||||||
|
<div class="name">XY</div>
|
||||||
|
<div class="value" v-text="`[${devices[selectedDevice].xy.join(', ')}]`" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].productname != null">
|
||||||
|
<div class="name">Product</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].productname" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].manufacturername != null">
|
||||||
|
<div class="name">Manufacturer</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].manufacturername " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].type != null">
|
||||||
|
<div class="name">Type</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].type " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].id != null">
|
||||||
|
<div class="name">ID on network</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].id " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].uniqueid != null">
|
||||||
|
<div class="name">Unique ID</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].uniqueid " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].swversion != null">
|
||||||
|
<div class="name">Software version</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].swversion " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].swupdate?.lastinstall">
|
||||||
|
<div class="name">Last software update</div>
|
||||||
|
<div class="value" v-text="formatDate(devices[selectedDevice].swupdate.lastinstall, true)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].swupdate?.state">
|
||||||
|
<div class="name">Update state</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].swupdate.state" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import SwitchMixin from "@/components/panels/Switches/Mixin";
|
||||||
|
import Switch from "@/components/panels/Switches/Switch";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LightHue",
|
||||||
|
components: {Modal, Switch, Loading},
|
||||||
|
mixins: [SwitchMixin],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async toggle(device) {
|
||||||
|
const response = await this.request(`${this.pluginName}.toggle`, {lights: [device]})
|
||||||
|
if (response.success)
|
||||||
|
this.devices[device].on = !this.devices[device].on
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../common";
|
||||||
|
</style>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script>
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SwitchesMixin",
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
pluginName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
bus: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { return {} },
|
||||||
|
},
|
||||||
|
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
selectedDevice: null,
|
||||||
|
devices: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onRefreshEvent(pluginName) {
|
||||||
|
if (pluginName !== this.pluginName)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async toggle(device) {
|
||||||
|
const response = await this.request(`${this.pluginName}.toggle`, {device: device})
|
||||||
|
this.devices[device].on = response.on
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
this.devices = (await this.request(`${this.pluginName}.status`)).reduce((obj, device) => {
|
||||||
|
const name = device.name?.length ? device.name : device.id
|
||||||
|
obj[name] = device
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$watch(() => this.selected, (newValue) => {
|
||||||
|
if (newValue && !this.initialized) {
|
||||||
|
this.refresh()
|
||||||
|
this.initialized = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.bus.on('refresh', this.onRefreshEvent)
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
this.bus.off('refresh', this.onRefreshEvent)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="switch" @click.stop="onToggle">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="name col-l-10 col-m-9 col-s-8">
|
||||||
|
<button v-if="hasInfo" @click.prevent="onInfo">
|
||||||
|
<i class="fa fa-info" />
|
||||||
|
</button>
|
||||||
|
<span class="name-content" v-text="name" />
|
||||||
|
</div>
|
||||||
|
<div class="toggler col-l-2 col-m-3 col-s-4">
|
||||||
|
<ToggleSwitch :disabled="loading" :value="state" @input="onToggle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Switch",
|
||||||
|
components: {Loading, ToggleSwitch},
|
||||||
|
emits: ['toggle', 'info'],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
state: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
hasInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onInfo(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.$emit('info')
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
onToggle(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.$emit('toggle')
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.switch {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
padding: .75em .5em;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches switchbot-switches">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="no-content" v-else-if="!Object.keys(devices).length">No SwitchBot switches found.</div>
|
||||||
|
|
||||||
|
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
|
||||||
|
v-for="(device, name) in devices" :key="name" :has-info="true"
|
||||||
|
@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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import SwitchMixin from "@/components/panels/Switches/Mixin";
|
||||||
|
import Switch from "@/components/panels/Switches/Switch";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SwitchSwitchbot",
|
||||||
|
components: {Modal, Switch, Loading},
|
||||||
|
mixins: [SwitchMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../common";
|
||||||
|
</style>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches tplink-switches">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="no-content" v-else-if="!Object.keys(devices).length">No TP-Link switches found.</div>
|
||||||
|
|
||||||
|
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
|
||||||
|
v-for="(device, name) in devices" :key="name" :has-info="true"
|
||||||
|
@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">IP</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].ip" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.mac">
|
||||||
|
<div class="name">MAC</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.mac" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].current_consumption != null">
|
||||||
|
<div class="name">Current Consumption</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].current_consumption" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.dev_name">
|
||||||
|
<div class="name">Device Type</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.dev_name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.fwId">
|
||||||
|
<div class="name">Firmware ID</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.fwId" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.hwId">
|
||||||
|
<div class="name">Hardware ID</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.hwId" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.hw_ver">
|
||||||
|
<div class="name">Hardware Version</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.hw_ver" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="devices[selectedDevice].hw_info?.sw_ver">
|
||||||
|
<div class="name">Software Version</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].hw_info.sw_ver" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import SwitchMixin from "@/components/panels/Switches/Mixin";
|
||||||
|
import Switch from "@/components/panels/Switches/Switch";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SwitchTplink",
|
||||||
|
components: {Modal, Switch, Loading},
|
||||||
|
mixins: [SwitchMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../common";
|
||||||
|
</style>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches wemo-switches">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="no-content" v-else-if="!Object.keys(devices).length">No WeMo switches found.</div>
|
||||||
|
|
||||||
|
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
|
||||||
|
v-for="(device, name) in devices" :key="name" :has-info="true"
|
||||||
|
@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">IP</div>
|
||||||
|
<div class="value" v-text="devices[selectedDevice].ip" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import SwitchMixin from "@/components/panels/Switches/Mixin";
|
||||||
|
import Switch from "@/components/panels/Switches/Switch";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SwitchWemo",
|
||||||
|
components: {Modal, Switch, Loading},
|
||||||
|
mixins: [SwitchMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../common";
|
||||||
|
</style>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div class="switches zigbee-mqtt-switches">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div class="no-content" v-else-if="!Object.keys(devices).length">No Zigbee switches found.</div>
|
||||||
|
|
||||||
|
<Switch :loading="loading" :name="name" :state="device.on" @toggle="toggle(name)"
|
||||||
|
v-for="(device, name) in devices" :key="name" :has-info="true"
|
||||||
|
@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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import SwitchMixin from "@/components/panels/Switches/Mixin";
|
||||||
|
import Switch from "@/components/panels/Switches/Switch";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ZigbeeMqtt",
|
||||||
|
components: {Modal, Switch, Loading},
|
||||||
|
mixins: [SwitchMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../common";
|
||||||
|
</style>
|
|
@ -0,0 +1,65 @@
|
||||||
|
.switches-container {
|
||||||
|
.switches {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.no-content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-info {
|
||||||
|
margin: -1em;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
@media screen and (max-width: calc(#{$tablet} - 1px)) {
|
||||||
|
min-width: calc(100vw - 3em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
min-width: 45em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: .5em 1em;
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: $background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: $default-bg-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: calc(#{$tablet} - 1px)) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.name,
|
||||||
|
.value {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
.name,
|
||||||
|
.value {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
$refresh-button-bg: #182c29;
|
||||||
|
$refresh-button-fg: white;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<div id="dashboard" class="columns is-mobile" :class="classes" :style="style">
|
<div id="dashboard" class="columns is-mobile" :class="classes" :style="style">
|
||||||
<Row v-for="(row, i) in rows" :key="i" :class="row.class" :style="row.style">
|
<Row v-for="(row, i) in rows" :key="i" :class="row.class" :style="row.style">
|
||||||
|
@ -118,6 +116,11 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "~lato-font/scss/public-api";
|
||||||
|
$lato-font-path: "~lato-font/fonts";
|
||||||
|
|
||||||
|
@include lato-include-font('medium');
|
||||||
|
|
||||||
#dashboard {
|
#dashboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -127,7 +130,7 @@ export default {
|
||||||
padding: 1em 1em 0 1em;
|
padding: 1em 1em 0 1em;
|
||||||
background: $dashboard-bg;
|
background: $dashboard-bg;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
font-family: Roboto, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif;
|
||||||
|
|
||||||
.blurred {
|
.blurred {
|
||||||
filter: blur(0.075em);
|
filter: blur(0.075em);
|
||||||
|
|
|
@ -84,6 +84,8 @@ export default {
|
||||||
this.request('config.get_procedures'),
|
this.request('config.get_procedures'),
|
||||||
this.request('config.get_device_id'),
|
this.request('config.get_device_id'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
this.plugins.switches = {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -64,18 +64,19 @@ class BluetoothBlePlugin(SensorPlugin):
|
||||||
if not output:
|
if not output:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
caps = set(output.pop(0).split('=').pop().strip().split(','))
|
caps = output[0]
|
||||||
return 'cap_net_raw+eip' in caps and 'cap_net_admin' in caps
|
return ('cap_net_raw+eip' in caps or 'cap_net_raw=eip' in caps) and 'cap_net_admin' in caps
|
||||||
|
|
||||||
def _check_ble_support(self):
|
def _check_ble_support(self):
|
||||||
# Check if the script is running as root or if the Python executable
|
# Check if the script is running as root or if the Python executable
|
||||||
# has 'cap_net_admin,cap_net_raw+eip' capabilities
|
# has 'cap_net_admin,cap_net_raw+eip' capabilities
|
||||||
exe = self._get_python_interpreter()
|
exe = self._get_python_interpreter()
|
||||||
if os.getuid() != 0 and not self._python_has_ble_capabilities(exe):
|
assert os.getuid() == 0 or self._python_has_ble_capabilities(exe), '''
|
||||||
raise RuntimeError('You are not running platypush as root and the Python interpreter has no ' +
|
You are not running platypush as root and the Python interpreter has no
|
||||||
'capabilities/permissions to access the BLE stack. Set the permissions on ' +
|
capabilities/permissions to access the BLE stack. Set the permissions on
|
||||||
'your Python interpreter through:\n' +
|
your Python interpreter through:
|
||||||
'\t[sudo] setcap "cap_net_raw,cap_net_admin+eip" {}'.format(exe))
|
|
||||||
|
[sudo] setcap "cap_net_raw,cap_net_admin+eip" {}'''.format(exe)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def scan(self, interface: Optional[str] = None, duration: int = 10) -> BluetoothScanResponse:
|
def scan(self, interface: Optional[str] = None, duration: int = 10) -> BluetoothScanResponse:
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from platypush.plugins import Plugin, action
|
from abc import ABC
|
||||||
|
|
||||||
|
from platypush.plugins import action
|
||||||
|
from platypush.plugins.switch import SwitchPlugin
|
||||||
|
|
||||||
|
|
||||||
class LightPlugin(Plugin):
|
class LightPlugin(SwitchPlugin, ABC):
|
||||||
"""
|
"""
|
||||||
Abstract plugin to interface your logic with lights/bulbs.
|
Abstract plugin to interface your logic with lights/bulbs.
|
||||||
"""
|
"""
|
||||||
|
@ -21,10 +24,5 @@ class LightPlugin(Plugin):
|
||||||
""" Toggle the light status (on/off) """
|
""" Toggle the light status (on/off) """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@action
|
|
||||||
def status(self, *args, **kwargs):
|
|
||||||
""" Get the light status """
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -4,6 +4,7 @@ import time
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from platypush.context import get_bus
|
from platypush.context import get_bus
|
||||||
from platypush.message.event.light import LightAnimationStartedEvent, LightAnimationStoppedEvent
|
from platypush.message.event.light import LightAnimationStartedEvent, LightAnimationStoppedEvent
|
||||||
|
@ -898,8 +899,49 @@ class LightHuePlugin(LightPlugin):
|
||||||
args=(lights,))
|
args=(lights,))
|
||||||
self.animation_thread.start()
|
self.animation_thread.start()
|
||||||
|
|
||||||
def status(self):
|
@property
|
||||||
# TODO
|
def switches(self) -> List[dict]:
|
||||||
pass
|
"""
|
||||||
|
:returns: Implements :meth:`platypush.plugins.switch.SwitchPlugin.switches` and returns the status of the
|
||||||
|
configured lights. Example:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "Lightbulb 1",
|
||||||
|
"on": true,
|
||||||
|
"bri": 254,
|
||||||
|
"hue": 1532,
|
||||||
|
"sat": 215,
|
||||||
|
"effect": "none",
|
||||||
|
"xy": [
|
||||||
|
0.6163,
|
||||||
|
0.3403
|
||||||
|
],
|
||||||
|
"ct": 153,
|
||||||
|
"alert": "none",
|
||||||
|
"colormode": "hs",
|
||||||
|
"reachable": true
|
||||||
|
"type": "Extended color light",
|
||||||
|
"modelid": "LCT001",
|
||||||
|
"manufacturername": "Philips",
|
||||||
|
"uniqueid": "00:11:22:33:44:55:66:77-88",
|
||||||
|
"swversion": "5.105.0.21169"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': id,
|
||||||
|
**light.pop('state', {}),
|
||||||
|
**light,
|
||||||
|
}
|
||||||
|
for id, light in self.bridge.get_light().items()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,18 +29,37 @@ class SwitchPlugin(Plugin):
|
||||||
@action
|
@action
|
||||||
def status(self, device=None, *args, **kwargs):
|
def status(self, device=None, *args, **kwargs):
|
||||||
""" 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.devices
|
devices = self.switches
|
||||||
if device:
|
if device:
|
||||||
devices = [d for d in self.devices 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.devices.pop(0)
|
return self.switches.pop(0)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def switches(self) -> List[dict]:
|
||||||
|
"""
|
||||||
|
This property must be implemented by the derived classes and must return a dictionary in the following format:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "switch_1",
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "switch_2",
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
``name`` and ``on`` are the minimum set of attributes that should be returned for a switch, but more attributes
|
||||||
|
can also be added.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import enum
|
import enum
|
||||||
import time
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from platypush.message.response.bluetooth import BluetoothScanResponse
|
from platypush.message.response.bluetooth import BluetoothScanResponse
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
|
@ -144,7 +145,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin, BluetoothBlePlugin):
|
||||||
return BluetoothScanResponse(devices=compatible_devices)
|
return BluetoothScanResponse(devices=compatible_devices)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def switches(self) -> List[dict]:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'address': addr,
|
'address': addr,
|
||||||
|
|
|
@ -148,7 +148,7 @@ class SwitchTplinkPlugin(SwitchPlugin):
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def switches(self) -> List[dict]:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'current_consumption': dev.current_consumption(),
|
'current_consumption': dev.current_consumption(),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
from platypush.plugins.switch import SwitchPlugin
|
from platypush.plugins.switch import SwitchPlugin
|
||||||
|
@ -48,24 +49,26 @@ class SwitchWemoPlugin(SwitchPlugin):
|
||||||
self._addresses = set(self._devices.values())
|
self._addresses = set(self._devices.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def switches(self) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Get the list of available devices
|
Get the list of available devices
|
||||||
:returns: The list of devices.
|
:returns: The list of devices.
|
||||||
|
|
||||||
Example output::
|
.. code-block:: json
|
||||||
|
|
||||||
output = [
|
[
|
||||||
{
|
{
|
||||||
"ip": "192.168.1.123",
|
"ip": "192.168.1.123",
|
||||||
"name": "Switch 1",
|
"name": "Switch 1",
|
||||||
"on": true,
|
"on": true
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
# ...
|
"ip": "192.168.1.124",
|
||||||
|
"name": "Switch 2",
|
||||||
|
"on": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -3,8 +3,10 @@ import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from platypush.backend.http.utils import HttpUtils
|
from platypush.backend.http.utils import HttpUtils
|
||||||
|
from platypush.config import Config
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
from platypush.procedure import Procedure
|
from platypush.procedure import Procedure
|
||||||
|
from platypush.utils import get_enabled_plugins
|
||||||
|
|
||||||
|
|
||||||
class UtilsPlugin(Plugin):
|
class UtilsPlugin(Plugin):
|
||||||
|
@ -25,6 +27,11 @@ class UtilsPlugin(Plugin):
|
||||||
_pending_timeouts_lock = threading.RLock()
|
_pending_timeouts_lock = threading.RLock()
|
||||||
_pending_intervals_lock = threading.RLock()
|
_pending_intervals_lock = threading.RLock()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._plugins = {}
|
||||||
|
self._plugins_lock = threading.RLock()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def sleep(self, seconds):
|
def sleep(self, seconds):
|
||||||
"""
|
"""
|
||||||
|
@ -89,9 +96,6 @@ class UtilsPlugin(Plugin):
|
||||||
:param name: Name of the timeout to clear
|
:param name: Name of the timeout to clear
|
||||||
:type name: str
|
:type name: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timer = None
|
|
||||||
|
|
||||||
with self._pending_timeouts_lock:
|
with self._pending_timeouts_lock:
|
||||||
if name not in self._pending_timeouts:
|
if name not in self._pending_timeouts:
|
||||||
self.logger.debug('{} is not a pending timeout'.format(name))
|
self.logger.debug('{} is not a pending timeout'.format(name))
|
||||||
|
@ -161,7 +165,7 @@ class UtilsPlugin(Plugin):
|
||||||
timeout name will be null.
|
timeout name will be null.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = { name: None }
|
response = {name: None}
|
||||||
|
|
||||||
with self._pending_timeouts_lock:
|
with self._pending_timeouts_lock:
|
||||||
timer = self._pending_timeouts.get(name)
|
timer = self._pending_timeouts.get(name)
|
||||||
|
@ -201,13 +205,12 @@ class UtilsPlugin(Plugin):
|
||||||
self._interval_hndl_idx += 1
|
self._interval_hndl_idx += 1
|
||||||
if not name:
|
if not name:
|
||||||
name = self._DEFAULT_INTERVAL_PREFIX + \
|
name = self._DEFAULT_INTERVAL_PREFIX + \
|
||||||
str(self._interval_hndl_idx)
|
str(self._interval_hndl_idx)
|
||||||
|
|
||||||
if name in self._pending_intervals:
|
if name in self._pending_intervals:
|
||||||
return (None,
|
return (None,
|
||||||
"An interval named '{}' is already running".format(name))
|
"An interval named '{}' is already running".format(name))
|
||||||
|
|
||||||
|
|
||||||
procedure = Procedure.build(name=name, requests=actions, _async=False)
|
procedure = Procedure.build(name=name, requests=actions, _async=False)
|
||||||
self._pending_intervals[name] = procedure
|
self._pending_intervals[name] = procedure
|
||||||
|
|
||||||
|
@ -233,16 +236,12 @@ class UtilsPlugin(Plugin):
|
||||||
:param name: Name of the interval to clear
|
:param name: Name of the interval to clear
|
||||||
:type name: str
|
:type name: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
interval = None
|
|
||||||
|
|
||||||
with self._pending_intervals_lock:
|
with self._pending_intervals_lock:
|
||||||
if name not in self._pending_intervals:
|
if name not in self._pending_intervals:
|
||||||
self.logger.debug('{} is not a running interval'.format(name))
|
self.logger.debug('{} is not a running interval'.format(name))
|
||||||
return
|
return
|
||||||
del self._pending_intervals[name]
|
del self._pending_intervals[name]
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_intervals(self):
|
def get_intervals(self):
|
||||||
"""
|
"""
|
||||||
|
@ -304,13 +303,14 @@ class UtilsPlugin(Plugin):
|
||||||
timeout name will be null.
|
timeout name will be null.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = { name: None }
|
response = {name: None}
|
||||||
|
|
||||||
with self._pending_intervals_lock:
|
with self._pending_intervals_lock:
|
||||||
timer = self._pending_intervals.get(name)
|
timer = self._pending_intervals.get(name)
|
||||||
if not timer:
|
if not timer:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
return {
|
return {
|
||||||
name: {
|
name: {
|
||||||
'seconds': timer._args[1],
|
'seconds': timer._args[1],
|
||||||
|
@ -328,5 +328,44 @@ class UtilsPlugin(Plugin):
|
||||||
def search_web_directory(self, directory, extensions):
|
def search_web_directory(self, directory, extensions):
|
||||||
return HttpUtils.search_web_directory(directory, *extensions)
|
return HttpUtils.search_web_directory(directory, *extensions)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def get_enabled_plugins(self) -> dict:
|
||||||
|
"""
|
||||||
|
:return: The list of enabled plugins as a ``name -> configuration`` map.
|
||||||
|
"""
|
||||||
|
if self._plugins:
|
||||||
|
return self._plugins
|
||||||
|
|
||||||
|
plugins = {}
|
||||||
|
with self._plugins_lock:
|
||||||
|
for name in get_enabled_plugins().keys():
|
||||||
|
plugins[name] = Config.get(name)
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
@action
|
||||||
|
def get_sensor_plugins(self) -> dict:
|
||||||
|
"""
|
||||||
|
:return: The list of enabled sensor plugins as a ``name -> configuration`` map.
|
||||||
|
"""
|
||||||
|
from platypush.plugins.sensor import SensorPlugin
|
||||||
|
return {
|
||||||
|
name: Config.get(name)
|
||||||
|
for name, plugin in get_enabled_plugins().items()
|
||||||
|
if isinstance(plugin, SensorPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
def get_switch_plugins(self) -> dict:
|
||||||
|
"""
|
||||||
|
:return: The list of enabled switch plugins as a ``name -> configuration`` map.
|
||||||
|
"""
|
||||||
|
from platypush.plugins.switch import SwitchPlugin
|
||||||
|
return {
|
||||||
|
name: Config.get(name)
|
||||||
|
for name, plugin in get_enabled_plugins().items()
|
||||||
|
if isinstance(plugin, SwitchPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from queue import Queue
|
||||||
from typing import Optional, List, Any, Dict, Union
|
from typing import Optional, List, Any, Dict, Union
|
||||||
|
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
from platypush.plugins.mqtt import MqttPlugin, action
|
from platypush.plugins.mqtt import MqttPlugin, action
|
||||||
|
from platypush.plugins.switch import SwitchPlugin
|
||||||
|
|
||||||
|
|
||||||
class ZigbeeMqttPlugin(MqttPlugin):
|
class ZigbeeMqttPlugin(MqttPlugin, SwitchPlugin):
|
||||||
"""
|
"""
|
||||||
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
||||||
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
||||||
|
@ -606,6 +608,63 @@ class ZigbeeMqttPlugin(MqttPlugin):
|
||||||
return self.publish(topic=self._topic(device) + '/get', reply_topic=self._topic(device),
|
return self.publish(topic=self._topic(device) + '/get', reply_topic=self._topic(device),
|
||||||
msg=self.build_device_get_request(exposes), **kwargs)
|
msg=self.build_device_get_request(exposes), **kwargs)
|
||||||
|
|
||||||
|
@action
|
||||||
|
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`
|
||||||
|
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:
|
||||||
|
retrieve all).
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
:return: Key->value map of the device properties:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"Bulb": {
|
||||||
|
"state": "ON",
|
||||||
|
"brightness": 254
|
||||||
|
},
|
||||||
|
"Sensor": {
|
||||||
|
"temperature": 22.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
kwargs = self._mqtt_args(**kwargs)
|
||||||
|
|
||||||
|
if not devices:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
devices = set([
|
||||||
|
device['friendly_name'] or device['ieee_address']
|
||||||
|
for device in self.devices(**kwargs).output
|
||||||
|
])
|
||||||
|
|
||||||
|
def worker(device: str, q: Queue):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
q.put(self.device_get(device, **kwargs).output)
|
||||||
|
|
||||||
|
queues = {}
|
||||||
|
workers = {}
|
||||||
|
response = {}
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
queues[device] = Queue()
|
||||||
|
workers[device] = threading.Thread(target=worker, args=(device, queues[device]))
|
||||||
|
workers[device].start()
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
try:
|
||||||
|
response[device] = queues[device].get(timeout=kwargs.get('timeout'))
|
||||||
|
workers[device].join(timeout=kwargs.get('timeout'))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning('An error while getting the status of the device {}: {}'.format(
|
||||||
|
device, str(e)))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
# 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):
|
||||||
|
@ -960,12 +1019,14 @@ class ZigbeeMqttPlugin(MqttPlugin):
|
||||||
(default: query the default configured device).
|
(default: query the default configured device).
|
||||||
"""
|
"""
|
||||||
return self._parse_response(
|
return self._parse_response(
|
||||||
self.publish(topic=self._topic('bridge/request/group/members/remove{}'.format('_all' if device is None else '')),
|
self.publish(
|
||||||
reply_topic=self._topic('bridge/response/group/members/remove{}'.format('_all' if device is None else '')),
|
topic=self._topic('bridge/request/group/members/remove{}'.format('_all' if device is None else '')),
|
||||||
msg={
|
reply_topic=self._topic(
|
||||||
'group': group,
|
'bridge/response/group/members/remove{}'.format('_all' if device is None else '')),
|
||||||
'device': device,
|
msg={
|
||||||
}, **self._mqtt_args(**kwargs)))
|
'group': group,
|
||||||
|
'device': device,
|
||||||
|
}, **self._mqtt_args(**kwargs)))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def bind_devices(self, source: str, target: str, **kwargs):
|
def bind_devices(self, source: str, target: str, **kwargs):
|
||||||
|
@ -986,7 +1047,7 @@ class ZigbeeMqttPlugin(MqttPlugin):
|
||||||
return self._parse_response(
|
return self._parse_response(
|
||||||
self.publish(topic=self._topic('bridge/request/device/bind'),
|
self.publish(topic=self._topic('bridge/request/device/bind'),
|
||||||
reply_topic=self._topic('bridge/response/device/bind'),
|
reply_topic=self._topic('bridge/response/device/bind'),
|
||||||
msg={'from': source, 'to': target}, **self._mqtt_args(**kwargs)) )
|
msg={'from': source, 'to': target}, **self._mqtt_args(**kwargs)))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def unbind_devices(self, source: str, target: str, **kwargs):
|
def unbind_devices(self, source: str, target: str, **kwargs):
|
||||||
|
@ -1003,7 +1064,92 @@ class ZigbeeMqttPlugin(MqttPlugin):
|
||||||
return self._parse_response(
|
return self._parse_response(
|
||||||
self.publish(topic=self._topic('bridge/request/device/unbind'),
|
self.publish(topic=self._topic('bridge/request/device/unbind'),
|
||||||
reply_topic=self._topic('bridge/response/device/unbind'),
|
reply_topic=self._topic('bridge/response/device/unbind'),
|
||||||
msg={'from': source, 'to': target}, **self._mqtt_args(**kwargs)) )
|
msg={'from': source, 'to': target}, **self._mqtt_args(**kwargs)))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def on(self, device, *args, **kwargs) -> dict:
|
||||||
|
"""
|
||||||
|
Implements :meth:`platypush.plugins.switch.plugin.SwitchPlugin.on` and turns on a Zigbee device with a writable
|
||||||
|
binary property.
|
||||||
|
"""
|
||||||
|
switch_info = self._get_switches_info().get(device)
|
||||||
|
assert switch_info, '{} is not a valid switch'.format(device)
|
||||||
|
props = self.device_set(device, switch_info['property'], switch_info['value_on']).output
|
||||||
|
return self._properties_to_switch(device=device, props=props, switch_info=switch_info)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def off(self, device, *args, **kwargs) -> dict:
|
||||||
|
"""
|
||||||
|
Implements :meth:`platypush.plugins.switch.plugin.SwitchPlugin.off` and turns off a Zigbee device with a
|
||||||
|
writable binary property.
|
||||||
|
"""
|
||||||
|
switch_info = self._get_switches_info().get(device)
|
||||||
|
assert switch_info, '{} is not a valid switch'.format(device)
|
||||||
|
props = self.device_set(device, switch_info['property'], switch_info['value_off']).output
|
||||||
|
return self._properties_to_switch(device=device, props=props, switch_info=switch_info)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def toggle(self, device, *args, **kwargs) -> dict:
|
||||||
|
"""
|
||||||
|
Implements :meth:`platypush.plugins.switch.plugin.SwitchPlugin.toggle` and toggles a Zigbee device with a
|
||||||
|
writable binary property.
|
||||||
|
"""
|
||||||
|
switch_info = self._get_switches_info().get(device)
|
||||||
|
assert switch_info, '{} is not a valid switch'.format(device)
|
||||||
|
props = self.device_set(device, switch_info['property'], switch_info['value_toggle']).output
|
||||||
|
return self._properties_to_switch(device=device, props=props, switch_info=switch_info)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _properties_to_switch(device: str, props: dict, switch_info: dict) -> dict:
|
||||||
|
return {
|
||||||
|
'on': props[switch_info['property']] == switch_info['value_on'],
|
||||||
|
'friendly_name': device,
|
||||||
|
'name': device,
|
||||||
|
**props,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_switches_info(self) -> dict:
|
||||||
|
def switch_info(device_info: dict) -> dict:
|
||||||
|
exposes = (device_info.get('definition', {}) or {}).get('exposes', [])
|
||||||
|
for exposed in exposes:
|
||||||
|
for feature in exposed.get('features', []):
|
||||||
|
if feature.get('type') == 'binary' and 'value_on' in feature and 'value_off' in feature and \
|
||||||
|
feature.get('access', 0) & 2:
|
||||||
|
return {
|
||||||
|
'property': feature['property'],
|
||||||
|
'value_on': feature['value_on'],
|
||||||
|
'value_off': feature['value_off'],
|
||||||
|
'value_toggle': feature.get('value_toggle', None),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
devices = self.devices().output
|
||||||
|
switches_info = {}
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
info = switch_info(device)
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
|
||||||
|
switches_info[device.get('friendly_name', device.get('ieee_address'))] = info
|
||||||
|
|
||||||
|
return switches_info
|
||||||
|
|
||||||
|
@property
|
||||||
|
def switches(self) -> List[dict]:
|
||||||
|
"""
|
||||||
|
Implements the :class:`platypush.plugins.switch.SwitchPlugin.switches` property and returns the state of any
|
||||||
|
device on the Zigbee network identified as a switch (a device is identified as a switch if it exposes a writable
|
||||||
|
``state`` property that can be set to ``ON`` or ``OFF``).
|
||||||
|
"""
|
||||||
|
switches_info = self._get_switches_info()
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
return [
|
||||||
|
self._properties_to_switch(device=name, props=switch, switch_info=switches_info[name])
|
||||||
|
for name, switch in self.devices_get(list(switches_info.keys())).output.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -420,4 +420,20 @@ def get_or_generate_jwt_rsa_key_pair():
|
||||||
return generate_rsa_key_pair(priv_key_file, size=2048)
|
return generate_rsa_key_pair(priv_key_file, size=2048)
|
||||||
|
|
||||||
|
|
||||||
|
def get_enabled_plugins() -> dict:
|
||||||
|
from platypush.config import Config
|
||||||
|
from platypush.context import get_plugin
|
||||||
|
|
||||||
|
plugins = {}
|
||||||
|
for name, config in Config.get_plugins().items():
|
||||||
|
try:
|
||||||
|
plugin = get_plugin(name)
|
||||||
|
if plugin:
|
||||||
|
plugins[name] = plugin
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue