Introduction
============
.. image:: https://readthedocs.org/projects/circuitpython-caveble/badge/?version=latest
:target: https://circuitpython-caveble.readthedocs.io/
:alt: Documentation Status
.. image:: https://github.com/furbrain/CircuitPython_CaveBLE/workflows/Build%20CI/badge.svg
:target: https://github.com/furbrain/CircuitPython_CaveBLE/actions
:alt: Build Status
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: Code Style: Black
Cave Surveying Bluetooth Protocol - a protocol for communicating with
paperless cave surveying tools e.g. `TopoDroid <https://github.com/marcocorvi/topodroid>`_ and
`SexyTopo <https://github.com/richsmith/sexytopo>`_. This protocol was developed for use with
the `Shetland Attack Pony 6 <https://www.shetlandattackpony.co.uk/>`_, 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
............
.. code-block:: python
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
.......................
.. code-block:: python
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
...........................
.. code-block:: python
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
.............................
.. code-block:: python
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
.................................
.. code-block:: python
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
......................
.. code-block:: java
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:
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
* `Adafruit BLE <https://github.com/adafruit/Adafruit_CircuitPython_BLE>`_
Please ensure all dependencies are available on the CircuitPython filesystem.
This is easily achieved by downloading
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
or individual libraries can be installed using
`circup <https://github.com/adafruit/circup>`_.
Installing from PyPI
====================
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
PyPI <https://pypi.org/project/circuitpython-caveble/>`_.
To install for current user:
.. code-block:: shell
pip3 install circuitpython-caveble
To install system-wide (this may be required in some cases):
.. code-block:: shell
sudo pip3 install circuitpython-caveble
To install in a virtual environment in your current project:
.. code-block:: shell
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:
.. code-block:: shell
pip3 install circup
With ``circup`` installed and your CircuitPython device connected use the
following command to install:
.. code-block:: shell
circup install caveble
Or the following command to update an existing version:
.. code-block:: shell
circup update
Full Usage Example
==================
.. code-block:: python
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 <https://circuitpython-caveble.readthedocs.io/>`_.
For information on building library documentation, please check out
`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
Contributing
============
Contributions are welcome! Please read our `Code of Conduct
<https://github.com/furbrain/CircuitPython_CaveBLE/blob/HEAD/CODE_OF_CONDUCT.md>`_
before contributing to help this project stay welcoming.
Raw data
{
"_id": null,
"home_page": "",
"name": "circuitpython-caveble",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "adafruit,blinka,circuitpython,micropython,disto,distox,bluetooth,cave,survey,caveble",
"author": "",
"author_email": "Phil Underwood <beardydoc@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/e0/96/8d9187fc176d924d558257608da226b4b1ae9a7544b304594461569091f2/circuitpython-caveble-1.1.2.tar.gz",
"platform": null,
"description": "Introduction\n============\n\n.. image:: https://readthedocs.org/projects/circuitpython-caveble/badge/?version=latest\n :target: https://circuitpython-caveble.readthedocs.io/\n :alt: Documentation Status\n\n\n.. image:: https://github.com/furbrain/CircuitPython_CaveBLE/workflows/Build%20CI/badge.svg\n :target: https://github.com/furbrain/CircuitPython_CaveBLE/actions\n :alt: Build Status\n\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://github.com/psf/black\n :alt: Code Style: Black\n\nCave Surveying Bluetooth Protocol - a protocol for communicating with\npaperless cave surveying tools e.g. `TopoDroid <https://github.com/marcocorvi/topodroid>`_ and\n`SexyTopo <https://github.com/richsmith/sexytopo>`_. This protocol was developed for use with\nthe `Shetland Attack Pony 6 <https://www.shetlandattackpony.co.uk/>`_, but is presented free to use\nfor anyone who wishes to use it for their cave surveying device.\n\nProtocol\n========\n\nIn the discussion below, 'instrument' means the device measuring the cave, 'surveyor' means the device\n(likely a phone or tablet) receiving the data from the instrument. Description of \"read\" or \"write\" is from the\nperspective of the surveyor.\n\nThis protocol uses a `SurveyProtocolService` on UUID ``137c4435-8a64-4bcb-93f1-3792c6bdc965``.\nIt has three characteristics:\n\n * Name characteristis UUID: ``137c4435-8a64-4bcb-93f1-3792c6bdc966``. This characteristic is read\n only and is a simple string indicating which protocol is being used. Currently this is hardcoded to \"SAP6\"\n * Leg characteristic - UUID: ``137c4435-8a64-4bcb-93f1-3792c6bdc968``. This characteristic is read only and will\n notify each time a shot is taken. It is a sequence of 17 bytes and is little-endian:\n\n * Byte 0: Sequence bit. This will alternate between 0 and 1 for successive legs. The surveyor\n must respond with an appropriate ACK (see later), otherwise the instrument will resend after 5\n seconds.\n * Bytes 1-4: Azimuth in degrees as a float.\n * Bytes 5-8: Inclination in degrees as a float.\n * Bytes 9-12: Roll in degrees as a float.\n * Bytes 13-16: Distance in metres as a float.\n\n * Command characteristic - UUID: ``137c4435-8a64-4bcb-93f1-3792c6bdc967``. Write only, surveyor\n can send a single byte to the instrument. Currently defined bytes:\n\n * ``0x55`` (ACK0): Signals that the surveyor has received a leg with sequence byte 0\n * ``0x56`` (ACK1): Signals that the surveyor has received a leg with sequence byte 1\n * ``0x31`` (START_CAL): Instrument should enter calibration mode\n * ``0x30`` (STOP_CAL): Instrument should leave calibration mode\n * ``0x36`` (LASER_ON): Instrument should turn the laser on\n * ``0x37`` (LASER_OFF): Instrument should turn the laser off\n * ``0x34`` (DEVICE_OFF): Instrument should turn off\n * ``0x38`` (TAKE_SHOT): Instrument should take a reading\n\n All the above constants are defined in both CircuitPython and Kotlin code\n\nUsage\n=====\n\nThe software in this repository simplifies use of the above protocol.\n\nCircuitPython\n-------------\n\nThe CircuitPython code manages the whole notification and responds to ACKs as per the protocol above. It keeps\na queue of legs that have not yet been sent if communication with the surveyor is interrupted and sends these once\ncommunication has been re-established.\n\nInitialising\n............\n\n.. code-block:: python\n\n from adafruit_ble import BLERadio\n from adafruit_ble.advertising.standard import ProvideServicesAdvertisement\n import caveble\n\n ble = BLERadio()\n ble.name = \"SAP6_AB\"\n survey_protocol = caveble.SurveyProtocolService()\n advertisement = ProvideServicesAdvertisement(survey_protocol)\n ble.start_advertising(advertisement)\n\nYou will need to either call `SurveyProtocolService.poll` repeatedly, or create `SurveyProtocolService.background_task`\nas an asyncio task.\n\n\nSending data using poll\n.......................\n\n.. code-block:: python\n\n while True:\n survey_protocol.poll()\n # if leg available to send..\n survey_protocol.send_data(azimuth, inclination, distance, roll)\n time.sleep(0.5)\n\nSending data asynchronously\n...........................\n\n.. code-block:: python\n\n asyncio.create_task(survey_protocol.background_task())\n while True:\n await leg_ready_event\n survey_protocol.send_data(azimuth, inclination, distance, roll)\n\n\nReceiving commands using poll\n.............................\n\n.. code-block:: python\n\n while True:\n message = survey_protocol.poll()\n if message is not None:\n # do something with message\n # will not receive ACK0 or ACK1 - these are dealt with by `SurveyProtocolService`\n time.sleep(0.5)\n\nReceiving commands asynchronously\n.................................\n\n.. code-block:: python\n\n async def callback(message: int):\n #process the message\n\n asyncio.create_task(survey_protocol.background_task(callback))\n while True:\n await leg_ready_event\n survey_protocol.send_data(azimuth, inclination, distance, roll)\n\n\nKotlin/Java (Android)\n---------------------\n\nYou can use ``CaveBLE.kt`` in your code - simply change the package to something appropriate on line one. Note Kotlin\nis fully compatible with Java and AndroidStudio comfortably uses these files interchangeably in the same project.\n\nTo use the device, you must create a ``CaveBLE`` object. you will need to pass in a bluetooth device object, a context,\na leg callback and an optional status callback.\nThe leg callback will be called whenever a new leg is received\nThe status callback will be called whenever the device connects or disconnects\n\nSample Java code\n......................\n\n.. code-block:: java\n\n package xxx.xxx.xxx.xxx;\n\n import android.bluetooth.BluetoothDevice;\n import android.content.Context;\n import xxx.xxx.xxx.CaveBLE;\n\n import kotlin.Unit;\n\n public class SAP6Communicator extends Communicator {\n\n private final CaveBLE caveBLE;\n\n\n public SAP6Communicator(Context context, BluetoothDevice bluetoothDevice) {\n this.caveBLE = new CaveBLE(bluetoothDevice, context, this::legCallback, this::statusCallback);\n }\n\n public boolean isConnected() {\n return caveBLE.isConnected();\n }\n\n public void requestConnect() {\n caveBLE.connect();\n }\n\n public void requestDisconnect() {\n caveBLE.disconnect();\n }\n\n public void laserOn() {\n caveBLE.laserOn()\n }\n\n // other commands have similar functions\n\n public Unit legCallback(float azimuth, float inclination, float roll, float distance) {\n // code to respond to a leg being received here.\n return Unit.INSTANCE; // you must return Unit.INSTANCE for callbacks to Kotlin code\n }\n\n public Unit statusCallback(int status, String msg) {\n switch (status) {\n case CaveBLE.CONNECTED:\n // code to run when device connects here\n break;\n case CaveBLE.DISCONNECTED:\n Log.device(\"Disconnected\");\n // code to run when device disconnects here\n break;\n case CaveBLE.CONNECTION_FAILED:\n Log.device(\"Communication error: \"+msg);\n }\n return Unit.INSTANCE;\n }\n }\n\n\nDependencies\n=============\nThis driver depends on:\n\n* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_\n* `Adafruit BLE <https://github.com/adafruit/Adafruit_CircuitPython_BLE>`_\n\nPlease ensure all dependencies are available on the CircuitPython filesystem.\nThis is easily achieved by downloading\n`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_\nor individual libraries can be installed using\n`circup <https://github.com/adafruit/circup>`_.\n\n\nInstalling from PyPI\n====================\n\nOn supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from\nPyPI <https://pypi.org/project/circuitpython-caveble/>`_.\nTo install for current user:\n\n.. code-block:: shell\n\n pip3 install circuitpython-caveble\n\nTo install system-wide (this may be required in some cases):\n\n.. code-block:: shell\n\n sudo pip3 install circuitpython-caveble\n\nTo install in a virtual environment in your current project:\n\n.. code-block:: shell\n\n mkdir project-name && cd project-name\n python3 -m venv .venv\n source .env/bin/activate\n pip3 install circuitpython-caveble\n\nInstalling to a Connected CircuitPython Device with Circup\n==========================================================\n\nMake sure that you have ``circup`` installed in your Python environment.\nInstall it with the following command if necessary:\n\n.. code-block:: shell\n\n pip3 install circup\n\nWith ``circup`` installed and your CircuitPython device connected use the\nfollowing command to install:\n\n.. code-block:: shell\n\n circup install caveble\n\nOr the following command to update an existing version:\n\n.. code-block:: shell\n\n circup update\n\nFull Usage Example\n==================\n\n.. code-block:: python\n\n import time\n\n import board\n import keypad\n from adafruit_ble import BLERadio\n from adafruit_ble.advertising.standard import ProvideServicesAdvertisement\n import caveble\n\n ble = BLERadio()\n ble.name = \"SAP6_AB\"\n print(ble.name)\n survey_protocol = caveble.SurveyProtocolService()\n advertisement = ProvideServicesAdvertisement(survey_protocol)\n ble.start_advertising(advertisement)\n\n\n KEY_PINS = (board.D5, board.D9)\n keys = keypad.Keys(KEY_PINS, value_when_pressed=False, pull=True)\n\n compass = 0\n clino = 0\n distance = 5\n while True:\n event = keys.events.get()\n if event:\n key_number = event.key_number\n if event.pressed:\n if key_number == 0:\n # change the values to send\n compass = (compass + 10.5) % 360\n clino += 5.5\n if clino > 90:\n clino -= 180\n distance = (distance + 3.4) % 10000\n print(compass, clino, distance)\n if key_number == 1:\n survey_protocol.send_data(compass, clino, distance)\n print(\"Data sent\")\n message = survey_protocol.poll()\n if message:\n print(f\"Message received: {message}\")\n time.sleep(0.03)\n\nDocumentation\n=============\nAPI documentation for this library can be found on `Read the Docs <https://circuitpython-caveble.readthedocs.io/>`_.\n\nFor information on building library documentation, please check out\n`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.\n\nContributing\n============\n\nContributions are welcome! Please read our `Code of Conduct\n<https://github.com/furbrain/CircuitPython_CaveBLE/blob/HEAD/CODE_OF_CONDUCT.md>`_\nbefore contributing to help this project stay welcoming.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Cave Surveying Bluetooth Protocol",
"version": "1.1.2",
"project_urls": {
"Homepage": "https://github.com/furbrain/CircuitPython_CaveBLE"
},
"split_keywords": [
"adafruit",
"blinka",
"circuitpython",
"micropython",
"disto",
"distox",
"bluetooth",
"cave",
"survey",
"caveble"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9962e53cd1ecf1bf116bea3610283f20cb9ab0b1b0d2f8f4eacbe61f7246f2a5",
"md5": "74d1fa7ee7dc2f8ed19ca986f61968b9",
"sha256": "22691d3deb2b8be271f2cbd36ab2fcbb6b65a427c6a951a8d443789434b2514b"
},
"downloads": -1,
"filename": "circuitpython_caveble-1.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "74d1fa7ee7dc2f8ed19ca986f61968b9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 8446,
"upload_time": "2023-09-16T14:41:44",
"upload_time_iso_8601": "2023-09-16T14:41:44.042676Z",
"url": "https://files.pythonhosted.org/packages/99/62/e53cd1ecf1bf116bea3610283f20cb9ab0b1b0d2f8f4eacbe61f7246f2a5/circuitpython_caveble-1.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "e0968d9187fc176d924d558257608da226b4b1ae9a7544b304594461569091f2",
"md5": "f797df126bcfdfadc55c051061cfb8f6",
"sha256": "5a20f244f8455195a3c2c995b10af815895e5406f73c19661c1a4f95e642bef2"
},
"downloads": -1,
"filename": "circuitpython-caveble-1.1.2.tar.gz",
"has_sig": false,
"md5_digest": "f797df126bcfdfadc55c051061cfb8f6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 32270,
"upload_time": "2023-09-16T14:41:45",
"upload_time_iso_8601": "2023-09-16T14:41:45.904286Z",
"url": "https://files.pythonhosted.org/packages/e0/96/8d9187fc176d924d558257608da226b4b1ae9a7544b304594461569091f2/circuitpython-caveble-1.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-09-16 14:41:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "furbrain",
"github_project": "CircuitPython_CaveBLE",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "circuitpython-caveble"
}