From 4c8ce6673e2efe96a36a2af2b774b3e43cc4a89c Mon Sep 17 00:00:00 2001
From: Marek Siarkowicz <mareksiarkowicz@gmail.com>
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