summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoic Guegan <manzerbredes@mailbox.org>2022-06-09 21:48:32 +0200
committerLoic Guegan <manzerbredes@mailbox.org>2022-06-09 21:48:32 +0200
commitc2e6aad09f893e4c8cb5cb9243c32a0d6d0d1e12 (patch)
treebebdb575f200c7ea75f3115a81deecd5b797c4ff
Init ESDS repository
-rw-r--r--.gitignore2
-rw-r--r--README.md30
-rw-r--r--esds.py581
-rw-r--r--example/receiver.py28
-rw-r--r--example/sender.py47
-rwxr-xr-xexample/simulator.py41
-rw-r--r--icon.svg251
-rw-r--r--plugins/__init__.py1
-rw-r--r--plugins/node_plugin.py29
-rw-r--r--plugins/operating_states.py66
-rw-r--r--plugins/power_states.py164
-rw-r--r--plugins/wireless_area.py71
-rw-r--r--tests/README.md6
-rw-r--r--tests/hidden_node_2s1r/out4
-rw-r--r--tests/hidden_node_2s1r/receiver.py5
-rw-r--r--tests/hidden_node_2s1r/sender.py4
-rwxr-xr-xtests/hidden_node_2s1r/simulator.py22
-rw-r--r--tests/mobility_eth0_bandwidth_1s1r/out4
-rw-r--r--tests/mobility_eth0_bandwidth_1s1r/receiver.py5
-rw-r--r--tests/mobility_eth0_bandwidth_1s1r/sender.py4
-rwxr-xr-xtests/mobility_eth0_bandwidth_1s1r/simulator.py38
-rw-r--r--tests/mobility_eth0_bandwidth_2s1r/out6
-rw-r--r--tests/mobility_eth0_bandwidth_2s1r/receiver.py5
-rw-r--r--tests/mobility_eth0_bandwidth_2s1r/sender.py4
-rwxr-xr-xtests/mobility_eth0_bandwidth_2s1r/simulator.py41
-rw-r--r--tests/mobility_eth0_bandwidth_2s1r/yoctosim.debug37
-rw-r--r--tests/mobility_eth0_latency_1s1r/out5
-rw-r--r--tests/mobility_eth0_latency_1s1r/receiver.py5
-rw-r--r--tests/mobility_eth0_latency_1s1r/sender.py4
-rwxr-xr-xtests/mobility_eth0_latency_1s1r/simulator.py39
-rw-r--r--tests/mobility_eth0_latency_2s1r/out7
-rw-r--r--tests/mobility_eth0_latency_2s1r/receiver.py5
-rw-r--r--tests/mobility_eth0_latency_2s1r/sender.py4
-rwxr-xr-xtests/mobility_eth0_latency_2s1r/simulator.py42
-rw-r--r--tests/mobility_eth0_latency_2s1r/yoctosim.debug51
-rw-r--r--tests/mobility_wlan0_bandwidth_1s1r/out4
-rw-r--r--tests/mobility_wlan0_bandwidth_1s1r/receiver.py5
-rw-r--r--tests/mobility_wlan0_bandwidth_1s1r/sender.py4
-rwxr-xr-xtests/mobility_wlan0_bandwidth_1s1r/simulator.py38
-rw-r--r--tests/mobility_wlan0_latency_1s1r/out5
-rw-r--r--tests/mobility_wlan0_latency_1s1r/receiver.py5
-rw-r--r--tests/mobility_wlan0_latency_1s1r/sender.py4
-rwxr-xr-xtests/mobility_wlan0_latency_1s1r/simulator.py39
-rwxr-xr-xtests/run.sh48
-rw-r--r--tests/simple_breakpoints_auto_1n/node.py8
-rw-r--r--tests/simple_breakpoints_auto_1n/out9
-rwxr-xr-xtests/simple_breakpoints_auto_1n/simulator.py20
-rw-r--r--tests/simple_breakpoints_manual_1n/node.py4
-rw-r--r--tests/simple_breakpoints_manual_1n/out7
-rwxr-xr-xtests/simple_breakpoints_manual_1n/simulator.py20
-rw-r--r--tests/simple_breakpoints_manual_no_callback_1n/node.py4
-rw-r--r--tests/simple_breakpoints_manual_no_callback_1n/out3
-rwxr-xr-xtests/simple_breakpoints_manual_no_callback_1n/simulator.py17
-rw-r--r--tests/simple_log_5n/node.py7
-rw-r--r--tests/simple_log_5n/out21
-rwxr-xr-xtests/simple_log_5n/simulator.py19
-rw-r--r--tests/simple_read_clock_2n/node.py7
-rw-r--r--tests/simple_read_clock_2n/out7
-rwxr-xr-xtests/simple_read_clock_2n/simulator.py16
-rw-r--r--tests/simple_read_eth0_ncom_2s1r/out9
-rw-r--r--tests/simple_read_eth0_ncom_2s1r/receiver.py12
-rw-r--r--tests/simple_read_eth0_ncom_2s1r/sender.py9
-rwxr-xr-xtests/simple_read_eth0_ncom_2s1r/simulator.py17
-rw-r--r--tests/simple_read_wlan0_ncom_2s1r/out11
-rw-r--r--tests/simple_read_wlan0_ncom_2s1r/receiver.py12
-rw-r--r--tests/simple_read_wlan0_ncom_2s1r/sender.py9
-rwxr-xr-xtests/simple_read_wlan0_ncom_2s1r/simulator.py17
-rw-r--r--tests/simple_receivet_eth0_1s1r/out8
-rw-r--r--tests/simple_receivet_eth0_1s1r/receiver.py15
-rw-r--r--tests/simple_receivet_eth0_1s1r/sender.py6
-rwxr-xr-xtests/simple_receivet_eth0_1s1r/simulator.py16
-rw-r--r--tests/simple_receivet_eth0_1s1r/yoctosim.debug43
-rw-r--r--tests/simple_send_eth0_1s1r/out11
-rw-r--r--tests/simple_send_eth0_1s1r/receiver.py20
-rw-r--r--tests/simple_send_eth0_1s1r/sender.py7
-rwxr-xr-xtests/simple_send_eth0_1s1r/simulator.py16
-rw-r--r--tests/simple_send_eth0_2s1r/out7
-rw-r--r--tests/simple_send_eth0_2s1r/receiver.py13
-rw-r--r--tests/simple_send_eth0_2s1r/sender.py5
-rwxr-xr-xtests/simple_send_eth0_2s1r/simulator.py17
-rw-r--r--tests/simple_send_eth0_3s1r/out40
-rw-r--r--tests/simple_send_eth0_3s1r/receiver.py19
-rw-r--r--tests/simple_send_eth0_3s1r/sender.py37
-rwxr-xr-xtests/simple_send_eth0_3s1r/simulator.py18
-rw-r--r--tests/simple_send_wlan0_1s2r/out16
-rw-r--r--tests/simple_send_wlan0_1s2r/receiver.py29
-rw-r--r--tests/simple_send_wlan0_1s2r/sender.py6
-rwxr-xr-xtests/simple_send_wlan0_1s2r/simulator.py17
-rw-r--r--tests/simple_send_wlan0_2s1r/out6
-rw-r--r--tests/simple_send_wlan0_2s1r/receiver.py5
-rw-r--r--tests/simple_send_wlan0_2s1r/sender.py4
-rwxr-xr-xtests/simple_send_wlan0_2s1r/simulator.py17
-rw-r--r--tests/simple_sendt_eth0_1s1r/out11
-rw-r--r--tests/simple_sendt_eth0_1s1r/receiver.py11
-rw-r--r--tests/simple_sendt_eth0_1s1r/sender.py14
-rwxr-xr-xtests/simple_sendt_eth0_1s1r/simulator.py16
-rw-r--r--tests/simple_sendt_wlan0_1s2r/out7
-rw-r--r--tests/simple_sendt_wlan0_1s2r/receiver.py13
-rw-r--r--tests/simple_sendt_wlan0_1s2r/sender.py8
-rwxr-xr-xtests/simple_sendt_wlan0_1s2r/simulator.py17
-rw-r--r--tests/simple_wait_2n/node.py9
-rw-r--r--tests/simple_wait_2n/out9
-rwxr-xr-xtests/simple_wait_2n/simulator.py16
-rw-r--r--tests/simple_wait_end_3n/node.py10
-rw-r--r--tests/simple_wait_end_3n/out7
-rwxr-xr-xtests/simple_wait_end_3n/simulator.py17
106 files changed, 2638 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aabeefd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+esds.debug
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5ad4a8e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+
+# ESDS: An Extensible Simulator For Distributed Systems and Cyber-Physical Systems
+
+**What is ESDS ?**
+It is a short learning curve and coarse-grain network simulator. It contains the essential building blocks for the simulation of *Cyber-Physical Systems* (CPS) and *Wireless Sensors Networks* (WSN) scenarios. It was originally designed to improve node implementation flexibility/faithfulness and mitigate the learning curve compare to existing network simulators.
+
+**What ESDS does not implements?**
+- Network protocols (such as IP/TCP/UDP)
+- Wireless physical layer models
+- Routing algorithms
+- And much more!
+
+**Features:**
+- The implementation of node behavior can be any python file that you have wrote
+- Wireless interferences detection (not modelization)
+- Simulation can be interrupted at constant intervals to perform custom actions (such as implement nodes mobility)
+
+**Current API:**
+- `api.args`
+- `api.send(interface, data,size, dst)`
+- `api.sendt(interface, data,size, dst,timeout)`
+- `api.receive(interface)`
+- `api.receivet(interface,timeout)`
+- `api.clock()`
+- `api.log(msg)`
+- `api.wait(duration)`
+- `api.turn_off(duration)`
+- *More details in [example/sender.py](example/sender.py)*
+
+**Documentation:** cf. `example/` and `tests/`
diff --git a/esds.py b/esds.py
new file mode 100644
index 0000000..cb81fc1
--- /dev/null
+++ b/esds.py
@@ -0,0 +1,581 @@
+#!/usr/bin/env python
+
+import numpy as np
+import threading,importlib,queue,sys,time
+
+class Node:
+ available_node_id=0
+ def __init__(self,src):
+ """
+
+ """
+ self.node_id=Node.available_node_id
+ Node.available_node_id+=1 # Refresh node id
+ self.src=src # Store the node source code
+ self.args=None # Store the node arguments (passed through Simulator.create_node()
+ self.rargs=None # Store the requests arguments
+ self.plugins=list() # Contains all registered node plugins
+ self.rqueue=queue.Queue() # Receive simulator acknowledgments
+ self.chest={"state":"running", "turned_on":True, "request": None, "interfaces":{"wlan0":queue.Queue(), "eth0":queue.Queue()}, "interfaces_queue_size":{"wlan0":0,"eth0":0}}
+ self.chest_lock=threading.Lock() # To access/modify self.chest
+
+ def plugin_register(self,plugin):
+ self.plugins.append(plugin)
+
+ def plugin_notify(self,reason,args):
+ """
+ This function strives to avoid using Python specific features
+ """
+ for p in self.plugins:
+ if reason == "receive_return" or reason == "receivet_return":
+ p.on_receive_return(args[0],args[1],args[2],args[3])
+ if reason == "send_call":
+ p.on_send_call(args[0],args[1],args[2],args[3])
+ if reason == "send_return":
+ p.on_send_return(args[0],args[1],args[2],args[3],args[4])
+ if reason == "terminated":
+ p.on_terminated()
+
+ def __getitem__(self,key):
+ self.chest_lock.acquire()
+ value=self.chest[key]
+ self.chest_lock.release()
+ return value
+
+ def __setitem__(self,key,value):
+ self.chest_lock.acquire()
+ value=self.chest[key]=value
+ self.chest_lock.release()
+
+ def log(self,msg):
+ self.rargs=msg
+ self["request"]="log"
+ self["state"]="call"
+ self.wait_ack(["log"])
+
+ def read(self, register):
+ self["request"]="read"
+ self.rargs=register
+ self["state"]="call"
+ ack=self.wait_ack(["read"])
+ return ack[1]
+
+ def wait(self,duration):
+ self.rargs=duration
+ self["request"]="timeout_add"
+ self["state"]="call"
+ self.wait_ack(["timeout_add"])
+ self["state"]="pending"
+ self.wait_ack(["timeout"])
+
+ def wait_end(self):
+ self["request"]="wait_end"
+ self["state"]="request"
+ self.wait_ack(["wait_end"])
+ self.wait_ack(["sim_end"])
+
+ def turn_off(self):
+ self["turned_on"]=False
+ self["request"]="turn_off"
+ self["state"]="call"
+ self.wait_ack(["turn_off"])
+
+ def turn_on(self):
+ self["turned_on"]=True
+ self["request"]="turn_on"
+ self["state"]="call"
+ self.wait_ack(["turn_on"])
+
+ def send(self, interface, data, datasize, dst):
+ self.plugin_notify("send_call",(interface,data,datasize,dst))
+ self.rargs=(interface, data, datasize, dst)
+ self["request"]="send"
+ self["state"]="request"
+ ack=self.wait_ack(["send","send_cancel"])
+ self.plugin_notify("send_return",(interface,data,datasize,dst,ack[1]))
+ return ack[1]
+
+ def receive(self,interface):
+ self["request"]="receive"
+ self.rargs=interface
+ self["state"]="request"
+ self.wait_ack(["receive"])
+ data,start_at,end_at=self["interfaces"][interface].get()
+ self.plugin_notify("receive_return",(interface,data,start_at,end_at))
+ return (0,data)
+
+ def sendt(self, interface, data, datasize, dst, timeout):
+ self.rargs=timeout
+ self["request"]="timeout_add"
+ self["state"]="call"
+ self.wait_ack(["timeout_add"])
+ self.rargs=(interface, data, datasize, dst)
+ self["request"]="send"
+ self["state"]="request"
+ ack=self.wait_ack(["send","timeout","send_cancel"])
+ if ack[0] == "timeout":
+ self["request"]="send_cancel"
+ self["state"]="call"
+ self.wait_ack(["send_cancel"])
+ return -1
+ self["request"]="timeout_remove"
+ self["state"]="call"
+ self.wait_ack(["timeout_remove"])
+ return ack[1]
+
+ def receivet(self,interface, timeout):
+ self.rargs=timeout
+ self["request"]="timeout_add"
+ self["state"]="call"
+ self.wait_ack(["timeout_add"])
+ self["request"]="receive"
+ self.rargs=interface
+ self["state"]="request"
+ ack=self.wait_ack(["receive","timeout"])
+ if ack[0] == "timeout":
+ return (-1,None)
+ self["request"]="timeout_remove"
+ self["state"]="call"
+ self.wait_ack(["timeout_remove"])
+ data,start_at,end_at=self["interfaces"][interface].get()
+ self.plugin_notify("receivet_return",(interface,data,start_at,end_at))
+ return (0,data)
+
+ def wait_ack(self, ack_types):
+ """
+ Wait for specific acks from the request queue (rqueue)
+ """
+ ack_buffer=list() # To filter ack
+ ack=None
+ while True:
+ ack=self.rqueue.get() # Wait for simulator acknowledgments
+ if ack[0] not in ack_types:
+ ack_buffer.append(ack)
+ else:
+ break
+ # Push back the filtered ack
+ for cur_ack in ack_buffer:
+ self.rqueue.put(cur_ack)
+ return(ack)
+
+ def sync(self):
+ """
+ Wait until node stop running
+ """
+ while self["state"] == "running":
+ pass
+
+ def run(self,args):
+ """
+ Load and run the user program
+ """
+ self.node=importlib.import_module(self.src)
+ self.args=args # Allow access to arguments
+ self.node.execute(self)
+ self["state"]="terminated"
+
+class Simulator:
+ """
+ Flow-Level Discrete Event Simulator for Cyber-Physical Systems
+ The general format for an event is (type,timestamp,event,priority)
+ Event types:
+ - 0 send (0,timestamp,(src,dst,interface,data,datasize,duration,datasize_remaining), 1)
+ - 1 timeout (1,timestamp,node_id,4)
+ - 2 breakpoint_manual (3,timestamp,0,0)
+ - 3 breakpoint_auto (4,timestamp,0,0)
+
+ Very important: when the simulator wakes up a node (changing is state to running)
+ data that should be received by that node on the current simulated time SHOULD be in the queue!
+ Thus, the send event must be handle before the other event (priority equals to 1). Otherwise plugings such as the power states
+ one may not gives accurate results because of missing entries in the nodes received queues.
+ """
+
+ def __init__(self,B,L):
+ self.B=B
+ self.L=L
+ self.nodes=list()
+ self.sharing=np.zeros(len(B))
+ self.events=np.empty((0,4),dtype=object)
+ self.events_dirty=True # For optimization reasons
+ self.startat=-1
+ self.time=0
+ self.debug_file_path="./esds.debug"
+ self.precision=".3f"
+ self.interferences=True
+ self.wait_end_nodes=list() # Keep track of nodes that wait for the end of the simulation
+ self.time_truncated=format(self.time,self.precision) # Truncated version is used in log print
+
+ def update_network(self,B,L):
+ for event in self.events:
+ if int(event[0]) == 0:
+ cur_event=event[2]
+ ts=float(event[1])
+ src_id,dst_id,interface, data, datasize,duration, datasize_remaining,start_at=cur_event
+ new_bw=B[int(src_id),int(dst_id)]
+ old_bw=self.B[int(src_id),int(dst_id)]
+ new_lat=L[int(src_id),int(dst_id)]
+ old_lat=self.L[int(src_id),int(dst_id)]
+ if new_bw != old_bw or new_lat != old_lat:
+ new_datasize_remaining=float(datasize_remaining)*((ts-self.time)/float(duration))
+ if new_datasize_remaining > 0:
+ latency_factor=new_datasize_remaining/float(datasize)
+ if interface == "wlan0":
+ new_duration=new_datasize_remaining*8/new_bw+new_lat*latency_factor
+ else:
+ new_duration=new_datasize_remaining*8/(new_bw/self.sharing[int(dst_id)])+new_lat*latency_factor
+
+# print("DataSize {}B | DataSize Remaining {}B | Old duration {}s | New duration {}s | Latency {}s".format(datasize,new_datasize_remaining,duration,new_duration,new_lat))
+ event[1]=self.time+new_duration
+ event[2][6]=new_datasize_remaining
+ event[2][5]=new_duration
+ self.B=B
+ self.L=L
+
+ def debug(self):
+ """
+ Log all the informations for debugging
+ """
+ stdout_save = sys.stdout
+ with open(self.debug_file_path, "a") as debug_file:
+ sys.stdout = debug_file
+ print("-----------------------------------------------")
+ print("Started since {}s".format(round(time.time()-self.startat,2)))
+ print("Simulated time {}s (or more precisely {}s)".format(self.time_truncated,self.time))
+ states=dict()
+ timeout_mode=list()
+ sharing=dict()
+ for node in self.nodes:
+ s=node["state"]
+ states[s]=states[s]+1 if s in states else 1
+ if self.sharing[node.node_id] > 0:
+ sharing["n"+str(node.node_id)]=str(int(self.sharing[node.node_id]))
+ print("Node number per state: ",end="")
+ for key in states:
+ print(key+"="+str(states[key]), end=" ")
+ print("\nNode sharing: ",end="")
+ for node_id in sharing:
+ print(node_id+"="+sharing[node_id], end=" ")
+ print("\nIds of node in timeout mode: ", end="")
+ for n in timeout_mode:
+ print(n,end=" ")
+ print("\nSorted events list:")
+ print(self.events)
+ sys.stdout = stdout_save
+
+ def create_node(self, src, args=None):
+ """
+ Create a node thread and run it
+ """
+ node=Node(src)
+ self.nodes.append(node)
+ thread=threading.Thread(target=node.run, daemon=False,args=[args])
+ thread.start()
+
+ def log(self,msg,node=None):
+ src = "esds" if node is None else "n"+str(node)
+ print("[t="+str(self.time_truncated)+",src="+src+"] "+msg)
+
+ def sort_events(self):
+ """
+ Sort the events by timestamp and priorities
+ """
+ sorted_indexes=np.lexsort((self.events[:,3],self.events[:,1]))
+ self.events=self.events[sorted_indexes]
+
+ def sync_node(self,node):
+ """
+ Process all call request and wait for Node.sync() to return
+ """
+ node.sync()
+ while node["state"] == "call":
+ if node["request"] == "log":
+ self.log(node.rargs,node=node.node_id)
+ node["state"]="running"
+ node.rqueue.put(("log",0))
+ elif node["request"] == "timeout_add":
+ self.add_event(1,self.time+node.rargs,node.node_id,priority=3)
+ node["state"]="running"
+ node.rqueue.put(("timeout_add",0))
+ elif node["request"] == "timeout_remove":
+ selector=list()
+ for event in self.events:
+ if event[0] == 1 and event[2]==node.node_id:
+ selector.append(True)
+ else:
+ selector.append(False)
+ self.events=self.events[~np.array(selector)]
+ node["state"]="running"
+ node.rqueue.put(("timeout_remove",0))
+ elif node["request"] == "read":
+ node["state"]="running"
+ if node.rargs == "clock":
+ node.rqueue.put(("read",self.time))
+ elif node.rargs == "wlan0_ncom":
+ count=0
+ # Count number of communication on wlan0
+ for event in self.events:
+ if event[0] == 0 and event[2][1] == node.node_id and event[2][2] == "wlan0":
+ count+=1
+ node.rqueue.put(("read",count))
+ elif node.rargs == "eth0_ncom":
+ count=0
+ # Count number of communication on eth0
+ for event in self.events:
+ if event[0] == 0 and event[2][1] == node.node_id and event[2][2] == "eth0":
+ count+=1
+ node.rqueue.put(("read",count))
+ else:
+ node.rqueue.put(("read",0)) # Always return 0 if register is unknown
+ elif node["request"] == "turn_on":
+ node["state"]="running"
+ node.rqueue.put(("turn_on",0))
+ self.log("Turned on",node=node.node_id)
+ elif node["request"] == "turn_off":
+ selector_wlan0=list()
+ selector_other=list()
+ for event in self.events:
+ if event[0]==0 and int(event[2][1])==node.node_id:
+ if event[2][2] == "wlan0":
+ selector_wlan0.append(True)
+ selector_other.append(False)
+ else:
+ selector_wlan0.append(False)
+ selector_other.append(True)
+ else:
+ selector_wlan0.append(False)
+ selector_other.append(False)
+ # Informed sender to cancel send
+ for event in self.events[selector_other]:
+ sender=self.nodes[int(event[2][0])]
+ sender["state"]="running"
+ sender.rqueue.put(("send_cancel",2))
+ # Remove communications
+ if(len(self.events) != 0):
+ self.events=self.events[~(np.array(selector_wlan0)|np.array(selector_other))]
+ self.sharing[node.node_id]=0 # Sharing goes back to zero
+ node["state"]="running"
+ node.rqueue.put(("turn_off",0))
+ self.log("Turned off",node=node.node_id)
+ elif node["request"] == "send_cancel":
+ selector=list()
+ for event in self.events:
+ if event[0]==0 and int(event[2][0]) == node.node_id:
+ selector.append(True)
+ if event[2][2] != "wlan0":
+ self.update_sharing(int(event[2][1]),-1)
+ else:
+ selector.append(False)
+ self.events=self.events[~np.array(selector)]
+ node["state"]="running"
+ node.rqueue.put(("send_cancel",0))
+ node.sync()
+
+ def update_sharing(self, dst, amount):
+ """
+ Manage bandwidth sharing on wired interfaces
+ """
+ sharing=self.sharing[dst]
+ new_sharing=sharing+amount
+ for event in self.events:
+ if event[0] == 0 and event[2][2] != "wlan0" and int(event[2][1]) == dst:
+ remaining=event[1]-self.time
+ if remaining > 0:
+ remaining=remaining/sharing if sharing>1 else remaining # First restore sharing
+ remaining=remaining*new_sharing if new_sharing > 1 else remaining # Then apply new sharing
+ event[2][5]=remaining # Update duration
+ event[1]=self.time+remaining # Update timestamp
+ self.sharing[dst]=new_sharing
+ self.sort_events()
+
+ def handle_interferences(self,sender,receiver):
+ """
+ Interferences are detected by looking for conflicts between
+ new events and existing events.
+ """
+ status=False
+ selector=list()
+ notify=set()
+ for event in self.events:
+ event_type=event[0]
+ com=event[2]
+ if event_type==0 and com[2] == "wlan0":
+ com_sender=int(com[0])
+ com_receiver=int(com[1])
+ select=False
+ if receiver==com_sender:
+ status=True
+ notify.add(receiver)
+ elif receiver==com_receiver:
+ status=True
+ select=True
+ notify.add(receiver)
+ if sender==com_receiver and com_sender != com_receiver:
+ select=True
+ notify.add(sender)
+ selector.append(select)
+ else:
+ selector.append(False)
+ if len(selector) != 0:
+ self.events=self.events[~np.array(selector)]
+ for node in notify:
+ self.log("Interferences on wlan0",node=node)
+ return status
+
+ def sync_event(self, node):
+ """
+ Collect events from the nodes
+ """
+ if node["state"] == "request":
+ if node["request"] == "send":
+ node["state"]="pending"
+ interface, data, datasize, dst=node.rargs
+ self.communicate(interface, node.node_id, dst, data, datasize)
+ elif node["request"] == "receive":
+ interface=node.rargs
+ if node["interfaces_queue_size"][interface] > 0:
+ node["interfaces_queue_size"][interface]-=1
+ node.rqueue.put(("receive",0))
+ node["state"]="running"
+ # Do not forget to collect the next event. This is the only request which is processed here
+ self.sync_node(node)
+ self.sync_event(node)
+ elif node["request"] == "wait_end":
+ node["state"]="pending"
+ node.rqueue.put(("wait_end",0))
+ self.wait_end_nodes.append(node.node_id)
+
+ def communicate(self, interface, src, dst, data, datasize):
+ """
+ Create communication event between src and dst
+ """
+ nsrc=self.nodes[src]
+ if interface=="wlan0":
+ self.log("Send "+str(datasize)+" bytes on "+interface,node=src)
+ for dst in self.list_receivers(nsrc):
+ if self.nodes[dst]["turned_on"]:
+ duration=datasize*8/self.B[src,dst]+self.L[src,dst]
+ if src == dst:
+ self.add_event(0,duration+self.time,(src,dst,interface,data,datasize,duration,datasize,self.time))
+ elif not self.interferences:
+ self.add_event(0,duration+self.time,(src,dst,interface,data,datasize,duration,datasize,self.time))
+ elif not self.handle_interferences(src,dst):
+ self.add_event(0,duration+self.time,(src,dst,interface,data,datasize,duration,datasize,self.time))
+ else:
+ if self.nodes[dst]["turned_on"]:
+ self.log("Send "+str(datasize)+" bytes to n"+str(dst)+" on "+interface,node=src)
+ self.update_sharing(dst,1) # Update sharing first
+ # Note that in the following we send more data than expected to handle bandwidth sharing (datasize*8*sharing):
+ duration=datasize*8/(self.B[src,dst]/self.sharing[dst])+self.L[src,dst]
+ self.add_event(0,duration+self.time,(src,dst,interface,data,datasize,duration,datasize,self.time))
+ else:
+ nsrc["state"]="request" # Try later when node is on
+
+
+ def list_receivers(self,node):
+ """
+ Deduce reachable receivers from the bandwidth matrix
+ """
+ selector = self.B[node.node_id,] > 0
+ return np.arange(0,selector.shape[0])[selector]
+
+
+ def add_event(self,event_type,event_ts,event,priority=1):
+ """
+ Call this function with sort=True the least amount of time possible
+ """
+ self.events=np.concatenate([self.events,[np.array([event_type,event_ts,np.array(event,dtype=object),priority],dtype=object)]]) # Add new events
+ self.sort_events()
+
+ def run(self, breakpoints=[],breakpoint_callback=lambda s:None,breakpoints_every=None,debug=False,interferences=True):
+ """
+ Run the simulation with the created nodes
+ """
+ ##### Setup simulation
+ self.startat=time.time()
+ self.interferences=interferences
+ for bp in breakpoints:
+ self.add_event(2,bp,0,0)
+ if breakpoints_every != None:
+ self.add_event(3,breakpoints_every,0,0)
+ if debug:
+ with open(self.debug_file_path, "w") as f:
+ f.write("Python version {}\n".format(sys.version))
+ f.write("Simulation started at {}\n".format(self.startat))
+ f.write("Number of nodes is "+str(len(self.nodes))+"\n")
+ f.write("Manual breakpoints list: "+str(breakpoints)+"\n")
+ f.write("Breakpoints every "+str(breakpoints_every)+"s\n")
+ ##### Simulation loop
+ while True:
+ # Synchronize every nodes
+ for node in self.nodes:
+ self.sync_node(node)
+ # Manage events
+ for node in self.nodes:
+ self.sync_event(node)
+ # Generate debug logs
+ if debug:
+ self.debug()
+ # Simulation end
+ if len(self.events) <= 0 or len(self.events) == 1 and self.events[0,0] == 3:
+ # Notify nodes that wait for the end of the simulation
+ # Note that we do not allow them to create new events (even if they try, they will not be processed)
+ for node_id in self.wait_end_nodes:
+ self.nodes[node_id].rqueue.put(("sim_end",0))
+ self.nodes[node_id]["state"]="running"
+ self.sync_node(self.nodes[node_id]) # Allow them for make call requests (printing logs for example)
+ break # End the event processing loop
+
+ # Update simulation time
+ self.time=self.events[0,1]
+ self.time_truncated=format(self.time,self.precision) # refresh truncated time
+
+ # Process events
+ while len(self.events) > 0 and self.events[0,1] == self.time:
+ event_type=int(self.events[0,0])
+ ts=self.events[0,1]
+ event=self.events[0,2]
+ self.events=np.delete(self.events,0,0) # Consume events NOW! not at the end of the loop (event list may change in between)
+ if event_type == 0:
+ src_id,dst_id,interface, data, datasize,duration,datasize_remaining,start_at=event
+ src=self.nodes[int(src_id)]
+ dst=self.nodes[int(dst_id)]
+ if interface == "wlan0":
+ if src.node_id != dst.node_id:
+ dst["interfaces"][interface].put((data,start_at,self.time))
+ dst["interfaces_queue_size"][interface]+=1
+ self.log("Receive "+str(datasize)+" bytes on "+interface,node=int(dst_id))
+ # If node is receiving makes it consume (this way if there is a timeout, it will be removed!)
+ if dst["state"] == "request" and dst["request"] == "receive":
+ dst["interfaces_queue_size"][interface]-=1
+ dst.rqueue.put(("receive",0))
+ dst["state"]="running"
+ self.sync_node(dst)
+ else:
+ src["state"]="running"
+ src.rqueue.put(("send",0))
+ else:
+ dst["interfaces"][interface].put((data,start_at,self.time))
+ dst["interfaces_queue_size"][interface]+=1
+ self.update_sharing(dst.node_id,-1)
+ self.log("Receive "+str(datasize)+" bytes on "+interface,node=int(dst_id))
+ # If node is receiving makes it consume (this way if there is a timeout, it will be removed!)
+ if dst["state"] == "request" and dst["request"] == "receive":
+ dst["interfaces_queue_size"][interface]-=1
+ dst.rqueue.put(("receive",0))
+ dst["state"]="running"
+ self.sync_node(dst)
+ src["state"]="running"
+ src.rqueue.put(("send",0))
+ elif event_type == 1:
+ node=self.nodes[int(event)]
+ node["state"]="running"
+ node.rqueue.put(("timeout",0))
+ self.sync_node(node)
+ elif event_type == 2 or event_type == 3:
+ breakpoint_callback(self)
+ if event_type == 3:
+ self.add_event(3,self.time+breakpoints_every,0,0)
+
+ ##### Simulation ends
+ self.log("Simulation ends")
+
diff --git a/example/receiver.py b/example/receiver.py
new file mode 100644
index 0000000..a2ed690
--- /dev/null
+++ b/example/receiver.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+import sys, random, time
+
+lr=random.Random(6)
+
+def execute(api):
+ uptime=api.args
+ endoff=0
+ for i in range(0,24):
+ startoff=random.randint(0,3600-uptime)
+ api.turn_off()
+ api.wait(startoff+endoff)
+ api.turn_on()
+ wakeat=api.read("clock")
+ wakeuntil=wakeat+uptime
+ # Receive until uptime seconds if elapsed
+ while api.read("clock") < wakeuntil:
+ code, data=api.receivet("wlan0",wakeuntil-api.read("clock"))
+ if code == 0:
+ api.log("Receive "+data)
+ api.log("Was up for {}s".format(api.read("clock")-wakeat))
+ endoff=3600*(i+1)-api.read("clock")
+ api.turn_off()
+ api.wait(endoff)
+ api.turn_on()
+
+
diff --git a/example/sender.py b/example/sender.py
new file mode 100644
index 0000000..8389c95
--- /dev/null
+++ b/example/sender.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+######################
+# _ ____ ___ #
+# / \ | _ \_ _| #
+# / _ \ | |_) | | #
+# / ___ \| __/| | #
+# /_/ \_\_| |___| #
+# #
+######################
+# api.args # Contains node arguments
+# api.send(interface, data,size, dst) # If interface is "wlan0" dst is not used
+# api.sendt(interface, data,size, dst,timeout) # Similar to api.send() but with timeout
+# api.receive(interface) # Receive the data by returning the following tuple (code,data) if code is 0 receive succeed
+# api.receivet(interface,timeout) # Similar to api.receive() but with timeout
+# api.read(register) # Read from the simulator registers (ex: clock)
+# api.log(msg) # Print log in the simulation console
+# api.wait(duration) # Wait for "duration" seconds of simulated time
+# api.turn_off(duration) # Turn the node off for "duration" seconds (no data can be receive during this time period)
+
+import sys, random
+
+# Note that the following is required to have different instance from thread to thread
+lr=random.Random(6)
+
+def execute(api):
+ uptime=api.args
+ endoff=0
+ for i in range(0,24):
+ startoff=random.randint(0,3600-uptime)
+ api.turn_off()
+ api.wait(startoff+endoff)
+ api.turn_on()
+ wakeat=api.read("clock")
+ wakeuntil=wakeat+uptime
+ # Send until uptime seconds if elapsed
+ while api.read("clock") < wakeuntil:
+ api.sendt("wlan0","hello",10,None, wakeuntil-api.read("clock"))
+ api.log("Was up for {}s".format(api.read("clock")-wakeat))
+ endoff=3600*(i+1)-api.read("clock")
+ api.turn_off()
+ api.wait(endoff)
+ api.turn_on()
+
+
+
+
diff --git a/example/simulator.py b/example/simulator.py
new file mode 100755
index 0000000..9b00887
--- /dev/null
+++ b/example/simulator.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../")
+import esds
+
+# Use numpy to construct bandwidth and latencies matrix
+import numpy as np
+
+##### Scenario
+# The simulated scenario comprises 1 that wakes up randomly
+# for a duration called "uptime" every hour. The sender try to transmit
+# his data during that uptime. Other nodes are receivers that have similar
+# random wake up parterns and strive to receive data from the sender.
+
+##### Bandwidth matrix
+# Bandwidth value can be 0 for unreachable nodes
+# Diagonal entries impact the transmission duration for wireless transmissions (wlan0)
+n=2 # Number of nodes including the sender
+B=np.full((n,n),5) # 5Mbps
+
+##### Latency matrix
+# If the latency entries match one with a bandwidth of 0
+# then it will be ignore since node is unreachable.
+L=np.full((n,n),0) # 0s
+
+##### Create the simulator
+s=esds.Simulator(B,L)
+
+##### Instantiate nodes
+uptime=180 # 180s uptime
+s.create_node("sender",args=uptime) # Load sender.py for the first node with 5 as argument (first row in B and L)
+
+# Aguments can be passed to nodes via: s.create_node("sender",args="my argument")
+for n in range(0,n-1): # Load receiver.py for the remaining nodes
+ s.create_node("receiver",args=uptime)
+
+##### Run the simulation
+#s.run(debug=True) # Generate a "esds.debug" file
+s.run()
diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..5f4407f
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="210mm"
+ height="297mm"
+ viewBox="0 0 210 297"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ sodipodi:docname="icon.svg"
+ inkscape:export-filename="/home/loic/bitmap.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:document-units="mm"
+ showgrid="false"
+ inkscape:zoom="1.4274173"
+ inkscape:cx="375.85366"
+ inkscape:cy="592.32856"
+ inkscape:window-width="2548"
+ inkscape:window-height="1390"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1"
+ inkscape:snap-object-midpoints="true" />
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g2217"
+ transform="rotate(90,96.944432,135.97703)">
+ <g
+ id="g2201">
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 96.944421,117.45824 V 102.56952"
+ id="path2189" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 112.53423,117.45824 V 102.56952"
+ id="path2191" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 81.354608,117.45824 V 102.56952"
+ id="path2193" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2195"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-117.23508"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2197"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-101.64526"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2199"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-86.05545"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ </g>
+ <g
+ id="g2215">
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 96.944436,165.95584 V 151.06712"
+ id="path2203" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 112.53426,165.95584 V 151.06712"
+ id="path2205" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 81.35463,165.95584 V 151.06712"
+ id="path2207" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2209"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-117.2351"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2211"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-101.64528"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect2213"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-86.055473"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ </g>
+ </g>
+ <g
+ id="g2187">
+ <g
+ id="g2171">
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 96.944421,117.45824 V 102.56952"
+ id="path2136" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 112.53423,117.45824 V 102.56952"
+ id="path2138" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 81.354608,117.45824 V 102.56952"
+ id="path1948" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1312"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-117.23508"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1314"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-101.64526"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1316"
+ width="9.4016829"
+ height="9.4016857"
+ x="96.596527"
+ y="-86.05545"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ </g>
+ <g
+ id="g2163">
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 96.944436,165.95584 V 151.06712"
+ id="path1914" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 112.53426,165.95584 V 151.06712"
+ id="path1916" />
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 81.35463,165.95584 V 151.06712"
+ id="path1537" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1320"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-117.2351"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1322"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-101.64528"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1324"
+ width="9.4016829"
+ height="9.4016857"
+ x="165.95584"
+ y="-86.055473"
+ ry="0.80720627"
+ transform="rotate(90)" />
+ </g>
+ </g>
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2.99158;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
+ id="rect1030"
+ width="47.008419"
+ height="47.008415"
+ x="73.440231"
+ y="112.47282"
+ ry="4.0360303" />
+ <path
+ style="font-variation-settings:normal;opacity:1;fill:#ffffff;fill-opacity:0;stroke:#4d4d4d;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ d="M 107.5001,136.0153 83.837287,122.26787 108.60262,149.76274 Z"
+ id="path2632" />
+ <circle
+ style="font-variation-settings:normal;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ id="path2475"
+ cx="83.837288"
+ cy="122.26787"
+ r="4.7406592" />
+ <circle
+ style="font-variation-settings:normal;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ id="circle2557"
+ cx="108.60262"
+ cy="149.76274"
+ r="3.1051633" />
+ <circle
+ style="font-variation-settings:normal;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
+ id="circle2559"
+ cx="107.5001"
+ cy="136.0153"
+ r="4.7406592" />
+ </g>
+</svg>
diff --git a/plugins/__init__.py b/plugins/__init__.py
new file mode 100644
index 0000000..abb734a
--- /dev/null
+++ b/plugins/__init__.py
@@ -0,0 +1 @@
+__all__ = [ "node_plugin", "power_states" ]
diff --git a/plugins/node_plugin.py b/plugins/node_plugin.py
new file mode 100644
index 0000000..325ff8a
--- /dev/null
+++ b/plugins/node_plugin.py
@@ -0,0 +1,29 @@
+class NodePlugin:
+ """
+ Node plugins get register to the node API get notified when events occurs.
+ The call and return suffixes are used for methods that are called at the beginning
+ and the end, respectively, of API calls triggered by the node source code.
+
+ Changing this API could brake most of the node plugins.
+ """
+
+ def __init__(self,plugin_name,api):
+ self.api=api
+ self.plugin_name=plugin_name
+ api.plugin_register(self)
+
+ def on_send_call(self,interface,data,datasize,dst):
+ pass
+
+ def on_send_return(self,interface,data,datasize,dst,code):
+ pass
+
+ def on_receive_return(self,interface,data,start_at,end_at):
+ pass
+
+ def on_terminated(self):
+ pass
+
+ def log(self,msg):
+ self.api.log(self.plugin_name+"(NP) "+msg)
+
diff --git a/plugins/operating_states.py b/plugins/operating_states.py
new file mode 100644
index 0000000..400aa1b
--- /dev/null
+++ b/plugins/operating_states.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+from plugins.node_plugin import *
+
+######################
+# _ ____ ___ #
+# / \ | _ \_ _| #
+# / _ \ | |_) | | #
+# / ___ \| __/| | #
+# /_/ \_\_| |___| #
+# #
+######################
+
+# import plugins.operating_states as op
+# # Load the directional transition graph from graph.txt starting at the "vertex1" state
+# opstate=op.OperatingStates(api,"graph.txt","vertex1")
+# Format of the graph.txt file consists in one edge per line
+# that consists on the source vertex and destination vertex sperated by a space
+# As an example:
+# vertex1 vertex2
+# vertex1 vertex3
+# vertex3 vertex2
+# vertex2 vertex1
+#
+# opstate.register_callback(boom)
+# # On each state transition boom will be called as boom(src_state,dst_state)
+# # This way the boom callback can contains power_state transitions for examples
+# opstate.goto("vertex2") # works
+# opstate.goto("vertex3") # wont work
+# opstate.goto("vertex1") # work since we are on vertex2
+
+class OperatingStates(NodePlugin):
+ """
+ OperatingStates plugin
+ """
+ def __init__(self,api, state_file, initial_state):
+ self.transitions=list()
+ self.callbacks=list()
+ self.state=initial_state
+ with open(state_file) as fp:
+ for i, line in enumerate(fp):
+ self.transitions.append(line)
+ super().__init__("OperatingStates",api)
+
+ def goto(self,state):
+ if (self.state+" "+state) in self.transitions:
+ old_state=self.state
+ self.state=state
+ for c in self.callbacks:
+ c(old_state,state)
+ else:
+ self.log("Invalid transition "+self.state+" => "+state)
+
+ def get_state(self):
+ return(self.state)
+
+ def register_callback(self,callback):
+ """
+ The callback will be called on each state transition
+ Callback takes two arguments which are:
+ - The source state
+ - The destination state
+ """
+ self.callbacks.append(callback)
+
+
diff --git a/plugins/power_states.py b/plugins/power_states.py
new file mode 100644
index 0000000..c8e9d0e
--- /dev/null
+++ b/plugins/power_states.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+
+from plugins.node_plugin import *
+
+# PowerStates allows you to measure the energy consumption of a
+# node that go through several power states during the simulation
+# Two version of Powerstates is provided by mean of two classes:
+# - Powerstates: Allow you to set power to any user's defined values
+# - PowerstatesFromFile: Allow you to set power from states defined in a file
+
+######################
+# _ ____ ___ #
+# / \ | _ \_ _| #
+# / _ \ | |_) | | #
+# / ___ \| __/| | #
+# /_/ \_\_| |___| #
+# #
+######################
+
+# #Regarding PowerStates:
+# import Powerstates as ps
+# pstates=ps.PowerStates(<node>,<power_init>)
+# pstates.set_power(<power>) # Switch the power consumption to <power>
+# pstates.report_energy() # Display the current node energy consumption up to the current simulated time
+# pstates.report_power_changes() # Display all the power changes up to the current simulated time
+
+# #Regarding PowerStatesFromFile:
+# #Format of <file> is one <entry> per line that follow this format <state-0>:<state-1>:...:<state-n>
+# #Each line can corresponds to one node
+# import Powerstates as ps
+# pstates=ps.PowerStatesFromFile(<node>,<file>,<entry-line>) # Create a power states on node <node> using line <entry-line> of file <file>
+# pstates.set_state(<id>) # Switch to the <id> power states
+# pstates.report_energy() # Display the current node energy consumption up to the current simulated time
+# pstates.report_power_changes() # Display all the power changes up to the current simulated time
+# pstates.report_state_changes() # Display all the states changes up to the current simulated time
+
+
+class PowerStates(NodePlugin):
+ """
+ PowerStates model the energy consumed by the various changes of power consumption of a node over time.
+ """
+ def __init__(self,node,power_init):
+ self.node=node
+ self.clock=self.node.clock()
+ self.energy=0
+ self.power=power_init
+ self.power_changes=dict()
+ self.set_power(power_init)
+ super().__init__("Powerstates",api)
+
+
+ def set_power(self,power_watt):
+ cur_clock=self.node.clock()
+ self.energy+=self.power*(cur_clock-self.clock)
+ self.clock=cur_clock
+ if self.power != power_watt:
+ self.power_changes[cur_clock]=power_watt
+ self.power=power_watt
+ return cur_clock
+
+ def report_energy(self):
+ self.set_power(self.power)
+ self.node.log("[PowerStates Plugin] Consumed "+str(self.energy) +"J")
+
+ def report_power_changes(self):
+ self.set_power(self.power)
+ for key in self.power_changes.keys():
+ self.node.log("[PowerStates Plugin] At t="+str(key)+" power is "+str(self.power_changes[key])+"W")
+
+
+
+class PowerStatesFromFile(PowerStates):
+ """
+ A version of Powerstates that load the power values from a file.
+ """
+ def __init__(self,node,state_file,entry_line=1):
+ self.node=node
+ self.state_changes=dict()
+ self.states=[]
+ self.state=0
+ with open(state_file) as fp:
+ for i, line in enumerate(fp):
+ if i+1 == entry_line:
+ self.states=line.split(":")
+ self.states=[float(i) for i in self.states]
+ assert len(self.states) > 0
+ super().__init__(node,self.states[0])
+ self.set_state(0)
+
+ def set_state(self,state_id):
+ assert state_id < len(self.states)
+ clock=super().set_power(self.states[state_id])
+ if self.state != state_id:
+ self.state_changes[clock]=state_id
+ self.state=state_id
+
+
+ def report_state_changes(self):
+ self.set_state(self.state)
+ for key in self.state_changes.keys():
+ self.node.log("[PowerStates Plugin] At t="+str(key)+" state is "+str(self.state_changes[key]))
+
+
+class PowerStatesComms(NodePlugin):
+ """
+ Monitor the energy consumed by the network interfaces by mean of power states.
+ Note that for finer grained predictions, bytes and packet power consumption must be accounted.
+ Which is not the case with these power states.
+ """
+
+ def __init__(self,api):
+ super().__init__("PowerStatesComms",api)
+ self.energy_dynamic=0.0 # Store the dynamic part of the energy consumption
+ self.power=dict() # Store the power states
+ self.tx_clock=0 # Dynamic clock (store the time at which a the last tx starts
+ self.idle_clock=api.clock() # Store the start time (to compute the idle part of the energy consumption)
+
+ def on_receive_return(self,interface,data,start_at,end_at):
+ duration=float(end_at)-float(start_at)
+ self.energy_dynamic+=self.power[interface]["rx"]*duration
+
+ def on_send_call(self,interface,data,datasize,dst):
+ self.tx_clock=self.api.clock()
+
+ def on_send_return(self,interface,data,datasize,dst,code):
+ clock=self.api.clock()
+ duration=(clock-float(self.tx_clock))
+ self.energy_dynamic+=self.power[interface]["tx"]*duration
+ self.tx_clock=clock # Any value could be use here
+
+ def set_power(self,interface,idle,tx,rx):
+ self.power[interface]=dict()
+ self.power[interface]["idle"]=idle
+ self.power[interface]["rx"]=rx
+ self.power[interface]["tx"]=tx
+
+ def get_idle(self):
+ clock=self.api.clock()
+ idle=0
+ for interface in self.power.keys():
+ idle+=(clock-self.idle_clock)*self.power[interface]["idle"]
+ return idle
+
+ def get_receive_queue_energy(self,interface):
+ """
+ Not that call to on_receive_return may not have happened yet (or never).
+ Thus we should manually compute the energy consumption stored in each queues of the node.
+ """
+ energy=0
+ # For each interface we should check if there is received data that has not been consumed
+ for data in list(self.api["interfaces"][interface].queue):
+ start_at=float(data[1])
+ end_at=float(data[2])
+ energy+=(end_at-start_at)*self.power[interface]["rx"]
+ return energy
+
+ def get_energy(self):
+ queue_energy=0
+ for interface in self.power.keys():
+ queue_energy+=self.get_receive_queue_energy(interface)
+ return self.get_idle()+self.energy_dynamic+queue_energy
+
+ def report_energy(self):
+ self.log("Communications consumed "+str(round(self.get_energy(),2))+"J")
diff --git a/plugins/wireless_area.py b/plugins/wireless_area.py
new file mode 100644
index 0000000..b2e2432
--- /dev/null
+++ b/plugins/wireless_area.py
@@ -0,0 +1,71 @@
+import math
+import numpy as np
+
+class WirelessArea:
+
+ def __init__(self):
+ self.nodes=list()
+
+ def dump_nodes(self):
+ i=0
+ for node in self.nodes:
+ x,y,z,com_range=node
+ print("Node {} at ({},{},{}) with a communication range of {}m".format(i,x,y,z,com_range))
+ i+=1
+
+ def dump_infos(self):
+ print("Number of nodes {}".format(len(self.nodes)))
+ adjacency=self.generate_adjacency_matrix(fill_diagonal=False)
+ print("Nodes average degree is {}".format(np.mean(np.sum(adjacency,axis=0))))
+ x = [node[0] for node in self.nodes]
+ y = [node[1] for node in self.nodes]
+ z = [node[2] for node in self.nodes]
+ com_range = [node[3] for node in self.nodes]
+ print("Nodes locations ranges: x in [{},{}] y in [{},{}] z in [{},{}]".format(min(x),max(x),min(y),max(y),min(z),max(z)))
+ print("Node communication ranges in [{},{}]".format(min(com_range),max(com_range)))
+
+ def add_node(self,x,y,z,com_range):
+ self.nodes.append((x,y,z,com_range))
+
+ def get_neighbours(self,node_id):
+ node=self.nodes[node_id]
+ neighbours=list()
+ for i in range(0,len(self.nodes)):
+ if i != node_id:
+ neighbour=self.nodes[i]
+ if math.dist(node[0:3],neighbour[0:3]) <= node[3]:
+ neighbours.append(i)
+ return neighbours
+
+ def generate_dot(self,filepath):
+ is_strict=False
+ com_range=self.nodes[0][3]
+ for node in self.nodes:
+ if node[3] != com_range:
+ is_strict=True
+ break
+
+ with open(filepath, "w") as f:
+ if is_strict:
+ f.write("digraph G {\n")
+ else:
+ f.write("strict graph G {\n")
+ for i in range(0,len(self.nodes)):
+ neighbours=self.get_neighbours(i)
+ for n in neighbours:
+ if is_strict:
+ f.write("{}->{}\n".format(i,n))
+ else:
+ f.write("{}--{}\n".format(i,n))
+ f.write("}")
+
+ def generate_adjacency_matrix(self,fill_diagonal=True):
+ matrix=np.full((len(self.nodes),len(self.nodes)),0)
+ if fill_diagonal:
+ np.fill_diagonal(matrix,1) # Required by ESDS
+ for i in range(0,len(self.nodes)):
+ neighbours=self.get_neighbours(i)
+ for n in neighbours:
+ matrix[i,n]=1
+ return matrix
+
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..9cf09c6
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,6 @@
+# Tests
+**Test folders names convention:**
+- **m** senders and **n** receivers is written `<m>s<n>r` *(ex: 1s5r)*
+- **n** nodes is written `<n>n` *(ex: 5n)*
+- Tests names follow the following format `<title>_<m>s<n>r` or `<title>_<n>n` *(ex: simple_send_rcv_1s5r, ping_pong_2n)*
+- Tests that test **elementary API functions** should start with the **simple** keyword
diff --git a/tests/hidden_node_2s1r/out b/tests/hidden_node_2s1r/out
new file mode 100644
index 0000000..0922147
--- /dev/null
+++ b/tests/hidden_node_2s1r/out
@@ -0,0 +1,4 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=0.000,src=n2] Send 1 bytes on wlan0
+[t=0.000,src=n1] Interferences on wlan0
+[t=1.000,src=esds] Simulation ends
diff --git a/tests/hidden_node_2s1r/receiver.py b/tests/hidden_node_2s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/hidden_node_2s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/hidden_node_2s1r/sender.py b/tests/hidden_node_2s1r/sender.py
new file mode 100644
index 0000000..80f0fc2
--- /dev/null
+++ b/tests/hidden_node_2s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("wlan0","Hello World!",1,1)
diff --git a/tests/hidden_node_2s1r/simulator.py b/tests/hidden_node_2s1r/simulator.py
new file mode 100755
index 0000000..63ca502
--- /dev/null
+++ b/tests/hidden_node_2s1r/simulator.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),8)
+
+# Hide the nodes from each others
+B[0,2]=0
+B[2,0]=0
+
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+s.create_node("sender")
+
+s.run()
diff --git a/tests/mobility_eth0_bandwidth_1s1r/out b/tests/mobility_eth0_bandwidth_1s1r/out
new file mode 100644
index 0000000..e0a3d04
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_1s1r/out
@@ -0,0 +1,4 @@
+[t=0.000,src=n0] Send 1 bytes to n1 on eth0
+[t=0.500,src=esds] Network update!
+[t=0.750,src=n1] Receive 1 bytes on eth0
+[t=0.750,src=esds] Simulation ends
diff --git a/tests/mobility_eth0_bandwidth_1s1r/receiver.py b/tests/mobility_eth0_bandwidth_1s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_1s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_eth0_bandwidth_1s1r/sender.py b/tests/mobility_eth0_bandwidth_1s1r/sender.py
new file mode 100644
index 0000000..753ccce
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_1s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,1)
diff --git a/tests/mobility_eth0_bandwidth_1s1r/simulator.py b/tests/mobility_eth0_bandwidth_1s1r/simulator.py
new file mode 100755
index 0000000..f7a0072
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_1s1r/simulator.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communcation duration(s) | Communcation ends at(s) |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | 0 | 0 | 8 | 8 | 1 | 1 |
+# | 0.5 | 0 | 16 | C/U*D = 4 | R/I * L + R/BW = 0.25 | 0.75 |
+# | 0.75 | 0 | 16 | | | |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+##############################
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B*2,simulator.L)
+
+s.run(breakpoints_every=1/2,breakpoint_callback=callback)
diff --git a/tests/mobility_eth0_bandwidth_2s1r/out b/tests/mobility_eth0_bandwidth_2s1r/out
new file mode 100644
index 0000000..7138457
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_2s1r/out
@@ -0,0 +1,6 @@
+[t=0.000,src=n0] Send 1 bytes to n2 on eth0
+[t=0.000,src=n1] Send 1 bytes to n2 on eth0
+[t=1.000,src=esds] Network update!
+[t=1.500,src=n2] Receive 1 bytes on eth0
+[t=1.500,src=n2] Receive 1 bytes on eth0
+[t=1.500,src=esds] Simulation ends
diff --git a/tests/mobility_eth0_bandwidth_2s1r/receiver.py b/tests/mobility_eth0_bandwidth_2s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_2s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_eth0_bandwidth_2s1r/sender.py b/tests/mobility_eth0_bandwidth_2s1r/sender.py
new file mode 100644
index 0000000..273cdc2
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_2s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,2)
diff --git a/tests/mobility_eth0_bandwidth_2s1r/simulator.py b/tests/mobility_eth0_bandwidth_2s1r/simulator.py
new file mode 100755
index 0000000..ba876d8
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_2s1r/simulator.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | This table is the same for both sender | | | | | |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communication duration(s) | Communcation ends at(s) |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | 0 | 0 | 8/2 | 8 | 2 | 2 |
+# | 1 | 0 | 16/2 | C/U*D = 4 | 0.5 | 1.5 |
+# | 1.5 | 0 | 16/2 | 0 | | |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+##############################
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B*2,simulator.L)
+
+s.run(breakpoints_every=1,breakpoint_callback=callback,debug=True)
diff --git a/tests/mobility_eth0_bandwidth_2s1r/yoctosim.debug b/tests/mobility_eth0_bandwidth_2s1r/yoctosim.debug
new file mode 100644
index 0000000..b0e4327
--- /dev/null
+++ b/tests/mobility_eth0_bandwidth_2s1r/yoctosim.debug
@@ -0,0 +1,37 @@
+Python version 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0]
+Simulation started at 1654802959.3233912
+Number of nodes is 3
+Manual breakpoints list: []
+Breakpoints every 1s
+-----------------------------------------------
+Started since 0.04s
+Simulated time 0.000s (or more precisely 0s)
+Node number per state: pending=2 terminated=1
+Node sharing: n2=2
+Ids of node in timeout mode:
+Sorted events list:
+[[3 1 array(0, dtype=object) 0]
+ [0 2.0 array([0, 2, 'eth0', 'Hello World!', 1, 2.0, 1, 0], dtype=object)
+ 1]
+ [0 2.0 array([1, 2, 'eth0', 'Hello World!', 1, 2.0, 1, 0], dtype=object)
+ 1]]
+-----------------------------------------------
+Started since 0.04s
+Simulated time 1.000s (or more precisely 1s)
+Node number per state: pending=2 terminated=1
+Node sharing: n2=2
+Ids of node in timeout mode:
+Sorted events list:
+[[0 1.5
+ array([0, 2, 'eth0', 'Hello World!', 1, 0.5, 0.5, 0], dtype=object) 1]
+ [0 1.5
+ array([1, 2, 'eth0', 'Hello World!', 1, 0.5, 0.5, 0], dtype=object) 1]
+ [3 2 array(0, dtype=object) 0]]
+-----------------------------------------------
+Started since 0.05s
+Simulated time 1.500s (or more precisely 1.5s)
+Node number per state: terminated=3
+Node sharing:
+Ids of node in timeout mode:
+Sorted events list:
+[[3 2 array(0, dtype=object) 0]]
diff --git a/tests/mobility_eth0_latency_1s1r/out b/tests/mobility_eth0_latency_1s1r/out
new file mode 100644
index 0000000..ef56e0f
--- /dev/null
+++ b/tests/mobility_eth0_latency_1s1r/out
@@ -0,0 +1,5 @@
+[t=0.000,src=n0] Send 1 bytes to n1 on eth0
+[t=0.500,src=esds] Network update!
+[t=1.000,src=esds] Network update!
+[t=1.333,src=n1] Receive 1 bytes on eth0
+[t=1.333,src=esds] Simulation ends
diff --git a/tests/mobility_eth0_latency_1s1r/receiver.py b/tests/mobility_eth0_latency_1s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_eth0_latency_1s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_eth0_latency_1s1r/sender.py b/tests/mobility_eth0_latency_1s1r/sender.py
new file mode 100644
index 0000000..753ccce
--- /dev/null
+++ b/tests/mobility_eth0_latency_1s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,1)
diff --git a/tests/mobility_eth0_latency_1s1r/simulator.py b/tests/mobility_eth0_latency_1s1r/simulator.py
new file mode 100755
index 0000000..525bb66
--- /dev/null
+++ b/tests/mobility_eth0_latency_1s1r/simulator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communcation duration(s) | Communcation ends at(s) |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | 0 | 0 | 8 | 8 | 1 | 1 |
+# | 0.5 | 0.5 | 8 | C/U*D = 4 | R/I * L + R/BW = 0.75 | 1.25 |
+# | 1 | 1 | 8 | C/U*D = 1.33 | R/I * L + R/BW = 0.33 | 1.33 |
+# | 1.33 | 1 | 8 | | | |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+##############################
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B,simulator.L+1/2)
+
+s.run(breakpoints_every=1/2,breakpoint_callback=callback)
diff --git a/tests/mobility_eth0_latency_2s1r/out b/tests/mobility_eth0_latency_2s1r/out
new file mode 100644
index 0000000..41d59fc
--- /dev/null
+++ b/tests/mobility_eth0_latency_2s1r/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Send 1 bytes to n2 on eth0
+[t=0.000,src=n1] Send 1 bytes to n2 on eth0
+[t=1.000,src=esds] Network update!
+[t=2.000,src=esds] Network update!
+[t=2.300,src=n2] Receive 1 bytes on eth0
+[t=2.300,src=n2] Receive 1 bytes on eth0
+[t=2.300,src=esds] Simulation ends
diff --git a/tests/mobility_eth0_latency_2s1r/receiver.py b/tests/mobility_eth0_latency_2s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_eth0_latency_2s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_eth0_latency_2s1r/sender.py b/tests/mobility_eth0_latency_2s1r/sender.py
new file mode 100644
index 0000000..273cdc2
--- /dev/null
+++ b/tests/mobility_eth0_latency_2s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,2)
diff --git a/tests/mobility_eth0_latency_2s1r/simulator.py b/tests/mobility_eth0_latency_2s1r/simulator.py
new file mode 100755
index 0000000..101004b
--- /dev/null
+++ b/tests/mobility_eth0_latency_2s1r/simulator.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | This table is the same for both sender | | | | | |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communication duration(s) | Communcation ends at(s) |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+# | 0 | 0 | 8/2 | 8 | 2 | 2 |
+# | 1 | 0.5 | 8/2 | C/U*D=4 | R/BW + R/I*L = 1.25 | 2.25 |
+# | 2 | 1 | 8/2 | C/U*D=0.8 | R/BW + R/I*L = 0.3 | 2.3 |
+# | 2.3 | 1 | 8/2 | 0 | | |
+# |----------------------------------------+------------+----------------+----------------------+---------------------------+-------------------------|
+##############################
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B,simulator.L+0.5)
+
+s.run(breakpoints_every=1,breakpoint_callback=callback,debug=True)
diff --git a/tests/mobility_eth0_latency_2s1r/yoctosim.debug b/tests/mobility_eth0_latency_2s1r/yoctosim.debug
new file mode 100644
index 0000000..efe50d9
--- /dev/null
+++ b/tests/mobility_eth0_latency_2s1r/yoctosim.debug
@@ -0,0 +1,51 @@
+Python version 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0]
+Simulation started at 1654802960.9297695
+Number of nodes is 3
+Manual breakpoints list: []
+Breakpoints every 1s
+-----------------------------------------------
+Started since 0.08s
+Simulated time 0.000s (or more precisely 0s)
+Node number per state: pending=2 terminated=1
+Node sharing: n2=2
+Ids of node in timeout mode:
+Sorted events list:
+[[3 1 array(0, dtype=object) 0]
+ [0 2.0 array([0, 2, 'eth0', 'Hello World!', 1, 2.0, 1, 0], dtype=object)
+ 1]
+ [0 2.0 array([1, 2, 'eth0', 'Hello World!', 1, 2.0, 1, 0], dtype=object)
+ 1]]
+-----------------------------------------------
+Started since 0.08s
+Simulated time 1.000s (or more precisely 1s)
+Node number per state: pending=2 terminated=1
+Node sharing: n2=2
+Ids of node in timeout mode:
+Sorted events list:
+[[3 2 array(0, dtype=object) 0]
+ [0 2.25
+ array([0, 2, 'eth0', 'Hello World!', 1, 1.25, 0.5, 0], dtype=object) 1]
+ [0 2.25
+ array([1, 2, 'eth0', 'Hello World!', 1, 1.25, 0.5, 0], dtype=object) 1]]
+-----------------------------------------------
+Started since 0.08s
+Simulated time 2.000s (or more precisely 2s)
+Node number per state: pending=2 terminated=1
+Node sharing: n2=2
+Ids of node in timeout mode:
+Sorted events list:
+[[0 2.3
+ array([0, 2, 'eth0', 'Hello World!', 1, 0.30000000000000004, 0.1, 0],
+ dtype=object) 1]
+ [0 2.3
+ array([1, 2, 'eth0', 'Hello World!', 1, 0.30000000000000004, 0.1, 0],
+ dtype=object) 1]
+ [3 3 array(0, dtype=object) 0]]
+-----------------------------------------------
+Started since 0.09s
+Simulated time 2.300s (or more precisely 2.3s)
+Node number per state: terminated=3
+Node sharing:
+Ids of node in timeout mode:
+Sorted events list:
+[[3 3 array(0, dtype=object) 0]]
diff --git a/tests/mobility_wlan0_bandwidth_1s1r/out b/tests/mobility_wlan0_bandwidth_1s1r/out
new file mode 100644
index 0000000..95cdeeb
--- /dev/null
+++ b/tests/mobility_wlan0_bandwidth_1s1r/out
@@ -0,0 +1,4 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=0.500,src=esds] Network update!
+[t=0.750,src=n1] Receive 1 bytes on wlan0
+[t=0.750,src=esds] Simulation ends
diff --git a/tests/mobility_wlan0_bandwidth_1s1r/receiver.py b/tests/mobility_wlan0_bandwidth_1s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_wlan0_bandwidth_1s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_wlan0_bandwidth_1s1r/sender.py b/tests/mobility_wlan0_bandwidth_1s1r/sender.py
new file mode 100644
index 0000000..80f0fc2
--- /dev/null
+++ b/tests/mobility_wlan0_bandwidth_1s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("wlan0","Hello World!",1,1)
diff --git a/tests/mobility_wlan0_bandwidth_1s1r/simulator.py b/tests/mobility_wlan0_bandwidth_1s1r/simulator.py
new file mode 100755
index 0000000..f7a0072
--- /dev/null
+++ b/tests/mobility_wlan0_bandwidth_1s1r/simulator.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communcation duration(s) | Communcation ends at(s) |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | 0 | 0 | 8 | 8 | 1 | 1 |
+# | 0.5 | 0 | 16 | C/U*D = 4 | R/I * L + R/BW = 0.25 | 0.75 |
+# | 0.75 | 0 | 16 | | | |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+##############################
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B*2,simulator.L)
+
+s.run(breakpoints_every=1/2,breakpoint_callback=callback)
diff --git a/tests/mobility_wlan0_latency_1s1r/out b/tests/mobility_wlan0_latency_1s1r/out
new file mode 100644
index 0000000..b1a2b0f
--- /dev/null
+++ b/tests/mobility_wlan0_latency_1s1r/out
@@ -0,0 +1,5 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=0.500,src=esds] Network update!
+[t=1.000,src=esds] Network update!
+[t=1.333,src=n1] Receive 1 bytes on wlan0
+[t=1.333,src=esds] Simulation ends
diff --git a/tests/mobility_wlan0_latency_1s1r/receiver.py b/tests/mobility_wlan0_latency_1s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/mobility_wlan0_latency_1s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/mobility_wlan0_latency_1s1r/sender.py b/tests/mobility_wlan0_latency_1s1r/sender.py
new file mode 100644
index 0000000..80f0fc2
--- /dev/null
+++ b/tests/mobility_wlan0_latency_1s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("wlan0","Hello World!",1,1)
diff --git a/tests/mobility_wlan0_latency_1s1r/simulator.py b/tests/mobility_wlan0_latency_1s1r/simulator.py
new file mode 100755
index 0000000..525bb66
--- /dev/null
+++ b/tests/mobility_wlan0_latency_1s1r/simulator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+########## Scenario ##########
+# Notations:
+# - Remaining communication duration (last communication ends minus current simulated time) = C
+# - Last communication duration (previous row) = U
+# - Last remaining data size (previous row) = D
+# - Current remaining data (current row) = R
+# - Initial data size (first row) = I
+# - Bandwidth = BW
+# - Latency = L
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | Simulated time(s) | Latency(s) | Bandwidth(bps) | Remaining data (bit) | Communcation duration(s) | Communcation ends at(s) |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+# | 0 | 0 | 8 | 8 | 1 | 1 |
+# | 0.5 | 0.5 | 8 | C/U*D = 4 | R/I * L + R/BW = 0.75 | 1.25 |
+# | 1 | 1 | 8 | C/U*D = 1.33 | R/I * L + R/BW = 0.33 | 1.33 |
+# | 1.33 | 1 | 8 | | | |
+# |-------------------+------------+----------------+----------------------+--------------------------+-------------------------|
+##############################
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+def callback(simulator):
+ simulator.log("Network update!")
+ simulator.update_network(simulator.B,simulator.L+1/2)
+
+s.run(breakpoints_every=1/2,breakpoint_callback=callback)
diff --git a/tests/run.sh b/tests/run.sh
new file mode 100755
index 0000000..8720736
--- /dev/null
+++ b/tests/run.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+BOLD='\033[1m'
+NC='\033[0m' # No Color
+
+wai=$(dirname $(readlink -f "$0")) # Current script directory
+tests=$(find ${wai}/ -maxdepth 1 -mindepth 1 -type d) # Find tests
+out=$(mktemp)
+test_timeout=20
+
+for test in ${tests}
+do
+ printf "%-50s%s %s" "- $(basename $test)" "=>"
+ cd $test
+ timeout $test_timeout ./simulator.py &> "$out"
+
+ # Ensure timeout
+ if [ $? -eq 124 ]
+ then
+ echo -e "${RED}${BOLD}failed${NC}"
+ echo "------------- Test timeout (should not exceed ${test_timeout}s) -------------"
+ cat "$out";
+ rm "$out"
+ exit 2
+ fi
+
+ # Ensure test output
+ if $(diff "$out" ./out &>/dev/null)
+ then
+ echo -e "${GREEN}${BOLD}passed${NC}"
+ else
+ echo -e "${RED}${BOLD}failed${NC}"
+ echo "------------- Expected -------------"
+ cat out
+ echo "------------- Got -------------"
+ cat "$out";
+ rm "$out"
+ exit 1
+ fi
+
+ # Prepare for next test
+ cd - &>/dev/null
+done
+
+rm "$out"
diff --git a/tests/simple_breakpoints_auto_1n/node.py b/tests/simple_breakpoints_auto_1n/node.py
new file mode 100644
index 0000000..e812c01
--- /dev/null
+++ b/tests/simple_breakpoints_auto_1n/node.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+
+def execute(api):
+ if api.node_id == 0:
+ api.send("eth0","Hello",5,1)
+ else:
+ api.receive("eth0")
+
diff --git a/tests/simple_breakpoints_auto_1n/out b/tests/simple_breakpoints_auto_1n/out
new file mode 100644
index 0000000..e8fce88
--- /dev/null
+++ b/tests/simple_breakpoints_auto_1n/out
@@ -0,0 +1,9 @@
+[t=0.000,src=n0] Send 5 bytes to n1 on eth0
+[t=3.300,src=esds] Hello Callback!
+[t=6.600,src=esds] Hello Callback!
+[t=9.900,src=esds] Hello Callback!
+[t=13.200,src=esds] Hello Callback!
+[t=16.500,src=esds] Hello Callback!
+[t=19.800,src=esds] Hello Callback!
+[t=20.000,src=n1] Receive 5 bytes on eth0
+[t=20.000,src=esds] Simulation ends
diff --git a/tests/simple_breakpoints_auto_1n/simulator.py b/tests/simple_breakpoints_auto_1n/simulator.py
new file mode 100755
index 0000000..c5e3b02
--- /dev/null
+++ b/tests/simple_breakpoints_auto_1n/simulator.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+n=2
+B=np.full((2,2),n)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+
+def callback(simulator):
+ simulator.log("Hello Callback!")
+
+s.run(breakpoints_every=3.3,breakpoint_callback=callback)
diff --git a/tests/simple_breakpoints_manual_1n/node.py b/tests/simple_breakpoints_manual_1n/node.py
new file mode 100644
index 0000000..f8c5633
--- /dev/null
+++ b/tests/simple_breakpoints_manual_1n/node.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("Running")
diff --git a/tests/simple_breakpoints_manual_1n/out b/tests/simple_breakpoints_manual_1n/out
new file mode 100644
index 0000000..40c44b7
--- /dev/null
+++ b/tests/simple_breakpoints_manual_1n/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Running
+[t=0.000,src=n1] Running
+[t=1.000,src=esds] Hello Callback!
+[t=2.000,src=esds] Hello Callback!
+[t=3.000,src=esds] Hello Callback!
+[t=10.000,src=esds] Hello Callback!
+[t=10.000,src=esds] Simulation ends
diff --git a/tests/simple_breakpoints_manual_1n/simulator.py b/tests/simple_breakpoints_manual_1n/simulator.py
new file mode 100755
index 0000000..a0d0d2b
--- /dev/null
+++ b/tests/simple_breakpoints_manual_1n/simulator.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+n=2
+B=np.full((n,n),n)
+L=np.full((n,n),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+
+def callback(simulator):
+ simulator.log("Hello Callback!")
+
+s.run(breakpoints=[1,2,3,10],breakpoint_callback=callback)
diff --git a/tests/simple_breakpoints_manual_no_callback_1n/node.py b/tests/simple_breakpoints_manual_no_callback_1n/node.py
new file mode 100644
index 0000000..f8c5633
--- /dev/null
+++ b/tests/simple_breakpoints_manual_no_callback_1n/node.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("Running")
diff --git a/tests/simple_breakpoints_manual_no_callback_1n/out b/tests/simple_breakpoints_manual_no_callback_1n/out
new file mode 100644
index 0000000..defce2c
--- /dev/null
+++ b/tests/simple_breakpoints_manual_no_callback_1n/out
@@ -0,0 +1,3 @@
+[t=0.000,src=n0] Running
+[t=0.000,src=n1] Running
+[t=10.000,src=esds] Simulation ends
diff --git a/tests/simple_breakpoints_manual_no_callback_1n/simulator.py b/tests/simple_breakpoints_manual_no_callback_1n/simulator.py
new file mode 100755
index 0000000..93b1fef
--- /dev/null
+++ b/tests/simple_breakpoints_manual_no_callback_1n/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+n=2
+B=np.full((n,n),n)
+L=np.full((n,n),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+
+s.run(breakpoints=[1,2,3,10])
diff --git a/tests/simple_log_5n/node.py b/tests/simple_log_5n/node.py
new file mode 100644
index 0000000..053c9fd
--- /dev/null
+++ b/tests/simple_log_5n/node.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("A")
+ api.log("B")
+ api.log("C")
+ api.log("D")
diff --git a/tests/simple_log_5n/out b/tests/simple_log_5n/out
new file mode 100644
index 0000000..7d89c56
--- /dev/null
+++ b/tests/simple_log_5n/out
@@ -0,0 +1,21 @@
+[t=0.000,src=n0] A
+[t=0.000,src=n0] B
+[t=0.000,src=n0] C
+[t=0.000,src=n0] D
+[t=0.000,src=n1] A
+[t=0.000,src=n1] B
+[t=0.000,src=n1] C
+[t=0.000,src=n1] D
+[t=0.000,src=n2] A
+[t=0.000,src=n2] B
+[t=0.000,src=n2] C
+[t=0.000,src=n2] D
+[t=0.000,src=n3] A
+[t=0.000,src=n3] B
+[t=0.000,src=n3] C
+[t=0.000,src=n3] D
+[t=0.000,src=n4] A
+[t=0.000,src=n4] B
+[t=0.000,src=n4] C
+[t=0.000,src=n4] D
+[t=0.000,src=esds] Simulation ends
diff --git a/tests/simple_log_5n/simulator.py b/tests/simple_log_5n/simulator.py
new file mode 100755
index 0000000..184340a
--- /dev/null
+++ b/tests/simple_log_5n/simulator.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((5,5),5)
+L=np.full((5,5),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+s.create_node("node")
+s.create_node("node")
+s.create_node("node")
+
+s.run()
diff --git a/tests/simple_read_clock_2n/node.py b/tests/simple_read_clock_2n/node.py
new file mode 100644
index 0000000..79a0b78
--- /dev/null
+++ b/tests/simple_read_clock_2n/node.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("Clock is {}s".format(api.read("clock")))
+ api.wait(5698.1256)
+ api.log("Clock is {}s".format(api.read("clock")))
+ api.log("Clock is {}s".format(api.read("clock")))
diff --git a/tests/simple_read_clock_2n/out b/tests/simple_read_clock_2n/out
new file mode 100644
index 0000000..4fa7877
--- /dev/null
+++ b/tests/simple_read_clock_2n/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Clock is 0s
+[t=0.000,src=n1] Clock is 0s
+[t=5698.126,src=n0] Clock is 5698.1256s
+[t=5698.126,src=n0] Clock is 5698.1256s
+[t=5698.126,src=n1] Clock is 5698.1256s
+[t=5698.126,src=n1] Clock is 5698.1256s
+[t=5698.126,src=esds] Simulation ends
diff --git a/tests/simple_read_clock_2n/simulator.py b/tests/simple_read_clock_2n/simulator.py
new file mode 100755
index 0000000..b2c042c
--- /dev/null
+++ b/tests/simple_read_clock_2n/simulator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((2,2),2)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+
+s.run()
diff --git a/tests/simple_read_eth0_ncom_2s1r/out b/tests/simple_read_eth0_ncom_2s1r/out
new file mode 100644
index 0000000..087e62b
--- /dev/null
+++ b/tests/simple_read_eth0_ncom_2s1r/out
@@ -0,0 +1,9 @@
+[t=0.000,src=n2] eth0 is 0
+[t=624.000,src=n2] eth0 is 0
+[t=1248.000,src=n0] Send 50 bytes to n2 on eth0
+[t=1249.000,src=n2] eth0 is 1
+[t=1249.000,src=n1] Send 50 bytes to n2 on eth0
+[t=1250.000,src=n2] eth0 is 2
+[t=1513.667,src=n2] Receive 50 bytes on eth0
+[t=1514.667,src=n2] Receive 50 bytes on eth0
+[t=1514.667,src=esds] Simulation ends
diff --git a/tests/simple_read_eth0_ncom_2s1r/receiver.py b/tests/simple_read_eth0_ncom_2s1r/receiver.py
new file mode 100644
index 0000000..819fc84
--- /dev/null
+++ b/tests/simple_read_eth0_ncom_2s1r/receiver.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("eth0 is {}".format(api.read("eth0_ncom")))
+ api.wait(624)
+ api.log("eth0 is {}".format(api.read("eth0_ncom")))
+ api.wait(624)
+ # Now we are at 624*2=1248 (first sender start a communication)
+ api.wait(1) # Let the communication starts
+ api.log("eth0 is {}".format(api.read("eth0_ncom"))) # Should print 1
+ api.wait(1) # Now second sender start a communication
+ api.log("eth0 is {}".format(api.read("eth0_ncom"))) # Should print 2
diff --git a/tests/simple_read_eth0_ncom_2s1r/sender.py b/tests/simple_read_eth0_ncom_2s1r/sender.py
new file mode 100644
index 0000000..7c9259a
--- /dev/null
+++ b/tests/simple_read_eth0_ncom_2s1r/sender.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.wait(1248)
+ if api.node_id==0:
+ api.send("eth0","hello",50,2)
+ else:
+ api.wait(1)
+ api.send("eth0","hello",50,2)
diff --git a/tests/simple_read_eth0_ncom_2s1r/simulator.py b/tests/simple_read_eth0_ncom_2s1r/simulator.py
new file mode 100755
index 0000000..5294b9d
--- /dev/null
+++ b/tests/simple_read_eth0_ncom_2s1r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),3)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run(interferences=False)
diff --git a/tests/simple_read_wlan0_ncom_2s1r/out b/tests/simple_read_wlan0_ncom_2s1r/out
new file mode 100644
index 0000000..93543eb
--- /dev/null
+++ b/tests/simple_read_wlan0_ncom_2s1r/out
@@ -0,0 +1,11 @@
+[t=0.000,src=n2] wlan0 is 0
+[t=624.000,src=n2] wlan0 is 0
+[t=1248.000,src=n0] Send 50 bytes on wlan0
+[t=1249.000,src=n2] wlan0 is 1
+[t=1249.000,src=n1] Send 50 bytes on wlan0
+[t=1250.000,src=n2] wlan0 is 2
+[t=1381.333,src=n1] Receive 50 bytes on wlan0
+[t=1381.333,src=n2] Receive 50 bytes on wlan0
+[t=1382.333,src=n0] Receive 50 bytes on wlan0
+[t=1382.333,src=n2] Receive 50 bytes on wlan0
+[t=1382.333,src=esds] Simulation ends
diff --git a/tests/simple_read_wlan0_ncom_2s1r/receiver.py b/tests/simple_read_wlan0_ncom_2s1r/receiver.py
new file mode 100644
index 0000000..9f652aa
--- /dev/null
+++ b/tests/simple_read_wlan0_ncom_2s1r/receiver.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("wlan0 is {}".format(api.read("wlan0_ncom")))
+ api.wait(624)
+ api.log("wlan0 is {}".format(api.read("wlan0_ncom")))
+ api.wait(624)
+ # Now we are at 624*2=1248 (first sender start a communication)
+ api.wait(1) # Let the communication starts
+ api.log("wlan0 is {}".format(api.read("wlan0_ncom"))) # Should print 1
+ api.wait(1) # Second sender start a communication
+ api.log("wlan0 is {}".format(api.read("wlan0_ncom"))) # Should print 2
diff --git a/tests/simple_read_wlan0_ncom_2s1r/sender.py b/tests/simple_read_wlan0_ncom_2s1r/sender.py
new file mode 100644
index 0000000..b89e5e2
--- /dev/null
+++ b/tests/simple_read_wlan0_ncom_2s1r/sender.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.wait(1248)
+ if api.node_id==0:
+ api.send("wlan0","hello",50,None)
+ else:
+ api.wait(1)
+ api.send("wlan0","hello",50,None)
diff --git a/tests/simple_read_wlan0_ncom_2s1r/simulator.py b/tests/simple_read_wlan0_ncom_2s1r/simulator.py
new file mode 100755
index 0000000..5294b9d
--- /dev/null
+++ b/tests/simple_read_wlan0_ncom_2s1r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),3)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run(interferences=False)
diff --git a/tests/simple_receivet_eth0_1s1r/out b/tests/simple_receivet_eth0_1s1r/out
new file mode 100644
index 0000000..98756b5
--- /dev/null
+++ b/tests/simple_receivet_eth0_1s1r/out
@@ -0,0 +1,8 @@
+[t=0.000,src=n0] Send 1 bytes to n1 on eth0
+[t=1.000,src=n1] Receive 1 bytes on eth0
+[t=1.000,src=n1] Received: Hello World!
+[t=1.000,src=n0] Send 1 bytes to n1 on eth0
+[t=1.500,src=n1] Receive failed code=-1
+[t=2.000,src=n1] Receive 1 bytes on eth0
+[t=2.000,src=n1] Received: Hello World!
+[t=2.000,src=esds] Simulation ends
diff --git a/tests/simple_receivet_eth0_1s1r/receiver.py b/tests/simple_receivet_eth0_1s1r/receiver.py
new file mode 100644
index 0000000..515ff6a
--- /dev/null
+++ b/tests/simple_receivet_eth0_1s1r/receiver.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+def receivet(node,timeout):
+ ##### Simple receive
+ code, data=node.receivet("eth0",timeout)
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ node.log(msg)
+
+def execute(api):
+ # Should works
+ receivet(api,2)
+ # Should failed
+ receivet(api,0.5) # At t=1.5s
+ # Should works (priorities says that communications should occurs before timeout)
+ receivet(api,0.5) # At t=2s (timeout+receive should occur)
diff --git a/tests/simple_receivet_eth0_1s1r/sender.py b/tests/simple_receivet_eth0_1s1r/sender.py
new file mode 100644
index 0000000..0b6bdb6
--- /dev/null
+++ b/tests/simple_receivet_eth0_1s1r/sender.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,1)
+ api.send("eth0","Hello World!",1,1)
+
diff --git a/tests/simple_receivet_eth0_1s1r/simulator.py b/tests/simple_receivet_eth0_1s1r/simulator.py
new file mode 100755
index 0000000..5ec9a25
--- /dev/null
+++ b/tests/simple_receivet_eth0_1s1r/simulator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run(debug=True)
diff --git a/tests/simple_receivet_eth0_1s1r/yoctosim.debug b/tests/simple_receivet_eth0_1s1r/yoctosim.debug
new file mode 100644
index 0000000..9d879f4
--- /dev/null
+++ b/tests/simple_receivet_eth0_1s1r/yoctosim.debug
@@ -0,0 +1,43 @@
+Python version 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0]
+Simulation started at 1654802960.7880125
+Number of nodes is 2
+Manual breakpoints list: []
+Breakpoints every Nones
+-----------------------------------------------
+Started since 0.02s
+Simulated time 0.000s (or more precisely 0s)
+Node number per state: pending=1 request=1
+Node sharing: n1=1
+Ids of node in timeout mode:
+Sorted events list:
+[[0 1.0 array([0, 1, 'eth0', 'Hello World!', 1, 1.0, 1, 0], dtype=object)
+ 1]
+ [1 2 array(1, dtype=object) 3]]
+-----------------------------------------------
+Started since 0.04s
+Simulated time 1.000s (or more precisely 1.0s)
+Node number per state: pending=1 request=1
+Node sharing: n1=1
+Ids of node in timeout mode:
+Sorted events list:
+[[1 1.5 array(1, dtype=object) 3]
+ [0 2.0
+ array([0, 1, 'eth0', 'Hello World!', 1, 1.0, 1, 1.0], dtype=object) 1]]
+-----------------------------------------------
+Started since 0.06s
+Simulated time 1.500s (or more precisely 1.5s)
+Node number per state: pending=1 request=1
+Node sharing: n1=1
+Ids of node in timeout mode:
+Sorted events list:
+[[0 2.0
+ array([0, 1, 'eth0', 'Hello World!', 1, 1.0, 1, 1.0], dtype=object) 1]
+ [1 2.0 array(1, dtype=object) 3]]
+-----------------------------------------------
+Started since 0.08s
+Simulated time 2.000s (or more precisely 2.0s)
+Node number per state: terminated=2
+Node sharing:
+Ids of node in timeout mode:
+Sorted events list:
+[]
diff --git a/tests/simple_send_eth0_1s1r/out b/tests/simple_send_eth0_1s1r/out
new file mode 100644
index 0000000..1634d95
--- /dev/null
+++ b/tests/simple_send_eth0_1s1r/out
@@ -0,0 +1,11 @@
+[t=0.000,src=n0] Send 1 bytes to n1 on eth0
+[t=1.000,src=n1] Receive 1 bytes on eth0
+[t=1.000,src=n1] Received: Hello World!
+[t=1.000,src=n0] Send 1 bytes to n1 on eth0
+[t=2.000,src=n1] Receive 1 bytes on eth0
+[t=3.000,src=n0] Send 1 bytes to n1 on eth0
+[t=3.000,src=n1] Received: Hello World!
+[t=3.000,src=n1] Turned off
+[t=4.000,src=n1] Turned on
+[t=5.000,src=n1] Receive failed code=-1
+[t=5.000,src=esds] Simulation ends
diff --git a/tests/simple_send_eth0_1s1r/receiver.py b/tests/simple_send_eth0_1s1r/receiver.py
new file mode 100644
index 0000000..96305bf
--- /dev/null
+++ b/tests/simple_send_eth0_1s1r/receiver.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+def execute(api):
+ ##### Simple receive
+ code, data=api.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ api.log(msg)
+ ##### Test if we still receive the data when we are not receiving
+ api.wait(2)
+ code, data=api.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ api.log(msg)
+ ##### Ensure data is not receive when turned off
+ api.turn_off()
+ api.wait(1)
+ api.turn_on()
+ code, data=api.receivet("eth0",1)
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ api.log(msg)
+
diff --git a/tests/simple_send_eth0_1s1r/sender.py b/tests/simple_send_eth0_1s1r/sender.py
new file mode 100644
index 0000000..9f4aa85
--- /dev/null
+++ b/tests/simple_send_eth0_1s1r/sender.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World!",1,1)
+ api.send("eth0","Hello World!",1,1)
+ api.wait(1) # Goto 3 seconds
+ api.send("eth0","Hello World!",1,1)
diff --git a/tests/simple_send_eth0_1s1r/simulator.py b/tests/simple_send_eth0_1s1r/simulator.py
new file mode 100755
index 0000000..f5f6ca6
--- /dev/null
+++ b/tests/simple_send_eth0_1s1r/simulator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_send_eth0_2s1r/out b/tests/simple_send_eth0_2s1r/out
new file mode 100644
index 0000000..4a5ebb8
--- /dev/null
+++ b/tests/simple_send_eth0_2s1r/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Send 1 bytes to n2 on eth0
+[t=0.000,src=n1] Send 1 bytes to n2 on eth0
+[t=2.000,src=n2] Receive 1 bytes on eth0
+[t=2.000,src=n2] Received: Hello World from 0!
+[t=2.000,src=n2] Receive 1 bytes on eth0
+[t=2.000,src=n2] Received: Hello World from 1!
+[t=2.000,src=esds] Simulation ends
diff --git a/tests/simple_send_eth0_2s1r/receiver.py b/tests/simple_send_eth0_2s1r/receiver.py
new file mode 100644
index 0000000..2f268fd
--- /dev/null
+++ b/tests/simple_send_eth0_2s1r/receiver.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+def execute(api):
+ ##### Simple receive from node 0
+ code, data=api.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ api.log(msg)
+
+ ##### Simple receive from node 1
+ code, data=api.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ api.log(msg)
+
diff --git a/tests/simple_send_eth0_2s1r/sender.py b/tests/simple_send_eth0_2s1r/sender.py
new file mode 100644
index 0000000..90daae5
--- /dev/null
+++ b/tests/simple_send_eth0_2s1r/sender.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("eth0","Hello World from {}!".format(api.node_id),1,2)
+
diff --git a/tests/simple_send_eth0_2s1r/simulator.py b/tests/simple_send_eth0_2s1r/simulator.py
new file mode 100755
index 0000000..9faaf3d
--- /dev/null
+++ b/tests/simple_send_eth0_2s1r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_send_eth0_3s1r/out b/tests/simple_send_eth0_3s1r/out
new file mode 100644
index 0000000..8cf82a6
--- /dev/null
+++ b/tests/simple_send_eth0_3s1r/out
@@ -0,0 +1,40 @@
+[t=0.000,src=n0] Send 1 bytes to n3 on eth0
+[t=0.000,src=n1] Send 1 bytes to n3 on eth0
+[t=0.000,src=n2] Send 1 bytes to n3 on eth0
+[t=3.000,src=n3] Receive 1 bytes on eth0
+[t=3.000,src=n3] Received: Hello World from 0!
+[t=3.000,src=n3] Receive 1 bytes on eth0
+[t=3.000,src=n3] Received: Hello World from 1!
+[t=3.000,src=n3] Receive 1 bytes on eth0
+[t=3.000,src=n3] Received: Hello World from 2!
+[t=3.000,src=n0] Send 2 bytes to n3 on eth0
+[t=3.000,src=n1] Send 1 bytes to n3 on eth0
+[t=3.000,src=n2] Send 1 bytes to n3 on eth0
+[t=6.000,src=n3] Receive 1 bytes on eth0
+[t=6.000,src=n3] Received: Hello World from 1!
+[t=6.000,src=n3] Receive 1 bytes on eth0
+[t=6.000,src=n3] Received: Hello World from 2!
+[t=7.000,src=n3] Receive 2 bytes on eth0
+[t=7.000,src=n3] Received: Hello World from 0!
+[t=7.000,src=n0] Send 2 bytes to n3 on eth0
+[t=7.000,src=n1] Send 2 bytes to n3 on eth0
+[t=7.000,src=n2] Send 1 bytes to n3 on eth0
+[t=10.000,src=n3] Receive 1 bytes on eth0
+[t=10.000,src=n3] Received: Hello World from 2!
+[t=12.000,src=n3] Receive 2 bytes on eth0
+[t=12.000,src=n3] Received: Hello World from 0!
+[t=12.000,src=n3] Receive 2 bytes on eth0
+[t=12.000,src=n3] Received: Hello World from 1!
+[t=12.000,src=n0] Send 1 bytes to n3 on eth0
+[t=12.000,src=n1] Send 2 bytes to n3 on eth0
+[t=12.000,src=n2] Send 3 bytes to n3 on eth0
+[t=15.000,src=n3] Receive 1 bytes on eth0
+[t=15.000,src=n3] Received: Hello World from 0!
+[t=17.000,src=n3] Receive 2 bytes on eth0
+[t=17.000,src=n3] Received: Hello World from 1!
+[t=18.000,src=n3] Receive 3 bytes on eth0
+[t=18.000,src=n3] Received: Hello World from 2!
+[t=18.000,src=n0] Send 5 bytes to n3 on eth0
+[t=23.000,src=n3] Receive 5 bytes on eth0
+[t=23.000,src=n3] Received: Hello World from 0!
+[t=23.000,src=esds] Simulation ends
diff --git a/tests/simple_send_eth0_3s1r/receiver.py b/tests/simple_send_eth0_3s1r/receiver.py
new file mode 100644
index 0000000..4516d7a
--- /dev/null
+++ b/tests/simple_send_eth0_3s1r/receiver.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+def receive(node, n):
+ for i in range(0,n):
+ code, data=node.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ node.log(msg)
+
+def execute(api):
+ # Receive the first 3 send that should end at 3s
+ receive(api,3)
+ # Receive the first 3 send that should end at 7s
+ receive(api,3)
+ # Receive the first 3 send that should end at 12s
+ receive(api,3)
+ # Receive the first 3 send that should end at 18s
+ receive(api,3)
+ # Should ends at 23s
+ receive(api,1)
diff --git a/tests/simple_send_eth0_3s1r/sender.py b/tests/simple_send_eth0_3s1r/sender.py
new file mode 100644
index 0000000..d3c7bf2
--- /dev/null
+++ b/tests/simple_send_eth0_3s1r/sender.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+# Node that bandwidths at setup in a way that 1 byte is send in 1 seconds with no sharing
+
+def execute(api):
+ # Should be completed at 3s (bandwidth divided by 3)
+ api.send("eth0","Hello World from {}!".format(api.node_id),1,3) # Shoud lasts 3s
+
+ # These send should start at 3s and be completed at 7s
+ if api.node_id==0:
+ api.send("eth0","Hello World from {}!".format(api.node_id),2,3) # Should lasts 3s + 1s = 4s
+ else:
+ api.send("eth0","Hello World from {}!".format(api.node_id),1,3) # Should lasts 3s
+ api.wait(1) # Sync with node 0 at 7s
+
+ # Those sends should start at 7s and be completed at 12s
+ if api.node_id<=1:
+ api.send("eth0","Hello World from {}!".format(api.node_id),2,3) # Should last 3s + 2s = 5s
+ # Completed at 12s since 3 nodes are sharing the bandwidth up to 10s
+ # then the 2 two remaining node send their last byte up to 12s
+ else:
+ api.send("eth0","Hello World from {}!".format(api.node_id),1,3) # Should last 3s
+ # Completed at 10s (3 nodes are sharing the bandwidth)
+ api.wait(2) # Sync with node 0-1 at 12s
+
+ # Should start at 12s
+ # Node 0 sends 1 byte, node 1 sends 2 byte and node 2 sends 3
+ # These send should end at 18s
+ api.send("eth0","Hello World from {}!".format(api.node_id),api.node_id+1,3) # Should lasts 3s, 5s and 6s
+
+ # Finally a single send from node 0
+ if api.node_id==0:
+ api.wait(3) # Since node 0 send ends at 15s we sync it to 18s
+ api.send("eth0","Hello World from {}!".format(api.node_id),5,3) # Should takes 5 seconds (ends at 23s)
+
+
+
diff --git a/tests/simple_send_eth0_3s1r/simulator.py b/tests/simple_send_eth0_3s1r/simulator.py
new file mode 100755
index 0000000..4a5aede
--- /dev/null
+++ b/tests/simple_send_eth0_3s1r/simulator.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((4,4),8)
+L=np.full((4,4),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_send_wlan0_1s2r/out b/tests/simple_send_wlan0_1s2r/out
new file mode 100644
index 0000000..98cc425
--- /dev/null
+++ b/tests/simple_send_wlan0_1s2r/out
@@ -0,0 +1,16 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=1.000,src=n1] Receive 1 bytes on wlan0
+[t=1.000,src=n1] Received: Hello World!
+[t=1.000,src=n2] Receive 1 bytes on wlan0
+[t=1.000,src=n2] Received: Hello World!
+[t=1.000,src=n2] Turned off
+[t=1.000,src=n0] Send 1 bytes on wlan0
+[t=2.000,src=n1] Receive 1 bytes on wlan0
+[t=2.000,src=n1] Received: Hello World!
+[t=2.000,src=n2] Turned on
+[t=2.000,src=n0] Send 1 bytes on wlan0
+[t=2.500,src=n2] Turned off
+[t=3.000,src=n1] Receive 1 bytes on wlan0
+[t=3.000,src=n1] Received: Hello World!
+[t=3.000,src=n2] Turned on
+[t=3.000,src=esds] Simulation ends
diff --git a/tests/simple_send_wlan0_1s2r/receiver.py b/tests/simple_send_wlan0_1s2r/receiver.py
new file mode 100644
index 0000000..5b86500
--- /dev/null
+++ b/tests/simple_send_wlan0_1s2r/receiver.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+def receive(node):
+ ##### Simple receive
+ code, data=node.receive("wlan0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ node.log(msg)
+
+
+def execute(api):
+ # Should works for all receivers
+ receive(api)
+
+ if api.node_id == 1:
+ receive(api) # Should works
+ else:
+ api.turn_off()
+ api.wait(1) # Node 2 should not receive anything during 1s
+ api.turn_on()
+
+
+ if api.node_id == 1:
+ receive(api) # Should works
+ else:
+ api.wait(0.5) # Check if started communication get cancelled on turning off
+ api.turn_off() # Node 2 should not receive anything
+ api.wait(0.5) # Node 2 should not receive anything during 0.5s
+ api.turn_on()
+
diff --git a/tests/simple_send_wlan0_1s2r/sender.py b/tests/simple_send_wlan0_1s2r/sender.py
new file mode 100644
index 0000000..ef1517b
--- /dev/null
+++ b/tests/simple_send_wlan0_1s2r/sender.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("wlan0","Hello World!",1,1)
+ api.send("wlan0","Hello World!",1,1)
+ api.send("wlan0","Hello World!",1,1)
diff --git a/tests/simple_send_wlan0_1s2r/simulator.py b/tests/simple_send_wlan0_1s2r/simulator.py
new file mode 100755
index 0000000..3f53e63
--- /dev/null
+++ b/tests/simple_send_wlan0_1s2r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_send_wlan0_2s1r/out b/tests/simple_send_wlan0_2s1r/out
new file mode 100644
index 0000000..788ee42
--- /dev/null
+++ b/tests/simple_send_wlan0_2s1r/out
@@ -0,0 +1,6 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=0.000,src=n1] Send 1 bytes on wlan0
+[t=0.000,src=n0] Interferences on wlan0
+[t=0.000,src=n1] Interferences on wlan0
+[t=0.000,src=n2] Interferences on wlan0
+[t=1.000,src=esds] Simulation ends
diff --git a/tests/simple_send_wlan0_2s1r/receiver.py b/tests/simple_send_wlan0_2s1r/receiver.py
new file mode 100644
index 0000000..0b48f12
--- /dev/null
+++ b/tests/simple_send_wlan0_2s1r/receiver.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+def execute(api):
+ pass
+
diff --git a/tests/simple_send_wlan0_2s1r/sender.py b/tests/simple_send_wlan0_2s1r/sender.py
new file mode 100644
index 0000000..80f0fc2
--- /dev/null
+++ b/tests/simple_send_wlan0_2s1r/sender.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.send("wlan0","Hello World!",1,1)
diff --git a/tests/simple_send_wlan0_2s1r/simulator.py b/tests/simple_send_wlan0_2s1r/simulator.py
new file mode 100755
index 0000000..9faaf3d
--- /dev/null
+++ b/tests/simple_send_wlan0_2s1r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_sendt_eth0_1s1r/out b/tests/simple_sendt_eth0_1s1r/out
new file mode 100644
index 0000000..e6d70d5
--- /dev/null
+++ b/tests/simple_sendt_eth0_1s1r/out
@@ -0,0 +1,11 @@
+[t=0.000,src=n0] Send 1 bytes to n1 on eth0
+[t=1.000,src=n1] Receive 1 bytes on eth0
+[t=1.000,src=n1] Received: Hello World!
+[t=1.000,src=n0] Send worked!
+[t=1.000,src=n0] Send 1 bytes to n1 on eth0
+[t=1.500,src=n0] Send failed
+[t=1.500,src=n0] Send 1 bytes to n1 on eth0
+[t=2.500,src=n1] Receive 1 bytes on eth0
+[t=2.500,src=n1] Received: Hello World!
+[t=2.500,src=n0] Send worked!
+[t=2.500,src=esds] Simulation ends
diff --git a/tests/simple_sendt_eth0_1s1r/receiver.py b/tests/simple_sendt_eth0_1s1r/receiver.py
new file mode 100644
index 0000000..0d8561e
--- /dev/null
+++ b/tests/simple_sendt_eth0_1s1r/receiver.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+def receive(node):
+ ##### Simple receive
+ code, data=node.receive("eth0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ node.log(msg)
+
+def execute(api):
+ receive(api)
+ receive(api)
diff --git a/tests/simple_sendt_eth0_1s1r/sender.py b/tests/simple_sendt_eth0_1s1r/sender.py
new file mode 100644
index 0000000..ee2ea9d
--- /dev/null
+++ b/tests/simple_sendt_eth0_1s1r/sender.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+def sendt(node,timeout):
+ code=node.sendt("eth0","Hello World!",1,1,timeout)
+ msg="Send worked!" if code == 0 else "Send failed"
+ node.log(msg)
+
+def execute(api):
+ # Should work
+ sendt(api,2)
+ # Should not work
+ sendt(api,0.5)
+ # Should work
+ sendt(api,2)
diff --git a/tests/simple_sendt_eth0_1s1r/simulator.py b/tests/simple_sendt_eth0_1s1r/simulator.py
new file mode 100755
index 0000000..f5f6ca6
--- /dev/null
+++ b/tests/simple_sendt_eth0_1s1r/simulator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((2,2),8)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_sendt_wlan0_1s2r/out b/tests/simple_sendt_wlan0_1s2r/out
new file mode 100644
index 0000000..5a4f82c
--- /dev/null
+++ b/tests/simple_sendt_wlan0_1s2r/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Send 1 bytes on wlan0
+[t=1.000,src=n1] Receive 1 bytes on wlan0
+[t=1.000,src=n1] Received: Hello World!
+[t=1.000,src=n2] Receive 1 bytes on wlan0
+[t=1.000,src=n2] Received: Hello World!
+[t=1.000,src=n0] Send 1 bytes on wlan0
+[t=1.500,src=esds] Simulation ends
diff --git a/tests/simple_sendt_wlan0_1s2r/receiver.py b/tests/simple_sendt_wlan0_1s2r/receiver.py
new file mode 100644
index 0000000..eca49ad
--- /dev/null
+++ b/tests/simple_sendt_wlan0_1s2r/receiver.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+def receive(node):
+ ##### Simple receive
+ code, data=node.receive("wlan0")
+ msg="Received: "+data if code == 0 else "Receive failed code="+str(code)
+ node.log(msg)
+
+
+def execute(api):
+ # Should works for all receivers
+ receive(api)
+
diff --git a/tests/simple_sendt_wlan0_1s2r/sender.py b/tests/simple_sendt_wlan0_1s2r/sender.py
new file mode 100644
index 0000000..fe2dc1a
--- /dev/null
+++ b/tests/simple_sendt_wlan0_1s2r/sender.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+
+def execute(api):
+ # Should works
+ api.sendt("wlan0","Hello World!",1,1,2)
+ # Should not work
+ api.sendt("wlan0","Hello World!",1,1,0.5)
+
diff --git a/tests/simple_sendt_wlan0_1s2r/simulator.py b/tests/simple_sendt_wlan0_1s2r/simulator.py
new file mode 100755
index 0000000..3f53e63
--- /dev/null
+++ b/tests/simple_sendt_wlan0_1s2r/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),8)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("sender")
+s.create_node("receiver")
+s.create_node("receiver")
+
+s.run()
diff --git a/tests/simple_wait_2n/node.py b/tests/simple_wait_2n/node.py
new file mode 100644
index 0000000..86b06de
--- /dev/null
+++ b/tests/simple_wait_2n/node.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+def execute(api):
+ api.log("Before wait")
+ api.wait(2)
+ api.log("After wait")
+ api.log("Before wait")
+ api.wait(3)
+ api.log("After wait")
diff --git a/tests/simple_wait_2n/out b/tests/simple_wait_2n/out
new file mode 100644
index 0000000..8a694cc
--- /dev/null
+++ b/tests/simple_wait_2n/out
@@ -0,0 +1,9 @@
+[t=0.000,src=n0] Before wait
+[t=0.000,src=n1] Before wait
+[t=2.000,src=n0] After wait
+[t=2.000,src=n0] Before wait
+[t=2.000,src=n1] After wait
+[t=2.000,src=n1] Before wait
+[t=5.000,src=n0] After wait
+[t=5.000,src=n1] After wait
+[t=5.000,src=esds] Simulation ends
diff --git a/tests/simple_wait_2n/simulator.py b/tests/simple_wait_2n/simulator.py
new file mode 100755
index 0000000..b2c042c
--- /dev/null
+++ b/tests/simple_wait_2n/simulator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((2,2),2)
+L=np.full((2,2),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+
+s.run()
diff --git a/tests/simple_wait_end_3n/node.py b/tests/simple_wait_end_3n/node.py
new file mode 100644
index 0000000..a8fbcd7
--- /dev/null
+++ b/tests/simple_wait_end_3n/node.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+def execute(api):
+ wait=api.node_id
+ api.log("Before wait for "+str(wait)+"s")
+ api.wait(wait) # Since 3 nodes max(wait)==2
+
+ # Ensure that wait end return back when simulation ends
+ api.wait_end()
+ api.log("Terminated")
diff --git a/tests/simple_wait_end_3n/out b/tests/simple_wait_end_3n/out
new file mode 100644
index 0000000..321b0c2
--- /dev/null
+++ b/tests/simple_wait_end_3n/out
@@ -0,0 +1,7 @@
+[t=0.000,src=n0] Before wait for 0s
+[t=0.000,src=n1] Before wait for 1s
+[t=0.000,src=n2] Before wait for 2s
+[t=2.000,src=n0] Terminated
+[t=2.000,src=n1] Terminated
+[t=2.000,src=n2] Terminated
+[t=2.000,src=esds] Simulation ends
diff --git a/tests/simple_wait_end_3n/simulator.py b/tests/simple_wait_end_3n/simulator.py
new file mode 100755
index 0000000..d4c9ec4
--- /dev/null
+++ b/tests/simple_wait_end_3n/simulator.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Load ESDS
+import sys
+sys.path.append("../../")
+import esds
+import numpy as np
+
+B=np.full((3,3),2)
+L=np.full((3,3),0)
+s=esds.Simulator(B,L)
+
+s.create_node("node")
+s.create_node("node")
+s.create_node("node")
+
+s.run()