Skip to content

Commit

Permalink
New version
Browse files Browse the repository at this point in the history
  • Loading branch information
dias-ko committed May 29, 2024
1 parent 9a346ec commit 9998fb2
Show file tree
Hide file tree
Showing 9 changed files with 1,282 additions and 161 deletions.
69 changes: 0 additions & 69 deletions .github/workflows/python-app.yml

This file was deleted.

6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
venv*/
__pycache__/
flask_session/
terrains/
imgs/
terrains/*
imgs/*
test.py
LoRaWAN-US915.pl
instance/
1,068 changes: 1,068 additions & 0 deletions LoRaWAN-US915.pl

Large diffs are not rendered by default.

149 changes: 97 additions & 52 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
import subprocess, datetime, os, re
from flask import Flask, render_template, redirect, session
import subprocess, os, re
from datetime import datetime, timedelta
from flask import Flask, render_template, session, redirect, url_for
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
from wtforms import IntegerField, SubmitField, SelectField, BooleanField, RadioField
from wtforms.validators import DataRequired, NumberRange, InputRequired
from flask_sqlalchemy import SQLAlchemy
from apscheduler.schedulers.background import BackgroundScheduler


FOLDER = os.path.basename(os.path.dirname(os.path.abspath(__file__))) + '/'
app = Flask(__name__, static_folder=os.getcwd())
app.secret_key = 'tO$&!|0wkamvVia0?n$NqIRVWOG'
app.config.from_pyfile('config.py')
bootstrap = Bootstrap5(app)
csrf = CSRFProtect(app)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
db = SQLAlchemy(app)
app.app_context().push()


class Sessions(db.Model):
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime)
terrain = db.Column(db.String)
img = db.Column(db.String)

def __init__(self, timestamp, terrain, img):
self.timestamp = timestamp
self.terrain = terrain
self.img = img


def delete_expired():
day_ago = datetime.now() - timedelta(days=1)
with app.app_context():
q = Sessions.query.filter(Sessions.timestamp < day_ago)
files = q.with_entities(Sessions.terrain, Sessions.img).all()
for t, i in files:
if os.path.exists(t):
os.remove(t)
if os.path.exists(i):
os.remove(i)
q.delete()
db.session.commit()

scheduler = BackgroundScheduler()
scheduler.add_job(func=delete_expired, trigger='interval', days=1)
scheduler.start()


def parse_res(str):
data = str.decode('utf-8')
Expand Down Expand Up @@ -43,80 +77,91 @@ def parse_res(str):


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()])
frequency = RadioField('Frequency Plan', choices=[('eu', 'EU868'), ('us', 'US915')], validators=[DataRequired()], default='eu', id='freq')
packets_per_hour = IntegerField('Packets per hour ', validators=[DataRequired(), NumberRange(min=1, max=1800)], default=10)
auto_simtime = BooleanField('Auto Simulation Time', id='auto_sim')
simulation_time = IntegerField('Simulation Time (s)', default=3600, validators=[DataRequired(), NumberRange(min=3600, max=3600*24*10)], 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()], id='policy', default='1')
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)
rx2sf = IntegerField('RX2 SF', validators=[DataRequired(), NumberRange(min=7, max=12)], default=7, id='channels')
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=[InputRequired(), 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)])
size = IntegerField('Terrain Side Length', validators=[DataRequired(), NumberRange(min=100)], default='1000')
nodes = IntegerField('Number of Nodes', default='100', validators=[DataRequired(), NumberRange(min=1, max=5000)])
gateways = IntegerField('Number of Gateways', default='2', validators=[DataRequired(), NumberRange(min=1)])
submit = SubmitField('Generate')

@app.route("/", methods=['GET', 'POST'])

@app.route('/', methods=['GET', 'POST'])
def index():
form = TerrainForm()
message = ""
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"
timestamp = datetime.now()
timestamp_str = timestamp.strftime('%Y-%m-%d-%H-%M-%S.%f')
file = 'terrains/' + timestamp_str + '.txt'
img_file = 'imgs/' + timestamp_str + '.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"
subprocess.run(['perl', 'generate_terrain.pl', str(size), str(nodes), str(gateways), '>', file], shell=True)
subprocess.run(['perl', 'draw_terrain.pl', file, img_file], shell=True)
new_session = Sessions(timestamp, file, img_file)
db.session.add(new_session)
db.session.commit()
session['id'] = new_session.id
img_file = FOLDER + img_file
except subprocess.CalledProcessError as e:
img_file = FOLDER + 'static/imgs/error_placeholder.png'
message = e
except Exception as e:
message = e
return redirect("/simulate")
else:
session["image"] = None
return render_template('index.html', form=form, message=message, tab="Generate")
img_file = None
return render_template('index.html', form=form, message=message, img_file=img_file, tab='Generate')


@app.route("/simulate", methods=['GET', 'POST'])
@app.route('/simulate', methods=['GET', 'POST'])
def simulate():
form = SimulationForm()
message = ""
message = ''
img_file = None
if session['id']:
user_session = Sessions.query.get(session['id'])
file = user_session.terrain
img_file = FOLDER + user_session.img
else:
return redirect(url_for(''))

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
script_name = 'LoRaWAN.pl' if form.frequency.data == 'eu' else 'LoRaWAN-US915.pl'
if os.path.exists(file):
try:
res = subprocess.check_output(['perl', script_name, str(packets_per_hour), str(simulation_time), str(ack_policy), file, 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 subprocess.CalledProcessError as e:
message = e
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")
session['result'] = None
return render_template('index.html', form=form, message=message, result=session['result'], img_file=img_file, tab="Simulate")
8 changes: 8 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from datetime import timedelta

DEBUG = True
SECRET_KEY = 'IoTLab'
SESSION_PERMANENT = False
SESSION_TYPE = 'filesystem'
SQLALCHEMY_DATABASE_URI= 'sqlite:///sessions.sqlite3'
PERMANENT_SESSION_LIFETIME = timedelta(days=1)
12 changes: 6 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
flask
bootstrap-flask
flask_session
flask_wtf
wtforms
gunicorn
APScheduler==3.10.4
Bootstrap_Flask==2.4.0
Flask==3.0.3
flask_sqlalchemy==3.1.1
flask_wtf==1.2.1
WTForms==3.1.2
Binary file added static/imgs/error_placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
<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">
<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css” />
</head>

<body>
<div class="container" style="width: 800px;">
<div class="container">
<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">
Expand Down
Loading

0 comments on commit 9998fb2

Please sign in to comment.