Skip to Content

Simulate CAN ​

Simulate CAN is EcuBus-Pro’s software virtual CAN bus. It needs no adapter, vendor DLL, or USB device. Use it to develop scripts, UDS testers, and a PC AUTOSAR MCAL (Can.c) on the same virtual network.

It is the same idea as ETAS VirtualCan: Peak / Kvaser / Vector stay real hardware. Simulate is the in-process bus.

Capabilities ​

CapabilityDetail
Channels64 handles: Simulate-0 … Simulate-63
ProtocolsCAN and CAN-FD
PlatformsWindows, Linux, macOS (no vendor SDK)
EcuBus featuresDBC, Interactive, node scripts, UDS tester, trace
PC MCALJSON-RPC 2.0 TCP when at least one simulate device is open

Virtual bus ​

Every open simulate handle is a node on one shared bus.

  • A frame transmitted on Simulate-0 is received on every other open handle (Simulate-1, …) as RX.
  • A node does not see its own TX as RX.
  • The same handle can be opened only once in a process (BUS ALREADY INIT).

Typical laboratory layout:

EcuBus project:  Simulate-0   (tester / script / trace)
PC Can.c:        Simulate-1 … N   (virtual ECU, opened over JSON-RPC)
                 └── same virtual bus ──

Trace ​

EcuBus trace shows only project devices. If the project has Simulate-0:

EventTrace on Simulate-0
Script / Interactive / UDS sendTX
Can.c send on Simulate-1RX

Simulate-1 is not a hardware node in the project, so it does not appear as a second trace channel.

Use in EcuBus ​

  1. Hardware → Simulate → add a CAN node.
  2. Pick a free handle (start with Simulate-0).
  3. Set bitrate / CAN-FD like any other vendor.
  4. Bind a DBC, Interactive table, node script, or UDS tester as usual.

Two project nodes on different handles (Simulate-0 and Simulate-1) also loop back to each other. That is useful when both sides are EcuBus scripts.

PC AUTOSAR MCAL (JSON-RPC) ​

A C Can.c keeps AUTOSAR signatures (Can_Init, Can_Write, Can_MainFunction_Read, …) and forwards each call as one JSON-RPC request.

[CanIf / CanTp / Com]
        |
    [Can.c]  -- TCP JSON-RPC -->  [EcuBus with Simulate-0 open]
                                        |
                                  Simulate-1 … N  (opened by Can.c)

When the TCP server starts ​

The server is not started at application boot.

  • GUI: starts listening when the project starts with at least one Simulate CAN device. Stops when the last project simulate device closes.
  • CLI ecb_cli test: opens every device in the project. The listener runs for that command when at least one opened device is Simulate CAN.
  • CLI ecb_cli seq: opens only that tester's CAN device. The listener runs when that device is Simulate. A project that merely contains another Simulate node does not listen.

Default bind: 127.0.0.1:17320 (Home → Setting → SIM-CAN). Listen address/port are bind settings only; they do not turn the server on by themselves.

Peak / Kvaser / Vector are not exposed on this API.

Handle ownership ​

WhoHandleResult
EcuBus (project)Simulate-0Opens and owns it
Can.cSimulate-0 (already open)Rejected (already open)
Can.cSimulate-1 … 63 if freeOpens in the same process. controllerId equals handle
Can.DeInitCloses only RPC-owned handles. Never closes project Simulate-0

The table is the usual lab layout, with the project on Simulate-0. Any free handle works: if the project uses Simulate-5, Can.c may open handle 0. controllerId is always that handle.

sys.shutdown closes the controllers Can.c opened and leaves the TCP listener up. Stopping the project is what closes the port.

Start the EcuBus project (or CLI seq/test) first, then connect Can.c.

A POSIX demo is in resources/examples/mcal_can_rpc. It inits handle 1 (and optionally 2), not handle 0.

Wire format ​

  • Transport: TCP.
  • Framing: NDJSON (one JSON value + \n). Concatenated JSON and LSP Content-Length frames are also accepted.
  • Spec: JSON-RPC 2.0, including batches and notifications (no id).
  • Named params only (params is an object).

Request / result:

json
{"jsonrpc":"2.0","method":"Can.Write","params":{"hth":0,"id":256,"sdu":[1,2,3,4],"swPduHandle":1},"id":1}
{"jsonrpc":"2.0","result":{"result":"E_OK","resultCode":0,"ts":1234},"id":1}

Payload: byte array [1,2,3] or hex "01 02 03". IDs: 256, "256", or "0x100".

E_NOT_OK / CAN_BUSY for Can.Write are returned in result, not as JSON-RPC errors.

CodeMeaning
-32700Parse error
-32600Invalid Request
-32601Method not found
-32602Invalid params
-32603Internal error
-32000CAN / driver error
-32001Controller not found
-32002Controller not STARTED
-32003HTH / HRH not found
-32005Handle already open / already initialized

AUTOSAR mapping ​

AUTOSAR APIJSON-RPC method
Can_InitCan.Init
Can_DeInitCan.DeInit
Can_GetVersionInfoCan.GetVersionInfo
Can_SetControllerModeCan.SetControllerMode (CAN_T_START / CAN_T_STOP / CAN_T_SLEEP / CAN_T_WAKEUP)
Can_GetControllerModeCan.GetControllerMode
Can_DisableControllerInterruptsCan.DisableControllerInterrupts
Can_EnableControllerInterruptsCan.EnableControllerInterrupts
Can_WriteCan.Write
Can_GetControllerErrorStateCan.GetControllerErrorState
Can_GetControllerTxErrorCounterCan.GetControllerTxErrorCounter
Can_GetControllerRxErrorCounterCan.GetControllerRxErrorCounter
Can_SetBaudrateCan.SetBaudrate
Can_CheckWakeupCan.CheckWakeup
Can_MainFunction_WriteCan.MainFunction_Write → confirmations[]
Can_MainFunction_ReadCan.MainFunction_Read → indications[]
Can_MainFunction_BusOffCan.MainFunction_BusOff
Can_MainFunction_WakeupCan.MainFunction_Wakeup
Can_MainFunction_ModeCan.MainFunction_Mode

Can.Init opens hardware but leaves controllers STOPPED. Call Can.SetControllerMode with CAN_T_START before Can.Write.

controllerId is the simulate handle. Example when EcuBus already holds Simulate-0:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "Can.Init",
  "params": {
    "controllers": [
      { "vendor": "simulate", "handle": 1, "name": "MCU" }
    ]
  }
}

Default hardware objects if hardwareObjects is omitted:

  • HTH handle * 2 — BASIC transmit
  • HRH handle * 2 + 1 — BASIC receive, standard id, accept all
  • HRH handle * 2 + 1000 — BASIC receive, extended id, accept all

Polling (typical MCAL): Can_MainFunction_Read then CanIf_RxIndication for each item.

Push: can.subscribe, then notifications can.rxIndication, can.txConfirmation, can.controllerBusOff, can.controllerModeIndication, can.controllerWakeup, can.error.

Low-level can.open / can.write / can.read are for bring-up. Production Can.c should use the Can.* methods. rpc.discover lists the catalog.

Keep one TCP connection while the server is listening. Do not reconnect on every successful Can_Write.

Stop and reconnect ​

Stopping the project closes the TCP server and the simulate handles Can.c opened. That is not a CAN bus-off and not a controller mode change. Can.c does not call CanIf_ControllerModeIndication or CanIf_ControllerBusOff. The driver stays CAN_READY. While the listener is down, Can_Write, Can_SetControllerMode, Can_GetControllerMode, and Can_MainFunction_* return without calling CanIf.

Two pre-compile parameters on CanGeneral/CanPcRpc:

ParameterDefaultMeaning
CanRpcAutoReconnecttrueThe next Can API call connects again after the listener is back
CanRpcReconnectIntervalMs500Minimum milliseconds between attempts. 0 retries on every call. Ignored when auto reconnect is off

Reconnect sends Can.Init with the same handles and hardware objects, then restores CAN_CS_STARTED or CAN_CS_SLEEP. CAN_CS_STOPPED is already the state after Can.Init. CanIf is not notified, because that mode is the one it already has. An injected error state does not survive the restart; the new session is Error Active. Can_DeInit does not reconnect.

Error injection ​

Call injectCanError from a node script. The CAN device on that node must be Simulate; Peak, Kvaser, Vector, and the other vendors throw. The device selects the virtual bus. The error is applied to every Can.c controller open on that bus. There is no separate controller argument.

ts
await injectCanError('BUSOFF')
await injectCanError('ACTIVE', { device: 'SIM0' })
StateDefault countersCanIf
ACTIVETEC 0, REC 0none; mode unchanged
PASSIVETEC 128none; mode stays CAN_CS_STARTED; TX and RX still work
BUSOFFTEC 256CanIf_ControllerBusOff only. Mode stays unchanged. Can_Write returns E_NOT_OK. can.write and can.startPeriodSend are rejected, and a running period task stops transmitting

Can_SetControllerMode(CAN_CS_STOPPED) after bus-off is what produces CanIf_ControllerModeIndication(STOPPED). A later CAN_CS_STARTED clears the error state to Active and zeros the counters.

What it is not ​

  • Not real transceiver timing, ACK, or error frames.
  • Not a gateway onto Peak / Kvaser / Vector. Those vendors stay independent hardware.
  • Not a second process: do not run two EcuBus instances that both open simulate on the same port.