Merge branch 'master' of https://git.yannweb.net/cli/pyHeatpump
This commit is contained in:
commit
681773098b
11 changed files with 340 additions and 116 deletions
1
config/42-rs485.rules
Normal file
1
config/42-rs485.rules
Normal file
|
|
@ -0,0 +1 @@
|
|||
SUBSYSTEMS=="usb-serial", DRIVERS=="ftdi_sio", SYMLINK+="rs485"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
Description=Timer to launch pyheatpump fetch every minute
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=1m
|
||||
OnStartSec=15sec
|
||||
OnUnitInactiveSec=1m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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__())
|
||||
|
|
|
|||
|
|
@ -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
210
staging/dev/INSTALL.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue