Plantronic Voyager Legend Bluetooth and BlueZ

According to the specifications, Platronics Voyager Legend uses [Phone Book Access Profile (PBAP)]. BlueZ lists [PBAP] as a supported protocol.

python test/test-device list

48:C1:AC:E2:21:A7 PLT_Legend

python test/test-device connect 48:C1:AC:E2:21:A7 PBAP

Name=PLT_Legend
Paired=1
Modalias=bluetooth:v0055p0113d005D
Connected=0
Address=48:C1:AC:E2:21:A7
Alias=PLT_Legend
dbus.Dictionary({dbus.String(u’Name’): dbus.String(u’PLT_Legend’, variant_level=1), dbus.String(u’Paired’): dbus.Boolean(True, variant_level=1), dbus.String(u’Modalias’): dbus.String(u’bluetooth:v0055p0113d005D’, variant_level=1), dbus.String(u’Adapter’): dbus.ObjectPath(‘/org/bluez/hci0′, variant_level=1), dbus.String(u’LegacyPairing’): dbus.Boolean(False, variant_level=1), dbus.String(u’Alias’): dbus.String(u’PLT_Legend’, variant_level=1), dbus.String(u’Connected’): dbus.Boolean(False, variant_level=1), dbus.String(u’UUIDs’): dbus.Array([dbus.String(u’00001108-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110b-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110c-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110e-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000111e-0000-1000-8000-00805f9b34fb’), dbus.String(u’00001200-0000-1000-8000-00805f9b34fb’), dbus.String(u’82972387-294e-4d62-97b5-2668aa35f618′)], signature=dbus.Signature(‘s’), variant_level=1), dbus.String(u’Address’): dbus.String(u’48:C1:AC:E2:21:A7′, variant_level=1), dbus.String(u’Blocked’): dbus.Boolean(False, variant_level=1), dbus.String(u’Class’): dbus.UInt32(2360324L, variant_level=1), dbus.String(u’Trusted’): dbus.Boolean(True, variant_level=1), dbus.String(u’Icon’): dbus.String(u’audio-card’, variant_level=1)}, signature=dbus.Signature(‘sv’))
Traceback (most recent call last):
File “./test-device”, line 117, in
device.ConnectProfile(args[2])
File “/usr/lib/python2.7/dist-packages/dbus/proxies.py”, line 70, in __call__
return self._proxy_method(*args, **keywords)
File “/usr/lib/python2.7/dist-packages/dbus/proxies.py”, line 145, in __call__
**keywords)
File “/usr/lib/python2.7/dist-packages/dbus/connection.py”, line 651, in call_blocking
message, timeout)
dbus.exceptions.DBusException: org.bluez.Error.NotAvailable: Operation currently not available

python test/pbap-client 48:C1:AC:E2:21:A7

Creating Session
Traceback (most recent call last):
File “pbap-client”, line 128, in
session_path = client.CreateSession(sys.argv[1], { “Target”: “PBAP” })
File “/usr/lib/python2.7/dist-packages/dbus/proxies.py”, line 70, in __call__
return self._proxy_method(*args, **keywords)
File “/usr/lib/python2.7/dist-packages/dbus/proxies.py”, line 145, in __call__
**keywords)
File “/usr/lib/python2.7/dist-packages/dbus/connection.py”, line 651, in call_blocking
message, timeout)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownObject: Method “CreateSession” with signature “sa{ss}” on interface “org.bluez.obex.Client1” doesn’t exist

[Phone Book Access (PBAP) client for Linux (Raspberry Pi)]

Python with Bluetooth

I’m making another attempt to write a Python script that can list the paired Bluetooth devices.

Add library dependencies for gatt bluetooth protocol.

sudo apt-get install libperl-dev libgtk2.0-dev libboost-python-dev libboost-thread-dev libbluetooth-dev libglib2.0-dev python-dev

Install the Python BlueZ libraries.

sudo apt-get install python-bluez

That allows the Python BlueZ library to install.

sudo pip install PyBluez
sudo pip3 install PyBluez

Add the gattlib to support the gatt bluetooth protocol. Make sure all apps are closed as the `gattlib` setup scripts use a lot of memory and will not complete without sufficient RAM.

sudo pip install gattlib
sudo pip3 install gattlib

The [test-device python script] is able to list paired Bluetooth devices.

python test/test-device list

The org.freedesktop.DBus.Properties has a block of data on the PLT_Legend Bluetooth headset.

dbus.Dictionary({dbus.String(u’Name’): dbus.String(u’PLT_Legend’, variant_level=1), dbus.String(u’Paired’): dbus.Boolean(True, variant_level=1), dbus.String(u’Modalias’): dbus.String(u’bluetooth:v0055p0113d005D’, variant_level=1), dbus.String(u’Adapter’): dbus.ObjectPath(‘/org/bluez/hci0′, variant_level=1), dbus.String(u’LegacyPairing’): dbus.Boolean(False, variant_level=1), dbus.String(u’Alias’): dbus.String(u’PLT_Legend’, variant_level=1), dbus.String(u’Connected’): dbus.Boolean(False, variant_level=1), dbus.String(u’UUIDs’): dbus.Array([dbus.String(u’00001108-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110b-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110c-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000110e-0000-1000-8000-00805f9b34fb’), dbus.String(u’0000111e-0000-1000-8000-00805f9b34fb’), dbus.String(u’00001200-0000-1000-8000-00805f9b34fb’), dbus.String(u’82972387-294e-4d62-97b5-2668aa35f618′)], signature=dbus.Signature(‘s’), variant_level=1), dbus.String(u’Address’): dbus.String(u’48:C1:AC:E2:21:A7′, variant_level=1), dbus.String(u’Blocked’): dbus.Boolean(False, variant_level=1), dbus.String(u’Class’): dbus.UInt32(2360324L, variant_level=1), dbus.String(u’Trusted’): dbus.Boolean(True, variant_level=1), dbus.String(u’Icon’): dbus.String(u’audio-card’, variant_level=1)}, signature=dbus.Signature(‘sv’))

Resources:
[bluez]
[pybluez]
[Android Linux / Raspberry Pi Bluetooth communication]
[bluetooth commands]

List paired bluetooth devices.

pi@raspberrypi:~/ $ bluetoothctl
[NEW] Controller B8:27:EB:05:8D:D8 raspberrypi [default]
[NEW] Device 48:C1:AC:E2:21:A7 PLT_Legend

Get device info from `bluetoothctl` console.

[bluetooth]# info 48:C1:AC:E2:21:A7
Device 48:C1:AC:E2:21:A7
 Name: PLT_Legend
 Alias: PLT_Legend
 Class: 0x240404
 Icon: audio-card
 Paired: yes
 Trusted: yes
 Blocked: no
 Connected: no
 LegacyPairing: no
 UUID: Headset (00001108-0000-1000-8000-00805f9b34fb)
 UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
 UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
 UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
 UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
 UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
 UUID: Vendor specific (82972387-294e-4d62-97b5-2668aa35f618)
 Modalias: bluetooth:v0055p0113d005D

Bluetooth – Installing and Using Bluetooth on the Raspberry Pi

Experimental

[jessie-backports] [issue]

Edit the deb sources list.

/etc/apt/sources.list.d/raspi.list

Add jessie-backports to the `raspi.list` by adding the following line to the file.

deb http://ftp.debian.org/debian jessie-backports main

Suppress the public key warnings.

gpg --keyserver pgpkeys.mit.edu --recv-key 8B48AD6246925553
gpg -a --export 8B48AD6246925553 | sudo apt-key add -

gpg --keyserver pgpkeys.mit.edu --recv-key 7638D0442B90D010
gpg -a --export 7638D0442B90D010 | sudo apt-key add -

Update apt-get.

sudo apt-get update

Install the bluetooth manager

Upgrade the packages.

sudo apt-get upgrade

Install `blueman`.

sudo apt-get -t jessie-backports install bluetooth blueman bluez python-gobject python-gobject-2 pulseaudio-module-bluetooth

Turn on bluetooth device on boot

If you require turning on your bluetooth device at boot time automatically, for instance when you require keyboard / mouse support. You can add a udev rule to enable that. Additionally, you need to make sure you have the package bluez-utils installed.

Create `/etc/udev/rules.d/50-bluetooth-hci-auto-poweron.rules` with the following content.

ACTION=="add", SUBSYSTEM=="bluetooth", KERNEL=="hci[0-9]*", RUN+="/bin/hciconfig %k up"

Use pavucontrol to configure pulse audio

[pavucontrol] is a GUI front-end for [PulseAudio].

sudo apt-get -t jessie-backports install pavucontrol

[blueman-project] [issue]
[enable analog out]
[Bluetooth – Installing and Using Bluetooth on the Raspberry Pi]
[Installing the Raspberry Pi Nano Bluetooth Dongle]

pi@raspberrypi:~/Documents/PythonScripts/Microphone $ bluetoothctl 
[NEW] Controller B8:27:EB:22:51:00 raspberrypi [default]
[NEW] Device 48:C1:AC:E2:21:A7 PLT_Legend
[bluetooth]# quit
pi@raspberrypi:~/Documents/PythonScripts/Microphone $ sudo l2ping -c 1 48:C1:AC:E2:21:A7
Ping: 48:C1:AC:E2:21:A7 from B8:27:EB:22:51:00 (data size 44) ...
16 bytes from 48:C1:AC:E2:21:A7 id 0 time 8.55ms
1 sent, 1 received, 0% loss
pi@raspberrypi:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****

Windows IoT Core Insider Preview for Raspberry Pi 3

[Windows 10 IoT Core – The operating system built for your Internet of Things]

[Windows IoT Core Walkthrough]

Raspberry PI has two Windows IoT compatible Bluetooth dongles.

  • CSR Mini USB Bluetooth V4.0 Adapter – Class 2 Bluetooth 4.0 Smart Ready Adapter, low energy, dual power
  • ORICO BTA-403 Mini Bluetooth 4.0 USB Dongle – Low Energy Bluetooth 4.0 Adapter USB Micro Adapter Dongle [verified compatible]
  • [Download Windows 10 IoT Core Insider Preview]

    Download Windows 10 IoT Core Insider Preview – Build 16193 for RPI3 so that [Xamarin Forms BluetoothLE samples] work.

    The Windows 10 IoT Core files are installed under C:\Program Files (x86)\Microsoft IoT.

  • Build 14366 – Flashing the RPI2 build works on RPI3
  • The Windows IoT install creates a default user of `administrator` and default password of `p@ssw0rd`.
  • [Windows Insider Program]
    [Get Started]
    [Setup Visual Studio for IoT]
    [Enable your device for development]
    [Windows IoT Remote Client]
    [IoT Documentation and Samples]
    [Get started with your first Hello World IoT Windows Core App]
    [BTLE Samples]
    [Pairing a BLE Device and GATT Attribute Table Dump Tool]
    [How to: Enable Debugging of Unmanaged Code] (unfortunately Windows IoT Core doesn’t support debugging mixed mode yet…)
    [Audio Recorder App on Windows 10 IoT Core]
    [Bluetooth Programming with Windows Sockets]
    [Universal Windows app samples]

    Windows IoT Core provides a web interface accessible from the browser on port 8080. I.e. http://192.168.1.194:8080.

    Python Get Sunrise and Sunset

    Install the `astral` Python package.

    sudo pip install astral
    

    Display the sunrise and sunset.

    import datetime
    from astral import Location
    
    # Get sunrise and sunset for Monroe, WA
    l = Location()
    l.latitude = 47.887059
    l.longitude = -121.8792998
    l.timezone = 'US/Pacific'
    
    sunrise = l.sun()['dawn']
    sunriseHour = sunrise.strftime('%H')
    sunriseMinute = sunrise.strftime('%M')
    print 'sunrise hour='+str(sunriseHour)
    print 'sunrise minute='+str(sunriseMinute)
    
    sunset = l.sun()['sunset']
    sunsetHour = sunset.strftime('%H')
    sunsetMinute = sunset.strftime('%M')
    print 'sunset hour='+str(sunsetHour)
    print 'sunset minute='+str(sunsetMinute)
    

    Raspberry PI Set Time Zone

    After setting the time zone from the terminal, the Raspberry PI will report the correct time given an Internet connection.

    sudo dpkg-reconfigure tzdata
    

    The following code can read and print the hours, minutes, and seconds.

    import datetime
    
    hours = datetime.datetime.now().strftime('%H')
    minutes = datetime.datetime.now().strftime('%M')
    seconds = datetime.datetime.now().strftime('%S')
    print 'hours='+hours+' minutes='+minutes+' seconds='+seconds
    

    Python Get Internal IP Address

    import os
    import urllib
    
    # run command to get wlan0 internal IP address
    command = os.popen('ifconfig | grep -A1 wlan0')
    
    # ready the command data
    client_ip = command.read()
    
    # put the internal IP address into the query params
    query = { 'client_ip' : client_ip }
    url = "http://domain/path/to/some.php?"+urllib.urlencode(query)
    
    # print the final url
    print url
    

    Change Exposure Time

    Import the fraction library.

    from fractions import Fraction
    

    Setting a longer exposure is handy in low-light environments.

    camera.framerate = Fraction(1,6)
    camera.shutter_speed = 6000000
    camera.exposure_mode = 'off'
    camera.iso = 800
    

    Raspberry PI Security Camera

    To setup a Raspberry PI security camera.

    I flash the SD with the latest [NOOBS] after formatting the SD card with the [SDCard Formatter].

    Boot into the desktop and put the Raspberry PI on the wifi network.

    Enable the [camera interface] and reboot.

    Sudo edit the /boot/config.txt file. Disable the camera LED by adding the entry:

    disable_camera_led=1
    

    Install the [poster] module by running the command-line:

    sudo pip install poster
    

    Create the file: ~/superscript

    cd ~/Documents/PythonScripts/Camera/
    python capture_image.py
    

    Add execute permissions to superscript.

    chmod +x ~/superscript
    

    Create a folder for the Camera Python scripts.

    mkdir ~/Documents/PythonScripts
    mkdir ~/Documents/PythonScripts/Camera/
    

    Create the ~/Documents/PythonScripts/Camera/capture_image.py script. Rotate the camera as necessary.

    #get access to the camera
    from picamera import PiCamera
    
    #import so we can invoke another script
    import subprocess
    
    #get access to the clock
    import datetime
    
    # get access to sunrise and sunset
    from astral import Location
    
    #sleep so we can wait on the camera
    from time import sleep
    
    from fractions import Fraction
    
    # create a camera object
    camera = PiCamera()
    
    #set the image resolution
    camera.resolution = (640, 480)
    
    #rotate the camera if upside-down
    camera.rotation = 180
    
    #flip the camera on the horizontal
    camera.hflip = True
    
    i = 0;
    
    #record defaults
    framerate = camera.framerate
    shutter_speed = camera.shutter_speed
    exposure_mode = camera.exposure_mode
    iso = camera.iso
    
    while True:
    
      # Get sunrise and sunset for Monroe, WA
      l = Location()
      l.latitude = 47.887059
      l.longitude = -121.8792998
      l.timezone = 'US/Pacific'
    
      sunrise = l.sun()['dawn']
      sunriseHour = int(sunrise.strftime('%H'))
      sunriseMinute = int(sunrise.strftime('%M'))
      sunset = l.sun()['sunset']
      sunsetHour = int(sunset.strftime('%H'))
      sunsetMinute = int(sunset.strftime('%M'))
    
      hours = int(datetime.datetime.now().strftime('%H'))
      minutes = int(datetime.datetime.now().strftime('%M'))
      seconds = int(datetime.datetime.now().strftime('%S'))
    
      if (hours >= sunriseHour and hours <= sunsetHour):
        #print 'work in the light'
        camera.framerate = framerate
        camera.shutter_speed = shutter_speed
        camera.exposure_mode = exposure_mode
        camera.iso = iso
      else:
        #print 'work in the dark'
        camera.framerate = Fraction(1,6)
        camera.shutter_speed = 6000000
        camera.exposure_mode = 'off'
        camera.iso = 800
    
      #show preview with some transparency
      #camera.start_preview(alpha=200)
    
      #wait two seconds for camera lighting
      #sleep(2)
    
      filename = 'image'+str(i)+'.jpg'
    
      #save image locally
      camera.capture(filename)
    
      #invoke the script to upload the image
      subprocess.call('python save_image.py '+filename, shell=True)
    
      #stop the preview
      #camera.stop_preview()
    
      sleep(3)
    
      i = (i + 1) % 12
    

    Create the save_image.py script. Alter the domain and path to ~/Documents/PythonScripts/Camera/save_image.py.

    #!/usr/bin/env python
    
    import urllib, urllib2, os, os.path, sys
    from poster.encode import multipart_encode
    from poster.streaminghttp import register_openers
    
    command = os.popen('ifconfig | grep -A1 wlan0')
    client_ip = command.read()
    #print client_ip
    
    register_openers()
    
    if (len(sys.argv) > 1):
      filename = sys.argv[1];
    else:
      filename = 'image.jpg';
    
    query = { 'filename' : filename, 'client_ip' : client_ip }
    url = "http://domain/path/save_image.php?"+urllib.urlencode(query)
    
    print 'Saved: '+filename;
    if (os.path.isfile(filename)) :
      values = {'image':open(filename)}
      data, headers = multipart_encode(values)
      headers['User-Agent'] = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
      headers['filename'] = filename
      req = urllib2.Request(url, data, headers)
      req.unverifiable = True
      content = urllib2.urlopen(req).read()
      #print (content)
    else:
      print 'No image file found to upload\r\n';
    

    On your PHP server somewhere, make a new folder and create the page save_image.php.

    <?php
    
    $filename = "image.jpg";
    if ($_GET['filename'] != null) {
       $filename = $_GET['filename'];
    }
    
    if ($_GET['client_ip'] != null) {
       $clientip = $_GET['client_ip'];
       file_put_contents('client_ip.txt', $clientip);
    }
    
    $image = $_FILES["image"];
    
    if ($image == null) {
       echo "Missing image to save as: ";
       echo $filename;
    } else {
       echo "Saved image!";
       $tmp_name = $_FILES["image"]["tmp_name"];
       move_uploaded_file($tmp_name, $filename);
    }
    
    ?>
    

    On your PHP server somewhere, save the page images.php near your server image paths.

    <html>
    <?php
    function displayFirstImage($path) {
       $files = glob($path);
       usort($files, function($a, $b) {
          return filemtime($a) < filemtime($b);
       });
       foreach($files as $file){
          printf('<img src="%1$s"/>', $file);
          break;
       }
    }
    displayFirstImage('path/to/camera1/*.jpg');
    displayFirstImage('path/to/camera2/*.jpg');
    displayFirstImage('path/to/camera3/*.jpg');
    displayFirstImage('path/to/camera4/*.jpg');
    ?>
    <script>
    setTimeout("window.location.reload()", 30000);
    </script>
    </html>
    
    

    Edit ~/.bashrc and add the following to the end of the file.

    ~/superscript &
    

    Reboot the Raspberry PI and the security camera is ready to go!

    Lightweight python motion detection

    Brian Flakes posted an example script in the RPI forums for how to do [lightweight python motion detection] which is useful when taking pictures with the RPI camera module.

    Converted the example to work with PiCamera.

    #get access to the camera
    from picamera import PiCamera
    
    import picamera.array #want to do it in memory
    
    from PIL import Image
    import requests
    
    #import so we can invoke another script
    import subprocess
    
    #sleep so we can wait on the camera
    from time import sleep
    
    token = 'YOUR_SECURITY_TOKEN'
    selector = 'group_id:SEE_THE_SELECTOR_DOCS'
    threshold = 40
    sensitivity = 20
    firstRun = True
    lastPixels = []
    detected = False
    showPreview = False
    hidePreview = False
    waitRedlight = 600
    i = 0
    headers = {
        "Authorization": "Bearer %s" % token,
    }
    
    # create a camera object
    camera = PiCamera()
    
    #set the image resolution
    camera.resolution = (640, 480)
    
    #rotate the camera if upside-down
    #camera.rotation = 180
    
    def Greenlight():
      payload = {
        "power": "off",
        "color": "green saturation:0.9",
        "brightness": 1.0,
        "duration": 1
      }
      try:
        response = requests.put('https://api.lifx.com/v1/lights/'+selector+'/state', data=payload, headers=headers)
      except:
        pass
    
    def Redlight():
      payload = {
        "power": "on",
        "color": "white",
        "brightness": 1.0,
        "duration": 1
      }
      try:
        response = requests.put('https://api.lifx.com/v1/lights/'+selector+'/state', data=payload, headers=headers)
      except:
        pass
    
    Greenlight();
    
    while True:
    
      if (showPreview):
        showPreview = False
        #show preview with some transparency
        camera.start_preview(alpha=200)
        hidePreview = True
    
      filename = 'image'+str(i)+'.jpg'
    
      #save image locally
      camera.capture(filename)
    
      image = Image.open(filename)
      pixels = image.load()
    
      width, height = image.size
      #print ('width='+str(width)+' height='+str(height)+"\n");
    
      if (firstRun):
        firstRun = False
        lastPixels = pixels
    
      # Count changed pixels
      changedPixels = 0
      for x in range(width):
        for y in range(height):
          # Just check green channel as it's the highest quality channel
          pixdiff = abs(pixels[x,y][1] - lastPixels[x,y][1])
          if pixdiff > threshold:
            changedPixels += 1
    
      print ('changedPixels='+str(changedPixels))
      lastPixels = pixels
    
      if (changedPixels < 100):
        if (detected):
          detected = False
          Greenlight();
          firstRun = True
      else:
        if (detected == False):
          detected = True
          Redlight();
          sleep(waitRedlight)
          firstRun = True
          showPreview = True
    
      if (hidePreview):
        hidePreview = False
        #stop the preview
        camera.stop_preview()
        firstRun = True
    
      sleep(1)
    
      i = (i + 1) % 12
      
    

    RASPBIAN

    The Raspberry PI organization hosts [RASPBIAN] which comes pre-installed with plenty of software for education, programming and general use. It has Python, Scratch, Sonic Pi, Java, Mathematica and more. [Noobs] is a tool that can flash an SD card to install Raspbian. The SD card organization has a [SD Card Formatter] tool to format the SD card.

    Reminder: On RPI2 with Raspbian, the default user is pi with the password raspberry.