icon

UNIHIKER: iOS Media Controller

0 1434 Medium

This tutorial will guide you through building a project that connects a UNIHIKER board to an iPhone via Bluetooth, allowing you to control the volume of a USB speaker connected to UNIHIKER. The control is facilitated by the buttons on the UNIHIKER, which adjust the volume and control media playback on the iPhone.

HARDWARE LIST
1 UNIHIKER
1 Silikon Case for UNIHIKER
1 USB Speaker
1 iPhone

You'll need the following components:

Ā 

UNIHIKER: A single board platform with onboard Bluetooth connectivity.

Ā 

USB Speaker: Any speaker with a USB connection.

Ā 

iPhone: The mobile device that will be connected to the UNIHIKER via Bluetooth.

Ā 

Ensure your USB speaker is connected to the UNIHIKER via the USB port.

STEP 1
Project Preparation and Requirements

Before starting, you'll need to ensure your UNIHIKER environment is set up with all the necessary libraries.

Ā 

python3-dbus: To communicate with the system's D-Bus interface, which is used to manage Bluetooth devices.

Ā 

You can install the python3-dbus library on your UNIHIKER by running:

CODE
```bash
# update package repositories
$ apt update

# install required packages
$ apt install -y python3-dbus
```

Project structure

Ā 

The root folder is named iOS_Media_Controller. Inside it, there's a subfolder named lib containing the gui.py file. The main.py file is directly under the root folder.

Ā 

STEP 2
Python Code

Below is the complete Python code for the project. You will have two Python scripts: main.py and gui.py (in the lib folder).

Ā 

main.py

CODE
from subprocess import run, PIPE, CalledProcessError
from sys import exit
from pinpong.board import Board, Pin
from pinpong.extension.unihiker import button_a, button_b
from lib.gui import GUI


IOS_MAC_ADDRESS: str = ''
DEFAULT_VOLUME: int = 50
SCREEN_WIDTH: int = 240
SCREEN_HEIGHT: int = 320


def get_connected_devices() -> list:
    """
    Retrieves a list of connected Bluetooth devices.

    :return: list of MAC addresses of the connected Bluetooth devices.
    """
    output = None
    devices = []

    try:
        result = run(['bluetoothctl', 'info'], check=True, stdout=PIPE, stderr=PIPE)
        output = result.stdout.decode()
    except CalledProcessError as err:
        print(f'[ERROR] {err}')
        return []

    if output is not None:
        for line in output.splitlines():
            if 'Device' in line:
                devices.append(line.split(' ')[1])

    return devices


def is_device_connected(devices: list) -> bool:
    """
    Verify if the iOS device MAC address is currently connected.

    :param devices: List of device MAC addresses currently connected.
    :type devices: list
    :return: True if the iOS device MAC address is found in the provided list of devices, False otherwise.
    :rtype: bool
    """
    return IOS_MAC_ADDRESS in devices


def set_volume(volume: int) -> None:
    """
    Set the volume for connected speakers.

    :param volume: The desired volume level as an integer percentage (e.g., 50 for 50%).
    :type volume: int
    :return: None
    """
    try:
        run(['amixer', 'set', 'Master', f'{volume}%'], check=True, stdout=PIPE, stderr=PIPE)
    except CalledProcessError as err:
        print(f'[ERROR] {err}')


def increase_volume(pin: Pin=None) -> None:
    """
    Increase the volume by 5%

    :param pin: Optional parameter, not used in the function
    :type pin: Pin
    :return: None
    """
    global current_volume
    global window

    _ = pin

    if current_volume < 100:
        current_volume += 5
        set_volume(current_volume)
        window.update_information(volume=current_volume)


def decrease_volume(pin: Pin=None) -> None:
    """
    Decrease the volume by 5%

    :param pin: Optional parameter, not used in the function
    :type pin: Pin
    :return: None
    """
    global current_volume
    global window

    _ = pin

    if current_volume > 0:
        current_volume -= 5
        set_volume(current_volume)
        window.update_information(volume=current_volume)


if __name__ == '__main__':
    connected_devices = get_connected_devices()

    if not connected_devices:
        exit(1)
    elif not is_device_connected(devices=connected_devices):
        print(f'[ERROR] {IOS_MAC_ADDRESS} not connected')
        exit(1)
    else:
        print(f'[INFO] {IOS_MAC_ADDRESS} connected')

    Board("UNIHIKER").begin()

    current_volume = DEFAULT_VOLUME
    set_volume(current_volume)

    window = GUI(width=SCREEN_WIDTH, height=SCREEN_HEIGHT, mac_address=IOS_MAC_ADDRESS)
    window.update_information(volume=current_volume)

    button_a.irq(trigger=Pin.IRQ_RISING, handler=increase_volume)
    button_b.irq(trigger=Pin.IRQ_RISING, handler=decrease_volume)

    window.mainloop()

Important

Ā 

You need to set the value for constant IOS_MAC_ADDRESS before running the application. The value is the MAC address of your iPhone. On your iPhone Bluetooth must be enabled. You can execute on UNIHIKER following Linux commands:

CODE
```bash
# scan with bluetoothctl
$ bluetoothctl scan on

# scan with hcitool
$ hcitool scan
```

lib/gui.py

CODE
from tkinter import Tk, Label, Frame, Button, LEFT
from dbus import SystemBus, Interface


class GUI(Tk):
    def __init__(self, width: int, height: int, mac_address: str):
        """
        Class constructor

        :param width: The width of the window.
        :type width: int
        :param height: The height of the window.
        :type height: int
        :param mac_address: The MAC address of the Bluetooth device.
        :type mac_address: str
        """
        super().__init__()
        mac = mac_address.replace(':', '_')

        self.geometry(f'{width}x{height}+0+0')
        self.resizable(width=False, height=False)

        self._label = Label(self, pady=5, font=('Arial', 25))
        self._label.pack()

        self._btn_frame = Frame(self)
        self._btn_frame.pack(anchor='center')

        self.btn_previous = Button(self._btn_frame, text='\u25C0\u25C0', command=lambda: self._btn_action('previous'))
        self.btn_previous.pack(side=LEFT)

        self.btn_play = Button(self._btn_frame, text='\u25B6', command=lambda: self._btn_action('play'))
        self.btn_play.pack(side=LEFT)

        self.btn_pause = Button(self._btn_frame, text='\u2759\u2759', command=lambda: self._btn_action('pause'))
        self.btn_pause.pack(side=LEFT)

        self.btn_next = Button(self._btn_frame, text='\u25B6\u25B6', command=lambda: self._btn_action('next'))
        self.btn_next.pack(side=LEFT)

        bus = SystemBus()
        media_player = bus.get_object('org.bluez', f'/org/bluez/hci0/dev_{mac}/player0')
        self._player = Interface(media_player, dbus_interface='org.bluez.MediaPlayer1')

    def update_information(self, volume: int) -> None:
        """
        Update label text with new volume level

        :param volume: An integer representing the new volume level to display.
        :type volume: int
        :return: None
        """
        self._label.config(text=f'Volume: {volume}')

    def _btn_action(self, action: str) -> None:
        """
        Button action handler

        :param action: The action to be performed by the player. Values are 'play', 'pause', 'next', and 'previous'.
        :type action: str
        :return: None
        """
        if action == 'play':
            self._player.Play()
        elif action == 'pause':
            self._player.Pause()
        elif action == 'next':
            self._player.Next()
        elif action == 'previous':
            self._player.Previous()
STEP 3
Upload Project to UNIHIKER

You can upload the project files to the UNIHIKER board using either SCP (secure copy) or SMB (shared folder).

Ā 

SCP Method: Use the following SCP command from your local machine to copy the project files:

Ā 

$ scp -r iOS_Media_Controller [email protected]:/root/

STEP 4
Bluetooth Connection UNIHIKER and iPhone

You'll need to use bluetoothctl to establish a connection between your iPhone and the UNIHIKER board.

CODE
```bash
# scan for devices
$ bluetoothctl scan on

# pair device
$ bluetoothctl pair <iPhone_MAC_Address>

# connect device
$ bluetoothctl connect <iPhone_MAC_Address>
```

Replace with the actual MAC address you obtained during the scan.

Ā 

Note: The Python script checks on start only if device is connected. Before you run the application, you need to connect manually.

STEP 5
Run Application

Once your iPhone is connected to the UNIHIKER and the project files are uploaded, you can run the application:

Ā 

$ python3 main.py

Ā 

It is also possible to start the application directly over the touch screen. Whatever you like more…

Ā 

You should now be able to control the volume of the USB speaker and manage iPhone media playback on the UNIHIKER.

Ā 

STEP 6
Annotations

Here are a few ways to improve and expand this project:

Ā 

UI: Add more graphical elements to the GUI to enhance user interaction.

Ā 

Playback Controls: Expand playback controls to include more functionalities like shuffle or repeat.

Ā 

Speaker: Instead of USB use Bluetooth speaker.

Ā 

This project can serve as a starting point for creating a Bluetooth-enabled media controller or smart speaker.

License
All Rights
Reserved
licensBg
0