Skip to content

Commit

Permalink
Added the possibility of building and running the project on Mac (wit…
Browse files Browse the repository at this point in the history
…hout bluetooth) (#32)
  • Loading branch information
RamonBeast authored May 11, 2024
1 parent b7c35d3 commit a540a28
Show file tree
Hide file tree
Showing 7 changed files with 566 additions and 534 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ $ python3 -m radiacode-examples.narodmon --bluetooth-mac 52:43:01:02:03:04
$ poetry install
$ poetry run python3 radiacode-examples/basic.py --bluetooth-mac 52:43:01:02:03:04 # or without --bluetooth-mac for USB connection
```

## MacOS
The library used to communicate over Bluetooh (```bluepy```) is [not supported](https://github.com/IanHarvey/bluepy/issues/44) on MacOS. Only the USB connection is available on Apple devices. A ```USB Serial Number```, obtainable from the ```Device Info``` menu on the device itself, can be specified if more than one Radiacode is connected via USB at the same time.

Make sure ```libusb``` is installed on your system, if you use ```Brew``` you can run: ```brew install libusb```
952 changes: 470 additions & 482 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include = ["radiacode-examples/*"]

[tool.poetry.dependencies]
python = "^3.9"
bluepy = "^1.3"
bluepy = { version = "^1.3", markers = "sys_platform != 'darwin'" }
pyusb = "^1.2"
aiohttp = {version = "^3.9", optional = true}
prometheus-client = {version = "^0.19", optional = true}
Expand Down
40 changes: 34 additions & 6 deletions radiacode-examples/basic.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import argparse
import time
import platform

from radiacode import RadiaCode
from radiacode.transports.usb import DeviceNotFound as DeviceNotFoundUSB
from radiacode.transports.bluetooth import DeviceNotFound as DeviceNotFoundBT


def main():
parser = argparse.ArgumentParser()
parser.add_argument('--bluetooth-mac', type=str, required=False, help='bluetooth MAC address of radiascan device')

if platform.system() != 'Darwin':
parser.add_argument(
'--bluetooth-mac', type=str, required=False, help='bluetooth MAC address of radiascan device (e.g. 00:11:22:33:44:55)'
)

parser.add_argument(
'--serial',
type=str,
required=False,
help='serial number of radiascan device (e.g. "RC-10x-xxxxxx"). Useful in case of multiple devices.',
)

args = parser.parse_args()

if args.bluetooth_mac:
print('will use Bluetooth connection')
rc = RadiaCode(bluetooth_mac=args.bluetooth_mac)
if hasattr(args, 'bluetooth_mac') and args.bluetooth_mac:
print(f'Connecting to Radiacode via Bluetooth (MAC address: {args.bluetooth_mac})')

try:
rc = RadiaCode(bluetooth_mac=args.bluetooth_mac)
except DeviceNotFoundBT as e:
print(e)
return
except ValueError as e:
print(e)
return
else:
print('will use USB connection')
rc = RadiaCode()
print('Connecting to Radiacode via USB' + (f' (serial number: {args.serial})' if args.serial else ''))

try:
rc = RadiaCode(serial_number=args.serial)
except DeviceNotFoundUSB:
print('Device not found, check your USB connection')
return

serial = rc.serial_number()
print(f'### Serial number: {serial}')
Expand Down
7 changes: 6 additions & 1 deletion radiacode/radiacode.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import struct
import platform
from typing import List, Optional, Union

from radiacode.bytes_buffer import BytesBuffer
Expand All @@ -25,7 +26,11 @@ def __init__(
ignore_firmware_compatibility_check: bool = False,
):
self._seq = 0
if bluetooth_mac is not None:

# Bluepy doesn't support MacOS: https://github.com/IanHarvey/bluepy/issues/44
self._bt_supported = platform.system() != 'Darwin'

if bluetooth_mac is not None and self._bt_supported is True:
self._connection = Bluetooth(bluetooth_mac)
else:
self._connection = Usb(serial_number=serial_number)
Expand Down
93 changes: 50 additions & 43 deletions radiacode/transports/bluetooth.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,59 @@
import struct

from bluepy.btle import BTLEDisconnectError, DefaultDelegate, Peripheral

from radiacode.bytes_buffer import BytesBuffer
import platform


class DeviceNotFound(Exception):
pass


class Bluetooth(DefaultDelegate):
def __init__(self, mac):
self._resp_buffer = b''
self._resp_size = 0
self._response = None

try:
self.p = Peripheral(mac)
except BTLEDisconnectError as ex:
raise DeviceNotFound('Device not found or bluetooth adapter is not powered on') from ex

self.p.withDelegate(self)

service = self.p.getServiceByUUID('e63215e5-7003-49d8-96b0-b024798fb901')
self.write_fd = service.getCharacteristics('e63215e6-7003-49d8-96b0-b024798fb901')[0].getHandle()
notify_fd = service.getCharacteristics('e63215e7-7003-49d8-96b0-b024798fb901')[0].getHandle()
self.p.writeCharacteristic(notify_fd + 1, b'\x01\x00')

def handleNotification(self, chandle, data):
if self._resp_size == 0:
self._resp_size = 4 + struct.unpack('<i', data[:4])[0]
self._resp_buffer = data[4:]
else:
self._resp_buffer += data
self._resp_size -= len(data)
assert self._resp_size >= 0
if self._resp_size == 0:
self._response = self._resp_buffer
self._resp_buffer = b''
if platform.system() == 'Darwin':

def execute(self, req) -> BytesBuffer:
for pos in range(0, len(req), 18):
rp = req[pos : min(pos + 18, len(req))]
self.p.writeCharacteristic(self.write_fd, rp)
class Bluetooth:
def __init__(self):
# Create an empty class if we are on MacOS
pass
else:
from bluepy.btle import BTLEDisconnectError, DefaultDelegate, Peripheral
from radiacode.bytes_buffer import BytesBuffer

while self._response is None:
self.p.waitForNotifications(2.0)

br = BytesBuffer(self._response)
self._response = None
return br
class Bluetooth(DefaultDelegate):
def __init__(self, mac):
self._resp_buffer = b''
self._resp_size = 0
self._response = None

try:
self.p = Peripheral(mac)
except BTLEDisconnectError as ex:
raise DeviceNotFound('Device not found or bluetooth adapter is not powered on') from ex

self.p.withDelegate(self)

service = self.p.getServiceByUUID('e63215e5-7003-49d8-96b0-b024798fb901')
self.write_fd = service.getCharacteristics('e63215e6-7003-49d8-96b0-b024798fb901')[0].getHandle()
notify_fd = service.getCharacteristics('e63215e7-7003-49d8-96b0-b024798fb901')[0].getHandle()
self.p.writeCharacteristic(notify_fd + 1, b'\x01\x00')

def handleNotification(self, chandle, data):
if self._resp_size == 0:
self._resp_size = 4 + struct.unpack('<i', data[:4])[0]
self._resp_buffer = data[4:]
else:
self._resp_buffer += data
self._resp_size -= len(data)
assert self._resp_size >= 0
if self._resp_size == 0:
self._response = self._resp_buffer
self._resp_buffer = b''

def execute(self, req) -> BytesBuffer:
for pos in range(0, len(req), 18):
rp = req[pos : min(pos + 18, len(req))]
self.p.writeCharacteristic(self.write_fd, rp)

while self._response is None:
self.p.waitForNotifications(2.0)

br = BytesBuffer(self._response)
self._response = None
return br
1 change: 0 additions & 1 deletion radiacode/transports/usb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import struct

import usb.core

from radiacode.bytes_buffer import BytesBuffer
Expand Down

0 comments on commit a540a28

Please sign in to comment.