From 2e8383760b2f32e8d068a4b235b6379cd3f06c17 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz 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