Dateien hochladen nach „“

This commit is contained in:
Manuel Kamper 2023-06-09 09:49:13 +00:00
parent 142732917e
commit 2f45904da3
3 changed files with 304 additions and 0 deletions

16
TimeUtils.py Normal file
View File

@ -0,0 +1,16 @@
import utime as time
class TimeUtils():
def __init__(self):
pass
def DateTimeNow(self):
tm = time.gmtime()
return "%02d.%02d.%04d,%02d:%02d" % (tm[2], tm[1], tm[0], tm[3], tm[4])
def IsOlderThanDays(self, date, days):
fdate = time.mktime((int(date[0]), int(date[1]), int(date[2]), 0, 0, 0, 0, 0))
if (fdate + (int(days) * 86400) <= time.time()):
return True
else:
return False

283
main.py Normal file
View File

@ -0,0 +1,283 @@
import rp2, re, os
import TimeUtils, Logger, Networking, NTP
import uasyncio as asyncio
import utime as time
import usocket as socket
import urequests as requests
from machine import ADC, Pin
from secrets import secrets
from configs import configs
rp2.country(configs['country'])
version = "0.3"
TimeUtils = TimeUtils.TimeUtils()
Logger = Logger.Logger(configs['log_housekeeping_days'])
Networking = Networking.Networking(Logger, secrets['ssid'], secrets['pw'])
NTP = NTP.NTP(Logger)
boottime = time.time()
tempSensor = ADC(4) # internal temperature sensor
s0Pin = Pin(5, Pin.IN, Pin.PULL_UP) # gpio 5
temperature = 0.0
zaehlerStand = 0 # Wh
momentanVerbrauch = 0.000 # W
interrupt = False
debounce_time = 0
secPerImp = 3600.0 / float(configs['ticks_per_kWh'])
lastSaved = 0
lastTick = 0
impulses = 0
lastImpTime = 0
# Handle s0 interrupts
def callback(s0Pin):
global interrupt, debounce_time
if (time.ticks_ms() - debounce_time) > 20:
interrupt = True
debounce_time = time.ticks_ms()
s0Pin.irq(trigger = Pin.IRQ_RISING, handler = callback)
# Checks if string is integer
def IsInt(possibleint):
try:
int(possibleint)
except:
return False
else:
return True
# Reboots the Pico W (f.e. in case of an error)
def Reboot():
Logger.LogMessage("Performing Reboot")
machine.reset()
# Calculates the uptime in hours and minutes
def Uptime():
global boottime
seconds = (time.time() - boottime) % (24 * 3600)
hours = seconds // 3600
seconds %= 3600
minutes = seconds // 60
seconds %= 60
return "%d:%02d" % (hours, minutes)
# Reads the onboard temperature sensor's value
def ReadTemp():
global temperature
temperature = 27 - ((tempSensor.read_u16() * (3.3 / (65535))) - 0.706) / 0.001721
# Checks the count of the Powermeter (reboot safe)
def CheckPowermeterCount():
global zaehlerStand
try:
file = open("powercount.txt", "r")
except:
file = open("powercount.txt", "w")
file.write("0.000")
countFile = float(file.read())
file.close()
if zaehlerStand < countFile:
zaehlerStand = countFile
Logger.LogMessage("Powermeter count is " + str(zaehlerStand / 1000) + " kWh.")
# store zaehlerStand and momentanVerbrauch via API in DB
def SendAPI():
global lastSaved, zaehlerStand, momentanVerbrauch
if ((time.ticks_ms() - lastSaved) > configs['db_api_interval_minutes'] * 60000):
lastSaved = time.ticks_ms()
response = requests.get(configs['db_api'] + "?action=send&total=" + str(zaehlerStand) + "&current=" + str(momentanVerbrauch))
response.close()
# calculate current power consumption and increase the total power consuption
def PowerMeasurement():
global interrupt, secPerImp, lastTick, impulses, lastImpTime
if interrupt:
#current power consumption (watt)
nowTick = time.ticks_ms()
timeDiff = nowTick - lastTick
#1000 (watt) / (timeDiff in s / secPerImp)
momentanVerbrauch = 1000.0 / ((timeDiff / 1000.0) / secPerImp)
lastTick = nowTick
impulses = impulses + 1
#powermeter total count (wh)
if (time.ticks_ms() - lastImpTime) > 60000: #every minute
nowImpTime = time.ticks_ms()
timeImpDiff = nowImpTime - lastImpTime
#energy = power times time (in hour)
newConsumption = (((1000.0 * impulses) / ((timeImpDiff / 1000.0) / secPerImp)) * (timeImpDiff / 3600000.0))
AddZaehlerstand(newConsumption)
impulses = 0
lastImpTime = nowImpTime
interrupt = False
# get power meter total in kWh
def GetZaehlerstand():
global zaehlerStand
return str((zaehlerStand / 1000))
# set power meter total in Wh
def SetZaehlerstand(setStand):
global zaehlerStand
zaehlerStand = float(setStand)
file = open("powercount.txt", "w")
file.write(str(zaehlerStand))
file.close()
# Adds power consumption to power meter total in Wh
def AddZaehlerstand(power):
global zaehlerStand
SetZaehlerstand(zaehlerstand + float(power))
#####################################################################
# Helper-method to allow error handling and output in asyncio
def set_global_exception():
def handle_exception(loop, context):
Logger.LogMessage("Fatal error: " + str(context["exception"]))
import sys
sys.print_exception(context["exception"])
sys.exit()
Reboot()
loop = asyncio.get_event_loop()
loop.set_exception_handler(handle_exception)
# Main method for all the Watering-handling
async def PowermeterHandling():
Logger.LogMessage("Powermeter handling started")
while True:
ReadTemp()
PowerMeasurement()
SendAPI()
await asyncio.sleep(0.01)
# Main method for the API
html = """<!DOCTYPE html>
<html>
<head> <title>Powermeter</title> </head>
<body> <h1>Powermeter</h1>
<p>%s</p>
</body>
</html>"""
json = """{ "Powermeter": { "%s" } }"""
async def APIHandling(reader, writer):
request_line = await reader.readline()
while await reader.readline() != b"\r\n":
pass
request = str(request_line)
try:
request = request.split()[1]
except IndexError:
pass
client_ip = writer.get_extra_info('peername')[0]
if request != "/favicon.ico":
Logger.LogMessage("API request: " + request + " - from client IP: " + client_ip)
if (configs['api_client_ip'] != "") and (configs['api_client_ip'] != client_ip):
Logger.LogMessage("Unauthorized client! Aborting API Handling now.")
stateis = "<b>Error 401:</b> Client '" + client_ip + "' is not authorized to use the API!<br><br>Set authorized client IP in configs.py!"
response = html % stateis
writer.write('HTTP/1.0 401 Unauthorized\r\nContent-type: text/html\r\n\r\n')
else:
req = request.split('/')
stateis = ""
if (len(req) == 3 or len(req) == 4 or len(req) == 5):
if (req[1] == secrets['api']):
if (req[2] == "ping"):
stateis = "ping: OK"
elif (req[2] == "stand"):
stateis = "Zaehlerstand: " + GetZaehlerstand() + " kWh"
elif (req[2] == "stats"):
stateis = "IP address: " + Networking.GetIPAddress() + "<br>MAC address: " + Networking.GetMACAddress() + "<br>Hostname: " + configs['hostname'] + "<br>API Port: " + str(configs['api_port']) + "<br>Uptime (h:m): " + Uptime() + "<br>Date/Time: " + TimeUtils.DateTimeNow() + "<br>Version: " + version + "<br>GMT Timezone Offset (hours): " + str(configs['gmt_offset']) + "<br>Auto summertime: " + str(configs['auto_summertime']) + "<br>Housekeep logfiles after days: " + str(configs['log_housekeeping_days']) + "<br>CPU frequency (MHz): " + str(machine.freq()/1000000) + "<br>Temperature (&#176;C): " + "%.2f" % temperature + "<br>Powermeter count (kWh): " + GetZaehlerstand() + "<br>Interval to store via API in DB (minutes): " + str(configs['db_api_interval_minutes']) + "<br>Ticks per kWh: " + str(configs['ticks_per_kWh'])
elif (req[2] == "reboot"):
stateis = "Rebooting device: now..."
Reboot()
elif (req[2] == "set"):
if (len(req) == 4):
if (IsInt(req[3])):
SetZaehlerstand(req[3])
stateis = "Zaehlerstand: " + GetZaehlerstand() + " kWh"
else:
stateis = "<b>Error:</b> Parameter for set not an integer!"
else:
stateis = "<b>Error:</b> Unknown command!"
elif (req[2] == "logs"):
if (len(req) >= 4 and req[3] != "json"):
if (IsInt(req[3])):
stateis = Logger.LastLogs(int(req[3]))
else:
stateis = "<b>Error:</b> Parameter for log length not an integer!"
else:
stateis = Logger.LastLogs(10)
else:
stateis = "<b>Error:</b> Unknown command!"
else:
stateis = "<b>Error:</b> API key is invalid!"
if ((len(req) == 4 and req[3] == "json") or (len(req) == 5 and req[4] == "json")):
if (req[2] != "logs"):
stateis = stateis.replace(": ", "\":\"")
stateis = stateis.replace("<br>", "\", \"")
stateis = stateis.replace("&#176;", "°")
else:
stateis = stateis.replace(";", "\":\"")
stateis = stateis.replace("<br>", "\", \"")
stateis = stateis.replace("\n", "").replace("\r", "")
response = json % stateis
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/json\r\n\r\n')
else:
response = html % stateis
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
else:
stateis = "<b>Error 400:</b> Invalid usage of API!<br><br><u>Usage:</u> http://servername/api_key/command[/json]<br><br><u>Commands:</u><ul><li>triggerdoor</li><li>triggerlight</li><li>togglepartymode</li><li>partymodeon</li><li>partymodeoff</li><li>partymodestate</li><li>ping</li><li>stats</li><li>reboot</li><li>ringdoor</li><li>ringfrontdoor</li></ul><br><u>API Key:</u> set 'api' in secrets.py file."
response = html % stateis
writer.write('HTTP/1.0 400 Bad Request\r\nContent-type: text/html\r\n\r\n')
writer.write(response)
await writer.drain()
await writer.wait_closed()
# Main method for daily housekeeping
async def Housekeeper():
Logger.LogMessage("Housekeeper started")
while True:
Logger.LogMessage("Housekeeper is performing actions")
Logger.Housekeeping()
Logger.LogMessage("Housekeeper is performing NTP sync")
NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime'])
Logger.LogMessage("Housekeeper has finished its jobs")
await asyncio.sleep(86400)
# Main entry point after booting
async def Main():
global autoState
set_global_exception()
Logger.LogMessage("Entering MainLoop")
boottime = time.time()
loop = asyncio.get_event_loop()
Logger.LogMessage("Setting up API on port " + str(configs['api_port']) + " with key " + secrets['api'])
loop.create_task(asyncio.start_server(APIHandling, Networking.GetIPAddress(), configs['api_port']))
Logger.LogMessage("API started")
Logger.LogMessage("Booting complete with Firmware " + version)
CheckPowermeterCount()
loop.create_task(PowermeterHandling())
loop.create_task(Housekeeper())
loop.run_forever()
# Booting the device
def Boot():
Networking.Connect(configs['disable_wifi_powersavingmode'])
if (Networking.Status()):
if (NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime'])):
Logger.DisableTempLogfile()
else:
time.sleep(3)
Reboot()
#####################################################################
Boot()
try:
asyncio.run(Main())
except KeyboardInterrupt:
Logger.LogMessage("Shutdown.")
finally:
asyncio.new_event_loop()

5
secrets.py Normal file
View File

@ -0,0 +1,5 @@
secrets = {
'ssid': 'your-wifi-name',
'pw': 'your-wifi-password',
'api': 'test',
}