diff --git a/dotbot/calibration/lighthouse2.py b/dotbot/calibration/lighthouse2.py index 6954f95c..8114ccca 100644 --- a/dotbot/calibration/lighthouse2.py +++ b/dotbot/calibration/lighthouse2.py @@ -379,8 +379,10 @@ def reprojection_residual_mm( # --- Wire ------------------------------------------------------------------- # One calibration message per station, `swrmt_lh2_calibration_data_t` in the -# swarmit netcore: count, index, the matrix, then the site fields every -# message of a push repeats. swarmit's `helpers.py` packs the same bytes; the +# swarmit netcore: the station mask, the station's index, its matrix and its +# rectangle, then the site name and calibration id every message of a push +# repeats. The net core commits once every station of the mask has arrived, +# keyed by mask and id. swarmit's `helpers.py` packs the same bytes; the # fixture test in each repo pins them. LH2_SITE_NAME_BYTES = 16 LH2_CALIBRATION_ID_BYTES = 8 @@ -483,11 +485,10 @@ def station_label(index: int) -> str: def pushable_stations(calibration: Calibration) -> list[StationSolution]: - """The stations in index order, refused when a robot would mis-key them. + """The stations in index order, refused when a robot could not hold them. - The receiver stores a matrix at its index and takes the count as "slots - 0 to count - 1 are valid", so a gap in the numbering would leave a slot - the count claims and nothing filled. + A robot stores each station in the slot of its index, 0 to 15, so any + set of indices in that range will do. """ ordered = sorted(calibration.stations, key=lambda s: s.index) if not ordered: @@ -497,30 +498,30 @@ def pushable_stations(calibration: Calibration) -> list[StationSolution]: f"{len(ordered)} stations exceeds the LH2 limit " f"({LH2_BASESTATION_COUNT_MAX})" ) - if [s.index for s in ordered] != list(range(len(ordered))): - got = ", ".join(str(s.index) for s in ordered) + outside = [s.index for s in ordered if not 0 <= s.index < LH2_BASESTATION_COUNT_MAX] + if outside: raise ValueError( - f"stations must be numbered from zero without gaps to be pushed, got {got}" + f"station index {outside} is outside 0 to {LH2_BASESTATION_COUNT_MAX - 1}" ) return ordered def site_fields_as_bytes(calibration: Calibration) -> bytes: - """valid_mm, site name and id: the 40 bytes after every matrix.""" - return ( - valid_mm_as_bytes(calibration.valid_mm) - + site_name_as_bytes(calibration.site.name) - + calibration_id_as_bytes(pushed_id(calibration)) + """Site name and id: the 24 bytes that close every message and the page.""" + return site_name_as_bytes(calibration.site.name) + calibration_id_as_bytes( + pushed_id(calibration) ) def calibration_messages(calibration: Calibration) -> list[bytes]: """One 84-byte calibration message per station, in index order.""" stations = pushable_stations(calibration) + mask = station_mask(calibration) site_fields = site_fields_as_bytes(calibration) return [ - struct.pack(" dict[str, Any]: self.pushed[-1][:LH2_CALIBRATION_MESSAGE_BYTES] ) info = SimpleNamespace( - info_version=3, lh2_site_name=site, lh2_calibration_id=calibration_id + info_version=4, lh2_site_name=site, lh2_calibration_id=calibration_id ) return {self.device: SimpleNamespace(info_gen=1, info=info)} diff --git a/dotbot/cli/swarm_lh2.py b/dotbot/cli/swarm_lh2.py index 43e0c359..df5f0c74 100644 --- a/dotbot/cli/swarm_lh2.py +++ b/dotbot/cli/swarm_lh2.py @@ -754,8 +754,16 @@ def _collect_spin( "report another one." ), ) +@click.option( + "--dry-run", + is_flag=True, + help=( + "Print the messages the push would send, one per station, and send " + "nothing; needs no swarm connection." + ), +) @click.pass_context -def _push(ctx, calibration, conn, swarm_id, site_name, site_changed): +def _push(ctx, calibration, conn, swarm_id, site_name, site_changed, dry_run): from dotbot.calibration.lighthouse2 import ( read_calibration_file, resolve_calibration_path, @@ -767,17 +775,45 @@ def _push(ctx, calibration, conn, swarm_id, site_name, site_changed): loaded = read_calibration_file(path) except ValueError as exc: raise click.ClickException(str(exc)) from exc + if dry_run: + _print_messages(loaded) + return client = _swarmit_client(ctx, conn, swarm_id) with client: _gated_push(client, loaded, site_changed=site_changed, devices=_devices(ctx)) +def _print_messages(calibration): + """What a push of `calibration` sends, message by message.""" + from dotbot.calibration.lighthouse2 import ( + calibration_messages, + station_label, + station_mask, + ) + + try: + messages = calibration_messages(calibration) + except ValueError as exc: + raise click.ClickException(str(exc)) from exc + stations = sorted(calibration.stations, key=lambda s: s.index) + click.echo( + f"Would send {len(messages)} message(s), {sum(map(len, messages))} B: " + f"id {calibration.id8}, site {calibration.site.name}, station mask " + f"0x{station_mask(calibration):04X}" + ) + for station, message in zip(stations, messages): + click.echo( + f" {station_label(station.index)}, rectangle {list(station.valid_mm)}: " + f"{message.hex()}" + ) + + def _gated_push(client, calibration, site_changed=False, devices=None, stop=False): """Check the robots' device info, push, and print the stale-id worklist. With `stop`, the named `devices` still in their app are stopped first. """ - from dotbot.calibration.lighthouse2 import calibration_payload + from dotbot.calibration.lighthouse2 import calibration_payload, station_label from dotbot.calibration.push import ( PushRefused, gate_push, @@ -810,8 +846,12 @@ def _gated_push(client, calibration, site_changed=False, devices=None, stop=Fals f"{len(check.other_site)} robot(s) move to site " f"{calibration.site.name} (--site-changed)." ) + stations = ", ".join( + station_label(s.index) + for s in sorted(calibration.stations, key=lambda s: s.index) + ) click.echo( - f"Sending {len(calibration.stations)} calibration matrix/matrices " + f"Sending {stations} " f"({len(payload)} B, id {calibration.id8}, site {calibration.site.name}) " f"to the swarm; {len(check.stale)} robot(s) hold another id..." ) diff --git a/dotbot/console-web/dev/fake_swarmit_server.py b/dotbot/console-web/dev/fake_swarmit_server.py index f961d2ba..aa54e305 100644 --- a/dotbot/console-web/dev/fake_swarmit_server.py +++ b/dotbot/console-web/dev/fake_swarmit_server.py @@ -174,17 +174,19 @@ def device_info(addr: str) -> dict: seed = _seed(addr) name, digest, size = IMAGES[seed % len(IMAGES)] # A minority are uncalibrated, which is the state an operator acts on. - homographies = 0 if seed % 9 == 0 else (2 if seed % 5 == 0 else 1) - flags = LH2_FLAG_VALID | LH2_FLAG_FROM_FLASH if homographies else 0 - noun = "basestation" if homographies == 1 else "basestations" + mask = 0 if seed % 9 == 0 else (0b101 if seed % 5 == 0 else 0b1) + flags = LH2_FLAG_VALID | LH2_FLAG_FROM_FLASH if mask else 0 + stations = [i for i in range(16) if mask >> i & 1] summary = ( "uncalibrated" - if not homographies - else f"{homographies} {noun} (valid, from flash)" + if not mask + else f"station{'s' if len(stations) > 1 else ''} " + f"{', '.join(map(str, stations))} (channel{'s' if len(stations) > 1 else ''} " + f"{', '.join(str(i + 1) for i in stations)}; valid, from flash)" ) return { # A few run sandbox firmware too old for this host's calibrations. - "info_version": 2 if seed % 7 == 0 else 3, + "info_version": 3 if seed % 7 == 0 else 4, "info_gen": 4, "boot_count": 2 + seed % 30, "uptime_s": 60 + seed % 9000, @@ -198,10 +200,10 @@ def device_info(addr: str) -> dict: "image_digest": digest, "image_name": name, "image_version": "", - "lh2_homography_count": homographies, + "lh2_station_mask": mask, "lh2_flags": flags, - "lh2_site_name": FAKE_SITE if homographies else "", - "lh2_calibration_id": FAKE_CALIBRATION_ID if homographies else "", + "lh2_site_name": FAKE_SITE if mask else "", + "lh2_calibration_id": FAKE_CALIBRATION_ID if mask else "", "lh2_summary": summary, "raw": "8f0104" + f"{seed:08x}" * 4, } diff --git a/dotbot/console-web/src/fleetViews.test.tsx b/dotbot/console-web/src/fleetViews.test.tsx index 9186e61e..efd5d8de 100644 --- a/dotbot/console-web/src/fleetViews.test.tsx +++ b/dotbot/console-web/src/fleetViews.test.tsx @@ -30,7 +30,7 @@ const site: Site = { }; const info = (over: Partial = {}): SwarmitDeviceInfo => ({ - info_version: 3, + info_version: 4, bl_version: "1.25.0", net_version: "1.25.0", boot_count: 3, diff --git a/dotbot/console-web/src/localization.test.ts b/dotbot/console-web/src/localization.test.ts index 7b83a269..8293d15a 100644 --- a/dotbot/console-web/src/localization.test.ts +++ b/dotbot/console-web/src/localization.test.ts @@ -56,7 +56,7 @@ const bot = (id: string, calibrationId?: string): UnifiedBot => calibrationId === undefined ? null : ({ - info: { info_version: 3, lh2_calibration_id: calibrationId }, + info: { info_version: 4, lh2_calibration_id: calibrationId }, } as unknown as SwarmitNode), }) as UnifiedBot; diff --git a/dotbot/console-web/src/localization.ts b/dotbot/console-web/src/localization.ts index 0d05627a..e04bbafa 100644 --- a/dotbot/console-web/src/localization.ts +++ b/dotbot/console-web/src/localization.ts @@ -39,7 +39,7 @@ export interface Coverage { } /** The device-info version of firmware that takes this host's calibrations; older needs a reflash. */ -export const DEVICE_INFO_VERSION_MIN = 3; +export const DEVICE_INFO_VERSION_MIN = 4; /** Who carries `savedId`, from the calibration id each robot's device info reports. */ export function calibrationCoverage(bots: UnifiedBot[], savedId: string): Coverage { diff --git a/dotbot/console-web/src/pushTarget.test.tsx b/dotbot/console-web/src/pushTarget.test.tsx index ed1140ba..a747f7a9 100644 --- a/dotbot/console-web/src/pushTarget.test.tsx +++ b/dotbot/console-web/src/pushTarget.test.tsx @@ -76,7 +76,7 @@ const bot = (id: string, calibrationId: string): UnifiedBot => ({ id, swarmit: { - info: { info_version: 3, lh2_calibration_id: calibrationId }, + info: { info_version: 4, lh2_calibration_id: calibrationId }, } as unknown as SwarmitNode, }) as UnifiedBot; diff --git a/dotbot/console-web/src/types.ts b/dotbot/console-web/src/types.ts index dac6b4a1..2d43c334 100644 --- a/dotbot/console-web/src/types.ts +++ b/dotbot/console-web/src/types.ts @@ -266,7 +266,8 @@ export interface SwarmitDeviceInfo { image_name: string; image_version: string; image_digest: string; - lh2_homography_count?: number; + // Bit i set: the bot holds station i's homography (channel i + 1). + lh2_station_mask?: number; lh2_flags?: number; // Device info v2: the site and calibration id the bot holds, "" for none. lh2_site_name?: string; diff --git a/dotbot/dotbot_simulator.py b/dotbot/dotbot_simulator.py index efc93f7e..efcb9c44 100644 --- a/dotbot/dotbot_simulator.py +++ b/dotbot/dotbot_simulator.py @@ -61,8 +61,8 @@ # How far apart `--robots N` puts a generated fleet FLEET_PITCH_MM = 200 # The stations a simulated robot holds when neither its world file nor the -# controller's calibration says: all eight -CALIBRATED_ALL = 0xFF +# controller's calibration says: all sixteen +CALIBRATED_ALL = 0xFFFF # The heading of every robot of a generated fleet: up (-y), 0 facing +y FLEET_FACING = 180 @@ -113,7 +113,7 @@ class SimulatedDotBotSettings(BaseModel): estimator starts out tracking; without one the robot faces +y and starts with no heading, as a real robot does from boot. `calibrated` is the bitmask of stations the robot holds a homography for; without one it - holds the stations of the controller's calibration, all eight when none + holds the stations of the controller's calibration, all sixteen when none is loaded. """ @@ -522,7 +522,7 @@ def __init__( ] calibrated = CALIBRATED_ALL if calibrated is None else calibrated self._calibrated = [ - (calibrated if s.calibrated is None else s.calibrated) & 0xFF + (calibrated if s.calibrated is None else s.calibrated) & 0xFFFF for s in settings ] self._dotbot_modes = [s.network_mode for s in settings] @@ -725,7 +725,7 @@ def _gru_residual(self, index: int, model, report): def _advertise(self, index: int, packet: bytearray): """Send robot `index`'s advertisement, as its firmware encoded it.""" # The calibration bitmask is the node's, not the control core's - packet[1] = self._calibrated[index] + packet[1:3] = self._calibrated[index].to_bytes(2, "little") self._counts[:, index] = 0 frame = Frame(header=self._headers[index], packet=Packet.from_bytes(packet)) if self._dotbot_modes[index] == SimulatedNetworkMode.MARI: diff --git a/dotbot/firmware/flash.py b/dotbot/firmware/flash.py index 4fa34417..bd8b3f4a 100644 --- a/dotbot/firmware/flash.py +++ b/dotbot/firmware/flash.py @@ -52,22 +52,27 @@ # swarmit's `swarmit_config_t` (SWARMIT_CONFIG_MAGIC_VALUE). Each value must # match its firmware's. CONFIG_MAGIC_BY_ROLE = { - "dotbot-v3": 0x5753524F, + "dotbot-v3": 0x57535250, "gateway": 0x5753524D, } CONFIG_MANIFEST_NAME = "config-manifest.json" # swarmit_config_t, little-endian and packed: magic, has_net_id, net_id, -# homography_count, sixteen 3x3 float32 matrices, valid_mm[4], site_name[16], -# calibration_id[8]. Every byte the calibration does not set is 0xFF, the -# erase state the firmware reads as "absent". +# station_mask, sixteen 3x3 float32 matrices by slot, sixteen valid_mm[4] by +# slot, site_name[16], calibration_id[8]; 872 bytes. Every byte the +# calibration does not set is 0xFF, the erase state the firmware reads as +# "absent"; slots outside the mask are ignored. LH2_MATRIX_BYTES = 3 * 3 * 4 +LH2_VALID_MM_BYTES = 4 * 4 LH2_MAX_HOMOGRAPHIES = 16 -SWARMIT_CONFIG_COUNT_OFFSET = 12 -SWARMIT_CONFIG_MATRICES_OFFSET = SWARMIT_CONFIG_COUNT_OFFSET + 4 -SWARMIT_CONFIG_SITE_OFFSET = ( +SWARMIT_CONFIG_MASK_OFFSET = 12 +SWARMIT_CONFIG_MATRICES_OFFSET = SWARMIT_CONFIG_MASK_OFFSET + 4 +SWARMIT_CONFIG_VALID_MM_OFFSET = ( SWARMIT_CONFIG_MATRICES_OFFSET + LH2_MAX_HOMOGRAPHIES * LH2_MATRIX_BYTES ) -SWARMIT_CONFIG_SITE_BYTES = 4 * 4 + 16 + 8 +SWARMIT_CONFIG_SITE_OFFSET = ( + SWARMIT_CONFIG_VALID_MM_OFFSET + LH2_MAX_HOMOGRAPHIES * LH2_VALID_MM_BYTES +) +SWARMIT_CONFIG_SITE_BYTES = 16 + 8 SWARMIT_CONFIG_BYTES = SWARMIT_CONFIG_SITE_OFFSET + SWARMIT_CONFIG_SITE_BYTES # Application images are linked after the bootloader. APP_FLASH_BASE_ADDR = 0x00010000 @@ -163,17 +168,15 @@ def make_config_hex_path( def load_calibration_file(path: Path): - """Read a schema 3 calibration file, refused when a robot could not take it.""" + """Read a calibration file, refused when a robot could not take it.""" from dotbot.calibration.lighthouse2 import ( - pushable_stations, + calibration_messages, read_calibration_file, - site_fields_as_bytes, ) try: calibration = read_calibration_file(Path(path)) - pushable_stations(calibration) - site_fields_as_bytes(calibration) + calibration_messages(calibration) except (OSError, ValueError) as exc: raise click.ClickException( f"Cannot use calibration file {path}: {exc}" @@ -187,22 +190,28 @@ def swarmit_config_page(net_id_value: int, calibration=None) -> bytes: homography_as_float32, pushable_stations, site_fields_as_bytes, + station_mask, + valid_mm_as_bytes, ) page = bytearray(b"\xff" * SWARMIT_CONFIG_BYTES) - page[:SWARMIT_CONFIG_COUNT_OFFSET] = struct.pack( + page[:SWARMIT_CONFIG_MASK_OFFSET] = struct.pack( " ButtonCapture ) -def _info(version=3, site="", calibration_id=""): +def _info(version=4, site="", calibration_id=""): """A robot's device info as swarmit decodes it.""" return SimpleNamespace( info_version=version, lh2_site_name=site, lh2_calibration_id=calibration_id @@ -115,7 +115,7 @@ def send_lh2_calibration(self, payload: bytes, devices=None) -> None: for addr, info in self.infos.items(): if devices is not None and addr not in devices: continue - if info is not None and info.info_version >= 3: + if info is not None and info.info_version >= 4: info.lh2_site_name, info.lh2_calibration_id = message_site( payload[:LH2_CALIBRATION_MESSAGE_BYTES] ) diff --git a/dotbot/tests/test_calibration_spin.py b/dotbot/tests/test_calibration_spin.py index 11d8e039..8af3d256 100644 --- a/dotbot/tests/test_calibration_spin.py +++ b/dotbot/tests/test_calibration_spin.py @@ -359,7 +359,7 @@ def test_a_spin_push_waits_for_the_robots_to_leave_the_app(monkeypatch, lab): sent = [] fleet.send_lh2_calibration = lambda payload, devices=None: sent.append(devices) for node in fleet.nodes.values(): - node.info.info_version = 3 + node.info.info_version = 4 node.info.lh2_site_name = "c405" node.info.lh2_calibration_id = "" node.info_gen = 1 @@ -763,5 +763,5 @@ def test_show_prints_a_corner_calibration_without_an_error_map(lab, tmp_path): path.write_text(FIXTURE_TOML, encoding="utf-8") shown = CliRunner().invoke(swarm_lh2.cmd, ["show", str(path)], obj={"config": lab}) assert shown.exit_code == 0, shown.output - assert "station 0 (channel 1): 4 marked points" in shown.output + assert "station 2 (channel 3): 4 marked points" in shown.output assert "predicted error" not in shown.output diff --git a/dotbot/tests/test_controller.py b/dotbot/tests/test_controller.py index 2cb5ef5d..88fba246 100644 --- a/dotbot/tests/test_controller.py +++ b/dotbot/tests/test_controller.py @@ -1597,7 +1597,8 @@ async def test_a_resend_that_fails_drops_the_command(controller, clock): def _old_calibration_settings(tmp_path, **kwargs): - """Settings loading the wire fixture, made 2026-09-10, by path.""" + """Settings loading the wire fixture, made 2026-09-10, by path: stations + 2 and 8.""" from dotbot.tests.lh2_wire_fixture import FIXTURE_TOML path = tmp_path / "calibration.toml" @@ -1648,7 +1649,7 @@ def test_robots_holding_stations_the_calibration_does_not_solve_are_warned_once( _old_calibration_settings(tmp_path, lh2_calibration_max_age_days=0) ) solved = sorted(station.index for station in controller.lh2_calibration) - first, second = sorted(set(range(8)) - set(solved))[:2] + first, second = sorted(set(range(16)) - set(solved))[-2:] held = sum(1 << index for index in solved) with capture_logs() as logs: for bot in (BOT, BOT + 1, BOT + 2): diff --git a/dotbot/tests/test_device.py b/dotbot/tests/test_device.py index 769d0903..3931e5f3 100644 --- a/dotbot/tests/test_device.py +++ b/dotbot/tests/test_device.py @@ -532,13 +532,13 @@ def test_the_sandbox_page_without_a_calibration_is_erased_past_the_net_id(): from dotbot.firmware.flash import swarmit_config_page page = swarmit_config_page(0x1234) - assert len(page) == 632 - assert page[:12] == bytes.fromhex("4f525357" "01000000" "34120000") - assert page[12:] == b"\xff" * 620 + assert len(page) == 872 + assert page[:12] == bytes.fromhex("50525357" "01000000" "34120000") + assert page[12:] == b"\xff" * 860 def test_the_sandbox_page_with_a_calibration_is_pinned(tmp_path): - """swarmit_config_t, 632 bytes, for the fixture file shared with swarmit.""" + """swarmit_config_t, 872 bytes, for the fixture file shared with swarmit.""" import hashlib from dotbot.firmware.flash import load_calibration_file, swarmit_config_page @@ -548,22 +548,24 @@ def test_the_sandbox_page_with_a_calibration_is_pinned(tmp_path): path.write_text(FIXTURE_TOML, encoding="utf-8") page = swarmit_config_page(0x1234, load_calibration_file(path)) - assert len(page) == 632 - assert page[:64].hex() == ( - "4f525357010000003412000002000000" - "cd6cbe44cdcc18c2cd2c7d449a992742" - "9a79bf443313774488855a3e7c61b2bd" - "0000803f0008b9c40000484100603845" - ) + assert len(page) == 872 + assert page[:16].hex() == "50525357010000003412000004010000" assert hashlib.sha256(page).hexdigest() == ( - "b1d492d3b8021d16976e5f49a984374655fada7df62e13b35bf9e4e351d6e530" + "c96414cbab5c61fc2bd78d0559dad586d2270c57ea87f10054c1e79f62d1b791" ) - # Station 1 in slot 1, the fourteen unused slots erased, then the site - # fields exactly as the calibration message carries them. + # Stations 2 and 8 in their slots, every other slot erased; each + # station's rectangle in its slot; then the site name and id exactly as + # the calibration message carries them. message = [bytes.fromhex(h) for h in MESSAGE_HEX] - assert page[16 + 36 : 16 + 72] == message[1][8:44] - assert page[88:592] == b"\xff" * 504 - assert page[592:632] == message[0][44:84] + matrix = [page[16 + 36 * i : 16 + 36 * (i + 1)] for i in range(16)] + rect = [page[592 + 16 * i : 592 + 16 * (i + 1)] for i in range(16)] + for i in range(16): + if i in (2, 8): + m = message[(2, 8).index(i)] + assert matrix[i] == m[8:44] and rect[i] == m[44:60] + else: + assert matrix[i] == b"\xff" * 36 and rect[i] == b"\xff" * 16 + assert page[848:872] == message[0][60:84] def test_the_sandbox_page_has_a_slot_per_station_the_calibration_allows(): @@ -598,9 +600,9 @@ def test_the_manifest_cache_misses_on_the_old_magic(tmp_path): from dotbot.firmware.flash import build_manifest_payload, manifest_matches payload = build_manifest_payload(tmp_path / "c.hex", "dotbot-v3", "local", "1234") - assert payload["magic"] == "0x5753524F" + assert payload["magic"] == "0x57535250" assert manifest_matches(payload, "dotbot-v3", "local", "1234") - for old in ("0x5753524E", "0x5753524D"): + for old in ("0x5753524F", "0x5753524E", "0x5753524D"): payload["magic"] = old assert not manifest_matches(payload, "dotbot-v3", "local", "1234") gateway = build_manifest_payload(tmp_path / "g.hex", "gateway", "local", "1234") diff --git a/dotbot/tests/test_dotbot_simulator.py b/dotbot/tests/test_dotbot_simulator.py index e35f6a55..e93f243f 100644 --- a/dotbot/tests/test_dotbot_simulator.py +++ b/dotbot/tests/test_dotbot_simulator.py @@ -562,7 +562,8 @@ def test_a_robot_given_a_heading_starts_tracking_it_where_it_was_put(tmp_path): @pytest.mark.parametrize( - "calibrated, expected", [(None, [0xFF, 0x05]), (0b11, [0b11, 0x05])] + "calibrated, expected", + [(None, [0xFFFF, 0x05]), (0b11, [0b11, 0x05]), (0x0104, [0x0104, 0x05])], ) def test_a_robot_holds_the_controllers_stations_unless_its_file_says( calibrated, expected diff --git a/dotbot/tests/test_protocol.py b/dotbot/tests/test_protocol.py index 0d9ca5de..47e4b810 100644 --- a/dotbot/tests/test_protocol.py +++ b/dotbot/tests/test_protocol.py @@ -981,7 +981,7 @@ def test_advertisement_waypoint_report(): report=True, ) data = advert.to_bytes() - assert len(data) == 41 + assert len(data) == 42 parsed = PayloadDotBotAdvertisement().from_bytes(data) assert parsed.has_report assert (parsed.waypoint_idx, parsed.waypoints_status, parsed.waypoints_reason) == ( @@ -993,7 +993,7 @@ def test_advertisement_waypoint_report(): assert (parsed.axle_x, parsed.axle_y) == (1234, 567) legacy = PayloadDotBotAdvertisement(direction=-90, waypoint_idx=1).to_bytes() - assert len(legacy) == 33 + assert len(legacy) == 34 parsed = PayloadDotBotAdvertisement().from_bytes(legacy) assert not parsed.has_report assert parsed.waypoint_idx == 1 and parsed.batch_id == 0 diff --git a/dotbot/tests/test_sim_core.py b/dotbot/tests/test_sim_core.py index 31319399..f89d4790 100644 --- a/dotbot/tests/test_sim_core.py +++ b/dotbot/tests/test_sim_core.py @@ -179,10 +179,10 @@ def test_the_advertisement_starts_the_encoder_deltas_over(): second = core.advertisement(0, 2900) assert len(first) == control.ADVERTISEMENT_BYTES # Type, calibration, direction, x, y, battery, duties, mode, then the deltas - assert int.from_bytes(first[17:21], "little", signed=True) == 7 - assert int.from_bytes(first[21:25], "little", signed=True) == -3 - assert int.from_bytes(second[17:25], "little") == 0 - assert int.from_bytes(first[12:14], "little") == 2900 + assert int.from_bytes(first[18:22], "little", signed=True) == 7 + assert int.from_bytes(first[22:26], "little", signed=True) == -3 + assert int.from_bytes(second[18:26], "little") == 0 + assert int.from_bytes(first[13:15], "little") == 2900 def test_a_seeded_robot_tracks_the_pose_it_is_given(): @@ -206,7 +206,10 @@ def test_the_batched_advertisements_are_those_the_step_asked_for(): indices, packets = core.advertisements(np.array([3000, 3001, 3002, 3003])) assert indices.tolist() == [1, 3] assert packets.shape == (2, control.ADVERTISEMENT_BYTES) - assert [int.from_bytes(p[12:14], "little") for p in packets] == [3001, 3003] - assert [int.from_bytes(p[17:21], "little") for p in packets] == [11, 13] + assert [int.from_bytes(p[13:15], "little") for p in packets] == [3001, 3003] + assert [int.from_bytes(p[18:22], "little") for p in packets] == [11, 13] empty_indices, empty_packets = core.advertisements(np.zeros(4)) - assert empty_indices.size == 0 and empty_packets.shape == (0, 42) + assert empty_indices.size == 0 and empty_packets.shape == ( + 0, + control.ADVERTISEMENT_BYTES, + )