Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/embeddedci-mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog — `embeddedci-mcp`

## 2.13.12

- `sensor_capture` waits for the DUT's next I2C transaction (up to 2 s) and captures it from its
START, so a DUT that reads its sensor now and then is caught; a quiet bus is a trigger timeout
error. Needs pod firmware with the `sensor_la_trigger` capability; older firmware captures at
once, as before.
- Requires `embeddedci` 2.13.12.

## 2.13.11

- Sensor tools for user-defined sensors (pod firmware with the `sensors` capability) replace the
Expand Down
2 changes: 1 addition & 1 deletion packages/embeddedci-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ version the SDK pins (0.1.0, bundled) unless `BENCHPOD_SENSOR_LIBRARY`, the CLI'
| `sensor_stop` | Stop one sensor, or all; the pod releases the pins and forgets the definition; returns the names it stopped. The emulation image stays loaded. | `instance` (default: all) |
| `sensor_status` | Running sensors (slot, pins, values, faults with `left_s`, bus counters such as `writes`; `transfers` is always null), the stimulus and the FPGA image, every page in one reply. | none |
| `sensor_regs` | The bytes an I2C/SPI sensor answers now. | **`instance`**, `start=0` (0..255), `length` (default: to the end of the 256-byte image) |
| `sensor_capture` | Capture an I2C sensor's bus and decode it into a transaction trace (I2C only; `capture_la` for SPI and UART sensors). | **`instance`**, `samples=4096` (1..4096 packed bytes), `address`, `register` |
| `sensor_capture` | Wait for the DUT's next transaction on an I2C sensor's bus (up to 2 s; firmware with `sensor_la_trigger`, older firmware captures at once), capture it and decode it into a transaction trace (I2C only; `capture_la` for SPI and UART sensors). | **`instance`**, `samples=4096` (1..4096 packed bytes), `address`, `register` |

### Pull resistors

Expand Down
4 changes: 2 additions & 2 deletions packages/embeddedci-mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "embeddedci-mcp"
version = "2.13.11"
version = "2.13.12"
description = "MCP server that lets AI agents drive an EmbeddedCI BenchPod hardware-in-the-loop bench"
readme = "README.md"
requires-python = ">=3.10"
Expand All @@ -26,7 +26,7 @@ classifiers = [
dependencies = [
# cloud: the embeddedci:<device> destination; discovery: connect("discover") over mDNS;
# analysis: dominant frequency in capture summaries; yaml: sensor definitions an agent writes.
"embeddedci[cloud,discovery,analysis,yaml]>=2.13.10,<3",
"embeddedci[cloud,discovery,analysis,yaml]>=2.13.12,<3",
"mcp>=1.12,<2",
"anyio>=4",
"pydantic>=2.7",
Expand Down
4 changes: 2 additions & 2 deletions packages/embeddedci-mcp/server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "io.github.embeddedci-com/embeddedci-mcp",
"title": "EmbeddedCI BenchPod",
"description": "Drive a BenchPod hardware-in-the-loop bench: power, flash, UART, I2C, CAN, analog and logic.",
"version": "2.13.11",
"version": "2.13.12",
"websiteUrl": "https://www.embeddedci.com/docs/benchpod-mcp",
"repository": {
"url": "https://github.com/embeddedci-com/embeddedci-python",
Expand All @@ -15,7 +15,7 @@
"registryType": "pypi",
"registryBaseUrl": "https://pypi.org",
"identifier": "embeddedci-mcp",
"version": "2.13.11",
"version": "2.13.12",
"runtimeHint": "uvx",
"transport": {
"type": "stdio"
Expand Down
2 changes: 1 addition & 1 deletion packages/embeddedci-mcp/src/embeddedci_mcp/guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- Flash and boot: flash(file) then power_cycle_and_capture(until_regex) — with a wiring profile that is all; otherwise pass swclk, swdio, target, nreset and rx, tx.
- SPI flash (a 25-series chip on four LA pins, wiring spi_sclk/spi_mosi/spi_miso/spi_cs): spi_flash_info to see it answers, then spi_flash_program(file, addr); hold_reset=true keeps the DUT off the bus. spi_transfer talks to any other SPI device.
- Interactive console: uart_open, power_on, uart_read(until_regex), uart_write, uart_close.
- Emulate sensors (I2C, SPI, UART, analog; firmware with the 'sensors' capability): the pod plays a sensor from a definition. sensor_library lists ready ones (bme280, bmp280, sht4x, mpu6050, gps_nmea, ...) and sensor_show gives one's YAML and params. For I2C, set_pull([sda, scl], true) on LA1-LA6 (LA7/LA8 pull DOWN) first. sensor_start(definition=<name or YAML>, pins={sda, scl} | {tx} | ...), then sensor_set(instance, values) changes readings, sensor_stimulus_load + sensor_stimulus_start play values over time, sensor_fault injects a fault, sensor_status shows bus activity, sensor_capture decodes an I2C sensor's bus, sensor_stop ends it. A part the library lacks: read sensor_format, write a definition from the datasheet, check it with sensor_validate, then sensor_start it. A GPS module is gps_nmea on pins={tx: <LA wired to the DUT RX>}.
- Emulate sensors (I2C, SPI, UART, analog; firmware with the 'sensors' capability): the pod plays a sensor from a definition. sensor_library lists ready ones (bme280, bmp280, sht4x, mpu6050, gps_nmea, ...) and sensor_show gives one's YAML and params. For I2C, set_pull([sda, scl], true) on LA1-LA6 (LA7/LA8 pull DOWN) first. sensor_start(definition=<name or YAML>, pins={sda, scl} | {tx} | ...), then sensor_set(instance, values) changes readings, sensor_stimulus_load + sensor_stimulus_start play values over time, sensor_fault injects a fault, sensor_status shows bus activity, sensor_capture waits for the DUT's next transaction on an I2C sensor's bus and decodes it, sensor_stop ends it. A part the library lacks: read sensor_format, write a definition from the datasheet, check it with sensor_validate, then sensor_start it. A GPS module is gps_nmea on pins={tx: <LA wired to the DUT RX>}.
- Analog: dac_output (DC), generate (sine/square/sawtooth) and replay drive the DAC and route its path; adc_read gives one calibrated value, capture_adc a waveform summary; dac_stop ends any DAC output.
- 4-20 mA input: adc_read(source='current_in') reads terminal J8 and returns current in amps (0.004 to 0.020 for a live loop) next to the voltage across the 249 ohm sense resistor. calibration shows whether the pod has calibrated its ADC offset there. If not, analog readings can be off by up to about 50 mV: ask the user to disconnect J8, then calibrate (once per pod, stored on the pod; on firmware 3.9.3+ it corrects every ADC source).
- 4-20 mA output: current_out(current) holds a loop current on terminal J9, in amps (0.012 is 12 mA), and returns the current actually held and the range. It needs an external floating loop supply on J9 and cannot go below about 4 mA or above about 20 mA. It shares the DAC with the voltage outputs and switches them off; dac_stop does not return the loop to 4 mA, current_out(0.004) does. The loop must float: nothing in it may touch pod ground, also not through a target that shares a ground with the pod (SWD, UART). Ask the user how J9 is wired first, and never suggest wiring J9 straight into J8. generate, replay and replay_waveform take dac_path='current_out' to play a waveform as a current (levels in amps); capture_adc(source='current_in') summarises the loop current in amps.
Expand Down
2 changes: 1 addition & 1 deletion packages/embeddedci-mcp/src/embeddedci_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,7 +1126,7 @@ async def sensor_capture(
address: Annotated[Optional[int], Field(description="Also report whether the DUT addressed this device.")] = None,
register: Annotated[Optional[int], Field(description="With address: the bytes the DUT read from this register.")] = None,
) -> m.I2cCaptureResult:
"""Capture an I2C sensor's bus pins and decode them into a transaction trace (S/Sr/P, addr, data, ACK). I2C only: capture an SPI or UART sensor's pins with capture_la."""
"""Wait for the DUT's next transaction on an I2C sensor's bus (up to 2 s), capture it from its START and decode it into a transaction trace (S/Sr/P, addr, data, ACK). A quiet bus is a trigger timeout error. Firmware without the sensor_la_trigger capability captures at once instead. I2C only: capture an SPI or UART sensor's pins with capture_la."""
def op() -> m.I2cCaptureResult:
txns = _emulation().i2c_transactions(instance, samples)
addrs = sorted({msg.address for t in txns for msg in t.messages if msg.address is not None})
Expand Down