Skip to content

Commit

Permalink
Feature: 'radioparam' command to change radiomodel parameters (#73)
Browse files Browse the repository at this point in the history
* [cli][radiomodel] Adding CLI command for changing radiomodel parameters at runtime.

* [pylibs] Added unit test for 'radioparams'.

* [cli] 'radioparam' command added to README.
  • Loading branch information
EskoDijk authored Sep 27, 2023
1 parent 103874a commit 248936c
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 79 deletions.
65 changes: 65 additions & 0 deletions cli/CmdRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -252,6 +253,8 @@ func (rt *CmdRunner) execute(cmd *Command, output io.Writer) {
rt.executeNetInfo(cc, cc.NetInfo)
} else if cmd.RadioModel != nil {
rt.executeRadioModel(cc, cc.RadioModel)
} else if cmd.RadioParam != nil {
rt.executeRadioParam(cc, cc.RadioParam)
} else if cmd.Energy != nil {
rt.executeEnergy(cc, cc.Energy)
} else if cmd.LogLevel != nil {
Expand Down Expand Up @@ -728,6 +731,68 @@ func (rt *CmdRunner) executeRadioModel(cc *CommandContext, cmd *RadioModelCmd) {
}
}

func displayRadioParam(val *reflect.Value) (string, bool) {
if val.CanFloat() {
f := val.Float()
if f == radiomodel.UndefinedDbValue {
return "undefined", false
}
return strconv.FormatFloat(f, 'f', -1, 64), true
} else if val.Bool() {
return "1", true
}
return "0", true
}

func (rt *CmdRunner) executeRadioParam(cc *CommandContext, cmd *RadioParamCmd) {
rt.postAsyncWait(cc, func(sim *simulation.Simulation) {
rp := sim.Dispatcher().GetRadioModel().GetParameters()
rpVal := reflect.ValueOf(rp).Elem()
rpTyp := reflect.TypeOf(rp).Elem()

// variant: radioparam
if len(cmd.Param) == 0 {
for i := 0; i < rpVal.NumField(); i++ {
fname := rpTyp.Field(i).Name
fval := rpVal.Field(i)
s, isDefined := displayRadioParam(&fval)
if isDefined {
cc.outputf("%-20s %s\n", fname, s)
}
}
return
}

// variant: radioparam <param-name>
// variant: radioparam <param-name> <param-value>
var fval reflect.Value
_, ok := rpTyp.FieldByName(cmd.Param)
if !ok {
cc.errorf("Unknown radiomodel parameter: %s", cmd.Param)
return
}

fval = rpVal.FieldByName(cmd.Param)
isFloat := fval.CanFloat()
if cmd.Val == nil { // show value of single parameter
s, _ := displayRadioParam(&fval)
cc.outputf("%s\n", s)
return
}

// set new parameter value
newVal := *cmd.Val
if cmd.Sign == "-" {
newVal = -newVal
}
if !isFloat { // if we're setting a bool parameter, use <=0 -> false ; >0 -> true
fval.SetBool(newVal > 0)
} else {
fval.SetFloat(newVal)
}
})
}

func (rt *CmdRunner) executeLogLevel(cc *CommandContext, cmd *LogLevelCmd) {
if cmd.Level == "" {
cc.outputf("%v\n", logger.GetLevelString(rt.sim.GetLogLevel()))
Expand Down
32 changes: 32 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Python libraries use the CLI to manage simulations.
* [plr](#plr)
* [radio](#radio-node-id-node-id--on--off--ft-fail-duration-fail-interval)
* [radiomodel](#radiomodel-modelname)
* [radioparam](#radioparam-param-name-new-value)
* [scan](#scan-node-id)
* [speed](#speed)
* [title](#title-string)
Expand Down Expand Up @@ -530,6 +531,37 @@ Done
>
```

### radioparam \[param-name\] \[new-value\]

Get or set parameters of the current radiomodel. Use without the optional arguments to get a list of all current
radiomodel parameters. Add the `param-name` to get only the value of that parameter. If both `param-name` and `new-value`
are provided, the parameter value is set to `new-value`. It has to be a numeric value.

```bash
> radioparam
MeterPerUnit 0.1
IsDiscLimit 0
RssiMinDbm -126
RssiMaxDbm 126
ExponentDb 17.3
FixedLossDb 40
NlosExponentDb 38.3
NlosFixedLossDb 26.77
NoiseFloorDbm -95
SnrMinThresholdDb -4
ShadowFadingSigmaDb 8.03
Done
> radioparam MeterPerUnit
0.1
Done
> radioparam MeterPerUnit 0.5
Done
> radioparam MeterPerUnit
0.5
Done
>
```

### scan \<node-id\>

Perform a network scan by the indicated node.
Expand Down
9 changes: 9 additions & 0 deletions cli/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Command struct {
Plr *PlrCmd `| @@` //nolint
Radio *RadioCmd `| @@` //nolint
RadioModel *RadioModelCmd `| @@` //nolint
RadioParam *RadioParamCmd `| @@` //nolint
Scan *ScanCmd `| @@` //nolint
Speed *SpeedCmd `| @@` //nolint
Time *TimeCmd `| @@` //nolint
Expand Down Expand Up @@ -459,6 +460,14 @@ type RadioModelCmd struct {
Model string `[(@Ident|@Int)]` //nolint
}

// noinspection GoVetStructTag
type RadioParamCmd struct {
Cmd struct{} `"radioparam"` //nolint
Param string `[@Ident]` //nolint
Sign string `[@("-"|"+")]` //nolint
Val *float64 `[ (@Int|@Float) ]` //nolint
}

// noinspection GoVetStructTag
type LogLevelCmd struct {
Cmd struct{} `"log"` //nolint
Expand Down
8 changes: 8 additions & 0 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,20 @@ func TestParseBytes(t *testing.T) {

assert.True(t, parseBytes([]byte("plr"), &cmd) == nil && cmd.Plr != nil && cmd.Plr.Val == nil)
assert.True(t, parseBytes([]byte("plr 1"), &cmd) == nil && cmd.Plr != nil && *cmd.Plr.Val == 1)
assert.True(t, parseBytes([]byte("plr 0.78910"), &cmd) == nil && cmd.Plr != nil && *cmd.Plr.Val == 0.78910)

assert.True(t, parseBytes([]byte("radio 1 on"), &cmd) == nil && cmd.Radio != nil)
assert.True(t, parseBytes([]byte("radio 1 off"), &cmd) == nil && cmd.Radio != nil)
assert.True(t, parseBytes([]byte("radio 1 2 3 on"), &cmd) == nil && cmd.Radio != nil)
assert.True(t, parseBytes([]byte("radio 4 5 6 off"), &cmd) == nil && cmd.Radio != nil)
assert.True(t, parseBytes([]byte("radio 4 5 6 ft 10 60"), &cmd) == nil && cmd.Radio != nil)
assert.True(t, parseBytes([]byte("radiomodel AnyName"), &cmd) == nil && cmd.RadioModel != nil && cmd.RadioModel.Model == "AnyName")
assert.True(t, parseBytes([]byte("radiomodel 42"), &cmd) == nil && cmd.RadioModel != nil && cmd.RadioModel.Model == "42")
assert.True(t, parseBytes([]byte("radiomodel"), &cmd) == nil && cmd.RadioModel != nil && cmd.RadioModel.Model == "")
assert.True(t, parseBytes([]byte("radioparam"), &cmd) == nil && cmd.RadioParam != nil && cmd.RadioParam.Param == "" && cmd.RadioParam.Val == nil)
assert.True(t, parseBytes([]byte("radioparam name1"), &cmd) == nil && cmd.RadioParam != nil && cmd.RadioParam.Param == "name1" && cmd.RadioParam.Val == nil)
assert.True(t, parseBytes([]byte("radioparam name1 0.43"), &cmd) == nil && cmd.RadioParam != nil && cmd.RadioParam.Param == "name1" && *cmd.RadioParam.Val == 0.43)
assert.True(t, parseBytes([]byte("radioparam name2 -23.512"), &cmd) == nil && cmd.RadioParam != nil && cmd.RadioParam.Param == "name2" && cmd.RadioParam.Sign == "-" && *cmd.RadioParam.Val == 23.512)

assert.True(t, parseBytes([]byte("scan 1"), &cmd) == nil && cmd.Scan != nil)

Expand Down
38 changes: 37 additions & 1 deletion pylibs/otns/cli/OTNS.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,55 @@ def speed(self, speed: float) -> None:
@property
def radiomodel(self) -> str:
"""
Get radiomodel used for simulation.
:return: current radio model used
"""
return self._expect_str(self._do_command(f'radiomodel'))

@radiomodel.setter
def radiomodel(self, model: str) -> None:
"""
Set radiomodel for simulation.
Set radiomodel to be used for simulation. Setting a radiomodel also resets all the
parameters.
:param model: name of new radio model to use. Default is "MutualInterference".
"""
assert self._do_command(f'radiomodel {model}')[0] == model

def radioparams(self) -> Dict[int, Collection[int]]:
"""
Get radiomodel parameters.
:return: dict with parameter strings as keys and parameter values as values
"""
output = self._do_command('radioparam')
params = {}
for line in output:
line = line.split()
parname = line[0]
parvalue = float(line[1])
params[parname] = parvalue
return params

def get_radioparam(self, parname: str):
"""
Get a radiomodel parameter.
:param parname: parameter name (string)
:return parameter value
"""
return float(self._do_command(f'radioparam {parname}')[0])

def set_radioparam(self, parname: str, parvalue: float):
"""
Set a radiomodel parameter to the specified value.
:param parname: parameter name (string)
:param parvalue: parameter value (float)
"""
self._do_command(f'radioparam {parname} {parvalue}')

@property
def loglevel(self) -> str:
"""
Expand Down
31 changes: 25 additions & 6 deletions pylibs/unittests/test_radiomodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,19 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

import unittest

from OTNSTestCase import OTNSTestCase
from test_basic import BasicTests
from test_commissioning import CommissioningTests
from test_ping import PingTests
from test_csl import CslTests
from otns.cli import errors, OTNS
from otns.cli import errors


class RadioModelTests(OTNSTestCase):

# override
def setUp(self):
super().setUp()
self.ns.radiomodel = 'MutualInterference'

def testRadioModelSwitching(self):
ns = self.ns
ns.radiomodel = 'Ideal'
Expand Down Expand Up @@ -92,6 +88,29 @@ def testRadioModelSwitching(self):
ns.go(200)
self.assertFormPartitions(1)

def testRadioParametersGet(self):
ns = self.ns
rp = ns.radioparams()
self.assertEqual(0,rp['IsDiscLimit'])
self.assertEqual(-126,rp['RssiMinDbm'])
self.assertEqual(126,rp['RssiMaxDbm'])
self.assertEqual(rp['NoiseFloorDbm'], ns.get_radioparam('NoiseFloorDbm'))
self.assertEqual(rp['ExponentDb'], ns.get_radioparam('ExponentDb'))
with self.assertRaises(errors.OTNSCliError):
ns.get_radioparam('NonExistingParam')

def testRadioParametersSet(self):
ns = self.ns
ns.set_radioparam('NlosExponentDb', 48.0)
rp = ns.radioparams()
self.assertEqual(48.0, rp['NlosExponentDb'])
self.assertEqual(0, rp['IsDiscLimit'])

ns.radiomodel = 'MIDisc' # selecting a model resets the parameters
rp = ns.radioparams()
self.assertNotEquals(48.0, rp['NlosExponentDb'])
self.assertEqual(1, rp['IsDiscLimit'])


class BasicTests_Ideal(BasicTests):

Expand Down
51 changes: 24 additions & 27 deletions radiomodel/pathloss_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,36 @@ package radiomodel

import "math"

func newIndoorModelParamsItu() *RadioModelParams {
return &RadioModelParams{
MeterPerUnit: defaultMeterPerUnit,
ExponentDb: 30.0,
FixedLossDb: 20.0*math.Log10(2400) - 28.0,
}
// custom parameter rounding function
func paround(param float64) float64 {
return math.Round(param*100.0) / 100.0
}

// ITU-T model
func setIndoorModelParamsItu(params *RadioModelParams) {
params.ExponentDb = 30.0
params.FixedLossDb = paround(20.0*math.Log10(2400) - 28.0)
}

// see 3GPP TR 38.901 V17.0.0, Table 7.4.1-1: Pathloss models.
func newIndoorModelParams3gpp() *RadioModelParams {
return &RadioModelParams{
MeterPerUnit: defaultMeterPerUnit,
ExponentDb: 17.3,
FixedLossDb: 32.4 + 20*math.Log10(2.4),
NlosExponentDb: 38.3,
NlosFixedLossDb: 17.3 + 24.9*math.Log10(2.4),
NoiseFloorDbm: noiseFloorIndoorDbm,
SnrMinThresholdDb: -4.0, // see calcber.m Octave file
ShadowFadingSigmaDb: 8.03,
}
func setIndoorModelParams3gpp(params *RadioModelParams) {
params.ExponentDb = 17.3
params.FixedLossDb = paround(32.4 + 20*math.Log10(2.4))
params.NlosExponentDb = 38.3
params.NlosFixedLossDb = paround(17.3 + 24.9*math.Log10(2.4))
params.NoiseFloorDbm = noiseFloorIndoorDbm
params.SnrMinThresholdDb = -4.0 // see calcber.m Octave file
params.ShadowFadingSigmaDb = 8.03
}

// experimental outdoor model with LoS
func newOutdoorModelParams() *RadioModelParams {
return &RadioModelParams{
MeterPerUnit: 0.5,
ExponentDb: 17.3,
FixedLossDb: 32.4 + 20*math.Log10(2.4),
NoiseFloorDbm: noiseFloorIndoorDbm,
SnrMinThresholdDb: -4.0, // see calcber.m Octave file
ShadowFadingSigmaDb: 3.0,
}
func setOutdoorModelParams(params *RadioModelParams) {
params.MeterPerUnit = 0.5
params.ExponentDb = 17.3
params.FixedLossDb = paround(32.4 + 20*math.Log10(2.4))
params.NoiseFloorDbm = noiseFloorIndoorDbm
params.SnrMinThresholdDb = -4.0 // see calcber.m Octave file
params.ShadowFadingSigmaDb = 3.0
}

// computeIndoorRssi computes the RSSI for a receiver at distance dist, using a simple indoor exponent loss model.
Expand Down Expand Up @@ -92,7 +90,6 @@ func computeIndoorRssi3gpp(dist float64, txPower DbValue, modelParams *RadioMode
pathloss = math.Max(pathloss, pathlossNLOS)
}
}

rssi := txPower - pathloss
return rssi
}
Loading

0 comments on commit 248936c

Please sign in to comment.