diff --git a/cli/README.md b/cli/README.md index 493caad8..fa605861 100644 --- a/cli/README.md +++ b/cli/README.md @@ -57,7 +57,8 @@ Add a node to the simulation and get the node ID. add [x ] [y ] [rr ] [id ] [restore] [exe ] [v11|v12|v13|v131] ``` -Node ID can be specified, otherwise OTNS assigns the next available +The `` 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 @@ -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. @@ -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 @@ -834,6 +838,7 @@ rxsens -100 (dBm) ccath -75 (dBm) cslacc 45 (PPM) cslunc 10 (10-us) +txintf 0 (%) Done > ``` @@ -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. diff --git a/cli/ast.go b/cli/ast.go index a5629ff7..b921ccf6 100644 --- a/cli/ast.go +++ b/cli/ast.go @@ -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 diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 3d658613..e529d835 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -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: @@ -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) @@ -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 { diff --git a/logger/nodelogger.go b/logger/nodelogger.go index d939611d..93720f86 100644 --- a/logger/nodelogger.go +++ b/logger/nodelogger.go @@ -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) } diff --git a/ot-rfsim b/ot-rfsim index 2db462e4..7279452f 160000 --- a/ot-rfsim +++ b/ot-rfsim @@ -1 +1 @@ -Subproject commit 2db462e416ed37e4892070c1a0f93aee4c627723 +Subproject commit 7279452f86b281da02e4d5ce17760ecebb3cf950 diff --git a/pylibs/unittests/test_basic.py b/pylibs/unittests/test_basic.py index 4bd4c261..8c5ed19d 100755 --- a/pylibs/unittests/test_basic.py +++ b/pylibs/unittests/test_basic.py @@ -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() diff --git a/radiomodel/radiomodelIdeal.go b/radiomodel/radiomodelIdeal.go index 4861e0c9..4bb47869 100644 --- a/radiomodel/radiomodelIdeal.go +++ b/radiomodel/radiomodelIdeal.go @@ -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) @@ -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) { diff --git a/radiomodel/radiomodelMutualInterference.go b/radiomodel/radiomodelMutualInterference.go index 062a0bdb..42f4bdae 100644 --- a/radiomodel/radiomodelMutualInterference.go +++ b/radiomodel/radiomodelMutualInterference.go @@ -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) @@ -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) { diff --git a/simulation/node.go b/simulation/node.go index b30e7920..7278489d 100644 --- a/simulation/node.go +++ b/simulation/node.go @@ -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 } } @@ -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 @@ -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() { diff --git a/simulation/node_config.go b/simulation/node_config.go index 958a5a45..66207f2b 100644 --- a/simulation/node_config.go +++ b/simulation/node_config.go @@ -28,6 +28,7 @@ package simulation import ( "fmt" + "math" "os" "path/filepath" "strings" @@ -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{ @@ -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{ @@ -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 { diff --git a/simulation/simulation_config.go b/simulation/simulation_config.go index e892aa36..3a1b3c45 100644 --- a/simulation/simulation_config.go +++ b/simulation/simulation_config.go @@ -37,8 +37,6 @@ const ( DefaultNetworkKey = "00112233445566778899aabbccddeeff" DefaultPanid = 0xface DefaultChannel = 11 - DefaultCslPeriod = 3 * 1000 // in units of 160 us - DefaultCslPeriodUs = 160 * DefaultCslPeriod // MUST be multiple of 160 us ) type Config struct { diff --git a/types/node_config.go b/types/node_config.go index aac95f13..7312f3eb 100644 --- a/types/node_config.go +++ b/types/node_config.go @@ -75,6 +75,11 @@ func (cfg *NodeConfig) UpdateNodeConfigFromType() { cfg.IsMtd = false cfg.IsBorderRouter = true cfg.RxOffWhenIdle = false + case WIFI: + cfg.IsRouter = true + cfg.IsMtd = false + cfg.IsBorderRouter = false + cfg.RxOffWhenIdle = false default: panic("unknown node type cfg.Type") } diff --git a/types/ot_types.go b/types/ot_types.go index 66cdf6f2..7e281f91 100644 --- a/types/ot_types.go +++ b/types/ot_types.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023, The OTNS Authors. +// Copyright (c) 2023-2024, The OTNS Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -29,9 +29,10 @@ package types // OT_ERROR_* error codes from OpenThread that can be sent by OT-NS to the OT nodes. // (See OpenThread error.h for details) const ( - OT_ERROR_NONE = 0 - OT_ERROR_ABORT = 11 - OT_ERROR_FCS = 17 + OT_ERROR_NONE = 0 + OT_ERROR_ABORT = 11 + OT_ERROR_FCS = 17 + OT_TX_TYPE_INTF = 192 // special status used for interference signals Tx ) const ( diff --git a/types/rfsim_types.go b/types/rfsim_types.go index 6d9a6ed0..ff0b7043 100644 --- a/types/rfsim_types.go +++ b/types/rfsim_types.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023, The OTNS Authors. +// Copyright (c) 2023-2024, The OTNS Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -44,6 +44,7 @@ const ( ParamCcaThreshold RfSimParam = 1 ParamCslAccuracy RfSimParam = 2 ParamCslUncertainty RfSimParam = 3 + ParamTxInterferer RfSimParam = 4 ParamUnknown RfSimParam = 255 ) @@ -54,9 +55,9 @@ const ( RfSimValueInvalid RfSimParamValue = math.MaxInt32 ) -var RfSimParamsList = []RfSimParam{ParamRxSensitivity, ParamCcaThreshold, ParamCslAccuracy, ParamCslUncertainty} -var RfSimParamNamesList = []string{"rxsens", "ccath", "cslacc", "cslunc"} -var RfSimParamUnitsList = []string{"dBm", "dBm", "PPM", "10-us"} +var RfSimParamsList = []RfSimParam{ParamRxSensitivity, ParamCcaThreshold, ParamCslAccuracy, ParamCslUncertainty, ParamTxInterferer} +var RfSimParamNamesList = []string{"rxsens", "ccath", "cslacc", "cslunc", "txintf"} +var RfSimParamUnitsList = []string{"dBm", "dBm", "PPM", "10-us", "%"} func ParseRfSimParam(parName string) RfSimParam { for i := 0; i < len(RfSimParamsList); i++ { diff --git a/types/types.go b/types/types.go index 2e6fd1c4..3551af2a 100644 --- a/types/types.go +++ b/types/types.go @@ -55,6 +55,7 @@ const ( BR = "br" MTD = "mtd" FTD = "ftd" + WIFI = "wifi" // Wi-Fi interferer node ) func GetNodeName(id NodeId) string { @@ -188,6 +189,8 @@ const ( RFSIM_RADIO_SUBSTATE_RX_ENERGY_SCAN RadioSubStates = iota RFSIM_RADIO_SUBSTATE_STARTUP RadioSubStates = iota RFSIM_RADIO_SUBSTATE_INVALID RadioSubStates = iota + RFSIM_RADIO_SUBSTATE_AWAIT_CCA RadioSubStates = iota + RFSIM_RADIO_SUBSTATE_CW_BACKOFF RadioSubStates = iota ) func (s RadioSubStates) String() string { @@ -224,6 +227,10 @@ func (s RadioSubStates) String() string { return "Startup" case RFSIM_RADIO_SUBSTATE_INVALID: return "Invalid" + case RFSIM_RADIO_SUBSTATE_AWAIT_CCA: + return "WaitCCA" + case RFSIM_RADIO_SUBSTATE_CW_BACKOFF: + return "CwBackf" default: return "???????" }