Cave Surveying Bluetooth Protocol
Project description
Introduction
Cave Surveying Bluetooth Protocol - a protocol for communicating with paperless cave surveying tools e.g. TopoDroid and SexyTopo. This protocol was developed for use with the Shetland Attack Pony 6, but is presented free to use for anyone who wishes to use it for their cave surveying device.
Protocol
In the discussion below, ‘instrument’ means the device measuring the cave, ‘surveyor’ means the device (likely a phone or tablet) receiving the data from the instrument. Description of “read” or “write” is from the perspective of the surveyor.
This protocol uses a SurveyProtocolService on UUID 137c4435-8a64-4bcb-93f1-3792c6bdc965. It has three characteristics:
Name characteristis UUID: 137c4435-8a64-4bcb-93f1-3792c6bdc966. This characteristic is read only and is a simple string indicating which protocol is being used. Currently this is hardcoded to “SAP6”
Leg characteristic - UUID: 137c4435-8a64-4bcb-93f1-3792c6bdc968. This characteristic is read only and will notify each time a shot is taken. It is a sequence of 17 bytes and is little-endian:
Byte 0: Sequence bit. This will alternate between 0 and 1 for successive legs. The surveyor must respond with an appropriate ACK (see later), otherwise the instrument will resend after 5 seconds.
Bytes 1-4: Azimuth in degrees as a float.
Bytes 5-8: Inclination in degrees as a float.
Bytes 9-12: Roll in degrees as a float.
Bytes 13-16: Distance in metres as a float.
Command characteristic - UUID: 137c4435-8a64-4bcb-93f1-3792c6bdc967. Write only, surveyor can send a single byte to the instrument. Currently defined bytes:
0x55 (ACK0): Signals that the surveyor has received a leg with sequence byte 0
0x56 (ACK1): Signals that the surveyor has received a leg with sequence byte 1
0x31 (START_CAL): Instrument should enter calibration mode
0x30 (STOP_CAL): Instrument should leave calibration mode
0x36 (LASER_ON): Instrument should turn the laser on
0x37 (LASER_OFF): Instrument should turn the laser off
0x34 (DEVICE_OFF): Instrument should turn off
0x38 (TAKE_SHOT): Instrument should take a reading
All the above constants are defined in both CircuitPython and Kotlin code
Usage
The software in this repository simplifies use of the above protocol.
CircuitPython
The CircuitPython code manages the whole notification and responds to ACKs as per the protocol above. It keeps a queue of legs that have not yet been sent if communication with the surveyor is interrupted and sends these once communication has been re-established.
Initialising
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
import caveble
ble = BLERadio()
ble.name = "SAP6_AB"
survey_protocol = caveble.SurveyProtocolService()
advertisement = ProvideServicesAdvertisement(survey_protocol)
ble.start_advertising(advertisement)
You will need to either call SurveyProtocolService.poll repeatedly, or create SurveyProtocolService.background_task as an asyncio task.
Sending data using poll
while True:
survey_protocol.poll()
# if leg available to send..
survey_protocol.send_data(azimuth, inclination, distance, roll)
time.sleep(0.5)
Sending data asynchronously
asyncio.create_task(survey_protocol.background_task())
while True:
await leg_ready_event
survey_protocol.send_data(azimuth, inclination, distance, roll)
Receiving commands using poll
while True:
message = survey_protocol.poll()
if message is not None:
# do something with message
# will not receive ACK0 or ACK1 - these are dealt with by `SurveyProtocolService`
time.sleep(0.5)
Receiving commands asynchronously
async def callback(message: int):
#process the message
asyncio.create_task(survey_protocol.background_task(callback))
while True:
await leg_ready_event
survey_protocol.send_data(azimuth, inclination, distance, roll)
Kotlin/Java (Android)
You can use CaveBLE.kt in your code - simply change the package to something appropriate on line one. Note Kotlin is fully compatible with Java and AndroidStudio comfortably uses these files interchangeably in the same project.
To use the device, you must create a CaveBLE object. you will need to pass in a bluetooth device object, a context, a leg callback and an optional status callback. The leg callback will be called whenever a new leg is received The status callback will be called whenever the device connects or disconnects
Sample Java code
package xxx.xxx.xxx.xxx;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import xxx.xxx.xxx.CaveBLE;
import kotlin.Unit;
public class SAP6Communicator extends Communicator {
private final CaveBLE caveBLE;
public SAP6Communicator(Context context, BluetoothDevice bluetoothDevice) {
this.caveBLE = new CaveBLE(bluetoothDevice, context, this::legCallback, this::statusCallback);
}
public boolean isConnected() {
return caveBLE.isConnected();
}
public void requestConnect() {
caveBLE.connect();
}
public void requestDisconnect() {
caveBLE.disconnect();
}
public void laserOn() {
caveBLE.laserOn()
}
// other commands have similar functions
public Unit legCallback(float azimuth, float inclination, float roll, float distance) {
// code to respond to a leg being received here.
return Unit.INSTANCE; // you must return Unit.INSTANCE for callbacks to Kotlin code
}
public Unit statusCallback(int status, String msg) {
switch (status) {
case CaveBLE.CONNECTED:
// code to run when device connects here
break;
case CaveBLE.DISCONNECTED:
Log.device("Disconnected");
// code to run when device disconnects here
break;
case CaveBLE.CONNECTION_FAILED:
Log.device("Communication error: "+msg);
}
return Unit.INSTANCE;
}
}
Dependencies
This driver depends on:
Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading the Adafruit library and driver bundle or individual libraries can be installed using circup.
Installing from PyPI
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally from PyPI. To install for current user:
pip3 install circuitpython-caveble
To install system-wide (this may be required in some cases):
sudo pip3 install circuitpython-caveble
To install in a virtual environment in your current project:
mkdir project-name && cd project-name
python3 -m venv .venv
source .env/bin/activate
pip3 install circuitpython-caveble
Installing to a Connected CircuitPython Device with Circup
Make sure that you have circup installed in your Python environment. Install it with the following command if necessary:
pip3 install circup
With circup installed and your CircuitPython device connected use the following command to install:
circup install caveble
Or the following command to update an existing version:
circup update
Full Usage Example
import time
import board
import keypad
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
import caveble
ble = BLERadio()
ble.name = "SAP6_AB"
print(ble.name)
survey_protocol = caveble.SurveyProtocolService()
advertisement = ProvideServicesAdvertisement(survey_protocol)
ble.start_advertising(advertisement)
KEY_PINS = (board.D5, board.D9)
keys = keypad.Keys(KEY_PINS, value_when_pressed=False, pull=True)
compass = 0
clino = 0
distance = 5
while True:
event = keys.events.get()
if event:
key_number = event.key_number
if event.pressed:
if key_number == 0:
# change the values to send
compass = (compass + 10.5) % 360
clino += 5.5
if clino > 90:
clino -= 180
distance = (distance + 3.4) % 10000
print(compass, clino, distance)
if key_number == 1:
survey_protocol.send_data(compass, clino, distance)
print("Data sent")
message = survey_protocol.poll()
if message:
print(f"Message received: {message}")
time.sleep(0.03)
Documentation
API documentation for this library can be found on Read the Docs.
For information on building library documentation, please check out this guide.
Contributing
Contributions are welcome! Please read our Code of Conduct before contributing to help this project stay welcoming.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for circuitpython-caveble-1.1.2.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5a20f244f8455195a3c2c995b10af815895e5406f73c19661c1a4f95e642bef2 |
|
MD5 | f797df126bcfdfadc55c051061cfb8f6 |
|
BLAKE2b-256 | e0968d9187fc176d924d558257608da226b4b1ae9a7544b304594461569091f2 |
Hashes for circuitpython_caveble-1.1.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 22691d3deb2b8be271f2cbd36ab2fcbb6b65a427c6a951a8d443789434b2514b |
|
MD5 | 74d1fa7ee7dc2f8ed19ca986f61968b9 |
|
BLAKE2b-256 | 9962e53cd1ecf1bf116bea3610283f20cb9ab0b1b0d2f8f4eacbe61f7246f2a5 |