Skip to content

Commit a267ff4

Browse files
committed
Compass: Add --compass=on option to enable compass
1 parent dca252e commit a267ff4

File tree

9 files changed

+80
-9
lines changed

9 files changed

+80
-9
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ in progress
99
- SignalK telemetry: Use Radian (rad) unit for angle values
1010
- SignalK telemetry: Use Kelvin unit for temperature
1111
- SignalK telemetry: Use ratio (percentage) for battery level
12+
- Compass: Add ``--compass=on`` option to enable compass
1213

1314

1415
2022-08-03 0.5.1

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ default, the Bluetooth adapter ``hci0`` will be used.
113113
# Get device reading.
114114
calypso-anemometer read
115115

116+
# Get device reading, with compass (roll, pitch, heading).
117+
calypso-anemometer read --compass=on
118+
116119
# Get device readings, continuously at 4 Hz (default).
117120
calypso-anemometer read --subscribe
118121

calypso_anemometer/cli.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from calypso_anemometer.core import CalypsoDeviceApi
1010
from calypso_anemometer.engine import handler_factory, run_engine
11-
from calypso_anemometer.model import CalypsoDeviceDataRate, CalypsoDeviceMode, Settings
11+
from calypso_anemometer.model import CalypsoDeviceCompassStatus, CalypsoDeviceDataRate, CalypsoDeviceMode, Settings
1212
from calypso_anemometer.util import EnumChoice, make_sync, setup_logging
1313

1414
logger = logging.getLogger(__name__)
@@ -66,6 +66,12 @@ def cli(ctx, quiet: t.Optional[bool], verbose: t.Optional[bool], debug: t.Option
6666
required=False,
6767
help="Set device data rate to one of HZ_1, HZ_4, or HZ_8.",
6868
)
69+
compass_option = click.option(
70+
"--compass",
71+
type=EnumChoice(CalypsoDeviceCompassStatus, case_sensitive=False),
72+
required=False,
73+
help="Set device compass status to one of OFF, ON.",
74+
)
6975
subscribe_option = click.option("--subscribe", is_flag=True, required=False, help="Continuously receive readings")
7076
target_option = click.option("--target", type=str, required=False, help="Submit telemetry data to target")
7177

@@ -128,6 +134,7 @@ async def explore(
128134
help="Set device mode to one of SLEEP, LOW_POWER, or NORMAL.",
129135
)
130136
@rate_option
137+
@compass_option
131138
@click.pass_context
132139
@make_sync
133140
async def set_option(
@@ -138,6 +145,7 @@ async def set_option(
138145
ble_connect_timeout: t.Optional[float] = None,
139146
mode: t.Optional[CalypsoDeviceMode] = None,
140147
rate: t.Optional[CalypsoDeviceDataRate] = None,
148+
compass: t.Optional[CalypsoDeviceCompassStatus] = None,
141149
):
142150
async def handler(calypso: CalypsoDeviceApi):
143151
if mode is not None:
@@ -146,6 +154,9 @@ async def handler(calypso: CalypsoDeviceApi):
146154
if rate is not None:
147155
logger.info(f"Setting device data rate to {rate}")
148156
await calypso.set_datarate(rate)
157+
if compass is not None:
158+
logger.info(f"Setting device compass status to {compass}")
159+
await calypso.set_compass(compass)
149160
await calypso.about()
150161

151162
settings = Settings(
@@ -165,6 +176,7 @@ async def handler(calypso: CalypsoDeviceApi):
165176
@subscribe_option
166177
@target_option
167178
@rate_option
179+
@compass_option
168180
@click.pass_context
169181
@make_sync
170182
async def read(
@@ -176,6 +188,7 @@ async def read(
176188
subscribe: t.Optional[bool] = False,
177189
target: t.Optional[str] = None,
178190
rate: t.Optional[CalypsoDeviceDataRate] = None,
191+
compass: t.Optional[CalypsoDeviceCompassStatus] = None,
179192
):
180193
quiet = ctx.parent.params.get("quiet")
181194
settings = Settings(
@@ -184,26 +197,28 @@ async def read(
184197
ble_discovery_timeout=ble_discovery_timeout,
185198
ble_connect_timeout=ble_connect_timeout,
186199
)
187-
handler = await handler_factory(subscribe=subscribe, target=target, rate=rate, quiet=quiet)
200+
handler = await handler_factory(subscribe=subscribe, target=target, rate=rate, compass=compass, quiet=quiet)
188201
await run_engine(workhorse=CalypsoDeviceApi, settings=settings, handler=handler)
189202

190203

191204
@click.command()
192205
@subscribe_option
193206
@target_option
194207
@rate_option
208+
@compass_option
195209
@click.pass_context
196210
@make_sync
197211
async def fake(
198212
ctx,
199213
subscribe: bool = False,
200214
target: t.Optional[str] = None,
201215
rate: t.Optional[CalypsoDeviceDataRate] = None,
216+
compass: t.Optional[CalypsoDeviceCompassStatus] = None,
202217
):
203218
from calypso_anemometer.fake import CalypsoDeviceApiFake
204219

205220
quiet = ctx.parent.params.get("quiet")
206-
handler = await handler_factory(subscribe=subscribe, target=target, rate=rate, quiet=quiet)
221+
handler = await handler_factory(subscribe=subscribe, target=target, rate=rate, compass=compass, quiet=quiet)
207222
await run_engine(workhorse=CalypsoDeviceApiFake, handler=handler)
208223

209224

calypso_anemometer/core.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828
from calypso_anemometer.model import (
2929
BleCharSpec,
30+
CalypsoDeviceCompassStatus,
3031
CalypsoDeviceDataRate,
3132
CalypsoDeviceInfo,
3233
CalypsoDeviceInfoCharacteristic,
@@ -212,6 +213,14 @@ async def set_datarate(self, rate: CalypsoDeviceDataRate):
212213
CalypsoDeviceStatusCharacteristic.rate.value.uuid, data=bytes([rate.value]), response=True
213214
)
214215

216+
async def set_compass(self, compass: CalypsoDeviceCompassStatus):
217+
if compass is None:
218+
return
219+
logger.info(f"Setting compass status to {compass}")
220+
await self.client.write_gatt_char(
221+
CalypsoDeviceStatusCharacteristic.compass.value.uuid, data=bytes([compass.value]), response=True
222+
)
223+
215224
async def get_reading(self):
216225
logger.info("Requesting reading")
217226
data: bytearray = await self.client.read_gatt_char(CalypsoDeviceReadingCharacteristic.data.value.uuid)

calypso_anemometer/engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from calypso_anemometer.core import CalypsoDeviceApi
99
from calypso_anemometer.exception import CalypsoError
10-
from calypso_anemometer.model import CalypsoDeviceDataRate, CalypsoReading, Settings
10+
from calypso_anemometer.model import CalypsoDeviceCompassStatus, CalypsoDeviceDataRate, CalypsoReading, Settings
1111
from calypso_anemometer.telemetry.adapter import TelemetryAdapter
1212
from calypso_anemometer.util import wait_forever
1313

@@ -32,6 +32,7 @@ async def handler_factory(
3232
subscribe: bool = False,
3333
target: t.Optional[str] = None,
3434
rate: t.Optional[CalypsoDeviceDataRate] = None,
35+
compass: t.Optional[CalypsoDeviceCompassStatus] = None,
3536
quiet: bool = False,
3637
) -> t.Callable:
3738
"""
@@ -40,6 +41,7 @@ async def handler_factory(
4041
:param subscribe: Whether to run in one-shot or continuous mode.
4142
:param target: Where to submit telemetry data to, and how.
4243
:param rate: At which rate to sample the readings.
44+
:param compass: If the compass should be enabled or not.
4345
:param quiet: Do not print to stdout or stderr.
4446
4547
:return: An asynchronous handler function accepting a reference to a workhorse instance.
@@ -66,6 +68,9 @@ def process_reading(reading: CalypsoReading):
6668

6769
# Main handler, which receives readings.
6870
async def handler(calypso: CalypsoDeviceApi):
71+
# Optionally enable compass.
72+
await calypso.set_compass(compass)
73+
6974
# One-shot reading.
7075
if not subscribe:
7176
reading = await calypso.get_reading()

calypso_anemometer/fake.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import aiorate
1010

11-
from calypso_anemometer.model import CalypsoDeviceDataRate, CalypsoReading, Settings
11+
from calypso_anemometer.model import CalypsoDeviceCompassStatus, CalypsoDeviceDataRate, CalypsoReading, Settings
1212

1313
logger = logging.getLogger(__name__)
1414

@@ -43,6 +43,7 @@ def __init__(self, settings: Optional[Settings] = None, ble_address: Optional[st
4343
self.settings = settings
4444
self.ble_address = settings.ble_address
4545
self.datarate: CalypsoDeviceDataRate = CalypsoDeviceDataRate.HZ_4
46+
self.compass: CalypsoDeviceCompassStatus = CalypsoDeviceCompassStatus.OFF
4647
self.reading: Optional[CalypsoReading] = None
4748

4849
async def __aenter__(self):
@@ -67,6 +68,9 @@ async def disconnect(self):
6768
async def set_datarate(self, rate: CalypsoDeviceDataRate):
6869
self.datarate = rate
6970

71+
async def set_compass(self, compass: CalypsoDeviceCompassStatus):
72+
self.compass = compass
73+
7074
async def get_reading(self):
7175
logger.info("Producing reading")
7276
return await self.produce_fake_reading()

testing/test_cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,13 @@ def test_cli_read_stdout_success(caplog):
147147
@mock.patch("calypso_anemometer.core.BleakClient.write_gatt_char", AsyncMock(return_value=None))
148148
@mock.patch("calypso_anemometer.core.BleakClient.start_notify", AsyncMock(return_value=None))
149149
@mock.patch("calypso_anemometer.engine.wait_forever", AsyncMock(return_value=None))
150-
def test_cli_subscribe_stdout_success(caplog):
150+
def test_cli_subscribe_rate_compass_stdout_success(caplog):
151151
"""
152152
Test successful `calypso-anemometer read --subscribe`
153153
"""
154154

155155
runner = CliRunner()
156-
result = runner.invoke(cli, ["read", "--subscribe", "--rate=HZ_8"], catch_exceptions=False)
156+
result = runner.invoke(cli, ["read", "--subscribe", "--rate=HZ_8", "--compass=on"], catch_exceptions=False)
157157
assert result.exit_code == 0
158158

159159
# TODO: Currently no reading is emitted and processed, because `start_notify` is mocked
@@ -164,6 +164,7 @@ def test_cli_subscribe_stdout_success(caplog):
164164
assert "Found device at address: bar: foo" in caplog.messages
165165
assert "Connecting to device at 'bar' with adapter 'hci0'" in caplog.messages
166166
assert "Setting data rate to 8" in caplog.messages
167+
assert "Setting compass status to 1" in caplog.messages
167168
assert "Subscribing to readings" in caplog.messages
168169
assert "Disconnecting" in caplog.messages
169170

testing/test_core_write.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import calypso_anemometer
1515
from calypso_anemometer.core import CalypsoDeviceApi
16-
from calypso_anemometer.model import CalypsoDeviceDataRate
16+
from calypso_anemometer.model import CalypsoDeviceCompassStatus, CalypsoDeviceDataRate
1717

1818

1919
@pytest.mark.asyncio
@@ -30,3 +30,35 @@ async def test_set_datarate_success(mocker: MockerFixture, caplog):
3030
assert "Connecting to device at 'bar' with adapter 'hci0'" in caplog.messages
3131
assert "Setting data rate to 8" in caplog.messages
3232
assert "Disconnecting" in caplog.messages
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_set_compass_enabled(mocker: MockerFixture, caplog):
37+
mocker.patch("calypso_anemometer.core.BleakClient.connect", AsyncMock(return_value=None))
38+
mocker.patch("calypso_anemometer.core.BleakClient.write_gatt_char", AsyncMock(return_value=None))
39+
40+
spy = mocker.spy(calypso_anemometer.core.BleakClient, "write_gatt_char")
41+
async with CalypsoDeviceApi(ble_address="bar") as calypso:
42+
await calypso.set_compass(CalypsoDeviceCompassStatus.ON)
43+
44+
assert spy.mock_calls == [call("0000a003-0000-1000-8000-00805f9b34fb", data=b"\x01", response=True)]
45+
46+
assert "Connecting to device at 'bar' with adapter 'hci0'" in caplog.messages
47+
assert "Setting compass status to 1" in caplog.messages
48+
assert "Disconnecting" in caplog.messages
49+
50+
51+
@pytest.mark.asyncio
52+
async def test_set_compass_disabled(mocker: MockerFixture, caplog):
53+
mocker.patch("calypso_anemometer.core.BleakClient.connect", AsyncMock(return_value=None))
54+
mocker.patch("calypso_anemometer.core.BleakClient.write_gatt_char", AsyncMock(return_value=None))
55+
56+
spy = mocker.spy(calypso_anemometer.core.BleakClient, "write_gatt_char")
57+
async with CalypsoDeviceApi(ble_address="bar") as calypso:
58+
await calypso.set_compass(CalypsoDeviceCompassStatus.OFF)
59+
60+
assert spy.mock_calls == [call("0000a003-0000-1000-8000-00805f9b34fb", data=b"\x00", response=True)]
61+
62+
assert "Connecting to device at 'bar' with adapter 'hci0'" in caplog.messages
63+
assert "Setting compass status to 0" in caplog.messages
64+
assert "Disconnecting" in caplog.messages

testing/test_fake.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77

88
from calypso_anemometer.fake import MAXIMUM_VALUES, MINIMUM_VALUES, CalypsoDeviceApiFake
9-
from calypso_anemometer.model import CalypsoDeviceDataRate, CalypsoReading
9+
from calypso_anemometer.model import CalypsoDeviceCompassStatus, CalypsoDeviceDataRate, CalypsoReading
1010

1111

1212
@pytest.mark.asyncio
@@ -26,6 +26,7 @@ async def test_set_datarate():
2626
async def test_subscribe_once():
2727
callback_mock = Mock()
2828
async with CalypsoDeviceApiFake() as fake:
29+
await fake.set_compass(CalypsoDeviceCompassStatus.ON)
2930
await fake.set_datarate(CalypsoDeviceDataRate.HZ_8)
3031
await fake.subscribe_reading(callback=callback_mock, run_once=True)
3132

0 commit comments

Comments
 (0)