Wireless Remote Control for Zoom H2n (Part 3 - Software)

Introduction

This is the third part of a series of posts regarding the construction of a Wireless Remote Control for the Zoom H2n. The project was outlined in Part 1, the hardware described in Part 2 and the software is available from Github.

The software, for the Android platform, is written in C# using the Xamarin extensions for Microsoft Visual Studio, largely because of my familiarity with C#. I had originally intended to use Xamarin Forms to make the application more portable, but there were some bugs at the time that made it easier to use a stock Android UI.

The principle source files are:

FilenameDescription
Common.csShared constants
DeviceListActivity.csAllow user to choose which bonded device to control
MainActivity.csThe code for the main activity - the main screen (showing the control buttons for the device)
RemoteCommunication.csAn Android service to handle the bluetooth serial communication with the device
ZoomRemote2.csprojVisual Studio project file

The resources directory contains the usual layout, strings, preferences and drawable images (principally the control buttons).

Structure

In normal operation, there is a single foreground activity (the main activity that starts when the App is launched) and a background service that communicates with the device. The main activity sends an Intent to the service when there is data to send to the recorder (such as a button press) and has a broadcast receiver through which it is notified of events arising in the other direction.

The use of a service resolves some of the problems of control being potentially lost if the app is put into the background owing to inactivity, means there are no other threads required in the main activity and is a possible precursor to being able to schedule recording tasks.

A second activity can be launched to select the bluetooth device to which the Zoom H2n is attached: the name and MAC address of this device is stored in the app's preferences and used as a default when the app is next launched.

Main Activity

The main activity is always in one of four states: idle, connected, synced, error. In the idle state it is unaware of being connected to the H2n (though the background service may be maintaining a connection); in the connected state it is aware of being successfully connected but has not exchanged the necessary handshake to start the control protocol; in the synced state the handshake has been exchanged and control messages can be sent; in the error state the protocol or bluetooth link has faulted for some reason.

The principal purpose of the main activity is to display the button and indicator grid (in either a landscape or portrait format depending on the orientation of the screen - layout resources are provided for both). The button resources ids are stored in the ButtonResources array. The control protocol calls for a code to be sent when a button is pressed and a further code when it is released (this allows, for example, auto-repeat on the Volume controls). The codes corresponding to the "press" event for each button are stored in the keySequence dictionary (the release code is the same for all keys).

When the activity is created (OnCreate), the code checks for a bluetooth adapter, creates a broadcast receiver (to receive data from the H2n), retrieves any previously stored bluetooth device name and address and sets all the buttons and indicators to an inactive state (reducing their alpha value to indicate this visually) and adds a touch event handler. The "scan" button to choose a different bluetooth device is enabled and a click-handler is provided.

When the activity is started (OnStart), the code checks whether the bluetooth adapter is enabled and, if not, sends an intent requesting this to be done. If the bluetooth adapter is enabled and a bluetooth device has been saved from a previous connection, then an attempt is made to connect to it.

OnActivityResult will be called when either the request to enable the bluetooth adapter completes, or when a bluetooth device is chosen by the DeviceListActivity. In the former case, if successful, the DeviceListActivity will be started to choose the bluetooth device to be used. In the latter case, if successful, the chosen device details are stored in the preferences and a connect request is sent to the RemoteCommunicationService service.

The result of the connect request will be returned via the DataReceiver broadcast receiver. Two types of event can be passed via this receiver - data received from the H2n or a status notification from the RemoteCommunicationService service. Status notifications take the value of one of the four activity states described above. Idle indicates a connection has been shut down and all enabled buttons are disabled. Connected means that a bluetooth connection has been established but the protocol handshake has not been completed so all buttons remain disabled. Synced means that handshake is complete so all (relevant) buttons are enabled (some buttons are context dependent) and the error count is reset. Error means an error has occurred - the error count is increased and an attempt is made to reconnect, with a limit on the number of retries in case the bluetooth device is no longer available. 

Data received from the H2n consists of a singled byte which is encoded as follows (red and green refer to VU meter, both on together corresponds to yellow):

Bit 7 - 0
Bit 6 - Green status for recorder channel 3 (not used for H2n)
Bit 5 - Green status for recorder channel 1
Bit 4 - Green status for recorder channel 2
Bit 3 - Red status for recorder channel 3 (not used for H2n)
Bit 2 - Red status for recorder channel 1
Bit 1 - Red status for recorder channel 2
Bit 0 - 1 if recording in progress (alternates 0 and 1 when recording paused)

The service will pass data to the broadcast receiver as it arrives from the H2n. The main activity calls UpdateUI to update the VU meter and also to configure the available buttons according to whether recording is currently in progress. 

The OnButtonTouch handler will be called when a button is pressed or released. The code will alter the visual appearance of the button to reflect its state and send the corresponding code for the up or down event to the connected service.

Communication Service

The communication service handles requests to connect to the device, disconnect from the device and to send data to the device. When the OnCreate handler is called for the service, it sets up a background thread with a looper and message handler thereby allowing requests from the foreground activity to be queued and processed in sequence. StartCommandResult will be called when a request is sent from the foreground activity: it is simply added to the queue and subsequently dispatched by the looper.

When a connection request is received, the service creates read and write streams to the bluetooth device using Stream.Synchronized - this is important to prevent the app crashing mysteriously during lifecycle events. It sends a "connected" status report to the main activity and creates a read thread to await data from the device. It also sends a "prompt" (promptSequence) as the initial part of the protocol handshake. 

A disconnection request results in the bluetooth connection being closed and an "idle" status report to the foreground.

A request to send data results in the data being written to the bluetooth output stream.

Significant effort is paid to detecting and trapping errors to prevent unexpected exits from the background code. Significant errors result in the connection being closed and an "error" status being reported to the main activity.

Until the device handshake is complete, the H2n sends a constant series of two-byte sequences with the top bit of the first byte set. It's possible the second byte may identify the device type (as other models share a similar protocol). Once the handshake (initiated by sending the prompt described above) is complete, the data stream changes to a series of single bytes with the top bit clear which are sent less frequently (mostly when an indicator needs to be updated). The read thread waits for a single byte sequence with the top bit clear and then sends a status message to the foreground indicating that the device is "synchronised". 

Device List Activity

To permit the user to pick the correct bluetooth device, a ListActivity is used. An initial version of the code used bluetooth discovery and the code necessary to do this remains present but commented out: it turned out that it made more practical sense to pair the bluetooth device with the phone and simply scan the bonded devices (btAdapter.BondedDevices) to find the right one. The list of devices is restricted to those that report a UUID for the "modem communication" service (which the bluetooth serial adapter offers).

Comments