Skip to content
Draft
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
10 changes: 10 additions & 0 deletions cli/CmdRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,14 @@ func (rt *CmdRunner) executeConfigVisualization(cc *CommandContext, cmd *ConfigV
opts.ChildTable = cmd.ChildTable.OnOrOff.On != nil
}

if cmd.LinkTxPower != nil {
opts.LinkStatsOpt.TxPower = cmd.LinkTxPower.OnOrOff.On != nil
}

if cmd.LinkRxRssi != nil {
opts.LinkStatsOpt.RxRssi = cmd.LinkRxRssi.OnOrOff.On != nil
}

sim.Dispatcher().SetVisualizationOptions(opts)
})

Expand All @@ -1174,6 +1182,8 @@ func (rt *CmdRunner) executeConfigVisualization(cc *CommandContext, cmd *ConfigV
cc.outputf("ack=%s\n", bool_to_onoroff(opts.AckMessage))
cc.outputf("rtb=%s\n", bool_to_onoroff(opts.RouterTable))
cc.outputf("ctb=%s\n", bool_to_onoroff(opts.ChildTable))
cc.outputf("txp=%s\n", bool_to_onoroff(opts.LinkStatsOpt.TxPower))
cc.outputf("rss=%s\n", bool_to_onoroff(opts.LinkStatsOpt.RxRssi))
}

func (rt *CmdRunner) enterNodeContext(nodeId NodeId) bool {
Expand Down
16 changes: 15 additions & 1 deletion cli/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ type ConfigVisualizationCmd struct {
UnicastMessage *CVUnicastMessage `| @@` //nolint
AckMessage *CVAckMessage `| @@` //nolint
RouterTable *CVRouterTable `| @@` //nolint
ChildTable *CVChildTable `| @@ )*` //nolint
ChildTable *CVChildTable `| @@` //nolint
LinkTxPower *CVLinkTxPower `| @@` //nolint
LinkRxRssi *CVLinkRxRssi `| @@ )*` //nolint
}

// noinspection GoVetStructTag
Expand Down Expand Up @@ -261,6 +263,18 @@ type CVChildTable struct {
OnOrOff OnOrOffFlag `@@` //nolint
}

// noinspection GoVetStructTag
type CVLinkTxPower struct {
Flag struct{} `"txp"` //nolint
OnOrOff OnOrOffFlag `@@` //nolint
}

// noinspection GoVetStructTag
type CVLinkRxRssi struct {
Flag struct{} `"rss"` //nolint
OnOrOff OnOrOffFlag `@@` //nolint
}

// noinspection GoVetStructTag
type CountDownCmd struct {
Cmd struct{} `"countdown"` //nolint
Expand Down
7 changes: 6 additions & 1 deletion cmd/otns-replay/grpc_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ waitloop:
}

func (gs *grpcService) NodeStats(req *pb.NodeStatsRequest, stream pb.VisualizeGrpcService_NodeStatsServer) error {
// TODO
// TODO - not implemented for replay yet
return nil
}

Expand All @@ -94,6 +94,11 @@ func (gs *grpcService) Command(context.Context, *pb.CommandRequest) (*pb.Command
return nil, errors.Errorf("can not run command on replay")
}

func (gs *grpcService) SelectNode(ctx context.Context, req *pb.SelectNodeRequest) (*pb.Empty, error) {
// No operation - ignored during replay
return &pb.Empty{}, nil
}

func (gs *grpcService) visualizeStream(stream pb.VisualizeGrpcService_VisualizeServer, visualizeDone chan struct{}) {
defer func() {
close(visualizeDone)
Expand Down
2 changes: 2 additions & 0 deletions dispatcher/Node.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type Node struct {
joinerSession *joinerSession
joinResults []*JoinResult
logger *logger.NodeLogger
linkStats map[uint64]NodeLinkStats // link stats for any Rx neighbor, indexed by neighbor's Node.ExtAddr
}

func newNode(d *Dispatcher, nodeid NodeId, cfg *NodeConfig) *Node {
Expand Down Expand Up @@ -119,6 +120,7 @@ func newNode(d *Dispatcher, nodeid NodeId, cfg *NodeConfig) *Node {
RadioNode: radiomodel.NewRadioNode(nodeid, radioCfg),
joinerState: OtJoinerStateIdle,
logger: logger.GetNodeLogger(d.cfg.OutputDir, d.cfg.SimulationId, cfg),
linkStats: make(map[uint64]NodeLinkStats),
}

nc.failureCtrl = newFailureCtrl(nc, NonFailTime)
Expand Down
71 changes: 50 additions & 21 deletions dispatcher/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ type Dispatcher struct {
nodesArray []*Node
deletedNodes map[NodeId]struct{}
aliveNodes map[NodeId]struct{}
selectedNodeId NodeId
pcap pcap.File
pcapFrameChan chan pcap.Frame
vis visualize.Visualizer
Expand Down Expand Up @@ -157,6 +158,7 @@ func NewDispatcher(ctx *progctx.ProgCtx, cfg *Config, cbHandler CallbackHandler)
nodesArray: make([]*Node, 0),
deletedNodes: map[NodeId]struct{}{},
aliveNodes: make(map[NodeId]struct{}),
selectedNodeId: InvalidNodeId,
extaddrMap: map[uint64]*Node{},
rloc16Map: rloc16Map{},
pcapFrameChan: make(chan pcap.Frame, 100000),
Expand Down Expand Up @@ -727,7 +729,7 @@ func (d *Dispatcher) sendRadioCommRxStartEvents(srcNode *Node, evt *Event) {
}

// dispatch the message to all in range that are receiving.
neighborNodes := map[NodeId]*Node{}
neighborNodes := make(map[NodeId]*Node, len(d.nodesArray))
for _, dstNode := range d.nodesArray {
if d.checkRadioReachable(srcNode, dstNode) {
d.sendOneRadioFrame(evt, srcNode, dstNode)
Expand Down Expand Up @@ -863,6 +865,10 @@ func (d *Dispatcher) sendOneRadioFrame(evt *Event, srcnode *Node, dstnode *Node)
if d.radioModel.OnEventDispatch(srcnode.RadioNode, dstnode.RadioNode, &evt2) {
// send the event plus time keeping - moves dstnode's time to the current send-event's time.
dstnode.sendEvent(&evt2)

// for every successful radio frame event dispatch, allow the Visualizer to update link statistics.
// This includes the start/end of frame events, and all of unicast/multicast/broadcast/interference.
d.vis.OnRadioFrameDispatch(srcnode.Id, dstnode.Id, &evt2)
}
}

Expand Down Expand Up @@ -1026,6 +1032,11 @@ func (d *Dispatcher) handleStatusPush(node *Node, data string) {
if d.visOptions.ChildTable {
d.vis.AddChildTable(srcid, extaddr)
}

// TODO when OT natively generates the 'parent' push, below needs to be removed.
childId := d.extaddrMap[extaddr].Id
d.vis.SetParent(childId, node.ExtAddr)
Comment on lines +1037 to +1038

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing d.extaddrMap[extaddr].Id could cause a panic if extaddr is not present in the map, as d.extaddrMap[extaddr] would be nil. While it's likely that a child node is known at this point, adding a check for existence would make the code more robust against unexpected states.

Suggested change
childId := d.extaddrMap[extaddr].Id
d.vis.SetParent(childId, node.ExtAddr)
child := d.extaddrMap[extaddr]
if child != nil {
d.vis.SetParent(child.Id, node.ExtAddr)
}


d.Counters.TopologyChanges++
case "child_removed":
extaddr, err := strconv.ParseUint(sp[1], 16, 64)
Expand All @@ -1035,6 +1046,7 @@ func (d *Dispatcher) handleStatusPush(node *Node, data string) {
}
d.Counters.TopologyChanges++
case "parent":
// TODO note this is currently not generated by OT.
extaddr, err := strconv.ParseUint(sp[1], 16, 64)
logger.PanicIfError(err)
d.vis.SetParent(srcid, extaddr)
Expand Down Expand Up @@ -1234,8 +1246,8 @@ func (d *Dispatcher) GetWatchingNodes() []NodeId {
return watchingNodeIds
}

func (d *Dispatcher) GetNode(id NodeId) *Node {
return d.nodes[id]
func (d *Dispatcher) GetNode(nodeid NodeId) *Node {
return d.nodes[nodeid]
}

func (d *Dispatcher) GetFailedCount() int {
Expand All @@ -1248,44 +1260,44 @@ func (d *Dispatcher) GetFailedCount() int {
return failCount
}

func (d *Dispatcher) SetNodePos(id NodeId, x, y, z int) {
node := d.nodes[id]
func (d *Dispatcher) SetNodePos(nodeid NodeId, x, y, z int) {
node := d.nodes[nodeid]
logger.AssertNotNil(node)

node.X, node.Y, node.Z = x, y, z
node.RadioNode.SetNodePos(x, y, z)
d.vis.SetNodePos(id, x, y, z)
d.vis.SetNodePos(nodeid, x, y, z)
}

func (d *Dispatcher) DeleteNode(id NodeId) {
node := d.nodes[id]
func (d *Dispatcher) DeleteNode(nodeid NodeId) {
node := d.nodes[nodeid]
logger.AssertNotNil(node)

delete(d.nodes, id)
delete(d.nodes, nodeid)
d.reconstructNodesArray()
d.Counters.TopologyChanges++
delete(d.aliveNodes, id)
delete(d.watchingNodes, id)
delete(d.aliveNodes, nodeid)
delete(d.watchingNodes, nodeid)
if node.Rloc16 != InvalidRloc16 {
d.rloc16Map.Remove(node.Rloc16, node)
}
if node.ExtAddr != InvalidExtAddr {
logger.AssertTrue(d.extaddrMap[node.ExtAddr] == node)
delete(d.extaddrMap, node.ExtAddr)
}
d.alarmMgr.DeleteNode(id)
d.deletedNodes[id] = struct{}{}
d.energyAnalyser.DeleteNode(id)
d.vis.DeleteNode(id)
d.radioModel.DeleteNode(id)
d.eventQueue.DisableEventsForNode(id)
d.alarmMgr.DeleteNode(nodeid)
d.deletedNodes[nodeid] = struct{}{}
d.energyAnalyser.DeleteNode(nodeid)
d.vis.DeleteNode(nodeid)
d.radioModel.DeleteNode(nodeid)
d.eventQueue.DisableEventsForNode(nodeid)
d.updateNodeStats()
}

// SetNodeFailed sets the radio of the node to failed (true) or operational (false) state.
// Setting this will disable the automatic failure control (FailureCtrl).
func (d *Dispatcher) SetNodeFailed(id NodeId, fail bool) {
node := d.nodes[id]
func (d *Dispatcher) SetNodeFailed(nodeid NodeId, fail bool) {
node := d.nodes[nodeid]
logger.AssertNotNil(node)

// if radio is set to on/off explicitly, failureCtrl should not be used anymore
Expand Down Expand Up @@ -1376,8 +1388,8 @@ func (d *Dispatcher) NotifyCommand(nodeid NodeId) {
d.setAlive(nodeid)
}

// NotifyNodeFailure is called by other goroutines to notify the dispatcher that a node process has
// failed. From failed nodes, we don't expect further messages and they can't be alive.
// NotifyNodeProcessFailure is called by other goroutines to notify the dispatcher that a node process has
// failed. From failed nodes, we don't expect further messages, and they can't be alive.
func (d *Dispatcher) NotifyNodeProcessFailure(nodeid NodeId) {
d.eventChan <- &Event{
Delay: 0,
Expand Down Expand Up @@ -1511,3 +1523,20 @@ func (d *Dispatcher) handleRadioState(node *Node, evt *Event) {
})
}
}

func (d *Dispatcher) OnNodeSelected(nodeid NodeId) {
if nodeid == InvalidNodeId {
if d.selectedNodeId != InvalidNodeId {
// when node deselected - hide linkstats for all nodes
optNotVisible := visualize.LinkStatsOptions{
Visible: false,
}
d.vis.SetLinkStats(AllNodesId, optNotVisible)
}
} else if d.visOptions.LinkStatsOpt.Visible && d.nodes[nodeid] != nil {
// enable linkstats for each selected node
d.vis.SetLinkStats(nodeid, d.visOptions.LinkStatsOpt)
}

d.selectedNodeId = nodeid
}
18 changes: 15 additions & 3 deletions dispatcher/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ import (
"github.com/openthread/ot-ns/visualize"
)

// FIXME - support deletion of stale ExtAddrs.

type TxStats struct {
DurationUs uint64
TxPowerDbm int8
RxRssiDbm int8
}

type NodeLinkStats struct {
LastTx TxStats
}

// updateNodeStats calculates fresh node statistics and sends it to the Visualizers.
func (d *Dispatcher) updateNodeStats() {
s := d.calcStats()
Expand Down Expand Up @@ -73,8 +85,8 @@ func (d *Dispatcher) visSendTimeWindowStats(stats *TimeWindowStats) {
WinStartUs: stats.WinStartUs,
WinWidthUs: stats.WinWidthUs,
NodePhyStats: stats.PhyStats,
PhyTxBytes: make(map[NodeId]uint64),
ChanSampleCount: make(map[NodeId]uint64),
PhyTxBytes: make(map[NodeId]uint64, len(stats.PhyStats)),
ChanSampleCount: make(map[NodeId]uint64, len(stats.PhyStats)),
}
for id, st := range stats.PhyStats {
statsInfo.PhyTxBytes[id] = st.TxBytes
Expand Down Expand Up @@ -135,7 +147,7 @@ func countRole(nodes map[NodeId]*Node, role OtDeviceRole) int {
func countUniquePts(nodes map[NodeId]*Node) int {
pts := make(map[uint32]struct{})
for _, n := range nodes {
if n.PartitionId > 0 {
if n.PartitionId != InvalidPartitionId {
pts[n.PartitionId] = struct{}{}
}
}
Expand Down
8 changes: 7 additions & 1 deletion dispatcher/visualization_options.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020, 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 @@ -26,12 +26,17 @@

package dispatcher

import (
"github.com/openthread/ot-ns/visualize"
)

type VisualizationOptions struct {
BroadcastMessage bool
UnicastMessage bool
AckMessage bool
RouterTable bool
ChildTable bool
LinkStatsOpt visualize.LinkStatsOptions
}

func defaultVisualizationOptions() VisualizationOptions {
Expand All @@ -41,5 +46,6 @@ func defaultVisualizationOptions() VisualizationOptions {
AckMessage: false,
RouterTable: true,
ChildTable: true,
LinkStatsOpt: visualize.DefaultLinkStatsOptions(),
}
}
435 changes: 363 additions & 72 deletions pylibs/otns/proto/visualize_grpc_pb2.py

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions pylibs/otns/proto/visualize_grpc_pb2_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def __init__(self, channel):
request_serializer=visualize__grpc__pb2.NodeStatsRequest.SerializeToString,
response_deserializer=visualize__grpc__pb2.VisualizeEvent.FromString,
)
self.SelectNode = channel.unary_unary(
'/visualize_grpc_pb.VisualizeGrpcService/SelectNode',
request_serializer=visualize__grpc__pb2.SelectNodeRequest.SerializeToString,
response_deserializer=visualize__grpc__pb2.Empty.FromString,
)


class VisualizeGrpcServiceServicer(object):
Expand Down Expand Up @@ -68,6 +73,13 @@ def NodeStats(self, request, context):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def SelectNode(self, request, context):
# missing associated documentation comment in .proto file
pass
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_VisualizeGrpcServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
Expand All @@ -91,6 +103,11 @@ def add_VisualizeGrpcServiceServicer_to_server(servicer, server):
request_deserializer=visualize__grpc__pb2.NodeStatsRequest.FromString,
response_serializer=visualize__grpc__pb2.VisualizeEvent.SerializeToString,
),
'SelectNode': grpc.unary_unary_rpc_method_handler(
servicer.SelectNode,
request_deserializer=visualize__grpc__pb2.SelectNodeRequest.FromString,
response_serializer=visualize__grpc__pb2.Empty.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'visualize_grpc_pb.VisualizeGrpcService', rpc_method_handlers)
Expand Down
2 changes: 1 addition & 1 deletion radiomodel/radiomodelMutualInterference.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (rm *RadioModelMutualInterference) OnEventDispatch(src *RadioNode, dst *Rad
// compute the RSSI and store in the event
evt.RadioCommData.PowerDbm = clipRssi(rm.GetTxRssi(src, dst))

// check for interference by other signals and apply to event.
// check for interference by other signals and apply to event: may result in FCS error.
rm.applyInterference(src, dst, evt)

case EventTypeRadioChannelSample:
Expand Down
Loading
Loading