425 lines
15 KiB
Python
425 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
#####################################################################
|
||
#
|
||
# P0188
|
||
#
|
||
# World domination panel
|
||
#
|
||
# This software checks status of systems (Ping/Website/MQTT)
|
||
# and writes status out to serial interface for
|
||
# RGB-LED-stripe
|
||
#
|
||
#####################################################################
|
||
#
|
||
# 2024-09-08 xsider version for public
|
||
# 2024-08-28 xsider code cleanup
|
||
# 2024-08-21 xsider created
|
||
#
|
||
#####################################################################
|
||
|
||
from http.server import HTTPServer, BaseHTTPRequestHandler #Python’s built-in library
|
||
import os
|
||
import time
|
||
from threading import Thread
|
||
import serial
|
||
import queue
|
||
import paho.mqtt.client as mqtt
|
||
from enum import Enum,auto
|
||
from ping3 import ping
|
||
|
||
hostName = "0.0.0.0"
|
||
serverPort = 8082 #You can choose any available port; by default, it is 8000
|
||
|
||
DISPTTY="/dev/ttyACM0"
|
||
DISPBAUD=115200
|
||
|
||
MQTTHOST="sigint5.internal"
|
||
MQTTPORT=1883
|
||
|
||
brightness = 0
|
||
|
||
# this is a replacement for enum
|
||
BLACK = "BLACK"
|
||
RED = "RED"
|
||
GREEN = "GREEN"
|
||
BLUE = "BLUE"
|
||
YELLOW = "YELLOW"
|
||
WHITE = "WHITE"
|
||
|
||
NOBLINK = False
|
||
BLINK = True
|
||
|
||
colordef = { BLACK : { "html" : "000000", "rgb" : b";0;0;0" },
|
||
RED : { "html" : "FF0000", "rgb" : b";255;0;0" },
|
||
GREEN : { "html" : "00FF00", "rgb" : b";0;255;0" },
|
||
BLUE : { "html" : "7070FF", "rgb": b";0;0;255" },
|
||
YELLOW : { "html" : "FFFF00", "rgb" : b";255;255;0" },
|
||
WHITE : { "html" : "FFFFFF", "rgb" : b";255;255;255" }
|
||
}
|
||
|
||
class AlarmTyp(Enum):
|
||
RUHE = auto()
|
||
TEST = auto()
|
||
|
||
q = queue.Queue()
|
||
|
||
hostlist = [
|
||
[0,"192.168.0.1","Router<br>AP EG",WHITE,NOBLINK,RED,NOBLINK],
|
||
[1,"192.168.0.2","Blackfin<br>Server",WHITE,NOBLINK,RED,BLINK],
|
||
[2,"192.168.0.3","Coreswitch",WHITE,NOBLINK,RED,BLINK],
|
||
[3,"192.168.0.4","Odin<br>Fileserver",WHITE,NOBLINK,RED,NOBLINK],
|
||
[4,"192.168.0.5","Thor<br>Mailserver", WHITE, NOBLINK, RED, NOBLINK],
|
||
[5,"192.168.0.6","Tyr<br>Webserver", WHITE, NOBLINK, RED, NOBLINK],
|
||
[6,"192.168.0.7","Frigg<br>Loginserver", WHITE, NOBLINK, RED, NOBLINK],
|
||
[7,"192.168.0.8","Fulla<br>Lizenzserver", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[8,"192.168.0.9","Observer 1", WHITE, NOBLINK, RED, NOBLINK],
|
||
[9,"192.168.0.10","Sigint 5", WHITE, NOBLINK, RED, NOBLINK],
|
||
[10,"192.168.0.11","AP4<br>1.OG Nord", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[11,"192.168.0.12","AP7<br>1.OG SÜd", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[12,"192.168.0.13","AP5<br>2.OG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[13,"192.168.0.14","Bridge<br>3.OG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[14,"192.168.0.15","AP3<br>Labor", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
|
||
[15,"192.168.0.16","Blitzortung", WHITE, NOBLINK, RED, NOBLINK],
|
||
[16,"192.168.0.17","urad<br>Monitor", WHITE, NOBLINK, RED, NOBLINK],
|
||
[17,"192.168.0.18","Feinstaub<br>Sensor", WHITE, NOBLINK, RED, NOBLINK],
|
||
[18,"192.168.0.19","Kiwi<br>SDR",WHITE , NOBLINK, BLACK, NOBLINK],
|
||
[19,"192.168.0.20","LoRaWAN<br>Gateway", WHITE, NOBLINK, RED, NOBLINK],
|
||
[20,"192.168.0.21","APC1", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[21,"192.168.0.22","APC2", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[22,"192.168.0.23","APC3", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[23,"192.168.0.24","Laborraum 4", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[24,"192.168.0.25","Kamera 2", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[25,"192.168.0.26","Kamera 4", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
|
||
[32,"192.168.0.33","Hamclock", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[33,"192.168.0.34","Manik<br>Backup", WHITE, NOBLINK, RED, NOBLINK],
|
||
[34,"192.168.0.35","Automatix<br>CNC-Fräse", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[35,"192.168.0.36","Indra<br>Solarmess", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[36,"192.168.0.37","Shiva<br>Bat.umschaltung", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
|
||
[38,"192.168.0.39","Redpitaya", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[39,"192.168.0.40","Rigol<br>Oszi", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[40,"192.168.0.41","Rigol<br>Spektrum", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[41,"192.168.0.42","Rigol<br>DMM", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[42,"192.168.0.43","Rigol<br>Arb.gen", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[43,"192.168.0.44","Victron<br>Gateway", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[44,"192.168.0.45","Roboterarm", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
|
||
[45,"192.168.0.46","Unmatched<br>RISC V", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[46,"192.168.0.47","Drucker<br>A4 Labor", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[47,"192.168.0.48","Drucker<br>A3 Labor", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[48,"192.168.0.49","DSKY", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[49,"192.168.0.50","Oc 5", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
|
||
[60,"192.168.0.61","Arbeitsplatz<br>EG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[61,"192.168.0.62","Drucker<br>EG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[62,"192.168.0.63","Inferno<br>1.OG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[63,"192.168.0.64","Drucker<br>1.OG", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[64,"192.168.0.65","Eb<br>Wetterstation", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[65,"192.168.0.66","Sigint 2<br>NOAA Sat.", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[66,"192.168.0.67","Weishaupt<br>Heizung", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[67,"192.168.0.68","Wago 1<br>SPS", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[68,"192.168.0.69","VUZero", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
[69,"192.168.0.70","Vishnu<br>Thermodrucker", WHITE, NOBLINK, BLACK, NOBLINK],
|
||
]
|
||
|
||
colorlist= ["666666"]*15*5
|
||
textlist=[""]*15*5
|
||
blinklist=[0]*15*5
|
||
funkok=False
|
||
funktest=False
|
||
|
||
def set_color( field, color, blink ):
|
||
sendstr = b"%d" % field
|
||
colorlist[field] = colordef[color]["html"]
|
||
blinklist[field] = blink
|
||
sendstr += colordef[color]["rgb"]
|
||
if ( blink ):
|
||
sendstr += colordef[BLACK]["rgb"]
|
||
else:
|
||
sendstr += colordef[color]["rgb"]
|
||
sendstr += b"\n"
|
||
q.put(sendstr)
|
||
|
||
|
||
TOPIC_POC=[("funk/+/ok/#",1), # Netzkennung
|
||
("funk/+/test/#",1), # Testkennung
|
||
]
|
||
|
||
def on_connect(client,userdata,flags,reason_code,properties):
|
||
print(f"Connected with result code {reason_code}")
|
||
for e in TOPIC_POC:
|
||
mqttclient.subscribe(e)
|
||
print("Subscribed", e)
|
||
|
||
def on_disconnet(client,userdata,rc):
|
||
print("Disconnect with result code"+str(rc))
|
||
time.sleep(1)
|
||
mqttclient.reconnect()
|
||
|
||
def on_message(client,userdata,msg):
|
||
|
||
global funkok,funktest
|
||
topic = msg.topic
|
||
|
||
Void,quelle,kennung,sub = topic.split("/")
|
||
|
||
if ( kennung == "ok" ):
|
||
funkok=True
|
||
if ( kennung == "test" ):
|
||
funktest=True
|
||
|
||
# old mosquitto_lib
|
||
#mqttclient=mqtt.Client(client_id="Anzeige",clean_session=False)
|
||
|
||
# new mosquitto_lib
|
||
mqttclient = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||
|
||
mqttclient.on_connect = on_connect
|
||
mqttclient.on_message = on_message
|
||
|
||
def funk_observe():
|
||
global funkok,funktest
|
||
timeoutfunk=time.time()
|
||
timeoutfunktest=time.time()
|
||
funkstate=AlarmTyp.RUHE
|
||
|
||
textlist[70] = "Funk OK"
|
||
while ( True ):
|
||
try:
|
||
if funkok:
|
||
timeoutfunk = time.time()
|
||
funkok=False;
|
||
if funkstate != AlarmTyp.TEST : # a test alarm is running, so keep green light instead of white
|
||
set_color( 70, WHITE, NOBLINK )
|
||
|
||
if funktest:
|
||
timeoutfunktest = time.time()
|
||
funkstate = AlarmTyp.TEST
|
||
funktest=False
|
||
set_color(70, GREEN, NOBLINK )
|
||
|
||
if funkstate == AlarmTyp.TEST and time.time() > (timeoutfunktest + 15*60):
|
||
funkstate = AlarmTyp.RUHE
|
||
# whitecolor comes from next LoRaWAN signal
|
||
|
||
if time.time() > ( timeoutfunk + 200 ): # no LoRaWAN for 3 minutes, color red
|
||
timeoutfunk = time.time()
|
||
set_color(70, RED, NOBLINK )
|
||
|
||
except Exception as error:
|
||
print ("Error: ", error)
|
||
|
||
time.sleep(0.1)
|
||
|
||
def HostUp(hostname):
|
||
# '''Function returns True if host IP returns a ping, else False'''
|
||
if ping(hostname,timeout=1):
|
||
HOST_UP = True
|
||
else:
|
||
HOST_UP = False
|
||
return HOST_UP
|
||
|
||
def ping_hosts():
|
||
for i in hostlist:
|
||
textlist[ i[0] ] = i[2]
|
||
|
||
while True:
|
||
for i in hostlist:
|
||
if HostUp( i[1] ):
|
||
set_color( i[0], i[3], i[4] )
|
||
# send a rgb string to display
|
||
time.sleep(1) # this is mandatory for slow down ping for prevent ping slowdown for unprivileged users
|
||
else:
|
||
set_color( i[0], i[5], i[6] )
|
||
|
||
from urllib.request import urlopen
|
||
import requests
|
||
import json
|
||
|
||
def decode_prusa_status( fieldnumber, statustext ):
|
||
if statustext == 'IDLE' or statustext == 'FINISHED' or statustext == 'READY':
|
||
set_color( fieldnumber, WHITE, NOBLINK )
|
||
|
||
if statustext == 'PRINTING':
|
||
set_color( fieldnumber, GREEN, NOBLINK )
|
||
|
||
if statustext == 'BUSY' or statustext == 'PAUSED' or statustext == 'STOPPED' or statustext == 'ATTENTION':
|
||
set_color( fieldnumber, YELLOW, NOBLINK )
|
||
|
||
if statustext == 'ERROR':
|
||
set_color( fieldnumber, RED, NOBLINK )
|
||
|
||
def decode_octopi_status( fieldnumber, statustext ):
|
||
if statustext == "Printing":
|
||
set_color( fieldnumber, GREEN, NOBLINK )
|
||
elif statustext == "Error" or statustext == "Offline after error":
|
||
set_color( fieldnumber, RED, NOBLINK )
|
||
elif statustext == "Operational":
|
||
set_color( fieldnumber, WHITE, NOBLINK )
|
||
else:
|
||
set_color( fieldnumber, YELLOW, NOBLINK )
|
||
|
||
|
||
def check_websites():
|
||
textlist[74] = "Aktivität < 50 cpm"
|
||
textlist[26] = "Ultimaker"
|
||
textlist[27] = "Prusa<br>MK3 MMU2"
|
||
textlist[28] = "Prusa<br>MK3+"
|
||
textlist[29] = "Prusa<br>Mini+"
|
||
textlist[30] = "Prusa<br>XL"
|
||
textlist[31] = "Prusa<br>SL1"
|
||
headersmk3={'X-Api-Key': 'keymk3'}
|
||
headersmmu={'X-Api-Key': 'keymmu'}
|
||
headersmin={'X-Api-Key': 'keymini'}
|
||
headersxl={'X-Api-Key': 'keyxl'}
|
||
headersulti={'X-Api-Key': 'keyulti'}
|
||
authsl = requests.auth.HTTPDigestAuth('maker','123456')
|
||
while (True):
|
||
time.sleep(60)
|
||
try:
|
||
r = requests.get("http://geiger.local/j")
|
||
a = r.json()
|
||
value = a["data"]["cpm"]
|
||
if value < 50:
|
||
set_color( 74, WHITE, NOBLINK )
|
||
else:
|
||
set_color( 74, RED, BLINK )
|
||
except:
|
||
set_color( 74, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://octopi.local/api/job', headers=headersulti )
|
||
a = r.json()
|
||
decode_octopi_status(26, a['state'] )
|
||
except:
|
||
set_color( 26, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://prusamk3mmu.local/api/v1/status', headers=headersmmu)
|
||
a = r.json()
|
||
decode_prusa_status(27, a['printer']['state'])
|
||
except:
|
||
set_color( 27, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://prusamk3.local/api/v1/status', headers=headersmk3)
|
||
a = r.json()
|
||
decode_prusa_status(28, a['printer']['state'])
|
||
except:
|
||
set_color( 28, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://prusamini.local/api/v1/status', headers=headersmin)
|
||
a = r.json()
|
||
decode_prusa_status(29, a['printer']['state'])
|
||
except:
|
||
set_color( 29, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://prusaxl.local/api/v1/status', headers=headersxl)
|
||
a = r.json()
|
||
decode_prusa_status(30, a['printer']['state'])
|
||
except:
|
||
set_color( 30, BLACK, NOBLINK )
|
||
|
||
try:
|
||
r = requests.get('http://prusasl1.local/api/printer', auth=authsl)
|
||
a = r.json()
|
||
decode_prusa_status(31, a['state']['text'].upper())
|
||
except:
|
||
set_color( 31, BLACK, NOBLINK )
|
||
|
||
|
||
def serialcommunication():
|
||
ser = serial.Serial( DISPTTY,DISPBAUD, timeout=1)
|
||
time.sleep(1) # wait for ARDUINO for wake up
|
||
ser.reset_input_buffer()
|
||
ser.write(b"\n")
|
||
a = ser.readline() # read buffers
|
||
if ( a != b''):
|
||
# System not ready yet
|
||
time.sleep(1)
|
||
else:
|
||
print("System Ready")
|
||
while True:
|
||
item = q.get(block=True)
|
||
a = b'-'
|
||
while ( a != item ):
|
||
ser.reset_input_buffer()
|
||
ser.write(item)
|
||
a = ser.readline()
|
||
if ( a != item ):
|
||
time.sleep(1)
|
||
|
||
class MyServer(BaseHTTPRequestHandler):
|
||
# make log quiet
|
||
def log_message(self, format, *args):
|
||
pass
|
||
|
||
def do_GET(self): #the do_GET method is inherited from BaseHTTPRequestHandler
|
||
self.send_response(200)
|
||
self.send_header("Content-type", "text/html")
|
||
self.end_headers()
|
||
self.wfile.write("<!DOCTYPE html>\n".encode() )
|
||
self.wfile.write("<html>\n".encode() )
|
||
self.wfile.write("<head>\n".encode() )
|
||
self.wfile.write("<meta charset=\"UTF-8\">\n".encode() )
|
||
self.wfile.write("<title>World Domination Status</title><meta http-equiv=\"refresh\" content=\"15\">".encode() )
|
||
self.wfile.write("</head>".encode() )
|
||
self.wfile.write("<body>".encode() )
|
||
self.wfile.write("\n<style>table,th,td {border: 1px solid black;border-collapse:collapse;text-align:center}\n".encode())
|
||
self.wfile.write(".blink_me { animation: blinker 1s linear infinite; }\n".encode () )
|
||
self.wfile.write("@keyframes blinker {50% { opacity: 0; } }\n".encode() )
|
||
self.wfile.write("</style>\n".encode() )
|
||
self.wfile.write("<h1>World Domination Panel</h1>".encode())
|
||
# define len of total entries, create table 5x3
|
||
totallen = len(colorlist)
|
||
|
||
for i in range( int(totallen/15) ):
|
||
self.wfile.write( "<table style=\"width:800px;font-family:osifont;font-size:20px\">\n".encode() )
|
||
for j in range(3):
|
||
self.wfile.write( "<tr style=\"height:80px\">".encode() )
|
||
for k in range(5):
|
||
self.wfile.write( ("<td style=\"width:20%; background-color:#" + colorlist[i*15+j*5+k] + \
|
||
( ";color:lightgray" if colorlist[i*15+j*5+k] == "000000" else " " ) + \
|
||
"\">" + \
|
||
( "<div class=\"blink_me\"> " if blinklist[i*15+j*5+k] else " " ) + \
|
||
textlist[i*15+j*5+k] + \
|
||
( "</div> " if blinklist[i*15+j*5+k] else " " ) + \
|
||
"</td>")\
|
||
.encode() )
|
||
|
||
|
||
self.wfile.write( "</tr>\n".encode() )
|
||
|
||
self.wfile.write( "</table><p>\n".encode() )
|
||
|
||
self.wfile.write( "</body></html>".encode() )
|
||
|
||
if __name__ == "__main__":
|
||
thread = Thread( target = ping_hosts, daemon=True )
|
||
thread2 = Thread( target = serialcommunication,daemon=True )
|
||
thread3 = Thread( target = funk_observe,daemon=True )
|
||
thread4 = Thread( target = check_websites,daemon=True )
|
||
thread.start()
|
||
thread2.start()
|
||
thread3.start()
|
||
thread4.start()
|
||
|
||
webServer = HTTPServer((hostName, serverPort), MyServer)
|
||
print("Server started http://%s:%s" % (hostName, serverPort)) #Server starts
|
||
mqttclient.connect(MQTTHOST, port=MQTTPORT,keepalive=60)
|
||
print("MQTT")
|
||
mqttclient.loop_start()
|
||
try:
|
||
webServer.serve_forever()
|
||
except KeyboardInterrupt:
|
||
pass
|
||
finally:
|
||
mqttclient.loop_stop()
|
||
webServer.server_close() #Executes when you hit a keyboard interrupt, closing the server
|
||
|
||
print("Server stopped.")
|