-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathesmart_fsm.py
executable file
·255 lines (213 loc) · 12.6 KB
/
esmart_fsm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import esmart
import heattrap
import time
import datetime
from transitions import Machine
import sys
import re
import traceback
import logging
try:
import pifacedigitalio
except ModuleNotFoundError:
pass
# 48V setup
CELLS = 24
TICK_SECS = 5
FULL_VOLT = 14.2
FULL_VOLT_CV = 13.8
FULL_POWER = 1200
LOW_VOLT = 13
CRITICAL_VOLT = 12.5
LOW_BATTERY_TIMEOUT = 120
CIRCULATION_DELAY_SECS = 30
RESTART_DELAY_SECS = 300
RETRY_SLEEP_SECS = 30
HOT_DEGREES = 59
COLD_DEGREES = 57
HEAT_PUMP_RELAY = 1
CIRCULATION_PUMP_RELAY = 0
#ESMART_HOST='containerpi4.local'
ESMART_HOST='192.168.8.104'
ESMART_PORT=8888
ESMART_TIMEOUT=5
HEATTRAP_PORT = "/dev/ttyACM0"
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
logging.getLogger('transitions').setLevel(logging.WARNING) # Set to INFO to see transitions logging
class esmartfsm(object):
states = ['off', 'on', 'starting circulation pump', 'waiting before stopping', 'stopping circulation pump low', 'waiting before restart low', 'stopping circulation pump hot', 'waiting before restart hot', 'hot']
transitions = [
{ 'source': 'off', 'trigger': 'full', 'dest': 'starting circulation pump', 'after': ['turn_heat_pump_on', 'set_circulation_delay_timer'] },
{ 'source': 'off', 'trigger': 'low', 'dest': 'off' },
{ 'source': 'off', 'trigger': 'critical', 'dest': 'off' },
{ 'source': 'off', 'trigger': 'tick', 'dest': 'off' },
{ 'source': 'off', 'trigger': 'hot', 'dest': 'hot' },
{ 'source': 'off', 'trigger': 'cold', 'dest': 'off' },
{ 'source': 'starting circulation pump', 'trigger': 'full', 'dest': 'starting circulation pump' },
{ 'source': 'starting circulation pump', 'trigger': 'low', 'dest': 'starting circulation pump' },
{ 'source': 'starting circulation pump', 'trigger': 'critical', 'dest': 'off', 'after': ['cancel_timer', 'turn_heat_pump_off'] },
{ 'source': 'starting circulation pump', 'trigger': 'tick', 'dest': 'starting circulation pump' },
{ 'source': 'starting circulation pump', 'trigger': 'hot', 'dest': 'hot', 'after': ['cancel_timer', 'turn_heat_pump_off'] },
{ 'source': 'starting circulation pump', 'trigger': 'cold', 'dest': 'starting circulation pump' },
{ 'source': 'starting circulation pump', 'trigger': 'timeout', 'dest': 'on', 'after': ['turn_circulation_pump_on'] },
{ 'source': 'on', 'trigger': 'full', 'dest': 'on' },
{ 'source': 'on', 'trigger': 'low', 'dest': 'waiting before stopping', 'after': ['set_low_battery_timer'] },
{ 'source': 'on', 'trigger': 'critical', 'dest': 'stopping circulation pump low', 'after': ['turn_heat_pump_off', 'set_circulation_delay_timer'] },
{ 'source': 'on', 'trigger': 'tick', 'dest': 'on' },
{ 'source': 'on', 'trigger': 'hot', 'dest': 'stopping circulation pump hot', 'after': ['turn_heat_pump_off', 'set_circulation_delay_timer'] },
{ 'source': 'on', 'trigger': 'cold', 'dest': 'on' },
{ 'source': 'waiting before stopping', 'trigger': 'full', 'dest': 'on', 'after': ['cancel_timer'] },
{ 'source': 'waiting before stopping', 'trigger': 'low', 'dest': 'waiting before stopping' },
{ 'source': 'waiting before stopping', 'trigger': 'critical', 'dest': 'stopping circulation pump low', 'after': ['turn_heat_pump_off', 'set_circulation_delay_timer'] },
{ 'source': 'waiting before stopping', 'trigger': 'tick', 'dest': 'on', 'after': ['cancel_timer'] },
{ 'source': 'waiting before stopping', 'trigger': 'hot', 'dest': 'stopping circulation pump hot', 'after': ['turn_heat_pump_off', 'set_circulation_delay_timer'] },
{ 'source': 'waiting before stopping', 'trigger': 'cold', 'dest': 'waiting before stopping' },
{ 'source': 'waiting before stopping', 'trigger': 'timeout', 'dest': 'stopping circulation pump low', 'after': ['turn_heat_pump_off', 'set_circulation_delay_timer'] },
{ 'source': 'stopping circulation pump low', 'trigger': 'full', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'low', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'critical', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'tick', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'hot', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'cold', 'dest': 'stopping circulation pump low' },
{ 'source': 'stopping circulation pump low', 'trigger': 'timeout', 'dest': 'waiting before restart low', 'after': ['turn_circulation_pump_off', 'set_restart_delay_timer'] },
{ 'source': 'waiting before restart low', 'trigger': 'full', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'low', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'critical', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'tick', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'hot', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'cold', 'dest': 'waiting before restart low' },
{ 'source': 'waiting before restart low', 'trigger': 'timeout', 'dest': 'off' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'full', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'low', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'critical', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'tick', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'hot', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'cold', 'dest': 'stopping circulation pump hot' },
{ 'source': 'stopping circulation pump hot', 'trigger': 'timeout', 'dest': 'waiting before restart hot', 'after': ['turn_circulation_pump_off', 'set_restart_delay_timer'] },
{ 'source': 'waiting before restart hot', 'trigger': 'full', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'low', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'critical', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'tick', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'hot', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'cold', 'dest': 'waiting before restart hot' },
{ 'source': 'waiting before restart hot', 'trigger': 'timeout', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'full', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'low', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'critical', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'tick', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'hot', 'dest': 'hot' },
{ 'source': 'hot', 'trigger': 'cold', 'dest': 'off' },
]
def __init__(self):
self.piface = pifacedigitalio.PiFaceDigital() if 'pifacedigitalio' in sys.modules else None
self.turn_heat_pump_off()
self.turn_circulation_pump_off()
self.heattrap = heattrap.heattrap(HEATTRAP_PORT)
self.esmart = esmart.esmart()
self.esmart.connect((ESMART_HOST, ESMART_PORT))
self.machine = Machine(model=self, states=esmartfsm.states, transitions=esmartfsm.transitions, initial='off')
self.ticker = 0
self.timer = None
def request_data(self):
if self.timer and time.time() >= self.timer:
logging.info('DELAY EXPIRED')
self.timer = None
self.timeout()
else:
timebefore = time.time()
tempsensors = self.heattrap.read(self.ticker)
self.ticker -= time.time() - timebefore
if self.ticker < 0:
self.ticker = 0
if tempsensors:
def log_temp_sensors(status):
logging.info('Temperature sensors: %s - %s' % (tempsensors, status))
if tempsensors[1] >= HOT_DEGREES:
log_temp_sensors('HOT')
self.hot()
elif tempsensors[1] <= COLD_DEGREES:
log_temp_sensors('COLD')
self.cold()
else:
log_temp_sensors('')
else:
if self.ticker == 0:
n = 10
while n > 0:
try:
data = self.esmart.read(ESMART_TIMEOUT)
break
except esmart.esmartError as exception:
logging.info(exception)
n -= 1
if n == 0:
raise RuntimeError('Too many eSmart errors.')
charge_mode = esmart.DEVICE_MODE[data['chg_mode']]
def log_charge_status(status):
logging.info('Charge mode: %s Battery %.1fV %.1fA - %s' % (charge_mode, data['bat_volt'], data['chg_cur'], status))
if ( ( charge_mode == 'FLOAT'
or ( charge_mode == 'CV' and data['bat_volt'] >= FULL_VOLT_CV * CELLS / 6 )
or data['bat_volt'] >= FULL_VOLT * CELLS / 6 )
and data['chg_cur'] < FULL_POWER / (CELLS * 2.0) ):
log_charge_status('FULL')
self.full()
elif data['bat_volt'] < CRITICAL_VOLT * CELLS / 6:
log_charge_status('CRITICAL')
self.critical()
elif data['bat_volt'] < LOW_VOLT * CELLS / 6:
log_charge_status('LOW')
self.low()
else:
log_charge_status('TICK')
self.tick()
self.ticker = TICK_SECS
def turn_heat_pump_on(self):
logging.info('TURN HEAT PUMP ON')
if self.piface:
self.piface.relays[HEAT_PUMP_RELAY].value = 1
def set_circulation_delay_timer(self):
logging.info('SET CIRCULATION DELAY TIMER')
self.timer = time.time() + CIRCULATION_DELAY_SECS
def turn_circulation_pump_on(self):
logging.info('TURN CIRCULATION PUMP ON')
if self.piface:
self.piface.relays[CIRCULATION_PUMP_RELAY].value = 1
def turn_heat_pump_off(self):
logging.info('TURN HEAT PUMP OFF')
if self.piface:
self.piface.relays[HEAT_PUMP_RELAY].value = 0
def turn_circulation_pump_off(self):
logging.info('TURN CIRCULATION PUMP OFF')
if self.piface:
self.piface.relays[CIRCULATION_PUMP_RELAY].value = 0
def set_restart_delay_timer(self):
logging.info('SET RESTART DELAY TIMER')
self.timer = time.time() + RESTART_DELAY_SECS
def set_low_battery_timer(self):
logging.info('SET LOW BATTERY TIMER')
self.timer = time.time() + LOW_BATTERY_TIMEOUT
def cancel_timer(self):
logging.info('CANCELLING TIMER')
self.timer = None
fsm = None
logging.info('STARTING DAEMON')
while True:
try:
if not fsm:
fsm = esmartfsm()
fsm.request_data()
except Exception as exception:
if fsm:
fsm.turn_heat_pump_off()
fsm.turn_circulation_pump_off()
fsm.piface.deinit_board()
del(fsm.piface)
del(fsm)
fsm = None
logging.info(traceback.format_exc())
logging.info(exception)
logging.info('SLEEPING BEFORE RETRYING')
time.sleep(RETRY_SLEEP_SECS)
continue