Package: circuitpython-caveble
IntroductionCave Surveying Bluetooth Protocol - a protocol for communicating with
paperless cave surveying tools e.g.TopoDroidandSexyTopo. This protocol was developed for use with
theShetland Attack Pony 6, but is presented free to use
for anyone who wishes to use it for their cave surveying device.ProtocolIn 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 aSurveyProtocolServiceon UUID137c4435-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 00x56(ACK1): Signals that the surveyor has received a leg with sequence byte 10x31(START_CAL): Instrument should enter calibration mode0x30(STOP_CAL): Instrument should leave calibration mode0x36(LASER_ON): Instrument should turn the laser on0x37(LASER_OFF): Instrument should turn the laser off0x34(DEVICE_OFF): Instrument should turn off0x38(TAKE_SHOT): Instrument should take a readingAll the above constants are defined in both CircuitPython and Kotlin codeUsageThe software in this repository simplifies use of the above protocol.CircuitPythonThe 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.Initialisingfromadafruit_bleimportBLERadiofromadafruit_ble.advertising.standardimportProvideServicesAdvertisementimportcavebleble=BLERadio()ble.name="SAP6_AB"survey_protocol=caveble.SurveyProtocolService()advertisement=ProvideServicesAdvertisement(survey_protocol)ble.start_advertising(advertisement)You will need to either callSurveyProtocolService.pollrepeatedly, or createSurveyProtocolService.background_taskas an asyncio task.Sending data using pollwhileTrue:survey_protocol.poll()# if leg available to send..survey_protocol.send_data(azimuth,inclination,distance,roll)time.sleep(0.5)Sending data asynchronouslyasyncio.create_task(survey_protocol.background_task())whileTrue:awaitleg_ready_eventsurvey_protocol.send_data(azimuth,inclination,distance,roll)Receiving commands using pollwhileTrue:message=survey_protocol.poll()ifmessageisnotNone:# do something with message# will not receive ACK0 or ACK1 - these are dealt with by `SurveyProtocolService`time.sleep(0.5)Receiving commands asynchronouslyasyncdefcallback(message:int):#process the messageasyncio.create_task(survey_protocol.background_task(callback))whileTrue:awaitleg_ready_eventsurvey_protocol.send_data(azimuth,inclination,distance,roll)Kotlin/Java (Android)You can useCaveBLE.ktin 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 aCaveBLEobject. 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 disconnectsSample Java codepackagexxx.xxx.xxx.xxx;importandroid.bluetooth.BluetoothDevice;importandroid.content.Context;importxxx.xxx.xxx.CaveBLE;importkotlin.Unit;publicclassSAP6CommunicatorextendsCommunicator{privatefinalCaveBLEcaveBLE;publicSAP6Communicator(Contextcontext,BluetoothDevicebluetoothDevice){this.caveBLE=newCaveBLE(bluetoothDevice,context,this::legCallback,this::statusCallback);}publicbooleanisConnected(){returncaveBLE.isConnected();}publicvoidrequestConnect(){caveBLE.connect();}publicvoidrequestDisconnect(){caveBLE.disconnect();}publicvoidlaserOn(){caveBLE.laserOn()}// other commands have similar functionspublicUnitlegCallback(floatazimuth,floatinclination,floatroll,floatdistance){// code to respond to a leg being received here.returnUnit.INSTANCE;// you must return Unit.INSTANCE for callbacks to Kotlin code}publicUnitstatusCallback(intstatus,Stringmsg){switch(status){caseCaveBLE.CONNECTED:// code to run when device connects herebreak;caseCaveBLE.DISCONNECTED:Log.device("Disconnected");// code to run when device disconnects herebreak;caseCaveBLE.CONNECTION_FAILED:Log.device("Communication error: "+msg);}returnUnit.INSTANCE;}}DependenciesThis driver depends on:Adafruit CircuitPythonAdafruit BLEPlease ensure all dependencies are available on the CircuitPython filesystem.
This is easily achieved by downloadingthe Adafruit library and driver bundleor individual libraries can be installed usingcircup.Installing from PyPIOn supported GNU/Linux systems like the Raspberry Pi, you can install the driver locallyfrom
PyPI.
To install for current user:pip3installcircuitpython-cavebleTo install system-wide (this may be required in some cases):sudopip3installcircuitpython-cavebleTo install in a virtual environment in your current project:mkdirproject-name&&cdproject-namepython3-mvenv.venvsource.env/bin/activatepip3installcircuitpython-cavebleInstalling to a Connected CircuitPython Device with CircupMake sure that you havecircupinstalled in your Python environment.
Install it with the following command if necessary:pip3installcircupWithcircupinstalled and your CircuitPython device connected use the
following command to install:circupinstallcavebleOr the following command to update an existing version:circupupdateFull Usage Exampleimporttimeimportboardimportkeypadfromadafruit_bleimportBLERadiofromadafruit_ble.advertising.standardimportProvideServicesAdvertisementimportcavebleble=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=0clino=0distance=5whileTrue:event=keys.events.get()ifevent:key_number=event.key_numberifevent.pressed:ifkey_number==0:# change the values to sendcompass=(compass+10.5)%360clino+=5.5ifclino>90:clino-=180distance=(distance+3.4)%10000print(compass,clino,distance)ifkey_number==1:survey_protocol.send_data(compass,clino,distance)print("Data sent")message=survey_protocol.poll()ifmessage:print(f"Message received:{message}")time.sleep(0.03)DocumentationAPI documentation for this library can be found onRead the Docs.For information on building library documentation, please check outthis guide.ContributingContributions are welcome! Please read ourCode of Conductbefore contributing to help this project stay welcoming.