From 4c8ce6673e2efe96a36a2af2b774b3e43cc4a89c Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Sun, 14 Jun 2015 22:27:44 +0200 Subject: Add resouce manager layer. --- ParrotZik.py | 172 ++++++++++++++++++---------------------------------- ParrotZikTray | 36 +++++++---- resource_manager.py | 135 +++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 4 files changed, 217 insertions(+), 128 deletions(-) create mode 100644 resource_manager.py diff --git a/ParrotZik.py b/ParrotZik.py index d07f8b9..8e4b31b 100644 --- a/ParrotZik.py +++ b/ParrotZik.py @@ -1,11 +1,14 @@ import sys + +from resource_manager import GenericResourceManager +from resource_manager import Version1ResourceManager +from resource_manager import Version2ResourceManager + if sys.platform == "darwin": import lightblue else: import bluetooth -import ParrotProtocol -from BeautifulSoup import BeautifulSoup def connect(addr=None): uuids = ["0ef0f502-f0ee-46c9-986c-54ed027807fb", @@ -23,7 +26,7 @@ def connect(addr=None): if len(service_matches) == 0: print "Failed to find Parrot Zik RFCOMM service" - return ParrotZikBase(ParrotZikApi(None)) + return GenericResourceManager(None) if sys.platform == "darwin": first_match = service_matches[0] @@ -47,64 +50,9 @@ def connect(addr=None): sock.send('\x00\x03\x00') data = sock.recv(1024) - api = ParrotZikApi(sock) - if api.version.startswith('1'): - return ParrotZikVersion1(api) - else: - return ParrotZikVersion2(api) - - -class ParrotZikApi(object): - def __init__(self, socket): - self.sock = socket - self.notifications = [] - - @property - def version(self): - answer = self.get("/api/software/version") - try: - return answer.software["version"] - except KeyError: - return answer.software['sip6'] - - def get(self, resource): - message = ParrotProtocol.getRequest(resource + '/get') - return self.send_message(message) - - def toggle_on(self, resource): - message = ParrotProtocol.getRequest(resource + '/enable') - return self.send_message(message) - - def toggle_off(self, resource): - message = ParrotProtocol.getRequest(resource + '/disable') - return self.send_message(message) - - def set(self, resource, arg): - message = ParrotProtocol.setRequest(resource + '/set', str(arg).lower()) - return self.send_message(message) - - def send_message(self, message): - try: - self.sock.send(str(message)) - except Exception: - self.sock = "" - return - if sys.platform == "darwin": - self.sock.recv(30) - else: - self.sock.recv(7) + return GenericResourceManager(sock) - data = BeautifulSoup(self.sock.recv(1024)) - while not data.answer: - if data.notify: - self.notifications.append(data.notify) - else: - raise AssertionError('Unknown response') - data = BeautifulSoup(self.sock.recv(1024)) - return data.answer - def close(self): - self.sock.close() class BatteryStates: CHARGED = 'charged' @@ -152,40 +100,43 @@ class NoiseControlTypes: class ParrotZikBase(object): - def __init__(self, api): - self.api = api + def __init__(self, resource_manager): + self.resource_manager = resource_manager @property def version(self): - return self.api.version + return self.resource_manager.api_version + + def refresh_battery(self): + self.resource_manager.fetch('/api/system/battery') @property def battery_state(self): - answer = self.api.get("/api/system/battery") + answer = self.resource_manager.get("/api/system/battery") return answer.system.battery["state"] def get_battery_level(self, field_name): - answer = self.api.get("/api/system/battery") + answer = self.resource_manager.get("/api/system/battery") return int(answer.system.battery[field_name]) @property def friendly_name(self): - answer = self.api.get("/api/bluetooth/friendlyname") + answer = self.resource_manager.get("/api/bluetooth/friendlyname") return answer.bluetooth["friendlyname"] @property def auto_connect(self): - answer = self.api.get("/api/system/auto_connection/enabled") + answer = self.resource_manager.get("/api/system/auto_connection/enabled") return self._result_to_bool( answer.system.auto_connection["enabled"]) @auto_connect.setter def auto_connect(self, arg): - self.api.set("/api/system/auto_connection/enabled", arg) + self.resource_manager.set("/api/system/auto_connection/enabled", arg) @property def anc_phone_mode(self): - answer = self.api.get("/api/system/anc_phone_mode/enabled") + answer = self.resource_manager.get("/api/system/anc_phone_mode/enabled") return self._result_to_bool( answer.system.anc_phone_mode["enabled"]) @@ -199,125 +150,118 @@ class ParrotZikBase(object): class ParrotZikVersion1(ParrotZikBase): - ''' - Known resources: - * /api/software/version - * /api/system/battery - * /api/bluetooth/friendlyname - * /api/system/auto_connection/enabled - * /api/system/anc_phone_mode/enabled - * /api/audio/specific_mode/enabled - * /api/audio/sound_effect/enabled - * /api/audio/noise_cancellation/enabled - ''' + def __init__(self, resource_manager): + super(ParrotZikVersion1, self).__init__( + resource_manager.get_resource_manager( + Version1ResourceManager)) + + @property + def version(self): + answer = self.resource_manager.get('/api/software/version') + return answer.software['version'] + @property def battery_level(self): return int(self.get_battery_level('level')) @property def lou_reed_mode(self): - answer = self.api.get("/api/audio/specific_mode/enabled") + answer = self.resource_manager.get("/api/audio/specific_mode/enabled") return self._result_to_bool( answer.audio.specific_mode["enabled"]) @lou_reed_mode.setter def lou_reed_mode(self, arg): - self.api.get("/api/audio/specific_mode/enabled", arg) + self.resource_manager.get("/api/audio/specific_mode/enabled", arg) @property def concert_hall(self): - answer = self.api.get("/api/audio/sound_effect/enabled") + answer = self.resource_manager.get("/api/audio/sound_effect/enabled") return self._result_to_bool( answer.audio.sound_effect["enabled"]) @concert_hall.setter def concert_hall(self, arg): - self.api.get("/api/audio/sound_effect/enabled", arg) + self.resource_manager.get("/api/audio/sound_effect/enabled", arg) @property def cancel_noise(self): - answer = self.api.get("/api/audio/noise_cancellation/enabled") + answer = self.resource_manager.get("/api/audio/noise_cancellation/enabled") return self._result_to_bool( answer.audio.noise_cancellation["enabled"]) @cancel_noise.setter def cancel_noise(self, arg): - self.api.set("/api/audio/noise_cancellation/enabled", arg) + self.resource_manager.set("/api/audio/noise_cancellation/enabled", arg) class ParrotZikVersion2(ParrotZikBase): - ''' - Known resources: - * /api/software/version - * /api/system/battery - * /api/system/pi - * /api/bluetooth/friendlyname - * /api/system/auto_connection/enabled - * /api/system/anc_phone_mode/enabled - * /api/flight_mode - * /api/sound_effect/enabled - * /api/sound_effect/room_size - * /api/sound_effect/angle - * /api/audio/noise - * /api/audio/noise_control - * /api/audio/noise_control/enabled - ''' + def __init__(self, resource_manager): + super(ParrotZikVersion2, self).__init__( + resource_manager.get_resource_manager( + Version2ResourceManager)) + + @property + def version(self): + answer = self.resource_manager.get('/api/software/version') + return answer.software['sip6'] + @property def battery_level(self): return self.get_battery_level('percent') @property def flight_mode(self): - answer = self.api.get('/api/flight_mode') + answer = self.resource_manager.get('/api/flight_mode') return self._result_to_bool(answer.flight_mode['enabled']) @flight_mode.setter def flight_mode(self, arg): if arg: - self.api.toggle_on('/api/flight_mode') + self.resource_manager.toggle_on('/api/flight_mode') else: - self.api.toggle_off('/api/flight_mode') + self.resource_manager.toggle_off('/api/flight_mode') @property def sound_effect(self): - answer = self.api.get('/api/audio/sound_effect/enabled') + answer = self.resource_manager.get('/api/audio/sound_effect/enabled') return self._result_to_bool(answer.audio.sound_effect['enabled']) @sound_effect.setter def sound_effect(self, arg): - self.api.set('/api/audio/sound_effect/enabled', arg) + self.resource_manager.set('/api/audio/sound_effect/enabled', arg) @property def room(self): - answer = self.api.get('/api/audio/sound_effect/room_size') + answer = self.resource_manager.get('/api/audio/sound_effect/room_size') return answer.audio.sound_effect['room_size'] @room.setter def room(self, arg): - self.api.set('/api/audio/sound_effect/room_size', arg) + self.resource_manager.set('/api/audio/sound_effect/room_size', arg) @property def external_noise(self): - answer = self.api.get('/api/audio/noise') + answer = self.resource_manager.get('/api/audio/noise') return int(answer.audio.noise['external']) @property def internal_noise(self): - answer = self.api.get('/api/audio/noise') + answer = self.resource_manager.get('/api/audio/noise') return int(answer.audio.noise['internal']) @property def angle(self): - answer = self.api.get('/api/audio/sound_effect/angle') + answer = self.resource_manager.get('/api/audio/sound_effect/angle') return int(answer.audio.sound_effect['angle']) @angle.setter def angle(self, arg): - self.api.set('/api/audio/sound_effect/angle', arg) + self.resource_manager.set('/api/audio/sound_effect/angle', arg) @property def noise_control(self): - answer = self.api.get('/api/audio/noise_control') + answer = self.resource_manager.get('/api/audio/noise_control') return NoiseControl.from_noise_control(answer.audio.noise_control) @noise_control.setter @@ -326,5 +270,5 @@ class ParrotZikVersion2(ParrotZikBase): @property def noise_control_enabled(self): - answer = self.api.get('/api/audio/noise_control/enabled') + answer = self.resource_manager.get('/api/audio/noise_control/enabled') return self._result_to_bool(answer.audio.noise_control['enabled']) diff --git a/ParrotZikTray b/ParrotZikTray index 373e9ee..6850a3f 100755 --- a/ParrotZikTray +++ b/ParrotZikTray @@ -5,6 +5,8 @@ import gtk import BluetoothPairedDevices from ParrotZik import BatteryStates +from ParrotZik import ParrotZikVersion1 +from ParrotZik import ParrotZikVersion2 from ParrotZik import NoiseControlTypes from ParrotZik import connect from ParrotZik import Rooms @@ -68,12 +70,12 @@ class ParrotZikIndicator(SysIndicator): mac = BluetoothPairedDevices.ParrotZikMac() if mac: self.info_item.set_label("Connecting") - parrot = connect(mac) - if parrot.api.sock: - if parrot.version.startswith('1'): - self.version_1_interface.activate(parrot) + resource_manager = connect(mac) + if resource_manager.sock: + if resource_manager.api_version.startswith('1'): + self.version_1_interface.activate(resource_manager) else: - self.version_2_interface.activate(parrot) + self.version_2_interface.activate(resource_manager) self.autorefresh(self) self.autorefresh.start(self, REFRESH_FREQUENCY) self.reconnect.stop() @@ -114,12 +116,12 @@ class ParrotZikBaseInterface(object): @property def connected(self): if self.parrot: - return self.parrot.api.sock + return self.parrot.resource_manager.sock else: return False - def activate(self, parrot): - self.parrot = parrot + def activate(self, resource_manager): + self.parrot = self.parrot_class(resource_manager) self.read_battery() self.indicator.info_item.set_label("Connected to: " + self.parrot.friendly_name) @@ -134,6 +136,10 @@ class ParrotZikBaseInterface(object): self.indicator.active_interface = self self.indicator.menu.reposition() + @property + def parrot_class(self): + raise NotImplementedError + def deactivate(self): self.parrot = None self.battery_level.hide() @@ -157,9 +163,9 @@ class ParrotZikBaseInterface(object): def read_battery(self): if self.connected: + self.parrot.refresh_battery() battery_level = self.parrot.battery_level battery_state = self.parrot.battery_state - if battery_state == BatteryStates.CHARGING: self.indicator.setIcon("zik-battery-charging") elif battery_level > 80: @@ -182,6 +188,8 @@ class ParrotZikBaseInterface(object): class ParrotZikVersion1Interface(ParrotZikBaseInterface): + parrot_class = ParrotZikVersion1 + def __init__(self, indicator): super(ParrotZikVersion1Interface, self).__init__(indicator) self.noise_cancelation = MenuItem( @@ -196,11 +204,11 @@ class ParrotZikVersion1Interface(ParrotZikBaseInterface): self.indicator.menu.append(self.lou_reed_mode) self.indicator.menu.append(self.concert_hall_mode) - def activate(self, parrot): + def activate(self, resource_manager): self.noise_cancelation.show() self.lou_reed_mode.show() self.concert_hall_mode.show() - super(ParrotZikVersion1Interface, self).activate(parrot) + super(ParrotZikVersion1Interface, self).activate(resource_manager) self.noise_cancelation.set_active(self.parrot.cancel_noise) self.lou_reed_mode.set_active(self.parrot.lou_reed_mode) @@ -238,6 +246,8 @@ class ParrotZikVersion1Interface(ParrotZikBaseInterface): class ParrotZikVersion2Interface(ParrotZikBaseInterface): + parrot_class = ParrotZikVersion2 + def __init__(self, indicator): self.room_dirty = False self.angle_dirty = False @@ -329,11 +339,11 @@ class ParrotZikVersion2Interface(ParrotZikBaseInterface): self.indicator.menu.append(self.noise_cancelation) self.indicator.menu.append(self.flight_mode) - def activate(self, parrot): + def activate(self, resource_manager): self.noise_cancelation.show() self.flight_mode.show() self.room_sound_effect.show() - super(ParrotZikVersion2Interface, self).activate(parrot) + super(ParrotZikVersion2Interface, self).activate(resource_manager) self.read_noise_cancelation() self.flight_mode.set_active(self.parrot.flight_mode) self.read_sound_effect_room() diff --git a/resource_manager.py b/resource_manager.py new file mode 100644 index 0000000..1a6e224 --- /dev/null +++ b/resource_manager.py @@ -0,0 +1,135 @@ +import sys +from BeautifulSoup import BeautifulSoup +import ParrotProtocol + + +class ResourceManagerBase(object): + resources = [ + ] + + def __init__(self, socket, resource_values=None): + self.sock = socket + self.resource_values = resource_values or {} + + def get(self, resource): + try: + return self.resource_values[resource] + except KeyError: + return self.fetch(resource) + + def fetch(self, resource): + assert resource in self.resources, 'Unknown resource {}'.format(resource) + message = ParrotProtocol.getRequest(resource + '/get') + result = self.send_message(message) + self.resource_values[resource] = result + return result + + def toggle_on(self, resource): + assert resource in self.resources, 'Unknown resource {}'.format(resource) + message = ParrotProtocol.getRequest(resource + '/enable') + self.send_message(message) + self.fetch(resource) + + def toggle_off(self, resource): + assert resource in self.resources, 'Unknown resource {}'.format(resource) + message = ParrotProtocol.getRequest(resource + '/disable') + self.send_message(message) + self.fetch(resource) + + def set(self, resource, arg): + assert resource in self.resources, 'Unknown resource {}'.format(resource) + message = ParrotProtocol.setRequest(resource + '/set', str(arg).lower()) + self.send_message(message) + self.fetch(resource) + + def send_message(self, message): + try: + self.sock.send(str(message)) + except Exception: + self.sock = "" + return + else: + return self.get_answer() + + def get_answer(self): + data = self.receive_message() + while not data.answer: + if data.notify: + self.handle_notification(data.notify) + else: + raise AssertionError('Unknown response') + data = self.receive_message() + return data.answer + + def handle_notification(self, notification): + + self.fetch(notification['path'].rsplit('/', 1)[0].encode('utf-8')) + + def receive_message(self): + if sys.platform == "darwin": + self.sock.recv(30) + else: + self.sock.recv(7) + return BeautifulSoup(self.sock.recv(1024)) + + def close(self): + self.sock.close() + + +class GenericResourceManager(ResourceManagerBase): + resources = { + '/api/software/version', + } + + def __init__(self, sock): + super(GenericResourceManager, self).__init__(sock) + self.notifications = [] + + def handle_notification(self, notification): + self.notifications.append(notification) + + def get_resource_manager(self, resource_manager_class): + resource_manager = resource_manager_class(self.sock, self.resource_values) + for notitifaction in self.notifications: + resource_manager.handle_notification(notitifaction) + return resource_manager + + @property + def api_version(self): + answer = self.get("/api/software/version") + try: + return answer.software["version"] + except KeyError: + return answer.software['sip6'] + + +class Version1ResourceManager(ResourceManagerBase): + resources = { + '/api/software/version', + '/api/system/battery', + '/api/bluetooth/friendlyname', + '/api/system/auto_connection/enabled', + '/api/system/anc_phone_mode/enabled', + '/api/audio/specific_mode/enabled', + '/api/audio/sound_effect/enabled', + '/api/audio/noise_cancellation/enabled', + } + +class Version2ResourceManager(ResourceManagerBase): + resources = { + '/api/software/version', + '/api/system/battery', + '/api/system/pi', + '/api/bluetooth/friendlyname', + '/api/system/auto_connection/enabled', + '/api/system/anc_phone_mode/enabled', + '/api/flight_mode', + '/api/audio/sound_effect/enabled', + '/api/audio/sound_effect/room_size', + '/api/audio/sound_effect/angle', + '/api/audio/noise', + '/api/audio/noise_control', + '/api/audio/noise_control/enabled', + '/api/audio/track/metadata', + } + diff --git a/setup.py b/setup.py index b919e8f..de58f17 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ setup( 'BeautifulSoup', 'bluetooth' ], - py_modules=['ParrotZik', 'ParrotProtocol'], + py_modules=['ParrotZik', 'ParrotProtocol', 'resource_manager'], scripts=["ParrotZikTray"] ) -- cgit v1.2.1