Skip to content
Open
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
6 changes: 2 additions & 4 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020-2025, The OTNS Authors.
# Copyright (c) 2020-2026, The OTNS Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -79,11 +79,9 @@ jobs:
fi
./script/install
- name: Build custom OT node
# FIXME: OT_CMAKE_NINJA_TARGET used to exclude tests, because test_platform.cpp is missing otPlatAssertFail()
run: |
cd ot-rfsim
OT_CMAKE_NINJA_TARGET="ot-cli-ftd ot-cli-mtd" ./script/build -DOT_HISTORY_TRACKER=ON
# ./script/build -DOT_HISTORY_TRACKER=ON # this should be the command above (after the OT fix)
./script/build -DOT_HISTORY_TRACKER=ON
- name: Setup dev environment
run: |
./script/setup-dev
26 changes: 25 additions & 1 deletion cli/CmdRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ func (rt *CmdRunner) execute(cmd *Command, output io.Writer) {
}

func (rt *CmdRunner) executeGo(cc *CommandContext, cmd *GoCmd) {
// verify that we're not in realtime mode.
if rt.sim.GetConfig().Realtime {
cc.errorf("cannot use 'go' commands in -realtime mode")
return
}

// determine duration and desired speed of the Go simulation period.
timeDurToGo, err := time.ParseDuration(cmd.Time)
if cmd.Ever == nil && err != nil {
Expand Down Expand Up @@ -382,6 +388,10 @@ func (rt *CmdRunner) executeAutoGo(cc *CommandContext, cmd *AutoGoCmd) {
}
cc.outputf("%d\n", autoGoInt)
} else {
if rt.sim.GetConfig().Realtime && cmd.Val.Yes == nil {
cc.errorf("cannot set 'autogo' to false in -realtime mode")
return
}
sim.SetAutoGo(cmd.Val.Yes != nil)
}
})
Expand Down Expand Up @@ -713,7 +723,7 @@ func (rt *CmdRunner) executeLsNodes(cc *CommandContext, cmd *NodesCmd) {
for _, nodeId := range sim.GetNodes() {
snode, dnode := rt.getNodeById(nodeId)
var line strings.Builder
line.WriteString(fmt.Sprintf("id=%d\ttype=%-6s extaddr=%016x rloc16=%04x x=%-2d\ty=%-3d\tz=%-3d\tstate=%s\tfailed=%v", nodeId, dnode.Type, dnode.ExtAddr, dnode.Rloc16,
line.WriteString(fmt.Sprintf("id=%d\ttype=%-6s extaddr=%016x rloc16=%04x x=%-2d\ty=%-3d\tz=%-3d\tstate=%s\tfailed=%v", nodeId, snode.GetType(), dnode.ExtAddr, dnode.Rloc16,
dnode.X, dnode.Y, dnode.Z, dnode.Role, dnode.IsFailed()))
line.WriteString(fmt.Sprintf("\texe=%s", snode.GetExecutableName()))
cc.outputf("%s\n", line.String())
Expand Down Expand Up @@ -1288,6 +1298,16 @@ func (rt *CmdRunner) executeExe(cc *CommandContext, cmd *ExeCmd) {
ec.Br = cmd.Path
}
cc.outputf("br : %s\n", ec.Br)
case RCP:
if isSetPath {
ec.Rcp = cmd.Path
}
cc.outputf("rcp: %s\n", ec.Rcp)
case HOST:
if isSetPath {
ec.RcpHost = cmd.Path
}
cc.outputf("host: %s\n", ec.RcpHost)
}
Comment thread
EskoDijk marked this conversation as resolved.
return
} else if isSetDefault && !isSetPath && !isSetNodeType && !isSetVersion {
Expand All @@ -1307,11 +1327,15 @@ func (rt *CmdRunner) executeExe(cc *CommandContext, cmd *ExeCmd) {
cc.outputf("mtd: %s\n", ec.Mtd)
cc.outputf("br : %s\n", ec.Br)
cc.outputf("matter: %s\n", ec.Matter)
cc.outputf("rcp: %s\n", ec.Rcp)
cc.outputf("host: %s\n", ec.RcpHost)
cc.outputf("Executables search path: %s\n", ec.SearchPathsString())
cc.outputf("Detected FTD path : %s\n", ec.FindExecutable(ec.Ftd))
cc.outputf("Detected MTD path : %s\n", ec.FindExecutable(ec.Mtd))
cc.outputf("Detected BR path : %s\n", ec.FindExecutable(ec.Br))
cc.outputf("Detected Matter path : %s\n", ec.FindExecutable(ec.Matter))
cc.outputf("Detected RCP path : %s\n", ec.FindExecutable(ec.Rcp))
cc.outputf("Detected RCP Host path : %s\n", ec.FindExecutable(ec.RcpHost))
})
}

Expand Down
16 changes: 13 additions & 3 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Add a node to the simulation and get the node ID.
add <type> [x <x>] [y <y>] [rr <radio-range>] [id <node-id>] [restore] [exe <path>] [v11|v12|v13|v14]
```

The `<type>` can be `router`, `fed`, `med`, `sed`, `ssed`, `br` (Border Router), `wifi` (for a Wi-Fi interferer node), or `matter`. Node ID can be specified using the `id` parameter, otherwise OTNS assigns the next available one. If the `restore` option is specified, the node restores its network configuration from persistent storage.
The `<type>` can be `router`, `fed`, `med`, `sed`, `ssed`, `br` (Border Router), `rcp`, `wifi` (for a Wi-Fi interferer node), or `matter`. Node ID can be specified using the `id` parameter, otherwise OTNS assigns the next available one. If the `restore` option is specified, the node restores its network configuration from persistent storage.
Comment thread
EskoDijk marked this conversation as resolved.

The (advanced) `exe` option can be used to specify a node executable for the new node; either a name only which is then located in the default search paths, or a full abs or rel pathname pointing to the executable to use.

Expand Down Expand Up @@ -254,10 +254,14 @@ The line `Executables search path` lists the paths where the executable of that
ftd: ot-cli-ftd
mtd: ot-cli-mtd
br : ot-cli-ftd_br
rcp: ot-rcp
host: ot-cli
Comment thread
EskoDijk marked this conversation as resolved.
Executables search path: [".", "./ot-rfsim/ot-versions", "./build/bin"]
Detected FTD path : ./ot-rfsim/ot-versions/ot-cli-ftd
Detected MTD path : ./ot-rfsim/ot-versions/ot-cli-mtd
Detected BR path : ./ot-rfsim/ot-versions/ot-cli-ftd_br
Detected RCP path : ./ot-rfsim/ot-versions/ot-rcp
Detected RCP Host path : ./ot-rfsim/ot-versions/ot-cli
Done
>
```
Expand Down Expand Up @@ -299,10 +303,10 @@ Done
#### exe: Change OT executable for particular node type

```shell
exe ( ftd | mtd | br ) ["<path-or-filename-of-executable>"]
exe ( ftd | mtd | br | rcp | host) ["<path-or-filename-of-executable>"]
Comment thread
EskoDijk marked this conversation as resolved.
```

Change the OpenThread (OT) executable, or shell script, for a particular node types as provided in the first argument (ftd, mtd, or br). The path-or-filename is provided in the second argument and will replace the current default executable for that node type. If only the first argument is given, the current executable for this node type will be listed and no change is made. If only a filename is given, without full path, the executable will be located using the search paths listed under `Executables search path`.
Change the OpenThread (OT) executable, or shell script, for a particular node types as provided in the first argument (ftd, mtd, br, rcp or host). The path-or-filename is provided in the second argument and will replace the current default executable for that node type. If only the first argument is given, the current executable for this node type will be listed and no change is made. If only a filename is given, without full path, the executable will be located using the search paths listed under `Executables search path`.

Note that the default executable is used when normally adding a node using the GUI or a command such as `add router x 200 y 200` where the executable is not explictly specified. The "exe" argument of the "add" command will however override the default executable always, for example as in the command `add router x 200 y 200 exe "./my-override-ot-cli-ftd"` .

Expand All @@ -317,10 +321,14 @@ Done
ftd: ./my-ot-cli-ftd
mtd: ot-cli-mtd
br : ./br-script.sh
rcp: ot-rcp
host: ot-cli
Comment thread
EskoDijk marked this conversation as resolved.
Executables search path: [".", "./ot-rfsim/ot-versions", "./build/bin"]
Detected FTD path : ./my-ot-cli-ftd
Detected MTD path : ./ot-rfsim/ot-versions/ot-cli-mtd
Detected BR path : ./br-script.sh
Detected RCP path : ./ot-rfsim/ot-versions/ot-rcp
Detected RCP Host path : ./ot-rfsim/ot-versions/ot-cli
Done
> exe mtd
mtd: ot-cli-mtd
Expand Down Expand Up @@ -349,6 +357,8 @@ go <duration> [speed <particular-speed>]

Simulate for a specified time in seconds or indefinitely (duration=`ever`). It is required in `-autogo=false` mode to advance the simulation. In `-autogo=true` mode, it can be optionally used to advance the simulation quickly by the given time. For example, in a paused simulation to quickly advance 64 us, 1 ms, 10 seconds, or an hour. The optional `speed` argument can be given to do the simulation at that speed e.g. to see the animations and log output better. The `duration` argument can optionally end with a time unit suffix: `us`, `ms`, `s`, `m`, or `h`.

In `-realtime` mode this command is not available and will always return an error.
Comment thread
EskoDijk marked this conversation as resolved.

```bash
> go 1
Done
Expand Down
4 changes: 2 additions & 2 deletions cli/ast.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2025, The OTNS Authors.
// Copyright (c) 2020-2026, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -312,7 +312,7 @@ type AddCmd struct {

// noinspection GoVetStructTag
type NodeTypeOrRole struct {
Val string `@("router"|"reed"|"fed"|"med"|"sed"|"ssed"|"br"|"mtd"|"ftd"|"wifi"|"matter")` //nolint
Val string `@("router"|"reed"|"fed"|"med"|"sed"|"ssed"|"br"|"mtd"|"ftd"|"wifi"|"matter"|"rcp"|"host")` //nolint
}

// noinspection GoVetStructTag
Expand Down
7 changes: 5 additions & 2 deletions dispatcher/FailureCtrl_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2024, The OTNS Authors.
// Copyright (c) 2020-2026, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -44,7 +44,10 @@ type mockDispatcherCallback struct {
func (m mockDispatcherCallback) OnUartWrite(nodeid NodeId, data []byte) {
}

func (m mockDispatcherCallback) OnLogWrite(nodeid NodeId, data []byte) {
func (m mockDispatcherCallback) OnUartDisconnected(nodeid NodeId) {
}

func (m mockDispatcherCallback) OnLogWrite(nodeid NodeId, data []byte, isFromHost bool) {
}

func (m mockDispatcherCallback) OnNextEventTime(nextTimeUs uint64) {
Expand Down
64 changes: 29 additions & 35 deletions dispatcher/Node.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ type Node struct {
CurTime uint64
Mode NodeMode
Role OtDeviceRole
Type string
RadioNode *radiomodel.RadioNode

cfg *NodeConfig
conn net.Conn
hasDisconnected bool // keeps track of whether the node process disconnected
hasDisconnected bool // keeps track of whether the node process Unix socket disconnected
msgId uint64
err error
failureCtrl *FailureCtrl // keeps track of radio failures scheduled by OTNS
Expand All @@ -106,41 +106,35 @@ func newNode(d *Dispatcher, nodeid NodeId, cfg *NodeConfig) *Node {
}

nc := &Node{
D: d,
Id: nodeid,
CurTime: d.CurTime,
CreateTime: d.CurTime,
X: cfg.X,
Y: cfg.Y,
Z: cfg.Z,
ExtAddr: InvalidExtAddr,
Rloc16: InvalidRloc16,
Mode: DefaultNodeMode(),
Role: OtDeviceRoleDisabled,
Type: cfg.Type,
conn: nil, // connection will be set when first event is received from node.
hasDisconnected: false,
err: nil, // keep track of connection errors.
RadioNode: radiomodel.NewRadioNode(nodeid, radioCfg),
joinerState: OtJoinerStateIdle,
logger: logger.GetNodeLogger(d.cfg.OutputDir, d.cfg.SimulationId, cfg),
D: d,
Id: nodeid,
CurTime: d.CurTime,
CreateTime: d.CurTime,
X: cfg.X,
Y: cfg.Y,
Z: cfg.Z,
ExtAddr: InvalidExtAddr,
Rloc16: InvalidRloc16,
Mode: DefaultNodeMode(),
Role: OtDeviceRoleDisabled,
cfg: cfg,
conn: nil, // connection will be set when first event is received from node.
err: nil, // keep track of connection errors.
RadioNode: radiomodel.NewRadioNode(nodeid, radioCfg),
joinerState: OtJoinerStateIdle,
logger: logger.GetNodeLogger(d.cfg.OutputDir, d.cfg.SimulationId, cfg),
}

nc.failureCtrl = newFailureCtrl(nc, NonFailTime)
return nc
}

func (node *Node) exit() {
node.DisconnectSocket()
node.hasDisconnected = true
node.logger.Close()
}

// DisconnectSocket closes the socket connection (if any) with the node process.
func (node *Node) DisconnectSocket() {
node.hasDisconnected = true
if node.conn != nil {
err := node.conn.Close()
if err == nil {
if err == nil { // if already closed, don't print the log message.
logger.Tracef("Closed dispatcher node %d socket connection.", node.Id)
}
}
Expand All @@ -150,9 +144,8 @@ func (node *Node) String() string {
return GetNodeName(node.Id)
}

// SendToUART sends any data to virtual time UART of the node.
func (node *Node) SendToUART(data []byte) error {
var err error
// SendToVirtualUART sends any data to the virtual-time UART of the node.
func (node *Node) SendToVirtualUART(data []byte) error {
evt := &Event{
Timestamp: node.D.CurTime,
Type: EventTypeUartWrite,
Expand All @@ -162,10 +155,7 @@ func (node *Node) SendToUART(data []byte) error {

node.logger.Tracef("UART-write: %s", data)
node.sendEvent(evt)
if node.err != nil {
err = node.err
}
return err
return node.err
}

func (node *Node) SendRfSimEvent(writeValue bool, param RfSimParam, value RfSimParamValue) error {
Expand Down Expand Up @@ -220,11 +210,14 @@ func (node *Node) sendEvent(evt *Event) {
}

if err, wasSocketClosed := node.sendRawData(evt.Serialize()); err != nil {
socketClosedMsg := ""
if wasSocketClosed {
node.DisconnectSocket() // just in case peer closed it, also close locally.
socketClosedMsg = " (socket was closed)"
}
err = fmt.Errorf("send event %v failed%s: %w", evt, socketClosedMsg, err)
node.logger.Error(err)
node.err = err
node.DisconnectSocket()
} else {
node.D.setAlive(node.Id)
}
Expand All @@ -234,6 +227,7 @@ func (node *Node) sendEvent(evt *Event) {
// a flag indicating whether the socket was closed by either side.
func (node *Node) sendRawData(msg []byte) (error, bool) {
logger.AssertNotNil(node.conn)

if _, err := node.conn.Write(msg); err != nil {
if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) {
return err, true
Expand Down
Loading
Loading