class
Board
A simulated board, and your session on it.
from hardlabs_sdk import BoardGet one from Client.start (booted) or Client.board (not yet booted). A board is a context manager: leaving the with block calls stop.
Parts are attributes by reference designator: board.U3 is board.part("U3").
Waits count simulated time, so a slow network or a busy server doesn't change a result.
Usage
from hardlabs_sdk import Client
with Client().start("hardlabs/demo-board") as board:
board.wait_for("temp")
board.do("temperature-sensor-mcp9808.overheat-75-c")
board.wait_for("ALARM", within=5)Attributes
idstr- The simulation's id.
namestr- The simulation's name:
owner/project@version.
Board.startmethod
Boots the board afresh, restarting it if it is running.
board.start(firmware=None, *, keep_memory=False) -> BoardCalled without firmware, it boots the firmware it booted last, or the board's own if none has been given.
The firmware is one ELF, or the images a flash holds. A raw .bin needs the address it is flashed at ("app.bin@0x08010000"). ELF and Intel HEX files carry their own addresses. The processor boots from the image at the lowest address, as it would from flash. An image can also name a memory chip ("config.bin@U7", or "config.bin@U7:0x100" for an offset in it).
A fresh start gives every memory chip the images you named and leaves the rest blank, so the same images start every run from the same state. keep_memory carries the running board's memory over instead, as restart does.
Serial ports opened with serial stay open, at the same address, across a restart.
Usage
board.start("build/app.elf")
board.start(["bootloader.bin@0x08000000", "app.bin@0x08010000", "config.bin@U7"])Arguments
firmwarestr | Path | Image | list | Nonedefault None- A path, an
"image@address"or"image@U7"string, anImage, or a list of them. keep_memorybooldefault False- Keep what the memory chips hold instead of starting them from the images.
Returns
Board — The board itself.
Raises
- FileNotFoundError — A firmware file doesn't exist.
- ValueError — A raw image has no address, or its address isn't a number.
- HardLabsError — The server couldn't boot the board.
Board.restartmethod
Power-cycles the board: the firmware boots again, and the memory chips keep what they hold.
board.restart(firmware=None) -> BoardCalled without firmware, it boots the same firmware again. Pass new firmware to boot that instead; images for memory chips are left out, since the chips keep what they hold. Serial ports you have open keep their addresses.
Usage
board.restart()
board.restart("build/app-v2.elf")Arguments
Returns
Board — The board itself.
Raises
- HardLabsError — The server couldn't boot the board.
Board.stopmethod
Ends the session and closes its serial ports.
board.stop(*, save_memory=None)Leaving a with block calls it. Calling it again does nothing. What the memory chips hold goes with the session unless you save it first.
Usage
board.stop(save_memory="nvm/")Arguments
save_memoryUnion[str, Path, None]default None- A folder to save every memory chip into first, as
save_memorydoes.
Board.memory_loadedproperty
What the last start put into the memory chips.
board.memory_loaded -> list[dict]A list of dicts, one per image: memory, offset, bytes and name.
Board.memoriesmethod
Lists the board's memory chips: what memory and file@U7 images name.
board.memories() -> list[Memory]Returns
list[Memory] — Every memory chip, with its size.
Board.memorymethod
Returns a memory chip by reference designator or slot, in any case.
board.memory(name) -> MemoryUsage
board.memory("U7").read(0, 16)Arguments
namestr- E.g.
"U7".
Returns
Memory — The chip.
Raises
- KeyError — The board has no such memory chip. The message lists the ones it has.
Board.save_memorymethod
Saves every memory chip's contents into a folder.
board.save_memory(directory) -> list[Path]Each chip goes into <name>.bin, and a memory.json says which is which: a record of the run, and what memory_images starts the next board from.
Usage
board.save_memory("nvm/")
board.start(["app.elf", *memory_images("nvm/")])Arguments
directoryUnion[str, Path]- The folder. It is made if needed.
Returns
list[Path] — The files written, one per chip.
Board.statusmethod
Returns the session's state as the API reports it.
board.status() -> dictReturns
dict — The raw state: whether it is running, its serial ports, its firmware.
Board.firmwareproperty
What the session runs, or None before start.
board.firmware -> dict | NoneA dict with the firmware's name and sha256 and, for images, images: where each one was loaded.
Board.clockmethod
Returns the simulated time since the board booted.
board.clock() -> floatReturns
float — Simulated seconds.
Board.waitmethod
Lets simulated time pass.
board.wait(seconds) -> floatArguments
secondsfloat- Simulated seconds to wait.
Returns
float — The clock afterwards, as clock returns it.
Raises
- SimulationStopped — Simulated time stopped advancing.
Board.sendmethod
Types one line into the firmware's console, as if Enter were pressed after it.
board.send(line, *, console=None)The line can be up to 512 characters. Output printed before the call can't match a later wait_for, so a wait after send finds the reply.
Usage
board.send("status")
board.wait_for("mode: SAFE")Arguments
linestr- The line to type.
consolestr | Nonedefault None- The console's name, when the board has more than one.
Board.linesmethod
Returns the console's lines so far.
board.lines(*, console=None) -> list[Line]Arguments
consolestr | Nonedefault None- The console's name, when the board has more than one.
Returns
list[Line] — Up to the last 2000 lines.
Board.wait_formethod
Waits for a console line that contains text, and returns it.
board.wait_for(text, *, within=30.0, console=None) -> LineThe match uses up that line and every line before it, so a second wait_for with the same text finds a later line.
Usage
board.do("reset.press")
board.wait_for("app ready", within=5)Arguments
textstr- The text to find. It matches anywhere in a line.
withinfloatdefault 30.0- Simulated seconds to wait.
consolestr | Nonedefault None- The console's name, when the board has more than one.
Returns
Line — The line that matched.
Raises
- ConsoleTimeout — No line matched in time. The message shows what the console printed instead.
- SimulationStopped — Simulated time stopped advancing.
Board.expect_consolemethod
Waits for several texts to appear in order, each on a later line.
board.expect_console(*texts, within=30.0, console=None) -> list[Line]The time allowed covers all of them together.
Usage
board.send("status")
board.expect_console("mode: SAFE", "faults: 0")Arguments
*textsstr- The texts to find, in order.
withinfloatdefault 30.0- Simulated seconds to wait for them all.
consolestr | Nonedefault None- The console's name, when the board has more than one.
Returns
list[Line] — The line each text matched.
Raises
- ConsoleTimeout — A text didn't appear in time.
- SimulationStopped — Simulated time stopped advancing.
Board.controlsmethod
Lists everything you can do to the board.
board.controls() -> list[Control]The names are the same in the SDK, the CLI and scenario files.
Usage
for c in board.controls():
print(c.usage)Returns
list[Control] — The board's controls.
Board.domethod
Works one of the board's controls.
board.do(control, value=None, **values) -> strControls are named <panel>.<control>. The part after the dot is enough when only one panel has it. A part's controls also answer to its reference designator: U3.set-ambient-temperature.
Usage
board.do("reset.press")
board.do("usb.plugged-in", True)
board.do("battery.level", 30)
board.do("bench-supply.set", volts=5.0)
board.do("U3.set-ambient-temperature", 30)Arguments
controlstr- The control's name.
controlslists them. valueAnydefault NoneTrue/Falsefor atoggle, a number for anumberorslider. Leave it out for abuttonorpreset.**valuesfloat- The named values for a
form,vectororgenerator.
Returns
str — The control's output, if it has any.
Raises
- HardLabsError — The board has no such control, or the value is out of range.
Board.readingsmethod
Returns the values the board's panels show now.
board.readings(*names) -> dict[str, Reading]Arguments
*namesstr- The readings to return. Every reading when none are given.
Returns
dict[str, Reading] — The readings, by name.
Board.readingmethod
Returns one reading, with its unit and label. read returns only its value.
board.reading(name) -> ReadingArguments
namestr- The reading's name,
<panel>.<reading>.
Returns
Reading — The reading.
Raises
- HardLabsError — The board has no such reading.
Board.readmethod
Returns the value of one reading.
board.read(name) -> AnyUsage
assert board.read("power-monitor.bus-current") < 0.2Arguments
namestr- The reading's name,
<panel>.<reading>or<ref>.<reading>.
Returns
Any — A number, True/False, a name, or None when the board doesn't report it.
Raises
- HardLabsError — The board has no such reading.
Board.partsmethod
Lists the board's parts, optionally filtered.
board.parts(*, bus=None, address=None, connected_to=None, status=None) -> list[Part]Passives are left out. Parts the simulation doesn't model are listed too, so the list shows what is left out. Filters combine.
Usage
board.parts(bus="i2c0")
board.parts(connected_to="U1", status="not_simulated")Arguments
busstr | Nonedefault None- Only parts on this bus, as the board names it:
"i2c0". addressint | str | Nonedefault None- Only parts at this I2C address:
0x6Bor"0x6B". connected_tostr | Part | Nonedefault None- Only parts wired to this part (a reference designator or a
Part). For the processor, this includes the parts on its buses. statusstr | Nonedefault None- Only parts simulated this far:
simulated,partial,folded,not_simulatedornot_fitted.
Returns
list[Part] — The parts that match.
Raises
- ValueError —
addressisn't a number.
Board.findmethod
Returns the one part that matches the filters.
board.find(**filters) -> PartUsage
imu = board.find(bus="i2c0", address=0x6B)Arguments
**filtersAny- The same filters as
parts.
Returns
Part — The part.
Raises
- LookupError — No part matches, or more than one does.
Board.mcuproperty
The board's processor, as a Part.
board.mcu -> Partboard.mcu.connected() lists every part it reaches. Raises LookupError when the parts list doesn't name the processor.
Board.partmethod
Returns one part by its reference designator. board.U3 is the same.
board.part(ref) -> PartArguments
refstr- The reference designator, in any case:
"U3"or"u3".
Returns
Part — The part.
Raises
- KeyError — The board has no such part. The message lists the parts it has.
Board.problemsmethod
Lists what has gone wrong in the run so far.
board.problems() -> list[dict]That includes the firmware's crashes, failed asserts and unexpected restarts, and behaviour the simulation may not cover. Check these before deciding a failure is the firmware's.
Returns
list[dict] — Each with source, title and, when there is one, detail.
Board.serial_portsmethod
Lists the serial ports serial can open.
board.serial_ports() -> list[str]Returns
list[str] — console, the board's own ports (rs485), and usb:<controller> ports.
Board.serialmethod
Opens one of the board's serial ports on this computer.
board.serial(port='console', *, pty=False) -> SerialBridgeYour own test framework opens the port by its address, like a real one. Bytes pass through unchanged in both directions. The address stays the same when the board restarts.
Usage
import serial
bus = board.serial("rs485")
port = serial.serial_for_url(bus.url, baudrate=115200, timeout=5)
port.write(frame)Arguments
portstrdefault 'console'- The port's name.
serial_portslists them. ptybooldefault False- Open a pseudo-terminal (a device file) instead of a
socket://address. macOS and Linux only.
Returns
SerialBridge — The open port. Its url is what pyserial opens.
Raises
- HardLabsError — The board has no such port, or
ptywas asked for on Windows.
Board.run_unit_testsmethod
Collects the results of a test binary running on the board.
board.run_unit_tests(*, within=300.0, done=None, console=None) -> UnitTestReportReads Unity or GoogleTest output from the console until the framework's summary line appears, or done does. A run that doesn't finish in time returns a report with finished set to False rather than raising.
Usage
board = Client().start("company_name/board_name", "build/tests.elf")
report = board.run_unit_tests(within=120)
assert report.ok, report.summary()Arguments
withinfloatdefault 300.0- Simulated seconds to wait for the run to finish.
donestr | Nonedefault None- A line that marks the end of the run, for output with no summary line.
consolestr | Nonedefault None- The console's name, when the board has more than one.
Returns
UnitTestReport — The results.