aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorm0sia <m0sia@m0sia.ru>2016-02-18 15:04:42 -0600
committerm0sia <m0sia@m0sia.ru>2016-02-18 15:04:42 -0600
commit52c1c1f9d06631b94c3b8ce8eaf816c5a36842b3 (patch)
tree020f823cf692d1ce6898649a15c7633c831dd972
parentf57d9a8d4ebc30effbec71382cce7e0a37b697e3 (diff)
parent92e47683fc783a2dbd3d9737af40eb0b3372af61 (diff)
downloadpyParrotZikTCP-52c1c1f9d06631b94c3b8ce8eaf816c5a36842b3.tar.xz
pyParrotZikTCP-52c1c1f9d06631b94c3b8ce8eaf816c5a36842b3.zip
Merge pull request #12 from serathius/master
Rework and Zik 2.0 implementation.
Diffstat (limited to '')
-rw-r--r--BluetoothPairedDevices.py44
-rw-r--r--ParrotProtocol.py19
-rw-r--r--ParrotZik.py146
-rwxr-xr-xParrotZikTray174
-rw-r--r--SysIndicator.py155
-rw-r--r--parrot_zik/__init__.py0
-rw-r--r--parrot_zik/bluetooth_paired_devices.py171
-rw-r--r--parrot_zik/indicator/__init__.py25
-rw-r--r--parrot_zik/indicator/base.py48
-rw-r--r--parrot_zik/indicator/gtk_wrapping.py46
-rw-r--r--parrot_zik/indicator/linux.py69
-rw-r--r--parrot_zik/indicator/mac.py70
-rw-r--r--parrot_zik/indicator/windows.py47
-rw-r--r--parrot_zik/interface/__init__.py0
-rw-r--r--parrot_zik/interface/base.py95
-rw-r--r--parrot_zik/interface/version1.py63
-rw-r--r--parrot_zik/interface/version2.py239
-rw-r--r--parrot_zik/message.py30
-rw-r--r--parrot_zik/model/__init__.py0
-rw-r--r--parrot_zik/model/base.py60
-rw-r--r--parrot_zik/model/version1.py48
-rw-r--r--parrot_zik/model/version2.py131
-rwxr-xr-xparrot_zik/parrot_zik_tray.py80
-rw-r--r--parrot_zik/resource_manager.py177
-rw-r--r--parrot_zik/status_app_mac.py (renamed from StatusAppMac.py)12
-rw-r--r--parrot_zik/utils.py32
-rw-r--r--setup.py70
27 files changed, 1474 insertions, 577 deletions
diff --git a/BluetoothPairedDevices.py b/BluetoothPairedDevices.py
deleted file mode 100644
index 6f9f673..0000000
--- a/BluetoothPairedDevices.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/python
-
-import sys
-import re
-import os
-
-
-def ParrotZikMac():
- 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}')
- if sys.platform == "linux2":
- out = os.popen("bluez-test-device list").read()
- res = p.findall(out)
- if len(res)>0:
- return res[0]
-
- 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("-",":")
- except:
- 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]
-
- except EnvironmentError:
- pass
-
-if sys.platform == "darwin":
- from binplist import binplist
-elif sys.platform == "win32":
- import _winreg \ No newline at end of file
diff --git a/ParrotProtocol.py b/ParrotProtocol.py
deleted file mode 100644
index 9324b8b..0000000
--- a/ParrotProtocol.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python
-
-def generateRequest(requestString):
- message=bytearray()
- message.extend(generateHeader(requestString))
- message.extend(bytearray(requestString))
- return message
-
-def generateHeader(requestString):
- header = bytearray([0])
- header.append(len(requestString)+3)
- header.append("\x80")
- return header
-
-def getRequest(apiString):
- return generateRequest("GET "+apiString)
-
-def setRequest(apiString,args):
- return generateRequest("SET "+apiString+"?arg="+args) \ No newline at end of file
diff --git a/ParrotZik.py b/ParrotZik.py
deleted file mode 100644
index 8a2fca6..0000000
--- a/ParrotZik.py
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-if sys.platform == "darwin":
- import lightblue
-else:
- import bluetooth
-
-import ParrotProtocol
-import struct
-from BeautifulSoup import BeautifulSoup
-
-class ParrotZik(object):
- def __init__(self,addr=None):
- uuid = "0ef0f502-f0ee-46c9-986c-54ed027807fb"
-
-
- if sys.platform == "darwin":
- service_matches = lightblue.findservices( name = "Parrot RFcomm service", addr = addr )
- else:
- service_matches = bluetooth.find_service( uuid = uuid, address = addr )
-
-
- if len(service_matches) == 0:
- print "Failed to find Parrot Zik RFCOMM service"
- self.sock =""
- return
-
- if sys.platform == "darwin":
- first_match = service_matches[0]
- port = first_match[1]
- name = first_match[2]
- host = first_match[0]
- else:
- first_match = service_matches[0]
- port = first_match["port"]
- name = first_match["name"]
- host = first_match["host"]
-
- print "Connecting to \"%s\" on %s" % (name, host)
-
- if sys.platform == "darwin":
- self.sock=lightblue.socket()
- else:
- self.sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM )
-
- self.sock.connect((host, port))
-
- self.sock.send('\x00\x03\x00')
- data = self.sock.recv(1024)
-
- self.BatteryLevel = 100
- self.BatteryCharging = False
- print "Connected"
-
- def getBatteryState(self):
- data = self.sendGetMessage("/api/system/battery/get")
- return data.answer.system.battery["state"]
-
- def getBatteryLevel(self):
- data = self.sendGetMessage("/api/system/battery/get")
- try:
- if data.answer.system.battery["level"] <> '':
- self.BatteryLevel = data.answer.system.battery["level"]
- if data.answer.system.battery["state"] == 'charging':
- self.BatteryCharging = True
- else:
- self.BatteryCharging = False
- except:
- pass
-
- try:
- print "notification received" + data.notify["path"]
- except:
- pass
-
- return self.BatteryLevel
-
- def getVersion(self):
- data = self.sendGetMessage("/api/software/version/get")
- return data.answer.software["version"]
-
- def getFriendlyName(self):
- data = self.sendGetMessage("/api/bluetooth/friendlyname/get")
- return data.answer.bluetooth["friendlyname"]
-
- def getAutoConnection(self):
- data = self.sendGetMessage("/api/system/auto_connection/enabled/get")
- return data.answer.system.auto_connection["enabled"]
-
- def setAutoConnection(self,arg):
- data = self.sendSetMessage("/api/system/auto_connection/enabled/set",arg)
- return data
-
- def getAncPhoneMode(self):
- data = self.sendGetMessage("/api/system/anc_phone_mode/enabled/get")
- return data.answer.system.anc_phone_mode["enabled"]
-
- def getNoiseCancel(self):
- data = self.sendGetMessage("/api/audio/noise_cancellation/enabled/get")
- return data.answer.audio.noise_cancellation["enabled"]
-
- def setNoiseCancel(self,arg):
- data = self.sendSetMessage("/api/audio/noise_cancellation/enabled/set",arg)
- return data
-
- def getLouReedMode(self):
- data = self.sendGetMessage("/api/audio/specific_mode/enabled/get")
- return data.answer.audio.specific_mode["enabled"]
-
- def setLouReedMode(self,arg):
- data = self.sendSetMessage("/api/audio/specific_mode/enabled/set",arg)
- return data
-
- def getParrotConcertHall(self):
- data = self.sendGetMessage("/api/audio/sound_effect/enabled/get")
- return data.answer.audio.sound_effect["enabled"]
-
- def setParrotConcertHall(self,arg):
- data = self.sendSetMessage("/api/audio/sound_effect/enabled/set",arg)
- return data
-
- def sendGetMessage(self,message):
- message = ParrotProtocol.getRequest(message)
- return self.sendMessage(message)
-
- def sendSetMessage(self,message,arg):
- message = ParrotProtocol.setRequest(message,arg)
- return self.sendMessage(message)
-
- def sendMessage(self,message):
- try:
- self.sock.send(str(message))
- except:
- self.sock =""
- return
- if sys.platform == "darwin":
- data = self.sock.recv(30)
- else:
- data = self.sock.recv(7)
- data = self.sock.recv(1024)
- data=BeautifulSoup(data)
- return data
-
- def Close(self):
- self.sock.close() \ No newline at end of file
diff --git a/ParrotZikTray b/ParrotZikTray
deleted file mode 100755
index d9bdbc1..0000000
--- a/ParrotZikTray
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import gtk
-import re
-import os
-import ParrotZik
-import BluetoothPairedDevices
-from SysIndicator import *
-
-UPDATE_FREQUENCY = 30 # seconds
-
-class ParrotZikIndicator(SysIndicator):
- def __init__(self):
-
- self.menu = UniversalMenu()
-
- self.info_item = MenuItem("Parrot Zik Not connected..",None,sensitive = False)
- self.menu.append(self.info_item)
-
- self.battery_level = MenuItem("Battery Level:",None,sensitive = False)
- self.menu.append(self.battery_level)
-
- self.battery_state = MenuItem("Battery State:",None,sensitive = False)
- self.menu.append(self.battery_state)
-
- self.firmware_version = MenuItem("Firmware Version:",None,sensitive = False)
- self.menu.append(self.firmware_version)
-
- self.check = MenuItem("Noise Cancellation",self.toggleANC,sensitive = False, checkitem = True)
- self.menu.append(self.check)
-
- self.check2 = MenuItem("Auto Connection",self.toggleAuto,sensitive = False, checkitem = True)
- self.menu.append(self.check2)
-
- self.check3 = MenuItem("Lou Reed Mode",self.toggleLouReedMode,sensitive = False, checkitem = True)
- self.menu.append(self.check3)
-
- self.check4 = MenuItem("Concert Hall Mode",self.toggleParrotConcertHall,sensitive = False, checkitem = True)
- self.menu.append(self.check4)
-
- self.quit = MenuItem("Quit",sys.exit,sensitive = True, checkitem = True)
- self.menu.append(self.quit)
-
- SysIndicator.__init__(self,icon = "zik-audio-headset",menu = self.menu)
-
- self.connected=False
- self.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}')
-
- def EstablishConnection(self):
- if self.connected:
- if not self.parrot.sock:
- print "Lost connection"
- self.connected = False
- else:
- print "Connection already established"
- else:
- mac=BluetoothPairedDevices.ParrotZikMac()
- if mac:
- self.parrot = ParrotZik.ParrotZik(mac)
- if not self.parrot.sock:
- print "Failed to connect to Parrot Zik %s" % mac
- return False
-
- self.connected = True
- self.name = self.parrot.getFriendlyName()
- self.version = self.parrot.getVersion()
-
- self.check.set_sensitive(True)
- self.check2.set_sensitive(True)
- self.check3.set_sensitive(True)
- self.check4.set_sensitive(True)
-
- if self.parrot.getNoiseCancel() == "true":
- self.check.set_active(True)
- else:
- self.check.set_active(False)
-
- if self.parrot.getAutoConnection() == "true":
- self.check2.set_active(True)
- else:
- self.check2.set_active(False)
-
- if self.parrot.getLouReedMode() == "true":
- self.check3.set_active(True)
- else:
- self.check3.set_active(False)
-
- if self.parrot.getParrotConcertHall() == "true":
- self.check4.set_active(True)
- else:
- self.check4.set_active(False)
-
- self.CheckBattery()
- return True
-
- def toggleANC(self,widget):
- if self.connected:
- if self.check.get_active():
- self.parrot.setNoiseCancel("true")
- else:
- self.parrot.setNoiseCancel("false")
-
- def toggleAuto(self,widget):
- if self.connected:
- if self.check2.get_active():
- self.parrot.setAutoConnection("true")
- else:
- self.parrot.setAutoConnection("false")
-
- def toggleLouReedMode(self,widget):
- if self.connected:
- if self.check3.get_active():
- self.parrot.setLouReedMode("true")
- self.check4.set_sensitive(False)
- else:
- self.parrot.setLouReedMode("false")
- self.check4.set_sensitive(True)
-
- def toggleParrotConcertHall(self,widget):
- if self.connected:
- if self.check4.get_active():
- self.parrot.setParrotConcertHall("true")
- else:
- self.parrot.setParrotConcertHall("false")
-
- def CheckBattery(self):
- if self.connected:
- print "Updating battery"
- self.batteryLevel = int(self.parrot.getBatteryLevel())
-
- if self.parrot.BatteryCharging:
- self.batteryLevel = "Charging"
- self.setIcon("zik-battery-charging")
- self.batteryLevel="Unknown"
- self.batteryState="Charging"
- elif self.batteryLevel>80:
- self.setIcon("zik-battery-100")
- self.batteryState="In Use"
- elif self.batteryLevel>60:
- self.setIcon("zik-battery-080")
- self.batteryState="In Use"
- elif self.batteryLevel>40:
- self.setIcon("zik-battery-060")
- self.batteryState="In Use"
- elif self.batteryLevel>20:
- self.setIcon("zik-battery-040")
- self.batteryState="In Use"
- else:
- self.setIcon("zik-battery-low")
- self.batteryState="In Use"
-
- self.info_item.set_label("Connected to: "+self.name)
- self.firmware_version.set_label("Firmware version: "+self.version)
- self.battery_state.set_label("State: "+self.batteryState)
- self.battery_level.set_label("Battery Level: "+str(self.batteryLevel))
- else:
- self.setIcon("zik-audio-headset")
- self.info_item.set_label("Parrot Zik Not connected..")
- self.check.set_sensitive(False)
- self.check2.set_sensitive(False)
- self.check3.set_sensitive(False)
- self.check4.set_sensitive(False)
- return True
-
- def main(self):
- self.EstablishConnection()
- gtk.timeout_add(UPDATE_FREQUENCY * 1000, self.EstablishConnection)
- gtk.timeout_add(UPDATE_FREQUENCY * 1000, self.CheckBattery)
- SysIndicator.main(self)
-
-if __name__ == "__main__":
- indicator = ParrotZikIndicator()
- indicator.main() \ No newline at end of file
diff --git a/SysIndicator.py b/SysIndicator.py
deleted file mode 100644
index 3c79286..0000000
--- a/SysIndicator.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import re
-import os
-import tempfile
-
-if sys.platform=="linux2" or sys.platform=="win32":
- import gtk
-elif sys.platform=="darwin":
- import objc
- from Foundation import *
- from AppKit import *
- from PyObjCTools import AppHelper
- from StatusAppMac import StatusApp
-
-
-class SysIndicator:
- def __init__(self, icon,menu):
- if sys.platform=="linux2":
- self.menu = menu.gtk_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
- self.statusicon = appindicator.Indicator("new-parrotzik-indicator",
- "indicator-messages",
- appindicator.CATEGORY_APPLICATION_STATUS)
- self.statusicon.set_status(appindicator.STATUS_ACTIVE)
- self.statusicon.set_icon_theme_path(self.icon_directory)
- self.statusicon.set_menu(self.menu)
-
- elif sys.platform=="win32":
- self.menu = menu.gtk_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.statusicon = gtk.StatusIcon()
- self.statusicon.connect("popup-menu", self.gtk_right_click_event)
- self.statusicon.set_tooltip("Parrot Zik")
- 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")
-
- elif sys.platform=="darwin":
- 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.statusicon = StatusApp.sharedApplication()
- self.statusicon.initMenu(menu)
-
- self.setIcon(icon)
-
- def setIcon(self, name):
- if sys.platform=="linux2":
- self.statusicon.set_icon(name)
- elif sys.platform=="win32":
- self.statusicon.set_from_file(self.icon_directory+name+'.png')
- elif sys.platform=="darwin":
- self.statusicon.setIcon(name,self.icon_directory)
-
- 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 main(self):
- if sys.platform=="linux2" or sys.platform=="win32":
- gtk.main()
- elif sys.platform=="darwin":
- #self.statusicon.run()
- AppHelper.runEventLoop()
-
- def show_about_dialog(self, widget):
- if sys.platform=="linux2" or sys.platform=="win32":
- 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 UniversalMenu:
- def __init__(self):
- if sys.platform=="linux2" or sys.platform=="win32":
- self.gtk_menu = gtk.Menu()
- elif sys.platform=="darwin":
- self.actions = {}
- self.menubarMenu = NSMenu.alloc().init()
- self.menubarMenu.setAutoenablesItems_(False)
-
-
- def append(self,MenuItem):
- if sys.platform=="linux2" or sys.platform=="win32":
- self.gtk_menu.append(MenuItem.gtk_item)
- elif sys.platform=="darwin":
- self.actions[MenuItem.title] = MenuItem.action
- self.menubarMenu.addItem_(MenuItem.nsmenu_item)
-
-class MenuItem:
- def __init__(self,name,action,sensitive = True, checkitem = False):
- if sys.platform=="linux2" or sys.platform=="win32":
- if checkitem:
- self.gtk_item=gtk.CheckMenuItem(name)
- else:
- self.gtk_item=gtk.MenuItem(name)
- self.gtk_item.show()
- if action:
- self.gtk_item.connect("activate", action)
-
- elif sys.platform=="darwin":
- self.title = name
- self.action = action
- self.nsmenu_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(name, 'clicked:', '')
-
- self.set_sensitive(sensitive)
-
- def set_sensitive(self,option):
- if sys.platform=="linux2" or sys.platform=="win32":
- return self.gtk_item.set_sensitive(option)
- elif sys.platform=="darwin":
- self.nsmenu_item.setEnabled_(option)
-
- def set_active(self,option):
- if sys.platform=="linux2" or sys.platform=="win32":
- return self.gtk_item.set_active(option)
- elif sys.platform=="darwin":
- self.nsmenu_item.setState_(option)
-
- def get_active(self):
- if sys.platform=="linux2" or sys.platform=="win32":
- return self.gtk_item.get_active()
- elif sys.platform=="darwin":
- print self.nsmenu_item.state
- return self.nsmenu_item.state
-
-
- def set_label(self,option):
- if sys.platform=="linux2" or sys.platform=="win32":
- return self.gtk_item.set_label(option)
- elif sys.platform=="darwin":
- self.title = option
- self.nsmenu_item.setTitle_(option)
- #self.rumps_item.title=option
-
-if __name__ == "__main__":
-
- quit_item = MenuItem("Quit",sys.exit,True)
-
- menu = UniversalMenu()
- menu.append(quit_item)
-
- indicator = SysIndicator(icon = "zik-audio-headset",menu = menu)
- indicator.main() \ No newline at end of file
diff --git a/parrot_zik/__init__.py b/parrot_zik/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parrot_zik/__init__.py
diff --git a/parrot_zik/bluetooth_paired_devices.py b/parrot_zik/bluetooth_paired_devices.py
new file mode 100644
index 0000000..905819f
--- /dev/null
+++ b/parrot_zik/bluetooth_paired_devices.py
@@ -0,0 +1,171 @@
+import dbus
+import sys
+import re
+from subprocess import Popen, PIPE, STDOUT
+
+from .resource_manager import GenericResourceManager
+
+if sys.platform == "darwin":
+ from binplist import binplist
+ import lightblue
+else:
+ import bluetooth
+ if sys.platform == "win32":
+ import _winreg
+
+
+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}')
+
+
+class BluetoothDeviceManager(object):
+ def is_bluetooth_on(self):
+ raise NotImplementedError
+
+ def get_mac(self):
+ raise NotImplementedError
+
+
+class BluezBluetoothDeviceManager(BluetoothDeviceManager):
+ def is_bluetooth_on(self):
+ pipe = Popen(['bluez-test-adapter', 'powered'], stdout=PIPE, stdin=PIPE,
+ stderr=STDOUT)
+ try:
+ stdout, stderr = pipe.communicate()
+ except dbus.exceptions.DBusException:
+ pass
+ else:
+ return bool(stdout.strip())
+
+ def get_mac(self):
+ pipe = Popen(['bluez-test-device', 'list'], stdout=PIPE, stdin=PIPE,
+ stderr=STDOUT)
+ try:
+ stdout, stderr = pipe.communicate()
+ except dbus.exceptions.DBusException:
+ pass
+ else:
+ res = p.findall(stdout)
+ if len(res) > 0:
+ return res[0]
+ else:
+ raise DeviceNotConnected
+
+
+class BluetoothCmdDeviceManager(BluetoothDeviceManager):
+ def is_bluetooth_on(self):
+ return True
+
+ def get_mac(self):
+ pipe = Popen(['bluetoothctl'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
+ res = pipe.communicate("exit")
+ if len(res) > 0 and res[0]:
+ match = p.search(res[0])
+ if match:
+ return match.group(0)
+ raise DeviceNotConnected
+
+
+def get_parrot_zik_mac_linux():
+ bluez_manager = BluezBluetoothDeviceManager()
+ try:
+ bluez_manager.is_bluetooth_on()
+ return bluez_manager.get_mac()
+ except OSError as e:
+ if e.errno == 2:
+ bluetoothcmd_manager = BluetoothCmdDeviceManager()
+ return bluetoothcmd_manager.get_mac()
+
+
+def get_parrot_zik_mac_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
+
+
+def get_parrot_zik_mac_windows():
+ 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
+
+
+if sys.platform in ['linux', 'linux2']:
+ get_parrot_zik_mac = get_parrot_zik_mac_linux
+elif sys.platform == 'darwin':
+ get_parrot_zik_mac = get_parrot_zik_mac_darwin
+elif sys.platform == 'win32':
+ get_parrot_zik_mac = get_parrot_zik_mac_windows
+else:
+ raise AssertionError('Platform not supported')
+
+
+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)
+
+ try:
+ sock.connect((host, port))
+ except bluetooth.btcommon.BluetoothError:
+ raise ConnectionFailure
+
+ 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/__init__.py b/parrot_zik/indicator/__init__.py
new file mode 100644
index 0000000..156f7de
--- /dev/null
+++ b/parrot_zik/indicator/__init__.py
@@ -0,0 +1,25 @@
+__all__ = ('SysIndicator', 'Menu', 'MenuItem')
+
+import sys
+
+if sys.platform in ['linux', 'linux2']:
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--gtk", action="store_true")
+ args = parser.parse_args()
+ if args.gtk:
+ from parrot_zik.indicator.linux import LinuxGtkIndicator as SysIndicator
+ else:
+ from parrot_zik.indicator.linux import LinuxAppIndicator as SysIndicator
+ from parrot_zik.indicator.gtk_wrapping import GTKMenuItem as MenuItem
+ from parrot_zik.indicator.gtk_wrapping import GTKMenu as Menu
+elif sys.platform in ['win32']:
+ from parrot_zik.indicator.windows import WindowsIndicator as SysIndicator
+ from parrot_zik.indicator.gtk_wrapping import GTKMenuItem as MenuItem
+ from parrot_zik.indicator.gtk_wrapping import GTKMenu as Menu
+elif sys.platform == 'darwin':
+ from parrot_zik.indicator.mac import DarwinIndicator as SysIndicator
+ from parrot_zik.indicator.mac import NSMenuItem as MenuItem
+ from parrot_zik.indicator.mac import NSMenu as Menu
+else:
+ raise Exception('Platform not supported')
diff --git a/parrot_zik/indicator/base.py b/parrot_zik/indicator/base.py
new file mode 100644
index 0000000..b29368e
--- /dev/null
+++ b/parrot_zik/indicator/base.py
@@ -0,0 +1,48 @@
+class BaseIndicator(object):
+ def __init__(self, icon, menu, statusicon):
+ self.menu = menu
+ self.statusicon = statusicon
+ self.setIcon(icon)
+
+ def setIcon(self, name):
+ raise NotImplementedError
+
+ @classmethod
+ def main(cls):
+ raise NotImplementedError
+
+ def show_about_dialog(self, widget):
+ raise NotImplementedError
+
+ def quit(self, _):
+ raise NotImplementedError
+
+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
diff --git a/parrot_zik/indicator/gtk_wrapping.py b/parrot_zik/indicator/gtk_wrapping.py
new file mode 100644
index 0000000..6d4a5a8
--- /dev/null
+++ b/parrot_zik/indicator/gtk_wrapping.py
@@ -0,0 +1,46 @@
+import gtk
+
+from parrot_zik.indicator.base import MenuItemBase
+
+
+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()
+
+ def popup(self, *args, **kwargs):
+ self.gtk_menu.popup(*args, **kwargs)
+
+ def poVpdown(self, *args, **kwargs):
+ pass
+
+
+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)
diff --git a/parrot_zik/indicator/linux.py b/parrot_zik/indicator/linux.py
new file mode 100644
index 0000000..f6f9cfc
--- /dev/null
+++ b/parrot_zik/indicator/linux.py
@@ -0,0 +1,69 @@
+import os
+
+import gtk
+
+from parrot_zik.indicator.base import BaseIndicator
+
+
+class LinuxIndicator(BaseIndicator):
+ def __init__(self, icon, menu, statusicon):
+ super(LinuxIndicator, self).__init__(icon, menu, statusicon)
+
+ 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.poVpdown()
+
+ @classmethod
+ def main(cls):
+ gtk.main()
+
+ def quit(self, _):
+ gtk.main_quit()
+
+ 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 LinuxAppIndicator(LinuxIndicator):
+ def __init__(self, icon, menu):
+ import appindicator
+ self.icon_directory = os.path.join('/', 'usr', 'share', 'icons', 'zik')
+ if not os.path.isdir(self.icon_directory):
+ self.icon_directory = os.path.join('share', 'icons', 'zik')
+ 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)
+
+
+
+class LinuxGtkIndicator(LinuxIndicator):
+ def __init__(self, icon, menu):
+ self.icon_directory = os.path.join(
+ '/usr', 'share', 'icons/')
+ self.menu_shown = False
+ statusicon = gtk.StatusIcon()
+ statusicon.connect("popup-menu", self.gtk_right_click_event)
+ statusicon.set_tooltip("Parrot Zik")
+ super(LinuxIndicator, self).__init__(icon, menu, statusicon)
+
+ def setIcon(self, name):
+ self.statusicon.set_from_file(self.icon_directory + name + '.png')
+
diff --git a/parrot_zik/indicator/mac.py b/parrot_zik/indicator/mac.py
new file mode 100644
index 0000000..ceeacb0
--- /dev/null
+++ b/parrot_zik/indicator/mac.py
@@ -0,0 +1,70 @@
+import os
+import sys
+
+from Foundation import *
+from AppKit import *
+from PyObjCTools import AppHelper
+
+from parrot_zik.indicator.base import BaseIndicator
+from parrot_zik.indicator.base import MenuItemBase
+from parrot_zik.status_app_mac import StatusApp
+
+
+class DarwinIndicator(BaseIndicator):
+ def __init__(self, icon, menu):
+ self.icon_directory = os.path.join(
+ os.path.dirname(os.path.realpath(sys.argv[0])), 'share', 'icons', 'zik')
+ statusicon = StatusApp.sharedApplication()
+ statusicon.initMenu(menu)
+ super(DarwinIndicator, self).__init__(icon, menu, statusicon)
+
+ def setIcon(self, name):
+ self.statusicon.setIcon(name, self.icon_directory)
+
+ @classmethod
+ def main(cls):
+ AppHelper.runEventLoop()
+
+ def show_about_dialog(self, widget):
+ pass
+
+ def quit(self, _):
+ 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 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)
diff --git a/parrot_zik/indicator/windows.py b/parrot_zik/indicator/windows.py
new file mode 100644
index 0000000..f10c91a
--- /dev/null
+++ b/parrot_zik/indicator/windows.py
@@ -0,0 +1,47 @@
+import sys
+import tempfile
+import gtk
+import os
+
+from parrot_zik.indicator.base import BaseIndicator
+
+
+class WindowsIndicator(BaseIndicator):
+ def __init__(self, icon, menu):
+ self.icon_directory = os.path.join(
+ os.path.dirname(os.path.realpath(sys.argv[0])), 'share', 'icons', 'zik')
+ self.menu_shown = False
+ sys.stdout = open(os.path.join(tempfile.gettempdir(), "zik_tray_stdout.log", "w"))
+ sys.stderr = open(os.path.join(tempfile.gettempdir(), "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 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.poVpdown()
+
+ def setIcon(self, name):
+ self.statusicon.set_from_file(self.icon_directory + name + '.png')
+
+ @classmethod
+ def main(cls):
+ gtk.main()
+
+ def quit(self, _):
+ gtk.main_quit()
+
+ 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()
diff --git a/parrot_zik/interface/__init__.py b/parrot_zik/interface/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parrot_zik/interface/__init__.py
diff --git a/parrot_zik/interface/base.py b/parrot_zik/interface/base.py
new file mode 100644
index 0000000..90caa02
--- /dev/null
+++ b/parrot_zik/interface/base.py
@@ -0,0 +1,95 @@
+from parrot_zik import resource_manager
+from parrot_zik.indicator import Menu
+from parrot_zik.indicator import MenuItem
+from parrot_zik.model.base import BatteryStates
+
+RECONNECT_FREQUENCY = 5000
+
+
+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.settings = MenuItem("Settings", None, visible=False)
+ self.settings_submenu = Menu()
+ self.settings.set_submenu(self.settings_submenu)
+
+ self.auto_connection = MenuItem("Auto Connection", self.toggle_auto_connection,
+ checkitem=True)
+ self.settings_submenu.append(self.auto_connection)
+
+ 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.settings)
+
+ 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.settings.show()
+ self.indicator.active_interface = self
+
+ @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.settings.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))
diff --git a/parrot_zik/interface/version1.py b/parrot_zik/interface/version1.py
new file mode 100644
index 0000000..d0a1b2b
--- /dev/null
+++ b/parrot_zik/interface/version1.py
@@ -0,0 +1,63 @@
+from parrot_zik import resource_manager
+from parrot_zik.indicator import MenuItem
+from parrot_zik.interface.base import ParrotZikBaseInterface
+from parrot_zik.model.version1 import ParrotZikVersion1
+
+
+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):
+ 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)
+
+ self.noise_cancelation.show()
+ self.lou_reed_mode.show()
+ self.concert_hall_mode.show()
+ self.indicator.menu.reposition()
+
+ 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()
diff --git a/parrot_zik/interface/version2.py b/parrot_zik/interface/version2.py
new file mode 100644
index 0000000..e6076ed
--- /dev/null
+++ b/parrot_zik/interface/version2.py
@@ -0,0 +1,239 @@
+import functools
+
+from parrot_zik import resource_manager
+from parrot_zik.indicator import MenuItem, Menu
+from parrot_zik.interface.base import ParrotZikBaseInterface
+from parrot_zik.model.version2 import ParrotZikVersion2
+from parrot_zik.model.version2 import NoiseControlTypes
+from parrot_zik.model.version2 import Rooms
+
+
+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)
+ self.noise_control_cancelation_on = MenuItem(
+ "Normal Cancelation", functools.partial(
+ self.toggle_noise_cancelation,
+ NoiseControlTypes.NOISE_CONTROL_ON), checkitem=True)
+ self.noise_control_off = MenuItem(
+ "Off", functools.partial(
+ self.toggle_noise_cancelation,
+ NoiseControlTypes.NOISE_CONTROL_OFF), checkitem=True)
+ self.noise_control_street_mode = MenuItem(
+ "Street Mode", functools.partial(
+ self.toggle_noise_cancelation,
+ NoiseControlTypes.STREET_MODE), checkitem=True)
+ self.noise_control_street_mode_max = MenuItem(
+ "Street Mode Max", functools.partial(
+ self.toggle_noise_cancelation,
+ NoiseControlTypes.STREET_MODE_MAX), checkitem=True)
+ 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.head_detection = MenuItem("Head Detection", self.toggle_head_detection, checkitem=True)
+ self.settings_submenu.append(self.head_detection)
+
+ 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):
+ 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()
+ self.head_detection.set_active(self.parrot.head_detection)
+
+ 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)
+
+ self.noise_cancelation.show()
+ self.flight_mode.show()
+ self.room_sound_effect.show()
+ self.indicator.menu.reposition()
+
+ 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)
+
+ def toggle_head_detection(self, widget):
+ try:
+ self.parrot.head_detection = self.head_detection.get_active()
+ self.head_detection.set_active(self.parrot.head_detection)
+ except resource_manager.DeviceDisconnected:
+ self.deactivate()
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/model/__init__.py b/parrot_zik/model/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parrot_zik/model/__init__.py
diff --git a/parrot_zik/model/base.py b/parrot_zik/model/base.py
new file mode 100644
index 0000000..fdacde0
--- /dev/null
+++ b/parrot_zik/model/base.py
@@ -0,0 +1,60 @@
+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] or 0)
+
+ @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
+ elif result == "invalid_on":
+ return True
+ else:
+ raise AssertionError(result)
+
+class BatteryStates:
+ CHARGED = 'charged'
+ IN_USE = 'in_use'
+ CHARGING = 'charging'
+ representation = {
+ CHARGED: 'Charged',
+ IN_USE: 'In Use',
+ CHARGING: 'Charging',
+ }
diff --git a/parrot_zik/model/version1.py b/parrot_zik/model/version1.py
new file mode 100644
index 0000000..324cc9f
--- /dev/null
+++ b/parrot_zik/model/version1.py
@@ -0,0 +1,48 @@
+from parrot_zik.model.base import ParrotZikBase
+from parrot_zik.resource_manager import Version1ResourceManager
+
+
+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.set("/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.set("/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)
diff --git a/parrot_zik/model/version2.py b/parrot_zik/model/version2.py
new file mode 100644
index 0000000..0cbdc1a
--- /dev/null
+++ b/parrot_zik/model/version2.py
@@ -0,0 +1,131 @@
+from parrot_zik.model.base import ParrotZikBase
+from parrot_zik.resource_manager import Version2ResourceManager
+
+
+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):
+ self.resource_manager.set('/api/audio/noise_control', arg)
+
+ @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'])
+
+ @property
+ def head_detection(self):
+ answer = self.resource_manager.get('/api/system/head_detection/enabled')
+ return self._result_to_bool(answer.system.head_detection['enabled'])
+
+ @head_detection.setter
+ def head_detection(self, arg):
+ self.resource_manager.set('/api/system/head_detection/enabled', arg)
+
+
+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 '{}&value={}'.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 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 SoundSource:
+ LINE_IN = 'line-in'
+ A2DP = 'a2dp'
diff --git a/parrot_zik/parrot_zik_tray.py b/parrot_zik/parrot_zik_tray.py
new file mode 100755
index 0000000..35931d7
--- /dev/null
+++ b/parrot_zik/parrot_zik_tray.py
@@ -0,0 +1,80 @@
+from parrot_zik.interface.version1 import ParrotZikVersion1Interface
+from parrot_zik.interface.version2 import ParrotZikVersion2Interface
+from parrot_zik import resource_manager
+from parrot_zik import bluetooth_paired_devices
+from parrot_zik.indicator import MenuItem
+from parrot_zik.indicator import Menu
+from parrot_zik.indicator import SysIndicator
+from parrot_zik.utils import repeat
+
+REFRESH_FREQUENCY = 30000
+RECONNECT_FREQUENCY = 5000
+
+
+
+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_item = MenuItem("Quit", self.quit, checkitem=True)
+ self.menu.append(self.quit_item)
+
+ 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()
+
+ @classmethod
+ def main(cls):
+ try:
+ indicator = cls()
+ cls.reconnect.start(indicator, RECONNECT_FREQUENCY)
+ super(ParrotZikIndicator, cls).main()
+ except KeyboardInterrupt:
+ pass
diff --git a/parrot_zik/resource_manager.py b/parrot_zik/resource_manager.py
new file mode 100644
index 0000000..aa141cb
--- /dev/null
+++ b/parrot_zik/resource_manager.py
@@ -0,0 +1,177 @@
+import bluetooth
+from operator import itemgetter
+import sys
+
+from bs4 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 "{}" for {}'.format(
+ data, message.request_string))
+ 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', 'set'],
+ '/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/StatusAppMac.py b/parrot_zik/status_app_mac.py
index fc0bc64..dccffe4 100644
--- a/StatusAppMac.py
+++ b/parrot_zik/status_app_mac.py
@@ -1,13 +1,12 @@
-import objc
from Foundation import *
from AppKit import *
-from PyObjCTools import AppHelper
class StatusApp(NSApplication):
- def initMenu(self,menu):
+ def initMenu(self, menu):
statusbar = NSStatusBar.systemStatusBar()
- self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
+ self.statusitem = statusbar.statusItemWithLength_(
+ NSVariableStatusItemLength)
self.mymenu = menu
#add menu to statusitem
@@ -15,11 +14,12 @@ class StatusApp(NSApplication):
self.statusitem.setToolTip_('Parrot Zik Indicator')
def setIcon(self,icon,icon_directory):
- self.icon = NSImage.alloc().initByReferencingFile_(icon_directory+icon+'.png')
+ 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!') \ No newline at end of file
+ NSLog('clicked!')
diff --git a/parrot_zik/utils.py b/parrot_zik/utils.py
new file mode 100644
index 0000000..86f52cd
--- /dev/null
+++ b/parrot_zik/utils.py
@@ -0,0 +1,32 @@
+import functools
+from threading import Lock
+
+import gtk
+
+
+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:
+ @functools.wraps(self.f)
+ 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()
+
diff --git a/setup.py b/setup.py
index ffdac88..01fa2c2 100644
--- a/setup.py
+++ b/setup.py
@@ -1,49 +1,53 @@
-from setuptools import setup
import glob
import sys
-if sys.platform=="win32":
- import py2exe
- from distutils.core import setup
+if sys.platform == "win32":
+ from distutils.core import setup
+else:
+ from setuptools import setup
setup(
- name = 'parrotziktray',
- description = 'Parrot Zik Tray Indicator',
+ name='parrotziktray',
+ description='Parrot Zik Tray Indicator',
author="Dmitry Moiseev",
author_email="m0sia@m0sia.ru",
maintainer_email="m0sia@m0sia.ru",
url="https://github.com/m0sia/pyParrotZik",
license="'GPLv2+'",
- version = '0.3',
-
- windows = [
- {
- 'script': 'ParrotZikTray',
- 'icon_resources': [(1, "./share/icons/zik/Headphone.ico")],
- }
- ],
-
- options = {
- 'py2exe': {
- #'packages':'encodings',
- # Optionally omit gio, gtk.keysyms, and/or rsvg if you're not using them
- 'includes': 'cairo, pango, pangocairo, atk, gobject, gio, gtk.keysyms, _winreg',
- }
- },
+ version='0.3',
+
+ windows=[
+ {
+ 'script': 'parrot_zik/parrot_zik_tray',
+ 'icon_resources': [(1, "./share/icons/zik/Headphone.ico")],
+ }
+ ],
+
+ options={
+ 'py2exe': {
+ #'packages':'encodings',
+ # Optionally omit gio, gtk.keysyms, and/or rsvg if you're not using them
+ 'includes': 'cairo, pango, pangocairo, atk, gobject, gio, gtk.keysyms, _winreg',
+ }
+ },
data_files=[
- ("share/icons/zik", glob.glob("share/icons/zik/*.png"))
- # If using GTK+'s built in SVG support, uncomment these
- #os.path.join(gtk_base_path, '..', 'runtime', 'bin', 'gdk-pixbuf-query-loaders.exe'),
- #os.path.join(gtk_base_path, '..', 'runtime', 'bin', 'libxml2-2.dll'),
- ],
+ ("share/icons/zik", glob.glob("share/icons/zik/*.png"))
+ # If using GTK+'s built in SVG support, uncomment these
+ #os.path.join(gtk_base_path, '..', 'runtime', 'bin', 'gdk-pixbuf-query-loaders.exe'),
+ #os.path.join(gtk_base_path, '..', 'runtime', 'bin', 'libxml2-2.dll'),
+ ],
install_requires=[
- 'BeautifulSoup','bluetooth'
- ],
-
- py_modules=['ParrotZik','ParrotProtocol'],
-
- scripts=["ParrotZikTray"]
+ 'beautifulsoup4', 'pybluez'
+ ],
+
+ packages=['parrot_zik', 'parrot_zik.interface', 'parrot_zik.indicator', 'parrot_zik.model'],
+ entry_points={
+ 'console_scripts': [
+ 'parrot_zik_tray=parrot_zik.parrot_zik_tray:ParrotZikIndicator.main',
+ ]
+ },
+ include_package_data=True,
)