Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dias-ko committed May 20, 2024
0 parents commit 5ee8f92
Show file tree
Hide file tree
Showing 8 changed files with 1,625 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.venv*/
venv*/
__pycache__/
flask_session/
terrains/
imgs/
test.py
LoRaWAN-US915.pl
1,095 changes: 1,095 additions & 0 deletions LoRaWAN.pl

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions draw_terrain.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/perl -w
#
# Script to create png file from 2d terrain data
#
# copied from https://github.com/rainbow-src/sensors/tree/master/terrain%20generators

use GD;
use strict;

my ($display_x, $display_y) = (800, 800); # 800x800 pixel display pane

my ($terrain, $norm_x, $norm_y) = (0, 0, 0);

my @sensors = ();
my @gws = ();

die "usage: $0 <terrain_file.txt> <output.png>\n"
unless (@ARGV == 2);

my $terrain_file = $ARGV[0];
my $output_file = $ARGV[1];


# COLLECT INFO FROM INPUT FILE

open(FH, "<$terrain_file") or
die "Error: could not open terrain file $terrain_file\n";

while(<FH>){
chomp;
if (/^# stats: (.*)/){
my $stats_line = $1;
if ($stats_line =~ /terrain=([0-9]+\.[0-9]+)m\^2/){
$terrain = $1;
}
$norm_x = sqrt($terrain);
$norm_y = sqrt($terrain);
} elsif (/^# node coords: (.*)/){
my $sensor_coord = $1;
my @coords = split(/\] /, $sensor_coord);
@sensors = map { /([0-9]+) \[([0-9]+\.[0-9]+) ([0-9]+\.[0-9]+)/; [$1, $2, $3]; } @coords;
} elsif (/^# gateway coords: (.*)/){
my $sensor_coord = $1;
my @coords = split(/\] /, $sensor_coord);
@gws = map { /([A-Z]+) \[([0-9]+\.[0-9]+) ([0-9]+\.[0-9]+)/; [$1, $2, $3]; } @coords;
}
}
close(FH);


### GENERATE SVG IMAGE OF TERRAIN ###

my $im = new GD::Image->new($display_x, $display_y);
my $white = $im->colorAllocate(255,255,255);
my $black = $im->colorAllocate(0,0,0);
my $red = $im->colorAllocate(255,0,0);

foreach my $po (@sensors){
my ($s, $x, $y) = @$po;
($x, $y) = (int(($x * $display_x)/$norm_x), int(($y * $display_y)/$norm_y));
$im->rectangle($x-2, $y-2, $x+2, $y+2, $black);
# $im->string(gdSmallFont,$x-2,$y-20,$s,$white);
}
#$im->filledRectangle(3-3, $display_y/2-3, 3+3, $display_y/2+3, $black);
foreach my $po (@gws){
my ($s, $x, $y) = @$po;
($x, $y) = (int(($x * $display_x)/$norm_x), int(($y * $display_y)/$norm_y));
$im->rectangle($x-5, $y-5, $x+5, $y+5, $red);
$im->string(gdGiantFont,$x-2,$y-20,$s,$white);
}


open(FILEOUT, ">$output_file") or
die "could not open file $output_file for writing!";
binmode FILEOUT;
print FILEOUT $im->png;
close FILEOUT;
64 changes: 64 additions & 0 deletions generate_terrain.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/perl -w
#
# Script to create a 2D terrain of nodes
#
# modified copy of https://github.com/rainbow-src/sensors/tree/master/terrain%20generators

use strict;
use Math::Random;

(@ARGV==3) || die "usage: $0 <terrain_side_size_(m)> <num_of_nodes> <num_of_gateways>\ne.g. $0 100 500 10\n";

my $tx = $ARGV[0];
my $nodes = $ARGV[1];
my $gws = $ARGV[2];

($tx < 1) && die "grid side must be higher than 1 meters!\n";
($nodes < 1) && die "number of nodes must be higher than 1!\n";

my @sensors;
my @gws;
my %coords;

for(my $i=1; $i<=$nodes; $i++){
my ($x, $y) = (int(rand($tx*10)), int(rand($tx*10)));
($x, $y) = ($x/10, $y/10);
while (exists $coords{$x}{$y}){
($x, $y) = (int(rand($tx*10)), int(rand($tx*10)));
($x, $y) = ($x/10, $y/10);
}
$coords{$x}{$y} = 1;
push(@sensors, [$x, $y]);
}
for(my $i=1; $i<=$gws; $i++){
my ($x, $y) = (int(rand($tx*10)), int(rand($tx*10)));
($x, $y) = ($x/10, $y/10);
while (exists $coords{$x}{$y}){
($x, $y) = (int(rand($tx*10)), int(rand($tx*10)));
($x, $y) = ($x/10, $y/10);
}
$coords{$x}{$y} = 1;
push(@gws, [$x, $y]);
}


printf "# terrain map [%i x %i]\n", $tx, $tx;
print "# node coords:";
my $n = 1;
foreach my $s (@sensors){
my ($x, $y) = @$s;
printf " %s [%.1f %.1f]", $n, $x, $y;
$n++;
}
print "\n";
print "# gateway coords:";
my $l = "A";
foreach my $g (@gws){
my ($x, $y) = @$g;
printf " %s [%.1f %.1f]", $l, $x, $y;
$l++;
}
print "\n";

print "# generated with: $0 ",join(" ",@ARGV),"\n";
printf "# stats: nodes=%i gateways=%i terrain=%.1fm^2 node_sz=%.2fm^2\n", scalar @sensors, scalar @gws, $tx*$tx, 0.1 * 0.1;
122 changes: 122 additions & 0 deletions server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import subprocess, random, datetime, os, re
from flask import Flask, render_template, redirect, url_for, request, session
from flask_bootstrap import Bootstrap5
from flask_session import Session
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import IntegerField, SubmitField, SelectField, BooleanField
from wtforms.validators import DataRequired, NumberRange

app = Flask(__name__, static_folder=os.getcwd())
app.secret_key = 'tO$&!|0wkamvVia0?n$NqIRVWOG'
bootstrap = Bootstrap5(app)
csrf = CSRFProtect(app)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

def parse_res(str):
data = str.decode('utf-8')

result = {}

pattern = re.compile(r'(.+?) = ([\d.]+(?: secs| J| times| bytes|))')
matches = pattern.findall(data)
for match in matches:
key, value = match
result[key.strip()] = value

gw_pattern = re.compile(r'GW (\w+) sent out (\d+) acks and commands')
gw_matches = gw_pattern.findall(data)
gw_list = []
for gw, value in gw_matches:
gw_list.append((f'Gateway {gw}', value))
result['GW'] = gw_list

node_pattern = re.compile(r'# of nodes with SF(\d+): (\d+), Avg retransmissions: ([\d.]+)')
node_matches = node_pattern.findall(data)
sf_list = []
for sf, num_nodes, avg_retrans in node_matches:
sf_list.append((f'SF{sf}', num_nodes, avg_retrans))
result['SF'] = sf_list

return result


class SimulationForm(FlaskForm):
# simulator = SelectField('Protocol', choices=[('eu', 'EU868'), ('us', 'US915')], validators=[DataRequired()])
packets_per_hour = IntegerField('Packets per hour ', validators=[DataRequired(), NumberRange(min=1)], default=10)
auto_simtime = BooleanField('Auto Simulation Time', id="auto_sim")
simulation_time = IntegerField('Simulation Time (s)', default=3600, validators=[DataRequired(), NumberRange(min=3600)], id="sim")
ack_policy = SelectField('Acknowledgement Policy', choices=[('1', 'First-Come First-Served (RCFS)'), ('2', 'Best Received Signal Strength Indicator (RSSI)'), ('3', 'Least busy Gateway')], validators=[DataRequired()])
max_retr = IntegerField('Max Retries', validators=[DataRequired(), NumberRange(min=1, max=8),], default=8)
channels = SelectField('Number of Channels', choices=[('3', '3'), ('8', '8')], validators=[DataRequired()], default='8')
rx2sf = IntegerField('RX2 SF', validators=[DataRequired(), NumberRange(min=7, max=12)], default=7)
fixed_packet_size = BooleanField('Fixed Packet Size', id = "fixed")
packet_size = IntegerField('Average Packet Size', validators=[NumberRange(min=1, max=50)], default=16, id="size")
packet_size_distr = SelectField('Packet Size Distribution', choices=[('normal', 'Normal'), ('uniform', 'Uniform')], id = "distr", default='normal')
confirmed_perc = IntegerField('Percentage of EDs that require an ACK', validators=[DataRequired(), NumberRange(min=0, max=100)], default=100)
submit = SubmitField('Simulate')

class TerrainForm(FlaskForm):
size = IntegerField('Terrain Size', validators=[DataRequired(), NumberRange(min=100)], default="1000")
nodes = IntegerField('Number of Nodes', default="100", validators=[DataRequired(), NumberRange(min=1)])
gateways = IntegerField('Number of Gateways', default="2", validators=[DataRequired(), NumberRange(min=1)])
submit = SubmitField('Generate')

@app.route("/", methods=['GET', 'POST'])
def index():
form = TerrainForm()
message = ""
if form.validate_on_submit():
size = form.size.data
nodes = form.nodes.data
gateways = form.gateways.data
session["id"] = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S.%f")
file = "terrains\\" + session["id"] + ".txt"
img_file = "imgs\\" + session["id"] + ".png"
try:
subprocess.run(["perl", "generate_terrain.pl", str(size), str(nodes), str(gateways), ">", "terrains\\" + session["id"] + ".txt"], shell=True)
subprocess.run(["perl", "draw_terrain.pl", file, img_file], shell=True)
session["image"] = "website/imgs/" + session["id"] + ".png"
except Exception as e:
message = e
return redirect("/simulate")
else:
session["image"] = None
return render_template('index.html', form=form, message=message, tab="Generate")

@app.route("/simulate", methods=['GET', 'POST'])
def simulate():
form = SimulationForm()
message = ""

if form.validate_on_submit():
# form.submit
packets_per_hour = form.packets_per_hour.data
simulation_time = form.simulation_time.data
ack_policy = form.ack_policy.data
max_retr = form.max_retr.data
channels = form.channels.data
rx2sf = form.rx2sf.data
if form.fixed_packet_size.data:
form.packet_size.label.text = "Packet Size"
form.packet_size_distr.render_kw = {'disabled': 'disabled'}
else:
form.packet_size.label.text = "Average Packet Size"
fixed_packet_size = 0 if form.fixed_packet_size.data == False else 1
packet_size_distr = form.packet_size_distr.data
auto_simtime = 0 if form.auto_simtime.data == False else 1
packet_size = form.packet_size.data
confirmed_perc = form.confirmed_perc.data / 100
# device = "LoRaWAN.pl" if form.simulator.data == 'eu' else "LoRaWAN-US915.pl"
if session["id"]:
file = "terrains\\" + session["id"] + ".txt"
if os.path.exists(file):
try:
res = subprocess.check_output(["perl", "LoRaWAN.pl", str(packets_per_hour), str(simulation_time), str(ack_policy), "terrains\\" + session["id"] + ".txt", str(max_retr), str(channels), str(rx2sf), str(fixed_packet_size), str(packet_size_distr), str(auto_simtime), str(packet_size), str(confirmed_perc)], shell=True)
session["result"] = parse_res(res)
except Exception as e:
message = e
else:
session["result"] = None
return render_template('index.html', form=form, message=message, result=session["result"], img_file=session["image"], tab="Simulate")
27 changes: 27 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LoraWAN simulation</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/5.2/assets/css/docs.css" rel="stylesheet">
</head>

<body>
<div class="container" style="width: 800px;">
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<div class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none">
<span class="fs-4">
<a href="/" class="link-dark link-underline-opacity-0">IoT Lab</a>
</span>
</div>
</header>
</div>

{% block content %}
{% endblock %}

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
Loading

0 comments on commit 5ee8f92

Please sign in to comment.