Added switch tab to the new web panel

This commit is contained in:
Fabio Manganiello 2019-07-02 12:02:28 +02:00
parent 26ee3fc75c
commit b932df1c12
8 changed files with 299 additions and 95 deletions

View file

@ -0,0 +1,36 @@
@import 'common/vars';
$head-bg: #e8e8e8;
.switches-root {
.switch-root {
.head {
padding: 1rem .5rem;
background: $head-bg;
border-top: $default-border-2;
border-bottom: $default-border-2;
text-transform: uppercase;
}
.switches {
display: flex;
flex-direction: column;
padding: 1rem;
}
.device {
display: flex;
align-items: center;
border-radius: 2rem;
.toggle {
text-align: right;
}
&:nth-child(odd) { background: rgba(255, 255, 255, 0.0); }
&:nth-child(even) { background: $default-bg-3; }
&:hover { background: $hover-bg; }
}
}
}

View file

@ -0,0 +1,99 @@
const SwitchDevice = Vue.component('switch-device', {
template: '#tmpl-switch-device',
props: {
bus: {
type: Object,
},
device: {
type: Object,
default: () => {},
},
},
});
const SwitchType = Vue.component('switch-type', {
template: '#tmpl-switch-type',
props: {
bus: {
type: Object,
},
name: {
type: String,
},
devices: {
type: Object,
default: () => {},
},
},
});
Vue.component('switches', {
template: '#tmpl-switches',
props: ['config'],
data: function() {
return {
bus: new Vue({}),
plugins: {},
};
},
methods: {
refresh: async function() {
if (!this.config.plugins) {
console.warn('Please specify a list of switch plugins in your switch section configuration');
return;
}
const promises = this.config.plugins.map(plugin => {
return new Promise((resolve, reject) => {
request(plugin + '.status').then(status => {
const ret = {};
ret[plugin] = status;
resolve(ret);
});
});
});
const statuses = (await Promise.all(promises)).reduce((obj, status) => {
obj[Object.keys(status)[0]] = Object.values(status)[0].reduce((obj2, device) => {
device.type = Object.keys(status)[0];
obj2[device.id] = device;
return obj2;
}, {});
return obj;
}, {});
for (const [name, status] of Object.entries(statuses)) {
this.plugins[name] = status;
const switchType = new SwitchType();
switchType.bus = this.bus;
switchType.name = name;
switchType.devices = this.plugins[name];
switchType.$mount();
this.$refs.root.appendChild(switchType.$el);
}
},
toggle: async function(type, device) {
let status = await request(type + '.toggle', {device: device});
this.plugins[type][status.id].on = status.on;
},
},
mounted: function() {
const self = this;
this.refresh();
this.bus.$on('switch-toggled', (evt) => {
self.toggle(evt.type, evt.device);
});
},
});

View file

@ -7,6 +7,7 @@
'media.vlc': 'fa fa-film',
'music.mpd': 'fa fa-music',
'music.snapcast': 'fa fa-volume-up',
'switches': 'fa fa-toggle-on',
'tts': 'fa fa-comment',
'tts.google': 'fa fa-comment',
}
@ -20,7 +21,7 @@
{% if plugin in pluginIcons %}
<i class="{{ pluginIcons[plugin] }}"></i>
{% else %}
{% raw %}{{ plugin }}{{% endraw %}
{{ plugin }}
{% endif %}
</a>
</li>

View file

@ -0,0 +1,27 @@
<script type="text/x-template" id="tmpl-switches">
<div class="switches-root" ref="root"></div>
</script>
<script type="text/x-template" id="tmpl-switch-type">
<div class="switch-root" ref="root">
<div class="head" v-text="name.split('.').pop()"></div>
<div class="switches">
<switch-device :device="device"
v-for="device in devices"
:key="device.name"
:bus="bus">
</switch-device>
</div>
</div>
</script>
<script type="text/x-template" id="tmpl-switch-device">
<div class="device">
<div class="col-10 name" v-text="device.name"></div>
<div class="col-2 toggle">
<toggle-switch :value="device.on" @toggled="bus.$emit('switch-toggled', {type: device.type, device: device.id})"></toggle-switch>
</div>
</div>
</script>

View file

@ -1,33 +1,45 @@
from platypush.plugins import Plugin, action
class SwitchPlugin(Plugin):
"""
Abstract class for interacting with switch devices
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
@action
def on(self, args):
def on(self, device, *args, **kwargs):
""" Turn the device on """
raise NotImplementedError()
@action
def off(self, args):
def off(self, device, *args, **kwargs):
""" Turn the device off """
raise NotImplementedError()
@action
def toggle(self, args):
def toggle(self, device, *args, **kwargs):
""" Toggle the device status (on/off) """
raise NotImplementedError()
@action
def status(self):
""" Get the device state """
def status(self, device=None, *args, **kwargs):
""" Get the status of a specified device or of all the configured devices (default)"""
devices = self.devices
if device:
devices = [d for d in self.devices if d.get('id') == device or d.get('name') == device]
if devices:
return self.devices.pop(0)
else:
return None
return devices
@property
def devices(self):
raise NotImplementedError()
# vim:sw=4:ts=4:et:

View file

@ -7,33 +7,37 @@ from bluetooth.ble import DiscoveryService, GATTRequester
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
class Scanner(object):
"""
XXX The Scanner object doesn't work. Add your devices by address statically to the plugin configuration for now
instead of relying on scanning capabilities
"""
service_uuid = '1bc5d5a5-0200b89f-e6114d22-000da2cb'
def __init__(self, bt_interface=None, timeout_secs=None):
self.bt_interface = bt_interface
self.timeout_secs = timeout_secs if timeout_secs else 2
@classmethod
def _get_uuids(cls, device):
uuids = set()
for id in device['uuids']:
if isinstance(id, tuple):
for uuid in device['uuids']:
if isinstance(uuid, tuple):
uuid = ''
for i in range(0, len(id)):
token = struct.pack('<I', id[i])
for i in range(0, len(uuid)):
token = struct.pack('<I', uuid[i])
for byte in token:
uuid += hex(byte)[2:].zfill(2)
uuid += ('-' if i < len(id)-1 else '')
uuid += ('-' if i < len(uuid)-1 else '')
uuids.add(uuid)
else:
uuids.add(hex(id)[2:])
uuids.add(hex(uuid)[2:])
return uuids
def scan(self):
service = DiscoveryService(self.bt_interface) \
if self.bt_interface else DiscoveryService()
@ -46,9 +50,9 @@ class Scanner(object):
class Driver(object):
handle = 0x16
commands = {
'press' : '\x57\x01\x00',
'on' : '\x57\x01\x01',
'off' : '\x57\x01\x02',
'press': '\x57\x01\x00',
'on': '\x57\x01\x01',
'off': '\x57\x01\x02',
}
def __init__(self, device, bt_interface=None, timeout_secs=None):
@ -57,7 +61,6 @@ class Driver(object):
self.timeout_secs = timeout_secs if timeout_secs else 5
self.req = None
def connect(self):
if self.bt_interface:
self.req = GATTRequester(self.device, False, self.bt_interface)
@ -66,6 +69,7 @@ class Driver(object):
self.req.connect(True, 'random')
connect_start_time = time.time()
while not self.req.is_connected():
if time.time() - connect_start_time >= self.timeout_secs:
raise RuntimeError('Connection to {} timed out after {} seconds'
@ -96,7 +100,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
"""
def __init__(self, bt_interface=None, connect_timeout=None,
scan_timeout=None, devices={}, *args, **kwargs):
scan_timeout=None, devices=None, **kwargs):
"""
:param bt_interface: Bluetooth interface to use (e.g. hci0) default: first available one
:type bt_interface: str
@ -111,20 +115,30 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
:type devices: dict
"""
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
if devices is None:
devices = {}
self.bt_interface = bt_interface
self.connect_timeout = connect_timeout if connect_timeout else 5
self.scan_timeout = scan_timeout if scan_timeout else 2
self.devices = devices
self.configured_devices = devices
self.configured_devices_by_name = {
name: addr
for addr, name in self.configured_devices.items()
}
def _run(self, device, command=None):
if device in self.configured_devices_by_name:
device = self.configured_devices_by_name[device]
try:
# XXX this requires sudo and it's executed in its own process
# because the Switchbot plugin requires root privileges to send
# raw bluetooth messages on the interface. Make sure that the user
# that runs platypush has the right permissions to run this with sudo
return subprocess.check_output((
output = subprocess.check_output((
'sudo python3 -m platypush.plugins.switch.switchbot ' +
'--device {} ' +
('--interface {} '.format(self.bt_interface) if self.bt_interface else '') +
@ -134,6 +148,8 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
except subprocess.CalledProcessError as e:
raise RuntimeError(e.output.decode('utf-8'))
self.logger.info('Output of switchbot command: {}'.format(output))
return self.status(device)
@action
def press(self, device):
@ -146,7 +162,11 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
return self._run(device)
@action
def on(self, device):
def toggle(self, device, **kwargs):
return self.press(device)
@action
def on(self, device, **kwargs):
"""
Send a press-on button command to a device
@ -156,7 +176,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
return self._run(device, 'on')
@action
def off(self, device):
def off(self, device, **kwargs):
"""
Send a press-off button command to a device
@ -167,7 +187,11 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
@action
def scan(self):
""" Scan for available Switchbot devices nearby """
"""
Scan for available Switchbot devices nearby.
XXX This action doesn't work for now. Configure your devices statically for now instead of
relying on the scanner
"""
try:
return subprocess.check_output(
'sudo python3 -m platypush.plugins.switch.switchbot --scan ' +
@ -177,6 +201,17 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
except subprocess.CalledProcessError as e:
raise RuntimeError(e.output.decode('utf-8'))
@property
def devices(self):
return [
{
'address': addr,
'id': addr,
'name': name,
'on': False,
}
for addr, name in self.configured_devices.items()
]
# vim:sw=4:ts=4:et:

View file

@ -17,15 +17,14 @@ class SwitchTplinkPlugin(SwitchPlugin):
_ip_to_dev = {}
_alias_to_dev = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def _scan(self):
devices = Discover.discover()
self._ip_to_dev = {}
self._alias_to_dev = {}
for (ip, dev) in devices.items():
self._ip_to_dev[ip] = dev
self._alias_to_dev[dev.alias] = dev
@ -47,27 +46,8 @@ class SwitchTplinkPlugin(SwitchPlugin):
else:
raise RuntimeError('Device {} not found'.format(device))
@action
def status(self):
"""
:returns: The available device over the network as a
"""
devices = { 'devices': {
ip: {
'alias': dev.alias,
'current_consumption': dev.current_consumption(),
'host': dev.host,
'hw_info': dev.hw_info,
'on': dev.is_on,
} for (ip, dev) in self._scan().items()
} }
return devices
@action
def on(self, device):
def on(self, device, **kwargs):
"""
Turn on a device
@ -77,11 +57,10 @@ class SwitchTplinkPlugin(SwitchPlugin):
device = self._get_device(device)
device.turn_on()
return {'status':'on'}
return self.status(device)
@action
def off(self, device):
def off(self, device, **kwargs):
"""
Turn off a device
@ -91,11 +70,10 @@ class SwitchTplinkPlugin(SwitchPlugin):
device = self._get_device(device)
device.turn_off()
return {'status':'off'}
return self.status(device)
@action
def toggle(self, device):
def toggle(self, device, **kwargs):
"""
Toggle the state of a device (on/off)
@ -103,15 +81,36 @@ class SwitchTplinkPlugin(SwitchPlugin):
:type device: str
"""
device = self._get_device(device, use_cache=False)
device = self._get_device(device)
if device.is_on:
device.turn_off()
else:
device.turn_on()
return {'status': 'off' if device.is_off else 'on'}
return {
'current_consumption': device.current_consumption(),
'id': device.host,
'ip': device.host,
'host': device.host,
'hw_info': device.hw_info,
'name': device.alias,
'on': device.is_on,
}
@property
def devices(self):
return [
{
'current_consumption': dev.current_consumption(),
'id': ip,
'ip': ip,
'host': dev.host,
'hw_info': dev.hw_info,
'name': dev.alias,
'on': dev.is_on,
} for (ip, dev) in self._scan().items()
]
# vim:sw=4:ts=4:et:

View file

@ -1,12 +1,10 @@
import json
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
class SwitchWemoPlugin(SwitchPlugin):
"""
Plugin to control a Belkin WeMo smart switch
Plugin to control a Belkin WeMo smart switches
(https://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)
Requires:
@ -14,13 +12,13 @@ class SwitchWemoPlugin(SwitchPlugin):
* **ouimeaux** (``pip install ouimeaux``)
"""
def __init__(self, discovery_seconds=3, *args, **kwargs):
def __init__(self, discovery_seconds=3, **kwargs):
"""
:param discovery_seconds: Discovery time when scanning for devices (default: 3)
:type discovery_seconds: int
"""
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.discovery_seconds = discovery_seconds
self.env = None
@ -29,7 +27,7 @@ class SwitchWemoPlugin(SwitchPlugin):
self.logger.info('Starting WeMo discovery')
self._get_environment()
self.env.discover(seconds=self.discovery_seconds)
self.devices = self.env.devices
self._devices = self.env.devices
def _get_environment(self):
if not self.env:
@ -38,18 +36,17 @@ class SwitchWemoPlugin(SwitchPlugin):
self.env.start()
self._refresh_devices()
@action
def get_devices(self):
@property
def devices(self):
"""
Get the list of available devices
:returns: The list of devices.
Example output::
output = {
"devices": [
output = [
{
"host": "192.168.1.123",
"ip": "192.168.1.123",
"name": "Switch 1",
"state": 1,
"model": "Belkin Plugin Socket 1.0",
@ -60,40 +57,39 @@ class SwitchWemoPlugin(SwitchPlugin):
# ...
}
]
}
"""
self._refresh_devices()
return {
'devices': [
return [
{
'host': dev.host,
'id': dev.name,
'ip': dev.host,
'name': dev.name,
'state': dev.get_state(),
'model': dev.model,
'on': True if dev.get_state() else False,
'serialnumber': dev.serialnumber,
}
for (name, dev) in self.devices.items()
for (name, dev) in self._devices.items()
]
}
def _exec(self, method, device, *args, **kwargs):
self._get_environment()
if device not in self.devices:
if device not in self._devices:
self._refresh_devices()
if device not in self.devices:
if device not in self._devices:
raise RuntimeError('Device {} not found'.format(device))
self.logger.info('Executing {} on WeMo device {}'.
format(method, device))
dev = self.devices[device]
dev = self._devices[device]
getattr(dev, method)(*args, **kwargs)
return {'device': device, 'state': dev.get_state()}
return self.status(device)
@action
def on(self, device):
def on(self, device, **kwargs):
"""
Turn a switch on
@ -103,7 +99,7 @@ class SwitchWemoPlugin(SwitchPlugin):
return self._exec('on', device)
@action
def off(self, device):
def off(self, device, **kwargs):
"""
Turn a switch off
@ -113,7 +109,7 @@ class SwitchWemoPlugin(SwitchPlugin):
return self._exec('off', device)
@action
def toggle(self, device):
def toggle(self, device, **kwargs):
"""
Toggle the state of a switch (on/off)
@ -124,4 +120,3 @@ class SwitchWemoPlugin(SwitchPlugin):
# vim:sw=4:ts=4:et: