From 947a9f7d10bc3c54939cfa72dab27b2e64034318 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Mon, 15 Jun 2015 11:33:33 +0200 Subject: Create packet. --- bluetooth_paired_devices.py | 107 -------- indicator.py | 254 ------------------ message.py | 30 --- parrot_zik.py | 222 --------------- parrot_zik/__init__.py | 0 parrot_zik/bluetooth_paired_devices.py | 107 ++++++++ parrot_zik/indicator.py | 254 ++++++++++++++++++ parrot_zik/message.py | 30 +++ parrot_zik/parrot_zik_model.py | 222 +++++++++++++++ parrot_zik/parrot_zik_tray | 472 ++++++++++++++++++++++++++++++++ parrot_zik/resource_manager.py | 176 ++++++++++++ parrot_zik/status_app_mac.py | 25 ++ parrot_zik_tray | 474 --------------------------------- resource_manager.py | 176 ------------ setup.py | 7 +- status_app_mac.py | 25 -- 16 files changed, 1289 insertions(+), 1292 deletions(-) delete mode 100644 bluetooth_paired_devices.py delete mode 100644 indicator.py delete mode 100644 message.py delete mode 100644 parrot_zik.py create mode 100644 parrot_zik/__init__.py create mode 100644 parrot_zik/bluetooth_paired_devices.py create mode 100644 parrot_zik/indicator.py create mode 100644 parrot_zik/message.py create mode 100644 parrot_zik/parrot_zik_model.py create mode 100755 parrot_zik/parrot_zik_tray create mode 100644 parrot_zik/resource_manager.py create mode 100644 parrot_zik/status_app_mac.py delete mode 100755 parrot_zik_tray delete mode 100644 resource_manager.py delete mode 100644 status_app_mac.py diff --git a/bluetooth_paired_devices.py b/bluetooth_paired_devices.py deleted file mode 100644 index fead080..0000000 --- a/bluetooth_paired_devices.py +++ /dev/null @@ -1,107 +0,0 @@ -import sys -import re -import os - -from resource_manager import GenericResourceManager - -if sys.platform == "darwin": - from binplist import binplist - import lightblue -else: - import bluetooth - if sys.platform == "win32": - import _winreg - - -def get_parrot_zik_mac(): - p = re.compile('90:03:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}|' - 'A0:14:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}') - if sys.platform == "linux2": - bluetooth_on = int(os.popen('bluez-test-adapter powered').read()) - if bluetooth_on == 1: - out = os.popen("bluez-test-device list").read() - res = p.findall(out) - if len(res) > 0: - return res[0] - else: - raise DeviceNotConnected - else: - raise BluetoothIsNotOn - elif sys.platform == "darwin": - fd = open("/Library/Preferences/com.apple.Bluetooth.plist", "rb") - plist = binplist.BinaryPlist(file_obj=fd) - parsed_plist = plist.Parse() - try: - for mac in parsed_plist['PairedDevices']: - if p.match(mac.replace("-", ":")): - return mac.replace("-", ":") - else: - raise DeviceNotConnected - except Exception: - pass - - elif sys.platform == "win32": - aReg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) - aKey = _winreg.OpenKey( - aReg, 'SYSTEM\CurrentControlSet\Services\ - BTHPORT\Parameters\Devices') - for i in range(10): - try: - asubkey_name = _winreg.EnumKey(aKey, i) - mac = ':'.join(asubkey_name[i:i+2] for i in range(0, 12, 2)) - res = p.findall(mac) - if len(res) > 0: - return res[0] - else: - raise DeviceNotConnected - except EnvironmentError: - pass - - -def connect(): - mac = get_parrot_zik_mac() - if sys.platform == "darwin": - service_matches = lightblue.findservices( - name="Parrot RFcomm service", addr=mac) - else: - uuids = ["0ef0f502-f0ee-46c9-986c-54ed027807fb", - "8B6814D3-6CE7-4498-9700-9312C1711F63"] - service_matches = [] - for uuid in uuids: - try: - service_matches = bluetooth.find_service(uuid=uuid, address=mac) - except bluetooth.btcommon.BluetoothError: - pass - if service_matches: - break - - if len(service_matches) == 0: - raise ConnectionFailure - first_match = service_matches[0] - - if sys.platform == "darwin": - host = first_match[0] - port = first_match[1] - sock = lightblue.socket() - else: - port = first_match["port"] - host = first_match["host"] - sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) - - sock.connect((host, port)) - - sock.send('\x00\x03\x00') - sock.recv(1024) - return GenericResourceManager(sock) - - -class DeviceNotConnected(Exception): - pass - - -class ConnectionFailure(Exception): - pass - - -class BluetoothIsNotOn(Exception): - pass diff --git a/indicator.py b/indicator.py deleted file mode 100644 index 05ffec7..0000000 --- a/indicator.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python - -import sys -import os -import tempfile - -if sys.platform == "linux2" or sys.platform == "win32": - import gtk -elif sys.platform == "darwin": - from Foundation import * - from AppKit import * - from PyObjCTools import AppHelper - from status_app_mac import StatusApp - - -class BaseIndicator(object): - def __init__(self, icon, menu, statusicon): - self.menu = menu - self.statusicon = statusicon - self.setIcon(icon) - - def gtk_right_click_event(self, icon, button, time): - if not self.menu_shown: - self.menu_shown = True - self.menu.popup(None, None, gtk.status_icon_position_menu, - button, time, self.statusicon) - else: - self.menu_shown = False - self.menu.popdown() - - def setIcon(self, name): - raise NotImplementedError - - def main(self): - raise NotImplementedError - - def show_about_dialog(self, widget): - raise NotImplementedError - - -class WindowsIndicator(BaseIndicator): - def __init__(self, icon, menu): - self.icon_directory = ( - os.path.dirname(os.path.realpath(sys.argv[0])) - + os.path.sep + 'share' + os.path.sep + 'icons' - + os.path.sep +'zik' + os.path.sep) - self.menu_shown = False - sys.stdout = open(tempfile.gettempdir() - + os.path.sep + "zik_tray_stdout.log", "w") - sys.stderr = open(tempfile.gettempdir() - + os.path.sep + "zik_tray_stderr.log", "w") - statusicon = gtk.StatusIcon() - statusicon.connect("popup-menu", self.gtk_right_click_event) - statusicon.set_tooltip("Parrot Zik") - super(WindowsIndicator, self).__init__(icon, menu, statusicon) - - def setIcon(self, name): - self.statusicon.set_from_file(self.icon_directory + name + '.png') - - def main(self): - gtk.main() - - def show_about_dialog(self, widget): - about_dialog = gtk.AboutDialog() - about_dialog.set_destroy_with_parent(True) - about_dialog.set_name("Parrot Zik Tray") - about_dialog.set_version("0.3") - about_dialog.set_authors(["Dmitry Moiseev m0sia@m0sia.ru"]) - about_dialog.run() - about_dialog.destroy() - - -class LinuxIndicator(BaseIndicator): - def __init__(self, icon, menu): - import appindicator - self.icon_directory = (os.path.sep + 'usr' + os.path.sep + 'share' - + os.path.sep + 'icons' + os.path.sep+'zik' - + os.path.sep) - if not os.path.isdir(self.icon_directory): - self.icon_directory = (os.path.dirname(sys.argv[0]) - + os.path.sep + 'share' + os.path.sep - + 'icons' + os.path.sep+'zik' - + os.path.sep) - statusicon = appindicator.Indicator( - "new-parrotzik-indicator", "indicator-messages", - appindicator.CATEGORY_APPLICATION_STATUS) - statusicon.set_status(appindicator.STATUS_ACTIVE) - statusicon.set_icon_theme_path(self.icon_directory) - statusicon.set_menu(menu.gtk_menu) - super(LinuxIndicator, self).__init__(icon, menu, statusicon) - - def setIcon(self, name): - self.statusicon.set_icon(name) - - def main(self): - gtk.main() - - def show_about_dialog(self, widget): - about_dialog = gtk.AboutDialog() - about_dialog.set_destroy_with_parent(True) - about_dialog.set_name("Parrot Zik Tray") - about_dialog.set_version("0.3") - about_dialog.set_authors(["Dmitry Moiseev m0sia@m0sia.ru"]) - about_dialog.run() - about_dialog.destroy() - - -class DarwinIndicator(BaseIndicator): - def __init__(self, icon, menu): - self.icon_directory = ( - os.path.dirname(os.path.realpath(sys.argv[0])) + os.path.sep - + 'share' + os.path.sep + 'icons' + os.path.sep + 'zik' - + os.path.sep) - statusicon = StatusApp.sharedApplication() - statusicon.initMenu(menu) - super(DarwinIndicator, self).__init__(icon, menu, statusicon) - - def setIcon(self, name): - self.statusicon.setIcon(name, self.icon_directory) - - def main(self): - AppHelper.runEventLoop() - - def show_about_dialog(self, widget): - pass - - -class NSMenu(object): - def __init__(self): - self.actions = {} - self.menubarMenu = NSMenu.alloc().init() - self.menubarMenu.setAutoenablesItems_(False) - - def append(self, menu_item): - self.actions[menu_item.title] = menu_item.action - self.menubarMenu.addItem_(menu_item.nsmenu_item) - - def reposition(self): - # TODO - pass - -class GTKMenu(object): - def __init__(self): - self.gtk_menu = gtk.Menu() - - def append(self, menu_item): - self.gtk_menu.append(menu_item.base_item) - - def reposition(self): - self.gtk_menu.reposition() - - -class MenuItemBase(object): - def __init__(self, base_item, sensitive, visible): - self.base_item = base_item - self.set_sensitive(sensitive) - if visible: - self.show() - else: - self.hide() - - def set_sensitive(self, option): - raise NotImplementedError - - def set_active(self, option): - raise NotImplementedError - - def get_active(self): - raise NotImplementedError - - def set_label(self, option): - raise NotImplementedError - - def show(self): - self.base_item.show() - - 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: - gtk_item = gtk.CheckMenuItem(name) - else: - gtk_item = gtk.MenuItem(name) - if action: - gtk_item.connect("activate", action) - super(GTKMenuItem, self).__init__(gtk_item, sensitive, visible) - - def set_sensitive(self, option): - return self.base_item.set_sensitive(option) - - def set_active(self, option): - return self.base_item.set_active(option) - - def get_active(self): - return self.base_item.get_active() - - 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): - self.title = name - self.action = action - nsmenu_item = ( - NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( - name, 'clicked:', '')) - super(NSMenuItem, self).__init__(nsmenu_item, sensitive, visible) - - def set_sensitive(self, option): - self.base_item.setEnabled_(option) - - def set_active(self, option): - self.base_item.setState_(option) - - def get_active(self): - return self.base_item.state - - def set_label(self, option): - self.title = option - self.base_item.setTitle_(option) - -if sys.platform == 'linux2': - SysIndicator = LinuxIndicator - Menu = GTKMenu - MenuItem = GTKMenuItem -elif sys.platform == 'win32': - SysIndicator = WindowsIndicator - Menu = GTKMenu - MenuItem = GTKMenuItem -elif sys.platform == 'darwin': - SysIndicator = DarwinIndicator - Menu = NSMenu - MenuItem = NSMenuItem -else: - raise Exception('Platform not supported') - -if __name__ == "__main__": - - quit_item = MenuItem("Quit", sys.exit, True) - - menu = Menu() - menu.append(quit_item) - - indicator = SysIndicator(icon="zik-audio-headset", menu=menu) - indicator.main() diff --git a/message.py b/message.py deleted file mode 100644 index 214b6f9..0000000 --- a/message.py +++ /dev/null @@ -1,30 +0,0 @@ -class Message: - def __init__(self, resource, method, arg=None): - self.method = method - self.resource = resource - self.arg = arg - - def __str__(self): - return str(self.request) - - @property - def request(self): - message = bytearray() - message.extend(self.header) - message.extend(bytearray(self.request_string)) - return message - - @property - def header(self): - header = bytearray([0]) - header.append(len(self.request_string) + 3) - header.append("\x80") - return header - - @property - def request_string(self): - if self.method == 'set': - return 'SET {}/{}?arg={}'.format(self.resource, self.method, - str(self.arg).lower()) - else: - return 'GET {}/{}'.format(self.resource, self.method) diff --git a/parrot_zik.py b/parrot_zik.py deleted file mode 100644 index 52414b5..0000000 --- a/parrot_zik.py +++ /dev/null @@ -1,222 +0,0 @@ -from resource_manager import Version1ResourceManager -from resource_manager import Version2ResourceManager - - -class BatteryStates: - CHARGED = 'charged' - IN_USE = 'in_use' - CHARGING = 'charging' - representation = { - CHARGED: 'Charged', - IN_USE: 'In Use', - CHARGING: 'Charging', - } - -class Rooms: - CONCERT_HALL = 'concert' - JAZZ_CLUB = 'jazz' - LIVING_ROOM = 'living' - SILENT_ROOM = 'silent' - representation = { - CONCERT_HALL: 'Concert Hall', - JAZZ_CLUB: 'Jazz Club', - LIVING_ROOM: 'Living Room', - SILENT_ROOM: 'Silent Room', - } - -class NoiseControl(object): - def __init__(self, type, value): - self.type = type - self.value = value - - @classmethod - def from_noise_control(cls, noise_control): - return cls(noise_control['type'], int(noise_control['value'])) - - def __eq__(self, other): - return self.type == other.type and self.value == other.value - - def __str__(self): - return '{}++{}'.format(self.type, self.value) - -class NoiseControlTypes: - NOISE_CONTROL_MAX = NoiseControl('anc', 2) - NOISE_CONTROL_ON = NoiseControl('anc', 1) - NOISE_CONTROL_OFF = NoiseControl('off', 1) - STREET_MODE = NoiseControl('aoc', 1) - STREET_MODE_MAX = NoiseControl('aoc', 2) - - -class ParrotZikBase(object): - def __init__(self, resource_manager): - self.resource_manager = resource_manager - - @property - def version(self): - return self.resource_manager.api_version - - def refresh_battery(self): - self.resource_manager.fetch('/api/system/battery') - - @property - def battery_state(self): - answer = self.resource_manager.get("/api/system/battery") - return answer.system.battery["state"] - - def get_battery_level(self, field_name): - answer = self.resource_manager.get("/api/system/battery") - return int(answer.system.battery[field_name]) - - @property - def friendly_name(self): - answer = self.resource_manager.get("/api/bluetooth/friendlyname") - return answer.bluetooth["friendlyname"] - - @property - def auto_connect(self): - 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.resource_manager.set("/api/system/auto_connection/enabled", arg) - - @property - def anc_phone_mode(self): - answer = self.resource_manager.get("/api/system/anc_phone_mode/enabled") - return self._result_to_bool( - answer.system.anc_phone_mode["enabled"]) - - def _result_to_bool(self, result): - if result == "true": - return True - elif result == "false": - return False - else: - raise AssertionError(result) - - -class ParrotZikVersion1(ParrotZikBase): - 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.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.resource_manager.get("/api/audio/specific_mode/enabled", arg) - - @property - def concert_hall(self): - 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.resource_manager.get("/api/audio/sound_effect/enabled", arg) - - @property - def cancel_noise(self): - 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.resource_manager.set("/api/audio/noise_cancellation/enabled", arg) - - -class ParrotZikVersion2(ParrotZikBase): - 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.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.resource_manager.toggle_on('/api/flight_mode') - else: - self.resource_manager.toggle_off('/api/flight_mode') - - @property - def sound_effect(self): - 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.resource_manager.set('/api/audio/sound_effect/enabled', arg) - - @property - def room(self): - 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.resource_manager.set('/api/audio/sound_effect/room_size', arg) - - @property - def external_noise(self): - answer = self.resource_manager.get('/api/audio/noise') - return int(answer.audio.noise['external']) - - @property - def internal_noise(self): - answer = self.resource_manager.get('/api/audio/noise') - return int(answer.audio.noise['internal']) - - @property - def angle(self): - answer = self.resource_manager.get('/api/audio/sound_effect/angle') - return int(answer.audio.sound_effect['angle']) - - @angle.setter - def angle(self, arg): - self.resource_manager.set('/api/audio/sound_effect/angle', arg) - - @property - def noise_control(self): - answer = self.resource_manager.get('/api/audio/noise_control') - return NoiseControl.from_noise_control(answer.audio.noise_control) - - @noise_control.setter - def noise_control(self, arg): - pass - - @property - def noise_control_enabled(self): - answer = self.resource_manager.get('/api/audio/noise_control/enabled') - return self._result_to_bool(answer.audio.noise_control['enabled']) diff --git a/parrot_zik/__init__.py b/parrot_zik/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/parrot_zik/bluetooth_paired_devices.py b/parrot_zik/bluetooth_paired_devices.py new file mode 100644 index 0000000..eb1ec09 --- /dev/null +++ b/parrot_zik/bluetooth_paired_devices.py @@ -0,0 +1,107 @@ +import sys +import re +import os + +from parrot_zik.resource_manager import GenericResourceManager + +if sys.platform == "darwin": + from binplist import binplist + import lightblue +else: + import bluetooth + if sys.platform == "win32": + import _winreg + + +def get_parrot_zik_mac(): + p = re.compile('90:03:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}|' + 'A0:14:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}') + if sys.platform == "linux2": + bluetooth_on = int(os.popen('bluez-test-adapter powered').read()) + if bluetooth_on == 1: + out = os.popen("bluez-test-device list").read() + res = p.findall(out) + if len(res) > 0: + return res[0] + else: + raise DeviceNotConnected + else: + raise BluetoothIsNotOn + elif sys.platform == "darwin": + fd = open("/Library/Preferences/com.apple.Bluetooth.plist", "rb") + plist = binplist.BinaryPlist(file_obj=fd) + parsed_plist = plist.Parse() + try: + for mac in parsed_plist['PairedDevices']: + if p.match(mac.replace("-", ":")): + return mac.replace("-", ":") + else: + raise DeviceNotConnected + except Exception: + pass + + elif sys.platform == "win32": + aReg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) + aKey = _winreg.OpenKey( + aReg, 'SYSTEM\CurrentControlSet\Services\ + BTHPORT\Parameters\Devices') + for i in range(10): + try: + asubkey_name = _winreg.EnumKey(aKey, i) + mac = ':'.join(asubkey_name[i:i+2] for i in range(0, 12, 2)) + res = p.findall(mac) + if len(res) > 0: + return res[0] + else: + raise DeviceNotConnected + except EnvironmentError: + pass + + +def connect(): + mac = get_parrot_zik_mac() + if sys.platform == "darwin": + service_matches = lightblue.findservices( + name="Parrot RFcomm service", addr=mac) + else: + uuids = ["0ef0f502-f0ee-46c9-986c-54ed027807fb", + "8B6814D3-6CE7-4498-9700-9312C1711F63"] + service_matches = [] + for uuid in uuids: + try: + service_matches = bluetooth.find_service(uuid=uuid, address=mac) + except bluetooth.btcommon.BluetoothError: + pass + if service_matches: + break + + if len(service_matches) == 0: + raise ConnectionFailure + first_match = service_matches[0] + + if sys.platform == "darwin": + host = first_match[0] + port = first_match[1] + sock = lightblue.socket() + else: + port = first_match["port"] + host = first_match["host"] + sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) + + sock.connect((host, port)) + + sock.send('\x00\x03\x00') + sock.recv(1024) + return GenericResourceManager(sock) + + +class DeviceNotConnected(Exception): + pass + + +class ConnectionFailure(Exception): + pass + + +class BluetoothIsNotOn(Exception): + pass diff --git a/parrot_zik/indicator.py b/parrot_zik/indicator.py new file mode 100644 index 0000000..2691113 --- /dev/null +++ b/parrot_zik/indicator.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python + +import sys +import os +import tempfile + +if sys.platform == "linux2" or sys.platform == "win32": + import gtk +elif sys.platform == "darwin": + from Foundation import * + from AppKit import * + from PyObjCTools import AppHelper + from status_app_mac import StatusApp + + +class BaseIndicator(object): + def __init__(self, icon, menu, statusicon): + self.menu = menu + self.statusicon = statusicon + self.setIcon(icon) + + def gtk_right_click_event(self, icon, button, time): + if not self.menu_shown: + self.menu_shown = True + self.menu.popup(None, None, gtk.status_icon_position_menu, + button, time, self.statusicon) + else: + self.menu_shown = False + self.menu.popdown() + + def setIcon(self, name): + raise NotImplementedError + + def main(self): + raise NotImplementedError + + def show_about_dialog(self, widget): + raise NotImplementedError + + +class WindowsIndicator(BaseIndicator): + def __init__(self, icon, menu): + self.icon_directory = ( + os.path.dirname(os.path.realpath(sys.argv[0])) + os.path.sep + '..' + + os.path.sep + 'share' + os.path.sep + 'icons' + + os.path.sep +'zik' + os.path.sep) + self.menu_shown = False + sys.stdout = open(tempfile.gettempdir() + + os.path.sep + "zik_tray_stdout.log", "w") + sys.stderr = open(tempfile.gettempdir() + + os.path.sep + "zik_tray_stderr.log", "w") + statusicon = gtk.StatusIcon() + statusicon.connect("popup-menu", self.gtk_right_click_event) + statusicon.set_tooltip("Parrot Zik") + super(WindowsIndicator, self).__init__(icon, menu, statusicon) + + def setIcon(self, name): + self.statusicon.set_from_file(self.icon_directory + name + '.png') + + def main(self): + gtk.main() + + def show_about_dialog(self, widget): + about_dialog = gtk.AboutDialog() + about_dialog.set_destroy_with_parent(True) + about_dialog.set_name("Parrot Zik Tray") + about_dialog.set_version("0.3") + about_dialog.set_authors(["Dmitry Moiseev m0sia@m0sia.ru"]) + about_dialog.run() + about_dialog.destroy() + + +class LinuxIndicator(BaseIndicator): + def __init__(self, icon, menu): + import appindicator + self.icon_directory = (os.path.sep + 'usr' + os.path.sep + 'share' + + os.path.sep + 'icons' + os.path.sep+'zik' + + os.path.sep) + if not os.path.isdir(self.icon_directory): + self.icon_directory = (os.path.dirname(sys.argv[0]) + os.path.sep + '..' + + os.path.sep + 'share' + os.path.sep + + 'icons' + os.path.sep+'zik' + + os.path.sep) + statusicon = appindicator.Indicator( + "new-parrotzik-indicator", "indicator-messages", + appindicator.CATEGORY_APPLICATION_STATUS) + statusicon.set_status(appindicator.STATUS_ACTIVE) + statusicon.set_icon_theme_path(self.icon_directory) + statusicon.set_menu(menu.gtk_menu) + super(LinuxIndicator, self).__init__(icon, menu, statusicon) + + def setIcon(self, name): + self.statusicon.set_icon(name) + + def main(self): + gtk.main() + + def show_about_dialog(self, widget): + about_dialog = gtk.AboutDialog() + about_dialog.set_destroy_with_parent(True) + about_dialog.set_name("Parrot Zik Tray") + about_dialog.set_version("0.3") + about_dialog.set_authors(["Dmitry Moiseev m0sia@m0sia.ru"]) + about_dialog.run() + about_dialog.destroy() + + +class DarwinIndicator(BaseIndicator): + def __init__(self, icon, menu): + self.icon_directory = ( + os.path.dirname(os.path.realpath(sys.argv[0])) + os.path.sep + '..' + os.path.sep + + 'share' + os.path.sep + 'icons' + os.path.sep + 'zik' + + os.path.sep) + statusicon = StatusApp.sharedApplication() + statusicon.initMenu(menu) + super(DarwinIndicator, self).__init__(icon, menu, statusicon) + + def setIcon(self, name): + self.statusicon.setIcon(name, self.icon_directory) + + def main(self): + AppHelper.runEventLoop() + + def show_about_dialog(self, widget): + pass + + +class NSMenu(object): + def __init__(self): + self.actions = {} + self.menubarMenu = NSMenu.alloc().init() + self.menubarMenu.setAutoenablesItems_(False) + + def append(self, menu_item): + self.actions[menu_item.title] = menu_item.action + self.menubarMenu.addItem_(menu_item.nsmenu_item) + + def reposition(self): + # TODO + pass + +class GTKMenu(object): + def __init__(self): + self.gtk_menu = gtk.Menu() + + def append(self, menu_item): + self.gtk_menu.append(menu_item.base_item) + + def reposition(self): + self.gtk_menu.reposition() + + +class MenuItemBase(object): + def __init__(self, base_item, sensitive, visible): + self.base_item = base_item + self.set_sensitive(sensitive) + if visible: + self.show() + else: + self.hide() + + def set_sensitive(self, option): + raise NotImplementedError + + def set_active(self, option): + raise NotImplementedError + + def get_active(self): + raise NotImplementedError + + def set_label(self, option): + raise NotImplementedError + + def show(self): + self.base_item.show() + + 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: + gtk_item = gtk.CheckMenuItem(name) + else: + gtk_item = gtk.MenuItem(name) + if action: + gtk_item.connect("activate", action) + super(GTKMenuItem, self).__init__(gtk_item, sensitive, visible) + + def set_sensitive(self, option): + return self.base_item.set_sensitive(option) + + def set_active(self, option): + return self.base_item.set_active(option) + + def get_active(self): + return self.base_item.get_active() + + 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): + self.title = name + self.action = action + nsmenu_item = ( + NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( + name, 'clicked:', '')) + super(NSMenuItem, self).__init__(nsmenu_item, sensitive, visible) + + def set_sensitive(self, option): + self.base_item.setEnabled_(option) + + def set_active(self, option): + self.base_item.setState_(option) + + def get_active(self): + return self.base_item.state + + def set_label(self, option): + self.title = option + self.base_item.setTitle_(option) + +if sys.platform == 'linux2': + SysIndicator = LinuxIndicator + Menu = GTKMenu + MenuItem = GTKMenuItem +elif sys.platform == 'win32': + SysIndicator = WindowsIndicator + Menu = GTKMenu + MenuItem = GTKMenuItem +elif sys.platform == 'darwin': + SysIndicator = DarwinIndicator + Menu = NSMenu + MenuItem = NSMenuItem +else: + raise Exception('Platform not supported') + +if __name__ == "__main__": + + quit_item = MenuItem("Quit", sys.exit, True) + + menu = Menu() + menu.append(quit_item) + + indicator = SysIndicator(icon="zik-audio-headset", menu=menu) + indicator.main() diff --git a/parrot_zik/message.py b/parrot_zik/message.py new file mode 100644 index 0000000..214b6f9 --- /dev/null +++ b/parrot_zik/message.py @@ -0,0 +1,30 @@ +class Message: + def __init__(self, resource, method, arg=None): + self.method = method + self.resource = resource + self.arg = arg + + def __str__(self): + return str(self.request) + + @property + def request(self): + message = bytearray() + message.extend(self.header) + message.extend(bytearray(self.request_string)) + return message + + @property + def header(self): + header = bytearray([0]) + header.append(len(self.request_string) + 3) + header.append("\x80") + return header + + @property + def request_string(self): + if self.method == 'set': + return 'SET {}/{}?arg={}'.format(self.resource, self.method, + str(self.arg).lower()) + else: + return 'GET {}/{}'.format(self.resource, self.method) diff --git a/parrot_zik/parrot_zik_model.py b/parrot_zik/parrot_zik_model.py new file mode 100644 index 0000000..e15200f --- /dev/null +++ b/parrot_zik/parrot_zik_model.py @@ -0,0 +1,222 @@ +from parrot_zik.resource_manager import Version1ResourceManager +from parrot_zik.resource_manager import Version2ResourceManager + + +class BatteryStates: + CHARGED = 'charged' + IN_USE = 'in_use' + CHARGING = 'charging' + representation = { + CHARGED: 'Charged', + IN_USE: 'In Use', + CHARGING: 'Charging', + } + +class Rooms: + CONCERT_HALL = 'concert' + JAZZ_CLUB = 'jazz' + LIVING_ROOM = 'living' + SILENT_ROOM = 'silent' + representation = { + CONCERT_HALL: 'Concert Hall', + JAZZ_CLUB: 'Jazz Club', + LIVING_ROOM: 'Living Room', + SILENT_ROOM: 'Silent Room', + } + +class NoiseControl(object): + def __init__(self, type, value): + self.type = type + self.value = value + + @classmethod + def from_noise_control(cls, noise_control): + return cls(noise_control['type'], int(noise_control['value'])) + + def __eq__(self, other): + return self.type == other.type and self.value == other.value + + def __str__(self): + return '{}++{}'.format(self.type, self.value) + +class NoiseControlTypes: + NOISE_CONTROL_MAX = NoiseControl('anc', 2) + NOISE_CONTROL_ON = NoiseControl('anc', 1) + NOISE_CONTROL_OFF = NoiseControl('off', 1) + STREET_MODE = NoiseControl('aoc', 1) + STREET_MODE_MAX = NoiseControl('aoc', 2) + + +class ParrotZikBase(object): + def __init__(self, resource_manager): + self.resource_manager = resource_manager + + @property + def version(self): + return self.resource_manager.api_version + + def refresh_battery(self): + self.resource_manager.fetch('/api/system/battery') + + @property + def battery_state(self): + answer = self.resource_manager.get("/api/system/battery") + return answer.system.battery["state"] + + def get_battery_level(self, field_name): + answer = self.resource_manager.get("/api/system/battery") + return int(answer.system.battery[field_name]) + + @property + def friendly_name(self): + answer = self.resource_manager.get("/api/bluetooth/friendlyname") + return answer.bluetooth["friendlyname"] + + @property + def auto_connect(self): + 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.resource_manager.set("/api/system/auto_connection/enabled", arg) + + @property + def anc_phone_mode(self): + answer = self.resource_manager.get("/api/system/anc_phone_mode/enabled") + return self._result_to_bool( + answer.system.anc_phone_mode["enabled"]) + + def _result_to_bool(self, result): + if result == "true": + return True + elif result == "false": + return False + else: + raise AssertionError(result) + + +class ParrotZikVersion1(ParrotZikBase): + 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.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.resource_manager.get("/api/audio/specific_mode/enabled", arg) + + @property + def concert_hall(self): + 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.resource_manager.get("/api/audio/sound_effect/enabled", arg) + + @property + def cancel_noise(self): + 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.resource_manager.set("/api/audio/noise_cancellation/enabled", arg) + + +class ParrotZikVersion2(ParrotZikBase): + 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.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.resource_manager.toggle_on('/api/flight_mode') + else: + self.resource_manager.toggle_off('/api/flight_mode') + + @property + def sound_effect(self): + 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.resource_manager.set('/api/audio/sound_effect/enabled', arg) + + @property + def room(self): + 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.resource_manager.set('/api/audio/sound_effect/room_size', arg) + + @property + def external_noise(self): + answer = self.resource_manager.get('/api/audio/noise') + return int(answer.audio.noise['external']) + + @property + def internal_noise(self): + answer = self.resource_manager.get('/api/audio/noise') + return int(answer.audio.noise['internal']) + + @property + def angle(self): + answer = self.resource_manager.get('/api/audio/sound_effect/angle') + return int(answer.audio.sound_effect['angle']) + + @angle.setter + def angle(self, arg): + self.resource_manager.set('/api/audio/sound_effect/angle', arg) + + @property + def noise_control(self): + answer = self.resource_manager.get('/api/audio/noise_control') + return NoiseControl.from_noise_control(answer.audio.noise_control) + + @noise_control.setter + def noise_control(self, arg): + pass + + @property + def noise_control_enabled(self): + answer = self.resource_manager.get('/api/audio/noise_control/enabled') + return self._result_to_bool(answer.audio.noise_control['enabled']) diff --git a/parrot_zik/parrot_zik_tray b/parrot_zik/parrot_zik_tray new file mode 100755 index 0000000..dcdacd0 --- /dev/null +++ b/parrot_zik/parrot_zik_tray @@ -0,0 +1,472 @@ +#!/usr/bin/env python +import functools +from threading import Lock +import gtk + +from parrot_zik import resource_manager +from parrot_zik import bluetooth_paired_devices +from parrot_zik.parrot_zik_model import BatteryStates +from parrot_zik.parrot_zik_model import ParrotZikVersion1 +from parrot_zik.parrot_zik_model import ParrotZikVersion2 +from parrot_zik.parrot_zik_model import NoiseControlTypes +from parrot_zik.parrot_zik_model import Rooms +from parrot_zik.indicator import MenuItem +from parrot_zik.indicator import Menu +from parrot_zik.indicator import SysIndicator + +REFRESH_FREQUENCY = 30000 +RECONNECT_FREQUENCY = 5000 + + +class repeat(object): + def __init__(self, f): + self.f = f + self.id = None + self.lock = Lock() + + def __call__(self, cls): + self.f(cls) + + def start(self, cls, frequency): + self.lock.acquire() + if not self.id: + def run(): + self.f(cls) + return True + + self.id = gtk.timeout_add(frequency, run) + self.lock.release() + + def stop(self): + self.lock.acquire() + if self.id: + gtk.timeout_remove(self.id) + self.id = None + self.lock.release() + + +class ParrotZikIndicator(SysIndicator): + def __init__(self): + + self.menu = Menu() + + self.info_item = MenuItem("Parrot Zik Not connected", + None, sensitive=False) + 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) + + SysIndicator.__init__(self, icon="zik-audio-headset", menu=self.menu) + + self.active_interface = None + + @repeat + def reconnect(self): + if self.active_interface: + self.reconnect.stop() + else: + self.info("Trying to connect") + try: + manager = bluetooth_paired_devices.connect() + except bluetooth_paired_devices.BluetoothIsNotOn: + self.info("Bluetooth is turned off") + except bluetooth_paired_devices.DeviceNotConnected: + self.info("Parrot Zik Not connected") + except bluetooth_paired_devices.ConnectionFailure: + self.info("Failed to connect") + else: + if manager.api_version.startswith('1'): + interface = self.version_1_interface + else: + interface = self.version_2_interface + try: + interface.activate(manager) + except resource_manager.DeviceDisconnected: + interface.deactivate() + else: + self.autorefresh(self) + self.autorefresh.start(self, REFRESH_FREQUENCY) + self.reconnect.stop() + + def info(self, message): + self.info_item.set_label(message) + print(message) + + @repeat + def autorefresh(self): + if self.active_interface: + self.active_interface.refresh() + else: + self.reconnect.start(self, RECONNECT_FREQUENCY) + self.autorefresh.stop() + + def main(self): + self.reconnect.start(self, RECONNECT_FREQUENCY) + SysIndicator.main(self) + +class ParrotZikBaseInterface(object): + def __init__(self, indicator): + self.indicator = indicator + self.parrot = None + self.battery_level = MenuItem("Battery Level:", None, sensitive=False, + visible=False) + self.battery_state = MenuItem("Battery State:", None, sensitive=False, + visible=False) + self.firmware_version = MenuItem("Firmware Version:", None, + sensitive=False, visible=False) + self.auto_connection = MenuItem("Auto Connection", self.toggle_auto_connection, + checkitem=True, visible=False) + self.indicator.menu.append(self.battery_level) + self.indicator.menu.append(self.battery_state) + self.indicator.menu.append(self.firmware_version) + self.indicator.menu.append(self.auto_connection) + + def activate(self, manager): + self.parrot = self.parrot_class(manager) + self.read_battery() + self.indicator.info("Connected to: " + self.parrot.friendly_name) + self.firmware_version.set_label( + "Firmware version: " + self.parrot.version) + self.auto_connection.set_active(self.parrot.auto_connect) + self.battery_level.show() + self.battery_state.show() + self.firmware_version.show() + self.auto_connection.show() + 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() + self.battery_state.hide() + self.firmware_version.hide() + self.auto_connection.hide() + self.indicator.menu.reposition() + self.indicator.active_interface = None + self.indicator.setIcon("zik-audio-headset") + self.indicator.info('Lost Connection') + self.indicator.reconnect.start(self.indicator, RECONNECT_FREQUENCY) + + def toggle_auto_connection(self, widget): + try: + self.parrot.auto_connect = self.auto_connection.get_active() + self.auto_connection.set_active(self.parrot.auto_connect) + except resource_manager.DeviceDisconnected: + self.deactivate() + + def refresh(self): + self.read_battery() + + def read_battery(self): + try: + self.parrot.refresh_battery() + battery_level = self.parrot.battery_level + battery_state = self.parrot.battery_state + except resource_manager.DeviceDisconnected: + self.deactivate() + else: + if battery_state == BatteryStates.CHARGING: + self.indicator.setIcon("zik-battery-charging") + elif battery_level > 80: + self.indicator.setIcon("zik-battery-100") + elif battery_level > 60: + self.indicator.setIcon("zik-battery-080") + elif battery_level > 40: + self.indicator.setIcon("zik-battery-060") + elif battery_level > 20: + self.indicator.setIcon("zik-battery-040") + else: + self.indicator.setIcon("zik-battery-low") + + self.battery_state.set_label( + "State: " + BatteryStates.representation[battery_state]) + self.battery_level.set_label( + "Battery Level: " + str(battery_level)) + + +class ParrotZikVersion1Interface(ParrotZikBaseInterface): + parrot_class = ParrotZikVersion1 + + def __init__(self, indicator): + super(ParrotZikVersion1Interface, self).__init__(indicator) + self.noise_cancelation = MenuItem( + "Noise Cancellation", self.toggle_noise_cancelation, + checkitem=True, visible=False) + self.lou_reed_mode = MenuItem("Lou Reed Mode", self.toggle_lou_reed_mode, + checkitem=True, visible=False) + self.concert_hall_mode = MenuItem( + "Concert Hall Mode", self.toggle_parrot_concert_hall, + checkitem=True, visible=False) + self.indicator.menu.append(self.noise_cancelation) + self.indicator.menu.append(self.lou_reed_mode) + self.indicator.menu.append(self.concert_hall_mode) + + def activate(self, manager): + self.noise_cancelation.show() + self.lou_reed_mode.show() + self.concert_hall_mode.show() + super(ParrotZikVersion1Interface, self).activate(manager) + self.noise_cancelation.set_active(self.parrot.cancel_noise) + self.lou_reed_mode.set_active(self.parrot.lou_reed_mode) + self.concert_hall_mode.set_active(self.parrot.concert_hall) + + def deactivate(self): + self.noise_cancelation.hide() + self.lou_reed_mode.hide() + self.concert_hall_mode.hide() + super(ParrotZikVersion1Interface, self).deactivate() + + def toggle_noise_cancelation(self, widget): + try: + self.parrot.cancel_noise = self.noise_cancelation.get_active() + self.noise_cancelation.set_active(self.parrot.cancel_noise) + except resource_manager.DeviceDisconnected: + self.deactivate() + + def toggle_lou_reed_mode(self, widget): + try: + self.parrot.lou_reed_mode = self.lou_reed_mode.get_active() + self.lou_reed_mode.set_active(self.parrot.lou_reed_mode) + self.concert_hall_mode.set_active(self.parrot.concert_hall) + self.concert_hall_mode.set_sensitive( + not self.lou_reed_mode.get_active()) + except resource_manager.DeviceDisconnected: + self.deactivate() + + def toggle_parrot_concert_hall(self, widget): + try: + self.parrot.concert_hall = self.concert_hall_mode.get_active() + self.concert_hall_mode.set_active(self.parrot.concert_hall) + except resource_manager.DeviceDisconnected: + self.deactivate() + + +class ParrotZikVersion2Interface(ParrotZikBaseInterface): + parrot_class = ParrotZikVersion2 + + def __init__(self, indicator): + self.room_dirty = False + self.angle_dirty = False + self.noise_cancelation_dirty = False + super(ParrotZikVersion2Interface, self).__init__(indicator) + self.noise_cancelation = MenuItem("Noise Control", None, visible=False) + self.noise_cancelation_submenu = Menu() + self.noise_cancelation.set_submenu(self.noise_cancelation_submenu) + + self.noise_control_cancelation_max = MenuItem( + "Max Calcelation", functools.partial( + self.toggle_noise_cancelation, + NoiseControlTypes.NOISE_CONTROL_MAX), checkitem=True, sensitive=False) + self.noise_control_cancelation_on = MenuItem( + "Normal Cancelation", functools.partial( + self.toggle_noise_cancelation, + NoiseControlTypes.NOISE_CONTROL_ON), checkitem=True, sensitive=False) + self.noise_control_off = MenuItem( + "Off", functools.partial( + self.toggle_noise_cancelation, + NoiseControlTypes.NOISE_CONTROL_OFF), checkitem=True, sensitive=False) + self.noise_control_street_mode = MenuItem( + "Street Mode", functools.partial( + self.toggle_noise_cancelation, + NoiseControlTypes.STREET_MODE), checkitem=True, sensitive=False) + self.noise_control_street_mode_max = MenuItem( + "Street Mode Max", functools.partial( + self.toggle_noise_cancelation, + NoiseControlTypes.STREET_MODE_MAX), checkitem=True, sensitive=False) + self.noise_cancelation_submenu.append(self.noise_control_cancelation_max) + self.noise_cancelation_submenu.append(self.noise_control_cancelation_on) + self.noise_cancelation_submenu.append(self.noise_control_off) + self.noise_cancelation_submenu.append(self.noise_control_street_mode) + self.noise_cancelation_submenu.append(self.noise_control_street_mode_max) + + self.room_sound_effect = MenuItem( + "Room Sound Effect", None, visible=False) + self.room_sound_effect_submenu = Menu() + self.room_sound_effect.set_submenu(self.room_sound_effect_submenu) + + self.room_sound_effect_enabled = MenuItem( + "Enabled", self.toggle_room_sound_effect, checkitem=True) + self.rooms = MenuItem("Rooms", None, checkitem=False) + self.angle = MenuItem("Angle", None, checkitem=False) + self.room_sound_effect_submenu.append(self.room_sound_effect_enabled) + self.room_sound_effect_submenu.append(self.rooms) + self.room_sound_effect_submenu.append(self.angle) + + self.rooms_submenu = Menu() + self.rooms.set_submenu(self.rooms_submenu) + + self.concert_hall_mode = MenuItem( + "Concert Hall", functools.partial(self.toggle_room, Rooms.CONCERT_HALL), checkitem=True) + self.jazz_mode = MenuItem( + "Jazz Club", functools.partial(self.toggle_room, Rooms.JAZZ_CLUB), checkitem=True) + self.living_mode = MenuItem( + "Living Room", functools.partial(self.toggle_room, Rooms.LIVING_ROOM), checkitem=True) + self.silent_mode = MenuItem( + "Silent Room", functools.partial(self.toggle_room, Rooms.SILENT_ROOM), checkitem=True) + self.rooms_submenu.append(self.concert_hall_mode) + self.rooms_submenu.append(self.jazz_mode) + self.rooms_submenu.append(self.living_mode) + self.rooms_submenu.append(self.silent_mode) + + self.angle_submenu = Menu() + self.angle.set_submenu(self.angle_submenu) + self.angle_30 = MenuItem( + "30", functools.partial(self.toggle_angle, 30), checkitem=True) + self.angle_60 = MenuItem( + "60", functools.partial(self.toggle_angle, 60), checkitem=True) + self.angle_90 = MenuItem( + "90", functools.partial(self.toggle_angle, 90), checkitem=True) + self.angle_120 = MenuItem( + "120", functools.partial(self.toggle_angle, 120), checkitem=True) + self.angle_150 = MenuItem( + "150", functools.partial(self.toggle_angle, 150), checkitem=True) + self.angle_180 = MenuItem( + "180", functools.partial(self.toggle_angle, 180), checkitem=True) + self.angle_submenu.append(self.angle_30) + self.angle_submenu.append(self.angle_60) + self.angle_submenu.append(self.angle_90) + self.angle_submenu.append(self.angle_120) + self.angle_submenu.append(self.angle_150) + self.angle_submenu.append(self.angle_180) + + self.flight_mode = MenuItem("Flight Mode", self.toggle_flight_mode, + checkitem=True, visible=False) + self.indicator.menu.append(self.room_sound_effect) + self.indicator.menu.append(self.noise_cancelation) + self.indicator.menu.append(self.flight_mode) + + def activate(self, manager): + self.noise_cancelation.show() + self.flight_mode.show() + self.room_sound_effect.show() + super(ParrotZikVersion2Interface, self).activate(manager) + self._read_noise_cancelation() + self.flight_mode.set_active(self.parrot.flight_mode) + self._read_sound_effect_room() + self._read_sound_effect_angle() + sound_effect = self.parrot.sound_effect + + self.room_sound_effect_enabled.set_active(sound_effect) + self.concert_hall_mode.set_sensitive(sound_effect) + self.jazz_mode.set_sensitive(sound_effect) + self.living_mode.set_sensitive(sound_effect) + self.silent_mode.set_sensitive(sound_effect) + + self.angle_30.set_sensitive(sound_effect) + self.angle_60.set_sensitive(sound_effect) + self.angle_90.set_sensitive(sound_effect) + self.angle_120.set_sensitive(sound_effect) + self.angle_150.set_sensitive(sound_effect) + self.angle_180.set_sensitive(sound_effect) + + def deactivate(self): + self.noise_cancelation.hide() + self.flight_mode.hide() + self.room_sound_effect.hide() + super(ParrotZikVersion2Interface, self).deactivate() + + def toggle_flight_mode(self, widget): + try: + self.parrot.flight_mode = self.flight_mode.get_active() + self.flight_mode.set_active(self.parrot.flight_mode) + except resource_manager.DeviceDisconnected: + self.deactivate() + + def toggle_room(self, room, widget): + try: + if not self.room_dirty: + self.parrot.room = room + self.room_dirty = True + self._read_sound_effect_room() + self.room_dirty = False + except resource_manager.DeviceDisconnected: + self.deactivate() + + def _read_sound_effect_room(self): + active_room = self.parrot.room + room_to_menuitem_map = ( + (Rooms.CONCERT_HALL, self.concert_hall_mode), + (Rooms.JAZZ_CLUB, self.jazz_mode), + (Rooms.LIVING_ROOM, self.living_mode), + (Rooms.SILENT_ROOM, self.silent_mode), + ) + for room, menu_item in room_to_menuitem_map: + menu_item.set_active(room == active_room) + + def toggle_room_sound_effect(self, widget): + try: + self.parrot.sound_effect = self.room_sound_effect_enabled.get_active() + sound_effect = self.parrot.sound_effect + self.room_sound_effect_enabled.set_active(sound_effect) + self.concert_hall_mode.set_sensitive(sound_effect) + self.jazz_mode.set_sensitive(sound_effect) + self.living_mode.set_sensitive(sound_effect) + self.silent_mode.set_sensitive(sound_effect) + self.angle_30.set_sensitive(sound_effect) + self.angle_60.set_sensitive(sound_effect) + self.angle_90.set_sensitive(sound_effect) + self.angle_120.set_sensitive(sound_effect) + self.angle_150.set_sensitive(sound_effect) + self.angle_180.set_sensitive(sound_effect) + except resource_manager.DeviceDisconnected: + self.deactivate() + + def toggle_angle(self, angle, widget): + try: + if not self.angle_dirty: + self.parrot.angle = angle + self.angle_dirty = True + self._read_sound_effect_angle() + self.angle_dirty = False + except resource_manager.DeviceDisconnected: + self.deactivate() + + def _read_sound_effect_angle(self): + active_angle = self.parrot.angle + angle_to_menuitem_map = ( + (30, self.angle_30), + (60, self.angle_60), + (90, self.angle_90), + (120, self.angle_120), + (150, self.angle_150), + (180, self.angle_180), + ) + for angle, menu_item in angle_to_menuitem_map: + menu_item.set_active(angle == active_angle) + + def toggle_noise_cancelation(self, noise_calcelation, widget): + try: + if not self.noise_cancelation_dirty: + self.parrot.noise_control = noise_calcelation + self.noise_cancelation_dirty = True + self._read_noise_cancelation() + self.noise_cancelation_dirty = False + except resource_manager.DeviceDisconnected: + self.deactivate() + + def _read_noise_cancelation(self): + active_noise_control = self.parrot.noise_control + noise_control_to_menuitem_map = ( + (NoiseControlTypes.NOISE_CONTROL_MAX, self.noise_control_cancelation_max), + (NoiseControlTypes.NOISE_CONTROL_ON, self.noise_control_cancelation_on), + (NoiseControlTypes.NOISE_CONTROL_OFF, self.noise_control_off), + (NoiseControlTypes.STREET_MODE, self.noise_control_street_mode), + (NoiseControlTypes.STREET_MODE_MAX, self.noise_control_street_mode_max), + ) + for noise_control, menu_item in noise_control_to_menuitem_map: + menu_item.set_active(active_noise_control == noise_control) + + +if __name__ == "__main__": + try: + indicator = ParrotZikIndicator() + indicator.main() + except KeyboardInterrupt: + pass diff --git a/parrot_zik/resource_manager.py b/parrot_zik/resource_manager.py new file mode 100644 index 0000000..8b63e8c --- /dev/null +++ b/parrot_zik/resource_manager.py @@ -0,0 +1,176 @@ +import bluetooth +from operator import itemgetter +import sys + +from BeautifulSoup import BeautifulSoup + +from parrot_zik.message import Message + + +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): + result = self.send_message(self._create_message(resource, 'get')) + self.resource_values[resource] = result + return result + + def toggle_on(self, resource): + self.send_message(self._create_message(resource, 'enable')) + self.fetch(resource) + + def toggle_off(self, resource): + self.send_message(self._create_message(resource, 'disable')) + self.fetch(resource) + + def set(self, resource, arg): + self.send_message(self._create_message(resource, 'set', arg)) + self.fetch(resource) + + def _create_message(self, resource, method, arg=None): + assert resource in self.resources, 'Unknown resource {}'.format(resource) + assert method in self.resources[resource], 'Unhandled method {} for {}'.format(method, resource) + return Message(resource, method, arg) + + def send_message(self, message): + try: + self.sock.send(str(message)) + return self.get_answer(message) + except bluetooth.btcommon.BluetoothError: + raise DeviceDisconnected + + def get_answer(self, message): + data = self.receive_message() + notifications = [] + while not data.answer: + if data.notify: + notifications.append(data.notify) + else: + raise AssertionError('Unknown response') + data = self.receive_message() + self.handle_notifications(notifications, message.resource) + return data.answer + + def handle_notifications(self, notifications, resource): + paths = map(itemgetter('path'), notifications) + clean_paths = set(map(self._clean_path, paths)) + for path in clean_paths: + if resource != path: + self.fetch(path) + + def _clean_path(self, path): + return 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': ['get'], + } + + 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) + resource_manager.handle_notifications(self.notifications, '/api/software/version') + 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': ['get'], + '/api/system/battery': ['get'], + '/api/bluetooth/friendlyname': ['get'], + '/api/system/auto_connection/enabled': ['get', 'set'], + '/api/system/anc_phone_mode/enabled': ['get', 'set'], + '/api/audio/specific_mode/enabled': ['get', 'set'], + '/api/audio/sound_effect/enabled': ['get', 'set'], + '/api/audio/noise_cancellation/enabled': ['get', 'set'], + } + +class Version2ResourceManager(ResourceManagerBase): + resources = { + '/api/account/username': ['get', 'set'], + '/api/appli_version': ['set'], + '/api/audio/counter': ['get'], + '/api/audio/equalizer/enabled': ['get', 'set'], + '/api/audio/equalizer/preset_id': ['set'], + '/api/audio/equalizer/preset_value': ['set'], + '/api/audio/noise_cancellation/enabled': ['get', 'set'], + '/api/audio/noise_control/enabled': ['get', 'set'], + '/api/audio/noise_control': ['get'], + '/api/audio/noise_control/phone_mode': ['get', 'set'], + '/api/audio/noise': ['get'], + '/api/audio/param_equalizer/value': ['set'], + '/api/audio/preset/bypass': ['get', 'set'], + '/api/audio/preset/': ['clear_all'], + '/api/audio/preset/counter': ['get'], + '/api/audio/preset/current': ['get'], + '/api/audio/preset': ['download', 'activate', 'save', 'remove', 'cancel_producer'], + '/api/audio/preset/synchro': ['start', 'stop'], + '/api/audio/smart_audio_tune': ['get', 'set'], + '/api/audio/sound_effect/angle': ['get', 'set'], + '/api/audio/sound_effect/enabled': ['get', 'set'], + '/api/audio/sound_effect': ['get'], + '/api/audio/sound_effect/room_size': ['get', 'set'], + '/api/audio/source': ['get'], + '/api/audio/specific_mode/enabled': ['get', 'set'], + '/api/audio/thumb_equalizer/value': ['get', 'set'], + '/api/audio/track/metadata': ['get', 'force'], + '/api/bluetooth/friendlyname': ['get', 'set'], + '/api/flight_mode': ['get', 'enable', 'disable'], + '/api/software/download_check_state': ['get'], + '/api/software/download_size': ['set'], + '/api/software/tts': ['get', 'enable', 'disable'], + '/api/software/version_checking': ['get'], + '/api/software/version': ['get'], + '/api/system/anc_phone_mode/enabled': ['get', 'set'], + '/api/system/auto_connection/enabled': ['get', 'set'], + '/api/system/auto_power_off': ['get', 'set'], + '/api/system/auto_power_off/presets_list': ['get'], + '/api/system/battery/forecast': ['get'], + '/api/system/battery': ['get'], + '/api/system/bt_address': ['get'], + '/api/system': ['calibrate'], + '/api/system/color': ['get'], + '/api/system/device_type': ['get'], + '/api/system/': ['factory_reset'], + '/api/system/head_detection/enabled': ['get', 'set'], + '/api/system/pi': ['get'], + } + +class DeviceDisconnected(Exception): + pass diff --git a/parrot_zik/status_app_mac.py b/parrot_zik/status_app_mac.py new file mode 100644 index 0000000..dccffe4 --- /dev/null +++ b/parrot_zik/status_app_mac.py @@ -0,0 +1,25 @@ +from Foundation import * +from AppKit import * + +class StatusApp(NSApplication): + + def initMenu(self, menu): + statusbar = NSStatusBar.systemStatusBar() + self.statusitem = statusbar.statusItemWithLength_( + NSVariableStatusItemLength) + + self.mymenu = menu + #add menu to statusitem + self.statusitem.setMenu_(menu.menubarMenu) + self.statusitem.setToolTip_('Parrot Zik Indicator') + + def setIcon(self,icon,icon_directory): + self.icon = NSImage.alloc().initByReferencingFile_( + icon_directory + icon + '.png') + self.icon.setScalesWhenResized_(True) + self.icon.setSize_((20, 20)) + self.statusitem.setImage_(self.icon) + + def clicked_(self, notification): + self.mymenu.actions[notification._.title]() + NSLog('clicked!') diff --git a/parrot_zik_tray b/parrot_zik_tray deleted file mode 100755 index b3deaa2..0000000 --- a/parrot_zik_tray +++ /dev/null @@ -1,474 +0,0 @@ -#!/usr/bin/env python -import functools -from threading import Lock - -import gtk - -import bluetooth_paired_devices -from parrot_zik import BatteryStates -from parrot_zik import ParrotZikVersion1 -from parrot_zik import ParrotZikVersion2 -from parrot_zik import NoiseControlTypes -from bluetooth_paired_devices import connect -from parrot_zik import Rooms -from indicator import MenuItem -from indicator import Menu -from indicator import SysIndicator -import resource_manager - -REFRESH_FREQUENCY = 30000 -RECONNECT_FREQUENCY = 5000 - - -class repeat(object): - def __init__(self, f): - self.f = f - self.id = None - self.lock = Lock() - - def __call__(self, cls): - self.f(cls) - - def start(self, cls, frequency): - self.lock.acquire() - if not self.id: - def run(): - self.f(cls) - return True - - self.id = gtk.timeout_add(frequency, run) - self.lock.release() - - def stop(self): - self.lock.acquire() - if self.id: - gtk.timeout_remove(self.id) - self.id = None - self.lock.release() - - -class ParrotZikIndicator(SysIndicator): - def __init__(self): - - self.menu = Menu() - - self.info_item = MenuItem("Parrot Zik Not connected", - None, sensitive=False) - 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) - - SysIndicator.__init__(self, icon="zik-audio-headset", menu=self.menu) - - self.active_interface = None - - @repeat - def reconnect(self): - if self.active_interface: - self.reconnect.stop() - else: - self.info("Trying to connect") - try: - manager = connect() - except bluetooth_paired_devices.BluetoothIsNotOn: - self.info("Bluetooth is turned off") - except bluetooth_paired_devices.DeviceNotConnected: - self.info("Parrot Zik Not connected") - except bluetooth_paired_devices.ConnectionFailure: - self.info("Failed to connect") - else: - if manager.api_version.startswith('1'): - interface = self.version_1_interface - else: - interface = self.version_2_interface - try: - interface.activate(manager) - except resource_manager.DeviceDisconnected: - interface.deactivate() - else: - self.autorefresh(self) - self.autorefresh.start(self, REFRESH_FREQUENCY) - self.reconnect.stop() - - def info(self, message): - self.info_item.set_label(message) - print(message) - - @repeat - def autorefresh(self): - if self.active_interface: - self.active_interface.refresh() - else: - self.reconnect.start(self, RECONNECT_FREQUENCY) - self.autorefresh.stop() - - def main(self): - self.reconnect.start(self, RECONNECT_FREQUENCY) - SysIndicator.main(self) - -class ParrotZikBaseInterface(object): - def __init__(self, indicator): - self.indicator = indicator - self.parrot = None - self.battery_level = MenuItem("Battery Level:", None, sensitive=False, - visible=False) - self.battery_state = MenuItem("Battery State:", None, sensitive=False, - visible=False) - self.firmware_version = MenuItem("Firmware Version:", None, - sensitive=False, visible=False) - self.auto_connection = MenuItem("Auto Connection", self.toggle_auto_connection, - checkitem=True, visible=False) - self.indicator.menu.append(self.battery_level) - self.indicator.menu.append(self.battery_state) - self.indicator.menu.append(self.firmware_version) - self.indicator.menu.append(self.auto_connection) - - def activate(self, manager): - self.parrot = self.parrot_class(manager) - self.read_battery() - self.indicator.info("Connected to: " + self.parrot.friendly_name) - self.firmware_version.set_label( - "Firmware version: " + self.parrot.version) - self.auto_connection.set_active(self.parrot.auto_connect) - self.battery_level.show() - self.battery_state.show() - self.firmware_version.show() - self.auto_connection.show() - 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() - self.battery_state.hide() - self.firmware_version.hide() - self.auto_connection.hide() - self.indicator.menu.reposition() - self.indicator.active_interface = None - self.indicator.setIcon("zik-audio-headset") - self.indicator.info('Lost Connection') - self.indicator.reconnect.start(self.indicator, RECONNECT_FREQUENCY) - - def toggle_auto_connection(self, widget): - try: - self.parrot.auto_connect = self.auto_connection.get_active() - self.auto_connection.set_active(self.parrot.auto_connect) - except resource_manager.DeviceDisconnected: - self.deactivate() - - def refresh(self): - self.read_battery() - - def read_battery(self): - try: - self.parrot.refresh_battery() - battery_level = self.parrot.battery_level - battery_state = self.parrot.battery_state - except resource_manager.DeviceDisconnected: - self.deactivate() - else: - if battery_state == BatteryStates.CHARGING: - self.indicator.setIcon("zik-battery-charging") - elif battery_level > 80: - self.indicator.setIcon("zik-battery-100") - elif battery_level > 60: - self.indicator.setIcon("zik-battery-080") - elif battery_level > 40: - self.indicator.setIcon("zik-battery-060") - elif battery_level > 20: - self.indicator.setIcon("zik-battery-040") - else: - self.indicator.setIcon("zik-battery-low") - - self.battery_state.set_label( - "State: " + BatteryStates.representation[battery_state]) - self.battery_level.set_label( - "Battery Level: " + str(battery_level)) - - -class ParrotZikVersion1Interface(ParrotZikBaseInterface): - parrot_class = ParrotZikVersion1 - - def __init__(self, indicator): - super(ParrotZikVersion1Interface, self).__init__(indicator) - self.noise_cancelation = MenuItem( - "Noise Cancellation", self.toggle_noise_cancelation, - checkitem=True, visible=False) - self.lou_reed_mode = MenuItem("Lou Reed Mode", self.toggle_lou_reed_mode, - checkitem=True, visible=False) - self.concert_hall_mode = MenuItem( - "Concert Hall Mode", self.toggle_parrot_concert_hall, - checkitem=True, visible=False) - self.indicator.menu.append(self.noise_cancelation) - self.indicator.menu.append(self.lou_reed_mode) - self.indicator.menu.append(self.concert_hall_mode) - - def activate(self, manager): - self.noise_cancelation.show() - self.lou_reed_mode.show() - self.concert_hall_mode.show() - super(ParrotZikVersion1Interface, self).activate(manager) - self.noise_cancelation.set_active(self.parrot.cancel_noise) - self.lou_reed_mode.set_active(self.parrot.lou_reed_mode) - self.concert_hall_mode.set_active(self.parrot.concert_hall) - - def deactivate(self): - self.noise_cancelation.hide() - self.lou_reed_mode.hide() - self.concert_hall_mode.hide() - super(ParrotZikVersion1Interface, self).deactivate() - - def toggle_noise_cancelation(self, widget): - try: - self.parrot.cancel_noise = self.noise_cancelation.get_active() - self.noise_cancelation.set_active(self.parrot.cancel_noise) - except resource_manager.DeviceDisconnected: - self.deactivate() - - def toggle_lou_reed_mode(self, widget): - try: - self.parrot.lou_reed_mode = self.lou_reed_mode.get_active() - self.lou_reed_mode.set_active(self.parrot.lou_reed_mode) - self.concert_hall_mode.set_active(self.parrot.concert_hall) - self.concert_hall_mode.set_sensitive( - not self.lou_reed_mode.get_active()) - except resource_manager.DeviceDisconnected: - self.deactivate() - - def toggle_parrot_concert_hall(self, widget): - try: - self.parrot.concert_hall = self.concert_hall_mode.get_active() - self.concert_hall_mode.set_active(self.parrot.concert_hall) - except resource_manager.DeviceDisconnected: - self.deactivate() - - -class ParrotZikVersion2Interface(ParrotZikBaseInterface): - parrot_class = ParrotZikVersion2 - - def __init__(self, indicator): - self.room_dirty = False - self.angle_dirty = False - self.noise_cancelation_dirty = False - super(ParrotZikVersion2Interface, self).__init__(indicator) - self.noise_cancelation = MenuItem("Noise Control", None, visible=False) - self.noise_cancelation_submenu = Menu() - self.noise_cancelation.set_submenu(self.noise_cancelation_submenu) - - self.noise_control_cancelation_max = MenuItem( - "Max Calcelation", functools.partial( - self.toggle_noise_cancelation, - NoiseControlTypes.NOISE_CONTROL_MAX), checkitem=True, sensitive=False) - self.noise_control_cancelation_on = MenuItem( - "Normal Cancelation", functools.partial( - self.toggle_noise_cancelation, - NoiseControlTypes.NOISE_CONTROL_ON), checkitem=True, sensitive=False) - self.noise_control_off = MenuItem( - "Off", functools.partial( - self.toggle_noise_cancelation, - NoiseControlTypes.NOISE_CONTROL_OFF), checkitem=True, sensitive=False) - self.noise_control_street_mode = MenuItem( - "Street Mode", functools.partial( - self.toggle_noise_cancelation, - NoiseControlTypes.STREET_MODE), checkitem=True, sensitive=False) - self.noise_control_street_mode_max = MenuItem( - "Street Mode Max", functools.partial( - self.toggle_noise_cancelation, - NoiseControlTypes.STREET_MODE_MAX), checkitem=True, sensitive=False) - self.noise_cancelation_submenu.append(self.noise_control_cancelation_max) - self.noise_cancelation_submenu.append(self.noise_control_cancelation_on) - self.noise_cancelation_submenu.append(self.noise_control_off) - self.noise_cancelation_submenu.append(self.noise_control_street_mode) - self.noise_cancelation_submenu.append(self.noise_control_street_mode_max) - - self.room_sound_effect = MenuItem( - "Room Sound Effect", None, visible=False) - self.room_sound_effect_submenu = Menu() - self.room_sound_effect.set_submenu(self.room_sound_effect_submenu) - - self.room_sound_effect_enabled = MenuItem( - "Enabled", self.toggle_room_sound_effect, checkitem=True) - self.rooms = MenuItem("Rooms", None, checkitem=False) - self.angle = MenuItem("Angle", None, checkitem=False) - self.room_sound_effect_submenu.append(self.room_sound_effect_enabled) - self.room_sound_effect_submenu.append(self.rooms) - self.room_sound_effect_submenu.append(self.angle) - - self.rooms_submenu = Menu() - self.rooms.set_submenu(self.rooms_submenu) - - self.concert_hall_mode = MenuItem( - "Concert Hall", functools.partial(self.toggle_room, Rooms.CONCERT_HALL), checkitem=True) - self.jazz_mode = MenuItem( - "Jazz Club", functools.partial(self.toggle_room, Rooms.JAZZ_CLUB), checkitem=True) - self.living_mode = MenuItem( - "Living Room", functools.partial(self.toggle_room, Rooms.LIVING_ROOM), checkitem=True) - self.silent_mode = MenuItem( - "Silent Room", functools.partial(self.toggle_room, Rooms.SILENT_ROOM), checkitem=True) - self.rooms_submenu.append(self.concert_hall_mode) - self.rooms_submenu.append(self.jazz_mode) - self.rooms_submenu.append(self.living_mode) - self.rooms_submenu.append(self.silent_mode) - - self.angle_submenu = Menu() - self.angle.set_submenu(self.angle_submenu) - self.angle_30 = MenuItem( - "30", functools.partial(self.toggle_angle, 30), checkitem=True) - self.angle_60 = MenuItem( - "60", functools.partial(self.toggle_angle, 60), checkitem=True) - self.angle_90 = MenuItem( - "90", functools.partial(self.toggle_angle, 90), checkitem=True) - self.angle_120 = MenuItem( - "120", functools.partial(self.toggle_angle, 120), checkitem=True) - self.angle_150 = MenuItem( - "150", functools.partial(self.toggle_angle, 150), checkitem=True) - self.angle_180 = MenuItem( - "180", functools.partial(self.toggle_angle, 180), checkitem=True) - self.angle_submenu.append(self.angle_30) - self.angle_submenu.append(self.angle_60) - self.angle_submenu.append(self.angle_90) - self.angle_submenu.append(self.angle_120) - self.angle_submenu.append(self.angle_150) - self.angle_submenu.append(self.angle_180) - - self.flight_mode = MenuItem("Flight Mode", self.toggle_flight_mode, - checkitem=True, visible=False) - self.indicator.menu.append(self.room_sound_effect) - self.indicator.menu.append(self.noise_cancelation) - self.indicator.menu.append(self.flight_mode) - - def activate(self, manager): - self.noise_cancelation.show() - self.flight_mode.show() - self.room_sound_effect.show() - super(ParrotZikVersion2Interface, self).activate(manager) - self._read_noise_cancelation() - self.flight_mode.set_active(self.parrot.flight_mode) - self._read_sound_effect_room() - self._read_sound_effect_angle() - sound_effect = self.parrot.sound_effect - - self.room_sound_effect_enabled.set_active(sound_effect) - self.concert_hall_mode.set_sensitive(sound_effect) - self.jazz_mode.set_sensitive(sound_effect) - self.living_mode.set_sensitive(sound_effect) - self.silent_mode.set_sensitive(sound_effect) - - self.angle_30.set_sensitive(sound_effect) - self.angle_60.set_sensitive(sound_effect) - self.angle_90.set_sensitive(sound_effect) - self.angle_120.set_sensitive(sound_effect) - self.angle_150.set_sensitive(sound_effect) - self.angle_180.set_sensitive(sound_effect) - - def deactivate(self): - self.noise_cancelation.hide() - self.flight_mode.hide() - self.room_sound_effect.hide() - super(ParrotZikVersion2Interface, self).deactivate() - - def toggle_flight_mode(self, widget): - try: - self.parrot.flight_mode = self.flight_mode.get_active() - self.flight_mode.set_active(self.parrot.flight_mode) - except resource_manager.DeviceDisconnected: - self.deactivate() - - def toggle_room(self, room, widget): - try: - if not self.room_dirty: - self.parrot.room = room - self.room_dirty = True - self._read_sound_effect_room() - self.room_dirty = False - except resource_manager.DeviceDisconnected: - self.deactivate() - - def _read_sound_effect_room(self): - active_room = self.parrot.room - room_to_menuitem_map = ( - (Rooms.CONCERT_HALL, self.concert_hall_mode), - (Rooms.JAZZ_CLUB, self.jazz_mode), - (Rooms.LIVING_ROOM, self.living_mode), - (Rooms.SILENT_ROOM, self.silent_mode), - ) - for room, menu_item in room_to_menuitem_map: - menu_item.set_active(room == active_room) - - def toggle_room_sound_effect(self, widget): - try: - self.parrot.sound_effect = self.room_sound_effect_enabled.get_active() - sound_effect = self.parrot.sound_effect - self.room_sound_effect_enabled.set_active(sound_effect) - self.concert_hall_mode.set_sensitive(sound_effect) - self.jazz_mode.set_sensitive(sound_effect) - self.living_mode.set_sensitive(sound_effect) - self.silent_mode.set_sensitive(sound_effect) - self.angle_30.set_sensitive(sound_effect) - self.angle_60.set_sensitive(sound_effect) - self.angle_90.set_sensitive(sound_effect) - self.angle_120.set_sensitive(sound_effect) - self.angle_150.set_sensitive(sound_effect) - self.angle_180.set_sensitive(sound_effect) - except resource_manager.DeviceDisconnected: - self.deactivate() - - def toggle_angle(self, angle, widget): - try: - if not self.angle_dirty: - self.parrot.angle = angle - self.angle_dirty = True - self._read_sound_effect_angle() - self.angle_dirty = False - except resource_manager.DeviceDisconnected: - self.deactivate() - - def _read_sound_effect_angle(self): - active_angle = self.parrot.angle - angle_to_menuitem_map = ( - (30, self.angle_30), - (60, self.angle_60), - (90, self.angle_90), - (120, self.angle_120), - (150, self.angle_150), - (180, self.angle_180), - ) - for angle, menu_item in angle_to_menuitem_map: - menu_item.set_active(angle == active_angle) - - def toggle_noise_cancelation(self, noise_calcelation, widget): - try: - if not self.noise_cancelation_dirty: - self.parrot.noise_control = noise_calcelation - self.noise_cancelation_dirty = True - self._read_noise_cancelation() - self.noise_cancelation_dirty = False - except resource_manager.DeviceDisconnected: - self.deactivate() - - def _read_noise_cancelation(self): - active_noise_control = self.parrot.noise_control - noise_control_to_menuitem_map = ( - (NoiseControlTypes.NOISE_CONTROL_MAX, self.noise_control_cancelation_max), - (NoiseControlTypes.NOISE_CONTROL_ON, self.noise_control_cancelation_on), - (NoiseControlTypes.NOISE_CONTROL_OFF, self.noise_control_off), - (NoiseControlTypes.STREET_MODE, self.noise_control_street_mode), - (NoiseControlTypes.STREET_MODE_MAX, self.noise_control_street_mode_max), - ) - for noise_control, menu_item in noise_control_to_menuitem_map: - menu_item.set_active(active_noise_control == noise_control) - - -if __name__ == "__main__": - try: - indicator = ParrotZikIndicator() - indicator.main() - except KeyboardInterrupt: - pass diff --git a/resource_manager.py b/resource_manager.py deleted file mode 100644 index daf6ecd..0000000 --- a/resource_manager.py +++ /dev/null @@ -1,176 +0,0 @@ -import bluetooth -from operator import itemgetter -import sys - -from BeautifulSoup import BeautifulSoup - -from message import Message - - -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): - result = self.send_message(self._create_message(resource, 'get')) - self.resource_values[resource] = result - return result - - def toggle_on(self, resource): - self.send_message(self._create_message(resource, 'enable')) - self.fetch(resource) - - def toggle_off(self, resource): - self.send_message(self._create_message(resource, 'disable')) - self.fetch(resource) - - def set(self, resource, arg): - self.send_message(self._create_message(resource, 'set', arg)) - self.fetch(resource) - - def _create_message(self, resource, method, arg=None): - assert resource in self.resources, 'Unknown resource {}'.format(resource) - assert method in self.resources[resource], 'Unhandled method {} for {}'.format(method, resource) - return Message(resource, method, arg) - - def send_message(self, message): - try: - self.sock.send(str(message)) - return self.get_answer(message) - except bluetooth.btcommon.BluetoothError: - raise DeviceDisconnected - - def get_answer(self, message): - data = self.receive_message() - notifications = [] - while not data.answer: - if data.notify: - notifications.append(data.notify) - else: - raise AssertionError('Unknown response') - data = self.receive_message() - self.handle_notifications(notifications, message.resource) - return data.answer - - def handle_notifications(self, notifications, resource): - paths = map(itemgetter('path'), notifications) - clean_paths = set(map(self._clean_path, paths)) - for path in clean_paths: - if resource != path: - self.fetch(path) - - def _clean_path(self, path): - return 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': ['get'], - } - - 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) - resource_manager.handle_notifications(self.notifications, '/api/software/version') - 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': ['get'], - '/api/system/battery': ['get'], - '/api/bluetooth/friendlyname': ['get'], - '/api/system/auto_connection/enabled': ['get', 'set'], - '/api/system/anc_phone_mode/enabled': ['get', 'set'], - '/api/audio/specific_mode/enabled': ['get', 'set'], - '/api/audio/sound_effect/enabled': ['get', 'set'], - '/api/audio/noise_cancellation/enabled': ['get', 'set'], - } - -class Version2ResourceManager(ResourceManagerBase): - resources = { - '/api/account/username': ['get', 'set'], - '/api/appli_version': ['set'], - '/api/audio/counter': ['get'], - '/api/audio/equalizer/enabled': ['get', 'set'], - '/api/audio/equalizer/preset_id': ['set'], - '/api/audio/equalizer/preset_value': ['set'], - '/api/audio/noise_cancellation/enabled': ['get', 'set'], - '/api/audio/noise_control/enabled': ['get', 'set'], - '/api/audio/noise_control': ['get'], - '/api/audio/noise_control/phone_mode': ['get', 'set'], - '/api/audio/noise': ['get'], - '/api/audio/param_equalizer/value': ['set'], - '/api/audio/preset/bypass': ['get', 'set'], - '/api/audio/preset/': ['clear_all'], - '/api/audio/preset/counter': ['get'], - '/api/audio/preset/current': ['get'], - '/api/audio/preset': ['download', 'activate', 'save', 'remove', 'cancel_producer'], - '/api/audio/preset/synchro': ['start', 'stop'], - '/api/audio/smart_audio_tune': ['get', 'set'], - '/api/audio/sound_effect/angle': ['get', 'set'], - '/api/audio/sound_effect/enabled': ['get', 'set'], - '/api/audio/sound_effect': ['get'], - '/api/audio/sound_effect/room_size': ['get', 'set'], - '/api/audio/source': ['get'], - '/api/audio/specific_mode/enabled': ['get', 'set'], - '/api/audio/thumb_equalizer/value': ['get', 'set'], - '/api/audio/track/metadata': ['get', 'force'], - '/api/bluetooth/friendlyname': ['get', 'set'], - '/api/flight_mode': ['get', 'enable', 'disable'], - '/api/software/download_check_state': ['get'], - '/api/software/download_size': ['set'], - '/api/software/tts': ['get', 'enable', 'disable'], - '/api/software/version_checking': ['get'], - '/api/software/version': ['get'], - '/api/system/anc_phone_mode/enabled': ['get', 'set'], - '/api/system/auto_connection/enabled': ['get', 'set'], - '/api/system/auto_power_off': ['get', 'set'], - '/api/system/auto_power_off/presets_list': ['get'], - '/api/system/battery/forecast': ['get'], - '/api/system/battery': ['get'], - '/api/system/bt_address': ['get'], - '/api/system': ['calibrate'], - '/api/system/color': ['get'], - '/api/system/device_type': ['get'], - '/api/system/': ['factory_reset'], - '/api/system/head_detection/enabled': ['get', 'set'], - '/api/system/pi': ['get'], - } - -class DeviceDisconnected(Exception): - pass diff --git a/setup.py b/setup.py index 7156620..19c391b 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( windows=[ { - 'script': 'parrot_zik_tray', + 'script': 'parrot_zik/parrot_zik_tray', 'icon_resources': [(1, "./share/icons/zik/Headphone.ico")], } ], @@ -43,7 +43,6 @@ setup( 'BeautifulSoup', 'bluetooth' ], - py_modules=['parrot_zik', 'message', 'resource_manager'], - - scripts=["parrot_zik_tray"] + packages=['parrot_zik'], + scripts=["parrot_zik/parrot_zik_tray"] ) diff --git a/status_app_mac.py b/status_app_mac.py deleted file mode 100644 index dccffe4..0000000 --- a/status_app_mac.py +++ /dev/null @@ -1,25 +0,0 @@ -from Foundation import * -from AppKit import * - -class StatusApp(NSApplication): - - def initMenu(self, menu): - statusbar = NSStatusBar.systemStatusBar() - self.statusitem = statusbar.statusItemWithLength_( - NSVariableStatusItemLength) - - self.mymenu = menu - #add menu to statusitem - self.statusitem.setMenu_(menu.menubarMenu) - self.statusitem.setToolTip_('Parrot Zik Indicator') - - def setIcon(self,icon,icon_directory): - self.icon = NSImage.alloc().initByReferencingFile_( - icon_directory + icon + '.png') - self.icon.setScalesWhenResized_(True) - self.icon.setSize_((20, 20)) - self.statusitem.setImage_(self.icon) - - def clicked_(self, notification): - self.mymenu.actions[notification._.title]() - NSLog('clicked!') -- cgit v1.2.1