Merge branch 'Development' into main
# Conflicts: # README.md
This commit is contained in:
40
server/engine.py
Normal file
40
server/engine.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Log:
|
||||
def __init__(self, settings=None):
|
||||
if settings is None:
|
||||
settings = {"save_error": True, "print_error": True, "save_warning": True, "print_warning": True,
|
||||
"save_message": False, "print_message": True, "enable_debug": False}
|
||||
self.save_error = settings["save_error"]
|
||||
self.save_warning = settings["save_warning"]
|
||||
self.save_messages = settings["save_message"]
|
||||
self.print_error = settings["print_error"]
|
||||
self.print_warning = settings["print_warning"]
|
||||
self.print_messages = settings["print_message"]
|
||||
self.debug_e = settings["enable_debug"]
|
||||
|
||||
def error(self, error):
|
||||
if self.print_error:
|
||||
print(f"{datetime.now()} -> ERROR: {error}")
|
||||
if self.save_error:
|
||||
with open("log.txt", "a") as file:
|
||||
file.write(f"\n{datetime.now()} -> ERROR: {error}")
|
||||
|
||||
def warning(self, warning):
|
||||
if self.print_warning:
|
||||
print(f"{datetime.now()} -> Warning: {warning}")
|
||||
if self.save_warning:
|
||||
with open("log.txt", "a") as file:
|
||||
file.write(f"\n{datetime.now()} -> Warning: {warning}")
|
||||
|
||||
def message(self, message):
|
||||
if self.print_messages:
|
||||
print(f"{datetime.now()} -> message: {message}")
|
||||
if self.save_messages:
|
||||
with open("log.txt", "a") as file:
|
||||
file.write(f"\n{datetime.now()} -> message: {message}")
|
||||
|
||||
def debug(self, debug):
|
||||
if self.debug_e:
|
||||
print(f"{datetime.now()} -> DEBUG: {debug}")
|
BIN
server/files/test.jpg
Normal file
BIN
server/files/test.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 354 KiB |
1
server/files/test.txt
Normal file
1
server/files/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
toto je test číslo 1 zo serveru s ID 2
|
1
server/files/test2.txt
Normal file
1
server/files/test2.txt
Normal file
@@ -0,0 +1 @@
|
||||
toto je test n. 2 zo serveru ID2
|
22
server/filesystem.json
Normal file
22
server/filesystem.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"ID": 0,
|
||||
"location": "izba",
|
||||
"descrpition": {
|
||||
"title": "legionrpi",
|
||||
"description_s": "krátky popis, ktorý bude zobrazený iba v náhladovom okne",
|
||||
"description_l": "dlhší popis zariadenia, ktorý bude zobrazený po otvorení",
|
||||
"photo_s": "mala_fotka.png",
|
||||
"photo_b": "velka fotka.png"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"name": "test",
|
||||
"format": ".jpg",
|
||||
"description": "toto je jpg test file"
|
||||
}, {
|
||||
"name": "test2",
|
||||
"format": ".txt",
|
||||
"description": "toto je txt test file"
|
||||
}
|
||||
]
|
||||
}
|
3
server/log.txt
Normal file
3
server/log.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
2021-03-15 10:13:26.660898 -> Warning: 192.168.1.232 disconnected/is not available
|
||||
2021-03-15 11:23:33.589998 -> Warning: 192.168.1.231 disconnected/is not available
|
181
server/main.py
Normal file
181
server/main.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
import engine
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import hashlib
|
||||
|
||||
with open("settings.json", "r") as f: # loading settings
|
||||
settings = json.load(f)
|
||||
|
||||
with open("filesystem.json", "r") as f: # loading settings
|
||||
filesystem = json.load(f)
|
||||
|
||||
IP = settings["IP"]
|
||||
ID = settings["ID"]
|
||||
location = settings["location"]
|
||||
|
||||
app = FastAPI() # init of FastAPI
|
||||
log = engine.Log(settings["log"]) # init of LOG
|
||||
offline = []
|
||||
|
||||
time_to_heartbeat = settings["time_to_heartbeat"] # Raspberry will be requesting heartbeat every __ seconds
|
||||
time_to_heartbeat_offline = settings[
|
||||
"time_to_heartbeat_offline"] # Raspberry will be requesting heartbeat every __ seconds from offline rpi
|
||||
|
||||
# json variables
|
||||
heartbeat_table = settings["heartbeat_table"]
|
||||
sensors = { # List of "live" data like tempeature, etc.
|
||||
"teplota": 24,
|
||||
"vlhkosť": 25,
|
||||
"počet ľudí": 10,
|
||||
"doba čakania": 2
|
||||
}
|
||||
|
||||
heartbeat_table["ID"].append(ID)
|
||||
heartbeat_table["IP"].append(IP)
|
||||
heartbeat_table["location"].append(location)
|
||||
heartbeat_table["file_system"].append(filesystem)
|
||||
heartbeat_table["last_heartbeat"].append(time_to_heartbeat)
|
||||
|
||||
|
||||
# Todo better "host" ID handeling
|
||||
|
||||
class ServerTable(BaseModel): # table of content for heartbeat request
|
||||
ID: list
|
||||
IP: list
|
||||
location: list
|
||||
file_system: list
|
||||
last_heartbeat: list
|
||||
|
||||
|
||||
@app.post("/heartbeat")
|
||||
def heartbeat(s_table: ServerTable, request: Request):
|
||||
log.message(f"server requested heartbeat {request.client.host}:{request.client.port}")
|
||||
log.debug(f"Recieved server table: {s_table}")
|
||||
|
||||
try:
|
||||
for position, server_id in enumerate(s_table.ID):
|
||||
if server_id in heartbeat_table["ID"]:
|
||||
if heartbeat_table["last_heartbeat"][heartbeat_table["ID"].index(server_id)] < \
|
||||
s_table.last_heartbeat[position]:
|
||||
heartbeat_table["last_heartbeat"][heartbeat_table["ID"].index(server_id)] = s_table.last_heartbeat[
|
||||
position]
|
||||
log.debug(f"updated {server_id}`s heartbeat to {s_table.last_heartbeat[position]}")
|
||||
heartbeat_table["file_system"][heartbeat_table["ID"].index(server_id)] = s_table.file_system[
|
||||
position]
|
||||
elif server_id == ID:
|
||||
log.debug(f"Updated my heartbeat from {s_table.last_heartbeat[position]} to {time_to_heartbeat}")
|
||||
heartbeat_table["last_heartbeat"][heartbeat_table["ID"].index(ID)] = time_to_heartbeat
|
||||
else:
|
||||
heartbeat_table["ID"].append(s_table.ID[position])
|
||||
heartbeat_table["IP"].append(s_table.IP[position])
|
||||
heartbeat_table["location"].append(s_table.location[position])
|
||||
heartbeat_table["file_system"].append(s_table.file_system[position])
|
||||
heartbeat_table["last_heartbeat"].append(s_table.last_heartbeat[position])
|
||||
except Exception as error:
|
||||
log.error(f"heartbeat > {error}")
|
||||
|
||||
if heartbeat_table["ID"][heartbeat_table["IP"].index(request.client.host)] in offline:
|
||||
offline.remove(heartbeat_table["ID"][heartbeat_table["IP"].index(request.client.host)])
|
||||
log.message(f"{request.client.host} gone online")
|
||||
|
||||
return heartbeat_table, {"ID": ID, "file_system": filesystem, "location": location}
|
||||
|
||||
|
||||
@app.get("/sensors")
|
||||
def get_sensors(request: Request):
|
||||
log.message(f"sensor data sent to {request.client.host}:{request.client.port}")
|
||||
log.debug(f"sensor data: {sensors}")
|
||||
return sensors
|
||||
|
||||
|
||||
@app.get("/files/{IDx}/{file}")
|
||||
def get_file(IDx: int, file: str):
|
||||
server_ip = heartbeat_table["IP"][heartbeat_table["ID"].index(IDx)]
|
||||
if IDx == ID:
|
||||
return FileResponse(f"files/{file}")
|
||||
elif IDx in heartbeat_table["ID"]:
|
||||
if os.path.isdir(f"cache/{IDx}"):
|
||||
if os.path.isfile(f"cache/{IDx}/{file}"):
|
||||
with open(f"cache/{IDx}/{file}", "rb") as compared_file:
|
||||
m = hashlib.md5()
|
||||
for line in compared_file:
|
||||
m.update(line)
|
||||
rr = requests.get(f"""http://{server_ip}:8000/compare/{file}""")
|
||||
if rr.text.strip('"') != str(m.hexdigest()):
|
||||
log.message(f"{file} on server {server_ip} is changed.")
|
||||
else:
|
||||
log.debug(f"returning cached file cache/{IDx}{file}")
|
||||
return FileResponse(f"cache/{IDx}/{file}")
|
||||
else:
|
||||
os.mkdir(f"cache/{IDx}")
|
||||
log.message(f"downloading {file} from {server_ip}")
|
||||
r = requests.get(f"http://{server_ip}:8000/files/{IDx}/{file}")
|
||||
with open(f"cache/{IDx}/{file}", "wb") as save:
|
||||
save.write(bytes(r.content))
|
||||
return FileResponse(f"cache/{IDx}/{file}")
|
||||
|
||||
|
||||
@app.post("/update")
|
||||
def update_sensors():
|
||||
pass
|
||||
# Todo Make option to upload "live data" manually to rpi
|
||||
|
||||
|
||||
@app.get("/compare/{file}")
|
||||
def comparision(file: str):
|
||||
with open(f"files/{file}", "rb") as compared_file:
|
||||
m = hashlib.md5()
|
||||
for line in compared_file:
|
||||
m.update(line)
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
@app.get("/devices_list")
|
||||
def get_devices_list():
|
||||
return heartbeat_table["file_system"]
|
||||
|
||||
|
||||
def send_heartbeat(ip, id):
|
||||
global heartbeat_table
|
||||
log.message(f"""sending heartbeat to {ip}({"offline" if id in offline else "online"})""")
|
||||
cache_request = requests.post(f"http://{ip}:8000/heartbeat", data=json.dumps(heartbeat_table))
|
||||
heartbeat_table = dict(cache_request.json()[0])
|
||||
log.debug(json.dumps(cache_request.json(), indent=4))
|
||||
|
||||
|
||||
def mainloop():
|
||||
while True:
|
||||
for device_number, device_ID in enumerate(heartbeat_table["ID"]):
|
||||
if device_ID != ID:
|
||||
if heartbeat_table["last_heartbeat"][device_number] < 0:
|
||||
try:
|
||||
send_heartbeat(heartbeat_table["IP"][device_number], heartbeat_table["ID"][device_number])
|
||||
except requests.exceptions.ConnectionError:
|
||||
if heartbeat_table["ID"][device_number] not in offline:
|
||||
log.warning(f"""{heartbeat_table["IP"][device_number]} disconnected/is not available""")
|
||||
offline.append(heartbeat_table["ID"][device_number])
|
||||
heartbeat_table["last_heartbeat"][int(device_number)] = int(time_to_heartbeat_offline)
|
||||
else:
|
||||
if heartbeat_table["ID"][device_number] in offline:
|
||||
offline.remove(heartbeat_table["ID"][device_number])
|
||||
log.message(f"""{heartbeat_table["IP"][device_number]} gone online""")
|
||||
heartbeat_table["last_heartbeat"][int(device_number)] = int(time_to_heartbeat) + 5
|
||||
log.debug(f"""{device_ID} : time to heartbeat : {heartbeat_table["last_heartbeat"][device_number]}""")
|
||||
heartbeat_table["last_heartbeat"][device_number] -= 1
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
thread_1 = threading.Thread(target=mainloop, daemon=True)
|
||||
thread_1.start()
|
||||
|
||||
# Todo in next release: disconnect offline client after set time
|
||||
# Todo send to mobile
|
||||
# Todo new filesystem handeling
|
||||
# Todo implement update system
|
||||
# Todo settings for easy adding/editing files/id/text
|
23
server/settings.json
Normal file
23
server/settings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"ID": 0,
|
||||
"IP": "192.168.1.99",
|
||||
"location": "izba",
|
||||
"time_to_heartbeat": 20,
|
||||
"time_to_heartbeat_offline": 25,
|
||||
"log": {
|
||||
"save_error": true,
|
||||
"print_error": true,
|
||||
"save_warning": true,
|
||||
"print_warning": true,
|
||||
"save_message": false,
|
||||
"print_message": true,
|
||||
"enable_debug": false
|
||||
},
|
||||
"heartbeat_table": {
|
||||
"ID": [],
|
||||
"IP": [],
|
||||
"location": [],
|
||||
"file_system": [],
|
||||
"last_heartbeat": []
|
||||
}
|
||||
}
|
12
server/setup.py
Normal file
12
server/setup.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import tkinter
|
||||
import json
|
||||
height = 750
|
||||
width = 1200
|
||||
with open("settings.json", "r") as file:
|
||||
settings = json.load(file)
|
||||
with open("settings.json", "r") as file:
|
||||
filesystem = json.load(file)
|
||||
canvas = tkinter.Canvas(height=height, width=width)
|
||||
canvas.pack()
|
||||
|
||||
canvas.mainloop()
|
47
server/test.json
Normal file
47
server/test.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"connected_id": 1,
|
||||
"1": {
|
||||
"ID": 1,
|
||||
"location": "GPS",
|
||||
"descrpition": {
|
||||
"title": "nazov rpi ako nazov",
|
||||
"description_s": "krátky popis, ktorý bude zobrazený iba v náhladovom okne",
|
||||
"description_l": "dlhší popis zariadenia, ktorý bude zobrazený po otvorení",
|
||||
"photo_s": "mala_fotka.png",
|
||||
"photo_b": "velka_fotka.png"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"name": "prehliadky",
|
||||
"format": ".pdf",
|
||||
"description": "tento súbor obsahuje prehliadky"
|
||||
}, {
|
||||
"name": "prehliadky",
|
||||
"format": ".pdf",
|
||||
"description": "tento súbor obsahuje prehliadky"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2": {
|
||||
"ID": 2,
|
||||
"location": "GPS",
|
||||
"descrpition": {
|
||||
"title": "nazov rpi ako nazov",
|
||||
"description_s": "krátky popis, ktorý bude zobrazený iba v náhladovom okne",
|
||||
"description_l": "dlhší popis zariadenia, ktorý bude zobrazený po otvorení",
|
||||
"photo_s": "mala_fotka.png",
|
||||
"photo_b": "velka fotka.png"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"name": "prehliadky",
|
||||
"format": ".pdf",
|
||||
"description": "tento súbor obsahuje prehliadky"
|
||||
}, {
|
||||
"name": "prehliadky",
|
||||
"format": ".pdf",
|
||||
"description": "tento súbor obsahuje prehliadky"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
60
server/test.py
Normal file
60
server/test.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import curses
|
||||
|
||||
menu = ['Home', 'Play', 'Scoreboard', 'Exit']
|
||||
|
||||
|
||||
def print_menu(stdscr, selected_row_idx):
|
||||
stdscr.clear()
|
||||
h, w = stdscr.getmaxyx()
|
||||
for idx, row in enumerate(menu):
|
||||
x = w//2 - len(row)//2
|
||||
y = h//2 - len(menu)//2 + idx
|
||||
if idx == selected_row_idx:
|
||||
stdscr.attron(curses.color_pair(1))
|
||||
stdscr.addstr(y, x, row)
|
||||
stdscr.attroff(curses.color_pair(1))
|
||||
else:
|
||||
stdscr.addstr(y, x, row)
|
||||
stdscr.refresh()
|
||||
|
||||
|
||||
def print_center(stdscr, text):
|
||||
stdscr.clear()
|
||||
h, w = stdscr.getmaxyx()
|
||||
x = w//2 - len(text)//2
|
||||
y = h//2
|
||||
stdscr.addstr(y, x, text)
|
||||
stdscr.refresh()
|
||||
|
||||
|
||||
def main(stdscr):
|
||||
# turn off cursor blinking
|
||||
curses.curs_set(0)
|
||||
|
||||
# color scheme for selected row
|
||||
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||
|
||||
# specify the current selected row
|
||||
current_row = 0
|
||||
|
||||
# print the menu
|
||||
print_menu(stdscr, current_row)
|
||||
|
||||
while 1:
|
||||
key = stdscr.getch()
|
||||
|
||||
if key == curses.KEY_UP and current_row > 0:
|
||||
current_row -= 1
|
||||
elif key == curses.KEY_DOWN and current_row < len(menu)-1:
|
||||
current_row += 1
|
||||
elif key == curses.KEY_ENTER or key in [10, 13]:
|
||||
print_center(stdscr, "You selected '{}'".format(menu[current_row]))
|
||||
stdscr.getch()
|
||||
# if user selected last row, exit the program
|
||||
if current_row == len(menu)-1:
|
||||
break
|
||||
|
||||
print_menu(stdscr, current_row)
|
||||
|
||||
|
||||
curses.wrapper(main)
|
4
server/version.json
Normal file
4
server/version.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "0.1",
|
||||
"type": "Alpha"
|
||||
}
|
Reference in New Issue
Block a user