Setting Up I2C Serial Communication on Raspberry Pi

This post shows steps to setup I2C serial communication [1] on Raspberry Pi with an I2C peripheral device. I’ll use a temperature and humidity sensor as an example of I2C peripheral. The goal is to read those sensors value on Raspberry Pi.

 

Assumptions

I assume that you already installed Raspbian OS on your Pi, if not, please setup Raspbian OS first.

 

Example Setup

 

Steps
1. Enabling I2C
1-1. I2C is disabled by default on Raspbian. To enable I2C, run raspi-config.

sudo raspi-config

1-2. Select “Interfacing Options”, then “I2C”.

1-3. Select “Yes” when it asks you to enable I2C.

1-5. Select “OK” and exit raspi-config.

 

2.  Installing i2c-tools
2-1. Install i2c-tools if it’s not already.

sudo apt-get install i2c-tools -y

2-2. Let’s check the I2C bus before connecting the sensor.

i2cdetect -y 1

The result should be like this. If any I2C device is connected, it’s going to be showing up with its I2C slave address. Since nothing is connected yet, nothing is coming up this time.

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

3. Wiring

3-1. Power off the Raspberry Pi.

3-2. Connect Raspberry Pi and the sensor with jumper wires (for Power, GND, Data, and Clock lines). You can find pins for I2C from here.

3-3. Boot up the Raspberry Pi.

3-4. Run the command again and check if the sensor is detected as an I2C peripheral.

i2cdetect -y 1

The result should be like this.

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --               

 

4. Testing
4-1. Install a library for HTU21D sensor. [3]

pip install htu21df

4-2. Launch python interactive shell.

python

4-3. Import the library and create HTU21 object.

from htu21 import HTU21 
htu = HTU21()

4-4. Read the temperature value.

htu.read_temperature()

4-5. Then, read the humidity value.

htu.read_humidity()

The result should be like this:

>>> htu.read_temperature()
26.670544433593754
>>> htu.read_humidity()
44.258636474609375

 

*If you want to connect other device than HTU21D sensor, use smbus/smbus2 [5] or raw device [6] instead.

 

References
[1] I2C – Wikipedia
[2] HTU21D(F) Sensor Datasheet
[3] MSeal/htu21df_sensor – GitHub
[4] Raspberry Pi I2C Pinout
[5] smbus2 – PyPI
[6] Need help with HTU21D Humidity Sensor (for RPi outreach)

 

 

Showing Album Cover Art Images for Bluetooth Audio

In the previous post, I was able to show song title, artist name, and album name of Bluetooth audio. Then, I thought it would be nice if the album cover art images could be displayed too.

Specification wise, transferring cover art over Bluetooth is supported since AVRCP 1.6 [1], but unfortunately it’s not widely implemented yet. So, my idea is to get cover art from Google Images [2] by web scraping the search results.

 

Assumptions
Bluetooth audio streaming (A2DP) and song info display (AVRCP) should be enabled by following the previous posts:

 

Example Setup

 

Steps
1. Installation of Required Software
1-1. Install “feh”, which will be used to display images.

sudo apt-get install feh -y

1-2. Install “beautifulsoup4” python package, which will be used for web scraping.

pip install beautifulsoup4

 

2. Updating Code
2-1. Open “media_control.py” from the previous post.

2-2. Add the following code which implements the cover art feature. [3] [4] [5]

import argparse
import requests
import bs4
import json
import subprocess

URL = 'https://www.google.com/search?tbm=isch&q='
HEADER = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36"}
viewer = None

def show_album_art(artist, album):
    if artist == '' or album == '':
        return

    global viewer
    if viewer:
        viewer.terminate()
        viewer.kill()

    artist = '"' + '+'.join(artist.split()) + '"'
    album = '"' + '+'.join(album.split()) + '"'
    res = requests.get(URL+'+'.join((artist, album)), headers=HEADER)
    soup = bs4.BeautifulSoup(res.text, 'html.parser')
    meta_elements = soup.find_all("div", {"class": "rg_meta"})
    meta_dicts = (json.loads(e.text) for e in meta_elements)

    for d in meta_dicts:
        if d['oh'] == d['ow']:
            image_url = d['ou']
            break

    res = requests.get(image_url)
    if res.status_code != 200:
        return

    with open('album_art', 'wb') as f:
        f.write(res.content)

    viewer = subprocess.Popen(['feh', '-g', '220x220', 'album_art'])

 2-3. Add the line below at the end of elif block in “on_property_changed()”.

show_album_art(value.get('Artist', ''), value.get('Album', ''))

After the modification, “on_property_changed()” should look like this.

def on_property_changed(interface, changed, invalidated):
    if interface != 'org.bluez.MediaPlayer1':
        return
    for prop, value in changed.iteritems():
        if prop == 'Status':
            print('Playback Status: {}'.format(value))
        elif prop == 'Track':
            print('Music Info:')
            for key in ('Title', 'Artist', 'Album'):
                print('   {}: {}'.format(key, value.get(key, '')))
            show_album_art(value.get('Artist', ''), value.get('Album', ''))

 

3. Test
3-1. Make sure Bluetooth audio connection is already established and bluealsa audio forwarding is enabled.

3-2. Run the updated program. Following images are examples of the results. The right side is the smartphone screen running Pandora streaming music. The left side (background) is the Raspberry Pi’s desktop showing the cover art image from Google Images.

When song changes, it closes feh window for the previous song, then re-open it with new cover art image for the current song.

 

Summary
There is still room for improvement (e.g. logic for image selection, error handling, etc), but so far I’m satisfied with the result as a prototype.

 

References
[1] Traditional Profile Specifications – Bluetooth SIG
[2] Google Images
[3] Python – Download Images from google Image search? – Stack Overflow
[4] scraping full size images from Google Images – GitHub Gist
[5] How can I close an image shown to the user with the Python Imaging Library? – Stack Overflow

 

 

Controlling Bluetooth Audio on Raspberry Pi


Update (April 21, 2020):
Volume control support added.


This post shows steps to control playback status of Bluetooth audio which is streamed from a connected phone (or any Bluetooth capable media player). The goal is to enable below.

  • Playback control (play, pause, next/previous song, and volume control)
  • Music information display in real time (song title, artist name, and album name)

 

Prerequisites

 

Steps
1. Enabling Bluetooth Audio (A2DP Sink)
1-1. If it’s not already done, please follow this post and enable Bluetooth Audio and connect a phone.

 

2. Implement Playback Control and Music Info Display [1]
2-1. Create a python script. This script prints playback status and music info when it’s updated. It also accept command input (i.e. “play”, “pause”, “next”, “prev”).

import dbus, dbus.mainloop.glib, sys
from gi.repository import GLib

def on_property_changed(interface, changed, invalidated):
    if interface != 'org.bluez.MediaPlayer1':
        return
    for prop, value in changed.items():
        if prop == 'Status':
            print('Playback Status: {}'.format(value))
        elif prop == 'Track':
            print('Music Info:')
            for key in ('Title', 'Artist', 'Album'):
                print('   {}: {}'.format(key, value.get(key, '')))

def on_playback_control(fd, condition):
    str = fd.readline()
    if str.startswith('play'):
        player_iface.Play()
    elif str.startswith('pause'):
        player_iface.Pause()
    elif str.startswith('next'):
        player_iface.Next()
    elif str.startswith('prev'):
        player_iface.Previous()
    elif str.startswith('vol'):
        vol = int(str.split()[1])
        if vol not in range(0, 128):
            print('Possible Values: 0-127')
            return True
        transport_prop_iface.Set(
                'org.bluez.MediaTransport1',
                'Volume',
                dbus.UInt16(vol))
    return True

if __name__ == '__main__':
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    obj = bus.get_object('org.bluez', "/")
    mgr = dbus.Interface(obj, 'org.freedesktop.DBus.ObjectManager')
    player_iface = None
    transport_prop_iface = None
    for path, ifaces in mgr.GetManagedObjects().items():
        if 'org.bluez.MediaPlayer1' in ifaces:
            player_iface = dbus.Interface(
                    bus.get_object('org.bluez', path),
                    'org.bluez.MediaPlayer1')
        elif 'org.bluez.MediaTransport1' in ifaces:
            transport_prop_iface = dbus.Interface(
                    bus.get_object('org.bluez', path),
                    'org.freedesktop.DBus.Properties')
    if not player_iface:
        sys.exit('Error: Media Player not found.')
    if not transport_prop_iface:
        sys.exit('Error: DBus.Properties iface not found.')

    bus.add_signal_receiver(
            on_property_changed,
            bus_name='org.bluez',
            signal_name='PropertiesChanged',
            dbus_interface='org.freedesktop.DBus.Properties')
    GLib.io_add_watch(sys.stdin, GLib.IO_IN, on_playback_control)
    GLib.MainLoop().run()

2-2. Run the script.

python media_control.py

 

3. Test: Playback Status and Music Info
3-1. Start music by operating a media player on the phone. The script should print the info like below:

$ python media_control.py 
Playback Status: playing
Music Info:
   Title: Basket Case
   Artist: Green Day
   Album: Dookie (Explicit)

 

4. Test: Playback/Volume Control
4-1. Enter commands from keyboard. You should be able to control the music. In the example below, line 1, 3, 5 and 10 are commands entered.

pause
Playback Status: paused
play
Playback Status: playing
next
Music Info:
   Title: Sloppy Seconds
   Artist: Watsky
   Album: Cardboard Castles
vol 100

 

What’s Next?
If you are interested in showing album cover art images, please check this out.

 

References
[1] bluez/doc/media-api.txt

 

 

Enabling Python Autocomplete in VIM on Raspberry Pi


This post shows steps to enable Python autocomplete in vim editor [1] on Raspberry Pi.

In this post, jedi-vim [2] and Vundle.vim [3] will be used for autocomplete plugin and plugin manager respectively. Also, even Raspbian Stretch has vim preinstalled, it’s a minimal version which is called “vim.tiny” and to use the plugin, we need to install another version of vim.

 

Prerequisites (parentheses indicate tested setup)

 

Steps
1. Install VIM
As mentioned above, another version of vim than the preinstalled one needs to be installed.

1-1. Update the package list.

sudo apt-get update

1-2. Install vim-nox.

sudo apt-get install vim-nox -y

 

2. Setup VIM
2-1. Clone VundleVim plugin.

git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

2-2. Create a vim configuration file with vim.

vim ~/.vimrc

2-3. Activate the paste mode by the command below.

:set paste

2-4. Type “i” to go insert mode. Make sure that the paste mode is on as below.

-- INSERT (paste) --

2-5. Copy and paste the lines from line 1 to line 10 below.
Note: The lines after line 13 are my preferred settings and just for my reference. 

set nocompatible                  " be iMproved, required
filetype off                      " required
set rtp+=~/.vim/bundle/Vundle.vim " set the runtime path to include Vundle and initialize 
call vundle#begin()
Plugin 'VundleVim/Vundle.vim'     " let Vundle manage Vundle, required
Plugin 'davidhalter/jedi-vim'     " jedi-vim

" All of your Plugins must be added before the following line
call vundle#end()                 " required
filetype plugin indent on         " required

" Put your non-Plugin stuff after this line
colo murphy      " set colorscheme 
syntax enable    " enable syntax highlighting 
set number       " show line numbers 
set mouse=a      " turn on xterm style mousing which allows to select text w/o line numbers 
set clipboard=unnamedplus " copy yanked text to system clipboard 
set ts=4         " set tabs to have 4 spaces
set autoindent   " indent when moving to the next line while writing code 
set expandtab    " expand tabs into spaces 
set shiftwidth=4 " when using the >> or << commands, shift lines by 4 spaces 
set cursorline   " show a visual line under the cursor's current line 
set showmatch    " show the matching part of the pair for [] {} and () 
set laststatus=2 " display file name on the bottom bar
set pastetoggle=<F3> "paste mode toggling
let python_highlight_all = 1 " enable all Python syntax highlighting features
highlight OverLength ctermbg=red ctermfg=white guibg=#592929
match OverLength /\%81v.\+/

2-6. Exit the insert mode by pressing “Ctrl” + “[“.

2-7. Save the file by typing “:w” and enter.

:w

2-8. Execute the file by the command below.

:source %

2-9. Install the plugins by entering the command below.

:PluginInstall

It will show “Processing” in the status bar. It takes a while.

2-10. When the status bar shows “Done!”, close the file by entering “:q”.

 

3. Verify Autocomplete

 

References
[1] Vim – the ubiquitous text editor
[2] jedi-vim – awesome Python autocompletion with VIM – GitHub
[3] Vundle.vim – GitHub
[4] jedi-vim – Vim Awesome
[5] VIM and Python – A Match Made in Heaven

 

 

Setting Up Bluetooth Serial Port Profile on Raspberry Pi using D-Bus API

This post shows steps to set up Bluetooth Serial Port Profile[1] (or SPP) on Raspberry Pi.

The goal is to establish an SPP connection between Raspberry Pi 3 and Android phone, then send and receive texts using serial terminal applications on both ends.

SPP is not available by default. There are two options to enable it in BlueZ (which is the default Bluetooth stack in Linux). This post shows steps for the 2nd option (D-Bus API).

  1. sdptool (please see this post for this option)
  2. D-bus API

 

Prerequisites (parentheses indicate my environment)

 

Steps
1. Installation
1-1. Install a serial terminal application on Raspberry Pi. In this post, I’ll use minicom[2].

sudo apt-get install minicom -y

 

2. Enable SPP on Raspberry Pi
2-1. Create a python script (e.g. “spp.py”)[3][4].

import dbus, time

service_record = """
<?xml version="1.0" encoding="UTF-8" ?>
<record>
  <attribute id="0x0001">
    <sequence>
      <uuid value="0x1101"/>
    </sequence>
  </attribute>
  <attribute id="0x0004">
    <sequence>
      <sequence>
        <uuid value="0x0100"/>
      </sequence>
      <sequence>
        <uuid value="0x0003"/>
        <uint8 value="1" name="channel"/>
      </sequence>
    </sequence>
  </attribute>
  <attribute id="0x0100">
    <text value="Serial Port" name="name"/>
  </attribute>
</record>
"""

bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/org/bluez"),
                        "org.bluez.ProfileManager1")
manager.RegisterProfile("/bluez",
                        "00001101-0000-1000-8000-00805f9b34fb",
                        {"AutoConnect":True, "ServiceRecord":service_record})
while True:
    time.sleep(1)

2-2. Run the script.

python spp.py

2-3. Suspend the script by pressing Ctrl+Z.

 

3. Pairing
To establish a connection, Raspberry Pi and the phone need to be paired.

3-1. Launch bluetoothctl.

bluetoothctl

3-2. Enter below in order to be discovered from the phone.

discoverable on

3-3. On the phone, scan for Raspberry Pi and pair. You should be able to see something like below.

[CHG] Device XX:XX:XX:XX:XX:XX Paired: yes

3-4. Press Ctrl+D to quit.

 

4. Establishing Connection from Phone

4-1. Listen for incoming connection on Raspberry Pi.

sudo rfcomm watch hci0

4-2. Install and launch “Serial Bluetooth Terminal” app[5] on the phone.

4-3. In the app, go to “Device” menu and select Raspberry Pi. If everything goes well and the connection is established, you should be able to see like this:

$ sudo rfcomm watch hci0
Waiting for connection on channel 1
Connection from XX:XX:XX:XX:XX:XX to /dev/rfcomm0
Press CTRL-C for hangup

 

5. Connecting Serial Terminal on Raspberry Pi
5-1. Open another terminal and launch the serial terminal.

minicom -b 9600 -o -D /dev/rfcomm0

 

6. Test
6-1. Input some text on the phone.

You should be able to see the text on Raspberry Pi’s serial terminal.

6-2. Input some text back to the phone on Raspberry Pi.

You should be able to see the text on the phone.

 

References
[1] Serial_Port_Profile_(SPP) – Wikipedia
[2] minicom(1) – Linux man page
[3] Re: Bluetooth Python script. – Raspberry Pi Forum
[4] Tutorial: Creating a Bluetooth service
[5] Serial Bluetooth Terminal – Google Play