├── examples ├── result-visualization │ ├── requirements.txt │ ├── config.json.tmpl │ └── README.md ├── web │ ├── config.json │ └── README.md ├── grid │ ├── README.md │ └── config.json ├── hierarchical │ ├── README.md │ └── config.json ├── mesh │ ├── config.json │ ├── config-of0.json │ ├── README.md │ └── config-manual.json ├── line │ ├── config.json │ ├── README.md │ └── config-manual.json ├── large-network │ └── README.md ├── multirun │ ├── README.md │ └── config.json ├── star │ ├── README.md │ ├── config-manual.json │ └── config.json └── scripting │ ├── README.md │ ├── config.json │ └── script.js ├── tests ├── tsch │ ├── empty.json │ ├── desync.json │ ├── different-hopping-sequences.json │ ├── keepalive.json │ ├── slot-duration.json │ ├── eb.json │ ├── subslots.json │ ├── join-hopseq-nojoin.json │ ├── non-standard-slots.json │ ├── non-standard-slots.js │ ├── join-hopseq.json │ ├── start-joined.json │ ├── subslots.js │ └── Makefile ├── Makefile.include ├── generation │ ├── grid.json │ ├── line.json │ ├── star.json │ ├── mesh.json │ └── Makefile ├── invert_grep.sh ├── routing │ ├── rpl-of0.json │ ├── rpl-recover-2nodes.json │ ├── rpl-recover-2nodes-of0.json │ ├── rpl-leaf.json │ ├── nullrouting.json │ ├── rpl-dao-ack.json │ ├── rpl-probing.json │ ├── rpl-recover-3nodes.json │ ├── rpl-no-dao-ack.json │ ├── rpl-no-probing.json │ ├── rpl-recover-3nodes-of0.json │ ├── disable-enable-links.js │ ├── rpl-loops.json │ ├── rpl-loops-drop.json │ ├── rpl-loops-of0.json │ ├── rpl-loops-drop-of0.json │ ├── lf.json │ └── Makefile ├── link_model │ ├── trace.json │ ├── udgm-constant-loss.json │ ├── logistic-loss.json │ ├── udgm.json │ ├── pister-hack.json │ ├── trace.k7 │ ├── Makefile │ ├── fixed-per-channel-quality.json │ └── fixed.json ├── Makefile ├── stats │ ├── Makefile │ └── duty-cycle.json ├── scheduling │ ├── 100percent-line.json │ ├── 100percent-star.json │ ├── 6tisch-min.json │ ├── lf.json │ ├── orchestra-rb-ns.json │ ├── 100percent-star.js │ ├── orchestra-link-based.json │ ├── orchestra-rb-storing.json │ ├── orchestra-sb-storing.json │ ├── orchestra-with-shorter-active-period.json │ ├── orchestra-special-for-root.json │ ├── orchestra-with-nondefault-periods.json │ └── Makefile ├── ip │ ├── fragmentation.json │ ├── no-fragmentation.json │ └── Makefile ├── applications │ ├── collection.json │ ├── dissemination.json │ ├── query.json │ └── Makefile ├── check_nojoin.mjs ├── check_good_par.mjs ├── mobility │ ├── Makefile │ ├── line.json │ └── random-waypoint.json ├── check_bad_par.mjs ├── check_par.mjs ├── check_log_stats.mjs ├── check_pdr.mjs └── check_stats_value.mjs ├── .github ├── codeql.yml └── workflows │ ├── main.yml │ └── codeql.yml ├── web ├── favicon.ico ├── images │ └── tsch-sim2.png ├── font-awesome │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff ├── js │ ├── const.js │ ├── utils.js │ └── notifications.js └── style.css ├── lgtm.yml ├── tsch-sim-web-interface.bat ├── tsch-sim-windows.bat ├── tsch-sim-web-interface.sh ├── tsch-sim.sh ├── LICENSE ├── source ├── expose.js ├── status.mjs ├── dirnames.mjs ├── routing_null.mjs ├── routing_lf.mjs ├── random.mjs ├── heap.mjs ├── log.mjs ├── scheduler_6tisch_min.mjs ├── constants.mjs ├── packet.mjs ├── time.mjs ├── collision_analyzer.mjs ├── route.mjs ├── mobility.mjs └── slotframe.mjs └── README.md /examples/result-visualization/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | -------------------------------------------------------------------------------- /tests/tsch/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "RESULTS_DIR": "./results" 3 | } 4 | -------------------------------------------------------------------------------- /.github/codeql.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | - source 3 | - tests 4 | - web/js 5 | - examples 6 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/favicon.ico -------------------------------------------------------------------------------- /examples/web/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "WEB_ENABLED": true, 3 | "SAVE_RESULTS" : false 4 | } 5 | -------------------------------------------------------------------------------- /web/images/tsch-sim2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/images/tsch-sim2.png -------------------------------------------------------------------------------- /web/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /web/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /web/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edi-riga/tsch-sim/HEAD/web/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /tests/Makefile.include: -------------------------------------------------------------------------------- 1 | 2 | 3 | NODE := $(shell command -v nodejs 2> /dev/null) 4 | ifeq ($(NODE),) 5 | NODE := $(shell command -v node 2> /dev/null) 6 | endif 7 | -------------------------------------------------------------------------------- /tests/generation/grid.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 0, 3 | "RESULTS_DIR": "./results", 4 | "POSITIONING_NUM_NODES": 100, 5 | "POSITIONING_LAYOUT": "Grid" 6 | } 7 | -------------------------------------------------------------------------------- /tests/generation/line.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 0, 3 | "RESULTS_DIR": "./results", 4 | "POSITIONING_NUM_NODES": 100, 5 | "POSITIONING_LAYOUT": "Line" 6 | } 7 | -------------------------------------------------------------------------------- /tests/generation/star.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 0, 3 | "RESULTS_DIR": "./results", 4 | "POSITIONING_NUM_NODES": 100, 5 | "POSITIONING_LAYOUT": "Star" 6 | } 7 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | javascript: 3 | index: 4 | include: 5 | - source 6 | - tests 7 | - web/js 8 | python: 9 | index: 10 | include: 11 | - examples 12 | -------------------------------------------------------------------------------- /tests/generation/mesh.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 0, 3 | "RESULTS_DIR": "./results", 4 | "POSITIONING_NUM_NODES": 100, 5 | "POSITIONING_NUM_DEGREES": 7, 6 | "POSITIONING_LAYOUT": "Mesh" 7 | } 8 | -------------------------------------------------------------------------------- /examples/web/README.md: -------------------------------------------------------------------------------- 1 | Starting the simulator with this configuration will start the web interface backend. 2 | 3 | Doing this bypasses the normal execution and starts a single interactive simulation that can be paused and reset on request. -------------------------------------------------------------------------------- /tests/invert_grep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Executes grep and logically inverts the return code 5 | # 6 | 7 | grep "$1" $2 8 | RETCODE=$? 9 | if [[ $RETCODE == 0 ]] 10 | then 11 | exit 1 12 | else 13 | exit 0 14 | fi 15 | -------------------------------------------------------------------------------- /tests/routing/rpl-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "POSITIONING_NUM_NODES": 30, 5 | "POSITIONING_NUM_DEGREES": 7, 6 | "POSITIONING_LAYOUT": "Mesh", 7 | "RPL_OBJECTIVE_FUNCTION" : "OF0", 8 | "LOG_LEVELS" : { "RPL": 4 } 9 | } 10 | -------------------------------------------------------------------------------- /tsch-sim-web-interface.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem This file starts the simulator along with the web interface. 3 | rem It is meant for Microsoft Windows 4 | 5 | rem start the simulator 6 | start tsch-sim-windows.bat examples\web\config.json 7 | 8 | rem start the default web browser 9 | start http://localhost:2020 10 | -------------------------------------------------------------------------------- /examples/grid/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **grid** topology. 2 | 3 | There is only one type of node. Each non-root node generates traffic, forwards traffic, and is connected to its one or direct neighbor nodes in the grid. 4 | 5 | The file `config.json` has node positions automatically generated, as configured by this line: 6 | * "POSITIONING_LAYOUT": "Grid" 7 | -------------------------------------------------------------------------------- /examples/grid/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1}, 9 | "CONNECTIONS": [{"TO_NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"}] 10 | } 11 | ], 12 | "POSITIONING_LAYOUT": "Grid" 13 | } 14 | -------------------------------------------------------------------------------- /tests/link_model/trace.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "TRACE_FILE": "trace.k7", 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 7, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "MAC_HOPPING_SEQUENCE": "TSCH_HOPPING_SEQUENCE_1_1" 14 | } 15 | -------------------------------------------------------------------------------- /examples/hierarchical/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **hierarchical** topology. 2 | 3 | It uses a preconfigured link based radio connectivity model. There are three types of nodes: 4 | 1) Root node 5 | 2) Forwarder nodes 6 | 3) Leaf nodes 7 | 8 | Each forwarder and leaf node generates traffic. 9 | Each forwarder is connected to the root node and to all leaf nodes, and forwards traffic. 10 | Each leaf node is connected to all forwarder nodes. 11 | -------------------------------------------------------------------------------- /examples/mesh/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "POSITIONING_LAYOUT": "Mesh", 13 | "POSITIONING_NUM_DEGREES": 5 14 | } 15 | -------------------------------------------------------------------------------- /examples/line/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1}, 9 | "CONNECTIONS": [{"TO_NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"}] 10 | } 11 | ], 12 | "POSITIONING_LAYOUT": "Line", 13 | "POSITIONING_LINK_QUALITY": 0.95 14 | } 15 | -------------------------------------------------------------------------------- /tsch-sim-windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem This file starts the simulator on Microsoft Windows 3 | 4 | if "%~1"=="" goto no_args 5 | 6 | set CONFIG_FILE=%~f1 7 | 8 | echo "CONFIG_FILE=%CONFIG_FILE%" 9 | set WORKDIR=%~dp0 10 | cd %WORKDIR% 11 | 12 | node --harmony --experimental-modules %NODE_ARGUMENTS% source/main.mjs "%CONFIG_FILE%" 13 | 14 | exit /b %errorlevel% 15 | 16 | :no_args 17 | echo Expected at least one argument: configuration file name 18 | exit /b 1 19 | -------------------------------------------------------------------------------- /examples/mesh/config-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "POSITIONING_LAYOUT": "Mesh", 13 | "POSITIONING_NUM_DEGREES": 5, 14 | "RPL_OBJECTIVE_FUNCTION" : "OF0" 15 | } 16 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | all: tests clean 2 | 3 | tests: 4 | make -C applications 5 | make -C generation 6 | make -C link_model 7 | make -C mobility 8 | make -C tsch 9 | make -C scheduling 10 | make -C routing 11 | make -C ip 12 | make -C stats 13 | 14 | clean: 15 | make -C applications clean 16 | make -C generation clean 17 | make -C routing clean 18 | make -C link_model clean 19 | make -C mobility clean 20 | make -C scheduling clean 21 | make -C tsch clean 22 | make -C ip clean 23 | make -C stats clean 24 | -------------------------------------------------------------------------------- /tests/stats/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: duty-cycle 4 | 5 | duty-cycle: duty-cycle.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | $(NODE) --harmony --experimental-modules ../check_stats_value.mjs results/stats.json 1 radio_duty_cycle = 2.02 10 | $(NODE) --harmony --experimental-modules ../check_stats_value.mjs results/stats.json 1 radio_duty_cycle_joined = 2.02 11 | 12 | clean: 13 | rm -rf results 14 | -------------------------------------------------------------------------------- /examples/line/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **line** topology. 2 | 3 | There is only one type of node. Each non-root node generates traffic, forwards traffic, and is connected to its one or two neighbor nodes. 4 | 5 | The file `config.json` has node positions automatically generated, as configured by this line: 6 | * "POSITIONING_LAYOUT": "Line". 7 | 8 | The file `config-manual.json` has node connections manually set up. It uses a radio connectivity model fixed in the configuration (from here comes the term "fixed" links). -------------------------------------------------------------------------------- /tsch-sim-web-interface.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file starts the simulator along with the web interface. 4 | # It is meant for UNIX-like operating systems: Linux and macOS. 5 | # For a Windows version, see `tsch-sim-web-windows.bat`. 6 | 7 | # start the simulator 8 | ./tsch-sim.sh examples/web/config.json $* & 9 | 10 | sleep 1 11 | 12 | # start a web browser 13 | if [ "`uname`" == "Linux" ]; then 14 | sensible-browser http://localhost:2020 15 | else 16 | # assume MAC 17 | open http://localhost:2020 18 | fi 19 | -------------------------------------------------------------------------------- /tests/tsch/desync.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "MAC_DESYNC_THRESHOLD_SEC": 10, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 2 10 | } 11 | ], 12 | "CONNECTIONS" : [ 13 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tests/tsch/different-hopping-sequences.json: -------------------------------------------------------------------------------- 1 | { 2 | "MAC_HOPPING_SEQUENCE": [15, 20, 25, 26], 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node1", 7 | "COUNT": 1 8 | }, 9 | { 10 | "NAME": "node2", 11 | "COUNT": 1, 12 | "MAC_HOPPING_SEQUENCE": [15, 20] 13 | }, 14 | { 15 | "NAME": "node3", 16 | "COUNT": 1, 17 | "MAC_HOPPING_SEQUENCE": [25, 26] 18 | } 19 | ], 20 | "POSITIONING_LAYOUT": "Star" 21 | } 22 | -------------------------------------------------------------------------------- /tests/tsch/keepalive.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 100, "TO_ID": 1} 10 | } 11 | ], 12 | "CONNECTIONS" : [ 13 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 15 | ], 16 | "LOG_LEVELS": {"Node": 4} 17 | } 18 | -------------------------------------------------------------------------------- /web/js/const.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | /* Schedule cell flags */ 3 | FLAG_RX: 1 << 0, 4 | FLAG_TX: 1 << 1, 5 | FLAG_SKIPPED_TX: 1 << 2, 6 | 7 | FLAG_PACKET_TX: 1 << 3, 8 | FLAG_PACKET_RX: 1 << 4, 9 | FLAG_PACKET_BADRX: 1 << 5, 10 | 11 | FLAG_ACK: 1 << 6, 12 | FLAG_ACK_OK: 1 << 7, 13 | 14 | /* Run speeds */ 15 | RUN_UNLIMITED: 1, 16 | RUN_1000_PERCENT: 2, 17 | RUN_100_PERCENT: 3, 18 | RUN_10_PERCENT: 4, 19 | RUN_STEP_NEXT_ACTIVE: 5, 20 | RUN_STEP_SINGLE: 6, 21 | }; 22 | -------------------------------------------------------------------------------- /tests/tsch/slot-duration.json: -------------------------------------------------------------------------------- 1 | { 2 | "MAC_SLOT_DURATION_US": 20000, 3 | "SIMULATION_DURATION_SEC": 600, 4 | "RESULTS_DIR": "./results", 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 2, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/large-network/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **large network** with 1000 nodes. It has a simple hierarchical topology. 2 | 3 | It uses a preconfigured link based radio connectivity model. There are three types of nodes: 4 | 1) Root node 5 | 2) Forwarder nodes 6 | 3) Leaf nodes 7 | 8 | Each leaf node generates traffic. 9 | Each forwarder is connected to the root node and to some leaf nodes, and forwards traffic. 10 | Each leaf node is connected to one forwarder node. 11 | 12 | Leaf-and-forwarder routing and scheduling options are used, as the Orchestra scheduler is not suitable for 1000+ node networks. 13 | -------------------------------------------------------------------------------- /tests/routing/rpl-recover-2nodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 2, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/scheduling/100percent-line.json: -------------------------------------------------------------------------------- 1 | { 2 | "RESULTS_DIR": "./results", 3 | "SIMULATION_DURATION_SEC": 1200, 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "CONNECTIONS": [ 13 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 15 | ], 16 | "MAC_KEEPALIVE_TIMEOUT_SEC" : 1 17 | } 18 | -------------------------------------------------------------------------------- /tests/tsch/eb.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "CONNECTIONS" : [ 13 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 15 | ], 16 | "LOG_LEVELS" : { 17 | "TSCH" : 4 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/multirun/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates how to configure the simulator to execute **multiple runs**. 2 | 3 | The base for this example is the "mesh" example in the parent directory. 4 | 5 | Each run gets a different random seed, so their results are not identical. More precisely, 6 | if the configured random seed is `N`, then the first run gets `N` as its random seed, the second `N+1` etc. 7 | 8 | Runs are executed in parallel and can fully benefit from multiple CPU cores for speedup. 9 | 10 | Each run creates its own log and stats file. The summary results of the runs are saved in the file "stats_merged.json". 11 | -------------------------------------------------------------------------------- /tests/stats/duty-cycle.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "CONNECTIONS" : [ 13 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 15 | ], 16 | "LOG_LEVELS" : { 17 | "TSCH" : 4 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/tsch/subslots.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "MAC_MAX_SUBSLOTS": 2, 5 | "SIMULATION_SCRIPT_FILE": "./subslots.js", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 2 11 | } 12 | ], 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 16 | ], 17 | "LOG_LEVELS" : { 18 | "TSCH" : 4 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/star/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **star** topology. 2 | 3 | There are two types of nodes: 4 | 1) Root node 5 | 2) Leaf nodes 6 | 7 | The file `config.json` has node positions automatically generated, as configured by this line: 8 | * "POSITIONING_LAYOUT": "Star". 9 | There are also connections between leaf nodes. 10 | 11 | The file `config-manual.json` has node connections set up depending on node type. All leaf nodes are directly connected to the root nodes, but there are no connections between the leaf nodes. If simulation of interference between the leaf nodes is desired, then connections between them should be added. 12 | 13 | -------------------------------------------------------------------------------- /examples/star/config-manual.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "root", 6 | "START_ID": 1, 7 | "COUNT": 1, 8 | "CONNECTIONS": [{"NODE_TYPE": "leaf", "LINK_MODEL": "Fixed", "LINK_QUALITY": 0.9}] 9 | }, 10 | { 11 | "NAME": "leaf", 12 | "START_ID": 2, 13 | "COUNT": 9, 14 | "ROUTING_IS_LEAF": true, 15 | "CONNECTIONS": [{"NODE_TYPE": "root", "LINK_MODEL": "Fixed", "LINK_QUALITY": 0.9}], 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/star/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "root", 6 | "START_ID": 1, 7 | "COUNT": 1, 8 | "CONNECTIONS": [{"NODE_TYPE": "leaf", "LINK_MODEL": "LogisticLoss"}] 9 | }, 10 | { 11 | "NAME": "leaf", 12 | "START_ID": 2, 13 | "COUNT": 9, 14 | "ROUTING_IS_LEAF": true, 15 | "CONNECTIONS": [{"NODE_TYPE": "root", "LINK_MODEL": "LogisticLoss"}], 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | } 18 | ], 19 | "POSITIONING_LAYOUT": "Star" 20 | } 21 | -------------------------------------------------------------------------------- /tsch-sim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file starts the simulator on UNIX-like operating systems: Linux and macOS 4 | # For a Windows version, see `tsch-sim-windows.bat`. 5 | 6 | if [ "$#" -lt 1 ]; then 7 | echo "Expected at least one argument: configuration file name" 8 | exit 1 9 | fi 10 | 11 | CONFIG_FILE=$(realpath "$1") 12 | if [ -d "$CONFIG_FILE" ] ; then 13 | CONFIG_FILE="$CONFIG_FILE/config.json" 14 | fi 15 | shift 16 | 17 | cd `dirname "$0"` 18 | 19 | if command -v nodejs &> /dev/null 20 | then 21 | NODE=nodejs 22 | else 23 | NODE=node 24 | fi 25 | 26 | $NODE --harmony --experimental-modules $NODE_ARGUMENTS source/main.mjs "$CONFIG_FILE" $* 27 | -------------------------------------------------------------------------------- /tests/link_model/udgm-constant-loss.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "UDGM_CONSTANT_LOSS": true, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "UDGM"}], 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "MAC_HOPPING_SEQUENCE": "TSCH_HOPPING_SEQUENCE_1_1", 15 | "POSITIONS" : [ 16 | {"ID": 1, "X": 0, "Y": 0}, 17 | {"ID": 2, "X": 0, "Y": 0}, 18 | {"ID": 3, "X": 60, "Y": 0} 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tests/tsch/join-hopseq-nojoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "MAC_HOPPING_SEQUENCE": [15, 20, 25, 26], 13 | "MAC_JOIN_HOPPING_SEQUENCE": [11, 12, 16, 21], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/link_model/logistic-loss.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 4, 9 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "MAC_HOPPING_SEQUENCE": "TSCH_HOPPING_SEQUENCE_1_1", 14 | "POSITIONS" : [ 15 | {"ID": 1, "X": 0, "Y": 0}, 16 | {"ID": 2, "X": 80, "Y": 0}, 17 | {"ID": 3, "X": -120, "Y": 0}, 18 | {"ID": 4, "X": 0, "Y": 200} 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tests/routing/rpl-recover-2nodes-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "RPL_OBJECTIVE_FUNCTION" : "OF0", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 2, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 17 | ], 18 | "LOG_LEVELS" : { "RPL": 4 } 19 | } 20 | -------------------------------------------------------------------------------- /tests/link_model/udgm.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "UDGM_CONSTANT_LOSS": false, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 4, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "UDGM"}], 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "MAC_HOPPING_SEQUENCE": "TSCH_HOPPING_SEQUENCE_1_1", 15 | "POSITIONS" : [ 16 | {"ID": 1, "X": 0, "Y": 0}, 17 | {"ID": 2, "X": 0, "Y": 20}, 18 | {"ID": 3, "X": 40, "Y": 0}, 19 | {"ID": 4, "X": -60, "Y": 0} 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/mesh/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates a **mesh** topology. 2 | 3 | It uses the LogisticLoss distance based radio connectivity model. There is only one type of node. 4 | 5 | Each node generates traffic, is connected to its geometrically closest nodes, and is capable of forwarding data. 6 | 7 | The file `config.json` has node positions automatically generated, as configured by these values: 8 | * "POSITIONING_LAYOUT": "Mesh", 9 | * "POSITIONING_LINK_QUALITY": 0.8, 10 | * "POSITIONING_NUM_DEGREES": 6. 11 | 12 | The file `config-manual.json` has node positions manually set up (based on the out of a generation script). 13 | 14 | The file `config-of0.json` has automated positioning, but uses RPL OF0 objective function. -------------------------------------------------------------------------------- /tests/link_model/pister-hack.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "PISTER_HACK_LOWER_SHIFT": 10, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 4, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "PisterHack"}], 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "MAC_HOPPING_SEQUENCE": "TSCH_HOPPING_SEQUENCE_1_1", 15 | "POSITIONS" : [ 16 | {"ID": 1, "X": 0, "Y": 0}, 17 | {"ID": 2, "X": 200, "Y": 0}, 18 | {"ID": 3, "X": -400, "Y": 0}, 19 | {"ID": 4, "X": 0, "Y": 1000} 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/tsch/non-standard-slots.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "SCHEDULING_ALGORITHM": "6tischMin", 5 | "SIMULATION_SCRIPT_FILE": "./non-standard-slots.js", 6 | "TSCH_SCHEDULE_CONF_DEFAULT_LENGTH": 3, 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "node", 10 | "START_ID": 1, 11 | "COUNT": 2, 12 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 13 | } 14 | ], 15 | "CONNECTIONS" : [ 16 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tests/tsch/non-standard-slots.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * The idea of this is to emulate slotframs of 7 slots compressed in a 3 slots. 4 | * Normally, the duration of a slot is 10 ms (0.01 seconds), and the slotframe: 70 ms (0.07 seconds). 5 | * Here, the slotframe has 3 slots, the last one of which is 5 times longer (50 ms). 6 | * The slotframe is still kept to 70 ms in total. Assuming that the last 4 slots were idle, 7 | * the operation of the protocol is not affected (other than the channel hopping being different). 8 | * The length 3 was selected to keep the slotframe size to a non-even prime number. 9 | */ 10 | state.log.log(state.log.INFO, null, "User", `Initializing non-standard slot size`); 11 | state.timeline.slot_timings = [0.01, 0.01, 0.05]; 12 | -------------------------------------------------------------------------------- /examples/scripting/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates **scripting integration** with TSCH-Sim. 2 | 3 | It does that by demonstrating how to force RPL parent switch during a simulation run. 4 | 5 | The network in the example has diamond-shape topology: 6 | 7 | root 8 | / \ 9 | A B 10 | \ / 11 | C 12 | 13 | Initially, the node C joins to the RPL network with one of the nodes A and B as its parent. 14 | Once the join has happened, the script that controls the simulation disables the link 15 | between the node C and its parent node, forcing it to use the other node as its parent. 16 | This setup is useful for experiments such as the measurement of RPL parent switch speed and 17 | its impact on the network performance. 18 | -------------------------------------------------------------------------------- /tests/routing/rpl-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "root", 7 | "START_ID": 1, 8 | "COUNT": 1, 9 | "CONNECTIONS": [{"NODE_TYPE": "leaf", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}] 10 | }, 11 | { 12 | "NAME": "leaf", 13 | "START_ID": 2, 14 | "COUNT": 9, 15 | "ROUTING_IS_LEAF": true, 16 | "CONNECTIONS": [{"NODE_TYPE": "root", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}], 17 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 18 | } 19 | ], 20 | "LOG_LEVELS" : { "TSCH": 4, "Node": 4, "RPL": 4 } 21 | } 22 | -------------------------------------------------------------------------------- /tests/tsch/join-hopseq.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 2, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "MAC_HOPPING_SEQUENCE": [15, 20, 25, 26], 13 | "MAC_JOIN_HOPPING_SEQUENCE": [24, 26], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 17 | ], 18 | "LOG_LEVELS" : { 19 | "RPL" : 4, 20 | "TSCH" : 4, 21 | "Node" : 4 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/routing/nullrouting.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "SCHEDULING_ALGORITHM": "LeafAndForwarder", 5 | "ROUTING_ALGORITHM": "NullRouting", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "root", 9 | "START_ID": 1, 10 | "COUNT": 1, 11 | "CONNECTIONS": [{"NODE_TYPE": "leaf", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}] 12 | }, 13 | { 14 | "NAME": "leaf", 15 | "START_ID": 2, 16 | "COUNT": 9, 17 | "ROUTING_IS_LEAF": true, 18 | "CONNECTIONS": [{"NODE_TYPE": "root", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}], 19 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tests/routing/rpl-dao-ack.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "RPL_WITH_DAO_ACK": true, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "LOG_LEVELS" : { "RPL": 4 }, 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/routing/rpl-probing.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "RPL_WITH_PROBING": true, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "LOG_LEVELS": { "RPL": 4 }, 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/routing/rpl-recover-3nodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | 17 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tests/routing/rpl-no-dao-ack.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "RPL_WITH_DAO_ACK": false, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "LOG_LEVELS" : { "RPL": 4 }, 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/routing/rpl-no-probing.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "RPL_WITH_PROBING": false, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 3, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "LOG_LEVELS" : { "RPL" : 4 }, 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/ip/fragmentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "APP_PACKET_SIZE": 1000, 4 | "RESULTS_DIR": "./results", 5 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "static", 9 | "START_ID": 1, 10 | "COUNT": 3, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 100, "TO_ID": 1} 12 | } 13 | ], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/applications/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "root", 7 | "START_ID": 1, 8 | "COUNT": 1, 9 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"}] 10 | }, 11 | { 12 | "NAME": "node", 13 | "START_ID": 2, 14 | "COUNT": 29, 15 | "CONNECTIONS" : [ 16 | {"NODE_TYPE": "root", "LINK_MODEL": "LogisticLoss"}, 17 | {"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"} 18 | ], 19 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 20 | } 21 | ], 22 | "POSITIONING_NUM_NODES": 30, 23 | "POSITIONING_LAYOUT": "Mesh" 24 | } 25 | -------------------------------------------------------------------------------- /tests/ip/no-fragmentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "APP_PACKET_SIZE": 1000, 4 | "IP_FRAGMENTATION_ENABLED": false, 5 | "RESULTS_DIR": "./results", 6 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "static", 10 | "START_ID": 1, 11 | "COUNT": 3, 12 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 100, "TO_ID": 1} 13 | } 14 | ], 15 | "CONNECTIONS" : [ 16 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 18 | 19 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 20 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tests/routing/rpl-recover-3nodes-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "RPL_OBJECTIVE_FUNCTION" : "OF0", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 3, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 20 | ], 21 | "LOG_LEVELS" : { "RPL": 4 } 22 | } 23 | -------------------------------------------------------------------------------- /tests/tsch/start-joined.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "MAC_START_JOINED": true, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 10, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "POSITIONS" : [ 15 | {"ID": 1, "X": 0, "Y": 0}, 16 | {"ID": 2, "X": 40, "Y": 0}, 17 | {"ID": 3, "X": 0, "Y": 40}, 18 | {"ID": 4, "X": -40, "Y": 0}, 19 | {"ID": 5, "X": 80, "Y": 0}, 20 | {"ID": 6, "X": 80, "Y": 0}, 21 | {"ID": 7, "X": 0, "Y": 80}, 22 | {"ID": 8, "X": 0, "Y": 80}, 23 | {"ID": 9, "X": -80, "Y": 0}, 24 | {"ID": 10, "X": -80, "Y": 0} 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tests/applications/dissemination.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "root", 7 | "START_ID": 1, 8 | "COUNT": 1, 9 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"}], 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 100, "APP_WARMUP_PERIOD_SEC": 500, "TO_TYPE": "node"} 11 | }, 12 | { 13 | "NAME": "node", 14 | "START_ID": 2, 15 | "COUNT": 29, 16 | "CONNECTIONS" : [ 17 | {"NODE_TYPE": "root", "LINK_MODEL": "LogisticLoss"}, 18 | {"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"} 19 | ] 20 | } 21 | ], 22 | "APP_WARMUP_PERIOD_SEC": 500, 23 | "POSITIONING_NUM_NODES": 30, 24 | "POSITIONING_NUM_DEGREES": 8, 25 | "POSITIONING_LAYOUT": "Mesh" 26 | } 27 | -------------------------------------------------------------------------------- /tests/scheduling/100percent-star.json: -------------------------------------------------------------------------------- 1 | { 2 | "RESULTS_DIR": "./results", 3 | "SIMULATION_DURATION_SEC": 1800, 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "root", 7 | "START_ID": 1, 8 | "COUNT": 1, 9 | "CONNECTIONS": [{"NODE_TYPE": "leaf", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}] 10 | }, 11 | { 12 | "NAME": "leaf", 13 | "START_ID": 2, 14 | "COUNT": 9, 15 | "ROUTING_IS_LEAF": true, 16 | "CONNECTIONS": [{"NODE_TYPE": "root", "LINK_MODEL": "Fixed", "LINK_QUALITY": 1.0}], 17 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 18 | } 19 | ], 20 | "ORCHESTRA_RULES": [ "orchestra_rule_eb_per_time_source", 21 | "orchestra_rule_unicast_per_neighbor_link_based", 22 | "orchestra_rule_default_common" ], 23 | "SIMULATION_SCRIPT_FILE": "./100percent-star.js" 24 | } 25 | -------------------------------------------------------------------------------- /tests/applications/query.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "root", 7 | "START_ID": 1, 8 | "COUNT": 1, 9 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"}], 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 100, "APP_WARMUP_PERIOD_SEC": 500, "IS_QUERY": true, "TO_TYPE": "node"} 11 | }, 12 | { 13 | "NAME": "node", 14 | "START_ID": 2, 15 | "COUNT": 29, 16 | "CONNECTIONS" : [ 17 | {"NODE_TYPE": "root", "LINK_MODEL": "LogisticLoss"}, 18 | {"NODE_TYPE": "node", "LINK_MODEL": "LogisticLoss"} 19 | ] 20 | } 21 | ], 22 | "APP_WARMUP_PERIOD_SEC": 300, 23 | "POSITIONING_NUM_NODES": 30, 24 | "POSITIONING_NUM_DEGREES": 29, 25 | "POSITIONING_LINK_QUALITY": 0.999, 26 | "POSITIONING_LAYOUT": "Mesh" 27 | } 28 | -------------------------------------------------------------------------------- /tests/scheduling/6tisch-min.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "SCHEDULING_ALGORITHM": "6tischMin", 6 | "TSCH_SCHEDULE_CONF_DEFAULT_LENGTH": 7, 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "node", 10 | "START_ID": 1, 11 | "COUNT": 10, 12 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 13 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 14 | } 15 | ], 16 | "POSITIONS" : [ 17 | {"ID": 1, "X": 0, "Y": 0}, 18 | {"ID": 2, "X": 40, "Y": 0}, 19 | {"ID": 3, "X": 0, "Y": 40}, 20 | {"ID": 4, "X": -40, "Y": 0}, 21 | {"ID": 5, "X": 80, "Y": 0}, 22 | {"ID": 6, "X": 80, "Y": 0}, 23 | {"ID": 7, "X": 0, "Y": 80}, 24 | {"ID": 8, "X": 0, "Y": 80}, 25 | {"ID": 9, "X": -80, "Y": 0}, 26 | {"ID": 10, "X": -80, "Y": 0} 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tests/scheduling/lf.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "SCHEDULING_ALGORITHM": "LeafAndForwarder", 6 | "ROUTING_ALGORITHM": "LeafAndForwarderRouting", 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "node", 10 | "START_ID": 1, 11 | "COUNT": 10, 12 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 13 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 14 | } 15 | ], 16 | "POSITIONS" : [ 17 | {"ID": 1, "X": 0, "Y": 0}, 18 | {"ID": 2, "X": 40, "Y": 0}, 19 | {"ID": 3, "X": 0, "Y": 40}, 20 | {"ID": 4, "X": -40, "Y": 0}, 21 | {"ID": 5, "X": 80, "Y": 0}, 22 | {"ID": 6, "X": 80, "Y": 0}, 23 | {"ID": 7, "X": 0, "Y": 80}, 24 | {"ID": 8, "X": 0, "Y": 80}, 25 | {"ID": 9, "X": -80, "Y": 0}, 26 | {"ID": 10, "X": -80, "Y": 0} 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tests/routing/disable-enable-links.js: -------------------------------------------------------------------------------- 1 | 2 | function enable_links(state, do_enable) 3 | { 4 | state.log.log(state.log.INFO, null, "User", `${do_enable? "enable" : "disable"} links`); 5 | const link_from = state.network.get_link(1, 2); 6 | const link_to = state.network.get_link(2, 1); 7 | 8 | if (do_enable) { 9 | link_from.link_quality = 1.0; 10 | link_to.link_quality = 1.0; 11 | } else { 12 | link_from.link_quality = 0.0; 13 | link_to.link_quality = 0.0; 14 | } 15 | } 16 | 17 | function reset_stats(state, do_enable) 18 | { 19 | state.log.log(state.log.INFO, null, "User", `reset all stats`); 20 | for (let [_, node] of state.network.nodes) { 21 | node.reset_stats(); 22 | } 23 | } 24 | 25 | let callbacks = { 26 | 60000: function(state) { enable_links(state, false); }, 27 | 70000: function(state) { enable_links(state, true); }, 28 | 120000: function(state) { reset_stats(state); } 29 | 30 | }; 31 | 32 | 33 | return callbacks; 34 | -------------------------------------------------------------------------------- /tests/applications/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: collection dissemination query 4 | 5 | collection: collection.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 50 10 | 11 | dissemination: dissemination.json 12 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 13 | @rm -rf results 14 | ../../tsch-sim.sh $< > /dev/null 15 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 70 1 16 | 17 | query: query.json 18 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 19 | @rm -rf results 20 | ../../tsch-sim.sh $< > /dev/null 21 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90 1 22 | $(NODE) --harmony --experimental-modules ../check_stats_value.mjs results/stats.json -1 app_num_replied \> 0 23 | 24 | clean: 25 | rm -rf results 26 | -------------------------------------------------------------------------------- /tests/generation/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: mesh grid star line 4 | 5 | mesh: mesh.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | grep "set position x=-445.05 y=-96.50" results/log.txt > /dev/null 10 | 11 | grid: grid.json 12 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 13 | @rm -rf results 14 | ../../tsch-sim.sh $< > /dev/null 15 | grep "set position x=1118.66 y=1118.66" results/log.txt > /dev/null 16 | 17 | star: star.json 18 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 19 | @rm -rf results 20 | ../../tsch-sim.sh $< > /dev/null 21 | grep "set position x=248.59 y=124.30" results/log.txt > /dev/null 22 | 23 | line: line.json 24 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 25 | @rm -rf results 26 | ../../tsch-sim.sh $< > /dev/null 27 | grep "set position x=12305.27 y=0.00" results/log.txt > /dev/null 28 | 29 | clean: 30 | rm -rf results 31 | -------------------------------------------------------------------------------- /examples/scripting/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_SCRIPT_FILE": "./script.js", 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 4, 8 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 9 | } 10 | ], 11 | "CONNECTIONS": [ 12 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 13 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 14 | 15 | { "FROM_ID": 1, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 3, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 4, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 2, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 20 | 21 | { "FROM_ID": 4, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 22 | { "FROM_ID": 3, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tests/tsch/subslots.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Add a packet source that is generating packets is a non-standard subslot. 4 | */ 5 | state.log.log(state.log.INFO, null, "User", `Initializing non-standard slot size`); 6 | state.timeline.slot_timings = [0.01, 0.01, 0.05]; 7 | 8 | function generate_packet(state) { 9 | const node = state.network.get_node(2); 10 | 11 | if (node.routing.is_joined()) { 12 | const packet = new state.pkt.Packet(node, state.constants.ROOT_NODE_ID, state.config.APP_PACKET_SIZE); 13 | if (this.seqnum === undefined) { 14 | this.seqnum = 0; 15 | } 16 | packet.seqnum = ++this.seqnum; 17 | /* set the subslot to non-default value */ 18 | packet.subslot = 1; 19 | node.add_app_packet(packet); 20 | } 21 | } 22 | 23 | const callbacks = { 24 | 5000: generate_packet, 25 | 15000: generate_packet, 26 | 25000: generate_packet, 27 | 35000: generate_packet, 28 | 45000: generate_packet, 29 | 55000: generate_packet, 30 | }; 31 | 32 | return callbacks; 33 | -------------------------------------------------------------------------------- /tests/ip/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: fragmentation no-fragmentation 4 | 5 | fragmentation: fragmentation.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | grep "id=2" results/log.txt | grep "packet reassembly started, seqnum=1 from=3" 10 | grep "id=2" results/log.txt | grep "packet reassembly completed, seqnum=1 from=3" 11 | grep "id=1" results/log.txt | grep "packet reassembly started, seqnum=1 from=2" 12 | grep "id=1" results/log.txt | grep "packet reassembly completed, seqnum=1 from=2" 13 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 100 14 | 15 | no-fragmentation: no-fragmentation.json 16 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 17 | @rm -rf results 18 | ../../tsch-sim.sh $< > /dev/null 19 | grep "id=2" results/log.txt | grep "dropping app packet seqnum=1 for=1 to=1: too big" 20 | grep "id=3" results/log.txt | grep "dropping app packet seqnum=1 for=1 to=2: too big" 21 | 22 | clean: 23 | rm -rf results 24 | -------------------------------------------------------------------------------- /tests/check_nojoin.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the nodes have not joined the network. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | 11 | if (process.argv.length < 3) { 12 | console.log("results file not supplied"); 13 | process.exit(1); 14 | } 15 | 16 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1]); 17 | const state = JSON.parse(filedata); 18 | const run_state = state[RUN_ID]; 19 | let is_any_valid = false; 20 | 21 | for (let key in run_state) { 22 | if (key === "global-stats" || key === ROOT_ID) continue; 23 | 24 | const join_time = run_state[key].stats_tsch_join_time_sec; 25 | if (join_time !== null) { 26 | console.log("Node " + key + " joined"); 27 | console.log(" state:\n" + JSON.stringify(run_state[key])); 28 | process.exit(1); 29 | } 30 | is_any_valid = true; 31 | } 32 | 33 | if (!is_any_valid) { 34 | console.log("No valid nodes in the results"); 35 | process.exit(1); 36 | } 37 | 38 | /* success */ 39 | process.exit(0); 40 | -------------------------------------------------------------------------------- /examples/result-visualization/config.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_NUM_RUNS" : 4, 3 | "SIMULATION_DURATION_SEC": 3600, 4 | "RESULTS_DIR": %RESULTS_DIR%, 5 | "SCHEDULING_ALGORITHM": %SCHEDULING_ALGORITHM%, 6 | "TSCH_SCHEDULE_CONF_DEFAULT_LENGTH": %TSCH_SCHEDULE_CONF_DEFAULT_LENGTH%, 7 | "ORCHESTRA_UNICAST_PERIOD": %ORCHESTRA_UNICAST_PERIOD%, 8 | "ORCHESTRA_RULES": %ORCHESTRA_RULES%, 9 | "ORCHESTRA_UNICAST_SENDER_BASED": %ORCHESTRA_UNICAST_SENDER_BASED%, 10 | "LOGLOSS_RX_SENSITIVITY_DBM": -97, 11 | "APP_WARMUP_PERIOD_SEC": 1800, 12 | "AWGN_GAUSSIAN_STD": 0, 13 | "LOG_LEVELS" : {"RPL": 2, "Node": 2, "TSCH": "2"}, 14 | "NODE_TYPES": [ 15 | { 16 | "NAME": "node", 17 | "START_ID": 1, 18 | "COUNT": 100, 19 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 20 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 60, "TO_ID": 1} 21 | } 22 | ], 23 | "POSITIONING_LAYOUT": "Mesh", 24 | "POSITIONING_LINK_QUALITY": 0.9, 25 | "POSITIONING_NUM_DEGREES": 7, 26 | "POSITIONING_RANDOM_SEED": 0 27 | } 28 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-rb-ns.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 10, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 11 | "ORCHESTRA_RULES": [ 12 | "orchestra_rule_eb_per_time_source", 13 | "orchestra_rule_unicast_per_neighbor_rpl_ns", 14 | "orchestra_rule_default_common" 15 | ], 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | } 18 | ], 19 | "POSITIONS" : [ 20 | {"ID": 1, "X": 0, "Y": 0}, 21 | {"ID": 2, "X": 40, "Y": 0}, 22 | {"ID": 3, "X": 0, "Y": 40}, 23 | {"ID": 4, "X": -40, "Y": 0}, 24 | {"ID": 5, "X": 80, "Y": 0}, 25 | {"ID": 6, "X": 80, "Y": 0}, 26 | {"ID": 7, "X": 0, "Y": 80}, 27 | {"ID": 8, "X": 0, "Y": 80}, 28 | {"ID": 9, "X": -80, "Y": 0}, 29 | {"ID": 10, "X": -80, "Y": 0} 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | with: 25 | fetch-depth: 0 26 | 27 | - uses: actions/setup-node@v1 28 | name: Set up Node.js 29 | with: 30 | node-version: 10 31 | 32 | - run: npm install express body-parser 33 | 34 | - run: | 35 | cd tests 36 | make 37 | -------------------------------------------------------------------------------- /tests/scheduling/100percent-star.js: -------------------------------------------------------------------------------- 1 | /* This function checks the connectivity of the leaf node and disabled */ 2 | function check_connectivity(state) { 3 | if (this.is_stat_collection_mode) return; 4 | 5 | let all_ok = true; 6 | 7 | for (const [id, node] of state.network.nodes) { 8 | if (id === 1) continue; 9 | 10 | if (!node.orchestra_parent_knows_us) { 11 | all_ok = false; 12 | break; 13 | } 14 | } 15 | 16 | if (all_ok) { 17 | /* reset stats and enter the collection mode */ 18 | state.log.log(state.log.INFO, null, "User", "reset stats and enter the collection mode"); 19 | for (const [id, node] of state.network.nodes) { 20 | node.reset_stats(); 21 | } 22 | this.is_stat_collection_mode = true; 23 | } 24 | } 25 | 26 | let callbacks = {}; 27 | /* Check the connectivity periodically, once 500 ASN */ 28 | for (let i = 1000; i < state.config.SIMULATION_DURATION_SEC * 100; i += 500) { 29 | callbacks[i] = check_connectivity; 30 | } 31 | 32 | /* Set the callbacks to be executed during the simulation */ 33 | return callbacks; 34 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-link-based.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 10, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 11 | "ORCHESTRA_RULES": [ 12 | "orchestra_rule_eb_per_time_source", 13 | "orchestra_rule_unicast_per_neighbor_link_based", 14 | "orchestra_rule_default_common" 15 | ], 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | } 18 | ], 19 | "POSITIONS" : [ 20 | {"ID": 1, "X": 0, "Y": 0}, 21 | {"ID": 2, "X": 40, "Y": 0}, 22 | {"ID": 3, "X": 0, "Y": 40}, 23 | {"ID": 4, "X": -40, "Y": 0}, 24 | {"ID": 5, "X": 80, "Y": 0}, 25 | {"ID": 6, "X": 80, "Y": 0}, 26 | {"ID": 7, "X": 0, "Y": 80}, 27 | {"ID": 8, "X": 0, "Y": 80}, 28 | {"ID": 9, "X": -80, "Y": 0}, 29 | {"ID": 10, "X": -80, "Y": 0} 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-rb-storing.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 10, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 11 | "ORCHESTRA_RULES": [ 12 | "orchestra_rule_eb_per_time_source", 13 | "orchestra_rule_unicast_per_neighbor_rpl_storing", 14 | "orchestra_rule_default_common" 15 | ], 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | } 18 | ], 19 | "POSITIONS" : [ 20 | {"ID": 1, "X": 0, "Y": 0}, 21 | {"ID": 2, "X": 40, "Y": 0}, 22 | {"ID": 3, "X": 0, "Y": 40}, 23 | {"ID": 4, "X": -40, "Y": 0}, 24 | {"ID": 5, "X": 80, "Y": 0}, 25 | {"ID": 6, "X": 80, "Y": 0}, 26 | {"ID": 7, "X": 0, "Y": 80}, 27 | {"ID": 8, "X": 0, "Y": 80}, 28 | {"ID": 9, "X": -80, "Y": 0}, 29 | {"ID": 10, "X": -80, "Y": 0} 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /examples/mesh/config-manual.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "POSITIONS": [ 13 | {"ID": 1, "X": -35.48795617439229, "Y": 1.2947804415850492}, 14 | {"ID": 2, "X": -3.8378407998081996, "Y": 35.34384739975304}, 15 | {"ID": 3, "X": 52.908873480447184, "Y": -2.967356449807018}, 16 | {"ID": 4, "X": -30.041522043006577, "Y": -51.99668761781551}, 17 | {"ID": 5, "X": 36.23180283240772, "Y": -53.2660500378449}, 18 | {"ID": 6, "X": 77.91409927413568, "Y": -0.24787960408874446}, 19 | {"ID": 7, "X": -2.4588488012306247, "Y": -83.47276426407818}, 20 | {"ID": 8, "X": 93.26951367782704, "Y": -32.46634073582364}, 21 | {"ID": 9, "X": -58.875951003194224, "Y": 83.3993636747067}, 22 | {"ID": 10, "X": -106.26429626833654, "Y": 5.315408476284129} 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tests/routing/rpl-loops.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 4, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | 17 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 18 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | 20 | { "FROM_ID": 2, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 21 | { "FROM_ID": 4, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 22 | 23 | { "FROM_ID": 4, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 24 | { "FROM_ID": 3, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 25 | ], 26 | "LOG_LEVELS" : { "RPL": 4 } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "15 6 * * 1" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript, python ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | config-file: ./.github/codeql.yml 34 | queries: +security-and-quality 35 | 36 | - name: Autobuild 37 | uses: github/codeql-action/autobuild@v2 38 | if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }} 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v2 42 | with: 43 | category: "/language:${{ matrix.language }}" 44 | -------------------------------------------------------------------------------- /examples/multirun/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_NUM_RUNS" : 4, 3 | "SIMULATION_DURATION_SEC": 600, 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 10, 9 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL" : "LogisticLoss"}], 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "POSITIONS": [ 14 | {"ID": 1, "X": -35.48795617439229, "Y": 1.2947804415850492}, 15 | {"ID": 2, "X": -3.8378407998081996, "Y": 35.34384739975304}, 16 | {"ID": 3, "X": 52.908873480447184, "Y": -2.967356449807018}, 17 | {"ID": 4, "X": -30.041522043006577, "Y": -51.99668761781551}, 18 | {"ID": 5, "X": 36.23180283240772, "Y": -53.2660500378449}, 19 | {"ID": 6, "X": 77.91409927413568, "Y": -0.24787960408874446}, 20 | {"ID": 7, "X": -2.4588488012306247, "Y": -83.47276426407818}, 21 | {"ID": 8, "X": 93.26951367782704, "Y": -32.46634073582364}, 22 | {"ID": 9, "X": -58.875951003194224, "Y": 83.3993636747067}, 23 | {"ID": 10, "X": -106.26429626833654, "Y": 5.315408476284129} 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-sb-storing.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "ORCHESTRA_UNICAST_SENDER_BASED": true, 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 10, 11 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 12 | "ORCHESTRA_RULES": [ 13 | "orchestra_rule_eb_per_time_source", 14 | "orchestra_rule_unicast_per_neighbor_rpl_storing", 15 | "orchestra_rule_default_common" 16 | ], 17 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 18 | } 19 | ], 20 | "POSITIONS" : [ 21 | {"ID": 1, "X": 0, "Y": 0}, 22 | {"ID": 2, "X": 40, "Y": 0}, 23 | {"ID": 3, "X": 0, "Y": 40}, 24 | {"ID": 4, "X": -40, "Y": 0}, 25 | {"ID": 5, "X": 80, "Y": 0}, 26 | {"ID": 6, "X": 80, "Y": 0}, 27 | {"ID": 7, "X": 0, "Y": 80}, 28 | {"ID": 8, "X": 0, "Y": 80}, 29 | {"ID": 9, "X": -80, "Y": 0}, 30 | {"ID": 10, "X": -80, "Y": 0} 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tests/routing/rpl-loops-drop.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "RPL_LOOP_ERROR_DROP": true, 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 4, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 20 | 21 | { "FROM_ID": 2, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 22 | { "FROM_ID": 4, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 23 | 24 | { "FROM_ID": 4, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 25 | { "FROM_ID": 3, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 26 | ], 27 | "LOG_LEVELS" : { "RPL": 4 } 28 | } 29 | -------------------------------------------------------------------------------- /tests/routing/rpl-loops-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "RPL_OBJECTIVE_FUNCTION" : "OF0", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "node", 9 | "START_ID": 1, 10 | "COUNT": 4, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | } 13 | ], 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 20 | 21 | { "FROM_ID": 2, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 22 | { "FROM_ID": 4, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 23 | 24 | { "FROM_ID": 4, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 25 | { "FROM_ID": 3, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 26 | ], 27 | "LOG_LEVELS" : { "RPL": 4 } 28 | } 29 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-with-shorter-active-period.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "ORCHESTRA_UNICAST_PERIOD": 17, 6 | "ORCHESTRA_UNICAST_ACTIVE_PERIOD": 7, 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "node", 10 | "START_ID": 1, 11 | "COUNT": 10, 12 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 13 | "ORCHESTRA_RULES": [ 14 | "orchestra_rule_eb_per_time_source", 15 | "orchestra_rule_unicast_per_neighbor_rpl_ns", 16 | "orchestra_rule_default_common" 17 | ], 18 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 19 | } 20 | ], 21 | "POSITIONS" : [ 22 | {"ID": 1, "X": 0, "Y": 0}, 23 | {"ID": 2, "X": 40, "Y": 0}, 24 | {"ID": 3, "X": 0, "Y": 40}, 25 | {"ID": 4, "X": -40, "Y": 0}, 26 | {"ID": 5, "X": 80, "Y": 0}, 27 | {"ID": 6, "X": 80, "Y": 0}, 28 | {"ID": 7, "X": 0, "Y": 80}, 29 | {"ID": 8, "X": 0, "Y": 80}, 30 | {"ID": 9, "X": -80, "Y": 0}, 31 | {"ID": 10, "X": -80, "Y": 0} 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/result-visualization/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates running simulations with multiple different settings, and visualization of the results of these simulation. 2 | 3 | The example uses Python's matplotlib for data visualization. Run `pip install -r requirements.txt` to get the dependencies. 4 | 5 | The file `experiment.py` can be executed with Python. It runs multiple different simulations, loads the resulting `.json` files, and plots some metrics such as the node-to-root packet delivery rate for a packet collection application. 6 | 7 | The configurations compared are taken from the original Orchestra paper: 8 | - 6TiSCH minimal schedule, 3 slot slotframe 9 | - 6TiSCH minimal schedule, 5 slot slotframe 10 | - Orchestra schedule, 7 slot receiver-based unicast slotframe 11 | - Orchestra schedule, 7 slot sender-based unicast slotframe 12 | - Orchestra schedule, 47 slot sender-based unicast slotframe 13 | 14 | The configurations are executed on a randomly generated mesh network with 100 nodes. Each configuration is executed four times, each run is 1 hour (3600 seconds) in the simulated time. 15 | 16 | The script visualizes the average results of all four runs using a bar plot, and applies error bars to show the results of the best and the worst run. 17 | -------------------------------------------------------------------------------- /tests/routing/rpl-loops-drop-of0.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1800, 3 | "RESULTS_DIR": "./results", 4 | "SIMULATION_SCRIPT_FILE": "./disable-enable-links.js", 5 | "RPL_LOOP_ERROR_DROP": true, 6 | "RPL_OBJECTIVE_FUNCTION" : "OF0", 7 | "NODE_TYPES": [ 8 | { 9 | "NAME": "node", 10 | "START_ID": 1, 11 | "COUNT": 4, 12 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 13 | } 14 | ], 15 | "CONNECTIONS" : [ 16 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 17 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 18 | 19 | { "FROM_ID": 2, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 20 | { "FROM_ID": 3, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 21 | 22 | { "FROM_ID": 2, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 23 | { "FROM_ID": 4, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 24 | 25 | { "FROM_ID": 4, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 26 | { "FROM_ID": 3, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 27 | ], 28 | "LOG_LEVELS" : { "RPL": 4 } 29 | } 30 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-special-for-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 10, 10 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 11 | "ORCHESTRA_RULES": [ 12 | "orchestra_rule_eb_per_time_source", 13 | "orchestra_rule_unicast_per_neighbor_rpl_storing", 14 | "orchestra_rule_special_for_root", 15 | "orchestra_rule_default_common" 16 | ], 17 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 18 | } 19 | ], 20 | "POSITIONS" : [ 21 | {"ID": 1, "X": 0, "Y": 0}, 22 | {"ID": 2, "X": 40, "Y": 0}, 23 | {"ID": 3, "X": 0, "Y": 40}, 24 | {"ID": 4, "X": -40, "Y": 0}, 25 | {"ID": 5, "X": 80, "Y": 0}, 26 | {"ID": 6, "X": 80, "Y": 0}, 27 | {"ID": 7, "X": 0, "Y": 80}, 28 | {"ID": 8, "X": 0, "Y": 80}, 29 | {"ID": 9, "X": -80, "Y": 0}, 30 | {"ID": 10, "X": -80, "Y": 0} 31 | ], 32 | "LOG_LEVELS" : { 33 | "TSCH": 4 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/js/utils.js: -------------------------------------------------------------------------------- 1 | /* utility functions */ 2 | TSCH_SIM.utils = function() { 3 | function format_time(seconds, num_digits=3) { 4 | let timestring = ""; 5 | try { 6 | let f = parseFloat(seconds); 7 | if (f >= 3600) { 8 | const hours = Math.trunc(f / 3600); 9 | f -= hours * 3600; 10 | timestring += hours + ":"; 11 | } 12 | const max_error = Math.pow(10, -num_digits); 13 | if (timestring || f >= 60 - max_error) { 14 | const mins = Math.trunc((f + max_error) / 60); 15 | f -= mins * 60; 16 | if (timestring !== "" && mins < 10) { 17 | timestring += "0"; 18 | } 19 | timestring += mins + ":"; 20 | } 21 | if (timestring !== "" && f < 10) { 22 | timestring += "0"; 23 | } 24 | if (f < 0.0) { 25 | f = 0.0; 26 | } 27 | timestring += f.toFixed(num_digits); 28 | } catch(err) { 29 | console.log("error: " + err); 30 | } 31 | return timestring; 32 | } 33 | 34 | return { 35 | format_time: format_time, 36 | }; 37 | 38 | }(); 39 | -------------------------------------------------------------------------------- /tests/scheduling/orchestra-with-nondefault-periods.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "RESULTS_DIR": "./results", 4 | "APP_WARMUP_PERIOD_SEC" : 300, 5 | 6 | "ORCHESTRA_EBSF_PERIOD": 299, 7 | "ORCHESTRA_COMMON_SHARED_PERIOD": 17, 8 | "ORCHESTRA_UNICAST_PERIOD": 7, 9 | 10 | "NODE_TYPES": [ 11 | { 12 | "NAME": "node", 13 | "START_ID": 1, 14 | "COUNT": 10, 15 | "CONNECTIONS" : [{"NODE_TYPE": "node", "LINK_MODEL": "UDGM"}], 16 | "ORCHESTRA_RULES": [ 17 | "orchestra_rule_eb_per_time_source", 18 | "orchestra_rule_unicast_per_neighbor_rpl_ns", 19 | "orchestra_rule_default_common" 20 | ], 21 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 22 | } 23 | ], 24 | "POSITIONS" : [ 25 | {"ID": 1, "X": 0, "Y": 0}, 26 | {"ID": 2, "X": 40, "Y": 0}, 27 | {"ID": 3, "X": 0, "Y": 40}, 28 | {"ID": 4, "X": -40, "Y": 0}, 29 | {"ID": 5, "X": 80, "Y": 0}, 30 | {"ID": 6, "X": 80, "Y": 0}, 31 | {"ID": 7, "X": 0, "Y": 80}, 32 | {"ID": 8, "X": 0, "Y": 80}, 33 | {"ID": 9, "X": -80, "Y": 0}, 34 | {"ID": 10, "X": -80, "Y": 0} 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /tests/link_model/trace.k7: -------------------------------------------------------------------------------- 1 | {"tx_length": 100, "stop_date": "2020-01-01T00:00:00.0", "channels": [20], "location": "virtual", "node_count": 7, "start_date": "2020-01-01T00:00:00.0", "interframe_duration": 100} 2 | datetime,src,dst,channel,mean_rssi,pdr,tx_count 3 | 2020-01-01T00:00:00.0,0,1,20,-80,0.0,100 4 | 2020-01-01T00:00:00.0,1,0,20,-80,0.0,100 5 | 2020-01-01T00:00:00.0,0,2,20,-80,0.0,100 6 | 2020-01-01T00:00:00.0,2,0,20,-80,0.0,100 7 | 2020-01-01T00:00:00.0,0,3,20,-80,0.0,100 8 | 2020-01-01T00:00:00.0,3,0,20,-80,0.0,100 9 | 2020-01-01T00:00:00.0,0,4,20,-80,0.0,100 10 | 2020-01-01T00:00:00.0,4,0,20,-80,0.0,100 11 | 2020-01-01T00:00:00.0,0,5,20,-80,0.0,100 12 | 2020-01-01T00:00:00.0,5,0,20,-80,0.0,100 13 | 2020-01-01T00:00:00.0,0,6,20,-80,0.0,100 14 | 2020-01-01T00:00:00.0,6,0,20,-80,0.0,100 15 | 2020-01-01T00:01:00.0,0,1,20,-80,1.0,100 16 | 2020-01-01T00:01:00.0,1,0,20,-80,1.0,100 17 | 2020-01-01T00:01:00.0,0,2,20,-80,0.8,100 18 | 2020-01-01T00:01:00.0,2,0,20,-80,0.8,100 19 | 2020-01-01T00:01:00.0,0,3,20,-80,0.6,100 20 | 2020-01-01T00:01:00.0,3,0,20,-80,0.6,100 21 | 2020-01-01T00:01:00.0,0,4,20,-80,0.4,100 22 | 2020-01-01T00:01:00.0,4,0,20,-80,0.4,100 23 | 2020-01-01T00:01:00.0,0,5,20,-80,0.2,100 24 | 2020-01-01T00:01:00.0,5,0,20,-80,0.2,100 25 | 2020-01-01T00:01:00.0,0,6,20,-80,0.0,100 26 | 2020-01-01T00:01:00.0,6,0,20,-80,0.0,100 27 | -------------------------------------------------------------------------------- /tests/check_good_par.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the PAR in a test is >99%. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | 11 | if (process.argv.length < 3) { 12 | console.log("results file not supplied"); 13 | process.exit(1); 14 | } 15 | 16 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1]); 17 | const state = JSON.parse(filedata); 18 | const run_state = state[RUN_ID]; 19 | 20 | for (let key in run_state) { 21 | if (key === "global-stats" || key === ROOT_ID) continue; 22 | 23 | const tx = run_state[key].mac_parent_tx_unicast; 24 | const acked = run_state[key].mac_parent_acked; 25 | 26 | if (!tx) { 27 | console.log("Some nodes unexpectedly transmitted no app packets, while nodes with worse links had packets"); 28 | process.exit(1); 29 | } 30 | 31 | let par = 100.0 * acked / tx; 32 | if (par < 99) { 33 | console.log(`Too few packets acked PAR=${par}`); 34 | console.log(" state:\n" + JSON.stringify(run_state[key])); 35 | process.exit(1); 36 | } 37 | } 38 | 39 | const pdr = run_state["global-stats"]["e2e-delivery"].value; 40 | if (pdr <= 0) { 41 | console.log("Zero PDR for all nodes"); 42 | process.exit(1); 43 | } 44 | 45 | /* success */ 46 | process.exit(0); 47 | -------------------------------------------------------------------------------- /tests/mobility/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: line random-waypoint 4 | 5 | line: line.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | grep "id=6" results/log.txt | grep "at 3600.000 pos is 40.000" > /dev/null 10 | grep "id=7" results/log.txt | grep "at 3600.000 pos is 0.000" > /dev/null 11 | grep "id=8" results/log.txt | grep "at 3600.000 pos is 40.000" > /dev/null 12 | grep "id=9" results/log.txt | grep "at 3600.000 pos is 80.000" > /dev/null 13 | grep "id=10" results/log.txt | grep "at 3600.000 pos is 120.000" > /dev/null 14 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 50 15 | 16 | random-waypoint: random-waypoint.json 17 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 18 | @rm -rf results 19 | ../../tsch-sim.sh $< > /dev/null 20 | grep "id=6" results/log.txt | grep "at 3600.000 pos is (" > /dev/null 21 | grep "id=7" results/log.txt | grep "at 3600.000 pos is (" > /dev/null 22 | grep "id=8" results/log.txt | grep "at 3600.000 pos is (" > /dev/null 23 | grep "id=9" results/log.txt | grep "at 3600.000 pos is (" > /dev/null 24 | grep "id=10" results/log.txt | grep "at 3600.000 pos is (" > /dev/null 25 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 50 26 | 27 | clean: 28 | rm -rf results 29 | -------------------------------------------------------------------------------- /tests/check_bad_par.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the PAR in a test is <50%. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | 11 | const MAX_PAR = 60; 12 | 13 | if (process.argv.length < 3) { 14 | console.log("results file not supplied"); 15 | process.exit(1); 16 | } 17 | 18 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1]); 19 | const state = JSON.parse(filedata); 20 | const run_state = state[RUN_ID]; 21 | 22 | for (let key in run_state) { 23 | if (key === "global-stats" || key === ROOT_ID) continue; 24 | 25 | const tx = run_state[key].mac_parent_tx_unicast; 26 | const acked = run_state[key].mac_parent_acked; 27 | 28 | if (!tx) { 29 | console.log("Some nodes unexpectedly transmitted no app packets, while nodes with worse links had packets"); 30 | process.exit(1); 31 | } 32 | 33 | let par = 100.0 * acked / tx; 34 | if (par >= MAX_PAR) { 35 | console.log(`Too many packets acked PAR=${par}`); 36 | console.log(" state:\n" + JSON.stringify(run_state[key])); 37 | process.exit(1); 38 | } 39 | } 40 | 41 | const pdr = run_state["global-stats"]["e2e-delivery"].value; 42 | if (pdr <= 0) { 43 | console.log("Zero PDR for all nodes"); 44 | process.exit(1); 45 | } 46 | 47 | /* success */ 48 | process.exit(0); 49 | -------------------------------------------------------------------------------- /tests/mobility/line.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "UDGM_CONSTANT_LOSS": true, 5 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "static", 9 | "START_ID": 1, 10 | "COUNT": 5, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | }, 13 | { 14 | "NAME": "mobile", 15 | "START_ID": 6, 16 | "COUNT": 5, 17 | "MOBILITY_MODEL": "Line", 18 | "MOBILITY_RANGE_X": 200, 19 | "MOBILITY_RANGE_Y": 0, 20 | "MOBILITY_SPEED": 0.1, 21 | "ROUTING_IS_LEAF": true, 22 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 23 | } 24 | ], 25 | "CONNECTIONS" : [ 26 | {"NODE_TYPE": "static", "LINK_MODEL": "UDGM"}, 27 | {"FROM_NODE_TYPE": "static", "TO_NODE_TYPE": "mobile", "LINK_MODEL": "UDGM"}, 28 | {"FROM_NODE_TYPE": "mobile", "TO_NODE_TYPE": "static", "LINK_MODEL": "UDGM"} 29 | ], 30 | "POSITIONS" : [ 31 | {"ID": 1, "X": 0, "Y": 0}, 32 | {"ID": 2, "X": 40, "Y": 0}, 33 | {"ID": 3, "X": 80, "Y": 0}, 34 | {"ID": 4, "X": 120, "Y": 0}, 35 | {"ID": 5, "X": 160, "Y": 0}, 36 | {"ID": 6, "X": 0, "Y": 0}, 37 | {"ID": 7, "X": 40, "Y": 0}, 38 | {"ID": 8, "X": 80, "Y": 0}, 39 | {"ID": 9, "X": 120, "Y": 0}, 40 | {"ID": 10, "X": 160, "Y": 0} 41 | ], 42 | "LOG_LEVELS" : { 43 | "Mobility": 4 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 | OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /tests/mobility/random-waypoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "UDGM_CONSTANT_LOSS": true, 5 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "static", 9 | "START_ID": 1, 10 | "COUNT": 5, 11 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 12 | }, 13 | { 14 | "NAME": "mobile", 15 | "START_ID": 6, 16 | "COUNT": 5, 17 | "MOBILITY_MODEL": "RandomWaypoint", 18 | "MOBILITY_RANGE_X": 200, 19 | "MOBILITY_RANGE_Y": 50, 20 | "MOBILITY_SPEED": 0.1, 21 | "ROUTING_IS_LEAF": true, 22 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 23 | } 24 | ], 25 | "CONNECTIONS" : [ 26 | {"NODE_TYPE": "static", "LINK_MODEL": "UDGM"}, 27 | {"FROM_NODE_TYPE": "static", "TO_NODE_TYPE": "mobile", "LINK_MODEL": "UDGM"}, 28 | {"FROM_NODE_TYPE": "mobile", "TO_NODE_TYPE": "static", "LINK_MODEL": "UDGM"} 29 | ], 30 | "POSITIONS" : [ 31 | {"ID": 1, "X": 0, "Y": 0}, 32 | {"ID": 2, "X": 40, "Y": 0}, 33 | {"ID": 3, "X": 80, "Y": 0}, 34 | {"ID": 4, "X": 120, "Y": 0}, 35 | {"ID": 5, "X": 160, "Y": 0}, 36 | {"ID": 6, "X": 0, "Y": 0}, 37 | {"ID": 7, "X": 40, "Y": 0}, 38 | {"ID": 8, "X": 80, "Y": 0}, 39 | {"ID": 9, "X": 120, "Y": 0}, 40 | {"ID": 10, "X": 160, "Y": 0} 41 | ], 42 | "LOG_LEVELS" : { 43 | "Mobility": 4 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/check_par.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the PAR in a test. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | 11 | if (process.argv.length < 3) { 12 | console.log("results file not supplied"); 13 | process.exit(1); 14 | } 15 | 16 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1]); 17 | const state = JSON.parse(filedata); 18 | const run_state = state[RUN_ID]; 19 | let last_par = 1.0; 20 | let had_no_packets = false; 21 | 22 | for (let key in run_state) { 23 | if (key === "global-stats" || key === ROOT_ID) continue; 24 | 25 | const tx = run_state[key].mac_parent_tx_unicast; 26 | const acked = run_state[key].mac_parent_acked; 27 | 28 | if (!tx) { 29 | had_no_packets = true; 30 | } else if (had_no_packets) { 31 | console.log("Some nodes unexpectedly transmitted no app packets, while nodes with worse links had packets"); 32 | process.exit(1); 33 | } 34 | 35 | let par = acked / tx; 36 | if (par > last_par) { 37 | console.log(`More packets acked, but link quality is worse: ${par} vs ${last_par}`); 38 | console.log(" state:\n" + JSON.stringify(run_state[key])); 39 | process.exit(1); 40 | } 41 | /* console.log(`Fewer or some packets acked and link quality is worse: ${par} vs ${last_par}`); */ 42 | last_par = par; 43 | } 44 | 45 | if (had_no_packets === false) { 46 | console.log("The last entry had some packets, expected none"); 47 | process.exit(1); 48 | } 49 | 50 | const pdr = run_state["global-stats"]["e2e-delivery"].value; 51 | if (pdr <= 0) { 52 | console.log("Zero PDR for all nodes"); 53 | process.exit(1); 54 | } 55 | 56 | /* success */ 57 | process.exit(0); 58 | -------------------------------------------------------------------------------- /tests/check_log_stats.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the TSCH "stats" printed in the log file is correct. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | if (process.argv.length < 3) { 9 | console.log("log file not supplied"); 10 | process.exit(1); 11 | } 12 | 13 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1], "utf8"); 14 | const lines = filedata.split("\n"); 15 | let is_all_valid = true; 16 | let is_any_valid = false; 17 | 18 | for (let line of lines) { 19 | if (line.indexOf("TSCH\tstats:") !== -1) { 20 | const fields = line.substr(10).split(/(\s)+/); 21 | if (fields.length > 6) { 22 | const stats = fields.slice(6, fields.length); 23 | let stats_obj; 24 | try { 25 | stats_obj = JSON.parse(stats); 26 | } catch (x) { 27 | console.log("Failed to parse, line=\"" + line + "\""); 28 | is_all_valid = false; 29 | continue; 30 | } 31 | let ok = true; 32 | for (let key in stats_obj) { 33 | const value = stats_obj[key]; 34 | if (isNaN(parseInt(value))) { 35 | console.log("Failed to parse as integer, value=" + value); 36 | is_all_valid = false; 37 | ok = false; 38 | } 39 | } 40 | if (ok) { 41 | is_any_valid = true; 42 | } 43 | } 44 | } 45 | } 46 | 47 | if (!is_any_valid) { 48 | console.log("No any TSCH stats in the log file"); 49 | process.exit(1); 50 | } 51 | 52 | if (!is_all_valid) { 53 | console.log("Not all TSCH stats are valid in the log file"); 54 | process.exit(1); 55 | } 56 | 57 | /* success */ 58 | process.exit(0); 59 | -------------------------------------------------------------------------------- /source/expose.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * This simple script exposes the `__dirname` variable to *.mjs files, 31 | * since this variable is only availanle in classic NodeJS, not from JS modules. 32 | */ 33 | module.exports = {__dirname}; 34 | -------------------------------------------------------------------------------- /tests/link_model/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: fixed fixed-per-channel-quality udgm udgm-constant-loss logistic-loss pister-hack trace 4 | 5 | fixed: fixed.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 10 | 11 | fixed-per-channel-quality: fixed-per-channel-quality.json 12 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 13 | @rm -rf results 14 | ../../tsch-sim.sh $< > /dev/null 15 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 16 | 17 | udgm: udgm.json 18 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 19 | @rm -rf results 20 | ../../tsch-sim.sh $< > /dev/null 21 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 22 | 23 | udgm-constant-loss: udgm-constant-loss.json 24 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 25 | @rm -rf results 26 | ../../tsch-sim.sh $< > /dev/null 27 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 28 | 29 | logistic-loss: logistic-loss.json 30 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 31 | @rm -rf results 32 | ../../tsch-sim.sh $< > /dev/null 33 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 34 | 35 | pister-hack: pister-hack.json 36 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 37 | @rm -rf results 38 | ../../tsch-sim.sh $< > /dev/null 39 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 40 | 41 | trace: trace.json 42 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 43 | @rm -rf results 44 | ../../tsch-sim.sh $< > /dev/null 45 | $(NODE) --harmony --experimental-modules ../check_par.mjs results/stats.json 46 | 47 | clean: 48 | rm -rf results 49 | -------------------------------------------------------------------------------- /tests/check_pdr.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that the end-to-end PDR in a test reaches a specified minimum on all non-root nodes. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | let only_id = null; 11 | 12 | let min_pdr = 100.0; 13 | 14 | if (process.argv.length < 3) { 15 | console.log("results file not supplied"); 16 | process.exit(1); 17 | } 18 | 19 | if (process.argv.length > 4) { 20 | only_id = parseInt(process.argv[process.argv.length - 1]).toString(); 21 | process.argv.length -= 1; 22 | } 23 | 24 | if (process.argv.length > 3) { 25 | min_pdr = parseFloat(process.argv[process.argv.length - 1]); 26 | process.argv.length -= 1; 27 | } 28 | 29 | const filedata = fs.readFileSync(process.argv[process.argv.length - 1]); 30 | const state = JSON.parse(filedata); 31 | const run_state = state[RUN_ID]; 32 | let is_any_valid = false; 33 | 34 | for (let key in run_state) { 35 | if (key === "global-stats") continue; 36 | 37 | if (only_id && key !== only_id) continue; 38 | if (only_id !== ROOT_ID && key === ROOT_ID) continue; 39 | 40 | const tx = run_state[key].app_num_tx; 41 | const lost = run_state[key].app_num_lost; 42 | const pdr = run_state[key].app_reliability; 43 | 44 | if (!tx) { 45 | console.log("No app data packets transmitted from node " + key); 46 | console.log(" state:\n" + JSON.stringify(run_state[key])); 47 | process.exit(1); 48 | } 49 | 50 | if (pdr < min_pdr) { 51 | console.log("Too many app data packets lost from node " + key + " pdr=" + pdr.toFixed(3) + " lost=" + lost); 52 | console.log(" state:\n" + JSON.stringify(run_state[key])); 53 | process.exit(1); 54 | } 55 | is_any_valid = true; 56 | } 57 | 58 | if (!is_any_valid) { 59 | console.log("No valid nodes in the results"); 60 | process.exit(1); 61 | } 62 | 63 | /* success */ 64 | process.exit(0); 65 | -------------------------------------------------------------------------------- /tests/link_model/fixed-per-channel-quality.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "AWGN_GAUSSIAN_STD": 3.0, 5 | "NODE_TYPES": [ 6 | { 7 | "NAME": "node", 8 | "START_ID": 1, 9 | "COUNT": 6, 10 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 11 | } 12 | ], 13 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 14 | "CONNECTIONS" : [ 15 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": {"15": 1.0, "20": 1.0, "25": 1.0, "26": 1.0}, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": {"15": 1.0, "20": 1.0, "25": 1.0, "26": 1.0}, "LINK_MODEL": "Fixed" }, 17 | 18 | { "FROM_ID": 1, "TO_ID": 3, "RSSI": -40, "LINK_QUALITY": {"15": 1.0, "20": 1.0, "25": 0.5, "26": 0.5}, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 3, "TO_ID": 1, "RSSI": -40, "LINK_QUALITY": {"15": 1.0, "20": 1.0, "25": 0.5, "26": 0.5}, "LINK_MODEL": "Fixed" }, 20 | 21 | { "FROM_ID": 1, "TO_ID": 4, "RSSI": -60, "LINK_QUALITY": {"15": 0.5, "20": 0.5, "25": 0.5, "26": 0.5}, "LINK_MODEL": "Fixed" }, 22 | { "FROM_ID": 4, "TO_ID": 1, "RSSI": -60, "LINK_QUALITY": {"15": 0.5, "20": 0.5, "25": 0.5, "26": 0.5}, "LINK_MODEL": "Fixed" }, 23 | 24 | { "FROM_ID": 1, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": {"15": 0.5, "20": 0.5, "25": 0.0, "26": 0.0}, "LINK_MODEL": "Fixed" }, 25 | { "FROM_ID": 5, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": {"15": 0.5, "20": 0.5, "25": 0.0, "26": 0.0}, "LINK_MODEL": "Fixed" }, 26 | 27 | { "FROM_ID": 1, "TO_ID": 6, "RSSI": -200, "LINK_QUALITY": {"15": 0.0, "20": 0.0, "25": 0.5, "26": 0.0}, "LINK_MODEL": "Fixed" }, 28 | { "FROM_ID": 6, "TO_ID": 1, "RSSI": -200, "LINK_QUALITY": {"15": 0.0, "20": 0.0, "25": 0.5, "26": 0.0}, "LINK_MODEL": "Fixed" } 29 | ], 30 | "LOG_LEVELS" : { 31 | "RPL": 3, 32 | "TSCH": 3, 33 | "Node": 3, 34 | "App": 2 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/line/config-manual.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 1200, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "node", 6 | "START_ID": 1, 7 | "COUNT": 10, 8 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 9 | } 10 | ], 11 | "CONNECTIONS": [ 12 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 13 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 14 | { "FROM_ID": 2, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 3, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 16 | { "FROM_ID": 3, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 17 | { "FROM_ID": 4, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 18 | { "FROM_ID": 4, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 19 | { "FROM_ID": 5, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 20 | { "FROM_ID": 5, "TO_ID": 6, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 21 | { "FROM_ID": 6, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 22 | { "FROM_ID": 6, "TO_ID": 7, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 23 | { "FROM_ID": 7, "TO_ID": 6, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 24 | { "FROM_ID": 7, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 25 | { "FROM_ID": 8, "TO_ID": 7, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 26 | { "FROM_ID": 8, "TO_ID": 9, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 27 | { "FROM_ID": 9, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 28 | { "FROM_ID": 9, "TO_ID": 10, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" }, 29 | { "FROM_ID": 10, "TO_ID": 9, "RSSI": -80, "LINK_QUALITY": 0.95, "LINK_MODEL": "Fixed" } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /source/status.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * This is the status information, shared with the web interface on refresh. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | const status = { 37 | simulator : {is_running: false, asn: 0}, 38 | network: { 39 | nodes: [], 40 | links: [], 41 | transmissions: [], 42 | }, 43 | schedule: [], 44 | log: [], 45 | } 46 | 47 | export default status; 48 | -------------------------------------------------------------------------------- /tests/check_stats_value.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file checks that a result statistic is equal to a value passed a an argument 3 | */ 4 | 5 | import fs from 'fs'; 6 | import process from 'process'; 7 | 8 | const RUN_ID = "0"; 9 | const ROOT_ID = "1"; 10 | 11 | if (process.argv.length < 6) { 12 | console.log("results file, node ID, stat name and value not supplied"); 13 | process.exit(1); 14 | } 15 | 16 | const filedata = fs.readFileSync(process.argv[2]); 17 | const node_id = parseInt(process.argv[3]).toString(); 18 | const stat_name = process.argv[4]; 19 | const op = process.argv[5]; 20 | const required_stat_value = parseFloat(process.argv[6]); 21 | 22 | const state = JSON.parse(filedata); 23 | const run_state = state[RUN_ID]; 24 | let is_any_valid = false; 25 | 26 | for (let key in run_state) { 27 | if (node_id == -1) { 28 | if (key === "global-stats" || key === ROOT_ID) continue; 29 | } else { 30 | if (key !== node_id) continue; 31 | } 32 | 33 | const stat_value = run_state[key][stat_name]; 34 | if (op == "=") { 35 | if (!(stat_value == required_stat_value)) { 36 | console.log(`For "${stat_name}" value ${stat_value} is not equal to required value ${required_stat_value}`); 37 | process.exit(1); 38 | } 39 | } else if (op == ">") { 40 | if (!(stat_value > required_stat_value)) { 41 | console.log(`For "${stat_name}" value ${stat_value} is not greater than required value ${required_stat_value}`); 42 | process.exit(1); 43 | } 44 | } else if (op == "<") { 45 | if (!(stat_value < required_stat_value)) { 46 | console.log(`For "${stat_name}" value ${stat_value} is not lesser than required value ${required_stat_value}`); 47 | process.exit(1); 48 | } 49 | } else { 50 | console.log(`Unknown op ${op} for ${stat_name}`); 51 | process.exit(1); 52 | } 53 | 54 | is_any_valid = true; 55 | } 56 | 57 | if (!is_any_valid) { 58 | console.log("No stats found for node " + node_id); 59 | process.exit(1); 60 | } 61 | 62 | /* success */ 63 | process.exit(0); 64 | -------------------------------------------------------------------------------- /tests/link_model/fixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 3600, 3 | "RESULTS_DIR": "./results", 4 | "NODE_TYPES": [ 5 | { 6 | "NAME": "node", 7 | "START_ID": 1, 8 | "COUNT": 11, 9 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 10 | } 11 | ], 12 | "MAC_HOPPING_SEQUENCE" : "TSCH_HOPPING_SEQUENCE_1_1", 13 | "CONNECTIONS" : [ 14 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 15 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -20, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 16 | 17 | { "FROM_ID": 1, "TO_ID": 3, "RSSI": -30, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 18 | { "FROM_ID": 3, "TO_ID": 1, "RSSI": -30, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 19 | 20 | { "FROM_ID": 1, "TO_ID": 4, "RSSI": -40, "LINK_QUALITY": 0.8, "LINK_MODEL": "Fixed" }, 21 | { "FROM_ID": 4, "TO_ID": 1, "RSSI": -40, "LINK_QUALITY": 0.8, "LINK_MODEL": "Fixed" }, 22 | 23 | { "FROM_ID": 1, "TO_ID": 5, "RSSI": -50, "LINK_QUALITY": 0.7, "LINK_MODEL": "Fixed" }, 24 | { "FROM_ID": 5, "TO_ID": 1, "RSSI": -50, "LINK_QUALITY": 0.7, "LINK_MODEL": "Fixed" }, 25 | 26 | { "FROM_ID": 1, "TO_ID": 6, "RSSI": -60, "LINK_QUALITY": 0.6, "LINK_MODEL": "Fixed" }, 27 | { "FROM_ID": 6, "TO_ID": 1, "RSSI": -60, "LINK_QUALITY": 0.6, "LINK_MODEL": "Fixed" }, 28 | 29 | { "FROM_ID": 1, "TO_ID": 7, "RSSI": -70, "LINK_QUALITY": 0.5, "LINK_MODEL": "Fixed" }, 30 | { "FROM_ID": 7, "TO_ID": 1, "RSSI": -70, "LINK_QUALITY": 0.5, "LINK_MODEL": "Fixed" }, 31 | 32 | { "FROM_ID": 1, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.4, "LINK_MODEL": "Fixed" }, 33 | { "FROM_ID": 8, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 0.4, "LINK_MODEL": "Fixed" }, 34 | 35 | { "FROM_ID": 1, "TO_ID": 9, "RSSI": -90, "LINK_QUALITY": 0.3, "LINK_MODEL": "Fixed" }, 36 | { "FROM_ID": 9, "TO_ID": 1, "RSSI": -90, "LINK_QUALITY": 0.3, "LINK_MODEL": "Fixed" }, 37 | 38 | { "FROM_ID": 1, "TO_ID": 10, "RSSI": -100, "LINK_QUALITY": 0.2, "LINK_MODEL": "Fixed" }, 39 | { "FROM_ID": 10, "TO_ID": 1, "RSSI": -100, "LINK_QUALITY": 0.2, "LINK_MODEL": "Fixed" }, 40 | 41 | { "FROM_ID": 1, "TO_ID": 11, "RSSI": -200, "LINK_QUALITY": 0.0, "LINK_MODEL": "Fixed" }, 42 | { "FROM_ID": 11, "TO_ID": 1, "RSSI": -200, "LINK_QUALITY": 0.0, "LINK_MODEL": "Fixed" } 43 | ], 44 | "LOG_LEVELS" : { 45 | "RPL": 3, 46 | "TSCH": 3, 47 | "Node": 3, 48 | "App": 2 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/js/notifications.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 IBM Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | TSCH_SIM.notify = function() { 17 | var currentNotifications = []; 18 | var c = 0; 19 | return function(msg,type,fixed,timeout) { 20 | if (currentNotifications.length > 4) { 21 | var ll = currentNotifications.length; 22 | for (var i = 0;ll > 4 && i 36 | */ 37 | 38 | import path from 'path'; 39 | import config from "./config.mjs"; 40 | 41 | /* the directory of the current script */ 42 | import expose from './expose.js'; 43 | const {__dirname} = expose; 44 | 45 | /* For Node 10+ versions, the directory of the current script is as simple as: */ 46 | /* const __dirname = path.dirname(fileURLToPath(import.meta.url)); */ 47 | 48 | /* decide the directory for the result files */ 49 | let results_dir = config.RESULTS_DIR; 50 | if (!results_dir) { 51 | const tzoffset = new Date(Date.now()).getTimezoneOffset() * 60000; 52 | const datetime = new Date(Date.now() - tzoffset).toISOString().replace(/T/, '_').replace(/\..+/, '').replace(/:/, '-').replace(/:/, '-'); 53 | results_dir = path.join(__dirname, "..", "results", datetime); 54 | } else { 55 | if (!path.isAbsolute(results_dir)) { 56 | /* set it relative to the configuration file */ 57 | results_dir = path.join(path.dirname(config.CONFIG_FILE), results_dir); 58 | } 59 | } 60 | 61 | const dirnames = {results_dir: results_dir, self_dir: __dirname}; 62 | export default dirnames; 63 | -------------------------------------------------------------------------------- /examples/scripting/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Simulator script example. 3 | * 4 | * The global simulator state is accessible through a `state` variable passed as argument to this script. 5 | * 6 | * The state has the following members with mostly self-explanatory names: 7 | * - is_running - Boolean flag, if set to true, the simulator will terminate 8 | * - network - network structure, keeps track of nodes and links 9 | * - timeline - has `seconds` and `asn` member variables 10 | * - scheduler - Orchestra or other 11 | * - routing - RPL or other 12 | * - config - configuration values from config file and the default config 13 | * - log - logging module 14 | */ 15 | 16 | /* this code is executed once, on startup */ 17 | state.log.log(state.log.INFO, null, "User", "---"); 18 | state.log.log(state.log.INFO, null, "User", "Hello world from user code!"); 19 | state.log.log(state.log.INFO, null, "User", `The network has ${state.network.nodes.size} nodes`); 20 | state.log.log(state.log.INFO, null, "User", "---"); 21 | 22 | /* The format of callback is: ASN -> function to call at that ASN */ 23 | let callbacks = { 24 | 100: function(state) { state.log.log(state.log.INFO, null, "User", "doing things at ASN=100"); }, 25 | 200: function(state) { state.log.log(state.log.INFO, null, "User", "doing things at ASN=200"); }, 26 | }; 27 | 28 | /* This function checks the connectivity of the leaf node and disabled */ 29 | function check_connectivity(state) { 30 | const node_id = 4; 31 | 32 | state.log.log(state.log.INFO, null, "User", `check_connectivity called at ASN=${state.timeline.asn}`); 33 | 34 | /* has the link been disabled already? */ 35 | if (this.is_link_disabled) { 36 | const node = state.network.get_node(node_id); 37 | /* has the node re-joined through alternative parent? */ 38 | if (node.routing.is_joined() 39 | && node.routing.preferred_parent.neighbor.id !== this.first_parent_id) { 40 | state.log.log(state.log.WARNING, null, "User", `node ${node_id} successfully switched parents (${this.first_parent_id} -> ${node.routing.preferred_parent.neighbor.id})`); 41 | /* terminate the simulation */ 42 | state.is_running = false; 43 | } 44 | return; 45 | } 46 | 47 | /* get node with ID 4 */ 48 | const node = state.network.get_node(node_id); 49 | if (!node.routing.is_joined()) { 50 | state.log.log(state.log.INFO, null, "User", `node ${node_id} not yet joined RPL`); 51 | return; 52 | } 53 | 54 | const parent_id = node.routing.preferred_parent.neighbor.id; 55 | state.log.log(state.log.WARNING, null, "User", `Disabling the link to ${parent_id} for the node ${node_id}`); 56 | 57 | const link_from = state.network.get_link(parent_id, node_id); 58 | const link_to = state.network.get_link(node_id, parent_id); 59 | link_from.link_quality = 0; 60 | link_to.link_quality = 0; 61 | /* remember variables for the next invocation */ 62 | this.is_link_disabled = true; 63 | this.first_parent_id = parent_id; 64 | } 65 | 66 | /* Check the connectivity periodically, once 500 ASN */ 67 | for (let i = 1000; i < state.config.SIMULATION_DURATION_SEC * 100; i += 500) { 68 | callbacks[i] = check_connectivity; 69 | } 70 | 71 | /* Set the callbacks to be executed during the simulation */ 72 | return callbacks; 73 | -------------------------------------------------------------------------------- /tests/tsch/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: empty eb keepalive desync join-hopseq join-hopseq-nojoin slot-duration non-standard-slots subslots \ 4 | start-joined different-hopping-sequences 5 | 6 | empty: empty.json 7 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 8 | @rm -rf results 9 | ../../tsch-sim.sh $< > /dev/null 10 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 11 | $(NODE) --harmony --experimental-modules ../check_log_stats.mjs results/log.txt 12 | 13 | eb: eb.json 14 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 15 | @rm -rf results 16 | ../../tsch-sim.sh $< > /dev/null 17 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 18 | grep "add a new EB packet" results/log.txt > /dev/null 19 | 20 | keepalive: keepalive.json 21 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 22 | @rm -rf results 23 | ../../tsch-sim.sh $< > /dev/null 24 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 25 | grep "send keepalive packet to=1" results/log.txt > /dev/null 26 | grep "keepalive packet sent: ok" results/log.txt > /dev/null 27 | 28 | desync: desync.json 29 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 30 | @rm -rf results 31 | ../../tsch-sim.sh $< > /dev/null 32 | grep "leaving network, did not resynchronize" results/log.txt > /dev/null 33 | 34 | join-hopseq: join-hopseq.json 35 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 36 | @rm -rf results 37 | ../../tsch-sim.sh $< > /dev/null 38 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 39 | 40 | join-hopseq-nojoin: join-hopseq-nojoin.json 41 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 42 | @rm -rf results 43 | ../../tsch-sim.sh $< > /dev/null 44 | grep "PDR=0.00%" results/log.txt > /dev/null 45 | 46 | slot-duration: slot-duration.json 47 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 48 | @rm -rf results 49 | ../../tsch-sim.sh $< > /dev/null 50 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 51 | ../invert_grep.sh "asn=60000" results/log.txt > /dev/null 52 | ../invert_grep.sh "asn=60001" results/log.txt > /dev/null 53 | 54 | non-standard-slots: non-standard-slots.json 55 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 56 | @rm -rf results 57 | ../../tsch-sim.sh $< > /dev/null 58 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 59 | ../invert_grep.sh "asn=60000" results/log.txt > /dev/null 60 | ../invert_grep.sh "asn=60001" results/log.txt > /dev/null 61 | 62 | subslots: subslots.json 63 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 64 | @rm -rf results 65 | ../../tsch-sim.sh $< > /dev/null 66 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 67 | 68 | start-joined: start-joined.json 69 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 70 | @rm -rf results 71 | ../../tsch-sim.sh $< > /dev/null 72 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 73 | 74 | different-hopping-sequences: different-hopping-sequences.json 75 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 76 | @rm -rf results 77 | ../../tsch-sim.sh $< > /dev/null 78 | $(NODE) --harmony --experimental-modules ../check_bad_par.mjs results/stats.json 79 | 80 | clean: 81 | rm -rf results 82 | -------------------------------------------------------------------------------- /source/routing_null.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Null routing implementation. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import config from './config.mjs'; 37 | 38 | export const plugin_name = "NullRouting"; 39 | 40 | /* Initialize the routing protocol configuration */ 41 | export function initialize(network) 42 | { 43 | const default_config = { 44 | }; 45 | 46 | for (const key in default_config) { 47 | /* set the ones that have not been set from the config file */ 48 | if (!config.hasOwnProperty(key)) { 49 | config[key] = default_config[key]; 50 | } 51 | } 52 | } 53 | 54 | /*---------------------------------------------------------------------------*/ 55 | 56 | class NullRouting 57 | { 58 | constructor(node) { 59 | this.node = node; 60 | } 61 | 62 | start() { 63 | /* nothing */ 64 | } 65 | 66 | on_tx(neighbor, packet, is_ok, is_ack_required) { 67 | /* nothing */ 68 | } 69 | 70 | on_prepare_tx_packet(packet) { 71 | /* nothing */ 72 | } 73 | 74 | on_forward(packet) { 75 | return true; 76 | } 77 | 78 | on_new_time_source(old_time_source, new_time_source) { 79 | /* nothing */ 80 | } 81 | 82 | local_repair() { 83 | /* nothing */ 84 | } 85 | 86 | is_joined() { 87 | return this.node.has_joined; 88 | } 89 | 90 | on_periodic_timer() { 91 | /* nothing */ 92 | } 93 | 94 | stats_get() { 95 | return { 96 | routing_tx: 0, 97 | routing_rx: 0, 98 | routing_join_time_sec: 0, 99 | routing_num_parent_changes : 1 100 | }; 101 | } 102 | 103 | } 104 | 105 | /*---------------------------------------------------------------------------*/ 106 | 107 | export function create_node_state(node) 108 | { 109 | return new NullRouting(node); 110 | } 111 | -------------------------------------------------------------------------------- /source/routing_lf.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * "Leaf and forwarder" routing implementation. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import config from './config.mjs'; 37 | 38 | export const plugin_name = "LeafAndForwarderRouting"; 39 | 40 | /* Initialize the protocol configuration */ 41 | export function initialize(network) 42 | { 43 | const default_config = { 44 | }; 45 | 46 | for (const key in default_config) { 47 | /* set the ones that have not been set from the config file */ 48 | if (!config.hasOwnProperty(key)) { 49 | config[key] = default_config[key]; 50 | } 51 | } 52 | } 53 | 54 | /*---------------------------------------------------------------------------*/ 55 | 56 | class LeafAndForwarderRouting 57 | { 58 | constructor(node) { 59 | this.node = node; 60 | } 61 | 62 | start() { 63 | /* nothing */ 64 | } 65 | 66 | on_tx(neighbor, packet, is_ok, is_ack_required) { 67 | /* nothing */ 68 | } 69 | 70 | on_prepare_tx_packet(packet) { 71 | /* nothing */ 72 | } 73 | 74 | on_forward(packet) { 75 | return true; 76 | } 77 | 78 | on_new_time_source(old_time_source, new_time_source) { 79 | if (new_time_source == null) { 80 | this.node.routes.remove_default_route(); 81 | } else { 82 | this.node.routes.add_default_route(new_time_source.id); 83 | } 84 | } 85 | 86 | local_repair() { 87 | /* nothing */ 88 | } 89 | 90 | is_joined() { 91 | return this.node.has_joined; 92 | } 93 | 94 | on_periodic_timer() { 95 | /* nothing */ 96 | } 97 | 98 | stats_get() { 99 | return { 100 | routing_tx: 0, 101 | routing_rx: 0, 102 | routing_join_time_sec: 0, 103 | routing_num_parent_changes : 1 104 | }; 105 | } 106 | } 107 | 108 | /*---------------------------------------------------------------------------*/ 109 | 110 | export function create_node_state(node) 111 | { 112 | return new LeafAndForwarderRouting(node); 113 | } 114 | -------------------------------------------------------------------------------- /source/random.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Random Number Generator (RNG) implementation. 32 | * The default JavaScript RNG cannot be seeded, meaning that reproducing simulation 33 | * results is difficult. This module implements an alternative RNG: 34 | * multiply-with-carry RNG, in order to get better reproducibility. 35 | * 36 | * \author 37 | * Atis Elsts 38 | */ 39 | 40 | export class Random { 41 | constructor() { 42 | this.m_w = 123456789; 43 | this.m_z = 987654321; 44 | this.mask = 0xffffffff; 45 | this.set_seed = 0; 46 | } 47 | 48 | /* Seeds the RNG. Takes any integer as the seed */ 49 | seed(i) { 50 | this.m_w = (123456789 + i) & this.mask; 51 | this.m_z = (987654321 - i) & this.mask; 52 | this.set_seed = i; 53 | } 54 | 55 | /* Returns number between 0 (inclusive) and 1.0 (exclusive), just like Math.random() */ 56 | random() { 57 | this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & this.mask; 58 | this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & this.mask; 59 | let result = ((this.m_z << 16) + (this.m_w & 65535)) >>> 0; 60 | result /= 4294967296.0; 61 | return result; 62 | } 63 | 64 | /* Returns a number in the interval [a, b), drawn from the uniform distribution */ 65 | uniform(a, b) { 66 | const delta = b - a; 67 | return this.random() * delta + a; 68 | } 69 | 70 | /* Returns a random point in the interval [I/2, I) - see RFC 6206 */ 71 | trickle_random(interval) { 72 | return this.uniform(interval / 2, interval); 73 | } 74 | 75 | /* Returns a random integer in the interval [a, b), drawn from the uniform distribution */ 76 | randint(a, b) { 77 | return Math.trunc(this.uniform(a, b)); 78 | } 79 | 80 | /* Returns a random number from the normal distribution N(0, 1) */ 81 | next_gaussian() { 82 | let u, v; 83 | do { 84 | u = this.random(); 85 | } while (u === 0); /* Converting [0,1) to (0,1) */ 86 | do { 87 | v = this.random(); 88 | } while (v === 0); /* Converting [0,1) to (0,1) */ 89 | /* uses the Box-Muller transform to convert uniform to Gaussian distribution */ 90 | return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); 91 | } 92 | } 93 | 94 | export const rng = new Random(); 95 | -------------------------------------------------------------------------------- /tests/routing/lf.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "RESULTS_DIR": "./results", 4 | "SCHEDULING_ALGORITHM": "LeafAndForwarder", 5 | "ROUTING_ALGORITHM": "LeafAndForwarderRouting", 6 | "NODE_TYPES": [ 7 | { 8 | "NAME": "root", 9 | "START_ID": 1, 10 | "COUNT": 1 11 | }, 12 | { 13 | "NAME": "forwarder", 14 | "START_ID": 2, 15 | "COUNT": 3, 16 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 17 | }, 18 | { 19 | "NAME": "leaf", 20 | "START_ID": 5, 21 | "COUNT": 6, 22 | "ROUTING_IS_LEAF": true, 23 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 24 | } 25 | ], 26 | "CONNECTIONS": [ 27 | { "FROM_ID": 1, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 28 | { "FROM_ID": 2, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 29 | { "FROM_ID": 1, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 30 | { "FROM_ID": 3, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 31 | { "FROM_ID": 1, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 32 | { "FROM_ID": 4, "TO_ID": 1, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 33 | 34 | { "FROM_ID": 2, "TO_ID": 5, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 35 | { "FROM_ID": 5, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 36 | { "FROM_ID": 2, "TO_ID": 6, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 37 | { "FROM_ID": 6, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 38 | { "FROM_ID": 2, "TO_ID": 7, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 39 | { "FROM_ID": 7, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 40 | { "FROM_ID": 2, "TO_ID": 8, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 41 | { "FROM_ID": 8, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 42 | { "FROM_ID": 2, "TO_ID": 9, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 43 | { "FROM_ID": 9, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 44 | { "FROM_ID": 2, "TO_ID": 10, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 45 | { "FROM_ID": 10, "TO_ID": 2, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 46 | 47 | { "FROM_ID": 3, "TO_ID": 5, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 48 | { "FROM_ID": 5, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 49 | { "FROM_ID": 3, "TO_ID": 6, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 50 | { "FROM_ID": 6, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 51 | { "FROM_ID": 3, "TO_ID": 7, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 52 | { "FROM_ID": 7, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 53 | { "FROM_ID": 3, "TO_ID": 8, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 54 | { "FROM_ID": 8, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 55 | { "FROM_ID": 3, "TO_ID": 9, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 56 | { "FROM_ID": 9, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 57 | { "FROM_ID": 3, "TO_ID": 10, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 58 | { "FROM_ID": 10, "TO_ID": 3, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 59 | 60 | { "FROM_ID": 4, "TO_ID": 5, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 61 | { "FROM_ID": 5, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 62 | { "FROM_ID": 4, "TO_ID": 6, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 63 | { "FROM_ID": 6, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 64 | { "FROM_ID": 4, "TO_ID": 7, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 65 | { "FROM_ID": 7, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 66 | { "FROM_ID": 4, "TO_ID": 8, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 67 | { "FROM_ID": 8, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 68 | { "FROM_ID": 4, "TO_ID": 9, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 69 | { "FROM_ID": 9, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 70 | { "FROM_ID": 4, "TO_ID": 10, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" }, 71 | { "FROM_ID": 10, "TO_ID": 4, "LINK_QUALITY": 1.0, "LINK_MODEL": "Fixed" } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /source/heap.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Heap data structure library. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import { assert } from "./utils.mjs"; 37 | 38 | /* Insert a new element */ 39 | export function heap_insert(heap, element, less_than) 40 | { 41 | let i = heap.length; 42 | heap.push(element); 43 | element.heap_position = i; 44 | while (i > 0) { 45 | const parent = Math.trunc((i - 1) / 2); 46 | if (less_than(heap[i], heap[parent])) { 47 | const t = heap[parent]; 48 | heap[parent] = heap[i]; 49 | heap[parent].heap_position = parent; 50 | heap[i] = t; 51 | t.heap_position = i; 52 | i = parent; 53 | } else { 54 | break; 55 | } 56 | } 57 | } 58 | 59 | /* Remove and return the minimum (root) element */ 60 | export function heap_extract_min(heap, less_than) 61 | { 62 | assert(heap.length, "heap must be nonempty"); 63 | const result = heap[0]; 64 | result.heap_position = -1; 65 | heap[0] = heap[heap.length - 1]; 66 | heap[0].heap_position = 0; 67 | heap.pop(); 68 | min_heapify(heap, 0, less_than); 69 | return result; 70 | } 71 | 72 | /* Remove an element at specific position `i` */ 73 | export function heap_remove_at(heap, i, less_than) 74 | { 75 | assert(i < heap.length, "nonexistent heap element"); 76 | heap[i].heap_position = -1; 77 | heap[i] = heap[heap.length - 1]; 78 | heap[i].heap_position = i; 79 | heap.pop(); 80 | min_heapify(heap, i, less_than); 81 | } 82 | 83 | /* Restore the heap property for element at position `i` */ 84 | export function min_heapify(heap, i, less_than) 85 | { 86 | const left = i * 2 + 1; 87 | const right = i * 2 + 2; 88 | let smallest = i; 89 | if (left < heap.length && less_than(heap[left], heap[smallest])) { 90 | smallest = left; 91 | } 92 | if (right < heap.length && less_than(heap[right], heap[smallest])) { 93 | smallest = right; 94 | } 95 | 96 | if (smallest !== i) { 97 | const t = heap[smallest]; 98 | heap[smallest] = heap[i]; 99 | heap[smallest].heap_position = smallest; 100 | heap[i] = t; 101 | t.heap_position = i; 102 | min_heapify(heap, smallest, less_than); 103 | } 104 | } 105 | 106 | /* Test the heap functionality */ 107 | export function test() 108 | { 109 | const heap = []; 110 | const lt = function(a, b) { return a < b; } 111 | heap_insert(heap, 11, lt); 112 | heap_insert(heap, 5, lt); 113 | heap_insert(heap, 8, lt); 114 | heap_insert(heap, 4, lt); 115 | heap_insert(heap, 3, lt); 116 | 117 | while (heap.length) { 118 | console.log(heap_extract_min(heap, lt)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /source/log.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Logging module 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import config from "./config.mjs"; 37 | import status from "./status.mjs"; 38 | import fs from 'fs'; 39 | 40 | /* Supported log levels */ 41 | export const ERROR = 1; 42 | export const WARNING = 2; 43 | export const INFO = 3; 44 | export const DEBUG = 4; 45 | 46 | /* Filled by the time module to avoid circular dependencies in imports */ 47 | let timeline = null; 48 | 49 | /* Log a message */ 50 | export function log(severity, node, topic, msg) 51 | { 52 | let log_level; 53 | if (config.LOG_LEVELS && config.LOG_LEVELS.hasOwnProperty(topic)) { 54 | log_level = config.LOG_LEVELS[topic]; 55 | } else if (config.LOG_LEVELS_DEFAULT.hasOwnProperty(topic)) { 56 | log_level = config.LOG_LEVELS_DEFAULT[topic]; 57 | } else { 58 | log_level = config.LOG_LEVEL_DEFAULT_NOTOPIC; 59 | } 60 | 61 | let node_s; 62 | if (node) { 63 | /* Use the node string ID */ 64 | node_s = node.sid; 65 | } else { 66 | /* Use -1 for log entries without an associated node */ 67 | node_s = "-1 "; 68 | } 69 | 70 | if (severity <= log_level) { 71 | if (severity <= ERROR) { 72 | msg = "error: " + msg; 73 | } else if (severity === WARNING) { 74 | msg = "warning: " + msg; 75 | } 76 | 77 | if (config.WEB_ENABLED) { 78 | if (status.log.length >= config.WEB_MAX_LOGS) { 79 | status.log.shift(); 80 | } 81 | status.log.push({node: node_s, time: timeline.seconds, topic: topic, msg: msg}); 82 | } 83 | 84 | /* Add ASN and node id to the message */ 85 | let sasn = `${timeline.asn}`; 86 | if (timeline.asn < 10000) { 87 | sasn += ' '; 88 | if (timeline.asn < 1000) { 89 | sasn += ' '; 90 | if (timeline.asn < 100) { 91 | sasn += ' '; 92 | if (timeline.asn < 10) { 93 | sasn += ' '; 94 | } 95 | } 96 | } 97 | } 98 | 99 | /* Sanitize the message before logging it */ 100 | msg = msg.replace(/\n|\r/g, ""); 101 | 102 | msg = `asn=${sasn}\tid=${node_s}\t${topic}\t${msg}`; 103 | if (severity <= WARNING) { 104 | console.log("\x1b[31m" + msg + "\x1b[0m"); 105 | } else { 106 | console.log(msg); 107 | } 108 | if (config.SAVE_RESULTS || config.LOG_FILE) { 109 | fs.appendFileSync(config.LOG_FILE, msg + "\n"); 110 | } 111 | } 112 | } 113 | 114 | /* ------------------------------------- */ 115 | 116 | export function initialize(tl) 117 | { 118 | timeline = tl; 119 | } 120 | -------------------------------------------------------------------------------- /tests/scheduling/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: orchestra-rb-ns orchestra-nondefault-periods orchestra-active-period orchestra-rb-storing orchestra-sb-storing orchestra-special-for-root orchestra-link-based lf 6tisch-min 100percent-line 100percent-star 4 | 5 | orchestra-rb-ns: orchestra-rb-ns.json 6 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 7 | @rm -rf results 8 | ../../tsch-sim.sh $< > /dev/null 9 | grep "initializing rule unicast per neighbor non-storing" results/log.txt > /dev/null 10 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90.0 11 | 12 | orchestra-nondefault-periods: orchestra-with-nondefault-periods.json 13 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 14 | @rm -rf results 15 | ../../tsch-sim.sh $< > /dev/null 16 | grep "initializing rule EB per time source (handle=0 slotframe_size=299)" results/log.txt > /dev/null 17 | grep "initializing rule unicast per neighbor non-storing (handle=1 slotframe_size=7)" results/log.txt > /dev/null 18 | grep "initializing rule default common (handle=2 slotframe_size=17)" results/log.txt > /dev/null 19 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90.0 20 | 21 | orchestra-active-period: orchestra-with-shorter-active-period.json 22 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 23 | @rm -rf results 24 | ../../tsch-sim.sh $< > /dev/null 25 | grep "using a shorter active period for unicast frames: 7 vs 17" results/log.txt > /dev/null 26 | grep "initializing rule unicast per neighbor non-storing" results/log.txt > /dev/null 27 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90.0 28 | 29 | 30 | orchestra-rb-storing: orchestra-rb-storing.json 31 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 32 | @rm -rf results 33 | ../../tsch-sim.sh $< > /dev/null 34 | grep "initializing rule unicast per neighbor storing" results/log.txt > /dev/null 35 | grep "storing rule, receiver based" results/log.txt > /dev/null 36 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90.0 37 | 38 | orchestra-sb-storing: orchestra-sb-storing.json 39 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 40 | @rm -rf results 41 | ../../tsch-sim.sh $< > /dev/null 42 | grep "initializing rule unicast per neighbor storing" results/log.txt > /dev/null 43 | grep "storing rule, sender based" results/log.txt > /dev/null 44 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 95.0 45 | 46 | orchestra-special-for-root: orchestra-special-for-root.json 47 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 48 | @rm -rf results 49 | ../../tsch-sim.sh $< > /dev/null 50 | grep "initializing rule special for root" results/log.txt > /dev/null 51 | grep "initializing rule unicast per neighbor storing" results/log.txt > /dev/null 52 | grep "storing rule, receiver based" results/log.txt > /dev/null 53 | grep "special_for_root_select_packet: use the root rule" results/log.txt > /dev/null 54 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 55 | 56 | orchestra-link-based: orchestra-link-based.json 57 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 58 | @rm -rf results 59 | ../../tsch-sim.sh $< > /dev/null 60 | grep "initializing rule unicast per neighbor link based" results/log.txt > /dev/null 61 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 90.0 62 | 63 | 6tisch-min: 6tisch-min.json 64 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 65 | @rm -rf results 66 | ../../tsch-sim.sh $< > /dev/null 67 | grep "initializing 6tisch minimal" results/log.txt > /dev/null 68 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 95.0 69 | 70 | lf: lf.json 71 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 72 | @rm -rf results 73 | ../../tsch-sim.sh $< > /dev/null 74 | grep "initializing leaf-and-forwarder scheduler" results/log.txt > /dev/null 75 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 95.0 76 | 77 | 100percent-line: 100percent-line.json 78 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 79 | @rm -rf results 80 | ../../tsch-sim.sh $< > /dev/null 81 | $(NODE) --harmony --experimental-modules ../check_good_par.mjs results/stats.json 82 | 83 | 100percent-star: 100percent-star.json 84 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 85 | @rm -rf results 86 | ../../tsch-sim.sh $< > /dev/null 87 | $(NODE) --harmony --experimental-modules ../check_good_par.mjs results/stats.json 88 | 89 | clean: 90 | rm -rf results 91 | -------------------------------------------------------------------------------- /examples/hierarchical/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SIMULATION_DURATION_SEC": 600, 3 | "NODE_TYPES": [ 4 | { 5 | "NAME": "root", 6 | "START_ID": 1, 7 | "COUNT": 1 8 | }, 9 | { 10 | "NAME": "forwarder", 11 | "START_ID": 2, 12 | "COUNT": 3, 13 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 14 | }, 15 | { 16 | "NAME": "leaf", 17 | "START_ID": 5, 18 | "COUNT": 6, 19 | "ROUTING_IS_LEAF": true, 20 | "APP_PACKETS": {"APP_PACKET_PERIOD_SEC": 10, "TO_ID": 1} 21 | } 22 | ], 23 | "CONNECTIONS": [ 24 | { "FROM_ID": 1, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 25 | { "FROM_ID": 2, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 26 | { "FROM_ID": 1, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 27 | { "FROM_ID": 3, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 28 | { "FROM_ID": 1, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 29 | { "FROM_ID": 4, "TO_ID": 1, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 30 | 31 | { "FROM_ID": 2, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 32 | { "FROM_ID": 5, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 33 | { "FROM_ID": 2, "TO_ID": 6, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 34 | { "FROM_ID": 6, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 35 | { "FROM_ID": 2, "TO_ID": 7, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 36 | { "FROM_ID": 7, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 37 | { "FROM_ID": 2, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 38 | { "FROM_ID": 8, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 39 | { "FROM_ID": 2, "TO_ID": 9, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 40 | { "FROM_ID": 9, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 41 | { "FROM_ID": 2, "TO_ID": 10, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 42 | { "FROM_ID": 10, "TO_ID": 2, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 43 | 44 | { "FROM_ID": 3, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 45 | { "FROM_ID": 5, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 46 | { "FROM_ID": 3, "TO_ID": 6, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 47 | { "FROM_ID": 6, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 48 | { "FROM_ID": 3, "TO_ID": 7, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 49 | { "FROM_ID": 7, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 50 | { "FROM_ID": 3, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 51 | { "FROM_ID": 8, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 52 | { "FROM_ID": 3, "TO_ID": 9, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 53 | { "FROM_ID": 9, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 54 | { "FROM_ID": 3, "TO_ID": 10, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 55 | { "FROM_ID": 10, "TO_ID": 3, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 56 | 57 | { "FROM_ID": 4, "TO_ID": 5, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 58 | { "FROM_ID": 5, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 59 | { "FROM_ID": 4, "TO_ID": 6, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 60 | { "FROM_ID": 6, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 61 | { "FROM_ID": 4, "TO_ID": 7, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 62 | { "FROM_ID": 7, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 63 | { "FROM_ID": 4, "TO_ID": 8, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 64 | { "FROM_ID": 8, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 65 | { "FROM_ID": 4, "TO_ID": 9, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 66 | { "FROM_ID": 9, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 67 | { "FROM_ID": 4, "TO_ID": 10, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" }, 68 | { "FROM_ID": 10, "TO_ID": 4, "RSSI": -80, "LINK_QUALITY": 0.9, "LINK_MODEL": "Fixed" } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /source/scheduler_6tisch_min.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * 6TiSCH minimal scheduler. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import config from './config.mjs'; 37 | import constants from './constants.mjs'; 38 | import * as log from './log.mjs'; 39 | import * as time from './time.mjs'; 40 | 41 | export const plugin_name = "6tischMin"; 42 | 43 | /* ------------------------------------------------- */ 44 | 45 | function set_timings() 46 | { 47 | const sf_size = config.TSCH_SCHEDULE_CONF_DEFAULT_LENGTH ? config.TSCH_SCHEDULE_CONF_DEFAULT_LENGTH : 1; 48 | 49 | let timings_usec = new Array(sf_size); 50 | /* all slots have the same duration */ 51 | for (let i = 0; i < sf_size; ++i) { 52 | timings_usec[i] = config.MAC_SLOT_DURATION_US; 53 | } 54 | time.timeline.slot_timings = timings_usec.map(x => x / 1000000); /* convert to seconds */ 55 | } 56 | 57 | /*---------------------------------------------------------------------------*/ 58 | 59 | export function on_new_time_source(node, old_neighbor, new_neighbor) 60 | { 61 | /* nothing */ 62 | } 63 | 64 | export function on_child_added(node, addr) 65 | { 66 | /* nothing */ 67 | } 68 | 69 | export function on_child_removed(node, addr) 70 | { 71 | /* nothing */ 72 | } 73 | 74 | export function on_packet_ready(node, packet) 75 | { 76 | /* always accept */ 77 | return true; 78 | } 79 | 80 | export function on_tx(node, packet, status_ok) 81 | { 82 | /* nothing */ 83 | } 84 | 85 | export function on_rx(node, packet) 86 | { 87 | /* nothing */ 88 | } 89 | 90 | export function add_root(node, root_id) 91 | { 92 | /* nothing */ 93 | } 94 | 95 | export function on_node_becomes_root(node) 96 | { 97 | /* nothing */ 98 | } 99 | 100 | /*---------------------------------------------------------------------------*/ 101 | 102 | /* Initialize a specific node: function required by the scheduling module API */ 103 | export function node_init(node) 104 | { 105 | log.log(log.INFO, node, "TSCH", `*** initializing 6tisch minimal, slotframe_size=${node.config.TSCH_SCHEDULE_CONF_DEFAULT_LENGTH}`) 106 | 107 | /* Add a single slotframe */ 108 | const sf_common = node.add_slotframe(0, "default", node.config.TSCH_SCHEDULE_CONF_DEFAULT_LENGTH); 109 | /* Add a single cell shared by all traffic */ 110 | node.add_cell(sf_common, 111 | constants.CELL_OPTION_RX | constants.CELL_OPTION_TX | constants.CELL_OPTION_SHARED, 112 | constants.CELL_TYPE_ADVERTISING, 113 | constants.BROADCAST_ID, 114 | 0, 0); 115 | } 116 | 117 | /* ------------------------------------------------- */ 118 | 119 | /* Initialize the module: function required by the scheduling module API */ 120 | export function initialize() 121 | { 122 | log.log(log.INFO, null, "TSCH", `initializing 6tisch minimal infrastructure`) 123 | 124 | const default_config = { 125 | /* The length of the 6tisch minimal slotframe */ 126 | TSCH_SCHEDULE_CONF_DEFAULT_LENGTH: 7 127 | }; 128 | 129 | for (const key in default_config) { 130 | /* set the ones that have not been set from the config file */ 131 | if (!config.hasOwnProperty(key)) { 132 | config[key] = default_config[key]; 133 | } 134 | } 135 | 136 | set_timings(); 137 | } 138 | -------------------------------------------------------------------------------- /source/constants.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Common constant values. 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | const constants = { 37 | /* Contiki-NG cell opptions */ 38 | CELL_OPTION_TX: 1, 39 | CELL_OPTION_RX: 2, 40 | CELL_OPTION_SHARED: 4, 41 | CELL_OPTION_PROBING: 16, 42 | CELL_OPTION_PROBING_ACK: 32, 43 | 44 | /* 802.15.4e cell types. CELL_TYPE_ADVERTISING_ONLY is an extra one: for EB-only cells. */ 45 | CELL_TYPE_NORMAL: 0, 46 | CELL_TYPE_ADVERTISING: 1, 47 | CELL_TYPE_ADVERTISING_ONLY: 2, 48 | 49 | /* Default IEEE 802.15.4e hopping sequences, obtained from https://gist.github.com/twatteyne/2e22ee3c1a802b685695 */ 50 | /* 16 channels, sequence length 16 */ 51 | TSCH_HOPPING_SEQUENCE_16_16: [ 16, 17, 23, 18, 26, 15, 25, 22, 19, 11, 12, 13, 24, 14, 20, 21 ], 52 | /* 4 channels, sequence length 16 */ 53 | TSCH_HOPPING_SEQUENCE_4_16: [ 20, 26, 25, 26, 15, 15, 25, 20, 26, 15, 26, 25, 20, 15, 20, 25 ], 54 | /* 4 channels, sequence length 4 */ 55 | TSCH_HOPPING_SEQUENCE_4_4: [ 15, 25, 26, 20 ], 56 | /* 2 channels, sequence length 2 */ 57 | TSCH_HOPPING_SEQUENCE_2_2: [ 20, 25 ], 58 | /* 1 channel, sequence length 1 */ 59 | TSCH_HOPPING_SEQUENCE_1_1: [ 20 ], 60 | 61 | /* 64 channels, sequence length 64 */ 62 | TSCH_HOPPING_SEQUENCE_64_64: [63, 5, 37, 7, 25, 21, 62, 26, 33, 39, 42, 22, 29, 55, 14, 58, 20, 13, 40, 45, 43, 32, 61, 23, 46, 10, 52, 51, 38, 4, 19, 50, 35, 56, 1, 18, 6, 41, 48, 0, 9, 44, 30, 34, 60, 27, 15, 47, 59, 12, 54, 16, 53, 57, 3, 2, 36, 11, 31, 28, 17, 8, 49, 24], 63 | 64 | /* From TCP/IP standards */ 65 | PROTO_ICMP: 1, 66 | PROTO_TCP: 6, 67 | PROTO_UDP: 17, 68 | PROTO_ICMP6: 58, 69 | 70 | /* Extension headers */ 71 | PROTO_EXT_HBHO: 0, 72 | PROTO_EXT_DESTO: 60, 73 | PROTO_EXT_ROUTING: 43, 74 | PROTO_EXT_FRAG: 44, 75 | PROTO_EXT_NONE: 59, 76 | 77 | /* Implementation specific (must be >= 256) */ 78 | PROTO_APP: 256, 79 | PROTO_TSCH: 257, 80 | 81 | /* From 802.15.4 standard */ 82 | FRAME802154_BEACONFRAME: 0, 83 | FRAME802154_DATAFRAME: 1, 84 | FRAME802154_ACKFRAME: 2, 85 | FRAME802154_CMDFRAME: 3, 86 | 87 | IEEE_ADDR_SIZE: 8, 88 | 89 | /* Special values (ID 0 is reserved) */ 90 | ROOT_NODE_ID: 1, /* Root node ID */ 91 | BROADCAST_ID: -1, /* Broadcast "neighbor" ID */ 92 | EB_ID: -2, /* EB "neighbor" ID */ 93 | 94 | /* Constants relevant to radio propagation */ 95 | TWO_DOT_FOUR_GHZ: 2400000000, /* Hz */ 96 | SPEED_OF_LIGHT: 299792458, /* m/s */ 97 | 98 | /* Schedule cell flags for the web interface */ 99 | FLAG_RX: 1 << 0, 100 | FLAG_TX: 1 << 1, 101 | FLAG_SKIPPED_TX: 1 << 2, 102 | 103 | FLAG_PACKET_TX: 1 << 3, 104 | FLAG_PACKET_RX: 1 << 4, 105 | FLAG_PACKET_BADRX: 1 << 5, 106 | 107 | FLAG_ACK: 1 << 6, 108 | FLAG_ACK_OK: 1 << 7, 109 | 110 | /* Run speeds for interactive simulations */ 111 | RUN_UNLIMITED: 1, 112 | RUN_1000_PERCENT: 2, 113 | RUN_100_PERCENT: 3, 114 | RUN_10_PERCENT: 4, 115 | RUN_STEP_NEXT_ACTIVE: 5, 116 | RUN_STEP_SINGLE: 6, 117 | 118 | /* Query packet statuses */ 119 | PACKET_IS_DATA: 0, 120 | PACKET_IS_REQUEST: 1, 121 | PACKET_IS_RESPONSE: 2, 122 | }; 123 | 124 | export default constants; 125 | -------------------------------------------------------------------------------- /source/packet.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Network packet 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import constants from './constants.mjs'; 37 | import * as time from './time.mjs'; 38 | import { id_to_addr } from './utils.mjs'; 39 | 40 | /* ------------------------------------- */ 41 | 42 | export class RxInfo { 43 | constructor(rssi) { 44 | /* RSSI of the reception */ 45 | this.rssi = rssi; 46 | /* Has the packet been received OK, without any errors? */ 47 | this.is_success = false; 48 | } 49 | } 50 | 51 | /* ------------------------------------- */ 52 | 53 | export class FragmentInfo { 54 | constructor(tag) { 55 | this.tag = tag; 56 | this.number = -1; 57 | this.total_fragments = 0; 58 | } 59 | } 60 | 61 | /* ------------------------------------- */ 62 | 63 | /* Network packet */ 64 | export class Packet { 65 | 66 | constructor(source, destination_id, length, is_on_link=false) { 67 | this.source = source; /* end-to-end: originator */ 68 | this.destination_id = destination_id; /* end-to-end: destination */ 69 | this.length = length; 70 | this.seqnum = -1; /* end-to-end sequence number */ 71 | this.link_layer_seqnum = -1; /* link layer sequence number */ 72 | this.packetbuf = {}; /* attributes for TSCH and other protocol layers */ 73 | this.packetbuf.PACKETBUF_ATTR_FRAME_TYPE = constants.FRAME802154_DATAFRAME; 74 | this.subslot = 0; /* some TSCH slots may have multiple subslots */ 75 | if (is_on_link) { 76 | this.nexthop_id = destination_id; 77 | } else { 78 | this.nexthop_id = source.routes.get_nexthop(destination_id); /* link-layer: destination */ 79 | } 80 | if (this.nexthop_id <= 0) { 81 | /* nexthop not found, or broadcast */ 82 | this.nexthop_addr = null; 83 | } else { 84 | this.nexthop_addr = id_to_addr(this.nexthop_id); 85 | } 86 | this.lasthop_id = source.id; /* link-layer: source */ 87 | this.lasthop_addr = source.addr; /* link-layer: source */ 88 | this.num_transmissions = 0; 89 | this.is_ack_required = (this.nexthop_id > 0); 90 | this.generation_time_s = time.timeline.seconds; 91 | this.packet_protocol = -1; 92 | this.msg_type = 0; 93 | this.query_status = constants.PACKET_IS_DATA; 94 | /* 6LoWPAN fragmentation */ 95 | this.fragment_info = null; 96 | /* function called when the packet is completed sending (ACKed or dropped) */ 97 | this.sent_callback = null; 98 | /* updated on each Tx attempt */ 99 | this.reserved_bit_set = false; 100 | this.hopcount = 0; 101 | this.tx_channel = null; 102 | /* updated on each Tx attempt for each receiver */ 103 | this.rx_info = {}; 104 | } 105 | 106 | copy(other) { 107 | this.source = other.source; 108 | this.destination_id = other.destination_id; 109 | this.length = other.length; 110 | this.seqnum = other.seqnum; 111 | this.link_layer_seqnum = other.link_layer_seqnum; 112 | this.packetbuf = JSON.parse(JSON.stringify(other.packetbuf)); 113 | this.subslot = other.subslot; 114 | this.num_transmissions = 0; 115 | this.lasthop_id = other.lasthop_id; 116 | this.lasthop_addr = JSON.parse(JSON.stringify(other.lasthop_addr)); 117 | this.nexthop_id = other.nexthop_id; 118 | this.nexthop_addr = JSON.parse(JSON.stringify(other.nexthop_addr)); 119 | this.is_ack_required = other.is_ack_required; 120 | this.generation_time_s = other.generation_time_s; 121 | this.packet_protocol = other.packet_protocol; 122 | this.msg_type = other.msg_type; 123 | this.query_status = other.query_status; 124 | this.fragment_info = other.fragment_info; 125 | this.sent_callback = other.sent_callback; 126 | this.reserved_bit_set = false; 127 | this.hopcount = other.hopcount; 128 | this.rx_info = {}; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /web/style.css: -------------------------------------------------------------------------------- 1 | .smallerbtn { 2 | font-size: 0.9rem !important; 3 | } 4 | 5 | .dropdown-menu { 6 | font-size: 0.9rem !important; 7 | } 8 | 9 | .btn-secondary { 10 | margin-left: 5px; 11 | margin-right: 5px; 12 | margin-top: 2px; 13 | margin-bottom: 2px; 14 | } 15 | 16 | .fa-check { 17 | margin-right: 1rem; 18 | } 19 | 20 | /* ---------------------------------------------------- */ 21 | /* Main layout */ 22 | /* ---------------------------------------------------- */ 23 | .grid-container { 24 | display: grid; 25 | margin-top: 56px; 26 | grid-template-rows: 0.6fr 0.2fr 0.2fr; 27 | grid-template-columns: 1fr; 28 | } 29 | 30 | .workspace { 31 | grid-row-start: 1; 32 | grid-row-end: 1; 33 | 34 | resize: vertical; 35 | overflow:scroll; 36 | height:500px; 37 | /* min-height:200px; */ 38 | } 39 | 40 | #cellview { 41 | grid-row-start: 2; 42 | grid-row-end: 2; 43 | 44 | height:250px; 45 | overflow: scroll; 46 | resize: vertical; 47 | /* display:none; */ 48 | } 49 | 50 | #logview { 51 | grid-row-start: 3; 52 | grid-row-end: 3; 53 | 54 | height:250px; 55 | overflow: scroll; 56 | resize: vertical; 57 | /* display:none; */ 58 | } 59 | 60 | /* ---------------------------------------------------- */ 61 | /* Network view */ 62 | /* ---------------------------------------------------- */ 63 | .node { 64 | fill:#eee; 65 | stroke:black; 66 | stroke-width:1; 67 | } 68 | 69 | .node_selected { 70 | stroke-width:2; 71 | } 72 | 73 | .node_label { 74 | font-weight:500; 75 | } 76 | 77 | .pdr_label { 78 | font-size: 0.9rem; 79 | background-color:#fff; 80 | } 81 | 82 | 83 | /* ---------------------------------------------------- */ 84 | /* Cell table */ 85 | /* ---------------------------------------------------- */ 86 | 87 | .cell-table { 88 | table-layout: fixed; 89 | cursor: crosshair; 90 | } 91 | 92 | .cell-table td { 93 | overflow: hidden; 94 | width: 25px; 95 | user-select: none; 96 | 97 | padding: 0rem; 98 | border-top: none; 99 | border-bottom: 1px solid #dee2e6; 100 | border-right: 1px solid #dee2e6; 101 | 102 | text-align:center; 103 | vertical-align:middle; 104 | } 105 | 106 | .cell-table tr:nth-child(1) { 107 | height: 4rem; 108 | } 109 | 110 | /* first and last columns are the node IDs */ 111 | .cell-table td:nth-child(1) { 112 | width: 100px; 113 | padding-right: 6px; 114 | text-align: right; 115 | font-weight: bold; 116 | } 117 | 118 | .cell-table td:nth-last-of-type(1) { 119 | width: 100px; 120 | padding-left: 6px; 121 | text-align: left; 122 | font-weight: bold; 123 | } 124 | 125 | .sch_time { 126 | font-weight: bold; 127 | font-size: 0.8rem; 128 | overflow: visible !important; 129 | border-top: none !important; 130 | border-right: none !important; 131 | border-left: none !important; 132 | transform: translate(-1.44rem) rotate(-90deg) ; 133 | text-align: left !important; 134 | vertical-align: bottom !important; 135 | } 136 | 137 | .sch:hover { 138 | background-color: grey; 139 | } 140 | 141 | .sch_tx { 142 | background-color:#6680FF; 143 | } 144 | 145 | .sch_notx { 146 | background-color:#3133ff; 147 | } 148 | 149 | .sch_rx { 150 | background-color:#0d0; 151 | } 152 | 153 | .sch_badrx { 154 | background-color:#f00; 155 | } 156 | 157 | .sch_norx { 158 | background-color:#329262; 159 | } 160 | 161 | .sch_both { 162 | background-color:#8B0707; 163 | } 164 | 165 | .sch_ch0 { background-color:#FF9900; } 166 | .sch_ch1 { background-color:#8d8; } 167 | .sch_ch2 { background-color:#d2f91f; } 168 | .sch_ch3 { background-color:#5574A6; } 169 | .sch_ch4 { background-color:#E67300; } 170 | .sch_ch5 { background-color:#3B3EAC; } 171 | .sch_ch6 { background-color:#0099C6; } 172 | .sch_ch7 { background-color:#DD4477; } 173 | .sch_ch8 { background-color:#668800; } 174 | .sch_ch9 { background-color:#B82E2E; } 175 | .sch_ch10 { background-color:#88f; } 176 | .sch_ch11 { background-color:#997799; } 177 | .sch_ch12 { background-color:#22AA99; } 178 | .sch_ch13 { background-color:#AAAA11; } 179 | .sch_ch14 { background-color:#6633CC; } 180 | .sch_ch15 { background-color:#990099; } 181 | 182 | .sch_sf0 { background-color:#B82E2E; } 183 | .sch_sf1 { background-color:#5574A6; } 184 | .sch_sf2 { background-color:#668800; } 185 | .sch_sf3 { background-color:#E67300; } 186 | .sch_sf4 { background-color:#8d8; } 187 | .sch_sf5 { background-color:#3B3EAC; } 188 | .sch_sf6 { background-color:#d2f91f; } 189 | .sch_sf7 { background-color:#FF9900; } 190 | .sch_sf8 { background-color:#0099C6; } 191 | .sch_sf9 { background-color:#DD4477; } 192 | .sch_sf10 { background-color:#88f; } 193 | .sch_sf11 { background-color:#997799; } 194 | .sch_sf12 { background-color:#22AA99; } 195 | .sch_sf13 { background-color:#AAAA11; } 196 | .sch_sf14 { background-color:#6633CC; } 197 | .sch_sf15 { background-color:#990099; } 198 | .sch_scan { background-color:#cccccc; } 199 | 200 | /* ---------------------------------------------------- */ 201 | /* Log table */ 202 | /* ---------------------------------------------------- */ 203 | .log-table { 204 | /* border: none; */ 205 | table-layout: fixed; 206 | } 207 | 208 | .log-table td { 209 | padding: 0rem; 210 | border: none; 211 | } 212 | 213 | .log-table td:nth-child(1) { 214 | text-align: right; 215 | } 216 | .log-table td:nth-child(2) { 217 | text-align: center; 218 | } 219 | 220 | /* ---------------------------------------------------- */ 221 | /* Settings dialog */ 222 | /* ---------------------------------------------------- */ 223 | 224 | .input-autogenerated { 225 | color: "grey"; 226 | } 227 | 228 | .input-autogenerated-mesh { 229 | color: "grey"; 230 | } 231 | -------------------------------------------------------------------------------- /source/time.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Time keeping class together with a timer library 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import * as utils from './utils.mjs'; 37 | import * as log from './log.mjs'; 38 | import { heap_insert, heap_extract_min, heap_remove_at } from './heap.mjs'; 39 | 40 | const timers = []; 41 | export let timeline; 42 | 43 | /* ------------------------------------- */ 44 | 45 | export class Timer 46 | { 47 | constructor(interval, is_periodic, argument, callback) { 48 | this.fire_at = timeline.seconds + interval; 49 | this.interval = interval; 50 | this.is_periodic = is_periodic; 51 | this.argument = argument; 52 | this.callback = callback; 53 | this.heap_position = -1; 54 | } 55 | } 56 | 57 | /* ------------------------------------- */ 58 | 59 | /* Network-wide timeline */ 60 | class Timeline { 61 | constructor() { 62 | /* the time in TSCH networks is kept in both ASN and seconds */ 63 | this.asn = 0; 64 | this.seconds = 0; 65 | /* default timings: 10 ms for all slots */ 66 | this.slot_timings = [0.01]; 67 | } 68 | 69 | get_next_seconds() { 70 | /* calculate the amount of seconds in this timeslot */ 71 | const timeslot = this.asn % this.slot_timings.length; 72 | return this.seconds + this.slot_timings[timeslot]; 73 | } 74 | 75 | step() { 76 | /* advance the seconds counter by the amount of seconds in this timeslot */ 77 | this.seconds = this.get_next_seconds(); 78 | /* increment the timeslot number */ 79 | this.asn += 1; 80 | 81 | /* fire timers that need to be fired */ 82 | const periodic_timers = []; 83 | let t; 84 | while (timers.length && timers[0].fire_at <= this.seconds) { 85 | /* get first timer and remove it from the array */ 86 | t = heap_extract_min(timers, timer_less_than); 87 | /* this may add the timer back to the timers array */ 88 | t.callback(t.argument, this.seconds); 89 | /* periodic timers (assumed to not add the timer in callback) */ 90 | if (t.is_periodic) { 91 | periodic_timers.push(t); 92 | } 93 | } 94 | 95 | /* add back all periodic timers */ 96 | const seconds = this.seconds; 97 | periodic_timers.forEach(function (t) { 98 | const new_t = add_timer(t.fire_at - seconds + t.interval, true, t.argument, t.callback); 99 | new_t.interval = t.interval; 100 | }); 101 | } 102 | } 103 | 104 | /* ------------------------------------- */ 105 | 106 | export function reset_time() 107 | { 108 | timeline = new Timeline(); 109 | log.initialize(timeline); 110 | /* clear all timers */ 111 | timers.length = 0; 112 | } 113 | 114 | /* ------------------------------------- */ 115 | 116 | function timer_less_than(a, b) 117 | { 118 | return a.fire_at < b.fire_at; 119 | } 120 | 121 | /* ------------------------------------- */ 122 | 123 | export function add_timer(interval, is_periodic, argument, callback) 124 | { 125 | utils.assert(!isNaN(interval), `interval should be a number, not ${interval}`); 126 | const t = new Timer(interval, is_periodic, argument, callback); 127 | /* log.log(log.INFO, null, "Main", `add timer, interval=${interval} fire_at=${t.fire_at}`); */ 128 | heap_insert(timers, t, timer_less_than); 129 | return t; 130 | } 131 | 132 | /* ------------------------------------- */ 133 | 134 | export function remove_timer(timer) 135 | { 136 | /* linear search, in case `heap_position` is not stored: */ 137 | /* for (let i = 0; i < timers.length; ++i) { 138 | if (timers[i] === timer) { 139 | heap_remove_at(timers, i, timer_less_than); 140 | break; 141 | } 142 | } */ 143 | utils.assert(timer.heap_position !== -1, "attempting to remove timer not in the heap"); 144 | heap_remove_at(timers, timer.heap_position, timer_less_than); 145 | } 146 | 147 | /* ------------------------------------- */ 148 | 149 | /* On loading, run the reset function */ 150 | reset_time(); 151 | -------------------------------------------------------------------------------- /source/collision_analyzer.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Packet collision analyzer 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import constants from './constants.mjs'; 37 | 38 | /*---------------------------------------------------------------------------*/ 39 | 40 | export const PACKET_RX_OK = 0; 41 | export const PACKET_RX_COLLISION = 1; 42 | export const PACKET_RX_LINK_FAILED = 2; 43 | export const PACKET_RX_WRONG_ADDRESS = 3; 44 | 45 | const PACKET_TYPE_TSCH = 0; /* EB and keepalive packets */ 46 | const PACKET_TYPE_RPL = 1; /* RPL protocol packets: DIO, DIS, DAO, DAO ACK */ 47 | const PACKET_TYPE_OTHER = 2; /* incldudes application data packets */ 48 | 49 | const PACKET_DESTINATION_BC = 0; /* broadcast */ 50 | const PACKET_DESTINATION_UC = 1; /* unicast */ 51 | 52 | function get_name(packet_type, packet_dst) 53 | { 54 | const types = ["TSCH", "RPL", "Other"]; 55 | const dst = ["BC", "UC"]; 56 | return types[packet_type] + "_" + dst[packet_dst]; 57 | } 58 | 59 | export class CollisionAnalyzer { 60 | constructor(node) { 61 | this.node = node; 62 | this.clear(); 63 | } 64 | 65 | clear() { 66 | this.packets = []; 67 | for (let packet_type = 0; packet_type < 3; ++packet_type) { 68 | this.packets[packet_type] = []; 69 | for (let packet_dst = 0; packet_dst < 2; ++packet_dst) { 70 | this.packets[packet_type][packet_dst] = []; 71 | for (let packet_status = 0; packet_status < 4; ++packet_status) { 72 | this.packets[packet_type][packet_dst][packet_status] = 0; 73 | } 74 | } 75 | } 76 | } 77 | 78 | add_packet(packet, packet_status) { 79 | const packet_dst = packet.nexthop_id <= 0 ? PACKET_DESTINATION_BC : PACKET_DESTINATION_UC; 80 | const packet_type = packet.packet_protocol === constants.PROTO_TSCH ? 81 | PACKET_TYPE_TSCH : 82 | (packet.packet_protocol === constants.PROTO_ICMP6 ? 83 | PACKET_TYPE_RPL : PACKET_TYPE_OTHER); 84 | this.packets[packet_type][packet_dst][packet_status] += 1; 85 | } 86 | 87 | get() { 88 | let r = {}; 89 | for (let packet_type = 0; packet_type < 3; ++packet_type) { 90 | for (let packet_dst = 0; packet_dst < 2; ++packet_dst) { 91 | const stats = this.packets[packet_type][packet_dst]; 92 | const name = get_name(packet_type, packet_dst); 93 | r[name] = [stats[PACKET_RX_OK], 94 | stats[PACKET_RX_COLLISION], 95 | stats[PACKET_RX_LINK_FAILED], 96 | stats[PACKET_RX_WRONG_ADDRESS]]; 97 | } 98 | } 99 | return r; 100 | } 101 | 102 | aggregate(existing_stats) { 103 | if (existing_stats == null) { 104 | existing_stats = {}; 105 | for (let packet_type = 0; packet_type < 3; ++packet_type) { 106 | for (let packet_dst = 0; packet_dst < 2; ++packet_dst) { 107 | const name = get_name(packet_type, packet_dst); 108 | existing_stats[name] = [0, 0, 0, 0]; 109 | } 110 | } 111 | } 112 | let my_stats = this.get(); 113 | for (let packet_type = 0; packet_type < 3; ++packet_type) { 114 | for (let packet_dst = 0; packet_dst < 2; ++packet_dst) { 115 | for (let packet_status = 0; packet_status < 4; ++packet_status) { 116 | const name = get_name(packet_type, packet_dst); 117 | existing_stats[name][packet_status] += my_stats[name][packet_status]; 118 | const total = existing_stats[name][0] + existing_stats[name][1] 119 | + existing_stats[name][2] + existing_stats[name][3]; 120 | const rate_collided = total <= 0 ? 0 : (existing_stats[name][1] / total); 121 | existing_stats[name + " % collisions"] = (100.0 * rate_collided).toFixed(2); 122 | } 123 | } 124 | } 125 | return existing_stats; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/routing/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | all: nullrouting lf rpl-leaf rpl-dao-ack rpl-no-dao-ack rpl-probing rpl-no-probing rpl-recover-2nodes rpl-recover-3nodes rpl-loops rpl-loops-drop \ 4 | rpl-of0 rpl-recover-2nodes-of0 rpl-recover-3nodes-of0 rpl-loops-of0 rpl-loops-drop-of0 5 | 6 | nullrouting: nullrouting.json 7 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 8 | @rm -rf results 9 | ../../tsch-sim.sh $< > /dev/null 10 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 11 | 12 | lf: lf.json 13 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 14 | @rm -rf results 15 | ../../tsch-sim.sh $< > /dev/null 16 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 17 | 18 | rpl-leaf: rpl-leaf.json 19 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 20 | @rm -rf results 21 | ../../tsch-sim.sh $< > /dev/null 22 | grep "using MRHOF objective function" results/log.txt > /dev/null 23 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 24 | grep "id=2" results/log.txt | grep "skip sending EB" > /dev/null 25 | grep "id=3" results/log.txt | grep "skip sending EB" > /dev/null 26 | grep "id=4" results/log.txt | grep "skip sending EB" > /dev/null 27 | grep "id=5" results/log.txt | grep "skip sending EB" > /dev/null 28 | grep "id=6" results/log.txt | grep "skip sending EB" > /dev/null 29 | grep "id=7" results/log.txt | grep "skip sending EB" > /dev/null 30 | grep "id=8" results/log.txt | grep "skip sending EB" > /dev/null 31 | grep "id=9" results/log.txt | grep "skip sending EB" > /dev/null 32 | grep "id=10" results/log.txt | grep "skip sending EB" > /dev/null 33 | 34 | rpl-dao-ack: rpl-dao-ack.json 35 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 36 | @rm -rf results 37 | ../../tsch-sim.sh $< > /dev/null 38 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 39 | grep "id=2" results/log.txt | grep "got DAO ACK seqno" | grep "status 0" > /dev/null 40 | grep "id=3" results/log.txt | grep "got DAO ACK seqno" | grep "status 0" > /dev/null 41 | 42 | rpl-no-dao-ack: rpl-no-dao-ack.json 43 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 44 | @rm -rf results 45 | ../../tsch-sim.sh $< > /dev/null 46 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 47 | ../invert_grep.sh 'DAO ACK' results/log.txt 48 | 49 | rpl-probing: rpl-probing.json 50 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 51 | @rm -rf results 52 | ../../tsch-sim.sh $< > /dev/null 53 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 54 | grep "id=2" results/log.txt | grep "probing node 3" > /dev/null 55 | 56 | rpl-no-probing: rpl-no-probing.json 57 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 58 | @rm -rf results 59 | ../../tsch-sim.sh $< > /dev/null 60 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 61 | ../invert_grep.sh "probing node" results/log.txt 62 | 63 | rpl-recover-2nodes: rpl-recover-2nodes.json 64 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 65 | @rm -rf results 66 | ../../tsch-sim.sh $< > /dev/null 67 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 68 | 69 | rpl-recover-3nodes: rpl-recover-3nodes.json 70 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 71 | @rm -rf results 72 | ../../tsch-sim.sh $< > /dev/null 73 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 74 | 75 | rpl-loops: rpl-loops.json 76 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 77 | @rm -rf results 78 | ../../tsch-sim.sh $< > /dev/null 79 | grep "loop detected, attempting repair" results/log.txt > /dev/null 80 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 81 | 82 | rpl-loops-drop: rpl-loops-drop.json 83 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 84 | @rm -rf results 85 | ../../tsch-sim.sh $< > /dev/null 86 | grep "rank error and loop detected, dropping" results/log.txt > /dev/null 87 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 88 | 89 | rpl-of0: rpl-of0.json 90 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 91 | @rm -rf results 92 | ../../tsch-sim.sh $< > /dev/null 93 | grep "using OF0 objective function" results/log.txt > /dev/null 94 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 70 95 | 96 | rpl-recover-2nodes-of0: rpl-recover-2nodes-of0.json 97 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 98 | @rm -rf results 99 | ../../tsch-sim.sh $< > /dev/null 100 | grep "using OF0 objective function" results/log.txt > /dev/null 101 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 102 | 103 | rpl-recover-3nodes-of0: rpl-recover-3nodes-of0.json 104 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 105 | @rm -rf results 106 | ../../tsch-sim.sh $< > /dev/null 107 | grep "using OF0 objective function" results/log.txt > /dev/null 108 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 109 | 110 | rpl-loops-of0: rpl-loops-of0.json 111 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 112 | @rm -rf results 113 | ../../tsch-sim.sh $< > /dev/null 114 | grep "using OF0 objective function" results/log.txt > /dev/null 115 | grep "loop detected, attempting repair" results/log.txt > /dev/null 116 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 117 | 118 | rpl-loops-drop-of0: rpl-loops-drop-of0.json 119 | @echo "\nTest group '$(shell basename `pwd`)', test target '$@'..." 120 | @rm -rf results 121 | ../../tsch-sim.sh $< > /dev/null 122 | grep "using OF0 objective function" results/log.txt > /dev/null 123 | grep "rank error and loop detected, dropping" results/log.txt > /dev/null 124 | $(NODE) --harmony --experimental-modules ../check_pdr.mjs results/stats.json 125 | 126 | clean: 127 | rm -rf results 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TSCH-Sim — a fast TSCH simulator 2 | 3 | ![CI](https://github.com/edi-riga/tsch-sim/workflows/CI/badge.svg) 4 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/edi-riga/tsch-sim.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/edi-riga/tsch-sim/context:javascript) 5 | 6 | TSCH-Sim is a TSCH simulator written in modern, modular JavaScript. It supports cross-platform execution using Node.js. Compared with existing alternatives such the OpenWSN 6TiSCH simulator and Cooja, TSCH-Sim has a much better performance, allowing to simulate networks with many thousands of nodes in real time. The simulator allows to use multiple radio propagation models, including models that have been theoretically or experimentally validated. It also includes support for mobile nodes. 7 | 8 | ![Web interface](https://atiselsts.github.io/resources/web-annotated.png) 9 | 10 | ## Features 11 | 12 | ### Protocol features 13 | 14 | * IEEE 802.15.4 TSCH 15 | * 6TiSCH minimal scheduler 16 | * Orchestra scheduler 17 | * RPL routing protocol with MRHOF and OF0 objective functions 18 | * Leaf-and-forwarder scheduler and routing module for two-hop networks 19 | * Multiple subslots inside the IEEE 802.15.4 TSCH slots 20 | 21 | ### Simulation infrastructure features 22 | 23 | * Command line and web based interfaces: for automated execution and for interaction or demonstrations, respectively 24 | * Network autogeneration for star, mesh, line and grid topologies 25 | * Automated accounting of performance metrics: packet delivery ratio, latency, network join time, and others 26 | * Compact simulation description files in JSON 27 | * Charge consumption model, validated on Texas Instruments CC2650 hardware 28 | * Random waypoint and line mobility models 29 | * Unit Disk Graph, Logistic loss (from Cooja), Pister-hack model (from the 6TiSCH simulator), and other radio propagation models 30 | * Trace-based simulations 31 | * Simulation results exported to JSON files, format largely compatible with the 6TiSCH simulator output 32 | * User control over the simulation from JavaScript code specified in configuration 33 | * Easily configurable parallel execution of multiple simulation runs 34 | 35 | ### Examples and tests 36 | 37 | * Demo with a star network: `examples/star` 38 | * Demo with a mesh network: `examples/mesh` 39 | * Demo with a line network: `examples/line` 40 | * Demo with a grid network: `examples/grid` 41 | * Demo with a two-hop hierarchical network: `examples/hierarchical` 42 | * Demo with a large network with 1000 nodes: `examples/large-network` 43 | * Demo that demonstrates parallelization of multiple simulation runs: `examples/multirun` 44 | * Demo that demonstrates control of a simulation with a user-defined script: `examples/scripting` 45 | * Demo that launches the web interface backend: `examples/web` 46 | * Demo with comparative simulation runs and result visualization using matplotlib with Python: `examples/result-visualization` 47 | * A number of regression tests for all main simulation features under `tests` 48 | 49 | ### Planned features 50 | 51 | * Clock drift simulation 52 | * 6top protocol 53 | * 6tisch Minimal Scheduling Function (MSF) 54 | 55 | 56 | ## Documentation 57 | 58 | [GitHub wiki](https://github.com/edi-riga/tsch-sim/wiki). 59 | 60 | [Video tutorial](https://www.youtube.com/watch?v=7_mNrosDpD4). 61 | 62 | 63 | ## Installation 64 | 65 | The simulator requires that Node.js is installed in the system. The minimal version supported Node.js version is 8. 66 | 67 | The source code is self-contained, no NPM packages are required to run TSCH-Sim. 68 | 69 | The simulator backend has been tested on Ubuntu Linux 20.04, Microsoft Windows 10, and Apple macOS 10.13. 70 | 71 | To use the optional web interface, a modern web browser is required. 72 | 73 | 74 | ## Getting started 75 | 76 | To execute the simulator on Linux or macOS: 77 | 78 | $ ./tsch-sim.sh 79 | 80 | On Windows: 81 | 82 | $ tsch-sim-windows.bat 83 | 84 | Assuming Node.js has been installed and the PATH environmental variable properly set up, it is also possible to start the simulator without using the command line. Simply drag-and-drop a config file onto the `tsch-sim-windows.bat` batch file. 85 | 86 | To pass custom configuration to the simulator, put the configuration in a file, e.g. `config.json` and pass that file name as the command line argument. For example, to run the star network demo example (RPL+Orchestra), use `examples/star/config.json`. 87 | 88 | To run the web interface on Linux or macOS: 89 | 90 | $ ./tsch-sim-web-interface.sh 91 | 92 | On Windows (alternativiely, doubleclick on the batch file): 93 | 94 | $ tsch-sim-windows-web.bat 95 | 96 | Running these scripts should open http://localhost:2020 in your web browser. The web interface has been tested in Mozilla Firefox, Microsoft Edge, Google Chrome, and Apple Safari. 97 | 98 | ## Repository structure 99 | 100 | Directories: 101 | 102 | * `source` — implementation of the simulator 103 | * `web` — implementation of the web frontend 104 | * `examples` — example simulation scenarios: configuration files along with their descriptions 105 | * `tests` — regression tests 106 | * `results` — is `SAVE_TO_FILE` is enabled in the configuration, logs and statistics from simulation runs are stored here 107 | 108 | ## Code origins 109 | 110 | Some parts of the simulator are based on the TSCH and RPL implementations in the [Contiki-NG operating system](https://github.com/contiki-ng/contiki-ng), developed by Simon Duquennoy and others. The OpenWSN 6tisch simulator also has been an inspiration, particularly in terms of functionality. 111 | 112 | 113 | ## License 114 | 115 | [3-Clause BSD](LICENSE). 116 | 117 | 118 | ## Referencing 119 | 120 | A paper on the simulator is available at https://www.mdpi.com/1424-8220/20/19/5663 121 | 122 | If you use this work in your scientific papers, please cite this article! 123 | 124 | @article{elsts2020tsch, 125 | title={{TSCH-Sim: Scaling Up Simulations of TSCH and 6TiSCH Networks}}, 126 | author={Elsts, Atis}, 127 | journal={Sensors}, 128 | volume={20}, 129 | number={19}, 130 | pages={5663}, 131 | year={2020}, 132 | publisher={Multidisciplinary Digital Publishing Institute} 133 | } 134 | -------------------------------------------------------------------------------- /source/route.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Route and the routing table classes 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import constants from './constants.mjs'; 37 | import * as log from './log.mjs'; 38 | import * as simulator from './simulator.mjs'; 39 | import { assert } from './utils.mjs'; 40 | 41 | /*---------------------------------------------------------------------------*/ 42 | 43 | export const ROUTE_INFINITE_LIFETIME = 0xFFFFFFFF; 44 | 45 | /*---------------------------------------------------------------------------*/ 46 | 47 | export class Route { 48 | constructor(prefix, nexthop_id) { 49 | this.prefix = prefix; 50 | this.nexthop_id = nexthop_id; 51 | this.lifetime = ROUTE_INFINITE_LIFETIME; 52 | } 53 | 54 | is_direct() { 55 | return this.nexthop_id === this.prefix; 56 | } 57 | } 58 | 59 | /*---------------------------------------------------------------------------*/ 60 | 61 | export class RoutingTable { 62 | constructor(node) { 63 | this.node = node; 64 | this.clear(); 65 | } 66 | 67 | clear() { 68 | this.routes = new Map(); 69 | this.default_route = null; 70 | } 71 | 72 | size() { 73 | return this.routes.size; 74 | } 75 | 76 | get_route(destination_id) { 77 | return this.routes.get(destination_id); 78 | } 79 | 80 | add_route(destination_id, nexthop_id) { 81 | assert(!this.routes.get(destination_id)); 82 | log.log(log.INFO, this.node, "Node", `add route to ${destination_id} via ${nexthop_id}`); 83 | let route = new Route(destination_id, nexthop_id); 84 | this.routes.set(destination_id, route); 85 | return route; 86 | } 87 | 88 | remove_route(destination_id) { 89 | log.log(log.INFO, this.node, "Node", `remove route to ${destination_id}`); 90 | this.routes.delete(destination_id); 91 | } 92 | 93 | add_default_route(nexthop_id) { 94 | if (this.default_route) { 95 | this.default_route.nexthop_id = nexthop_id; 96 | } else { 97 | log.log(log.INFO, this.node, "Node", `add the default route via ${nexthop_id}`); 98 | this.default_route = new Route(0, nexthop_id); 99 | } 100 | return this.default_route; 101 | } 102 | 103 | remove_default_route() { 104 | if (this.default_route) { 105 | log.log(log.INFO, this.node, "Node", `remove the default route`); 106 | this.default_route = null; 107 | } 108 | } 109 | 110 | lookup_route(destination_id) { 111 | /* if there is a specific route with /128 bit match, return it */ 112 | if (this.routes.has(destination_id)) { 113 | return this.routes.get(destination_id); 114 | } 115 | /* returns null in case the default route is not present */ 116 | return this.default_route; 117 | } 118 | 119 | get_nexthop(destination_id) { 120 | if (destination_id === this.node.id) { 121 | return destination_id; 122 | } 123 | if (destination_id === constants.BROADCAST_ID 124 | || destination_id === constants.EB_ID) { 125 | return constants.BROADCAST_ID; 126 | } 127 | const route = this.lookup_route(destination_id); 128 | if (!route) { 129 | log.log(log.WARNING, this.node, "Node", `failed to find a nexthop for=${destination_id}`); 130 | } 131 | return route ? route.nexthop_id : null; 132 | } 133 | } 134 | 135 | /*---------------------------------------------------------------------------*/ 136 | 137 | export function periodic_process(period_seconds) 138 | { 139 | log.log(log.INFO, null, "Main", `periodic route processing for all nodes`); 140 | 141 | const total_nodes = simulator.get_nodes().size; 142 | let num_joined_tsch = 0; 143 | let num_joined_routing = 0; 144 | 145 | for (const [_, node] of simulator.get_nodes()) { 146 | const to_remove = []; 147 | for (const [_, route] of node.routes.routes) { 148 | if (route.lifetime !== ROUTE_INFINITE_LIFETIME) { 149 | route.lifetime -= period_seconds; 150 | if (route.lifetime <= 0) { 151 | to_remove.push(route); 152 | } 153 | } 154 | } 155 | if (node.has_joined) { 156 | num_joined_tsch += 1; 157 | if (node.routing.is_joined()) { 158 | num_joined_routing += 1; 159 | } 160 | } 161 | 162 | for (const rr of to_remove) { 163 | node.routes.remove_route(rr.prefix); 164 | } 165 | } 166 | 167 | log.log(log.INFO, null, "Main", `joined_routing/joined_tsch/total=${num_joined_routing}/${num_joined_tsch}/${total_nodes}`); 168 | 169 | } 170 | -------------------------------------------------------------------------------- /source/mobility.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * Mobility model in the network simulations 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import config from "./config.mjs"; 37 | import { get_distance } from './utils.mjs'; 38 | import * as log from './log.mjs'; 39 | import * as time from './time.mjs'; 40 | import { rng } from './random.mjs'; 41 | 42 | /* A mobility model */ 43 | export class MobilityModel { 44 | constructor(network) { 45 | this.network = network; 46 | this.current_period_num = null; 47 | } 48 | 49 | update_positions() { 50 | const period_num = Math.trunc(time.timeline.seconds / config.MOBILITY_UPDATE_PERIOD_SEC); 51 | if (this.current_period_num !== period_num) { 52 | log.log(log.DEBUG, null, "Mobility", "update positions"); 53 | this.current_period_num = period_num; 54 | for (const [, node] of this.network.nodes) { 55 | if (node.config.MOBILITY_MODEL === "Line") { 56 | this.update_position_line(node, time.timeline.seconds); 57 | } else if (node.config.MOBILITY_MODEL === "RandomWaypoint") { 58 | this.update_position_rw(node, time.timeline.seconds); 59 | } else if (node.config.MOBILITY_MODEL !== "Static") { 60 | log.log(log.WARNING, node, "Mobility", `unknown mobility model ${node.config.MOBILITY_MODEL}`); 61 | } 62 | } 63 | } 64 | } 65 | 66 | /* Line mobility model */ 67 | update_position_line(node, seconds) { 68 | if (!node.hasOwnProperty("start_pos_x")) { 69 | node.start_pos_x = node.pos_x; 70 | } 71 | 72 | /* calculate current position */ 73 | let pos_x = node.start_pos_x + node.config.MOBILITY_SPEED * seconds; 74 | /* bound it relative to the range */ 75 | pos_x %= 2 * node.config.MOBILITY_RANGE_X; 76 | if (pos_x >= node.config.MOBILITY_RANGE_X) { 77 | /* go backwards */ 78 | pos_x = 2 * node.config.MOBILITY_RANGE_X - pos_x; 79 | } 80 | /* update the node's position */ 81 | node.pos_x = pos_x; 82 | log.log(log.DEBUG, node, "Mobility", `at ${seconds.toFixed(3)} pos is ${pos_x.toFixed(3)}`); 83 | this.network.update_node_links(node); 84 | } 85 | 86 | generate_next_waypoint(node) { 87 | /* update the last waypoint */ 88 | node.wp_last_x = node.wp_next_x; 89 | node.wp_last_y = node.wp_next_y; 90 | node.wp_last_time = node.wp_next_time; 91 | /* update the next waypoint */ 92 | node.wp_next_x = rng.random() * node.config.MOBILITY_RANGE_X - node.config.MOBILITY_RANGE_X / 2; 93 | node.wp_next_y = rng.random() * node.config.MOBILITY_RANGE_Y - node.config.MOBILITY_RANGE_Y / 2; 94 | let d = get_distance(node.wp_last_x, node.wp_last_y, node.wp_next_x, node.wp_next_y); 95 | if (d <= 0) { 96 | /* Do not allow the distance to be zero */ 97 | d = 0.01; 98 | } 99 | node.wp_next_time = d / node.config.MOBILITY_SPEED; 100 | node.wp_next_time += node.wp_last_time; 101 | } 102 | 103 | /* Random waypoint mobility model */ 104 | update_position_rw(node, seconds) { 105 | if (!node.hasOwnProperty("wp_last_x")) { 106 | /* initialize the node's state - it will contain info about the waypoints and progress */ 107 | node.start_pos_x = node.pos_x; 108 | node.start_pos_y = node.pos_y; 109 | node.wp_next_x = 0; 110 | node.wp_next_y = 0; 111 | node.wp_next_time = seconds; 112 | this.generate_next_waypoint(node); 113 | } 114 | let progress = (seconds - node.wp_last_time) / (node.wp_next_time - node.wp_last_time); 115 | if (progress > 1.0) { 116 | /* Avoid jerky motion: 117 | * if the node had reached the waypoint before this function was called, 118 | * we assume it sat there still for the time until the call. 119 | */ 120 | progress = 1.0; 121 | node.wp_next_time = seconds; 122 | } 123 | /* update the node's position */ 124 | node.pos_x = node.start_pos_x + node.wp_last_x + (node.wp_next_x - node.wp_last_x) * progress; 125 | node.pos_y = node.start_pos_y + node.wp_last_y + (node.wp_next_y - node.wp_last_y) * progress; 126 | 127 | log.log(log.DEBUG, node, "Mobility", `at ${seconds.toFixed(3)} pos is (${node.pos_x.toFixed(3)}, ${node.pos_y.toFixed(3)}) wp=(${node.wp_next_x.toFixed(3)}, ${node.wp_next_y.toFixed(3)})`); 128 | 129 | this.network.update_node_links(node); 130 | 131 | /* time for a new direction? */ 132 | if (progress >= 1.0) { 133 | this.generate_next_waypoint(node); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /source/slotframe.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Institute of Electronics and Computer Science (EDI) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 3. Neither the name of the Institute nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software 14 | * without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /** 30 | * \file 31 | * TSCH cell and slotframe classes 32 | * \author 33 | * Atis Elsts 34 | */ 35 | 36 | import constants from './constants.mjs'; 37 | import * as log from './log.mjs'; 38 | 39 | /* ------------------------------------- */ 40 | 41 | /* An IEEE 802.15.4-2015 TSCH cell */ 42 | export class Cell { 43 | constructor(timeslot, channel_offset, slotframe, options) { 44 | this.timeslot = timeslot; 45 | this.channel_offset = channel_offset; 46 | this.slotframe = slotframe; 47 | this.options = options; 48 | this.type = constants.CELL_TYPE_NORMAL; 49 | this.neighbor_id = constants.BROADCAST_ID; 50 | this.action = null; 51 | } 52 | 53 | is_tx() { 54 | return this.options & constants.CELL_OPTION_TX; 55 | } 56 | 57 | is_rx() { 58 | return this.options & constants.CELL_OPTION_RX; 59 | } 60 | 61 | is_shared() { 62 | return this.options & constants.CELL_OPTION_SHARED; 63 | } 64 | 65 | is_dedicated() { 66 | return this.neighbor_id !== constants.BROADCAST_ID; 67 | } 68 | 69 | optionstring() { 70 | let result = ""; 71 | if (this.is_tx()) result += "Tx"; 72 | if (this.is_rx()) result += "Rx"; 73 | return result; 74 | } 75 | 76 | str() { 77 | return `timeslot=${this.timeslot} channel_offset=${this.channel_offset} slotframe=${this.slotframe.handle} options=${this.optionstring()} neighbor=${this.neighbor_id}` 78 | } 79 | } 80 | 81 | /* ------------------------------------- */ 82 | 83 | /** 802.15.4e slotframe (contains cells) */ 84 | export class Slotframe { 85 | constructor(node, handle, rule_name, size) { 86 | this.node = node; 87 | this.handle = handle; 88 | this.rule_name = rule_name; 89 | this.size = size; 90 | this.cells = []; 91 | } 92 | 93 | /* This function works similarly as `tsch_schedule_add_link` in the Contiki-NG code */ 94 | add_cell(options, type, neighbor_id, timeslot, channel_offset, keep_old) { 95 | 96 | /* validate arguments */ 97 | if (timeslot >= this.size) { 98 | log.log(log.ERROR, this.node, "Node", `add cell: too large timeslot=${timeslot}`); 99 | return null; 100 | } 101 | 102 | if (!keep_old) { 103 | /* remove old cells at this timeslot and offset */ 104 | this.remove_cell_by_timeslot_and_co(timeslot, channel_offset); 105 | } 106 | 107 | var cell = new Cell(timeslot, channel_offset, this, options); 108 | this.cells.push(cell); 109 | cell.type = type; 110 | cell.neighbor_id = neighbor_id; 111 | 112 | log.log(log.DEBUG, this.node, "TSCH", `added cell timeslot=${timeslot} channel_offset=${channel_offset} options=${cell.optionstring()} slotframe=${this.handle}`); 113 | return cell; 114 | } 115 | 116 | str() { 117 | let s = `size=${this.size}\n`; 118 | for (let c of this.cells) { 119 | s += " cell " + c.str() + "\n"; 120 | } 121 | return s; 122 | } 123 | 124 | get_cell(timeslot, channel_offset) { 125 | for (let c of this.cells) { 126 | if (c.timeslot === timeslot && c.channel_offset === channel_offset) { 127 | return c; 128 | } 129 | } 130 | return null; 131 | } 132 | 133 | remove_cell_by_timeslot(timeslot) { 134 | const old_num_cells = this.cells.length; 135 | this.cells = this.cells.filter(function (cell) { return cell.timeslot !== timeslot; }); 136 | return old_num_cells !== this.cells.length; 137 | } 138 | 139 | remove_cell_by_timeslot_and_co(timeslot, channel_offset) { 140 | const old_num_cells = this.cells.length; 141 | this.cells = this.cells.filter(function (cell) { 142 | return cell.timeslot !== timeslot || cell.channel_offset !== channel_offset; 143 | }); 144 | return old_num_cells !== this.cells.length; 145 | } 146 | 147 | remove_cell_by_timeslot_co_and_options(timeslot, channel_offset, options) { 148 | const old_num_cells = this.cells.length; 149 | this.cells = this.cells.filter(function (cell) { 150 | return cell.timeslot !== timeslot || cell.channel_offset !== channel_offset || cell.options !== options; 151 | }); 152 | return old_num_cells !== this.cells.length; 153 | } 154 | } 155 | 156 | /* ------------------------------------- */ 157 | 158 | /* Returns the cell that should be preferred from the two given cells `a` and `b`. 159 | * Used in case there are multiple cells at the same slotframe scheduled at the same timeslot. */ 160 | export function select_best_tsch_cell(node, a, b, cell_options) 161 | { 162 | if (a.slotframe.handle !== b.slotframe.handle) { 163 | /* prioritize by lower slotframe */ 164 | return a.slotframe.handle < b.slotframe.handle ? a : b; 165 | } 166 | 167 | if (!(cell_options & constants.CELL_OPTION_TX)) { 168 | /* none of the cells are Tx: simply return the first cell */ 169 | return a; 170 | } 171 | 172 | /* prioritize by number of packets, keep `a` in case equal */ 173 | if (a.neighbor_id === b.neighbor_id) { 174 | /* fast path */ 175 | return a; 176 | } 177 | const num_packets_a = node.neighbors.get(a.neighbor_id).get_queue_size(); 178 | const num_packets_b = node.neighbors.get(b.neighbor_id).get_queue_size(); 179 | return num_packets_a >= num_packets_b ? a : b; 180 | } 181 | --------------------------------------------------------------------------------