Skip to content

Commit

Permalink
Feature: Wi-Fi interferer node type (#138)
Browse files Browse the repository at this point in the history
* [cli][ot-rfsim][simulation][types] Added WiFi interferer node type ('wifi')
* [pylibs] added WiFi interferer unit test
* [all] added 'interference' type transmissions in RadioCommStart event
* [simulation] set txpower and ccathreshold for wifi node
* [cli] README updated with node types, 'wifi' type, and 'txintf' rfsim parameter.
* [simulation] added constants (solving FIXMEs/TODOs); refactoring of node config items into node_config top of file.
  • Loading branch information
EskoDijk committed Sep 24, 2024
1 parent 34c818f commit c4a89a1
Show file tree
Hide file tree
Showing 15 changed files with 165 additions and 87 deletions.
13 changes: 12 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ 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|v131]
```

Node ID can be specified, otherwise OTNS assigns the next available
The `<type>` can be `router`, `fed`, `med`, `sed`, `ssed`, `br` (Border Router), or `wifi` (for a Wi-Fi interferer node).
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 (advanced) `exe` option can be used to specify a node executable for the new node; either a name only which is
Expand Down Expand Up @@ -814,6 +815,8 @@ The following parameters are supported:
* `ccath` - 802.15.4 CCA Energy Detect (ED) threshold (dBm), in the range -126 to 126.
* `cslacc` - 802.15.4 Coordinated Sampled Listening (CSL) accuracy in ppm, range 0-255.
* `cslunc` - 802.15.4 CSL uncertainty in units of 10 microsec, range 0-255.
* `txintf` - for the `wifi` node type, sets the percentage of Wi-Fi traffic, range 0 to 100. Must not be >0 on other
node types.

NOTE: To change global radio model parameters for all nodes, use the [radioparam](#radioparam) command.

Expand All @@ -823,6 +826,7 @@ rxsens -100 (dBm)
ccath -75 (dBm)
cslacc 20 (PPM)
cslunc 10 (10-us)
txintf 0 (%)
Done
> rfsim 1 cslacc 45
Done
Expand All @@ -834,6 +838,7 @@ rxsens -100 (dBm)
ccath -75 (dBm)
cslacc 45 (PPM)
cslunc 10 (10-us)
txintf 0 (%)
Done
>
```
Expand All @@ -850,6 +855,12 @@ Information about a node that will be saved in the file: type, position, and Thr
internal state like 802.15.4 addresses, IP addresses, routing information, flash, counters etc is not
saved. The saved YAML file can be loaded again with [`load`](#load)

```bash
> save "./tmp/mynetwork.yaml"
Done
>
```

### scan

Perform a network scan by the indicated node.
Expand Down
2 changes: 1 addition & 1 deletion cli/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ type AddCmd struct {

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

// noinspection GoVetStructTag
Expand Down
26 changes: 21 additions & 5 deletions dispatcher/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,9 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) {
d.setSleeping(node.Id)
}
d.alarmMgr.SetTimestamp(nodeid, d.CurTime+delay) // schedule future wake-up of node
case EventTypeRadioCommStart:
fallthrough
case EventTypeRadioState:
fallthrough
case EventTypeRadioChannelSample:
case EventTypeRadioCommStart,
EventTypeRadioState,
EventTypeRadioChannelSample:
d.Counters.RadioEvents += 1
d.eventQueue.Add(evt)
case EventTypeStatusPush:
Expand Down Expand Up @@ -542,6 +540,12 @@ func (d *Dispatcher) processNextEvent(simSpeed float64) bool {
d.advanceNodeTime(node, evt.Timestamp, false)
case EventTypeRadioLog:
node.logger.Tracef("%s", string(evt.Data))
case EventTypeRadioCommStart:
if evt.RadioCommData.Error == OT_TX_TYPE_INTF {
// for interference transmissions, visualized here.
d.visSendInterference(evt.NodeId, BroadcastNodeId, evt.RadioCommData)
}
d.radioModel.HandleEvent(node.RadioNode, d.eventQueue, evt)
case EventTypeRadioState:
d.handleRadioState(node, evt)
d.radioModel.HandleEvent(node.RadioNode, d.eventQueue, evt)
Expand Down Expand Up @@ -1052,6 +1056,18 @@ func (d *Dispatcher) visSendFrame(srcid NodeId, dstid NodeId, pktframe *wpan.Mac
})
}

func (d *Dispatcher) visSendInterference(srcid NodeId, dstid NodeId, commData RadioCommEventData) {
d.visSend(srcid, dstid, &visualize.MsgVisualizeInfo{
Channel: commData.Channel,
FrameControl: 0x04,
Seq: 0,
DstAddrShort: 0,
DstAddrExtended: 0,
SendDurationUs: uint32(commData.Duration),
PowerDbm: commData.PowerDbm,
})
}

func (d *Dispatcher) visSend(srcid NodeId, dstid NodeId, visInfo *visualize.MsgVisualizeInfo) {
if dstid == BroadcastNodeId {
if visInfo.FrameControl.FrameType() == wpan.FrameTypeAck {
Expand Down
4 changes: 4 additions & 0 deletions logger/nodelogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ func (nl *NodeLogger) SetDisplayLevel(level Level) {
nl.displayLevel = level
}

func (nl *NodeLogger) IsLevelVisible(level Level) bool {
return nl.displayLevel >= level
}

func (nl *NodeLogger) Log(level Level, msg string) {
NodeLogf(nl.Id, level, msg)
}
Expand Down
2 changes: 1 addition & 1 deletion ot-rfsim
9 changes: 9 additions & 0 deletions pylibs/unittests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,15 @@ def testRealtimeMode(self):
ns.speed = 23
self.assertEqual(1.0, ns.speed)

def testWifiInterferers(self):
ns: OTNS = self.ns
ns.add('router')
ns.add('router')
ns.add('router')
ns.add('wifi')
ns.go(20)
# the wifi node stays on partition 0 (Thread is disabled)
self.assertEqual(2, len(ns.partitions()))

if __name__ == '__main__':
unittest.main()
28 changes: 15 additions & 13 deletions radiomodel/radiomodelIdeal.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,18 @@ func (rm *RadioModelIdeal) txStart(srcNode *RadioNode, evt *Event) {
srcNode.TxPower = DbValue(evt.RadioCommData.PowerDbm) // get last node's properties from the OT node's event params.
srcNode.SetChannel(evt.RadioCommData.Channel)

// dispatch radio event RadioComm 'start of frame Rx' to listening nodes.
rxStartEvt := evt.Copy()
rxStartEvt.Type = EventTypeRadioCommStart
rxStartEvt.RadioCommData.Error = OT_ERROR_NONE
rxStartEvt.MustDispatch = true
rm.eventQ.Add(&rxStartEvt)
if evt.RadioCommData.Error == OT_ERROR_NONE {
// dispatch radio event RadioComm 'start of frame Rx' to listening nodes.
rxStartEvt := evt.Copy()
rxStartEvt.Type = EventTypeRadioCommStart
rxStartEvt.RadioCommData.Error = OT_ERROR_NONE
rxStartEvt.MustDispatch = true
rm.eventQ.Add(&rxStartEvt)
}

// schedule new internal event to call txStop() at end of duration.
txDoneEvt := evt.Copy()
txDoneEvt.Type = EventTypeRadioTxDone
txDoneEvt.RadioCommData.Error = OT_ERROR_NONE
txDoneEvt.MustDispatch = false
txDoneEvt.Timestamp += evt.RadioCommData.Duration
rm.eventQ.Add(&txDoneEvt)
Expand All @@ -173,15 +174,16 @@ func (rm *RadioModelIdeal) txStop(node *RadioNode, evt *Event) {
// Dispatch TxDone event back to the source
txDoneEvt := evt.Copy()
txDoneEvt.Type = EventTypeRadioTxDone
txDoneEvt.RadioCommData.Error = OT_ERROR_NONE
txDoneEvt.MustDispatch = true
rm.eventQ.Add(&txDoneEvt)

// Create RxDone event, to signal nearby node(s) the frame Rx is done.
rxDoneEvt := evt.Copy()
rxDoneEvt.Type = EventTypeRadioRxDone
rxDoneEvt.MustDispatch = true
rm.eventQ.Add(&rxDoneEvt)
if evt.RadioCommData.Error == OT_ERROR_NONE {
// Create RxDone event, to signal nearby node(s) the frame Rx is done.
rxDoneEvt := evt.Copy()
rxDoneEvt.Type = EventTypeRadioRxDone
rxDoneEvt.MustDispatch = true
rm.eventQ.Add(&rxDoneEvt)
}
}

func (rm *RadioModelIdeal) statsTxStart(node *RadioNode, evt *Event) {
Expand Down
34 changes: 18 additions & 16 deletions radiomodel/radiomodelMutualInterference.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,18 @@ func (rm *RadioModelMutualInterference) txStart(node *RadioNode, evt *Event) {

rm.activeTransmitters[ch][node.Id] = node

// dispatch radio event RadioComm 'start of frame Rx' to listening nodes.
rxStartEvt := evt.Copy()
rxStartEvt.Type = EventTypeRadioCommStart
rxStartEvt.RadioCommData.Error = OT_ERROR_NONE
rxStartEvt.MustDispatch = true
rm.eventQ.Add(&rxStartEvt)
if evt.RadioCommData.Error == OT_ERROR_NONE {
// dispatch radio event RadioComm 'start of frame Rx' to listening nodes.
rxStartEvt := evt.Copy()
rxStartEvt.Type = EventTypeRadioCommStart
rxStartEvt.RadioCommData.Error = OT_ERROR_NONE
rxStartEvt.MustDispatch = true
rm.eventQ.Add(&rxStartEvt)
}

// schedule new internal event to call txStop() at end of duration.
txDoneEvt := evt.Copy()
txDoneEvt.Type = EventTypeRadioTxDone
txDoneEvt.RadioCommData.Error = OT_ERROR_NONE
txDoneEvt.MustDispatch = false
txDoneEvt.Timestamp += evt.RadioCommData.Duration
rm.eventQ.Add(&txDoneEvt)
Expand All @@ -259,19 +260,20 @@ func (rm *RadioModelMutualInterference) txStop(node *RadioNode, evt *Event) {
// Dispatch TxDone event back to the source, at time==now
txDoneEvt := evt.Copy()
txDoneEvt.Type = EventTypeRadioTxDone
txDoneEvt.RadioCommData.Error = OT_ERROR_NONE
txDoneEvt.MustDispatch = true
rm.eventQ.Add(&txDoneEvt)

// Create RxDone event, to signal nearby node(s) that the frame Rx is done, at time==now
rxDoneEvt := evt.Copy()
rxDoneEvt.Type = EventTypeRadioRxDone
rxDoneEvt.RadioCommData.Error = OT_ERROR_NONE
if isChannelChangedDuringTx {
rxDoneEvt.RadioCommData.Error = OT_ERROR_FCS
if evt.RadioCommData.Error == OT_ERROR_NONE {
// Create RxDone event, to signal nearby node(s) that the frame Rx is done, at time==now
rxDoneEvt := evt.Copy()
rxDoneEvt.Type = EventTypeRadioRxDone
rxDoneEvt.RadioCommData.Error = OT_ERROR_NONE
if isChannelChangedDuringTx {
rxDoneEvt.RadioCommData.Error = OT_ERROR_FCS
}
rxDoneEvt.MustDispatch = true
rm.eventQ.Add(&rxDoneEvt)
}
rxDoneEvt.MustDispatch = true
rm.eventQ.Add(&rxDoneEvt)
}

func (rm *RadioModelMutualInterference) applyInterference(src *RadioNode, dst *RadioNode, evt *Event) {
Expand Down
32 changes: 18 additions & 14 deletions simulation/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,17 +388,16 @@ func (node *Node) SetChannel(ch ChannelId) {

func (node *Node) GetRfSimParam(param RfSimParam) RfSimParamValue {
switch param {
case ParamRxSensitivity:
fallthrough
case ParamCslUncertainty:
fallthrough
case ParamCslAccuracy:
return node.getOrSetRfSimParam(false, param, 0) // TODO
case ParamRxSensitivity,
ParamCslUncertainty,
ParamTxInterferer,
ParamCslAccuracy:
return node.getOrSetRfSimParam(false, param, 0)
case ParamCcaThreshold:
return node.GetCcaThreshold()
default:
node.error(fmt.Errorf("unknown RfSim parameter: %d", param))
return 0 // FIXME
return 0
}
}

Expand All @@ -410,9 +409,9 @@ func (node *Node) SetRfSimParam(param RfSimParam, value RfSimParamValue) {
return
}
node.getOrSetRfSimParam(true, param, value)
case ParamCslAccuracy:
fallthrough
case ParamCslUncertainty:
case ParamCslAccuracy,
ParamCslUncertainty,
ParamTxInterferer:
if value < 0 || value > 255 {
node.error(fmt.Errorf("parameter out of range 0-255"))
return
Expand Down Expand Up @@ -949,10 +948,15 @@ loop:
}

func (node *Node) onStart() {
node.Logger.Infof("started, panid=0x%04x, chan=%d, eui64=%#v, extaddr=%#v, state=%s, key=%#v, mode=%v",
node.GetPanid(), node.GetChannel(), node.GetEui64(), node.GetExtAddr(), node.GetState(),
node.GetNetworkKey(), node.GetMode())
node.Logger.Infof(" version=%s", node.GetVersion())
if node.Logger.IsLevelVisible(logger.InfoLevel) {
node.Logger.Infof("started, panid=0x%04x, chan=%d, eui64=%#v, extaddr=%#v, state=%s, key=%#v, mode=%v",
node.GetPanid(), node.GetChannel(), node.GetEui64(), node.GetExtAddr(), node.GetState(),
node.GetNetworkKey(), node.GetMode())
node.Logger.Infof(" version=%s", node.GetVersion())
}
if node.cfg.Type == WIFI {
node.SetRfSimParam(ParamTxInterferer, defaultWiFiTxInterfererPercentage)
}
}

func (node *Node) onProcessFailure() {
Expand Down
70 changes: 44 additions & 26 deletions simulation/node_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package simulation

import (
"fmt"
"math"
"os"
"path/filepath"
"strings"
Expand All @@ -37,31 +38,13 @@ import (
. "github.com/openthread/ot-ns/types"
)

type ExecutableConfig struct {
Version string
Ftd string
Mtd string
Br string
SearchPaths []string
}

type NodeAutoPlacer struct {
X, Y, Z int
Xref, Yref int
Xmax int
NodeDeltaCoarse int
NodeDeltaFine int
fineCount int
isReset bool
}

var DefaultExecutableConfig ExecutableConfig = ExecutableConfig{
Version: "",
Ftd: "ot-cli-ftd",
Mtd: "ot-cli-mtd",
Br: "ot-cli-ftd_br",
SearchPaths: []string{".", "./ot-rfsim/ot-versions", "./build/bin"},
}
const (
DefaultCslPeriod = 3 * 1000 // in units of 160 us
DefaultCslPeriodUs = 160 * DefaultCslPeriod // MUST be multiple of 160 us
defaultRadioRange = 220
wifiCcaThreshold = 20.0 // in dBm above the noise floor
defaultWiFiTxInterfererPercentage = 10
)

// defaultNodeInitScript is an array of commands, sent to a new node by default (unless changed).
var defaultNodeInitScript = []string{
Expand Down Expand Up @@ -96,7 +79,35 @@ var defaultLegacyCslScript = []string{
fmt.Sprintf("csl period %d", DefaultCslPeriod),
}

var defaultRadioRange = 220
var defaultWifiInterfererScript = []string{
"txpower 20",
}

type ExecutableConfig struct {
Version string
Ftd string
Mtd string
Br string
SearchPaths []string
}

type NodeAutoPlacer struct {
X, Y, Z int
Xref, Yref int
Xmax int
NodeDeltaCoarse int
NodeDeltaFine int
fineCount int
isReset bool
}

var DefaultExecutableConfig ExecutableConfig = ExecutableConfig{
Version: "",
Ftd: "ot-cli-ftd",
Mtd: "ot-cli-mtd",
Br: "ot-cli-ftd_br",
SearchPaths: []string{".", "./ot-rfsim/ot-versions", "./build/bin"},
}

func DefaultNodeConfig() NodeConfig {
return NodeConfig{
Expand Down Expand Up @@ -154,6 +165,13 @@ func (s *Simulation) NodeConfigFinalize(nodeCfg *NodeConfig) {
}
nodeCfg.InitScript = append(nodeCfg.InitScript, cslScript...)
}

// for Wifi interferer, run specific script.
if nodeCfg.Type == WIFI {
nodeCfg.InitScript = defaultWifiInterfererScript
ccaThresh := math.Round(s.Dispatcher().GetRadioModel().GetParameters().NoiseFloorDbm + wifiCcaThreshold)
nodeCfg.InitScript = append(nodeCfg.InitScript, fmt.Sprintf("ccathreshold %d", int(ccaThresh)))
}
}

func (cfg *ExecutableConfig) SearchPathsString() string {
Expand Down
Loading

0 comments on commit c4a89a1

Please sign in to comment.