Python API Reference¶
The public methods and properties of beebo.api and beebo.trials, generated from the classes themselves. For an introduction and examples see Python API. SyncNode has the same operations as Node, blocking instead of async.
beebo.api.Node¶
A single device connection, resolved explicitly -- never from sys.argv[0]/the currently-invoked CLI alias.
await advert(self, flood: bool = False) -> None: Send a direct (default) or flood advert. Raises BeeboDeviceError on a device-level ERROR reply.await battery(self) -> dict: Battery voltage/state/presence as plain data -- see node.py's_fetch_battery_datafor the returned keys. Raises BeeboDeviceError on a device-level failure.await channel_get(self, channel) -> dict: One channel by slot index or name -- see channels.py.await channel_remove(self, channel) -> int: Empty a channel's slot (channels.json keeps the entry); returns the slot index.await channel_send(self, channel, text: str, *, max_hops: int | None = None) -> int: Send a flood group message;max_hops(1-21, None or 0 = no cap) limits how many repeaters forward it. Returns the slot index.await channel_set(self, idx: int, name: str, secret: str | None = None, *, random: bool = False) -> dict: Write slotidxand record it in <root>/mesh/channels.json.secretis 32 hex digits;randomgenerates one; a '#name' channel derives its own (give neither). Returns {"idx", "name", "secret"}. Raises BeeboError on a bad or conflicting secret.await channels_list(self) -> list: The node's configured channels as [{"idx", "name", "secret" (hex)}] -- see channels.py'slist_channels. Companion role only.await channels_pull(self) -> list: Record the node's channels into channels.json; returns the names.await channels_push(self) -> dict: Write channels.json to the node; returns {name: slot index}.await connect(self, *, node: str | None = None, ble: str | None = None, tcp: str | None = None, usb: str | None = None, raw: bool | None = None) -> 'Node': Open the connection, optionally choosing the target here.await disconnect(self) -> None: Explicitly close the connection -- thedisconnect()counterpart toconnect(). A no-op if not connected (mirrorsrelease_cmd's tolerance of "nothing to release").await download_messages(self, *, limit: int = 0) -> list: Drain every pending message off the node -- see messages.py'sdrain_messagesfor the returned row shape.limit(0 = whole backlog) stops early after that many messages.await get(self, path: str) -> str: Value of the setting at a dotted path, e.g.await node.get("repeater.fem.rxgain")-> "on". Raises BeeboError for an unknown path or a group.lookup_contact(self, token: str | None) -> tuple: (contact, matches) fortokenagainst the local contacts.json cache -- see contacts.py'slookup_contact. Synchronous: reads only the local cache file, no device round trip.mc(property): The underlyingmeshcore.MeshCore(companion) orbeebo.common_link.CommonLink(repeater/raw text) connection -- pass this to any command module's_foo(mc, ...)helper.await monitor_clear(self) -> None: Clear the monitoring ring. Raises BeeboDeviceError on failure.await monitor_pull(self, incremental: bool = True, clear: bool = False, to: str | None = None) -> list: Pull the monitoring ring into this node's local dataset (whatmonitor pulldoes) and return every monring_<date>.bin file there, oldest first -- hand them toTrial.from_files(). incremental=False re-fetches everything; clear=True empties the ring afterwards. to=NAME appends to NAME.bin instead (monitor pull --to) and returns just that file.await monitor_stats(self) -> dict | None: Raw MonRing ring header -- occupancy, capture state, start/end/ per-kind record counts -- see monitor.py's_get_header/_get_header_repeaterfor the returned dict's keys (they differ slightly between companion/multi_role and repeater). Returns None if the ring itself isn't available (older firmware).await monitor_status(self) -> dict | None: Health/service counters as plain data -- see monitor.py'sget_monitor_status_datafor the returned dict's shape. Returns None if the ring itself isn't available.name(property): The live device'sboard.node.name, from the connect handshake.await reboot(self) -> None: Reboot the device and sync its clock. No reply either way (the device restarts immediately) -- returns once the command is sent, not once the device is back up.await refresh_contacts(self) -> dict: Re-query the node's contact list and merge into the local contacts.json cache; returns the merged cache dict -- see contacts.py'srefresh_cache. Already plain data-in/data-out, no Rich/console output, so no error-path conversion was needed here.await region_define_tree(self, segments: str) -> str | None: Bulk-define a region subtree in one round trip -- see region.py'sdefine_region_treefor the segment syntax.await region_put(self, name: str, parent: str = '') -> None: Define a region, or re-parent it if it already exists.await region_remove(self, name: str) -> None: Remove a region -- must have no child regions.await region_save(self) -> None: Explicitly persist the region tree to flash.await region_set_flood(self, name: str, allow: bool) -> None: Allow (allow=True) or deny (allow=False) flood traffic tagged with this region.await region_tree(self) -> tuple: (raw export text, default_scope) for the repeater's named- region registry -- see region.py'sget_region_tree_data.role(property): 'companion' or 'repeater', resolved the same way connect.py's own identity check does -- None if the connection is a CommonLink (a CommonLink only ever reaches the live repeater role, but it doesn't carry adv_type to derive that from generically) or the adv_type is unrecognized.await send_message(self, target: str, text: str, *, flood: bool = False, direct: bool = False, force: bool = False) -> dict: Send a text message to a contact and wait for delivery confirmation -- see messages.py'ssend_messagefor the full flood/direct/force semantics and the returned dict's shape. Raises BeeboDeviceError on any failure.await set(self, path: str, value) -> str: Set the setting at a dotted path (valueis str()-converted, as typed on the CLI) and return the value read back. An invalid value raises BeeboError.await status(self) -> dict: Node/radio/resources status as plain data -- the same fieldsbeebo statusrenders, without building a Rich table. Returns {"node": {...}, "radio": {...}, "resources": {...}}; each section's keys are documented on its own_fetch_*_datahelper in node.py (they differ slightly between companion/multi_role and repeater, since the two protocols expose different raw fields). Raises BeeboDeviceError if the device's role can't be read.await sustain_start(self, channel, rate: int) -> int: Have the (companion) node inject on a channel to sustainrateRX packets a minute by itself; returns the slot -- see sustain.py.await sustain_status(self) -> dict: {channel (None = off), rate, sent} of the node's injector.await sustain_stop(self) -> None: Stop the node's traffic injector.await traffic_inject(self, channel, rate: float, *, size: int = 32, duration: float | None = None, count: int | None = None, max_hops: int | None = None, on_send=None) -> int: Sendrateflood group messages per minute on a channel untilcountsent,durationseconds elapsed, or the task is cancelled; returns the number sent -- see traffic.py'sinject.on_send(n, idx, text)is called after each send.transport(property): Which transport this connection uses: 'tcp', 'usb', or 'ble'.transport_selected(property): Whether a target has been resolved yet -- False for a bareNode()whose transport comes later, on connect().
beebo.api.SyncNode¶
Blocking counterpart to Node, for scripts that aren't async.
advert(self, flood: bool = False) -> None: Send a direct (default) or flood advert -- see Node.advert.battery(self) -> dict: Battery voltage/state/presence -- see Node.battery.channel_get(self, channel) -> dict: One channel by index or name -- see Node.channel_get.channel_remove(self, channel) -> int: Empty a channel slot -- see Node.channel_remove.channel_send(self, channel, text: str, *, max_hops: int | None = None) -> int: Send a flood group message -- see Node.channel_send.channel_set(self, idx: int, name: str, secret: str | None = None, *, random: bool = False) -> dict: Write a channel slot -- see Node.channel_set.channels_list(self) -> list: The node's channels -- see Node.channels_list.channels_pull(self) -> list: Record the node's channels into channels.json -- see Node.channels_pull.channels_push(self) -> dict: Write channels.json to the node -- see Node.channels_push.connect(self, *, node: str | None = None, ble: str | None = None, tcp: str | None = None, usb: str | None = None, raw: bool | None = None) -> 'SyncNode': Open the connection, optionally choosing the target here -- see Node.connect. Returns self soSyncNode().connect(tcp=...)reads cleanly. Raises RuntimeError if already connected.connected(property): Whether connect() has succeeded and disconnect() hasn't run.disconnect(self) -> None: Close the connection; a no-op if not connected. The shared background loop stays running for any other live SyncNode.download_messages(self, *, limit: int = 0) -> list: Drain pending messages -- see Node.download_messages.get(self, path: str) -> str: Value of one setting by dotted path -- see Node.get.lookup_contact(self, token: str | None) -> tuple: (contact, matches) from the local cache -- see Node.lookup_contact. Already synchronous on Node (no device round trip), forwarded as-is.mc(property): The underlying MeshCore/CommonLink connection -- see Node.mc.monitor_clear(self) -> None: Clear the monitoring ring -- see Node.monitor_clear.monitor_pull(self, incremental: bool = True, clear: bool = False, to: str | None = None) -> list: Pull the ring into the local dataset, return its files -- see Node.monitor_pull.monitor_stats(self) -> dict | None: Raw MonRing ring header -- see Node.monitor_stats.monitor_status(self) -> dict | None: Health/service counters -- see Node.monitor_status.name(property): The live device'sboard.node.name-- see Node.name.node(property): The wrapped asyncNode, for dropping back into async code.reboot(self) -> None: Reboot the device -- see Node.reboot.refresh_contacts(self) -> dict: Re-query and merge the contact cache -- see Node.refresh_contacts.region_define_tree(self, segments: str) -> str | None: Bulk-define a region subtree -- see Node.region_define_tree.region_put(self, name: str, parent: str = '') -> None: Define or re-parent a region -- see Node.region_put.region_remove(self, name: str) -> None: Remove a childless region -- see Node.region_remove.region_save(self) -> None: Persist the region tree to flash -- see Node.region_save.region_set_flood(self, name: str, allow: bool) -> None: Allow/deny flood traffic for a region -- see Node.region_set_flood.region_tree(self) -> tuple: (raw export text, default_scope) -- see Node.region_tree.role(property): 'companion' or 'repeater' -- see Node.role.send_message(self, target: str, text: str, *, flood: bool = False, direct: bool = False, force: bool = False) -> dict: Send a message and await delivery -- see Node.send_message.set(self, path: str, value) -> str: Set one setting by dotted path -- see Node.set.settings(property): The node's settings as attributes:node.settings.repeater.fem.rxgainreads,node.settings.repeater.fem.rxgain = "on"writes -- see Settings.status(self) -> dict: Node/radio/resources status as plain data -- see Node.status.sustain_start(self, channel, rate: int) -> int: Start the node's traffic injector -- see Node.sustain_start.sustain_status(self) -> dict: The node's injector state -- see Node.sustain_status.sustain_stop(self) -> None: Stop the node's traffic injector -- see Node.sustain_stop.traffic_inject(self, channel, rate: float, *, size: int = 32, duration: float | None = None, count: int | None = None, max_hops: int | None = None, on_send=None) -> int: Constant-rate flood group messages, blocking until done -- see Node.traffic_inject.transport(property): 'tcp', 'usb', or 'ble' -- see Node.transport.transport_selected(property): Whether a target has been resolved yet -- see Node.transport_selected.
beebo.trials.Trial¶
One trial run: its blocks, paired statistics and the node's result.
alternative(property): Value compared against it (side B), None when unknown.blocks(property)change(property): Mean paired score difference B - A, in ln units (e^x - 1 is the relative gain).confidence(property): Two-sided t confidence (percent) thatchangeis not zero.finished(property): The node closed the trial (its MON_TRIAL end record exists).from_files(files, score: str | None = None) -> List[Trial]: Every trial in these monring_<date>.bin files, oldest first.score("received=10") re-scores the blocks with those weights, asanalyze tuning trial --scoredoes.latest(files, score: str | None = None) -> Trial | None: The newest trial in these files, None when there is none.original(property): Value before the trial (side A), None when the start record is missing.outcome(property): The node's result: keep_a, adopt_b, inconclusive, aborted_*; None while running.pairs(property): Matched A/B pairs behind the statistics.parts(property): A combined trial's participants as (setting, A value, B value); [] for a single switch.report(self, blocks: bool = False) -> str: Theanalyze tuning trialreport as text; blocks=True adds every block.se(property): Standard error ofchange.setting(property): The setting the trial switches, e.g. 'fem.rxgain'.started(property): Start time, epoch seconds.verdict(property): The offline replay's decision from the recorded blocks.