#!/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.concert_hall_mode.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