nas 3 years ago
parent
commit
681773098b

+ 1
- 0
config/42-rs485.rules View File

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

+ 3
- 2
config/pyheatpump_fetch.service View File

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

+ 2
- 1
config/pyheatpump_fetch.timer View File

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

+ 0
- 12
config/pyheatpump_supervise.service View File

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

+ 0
- 9
config/pyheatpump_supervise.timer View File

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

+ 117
- 100
pyheatpump/cli.py View File

@@ -60,88 +60,98 @@ def fetch(type):
60 60
 
61 61
     from pyheatpump import modbus
62 62
 
63
-    if type is None:
64
-        var_types = VariableType.getall()
65
-    else:
66
-        var_types = {}
67
-        for label, var_type in VariableType.getall().items():
68
-            if label in type or var_type.slabel in type:
69
-                var_types[label] = var_type
70
-
71
-    # Analog - float
72
-    if 'Analog' in var_types.keys():
73
-        analog = var_types['Analog']
74
-        logger.info('Read analog variables in registers [{}, {}]'.format(
75
-            analog.start_address, analog.end_address
76
-        ))
77
-        res = modbus.read_holding_registers(analog.start_address, analog.end_address)
78
-
79
-        for r in range(len(res)):
80
-            var = Variable(**{
81
-                'type': analog,
82
-                'address': r + analog.start_address})
83
-
84
-            if not var.exists():
85
-                logger.info('Insert variable {}:{}'.format(
86
-                    var.type, var.address))
87
-                var.insert()
88
-
89
-            val = VariableValue(**{
90
-                'type': var.type,
91
-                'address': var.address,
92
-                'value': res[r]})
93
-            val.insert()
94
-
95
-
96
-    # Integer - int
97
-    if 'Integer' in var_types.keys():
98
-        integer = var_types['Integer']
99
-        logger.info('Read integer variables in registers [{}, {}]'.format(
100
-            integer.start_address, integer.end_address
101
-        ))
102
-        res = modbus.read_holding_registers(integer.start_address, integer.end_address)
103
-
104
-        for r in range(len(res)):
105
-            var = Variable(**{
106
-                'type': integer,
107
-                'address': r + integer.start_address})
108
-
109
-            if not var.exists():
110
-                logger.info('Insert variable {}:{}'.format(
111
-                    var.type, var.address))
112
-                var.insert()
113
-
114
-            val = VariableValue(**{
115
-                'type': var.type,
116
-                'address': var.address,
117
-                'value': res[r]})
118
-            val.insert()
119
-
120
-    # Digital - bool
121
-    if 'Digital' in var_types.keys():
122
-        digital = var_types['Digital']
123
-        logger.info('Read digital variables in coils [{}, {}]'.format(
124
-            digital.start_address, digital.end_address
125
-        ))
126
-        res = modbus.read_coils(digital.start_address, digital.end_address)
127
-
128
-        for r in range(len(res)):
129
-            var = Variable(**{
130
-                'type': digital,
131
-                'address': r + digital.start_address})
132
-
133
-            if not var.exists():
134
-                logger.info('Insert variable {}:{}'.format(
135
-                    var.type, var.address))
136
-                var.insert()
137
-
138
-            val = VariableValue(**{
139
-                'type': var.type,
140
-                'address': var.address,
141
-                'value': res[r]})
142
-            val.insert()
143
-
144
-    logger.info('Successfully read all variables')
63
+    try:
64
+        if type is None:
65
+            var_types = VariableType.getall()
66
+        else:
67
+            var_types = {}
68
+            for label, var_type in VariableType.getall().items():
69
+                if label in type or var_type.slabel in type:
70
+                    var_types[label] = var_type
71
+
72
+        # Analog - float
73
+        if 'Analog' in var_types.keys():
74
+            analog = var_types['Analog']
75
+            logger.info('Read analog variables in registers [{}, {}]'.format(
76
+                analog.start_address, analog.end_address
77
+            ))
78
+            res = modbus.read_holding_registers(analog.start_address, analog.end_address)
79
+
80
+            logger.debug(f'analog length : {len(res)}')
81
+            for r in range(len(res)):
82
+                var = Variable(**{
83
+                    'type': analog,
84
+                    'address': r + analog.start_address})
85
+
86
+                if not var.exists():
87
+                    logger.info('Insert variable {}:{}'.format(
88
+                        var.type, var.address))
89
+                    var.insert()
90
+
91
+                val = VariableValue(**{
92
+                    'type': var.type,
93
+                    'address': var.address,
94
+                    'value': res[r]})
95
+                val.insert()
96
+
97
+
98
+        # Integer - int
99
+        if 'Integer' in var_types.keys():
100
+            integer = var_types['Integer']
101
+            logger.info('Read integer variables in registers [{}, {}]'.format(
102
+                integer.start_address, integer.end_address
103
+            ))
104
+            res = modbus.read_holding_registers(integer.start_address, integer.end_address)
105
+
106
+            logger.debug(f'integer length : {len(res)}')
107
+            for r in range(len(res)):
108
+                var = Variable(**{
109
+                    'type': integer,
110
+                    'address': r + integer.start_address})
111
+
112
+                if not var.exists():
113
+                    logger.info('Insert variable {}:{}'.format(
114
+                        var.type, var.address))
115
+                    var.insert()
116
+
117
+                val = VariableValue(**{
118
+                    'type': var.type,
119
+                    'address': var.address,
120
+                    'value': res[r]})
121
+                val.insert()
122
+
123
+        # Digital - bool
124
+        if 'Digital' in var_types.keys():
125
+            digital = var_types['Digital']
126
+            logger.info('Read digital variables in coils [{}, {}]'.format(
127
+                digital.start_address, digital.end_address
128
+            ))
129
+            res = modbus.read_coils(digital.start_address, digital.end_address)
130
+
131
+            logger.debug(f'digital length : {len(res)}')
132
+            for r in range(len(res)):
133
+                var = Variable(**{
134
+                    'type': digital,
135
+                    'address': r + digital.start_address})
136
+
137
+                if not var.exists():
138
+                    logger.info('Insert variable {}:{}'.format(
139
+                        var.type, var.address))
140
+                    var.insert()
141
+
142
+                val = VariableValue(**{
143
+                    'type': var.type,
144
+                    'address': var.address,
145
+                    'value': res[r]})
146
+                val.insert()
147
+
148
+        logger.info('Successfully read all variables')
149
+    except Exception as exc:
150
+        logger.error(exc)
151
+
152
+    if modbus.serial_conn:
153
+        modbus.serial_conn.close()
154
+
145 155
 
146 156
 
147 157
 @click.option('--since', is_flag=True)
@@ -161,8 +171,12 @@ def supervise(since):
161 171
         last_update = 0
162 172
     else:
163 173
         last_update = get_last_update()
164
-
165
-    h = Heatpump(mac_address, last_update)
174
+    
175
+    post_packets = [
176
+        Heatpump(mac_address, last_update, ['Analog']).__dict__(),
177
+        Heatpump(mac_address, last_update, ['Digital']).__dict__(),
178
+        Heatpump(mac_address, last_update, ['Integer']).__dict__()
179
+    ]
166 180
 
167 181
     base_url = {
168 182
         'scheme':config.get('supervisor', 'scheme'),
@@ -190,23 +204,26 @@ def supervise(since):
190 204
     }
191 205
 
192 206
     logger.info(build_url(post_url))
193
-    data = h.__dict__()
194
-    try:
195
-        logger.debug(json.dumps(data))
196
-    except Exception as e:
197
-        print(e)
198
-        sys.exit(1)
199 207
 
200
-
201
-    logger.debug('Will send %s', data)
202
-    post_resp = requests.post(
203
-        url=build_url(post_url),
204
-        json=data,
205
-        verify=False
206
-    )
207
-    if post_resp.status_code == 200:
208
-        logger.info('POST to supervisor succeeded')
209
-        set_last_update(int(datetime.now().strftime('%s')))
208
+    for packet in post_packets:
209
+        try:
210
+            logger.debug(json.dumps(packet))
211
+        except Exception as e:
212
+            print(e)
213
+            sys.exit(1)
214
+
215
+
216
+    for packet in post_packets:
217
+        logger.debug('Will send %s', packet)
218
+        post_resp = requests.post(
219
+            url=build_url(post_url),
220
+            json=packet,
221
+            verify=False
222
+        )
223
+        if post_resp.status_code == 200:
224
+            logger.info('POST to supervisor succeeded')
225
+
226
+    set_last_update(int(datetime.now().strftime('%s')))
210 227
 
211 228
     get_path = '/'.join((
212 229
         config.get('supervisor', 'get_path'),

+ 2
- 1
pyheatpump/db.py View File

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

+ 9
- 3
pyheatpump/modbus.py View File

@@ -1,4 +1,5 @@
1 1
 #!/usr/bin/env python3
2
+import os
2 3
 from serial import Serial
3 4
 from serial.serialutil import SerialException
4 5
 from umodbus.client.serial import rtu
@@ -16,10 +17,15 @@ def connect():
16 17
     global serial_conn
17 18
 
18 19
     if serial_conn is None:
19
-        print('Connecting to serial port *{}*'.format(
20
-            config.get('heatpump', 'serial_port')))
20
+        real_serial_port = os.path.realpath(
21
+            config.get('heatpump', 'serial_port'))
22
+
23
+        print('Connecting to serial port *{}* ({})'.format(
24
+            config.get('heatpump', 'serial_port'),
25
+            real_serial_port))
26
+
21 27
         serial_conn = Serial(
22
-            port=config.get('heatpump', 'serial_port'),
28
+            port=real_serial_port,
23 29
             baudrate=config.get('heatpump', 'baudrate'),
24 30
             bytesize=8,
25 31
             parity='N',

+ 9
- 2
pyheatpump/models/heatpump.py View File

@@ -14,10 +14,17 @@ class Heatpump:
14 14
     var_types: Dict
15 15
     last_update: int = None
16 16
 
17
-    def __init__(self, mac_address, last_update):
17
+    def __init__(self, mac_address, last_update, types=[]):
18 18
         self.mac_address = mac_address
19 19
         self.last_update = last_update
20
-        self.var_types = VariableType.getall()
20
+        if len(types):
21
+            self.var_types = {
22
+                key: val
23
+                for key,val in VariableType.getall().items()
24
+                if key in types
25
+            }
26
+        else:
27
+            self.var_types = VariableType.getall()
21 28
 
22 29
     def __str__(self):
23 30
         return str(self.__dict__())

+ 2
- 1
pyheatpump/models/variable.py View File

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

+ 210
- 0
staging/dev/INSTALL.md View File

@@ -0,0 +1,210 @@
1
+# Install instructions
2
+
3
+## Resources
4
+
5
+You'll need a rasbpiOs image, and the kernel and dtb for qemu 
6
+
7
+## Procedure
8
+
9
+### Enlarge img
10
+
11
+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 :
12
+
13
+```
14
+qemu-img resize rpi.img +1G
15
+```
16
+
17
+### After booting
18
+
19
+Log-in : rpi
20
+Password : raspberry
21
+
22
+Login as root :
23
+
24
+```
25
+sudo su
26
+```
27
+
28
+Create udev rules :
29
+
30
+```
31
+cat << EOF > /etc/udev/rules.d/42-rs485.rules
32
+SUBSYSTEMS=="usb-serial", DRIVERS=="ftdi_sio", SYMLINK+="rs485"
33
+EOF
34
+```
35
+
36
+
37
+#### Dependencies
38
+
39
+```
40
+apt install git sqlite3 python3-pip python3-click python3-requests python3-netifaces python3-serial
41
+```
42
+
43
+#### Deployment 
44
+
45
+Deploy pyHeatpump. Replace MONITORING_SERVER with the address of your server :
46
+
47
+```
48
+mkdir /var/lib/pyheatpump
49
+echo 'pip3 install --upgrade git+https://git.yannweb.net/cli/pyHeatpump.git' > /var/lib/pyheatpump/pyheatpump_upgrade.sh
50
+chmod +x /var/lib/pyheatpump/pyheatpump_upgrade.sh
51
+/var/lib/pyheatpump/pyheatpump_upgrade.sh
52
+```
53
+
54
+#### Database initialisation
55
+```
56
+cat << EOF > /var/lib/pyheatpump/pyheatpump.sql
57
+
58
+CREATE TABLE IF NOT EXISTS var_type (
59
+  slabel CHAR(1) UNIQUE PRIMARY KEY,
60
+  label VARCHAR(10) UNIQUE,
61
+  type VARCHAR(10) NOT NULL,
62
+  start_address INT NOT NULL DEFAULT 0,
63
+  end_address INT NOT NULL DEFAULT 250
64
+);
65
+
66
+CREATE TABLE IF NOT EXISTS variable (
67
+  type CHAR(1) NOT NULL,
68
+  address INT NOT NULL,
69
+  unit VARCHAR(5) NULL,
70
+  last_update INT NULL,
71
+
72
+
73
+  FOREIGN KEY (type) REFERENCES var_type(slabel)
74
+    ON DELETE CASCADE,
75
+  PRIMARY KEY(type, address)
76
+);
77
+
78
+CREATE TABLE IF NOT EXISTS var_value (
79
+  type CHAR(1) NOT NULL,
80
+  address INT NOT NULL,
81
+  time INT DEFAULT (strftime('%s', datetime('now'))),
82
+  value INT NOT NULL,
83
+
84
+  FOREIGN KEY (type) REFERENCES variable(type)
85
+    ON DELETE CASCADE,
86
+  FOREIGN KEY (address) REFERENCES variable(address)
87
+    ON DELETE CASCADE,
88
+  PRIMARY KEY(type, address, time)
89
+);
90
+
91
+INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
92
+  'A', 'Analog', 'float', 1, 5000);
93
+INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
94
+  'I', 'Integer', 'int', 5002, 10002);
95
+INSERT INTO var_type (slabel, label, type, start_address, end_address) VALUES (
96
+  'D', 'Digital', 'bool', 1, 2048);
97
+
98
+CREATE TRIGGER variable_last_update AFTER INSERT ON var_value
99
+FOR EACH ROW
100
+BEGIN
101
+  UPDATE variable
102
+  SET last_update = NEW.time
103
+  WHERE
104
+    type = NEW.type
105
+    AND
106
+    address = NEW.address;
107
+END;
108
+
109
+EOF
110
+
111
+sqlite3 -init /var/lib/pyheatpump/pyheatpump.sql /var/lib/pyheatpump/pyheatpump.sqlite3
112
+
113
+```
114
+
115
+#### PyHeatpump configuration file
116
+```
117
+cat << EOF > /var/lib/pyheatpump/pyheatpump.ini
118
+[heatpump]
119
+database = /var/lib/pyheatpump/pyheatpump.sqlite3
120
+serial_port = /dev/rsa485
121
+
122
+[supervisor]
123
+scheme = https
124
+host = MONITORING_SERVER
125
+port = 8081
126
+post_path = /Symfony/web/app_dev.php/boardws/insert
127
+get_path = /Symfony/web/app_dev.php/boardws/orders
128
+interval = 60
129
+heatpump_id = 42
130
+
131
+[api]
132
+host = 0.0.0.0
133
+port = 80
134
+
135
+EOF
136
+```
137
+
138
+
139
+#### API Service (no-need in production)
140
+```
141
+cat << EOF > /var/lib/pyheatpump/pyheatpump_api.service
142
+[Unit]
143
+Description=API to fetch data from heatpump with HTTP
144
+After=network.target
145
+
146
+[Service]
147
+Type=simple
148
+WorkingDirectory=/var/lib/pyheatpump/
149
+Environment="LOGLEVEL=INFO"
150
+ExecStart=/usr/bin/env pyheatpump run
151
+KillMode=mixed
152
+TimeoutStopSec=30
153
+PrivateTmp=true
154
+
155
+[Install]
156
+WantedBy=multi-user.target
157
+
158
+EOF
159
+```
160
+
161
+
162
+#### Fetch Service
163
+```
164
+cat << EOF > /var/lib/pyheatpump/pyheatpump_fetch.service
165
+[Unit]
166
+Description=pyHeatpump fetch - retrieve the data from the serial port with modbus
167
+Wants=dev-rs485.device
168
+
169
+[Service]
170
+Type=simple
171
+WorkingDirectory=/var/lib/pyheatpump/
172
+Environment="LOGLEVEL=INFO"
173
+ExecStart=/usr/bin/env pyheatpump fetch -t D -t A -t I
174
+ExecStop=/usr/bin/env pyheatpump supervise --since
175
+KillMode=mixed
176
+TimeoutStopSec=30
177
+PrivateTmp=true
178
+
179
+EOF
180
+```
181
+
182
+
183
+#### Fetch Timer
184
+```
185
+cat << EOF > /var/lib/pyheatpump/pyheatpump_fetch.service
186
+[Unit]
187
+Description=Timer to launch pyheatpump fetch every minute
188
+
189
+[Timer]
190
+OnStartSec=15sec
191
+OnUnitInactiveSec=1m
192
+
193
+[Install]
194
+WantedBy=timers.target
195
+
196
+EOF
197
+```
198
+
199
+#### Installation of the services
200
+```
201
+cd /etc/systemd/system
202
+ln -s /var/lib/pyheatpump/pyheatpump_api.service
203
+ln -s /var/lib/pyheatpump/pyheatpump_fetch.service
204
+ln -s /var/lib/pyheatpump/pyheatpump_fetch.timer
205
+
206
+systemctl daemon-reload
207
+systemctl enable pyheatpump_api.service pyheatpump_fetch.timer pyheatpump_supervise.timer
208
+```
209
+
210
+You may now reboot to see if everything works.

Loading…
Cancel
Save