This commit is contained in:
nas 2020-12-02 22:28:57 +01:00
commit 681773098b
11 changed files with 340 additions and 116 deletions

1
config/42-rs485.rules Normal file
View file

@ -0,0 +1 @@
SUBSYSTEMS=="usb-serial", DRIVERS=="ftdi_sio", SYMLINK+="rs485"

View file

@ -1,12 +1,13 @@
[Unit]
Description=pyHeatpump fetch - retrieve the data from the serial port with modbus
Requires=serial-getty@ttyUSB0
Wants=dev-ttyUSB0.device
[Service]
Type=simple
WorkingDirectory=/var/lib/pyheatpump/
Environment="LOGLEVEL=INFO"
ExecStart=/usr/bin/env pyheatpump fetch
ExecStart=/usr/bin/env pyheatpump fetch -t D -t A -t I
ExecStop=/usr/bin/env pyheatpump supervise --since
KillMode=mixed
TimeoutStopSec=30
PrivateTmp=true

View file

@ -2,7 +2,8 @@
Description=Timer to launch pyheatpump fetch every minute
[Timer]
OnActiveSec=1m
OnStartSec=15sec
OnUnitInactiveSec=1m
[Install]
WantedBy=timers.target

View file

@ -1,12 +0,0 @@
[Unit]
Description=pyHeatpump supervise - communicates with the supervisor
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/lib/pyheatpump/
Environment="LOGLEVEL=INFO"
ExecStart=/usr/bin/env pyheatpump supervise --since
KillMode=mixed
TimeoutStopSec=30
PrivateTmp=true

View file

@ -1,9 +0,0 @@
[Unit]
Description=Timer to launch pyheatpump supervise every minute, with an offset of 30 seconds
[Timer]
OnBooSec=90s
OnActiveSec=1m
[Install]
WantedBy=timers.target

View file

@ -60,88 +60,98 @@ def fetch(type):
from pyheatpump import modbus
if type is None:
var_types = VariableType.getall()
else:
var_types = {}
for label, var_type in VariableType.getall().items():
if label in type or var_type.slabel in type:
var_types[label] = var_type
try:
if type is None:
var_types = VariableType.getall()
else:
var_types = {}
for label, var_type in VariableType.getall().items():
if label in type or var_type.slabel in type:
var_types[label] = var_type
# Analog - float
if 'Analog' in var_types.keys():
analog = var_types['Analog']
logger.info('Read analog variables in registers [{}, {}]'.format(
analog.start_address, analog.end_address
))
res = modbus.read_holding_registers(analog.start_address, analog.end_address)
# Analog - float
if 'Analog' in var_types.keys():
analog = var_types['Analog']
logger.info('Read analog variables in registers [{}, {}]'.format(
analog.start_address, analog.end_address
))
res = modbus.read_holding_registers(analog.start_address, analog.end_address)
for r in range(len(res)):
var = Variable(**{
'type': analog,
'address': r + analog.start_address})
logger.debug(f'analog length : {len(res)}')
for r in range(len(res)):
var = Variable(**{
'type': analog,
'address': r + analog.start_address})
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
# Integer - int
if 'Integer' in var_types.keys():
integer = var_types['Integer']
logger.info('Read integer variables in registers [{}, {}]'.format(
integer.start_address, integer.end_address
))
res = modbus.read_holding_registers(integer.start_address, integer.end_address)
# Integer - int
if 'Integer' in var_types.keys():
integer = var_types['Integer']
logger.info('Read integer variables in registers [{}, {}]'.format(
integer.start_address, integer.end_address
))
res = modbus.read_holding_registers(integer.start_address, integer.end_address)
for r in range(len(res)):
var = Variable(**{
'type': integer,
'address': r + integer.start_address})
logger.debug(f'integer length : {len(res)}')
for r in range(len(res)):
var = Variable(**{
'type': integer,
'address': r + integer.start_address})
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
# Digital - bool
if 'Digital' in var_types.keys():
digital = var_types['Digital']
logger.info('Read digital variables in coils [{}, {}]'.format(
digital.start_address, digital.end_address
))
res = modbus.read_coils(digital.start_address, digital.end_address)
# Digital - bool
if 'Digital' in var_types.keys():
digital = var_types['Digital']
logger.info('Read digital variables in coils [{}, {}]'.format(
digital.start_address, digital.end_address
))
res = modbus.read_coils(digital.start_address, digital.end_address)
for r in range(len(res)):
var = Variable(**{
'type': digital,
'address': r + digital.start_address})
logger.debug(f'digital length : {len(res)}')
for r in range(len(res)):
var = Variable(**{
'type': digital,
'address': r + digital.start_address})
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
if not var.exists():
logger.info('Insert variable {}:{}'.format(
var.type, var.address))
var.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
val = VariableValue(**{
'type': var.type,
'address': var.address,
'value': res[r]})
val.insert()
logger.info('Successfully read all variables')
except Exception as exc:
logger.error(exc)
if modbus.serial_conn:
modbus.serial_conn.close()
logger.info('Successfully read all variables')
@click.option('--since', is_flag=True)
@ -161,8 +171,12 @@ def supervise(since):
last_update = 0
else:
last_update = get_last_update()
h = Heatpump(mac_address, last_update)
post_packets = [
Heatpump(mac_address, last_update, ['Analog']).__dict__(),
Heatpump(mac_address, last_update, ['Digital']).__dict__(),
Heatpump(mac_address, last_update, ['Integer']).__dict__()
]
base_url = {
'scheme':config.get('supervisor', 'scheme'),
@ -190,23 +204,26 @@ def supervise(since):
}
logger.info(build_url(post_url))
data = h.__dict__()
try:
logger.debug(json.dumps(data))
except Exception as e:
print(e)
sys.exit(1)
for packet in post_packets:
try:
logger.debug(json.dumps(packet))
except Exception as e:
print(e)
sys.exit(1)
logger.debug('Will send %s', data)
post_resp = requests.post(
url=build_url(post_url),
json=data,
verify=False
)
if post_resp.status_code == 200:
logger.info('POST to supervisor succeeded')
set_last_update(int(datetime.now().strftime('%s')))
for packet in post_packets:
logger.debug('Will send %s', packet)
post_resp = requests.post(
url=build_url(post_url),
json=packet,
verify=False
)
if post_resp.status_code == 200:
logger.info('POST to supervisor succeeded')
set_last_update(int(datetime.now().strftime('%s')))
get_path = '/'.join((
config.get('supervisor', 'get_path'),

View file

@ -55,7 +55,8 @@ class DB():
@staticmethod
def sql(query, params={}):
logger.debug(query, params)
logger.debug('Query : %s', query)
logger.debug('Params : %s', params)
if not DB.connect():
raise Exception('Can\'t connect to DB')

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3
import os
from serial import Serial
from serial.serialutil import SerialException
from umodbus.client.serial import rtu
@ -16,10 +17,15 @@ def connect():
global serial_conn
if serial_conn is None:
print('Connecting to serial port *{}*'.format(
config.get('heatpump', 'serial_port')))
real_serial_port = os.path.realpath(
config.get('heatpump', 'serial_port'))
print('Connecting to serial port *{}* ({})'.format(
config.get('heatpump', 'serial_port'),
real_serial_port))
serial_conn = Serial(
port=config.get('heatpump', 'serial_port'),
port=real_serial_port,
baudrate=config.get('heatpump', 'baudrate'),
bytesize=8,
parity='N',

View file

@ -14,10 +14,17 @@ class Heatpump:
var_types: Dict
last_update: int = None
def __init__(self, mac_address, last_update):
def __init__(self, mac_address, last_update, types=[]):
self.mac_address = mac_address
self.last_update = last_update
self.var_types = VariableType.getall()
if len(types):
self.var_types = {
key: val
for key,val in VariableType.getall().items()
if key in types
}
else:
self.var_types = VariableType.getall()
def __str__(self):
return str(self.__dict__())

View file

@ -97,7 +97,8 @@ class Variable(RowClass):
AND var.last_update > :since""",
params)
if row['address'] and row['value']
if 'address' in row.keys() and 'value' in row.keys() and
row['value'] is not None
}

210
staging/dev/INSTALL.md Normal file
View file

@ -0,0 +1,210 @@
# Install instructions
## Resources
You'll need a rasbpiOs image, and the kernel and dtb for qemu
## Procedure
### Enlarge img
The downloaded img is resized on the SD card during the installation. We will need more space on the img. Qemu has command for that :
```
qemu-img resize rpi.img +1G
```
### After booting
Log-in : rpi
Password : raspberry
Login as root :
```
sudo su
```
Create udev rules :
```
cat << EOF > /etc/udev/rules.d/42-rs485.rules
SUBSYSTEMS=="usb-serial", DRIVERS=="ftdi_sio", SYMLINK+="rs485"
EOF
```
#### Dependencies
```
apt install git sqlite3 python3-pip python3-click python3-requests python3-netifaces python3-serial
```
#### Deployment
Deploy pyHeatpump. Replace MONITORING_SERVER with the address of your server :
```
mkdir /var/lib/pyheatpump
echo 'pip3 install --upgrade git+https://git.yannweb.net/cli/pyHeatpump.git' > /var/lib/pyheatpump/pyheatpump_upgrade.sh
chmod +x /var/lib/pyheatpump/pyheatpump_upgrade.sh
/var/lib/pyheatpump/pyheatpump_upgrade.sh
```
#### Database initialisation
```
cat << EOF > /var/lib/pyheatpump/pyheatpump.sql
CREATE TABLE IF NOT EXISTS var_type (
slabel CHAR(1) UNIQUE PRIMARY KEY,
label VARCHAR(10) UNIQUE,
type VARCHAR(10) NOT NULL,
start_address INT NOT NULL DEFAULT 0,
end_address INT NOT NULL DEFAULT 250
);
CREATE TABLE IF NOT EXISTS variable (
type CHAR(1) NOT NULL,
address INT NOT NULL,
unit VARCHAR(5) NULL,
last_update INT NULL,
FOREIGN KEY (type) REFERENCES var_type(slabel)
ON DELETE CASCADE,
PRIMARY KEY(type, address)
);
CREATE TABLE IF NOT EXISTS var_value (
type CHAR(1) NOT NULL,
address INT NOT NULL,
time INT DEFAULT (strftime('%s', datetime('now'))),
value INT NOT NULL,
FOREIGN KEY (type) REFERENCES variable(type)
ON DELETE CASCADE,
FOREIGN KEY (address) REFERENCES variable(address)
ON DELETE CASCADE,
PRIMARY KEY(type, address, time)
);
INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
'A', 'Analog', 'float', 1, 5000);
INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
'I', 'Integer', 'int', 5002, 10002);
INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
'D', 'Digital', 'bool', 1, 2048);
CREATE TRIGGER variable_last_update AFTER INSERT ON var_value
FOR EACH ROW
BEGIN
UPDATE variable
SET last_update = NEW.time
WHERE
type = NEW.type
AND
address = NEW.address;
END;
EOF
sqlite3 -init /var/lib/pyheatpump/pyheatpump.sql /var/lib/pyheatpump/pyheatpump.sqlite3
```
#### PyHeatpump configuration file
```
cat << EOF > /var/lib/pyheatpump/pyheatpump.ini
[heatpump]
database = /var/lib/pyheatpump/pyheatpump.sqlite3
serial_port = /dev/rsa485
[supervisor]
scheme = https
host = MONITORING_SERVER
port = 8081
post_path = /Symfony/web/app_dev.php/boardws/insert
get_path = /Symfony/web/app_dev.php/boardws/orders
interval = 60
heatpump_id = 42
[api]
host = 0.0.0.0
port = 80
EOF
```
#### API Service (no-need in production)
```
cat << EOF > /var/lib/pyheatpump/pyheatpump_api.service
[Unit]
Description=API to fetch data from heatpump with HTTP
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/lib/pyheatpump/
Environment="LOGLEVEL=INFO"
ExecStart=/usr/bin/env pyheatpump run
KillMode=mixed
TimeoutStopSec=30
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
```
#### Fetch Service
```
cat << EOF > /var/lib/pyheatpump/pyheatpump_fetch.service
[Unit]
Description=pyHeatpump fetch - retrieve the data from the serial port with modbus
Wants=dev-rs485.device
[Service]
Type=simple
WorkingDirectory=/var/lib/pyheatpump/
Environment="LOGLEVEL=INFO"
ExecStart=/usr/bin/env pyheatpump fetch -t D -t A -t I
ExecStop=/usr/bin/env pyheatpump supervise --since
KillMode=mixed
TimeoutStopSec=30
PrivateTmp=true
EOF
```
#### Fetch Timer
```
cat << EOF > /var/lib/pyheatpump/pyheatpump_fetch.service
[Unit]
Description=Timer to launch pyheatpump fetch every minute
[Timer]
OnStartSec=15sec
OnUnitInactiveSec=1m
[Install]
WantedBy=timers.target
EOF
```
#### Installation of the services
```
cd /etc/systemd/system
ln -s /var/lib/pyheatpump/pyheatpump_api.service
ln -s /var/lib/pyheatpump/pyheatpump_fetch.service
ln -s /var/lib/pyheatpump/pyheatpump_fetch.timer
systemctl daemon-reload
systemctl enable pyheatpump_api.service pyheatpump_fetch.timer pyheatpump_supervise.timer
```
You may now reboot to see if everything works.