Moved Python stuff to dedicated repo, preparing to clean up packagenaming

This commit is contained in:
Jarno Rankinen 2023-03-15 21:17:59 +02:00
parent ea0a62598a
commit 208d7d05d7
11 changed files with 0 additions and 401 deletions

View File

@ -1,6 +0,0 @@
*/__pycache__/
bin/
include/
lib/
lib64
share/

View File

@ -1 +0,0 @@
../index.html

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/static/tabledata.css">
<script src="/static/tabledata.js"></script>
<meta charset="UTF-8">
<title>Enervent Pingvin Kotilämpö</title>
</head>
<body onload="getData()">
<table id="data">
<caption>Coil values at <span id="time"></span></caption>
<thead><th>Address</th><th>Value</th><th>Symbol</th><th>Description</th></thead>
<tbody id="coildata"></tbody>
</table>
</body>
</html>

View File

@ -1 +0,0 @@
../index.html

View File

@ -1,17 +0,0 @@
.addr {
text-align: center;
}
.val {
text-align: center;
}
#data {
padding: 2pt;
border-collapse: collapse;
}
thead {
border-bottom: 1px solid;
text-align: left;
}
td {
padding: 2pt;
}

View File

@ -1,52 +0,0 @@
function zeroPad(number) {
return ("0" + number).slice(-2)
}
function getData() {
now = new Date()
Y = now.getFullYear()
m = now.getMonth()
d = now.getDate()
H = zeroPad(now.getHours())
M = zeroPad(now.getMinutes())
S = zeroPad(now.getSeconds())
document.getElementById('time').innerHTML = `${Y}-${m}-${d} ${H}:${M}:${S}`
error = false
// The same index.html is used for both coil and register data,
// change api url based on which we're looking at
if (document.location.pathname == "/coils/") {
url = "/api/v1/coils"
}
else if (document.location.pathname == "/registers/") {
url = "/api/v1/registers"
}
else {
document.getElementById("data").innerHTML = 'Page not found'
error = true
}
if (!error) {
// Fetch data from API
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`Error fetching data: ${response.status}`)
}
return response.json()
})
.then((data) => {
// Populate table
document.getElementById('coildata').innerHTML = "";
for (n=0; n<data.length; n++) {
tablerow = `<tr><td class="addr" id="addr_${data[n].address}">${data[n].address}</td>\
<td class ="val" id="value_${data[n].address}">${Number(data[n].value)}</td>\
<td class="symbol" id="symbol_${data[n].address}">${data[n].symbol}</td>\
<td class="desc" id="description_${data[n].address}">${data[n].description}</td></tr>`
document.getElementById('coildata').innerHTML += tablerow
}
});
}
// Using setTimeout instead of setInterval to avoid possible connection issues
// There's no need to update exactly every 5 seconds, the skew is fine
setTimeout(getData, 1*1000);
}

View File

@ -1,59 +0,0 @@
upstream enervent-ctrl {
server localhost:8888;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /home/jarno/enervent-ctrl/enervent-ctrl-python/html;
index index.html;
server_name _;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
if ($http_user_agent ~* "^curl") {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://enervent-ctrl;
}
try_files $uri $uri/ =404;
}
#location ~ /static|/coils|/registers {
# root /home/jarno/enervent-ctrl/enervent-ctrl-python/html;
#}
location ~ /api {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://enervent-ctrl;
}
}

View File

@ -1,3 +0,0 @@
home = /usr/bin
include-system-site-packages = false
version = 3.9.2

View File

@ -1,11 +0,0 @@
click==8.1.3
Flask==2.2.2
importlib-metadata==6.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
minimalmodbus==2.0.1
pyserial==3.5
waitress==2.1.2
Werkzeug==2.2.3
zipp==3.11.0

View File

@ -1,188 +0,0 @@
import minimalmodbus
import logging
from flask import jsonify
from threading import Lock
from time import sleep
class PingvinCoil():
"""Single coil data structure"""
def __init__(self, symbol="-", description="-"):
self.symbol = symbol
self.value = False
self.description = description
self.reserved = symbol == "-" and description == "-"
def serialize(self):
return {
"value": self.value,
"symbol": self.symbol,
"description": self.description,
"reserved": self.reserved
}
def get(self):
return jsonify(self.serialize())
def flip(self):
self.value = not self.value
class PingvinCoils():
"""Class for handling Modbus coils"""
## coil descriptions and symbols courtesy of Ensto Enervent
## https://doc.enervent.com/out/out.ViewDocument.php?documentid=59
coils = [
PingvinCoil("COIL_STOP", "Stop"),
PingvinCoil("COIL_AWAY", "Away mode"),
PingvinCoil("COIL_AWAY_L", "Away Long mode"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_MAX_H", "Max Heating"),
PingvinCoil("COIL_MAX_C", "Max Cooling"),
PingvinCoil("COIL_CO_BOOST_EN", "CO2 boost"),
PingvinCoil("COIL_RH_BOOST_EN", "Relative humidity boost"),
PingvinCoil("COIL_M_BOOST", "Manual boost 100%"),
PingvinCoil("COIL_TEMP_BOOST_EN", "Temperature boost"),
PingvinCoil("COIL_SNC", "Summer night cooling"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_AWAY_H", "Heating enabled/disabled in AWAY mode"),
PingvinCoil("COIL_AWAY_C", "Cooling enabled/disabled in AWAY mode"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_LTO_ON", "Heat recycler state (running=1, stopped = 0)"),
PingvinCoil(),
PingvinCoil("COIL_HEAT_ON", "After heater element state (On = 1, Off = 0)"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_TEMP_DECREASE", "Temperature decrease function"),
PingvinCoil("COIL_OVERTIME", "Programmatic equivalent of OVERTIME digital input"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_ECO_MODE", "Eco mode"),
PingvinCoil("COIL_ALARM_A", "Alarm of class A active"),
PingvinCoil("COIL_ALARM_B", "Alarm of class B active"),
PingvinCoil("COIL_CLK_PROG", "Clock program is currently active"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_SILENT_MODE", "Silent mode"),
PingvinCoil("COIL_STOP_SLP_COOLING", "Electrical heater cool-off function enabled when the machine has stopped"),
PingvinCoil("COIL_SERVICE_EN", "Service reminder"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil("COIL_COOLING_EN", "Active cooling function enabled"),
PingvinCoil("COIL_LTO_EN", "N/A"),
PingvinCoil("COIL_HEATING_EN", "Active heating function enabled"),
PingvinCoil("COIL_LTO_DEFROST_EN", "HRC defrosting function enabled during winter season"),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil(),
PingvinCoil()
]
def __init__(self, device, semaphore, debug=False):
self.pingvin = device
self.semaphore = semaphore
def __getitem__(self, item):
return self.coils[item]
def update(self, debug=False):
"""Fetch all coils values from device"""
self.pingvin.serial.timeout = 0.2
self.pingvin.debug = debug
if debug: logging.info(f"{len(self.coils)} coils registered")
self.semaphore.acquire()
curvalues = self.pingvin.read_bits(0,len(self.coils),1)
self.semaphore.release()
for i, coil in enumerate(self.coils):
self.coils[i].value = bool(curvalues[i])
if debug: logging.info("Coil values read succesfully\n")
def fetchValue(self, address, debug=False):
"""Update single coil value from device and return it"""
self.pingvin.debug = debug
if debug: logging.debug("Updating coil value from device to cache")
self.semaphore.acquire()
self.coils[address].value = bool(self.pingvin.read_bit(address, 1))
self.semaphore.release()
return self.value(address, debug)
def value(self, address, debug=False):
"""Get single local coil value"""
if debug: logging.debug("Reading coil value from cache")
return self.coils[address].value
def print(self, debug=False):
"""Human-readable print of all coil values"""
coilvals = ""
for i, coil in enumerate(self.coils):
coilvals = coilvals + f"Coil {i : <{4}}{coil.value : <{2}} {coil.symbol : <{25}}{coil.description}\n"
return coilvals
def serialize(self, include_reserved=False):
"""Returns coil values as parseable Python object"""
coilvals = []
for i, coil in enumerate(self.coils):
if not coil.reserved or include_reserved:
coil = coil.serialize()
coil['address'] = i
coilvals.append(coil)
return coilvals
def get(self, include_reserved=False, live=False, debug=False):
"""Return all coil values in JSON format"""
if live: self.update(debug)
return jsonify(self.serialize(include_reserved))
def write(self, address):
self.semaphore.acquire()
self.pingvin.write_bit(address, int(not self.coils[address].value))
if self.pingvin.read_bit(address, 1) != self.coils[address].value:
self.coils[address].flip()
self.semaphore.release()
return True
self.semaphore.release()
return False
class PingvinKL():
"""Class for communicating with an Enervent Pinvin Kotilämpö ventilation/heating unit"""
def __init__(self, serialdevice='/dev/ttyS0', modbusaddr=1, debug=False):
self.semaphore = Lock()
self.pingvin = minimalmodbus.Instrument(serialdevice, modbusaddr)
self.coils = PingvinCoils(self.pingvin, self.semaphore, debug)
self.run = False
def monitor(self, interval=15, debug=False):
if not self.run: # Prevent starting two monitor threads
self.run = True
logging.info("Starting data monitor loop")
while self.run:
if debug: logging.info("Data monitor updating coil data")
self.coils.update(debug)
sleep(interval)

View File

@ -1,47 +0,0 @@
#!/usr/bin/env python
import logging
from PingvinKL import PingvinKL
from flask import Flask, request
import threading
from waitress import serve
VERSION = "0.0.1"
DEBUG = False
## Logging configuration
log = logging.getLogger(__name__)
if DEBUG:
dbglevel = logging.DEBUG
else:
dbglevel = logging.INFO
logging.basicConfig(
level=dbglevel,
format='%(asctime)s %(message)s',
datefmt='%y/%m/%d %H:%M:%S'
)
pingvin = PingvinKL('/dev/ttyS0',1,debug=DEBUG)
app = Flask(__name__)
@app.route('/api/v1/coils')
def get_all():
return pingvin.coils.get(include_reserved=request.args.get('include_reserved'),live=request.args.get('live'),debug=DEBUG)
@app.route('/api/v1/coils/<int:address>', methods=["GET","PUT"])
def coil(address):
if request.method == 'GET':
coil = pingvin.coils[address].get()
return coil
elif request.method == 'PUT':
return {"success": pingvin.coils.write(address)}
@app.route('/')
def dump():
return pingvin.coils.print(debug=DEBUG)
if __name__ == "__main__":
log.info(f"Starting enervent-logger {VERSION}")
datathread = threading.Thread(target=pingvin.monitor, kwargs={"interval": 2, "debug": DEBUG})
datathread.start()
# app.run(host='0.0.0.0', port=8888)
serve(app, listen='*:8888', trusted_proxy='127.0.0.1', trusted_proxy_headers="x-forwarded-for x-forwarded-host x-forwarded-proto x-forwarded-port")