From 2e8383760b2f32e8d068a4b235b6379cd3f06c17 Mon Sep 17 00:00:00 2001
From: Marek Siarkowicz <mareksiarkowicz@gmail.com>
Date: Sat, 13 Jun 2015 00:06:19 +0200
Subject: First step of zik 2.0 feature implementation.

---
 ParrotZik.py    | 204 +++++++++++++++++++++++++++++++-------------------------
 ParrotZikTray   | 119 +++++++++++++++++++++++++++++++--
 SysIndicator.py |   6 ++
 3 files changed, 234 insertions(+), 95 deletions(-)

diff --git a/ParrotZik.py b/ParrotZik.py
index 514be50..6c1cd69 100644
--- a/ParrotZik.py
+++ b/ParrotZik.py
@@ -7,63 +7,111 @@ else:
 import ParrotProtocol
 from BeautifulSoup import BeautifulSoup
 
-class ParrotZik(object):
-    def __init__(self, addr=None):
-        uuids = ["0ef0f502-f0ee-46c9-986c-54ed027807fb",
-                 "8B6814D3-6CE7-4498-9700-9312C1711F63"]
+def connect(addr=None):
+    uuids = ["0ef0f502-f0ee-46c9-986c-54ed027807fb",
+             "8B6814D3-6CE7-4498-9700-9312C1711F63"]
+
+    if sys.platform == "darwin":
+        service_matches = lightblue.findservices(
+            name="Parrot RFcomm service", addr=addr)
+    else:
+        for uuid in uuids:
+            service_matches = bluetooth.find_service(uuid=uuid,
+                                                     address=addr)
+            if service_matches:
+                break
+
+    if len(service_matches) == 0:
+        print "Failed to find Parrot Zik RFCOMM service"
+        return ParrotZikBase(ParrotZikApi(None))
+
+    if sys.platform == "darwin":
+        first_match = service_matches[0]
+        port = first_match[1]
+        name = first_match[2]
+        host = first_match[0]
+    else:
+        first_match = service_matches[0]
+        port = first_match["port"]
+        name = first_match["name"]
+        host = first_match["host"]
+
+    print "Connecting to \"%s\" on %s" % (name, host)
+
+    if sys.platform == "darwin":
+        sock = lightblue.socket()
+    else:
+        sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
+
+    sock.connect((host, port))
+
+    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
 
-        if sys.platform == "darwin":
-            service_matches = lightblue.findservices(
-                name="Parrot RFcomm service", addr=addr)
-        else:
-            for uuid in uuids:
-                service_matches = bluetooth.find_service(uuid=uuid,
-                                                         address=addr)
-                if service_matches:
-                    break
-
-        if len(service_matches) == 0:
-            print "Failed to find Parrot Zik RFCOMM service"
-            self.sock = ""
-            return
+    @property
+    def version(self):
+        data = self.get("/api/software/version/get")
+        try:
+            return data.answer.software["version"]
+        except KeyError:
+            return data.answer.software['sip6']
 
-        if sys.platform == "darwin":
-            first_match = service_matches[0]
-            port = first_match[1]
-            name = first_match[2]
-            host = first_match[0]
-        else:
-            first_match = service_matches[0]
-            port = first_match["port"]
-            name = first_match["name"]
-            host = first_match["host"]
+    def get(self, message):
+        message = ParrotProtocol.getRequest(message)
+        return self.send_message(message)
 
-        print "Connecting to \"%s\" on %s" % (name, host)
+    def set(self, message, arg):
+        message = ParrotProtocol.setRequest(message, 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 = lightblue.socket()
+            data = self.sock.recv(30)
         else:
-            self.sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
+            data = self.sock.recv(7)
+        data = self.sock.recv(1024)
+        data = BeautifulSoup(data)
+        return data
 
-        self.sock.connect((host, port))
+    def close(self):
+        self.sock.close()
 
-        self.sock.send('\x00\x03\x00')
-        data = self.sock.recv(1024)
 
+class ParrotZikBase(object):
+    def __init__(self, api):
+        self.api = api
         self.BatteryLevel = 100
         self.BatteryCharging = False
 
+    @property
+    def version(self):
+        return self.api.version
+
     @property
     def battery_state(self):
-        data = self.get("/api/system/battery/get")
+        data = self.api.get("/api/system/battery/get")
         return data.answer.system.battery["state"]
 
-    @property
-    def battery_level(self):
-        data = self.get("/api/system/battery/get")
+    def get_battery_level(self, field_name):
+        data = self.api.get("/api/system/battery/get")
         try:
-            if data.answer.system.battery["level"] != '':
-                self.BatteryLevel = data.answer.system.battery["level"]
+            if data.answer.system.battery[field_name] != '':
+                self.BatteryLevel = data.answer.system.battery[field_name]
             if data.answer.system.battery["state"] == 'charging':
                 self.BatteryCharging = True
             else:
@@ -78,38 +126,30 @@ class ParrotZik(object):
 
         return self.BatteryLevel
 
-    @property
-    def version(self):
-        data = self.get("/api/software/version/get")
-        try:
-            return data.answer.software["version"]
-        except KeyError:
-            return data.answer.software['sip6']
-
     @property
     def friendly_name(self):
-        data = self.get("/api/bluetooth/friendlyname/get")
+        data = self.api.get("/api/bluetooth/friendlyname/get")
         return data.answer.bluetooth["friendlyname"]
 
     @property
     def auto_connect(self):
-        data = self.get("/api/system/auto_connection/enabled/get")
+        data = self.api.get("/api/system/auto_connection/enabled/get")
         return self._result_to_bool(
             data.answer.system.auto_connection["enabled"])
 
     @auto_connect.setter
     def auto_connect(self, arg):
-        self.set("/api/system/auto_connection/enabled/set", arg)
+        self.api.get("/api/system/auto_connection/enabled/set", arg)
 
     @property
     def anc_phone_mode(self):
-        data = self.get("/api/system/anc_phone_mode/enabled/get")
+        data = self.api.get("/api/system/anc_phone_mode/enabled/get")
         return self._result_to_bool(
             data.answer.system.anc_phone_mode["enabled"])
 
     @property
     def noise_cancel(self):
-        data = self.get("/api/audio/noise_cancellation/enabled/get")
+        data = self.api.get("/api/audio/noise_cancellation/enabled/get")
         try:
             return self._result_to_bool(
                 data.answer.audio.noise_cancellation["enabled"])
@@ -118,24 +158,11 @@ class ParrotZik(object):
 
     @noise_cancel.setter
     def noise_cancel(self, arg):
-        self.set("/api/audio/noise_cancellation/enabled/set", arg)
-
-    @property
-    def lou_reed_mode(self):
-        data = self.get("/api/audio/specific_mode/enabled/get")
-        try:
-            return self._result_to_bool(
-                data.answer.audio.specific_mode["enabled"])
-        except TypeError:
-            return False
-
-    @lou_reed_mode.setter
-    def lou_reed_mode(self, arg):
-        self.set("/api/audio/specific_mode/enabled/set", arg)
+        self.api.get("/api/audio/noise_cancellation/enabled/set", arg)
 
     @property
     def concert_hall(self):
-        data = self.get("/api/audio/sound_effect/enabled/get")
+        data = self.api.get("/api/audio/sound_effect/enabled/get")
         try:
             return self._result_to_bool(
                 data.answer.audio.sound_effect["enabled"])
@@ -144,7 +171,7 @@ class ParrotZik(object):
 
     @concert_hall.setter
     def concert_hall(self, arg):
-        self.set("/api/audio/sound_effect/enabled/set", arg)
+        self.api.get("/api/audio/sound_effect/enabled/set", arg)
 
     def _result_to_bool(self, result):
         if result == "true":
@@ -154,27 +181,24 @@ class ParrotZik(object):
         else:
             raise AssertionError(result)
 
-    def get(self, message):
-        message = ParrotProtocol.getRequest(message)
-        return self.send_message(message)
 
-    def set(self, message, arg):
-        message = ParrotProtocol.setRequest(message, str(arg).lower())
-        return self.send_message(message)
+class ParrotZikVersion1(ParrotZikBase):
+    @property
+    def battery_level(self):
+        return self.get_battery_level('level')
 
-    def send_message(self, message):
-        try:
-            self.sock.send(str(message))
-        except Exception:
-            self.sock = ""
-            return
-        if sys.platform == "darwin":
-            data = self.sock.recv(30)
-        else:
-            data = self.sock.recv(7)
-        data = self.sock.recv(1024)
-        data = BeautifulSoup(data)
-        return data
+    @property
+    def lou_reed_mode(self):
+        data = self.api.get("/api/audio/specific_mode/enabled/get")
+        return self._result_to_bool(
+            data.answer.audio.specific_mode["enabled"])
 
-    def close(self):
-        self.sock.close()
+    @lou_reed_mode.setter
+    def lou_reed_mode(self, arg):
+        self.api.get("/api/audio/specific_mode/enabled/set", arg)
+
+
+class ParrotZikVersion2(ParrotZikBase):
+    @property
+    def battery_level(self):
+        return self.get_battery_level('percent')
diff --git a/ParrotZikTray b/ParrotZikTray
index 2245807..b955a47 100755
--- a/ParrotZikTray
+++ b/ParrotZikTray
@@ -43,6 +43,7 @@ class ParrotZikIndicator(SysIndicator):
         self.menu.append(self.info_item)
 
         self.version_1_interface = ParrotZikVersion1Interface(self)
+        self.version_2_interface = ParrotZikVersion2Interface(self)
         self.quit = MenuItem("Quit", gtk.main_quit, checkitem=True)
         self.menu.append(self.quit)
 
@@ -53,7 +54,7 @@ class ParrotZikIndicator(SysIndicator):
     @repeat
     def reconnect(self):
         if self.active_interface:
-            if not self.active_interface.parrot.sock:
+            if not self.active_interface.connected:
                 print "Lost connection"
                 self.active_interface.deactivate()
             else:
@@ -61,9 +62,12 @@ class ParrotZikIndicator(SysIndicator):
         else:
             mac = BluetoothPairedDevices.ParrotZikMac()
             if mac:
-                parrot = ParrotZik.ParrotZik(mac)
-                if parrot.sock:
-                    self.version_1_interface.activate(parrot)
+                parrot = ParrotZik.connect(mac)
+                if parrot.api.sock:
+                    if parrot.version.startswith('1'):
+                        self.version_1_interface.activate(parrot)
+                    else:
+                        self.version_2_interface.activate(parrot)
                     self.autorefresh(self)
                     self.autorefresh.start(self, REFRESH_FREQUENCY)
                     self.reconnect.stop()
@@ -103,7 +107,7 @@ class ParrotZikBaseInterface(object):
     @property
     def connected(self):
         if self.parrot:
-            return self.parrot.sock
+            return self.parrot.api.sock
         else:
             return False
 
@@ -138,6 +142,9 @@ class ParrotZikBaseInterface(object):
             self.parrot.auto_connection = self.auto_connection.get_active()
             self.auto_connection.set_active(self.parrot.auto_connection)
 
+    def refresh(self):
+        raise NotImplementedError
+
 
 class ParrotZikVersion1Interface(ParrotZikBaseInterface):
     def __init__(self, indicator):
@@ -223,6 +230,108 @@ class ParrotZikVersion1Interface(ParrotZikBaseInterface):
             self.deactivate()
 
 
+class ParrotZikVersion2Interface(ParrotZikBaseInterface):
+    def __init__(self, indicator):
+        super(ParrotZikVersion2Interface, self).__init__(indicator)
+        self.noise_cancelation = MenuItem("Noise Cancellation", None,
+                                          sensitive=True, visible=False)
+        self.noise_cancelation_submenu = Menu()
+        self.noise_cancelation.set_submenu(self.noise_cancelation_submenu)
+
+        self.noise_cancelation_enabled = MenuItem("Enabled", self.toggleANC,
+                                                  sensitive=False, checkitem=True)
+        self.noise_cancelation_mode0 = MenuItem("Mode0", self.toggledummy,
+                                                sensitive=False, checkitem=True)
+        self.noise_cancelation_mode1 = MenuItem("Mode1", self.toggledummy,
+                                                sensitive=False, checkitem=True)
+        self.noise_cancelation_mode2 = MenuItem("Mode2", self.toggledummy,
+                                                sensitive=False, checkitem=True)
+        self.noise_cancelation_mode3 = MenuItem("Mode3", self.toggledummy,
+                                                sensitive=False, checkitem=True)
+        self.noise_cancelation_submenu.append(self.noise_cancelation_enabled)
+        self.noise_cancelation_submenu.append(self.noise_cancelation_mode0)
+        self.noise_cancelation_submenu.append(self.noise_cancelation_mode1)
+        self.noise_cancelation_submenu.append(self.noise_cancelation_mode2)
+        self.noise_cancelation_submenu.append(self.noise_cancelation_mode3)
+        self.indicator.menu.append(self.noise_cancelation)
+
+        self.concert_hall_mode = MenuItem(
+            "Sound Mode", None, sensitive=True, checkitem=False, visible=False)
+        self.concert_hall_mode_submenu = Menu()
+        self.concert_hall_mode.set_submenu(self.concert_hall_mode_submenu)
+
+        self.concert_hall_mode_enabled = MenuItem("Enabled", None,
+                                                  sensitive=True, checkitem=True)
+        self.concert_hall = MenuItem("Concert Hall", None,
+                                     sensitive=True, checkitem=True)
+        self.jazz_mode = MenuItem("Jazz Club", None,
+                                  sensitive=True, checkitem=True)
+        self.living_mode = MenuItem("Libing Room", None,
+                                    sensitive=True, checkitem=True)
+        self.silent_mode = MenuItem("Silent Room", None,
+                                    sensitive=True, checkitem=True)
+        self.concert_hall_mode_submenu.append(self.concert_hall_mode_enabled)
+        self.concert_hall_mode_submenu.append(self.concert_hall)
+        self.concert_hall_mode_submenu.append(self.jazz_mode)
+        self.concert_hall_mode_submenu.append(self.living_mode)
+        self.concert_hall_mode_submenu.append(self.silent_mode)
+        self.indicator.menu.append(self.concert_hall_mode)
+
+        self.flight_mode = MenuItem("Flight Mode", None,
+                                    sensitive=True, checkitem=True, visible=False)
+        self.indicator.menu.append(self.flight_mode)
+
+    def activate(self, parrot):
+        self.noise_cancelation.show()
+        self.concert_hall_mode.show()
+        self.flight_mode.show()
+        super(ParrotZikVersion2Interface, self).activate(parrot)
+        self.noise_cancelation_enabled.set_active(self.parrot.noise_cancel)
+        # self.flight_mode.set_active(self.parrot.flight_mode)
+
+    def toggleANC(self, widget):
+        if self.connected:
+            self.parrot.noise_cancel = self.noise_cancelation.get_active()
+            self.noise_cancelation.set_active(self.parrot.noise_cancel)
+
+    def toggledummy(self, widget):
+        print(widget.get_name())
+
+    def refresh(self):
+        if self.connected:
+            print "Updating battery"
+            self.batteryLevel = int(self.parrot.battery_level)
+
+            if self.parrot.BatteryCharging:
+                self.batteryLevel = "Charging"
+                self.indicator.setIcon("zik-battery-charging")
+                self.batteryLevel = "Unknown"
+                self.batteryState = "Charging"
+            elif self.batteryLevel > 80:
+                self.indicator.setIcon("zik-battery-100")
+                self.batteryState = "In Use"
+            elif self.batteryLevel > 60:
+                self.indicator.setIcon("zik-battery-080")
+                self.batteryState = "In Use"
+            elif self.batteryLevel > 40:
+                self.indicator.setIcon("zik-battery-060")
+                self.batteryState = "In Use"
+            elif self.batteryLevel > 20:
+                self.indicator.setIcon("zik-battery-040")
+                self.batteryState = "In Use"
+            else:
+                self.indicator.setIcon("zik-battery-low")
+                self.batteryState = "In Use"
+
+            self.indicator.info_item.set_label("Connected to: " + self.name)
+            self.firmware_version.set_label(
+                "Firmware version: " + self.version)
+            self.battery_state.set_label("State: " + self.batteryState)
+            self.battery_level.set_label(
+                "Battery Level: " + str(self.batteryLevel))
+        else:
+            self.deactivate()
+
 if __name__ == "__main__":
     indicator = ParrotZikIndicator()
     indicator.main()
diff --git a/SysIndicator.py b/SysIndicator.py
index 804b001..6aa4686 100644
--- a/SysIndicator.py
+++ b/SysIndicator.py
@@ -177,6 +177,9 @@ class MenuItemBase(object):
     def hide(self):
         self.base_item.hide()
 
+    def set_submenu(self, menu):
+        raise NotImplementedError
+
 class GTKMenuItem(MenuItemBase):
     def __init__(self, name, action, sensitive=True, checkitem=False, visible=True):
         if checkitem:
@@ -199,6 +202,9 @@ class GTKMenuItem(MenuItemBase):
     def set_label(self, option):
         return self.base_item.set_label(option)
 
+    def set_submenu(self, menu):
+        self.base_item.set_submenu(menu.gtk_menu)
+
 
 class NSMenuItem(MenuItemBase):
     def __init__(self, name, action, sensitive=True, checkitem=False, visible=True):
-- 
cgit v1.2.1