- 增加出光状态轮询、收尾重开、启动阶段取消和报警后关闭保持场景 - 为送丝使能记录真实PF2累计边沿,捕捉快照之间的短暂重新开启 - 按LWS读取方式核对数字焊枪参数、报警全局位及外设断电恢复 - 在拒绝错误从站响应前消费完整已知帧,避免残余CRC污染恢复查询 - 为依赖写入产物的重启阶段增加清单校验和未执行状态 - 补充测试工具反例自检、运行说明和协议测试记录
26 KiB
Board WebSocket
The server runs inside the Renode process. No Web app or Bun backend is required.
- URL:
ws://127.0.0.1:12322/board - Configuration: root
.env→H723_WS_PORT, orjust run --ws-port <port> - Transport: standard WebSocket, UTF-8 JSON text messages
- Scope: local connections; indicator, six-axis waveform and general-output subscriptions, plus eight general-purpose digital inputs
Subscribe
Send:
{"type":"subscribe","topic":"board.leds"}
The first reply is a complete snapshot:
{"type":"snapshot","topic":"board.leds","sequence":0,"virtualTimeUs":0,"data":{"power":true,"run":false,"fault":null}}
Subsequent changed messages contain the complete state after each LED transition. reset messages mark machine resets. Sequence numbers increase across changes and resets within one server instance. A new instance starts a new sequence. virtualTimeUs is simulation time in microseconds, which stops while the emulation is paused.
| Field | Source | Appearance |
|---|---|---|
power |
Present simulation instance | Green; remains on while the CPU is paused |
run |
Renode LED model wired to firmware PB5 | Green; follows GPIO changes without a Web timer |
fault |
Not mapped yet; always null |
Red lens remains off; null does not assert the absence of a fault |
PB5 currently uses an active-high LED model. The observed Factory firmware toggles it approximately every 200 ms of simulation time; the Upgrade firmware toggles it approximately every 510 ms. Physical LED polarity has not been verified against the schematic.
Connections are initially unsubscribed. Reconnect and subscribe again to obtain a fresh snapshot. No historical events are replayed. The Web client clears its displayed state when the connection closes.
Other requests
{"type":"snapshot","topic":"board.leds"}
Returns the current snapshot without changing the subscription.
{"type":"unsubscribe","topic":"board.leds"}
Stops future change events and returns {"type":"unsubscribed","topic":"board.leds"}. Already queued events precede this acknowledgement.
Malformed JSON returns {"type":"error","code":"invalid_json"}. Unsupported requests or topics return {"type":"error","code":"unsupported_request"}. Only the eight mapped general inputs accept level changes through board.inputs; arbitrary GPIO writes are not exposed. Requests must fit in 4096 bytes. A slow client exceeding its bounded outgoing queue is disconnected; firmware execution never waits for network delivery.
Independent client
Run this JavaScript with Bun, or use any standard WebSocket client:
const socket = new WebSocket('ws://127.0.0.1:12322/board')
socket.onopen = () => socket.send(JSON.stringify({ type: 'subscribe', topic: 'board.leds' }))
socket.onmessage = ({ data }) => console.log(JSON.parse(data))
Axis waveforms
Subscribe with {"type":"subscribe","topic":"board.waveform.x"}; replace the final letter with y, z, a, b, or c for another axis. A client may subscribe to multiple axes; each has independent sequence numbers, counters, and edge buffers. The first message is a snapshot; subsequent batch messages are emitted approximately every 50 ms of host time. No GPIO writes or movement commands are accepted by this subscription.
Sources are actual post-multiplexer GPIO outputs. The Factory ELF timer_startPluseOut, MX_TIM*_Init, and Axle_Start_Run establish this mapping:
| Axis | PCB output | Timer | Pulse GPIO | DIR GPIO |
|---|---|---|---|---|
| X | PWM1 | TIM15_CH1 | PA2 | PC1 |
| Y | PWM3 | TIM17_CH1 | PF7 | PD9 |
| Z | PWM5 | TIM3_CH1 | PB4 | PB3 |
| A | PWM2 | TIM1_CH1 | PE9 | PE10 |
| B | PWM4 | TIM8_CH1 | PC6 | PC7 |
| C | PWM6 | TIM4_CH1 | PD12 | PD8 |
Each edge is [timeNs, channel, level], where channel 0 is the selected axis pulse output and channel 1 is DIR. Timestamps come from the peripheral clock source, not the sending timer or browser rendering clock. DIR is reported as an electrical logic level; the UI does not infer positive/negative motion from it.
Every message includes:
epoch,sequence: reset generation and consecutive batch number; reset sends a new snapshot.nowNs,start,end: simulation time and the two channel levels before/after the batch.edges,dropped: bounded edge batch and count of edges omitted due to capacity. The browser clears the trace at a gap instead of drawing fictitious continuity.pulseCount: axis pulse rising edges since the current generation started. The browser subtracts its initial snapshot counter to show the current capture count.frequencyHz,periodNs,highNs: latest measured cycle/high width; frequency returns to zero after no rising edge for the larger of two periods and 10 ms.
Each axis batch stores at most 32768 edges. The existing 64-message per-client queue disconnects slow consumers. Reconnection requires a new snapshot. Unsubscribe with {"type":"unsubscribe","topic":"board.waveform.x"}.
Batches are shared between subscribers. The first batch after a snapshot can include edges collected before that snapshot, and its start describes the shared batch boundary rather than the new subscriber's baseline. Independent clients should initialize their trace from the snapshot's end at nowNs, ignore subsequent edges earlier than that timestamp, and keep the snapshot's pulseCount as their capture baseline. Apply the same rule after requesting another snapshot or receiving a new epoch. The browser already applies this timestamp filter.
The browser uses uPlot stepped paths and retains the latest ten seconds of simulation time, including a boundary level sample, with 1 ms–10 s display windows. The UI displays the selected axis pulse output; DIR remains available to independent protocol clients. Live mode follows the latest timestamp. Explicitly pause to zoom, pan, or measure a frozen snapshot while acquisition continues. Return to live uses the current stream without resetting pulse counts. Clear removes local history and resets the local pulse baseline without controlling the board. Two draggable time cursors measure elapsed time, rising-edge count, and average frequency (count divided by elapsed time). Counts use a left-inclusive, right-exclusive interval; the initial boundary level is not counted as an edge. Measurements outside retained data are unavailable. Switching axes starts a fresh view and subscription, without retaining the previous axis snapshot. The board continues running when the waveform is closed or paused.
Host motion protocol reference
The host application's src/ui/LaserWeldingIC/Services/Devices/MotionServiceSelf.cs implements MoveAxis using Modbus RTU function 0x10 at 0xA000 with five registers: axis (X=0), direction (positive=0, negative=1), mode (fixed=0, continuous=1), distance multiplied by 10, and speed. Normal stop writes 1 to 0xA011 using function 0x06. The web view observes the resulting outputs; clicking an axis does not send a move.
General inputs
Subscribe or request a snapshot using board.inputs. levels contains I1 through I8 in order, with 1 for high and 0 for low. New Renode instances initialize all eight inputs high. Latched levels survive panel closure and client disconnects. Momentary holds are owned by the pressing connection and released high on release, unsubscribe, or disconnect. Firmware reset preserves the current external levels; stopping and starting Renode initializes them again.
{"type":"set","topic":"board.inputs","requestId":"unique-id","input":1,"level":0}
input must be an integer from 1 through 8, level must be 0 or 1, and requestId must be a nonempty string of at most 64 characters. Each connection can have one pending ordinary set write per input topic; additional ordinary writes to that topic return busy. Different groups such as board.inputs and board.controls can have independent pending writes on the same connection. Release requests bypass this limit. Invalid requests return invalid_request. Errors contain the topic and request ID when supplied.
Writes are applied at a simulation synchronization point. After application, the requester and all subscribers receive a changed frame containing requestId, sequence, nowNs, and the full levels array. Snapshot frames have the same fields with a null request ID. Acknowledgement means the GPIO level was applied, not that firmware has completed processing it. Accepted latched writes are not cancelled by disconnecting; clients should fetch a snapshot after reconnecting rather than replaying requests.
| Input | GPIO |
|---|---|
| I1 | PG12 |
| I2 | PG11 |
| I3 | PG10 |
| I4 | PD7 |
| I5 | PD4 |
| I6 | PD3 |
| I7 | PD1 |
| I8 | PD0 |
The mapping follows the supplied Factory ELF MotionManager::check_general_input, which passes raw GPIO levels to set_level indices 27–34. The installation manual specifies low-active general inputs and a contact to GND. This model represents the digital logic boundary with matching high/low levels; it does not simulate terminal voltages, optocoupler delays, or contact bounce. Input activation polarity remains firmware-configurable.
Momentary inputs use {"type":"press","topic":"board.inputs","requestId":"p1","input":1} and a matching release request (with its own unique request ID). Press drives low; release drives high. A held input rejects writes from other clients with busy. Release bypasses the ordinary-write busy gate. At a simulation synchronization point, a newer release supersedes a press that has not yet been applied; sub-synchronization presses can therefore produce no observable low pulse. No fixed pulse duration is synthesized.
General outputs
Subscribe with {"type":"subscribe","topic":"board.outputs"}. The first frame is snapshot, followed by changed frames from actual post-multiplexer GPIO hooks. Each contains sequence, nowNs, and ten levels values in O1–O10 order. snapshot and unsubscribe requests are also supported. Writes to this topic are rejected: outputs remain under firmware control.
Output-bank frames, including board.signal2 and board.axisDirections, also contain per-channel risingEdges, fallingEdges, lastRiseNs and lastFallNs arrays. Counts start at zero when the observer is created and increase only on actual GPIO transitions; initial levels are not edges. A zero count means its corresponding timestamp has not been observed. Timestamps are simulation nanoseconds captured in the GPIO hook, not the time a snapshot is requested. Read a baseline counter before an action to distinguish its new edges from earlier activity.
| Output | GPIO |
|---|---|
| O1 | PE8 |
| O2 | PE7 |
| O3 | PG1 |
| O4 | PF15 |
| O5 | PD14 |
| O6 | PD15 |
| O7 | PF14 |
| O8 | PF13 |
| O9 | PB2 |
| O10 | PA5 |
Mapping is taken from the supplied Factory ELF MotionManager::out_io_with_valid_level. The host manual-output command writes registers 0xA964 through 0xA96D; configured active levels are at 0xA950 through 0xA959. The viewer observes GPIO levels rather than assuming an ON command always means high.
The installation manual distinguishes O1–O6 (24 V power outputs) from O7–O10 (open-drain outputs). Displayed high/low refers to the firmware GPIO control signal, not measured terminal voltage or an inferred load state. No external pull-up, supply, or load is simulated by this panel. Disconnection clears the displayed levels to unknown; reconnection fetches a fresh snapshot.
External controls
board.controls uses the same request and frame schema as board.inputs, with three levels in START, PAUSE, STOP order. Supported requests are subscribe, snapshot, unsubscribe, set, press, and release. Input numbers are local to each topic.
| Input | Terminal | GPIO | Action |
|---|---|---|---|
| 1 | 2 / START | PE15 | Start or resume |
| 2 | 4 / PAUSE | PE14 | Pause |
| 3 | 6 / STOP | PE13 | Stop |
Mapping follows Factory ELF MotionManager::check_ctrl_signal at 0x08036EFC and set_ctrl_signal. Terminals 1, 3, and 5 are GND. Each input defaults high; pressing connects it to GND (low), releasing returns it high. The installation manual requires enabling external control in the host application for start/pause. The firmware decides how the physical input affects processing; this API does not issue host commands or enable external control automatically.
Example: {"type":"press","topic":"board.controls","requestId":"start-down","input":1}; send release with a new request ID when released. For a retained level, use set with level: 0 or level: 1. Momentary ownership, disconnect release, synchronization, and reset restoration match general inputs. Latched levels remain until changed or the Renode instance ends.
The web panel stores each button's mode in localStorage (pcb-viewer-control-modes), defaulting to momentary. It does not store physical levels. The retained mode toggles on click; momentary mode releases on pointer cancellation, focus loss, panel closure, or connection loss. Switching from a retained low level to momentary releases the input.
Axis driver signals
board.axisAlarms is an input bank with six levels in XYZABC order, using the existing snapshot and set protocol. Each Web ALM switch latches its state: open is MCU level 0, closed is level 1. New instances retain the previous low-level driver-input baseline; machine resets restore the selected levels. Whether a level raises an alarm depends on the firmware's alarm enablement and polarity configuration.
board.axisDirections is a read-only output bank with six levels in the same order. It supports subscribe, snapshot, and unsubscribe, with the same frame structure as board.outputs. It observes the same GPIO signals as waveform channel 1 without duplicating pulse history. The Web arrows show the current DIR bit (0 left, 1 right), not mechanical positive/negative direction or motion activity.
| Axis | ALM input | DIR output |
|---|---|---|
| X | PC3 | PC1 |
| Y | PD11 | PD9 |
| Z | PG14 | PB3 |
| A | PE12 | PE10 |
| B | PA9 | PC7 |
| C | PB13 | PD8 |
ALM mapping follows the Factory ELF MotionManager::check_hard_alarm() GPIO reads; DIR mapping is shared with the six-axis waveform observer. The external differential driver and optocoupler voltages are not modeled. Selecting an axis opens the right panel: live ALM and DIR controls occupy the top section, and the pulse waveform occupies the section below. Pausing waveform inspection does not pause the live signals or ALM control.
Axis limit inputs
board.limits uses the same snapshot, subscription, and set schema as board.inputs, with 12 levels in terminal order. The Web limit switches retain their level until clicked again; 0 is the simulated NPN switch pulling the input low, and 1 is released. The firmware's configured active level determines whether a low input is treated as a limit alarm. The UI shows the electrical switch state, not an inferred alarm state.
| Terminal | GPIO | Terminal | GPIO |
|---|---|---|---|
| X+ / X0 | PA12 | X− | PA11 |
| Y+ / Y0 | PA10 | Y− | PG8 |
| Z+ / Z0 | PG7 | Z− | PG6 |
| A+ / A0 | PG5 | A− | PG4 |
| B+ / B0 | PG3 | B− | PG2 |
| C+ / C0 | PB15 | C− | PB14 |
Mapping follows the supplied Factory ELF MotionManager::check_hard_limit GPIO reads. As with the other input banks, levels are restored after a machine reset, but a new Renode instance starts with all limit inputs high. The model does not simulate 24 V supply, optocoupler timing, or sensor faults.
Signal 1 inputs
board.signal1 uses the same input-bank subscription and set requests. Its three levels follow the order gas alarm, water-cooler alarm, and wire-break detection. The Web panel displays only the gas and water-cooler switches, centered as a compact pair. The switches default open (1) and retain the closed state (0) after a click; click again to open. Wire-break detection remains available through the API; its − and + terminals represent one logical input, not two independently writable GPIOs.
| Input | Terminal | Firmware GPIO |
|---|---|---|
| 1 | Gas alarm | PF4 |
| 2 | Water-cooler alarm | PF3 |
| 3 | Wire-break pair | PF5 |
The mapping follows Factory ELF MotionManager::check_hard_alarm: dt_water_cooler reads PF3, dt_gas_tanks reads PF4, and get_wire_break_signal returns the PF5 sample. The browser shows the simulated electrical input state; firmware alarm polarity and feature enablement remain under firmware configuration. The differential terminal voltage and sensing circuit are not modeled.
Laser interface
The board.laserAlarm input bank has one active-low contact on PC0. The Web switch starts open (high), latches closed (low) on click, and uses the same set/snapshot protocol as other input banks. The pin follows the Factory firmware's laser alarm input and the launch script's idle-high wiring.
board.laser is a read-only subscription for the actual PF6/TIM16 laser PWM and PA1 laser-enable GPIO output. Snapshot and 50 ms changed frames contain pwmActive (at least one rising edge in that interval), pwmLevel, and enableLevel. Both level fields preserve the raw MCU pin state. The current firmware uses PF6 active-low: idle is high, and 100% output holds low. The Web lights the PWM indicator when edges are observed or PF6 is low, and lights the enable indicator when PA1 is high. This summarizes PWM activity for a lamp; it does not transport every edge or measure terminal voltage.
enableRisingEdges, enableFallingEdges, enableLastRiseNs and enableLastFallNs retain actual PA1 transitions with the same counting and timestamp semantics as output-bank frames. They let tests measure gas-to-enable delays and finite enable duration even when both transitions occur between snapshots. They do not represent measured optical output or individual PWM edges.
The laser alarm is an input switch; the enable terminal is an output and has no Web toggle. PWM+ and PWM− form one differential pair in the Web model and feed one activity indicator; their animated traces are opposite in phase. The current Renode observation is the MCU's PF6 source signal, not a measurement of both physical terminal voltages. The installation manual labels PWM− as GND, so the electrical driver topology remains unverified by this model.
Signal 2 gas-valve output
board.signal2 is a read-only output bank. It supports subscribe, snapshot, and unsubscribe with the same frame shape as board.outputs. Its levels array currently contains one value: the PA0 gas-valve control GPIO, confirmed from Factory ELF Ctl_Open_Gas and Ctl_Close_Gas. Writes are rejected.
The Web panel toggles from the Signal 2 connector and shows one gas-valve indicator with two connections to the valve −/+ terminals. It observes the firmware control state, not voltage across the terminals or actual gas flow. Disconnecting clears the cached state. Wire-feed direction and RS232 communication are handled by the feeder model below; wire-break output remains unmapped.
Wire-feeder control path (verified Factory firmware)
Manual feed and retract are separate Modbus commands, not opposite levels on the feed −/+ terminals:
| Action | Host -> controller (slave 8) | Controller -> feeder (slave 10, UART8) |
|---|---|---|
| Feed start / stop | FC06 0xA811 = 1 / 0 |
FC06 0x0143 = 1 / 0 |
| Retract start / stop | FC06 0xA812 = 1 / 0 |
FC06 0x0144 = 1 / 0 |
The host uses FeedWireAction / BackWireAction through ServiceBase to write these controller registers. Factory ProcessManager::manual_feed_wire / manual_back_wire checks external stop, cached feeder connection, and wire enable (0x0203) before sending a command. A disconnected or disabled feeder can yield a successful controller reply without an outgoing motion request.
MessageManager::init binds g_feeder_master to the serial transport backed by Uart_Ssj_HAL_UART_Transmit, which uses UART8 at 115200 baud. Connection probing reads one holding register at 0x0100 from slave 10. The PF2 GPIO controls the separate feed-enable output and does not encode manual feed/retract direction.
UART8 RX DMA must reach DMAMUX request 81, matching Factory HAL_UART_MspInit. The platform now includes this connection. An isolated protocol responder was used to acknowledge the probe and echo FC06 requests; both start/stop pairs were observed on UART8 after the host commands, without patching firmware RAM or connection flags. This validates command routing only, not feeder mechanics or speed. Normal startup now attaches the wireFeeder peripheral to UART8. It responds through actual UART RX timing and DMA; firmware performs its own connection detection.
Simulated feeder and Web state
board.feeder supports read-only subscribe, snapshot, and unsubscribe. Snapshot and 50 ms changed frames contain sequence, nowNs, direction (stopped, feed, retract), speed (cm/min), and enableLevel (actual PF2 GPIO). Diagnostic fields runEnabled, manualFeed and manualRetract expose the independent permission and manual switches. The model accepts slave-10 FC03 parameter reads, FC06 parameter/action writes, and FC16 parameter blocks. It validates RTU lengths and CRC, and schedules replies on simulation time with a 1 ms turnaround before normal UART reception.
The Signal 2 panel shows compact gas-valve and feeder nodes. Feeder chevrons indicate commanded direction; its four ports map to feed −/+, RX, and TX. The serial wires indicate logical activity, not a byte-level serial waveform. Automatic feed requires both PF2 high and the 0x0115 run permission; 0x0031 is not a feeder register in the V1.51 protocol and is rejected. Manual actions also require run permission; manual forward/retract use 0x0101/0x0102 speeds. The latest nonzero manual direction command replaces the other manual direction. Releasing that action returns to automatic feed only if PF2 and run permission remain enabled. Reading 0x0115 returns its configured permission, not inferred movement. FC06 and FC16 single-register writes support 0x0115, save and manual-action addresses; multi-register action writes are rejected. Legacy 0x0140/0x0141 actions remain silent and 0x0143/0x0144 acknowledge requests. Zero speed is displayed as stopped.
This is a logical peripheral, not a motor or wire-length simulation. Default speeds are 100 cm/min simulation parameters; the fixture starts with run permission enabled to represent a powered, ready feeder, without claiming that every physical feeder has this startup default; hardware/software versions are unspecified (zero), overcurrent reads normal (2). 0x013F saves parameters only for this peripheral instance, surviving a machine reset but not a new Renode process. Reset cancels pending replies and manual actions and restores the fixture run permission. Saving parameters does not persist transient run permission or manual switches. Host configuration and the firmware's external-stop/wire-enable checks remain in force. No firmware RAM is patched.
Optional external devices
The preceding feeder section describes the unchanged default model. With --wire, board.feeder reads the complete V1.51 device instead: initial run permission is on, versions are populated, and automatic feed includes delay/retract/refill stages. --laser1 and --laser2 select one actual USART2 protocol responder. The board.devices topic publishes startup configuration, effective device parameters and external-input controls; see external devices. These models operate without a browser, and do not persist device parameters across Renode processes.
The complete feeder publishes powered and read-only process parameters on board.devices. Sending set with device: "wire", input: "power", and value: 0/1 holds its power off/on. While off it is silent on UART8 and cannot move; power-on restores instance-saved parameters. Its Web card hides protocol run permission, provides momentary physical buttons, and stores only parameter-section expansion in browser local storage.
board.feeder includes enableRisingEdges and enableFallingEdges, counted from the actual PF2 pin over the WebSocket server's lifetime for both the default and complete feeder models. They remain cumulative across MCU reset, like the laser-enable counters. These fields reveal reactivation between snapshots; they do not infer mechanical wire displacement.
Both torch profiles now publish independent powered state, per-sensor temperatures and thresholds, alarm masks, collision latch state, versions and synchronization. The same set power request holds a torch off/on. collision is a connection-owned physical input; disconnect releases the source while protocol latching remains effective. The UI uses collisionFault: switching off releases its source and executes the collision-clear operation, without clearing other fault sources or another connection's active collision. Releasing the last fault-injection connection also clears that injection. processIo1/processIo2/processIo3 set individual digital input lines without replacing the other lines. Protocol-driven status/process lamps are reported separately from actual alarm state. See external devices for exact masks and sensor ordering.
torch.displayParameters reports the current process's received screen values (shape, width, height, angle, frequency, spacing), using the same units as the corresponding top-level effective fields. For digital torches, a cache-only write updates these screen values while the effective fields and current-process protocol readback remain unchanged until application. Non-current process writes do not replace the current screen values. Legacy torch screen values match its effective fields.