-
Notifications
You must be signed in to change notification settings - Fork 0
/
__init__.py
328 lines (284 loc) · 14.8 KB
/
__init__.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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# -*- coding: utf-8 -*-
# adapted from Flowmeter plugin for Craftbeerpi by nanab
# https://github.com/nanab/Flowmeter
################################################################################
import time
from modules import cbpi
from modules.core.hardware import ActorBase, SensorActive
from modules.core.step import StepBase
from modules.core.props import Property, StepProperty
try:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
except Exception as e:
print e
################################################################################
class PulseCounter(object):
#-------------------------------------------------------------------------------
def __init__(self, gpio):
self.count = 0
self.last = self.prior = 0.0
try:
GPIO.setup(gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.RISING, callback=self.pulse_input, bouncetime=20)
cbpi.app.logger.info("FlowSensor pulse counter initialized for GPIO {}".format(gpio))
except Exception as e:
cbpi.notify("Failure to initialize FlowSensor pulse counter", "Could not create callback for GPIO {}".format(gpio), type="danger", timeout="None")
cbpi.app.logger.error("Failure to initialize FlowSensor pulse counter \n{}".format(e))
#-------------------------------------------------------------------------------
def pulse_input(self, channel):
self.count += 1
self.prior = self.last
self.last = time.time()
################################################################################
@cbpi.sensor
class FlowSensor(SensorActive):
# properties
a_gpio_prop = Property.Select("GPIO", options=[0, 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])
b_display_prop = Property.Select("Display", options=["volume", "flow"])
c_volume_units_prop = Property.Text("Volume Units", configurable=True, default_value="L", description="Can by anything, just be sure to calibrate using same units")
d_time_units_prop = Property.Select("Flow Time Units", options=["/s","/m"])
e_calibration_units_prop = Property.Number("Calibration Units", configurable=True, default_value=1, description="Actual units transferred during calibration test")
f_calibration_count_prop = Property.Number("Calibration Count", configurable=True, default_value=485, description="Reported pulse count from calibration test")
# class attribute!
_gpio_counters = dict()
#-------------------------------------------------------------------------------
@classmethod
def initialize_gpio_counter(cls, gpio):
# create a single callback/counter per GPIO (shared by instances)
if not cls._gpio_counters.get(gpio):
cls._gpio_counters[gpio] = PulseCounter(gpio)
#-------------------------------------------------------------------------------
@classmethod
def get_gpio_pulse_count(cls, gpio):
return cls._gpio_counters[gpio].count
#-------------------------------------------------------------------------------
@classmethod
def get_gpio_pulse_rate(cls, gpio):
last = cls._gpio_counters[gpio].last
prior = cls._gpio_counters[gpio].prior
# time between last 2 pulses
delta1 = last - prior
# time between last pulse and now
delta2 = time.time() - last
# inverse of secs between pulses is pulses/sec
return 1.0/max(delta1, delta2)
#-------------------------------------------------------------------------------
def init(self):
# convert properties to usable attributes
self.gpio = int(self.a_gpio_prop)
self.display_type = self.b_display_prop if self.b_display_prop else "volume"
self.volume_units = self.c_volume_units_prop if self.c_volume_units_prop else "Units"
self.time_units = str(self.d_time_units_prop)
self.period_adjust = 60.0 if (self.d_time_units_prop == "/m") else 1.0
self.calibration = float(self.e_calibration_units_prop)/float(self.f_calibration_count_prop)
# initialize
self.initialize_gpio_counter(self.gpio)
self.reset_volume()
SensorActive.init(self)
#-------------------------------------------------------------------------------
def execute(self):
while self.is_running():
sensor_data = self.read_sensor_data()
self.data_received("{:.2f}".format(sensor_data[self.display_type]))
self.sleep(1.0)
#-------------------------------------------------------------------------------
def read_sensor_data(self):
# pulses since last reset
pulse_count = self.get_gpio_pulse_count(self.gpio) - self._reset_count
# volume since last reset
volume = pulse_count * self.calibration
# flow rate
flow = self.get_gpio_pulse_rate(self.gpio) * self.calibration * self.period_adjust
return {'count':pulse_count, 'flow':flow, 'volume':volume}
#-------------------------------------------------------------------------------
@cbpi.action("Reset Volume")
def reset_volume(self):
self._reset_count = self.get_gpio_pulse_count(self.gpio)
#-------------------------------------------------------------------------------
def get_unit(self):
unit = self.volume_units
if self.display_type == "flow":
unit += self.time_units
return unit
################################################################################
@cbpi.sensor
class SimulatedFlowSensor(SensorActive):
# properties
a_flow_actor_prop = Property.Actor("Actor", description="The actor this sensor responds to")
a_flow_rate_prop = Property.Number("Flow Rate", configurable=True, default_value=2)
b_display_prop = Property.Select("Display", options=["volume", "flow"])
c_volume_units_prop = Property.Text("Volume Units", configurable=True, default_value="L", description="Can by anything")
d_time_units_prop = Property.Select("Flow Time Units", options=["/s","/m"])
#-------------------------------------------------------------------------------
def init(self):
# convert properties to usable attributes
try:
self.flow_actor = int(self.a_flow_actor_prop)
self.flow_rate = float(self.a_flow_rate_prop)
except:
self.flow_actor = None
self.flow_rate = 0.0
self.display_type = self.b_display_prop if self.b_display_prop else 'volume'
self.time_units = str(self.d_time_units_prop)
self.period_adjust = 1.0/60.0 if (self.d_time_units_prop == "/m") else 1.0
self.volume_units = "Units" if self.c_volume_units_prop == "" else self.c_volume_units_prop
self.flow_device = None
# initialize
self.reset_volume()
SensorActive.init(self)
#-------------------------------------------------------------------------------
def execute(self):
# at startup, wait for actors to initialze
while cbpi.cache.get("actors") is None:
self.sleep(5)
self.flow_device = cbpi.cache.get("actors").get(self.flow_actor, None)
# primary sensor loop
while self.is_running():
sensor_data = self.read_sensor_data()
self.data_received("{:.2f}".format(sensor_data[self.display_type]))
self.sleep(1.0)
#-------------------------------------------------------------------------------
def read_sensor_data(self):
time_now = time.time()
if self.flow_device and int(self.flow_device.state):
# configured flow rate x actor power level
flow = self.flow_rate * (float(self.flow_device.power) / 100.0)
# increment by flow rate x elapsed time
self._volume += flow * (time_now - self._time_last) * self.period_adjust
else:
flow = 0.0
self._time_last = time_now
return {'count':0, 'flow':flow, 'volume':self._volume}
#-------------------------------------------------------------------------------
@cbpi.action("Reset Volume")
def reset_volume(self):
self._volume = 0.0
self._time_last = time.time()
#-------------------------------------------------------------------------------
def get_unit(self):
unit = self.volume_units
if self.display_type == "flow":
unit += self.time_units
return unit
################################################################################
@cbpi.step
class FlowSensorTransfer(StepBase):
a_sensor_prop = StepProperty.Sensor("Flow Sensor", description="Sensor that contols this step")
b_actor1_prop = StepProperty.Actor("Actor 1", description="Actor to turn on for the duration of this step")
c_actor2_prop = StepProperty.Actor("Actor 2", description="Actor to turn on for the duration of this step")
d_volume_prop = Property.Number("Target Volume", configurable=True, description="Leave blank to continue until flow stops")
e_reset_start_prop = Property.Select("Reset sensor at start?", options=["Yes","No"])
f_reset_finish_prop = Property.Select("Reset sensor at finish?", options=["Yes","No"])
g_threshold_prop = Property.Number("Flow threshold", configurable=True, default_value=0.1, description="Value at which flow is considered stopped")
#-------------------------------------------------------------------------------
def init(self):
# convert properties to usable attributes
self.sensor = cbpi.cache.get("sensors")[int(self.a_sensor_prop)].instance
self.actors = [self.b_actor1_prop, self.c_actor2_prop]
try: self.target_volume = float(self.d_volume_prop)
except: self.target_volume = 0.0
self.reset_start = self.e_reset_start_prop == "Yes"
self.reset_finish = self.f_reset_finish_prop == "Yes"
try: self.threshold = float(self.g_threshold_prop)
except: self.threshold = 0.1
self.flowing = False
# reset sensor volume if indicated
if self.reset_start:
self.sensor.reset_volume()
# turn on actors
self.actors_on()
#-------------------------------------------------------------------------------
def execute(self):
sensor_data = self.sensor.read_sensor_data()
if (not self.flowing) and (sensor_data['flow'] >= self.threshold):
# flow has started
cbpi.app.logger.info("FlowSensor '{}' transfer flow started".format(self.sensor.name))
self.flowing = True
elif (self.flowing) and (sensor_data['flow'] <= self.threshold):
# flow has stopped
cbpi.app.logger.info("FlowSensor '{}' transfer flow stopped".format(self.sensor.name))
self.next()
elif self.target_volume and (sensor_data['volume'] >= self.target_volume):
# target volume reached
cbpi.app.logger.info("FlowSensor '{}' transfer target volume reached".format(self.sensor.name))
self.next()
#-------------------------------------------------------------------------------
def finish(self):
# turn actors off
self.actors_off()
# notify complete and total volume
sensor_data = self.sensor.read_sensor_data()
self.notify("{} complete".format(self.name), "Total Volume: {:.2f}{}".format(sensor_data['volume'], self.sensor.volume_units), timeout=None)
# reset sensor volume if indicated
if self.reset_finish:
self.sensor.reset_volume()
#-------------------------------------------------------------------------------
def reset(self):
self.actors_off()
#-------------------------------------------------------------------------------
def actors_on(self):
for actor in self.actors:
try: self.actor_on(int(actor))
except: pass
def actors_off(self):
for actor in self.actors:
try: self.actor_off(int(actor))
except: pass
################################################################################
@cbpi.step
class FlowSensorCalibrate(StepBase):
# properties
actor_prop = StepProperty.Actor("Actor")
sensor_prop = StepProperty.Sensor("Sensor")
timer_prop = Property.Number("Timer", configurable=True, default_value=10)
threshold_prop = Property.Number("Flow threshold", configurable=True, default_value=0.1, description="Value at which flow is considered stopped")
#-------------------------------------------------------------------------------
def init(self):
# convert properties to usable attributes
self.actor = int(self.actor_prop)
self.sensor = cbpi.cache.get("sensors")[int(self.sensor_prop)].instance
self.threshold = float(self.threshold_prop)
self.flowing = False
# reset sensor volume to start calibration
self.sensor.reset_volume()
# turn on actor
self.actor_on(self.actor)
# start timer
if self.is_timer_finished() is None:
self.start_timer(float(self.timer_prop) * 60)
#-------------------------------------------------------------------------------
def execute(self):
sensor_data = self.sensor.read_sensor_data()
if (not self.flowing) and (sensor_data['flow'] >= self.threshold):
# flow has started
cbpi.app.logger.info("FlowSensor '{}' calibrate flow started".format(self.sensor.name))
self.flowing = True
elif (self.flowing) and (sensor_data['flow'] <= self.threshold):
# flow has stopped
cbpi.app.logger.info("FlowSensor '{}' calibrate flow stopped".format(self.sensor.name))
self.next()
elif self.is_timer_finished() == True:
# timer has expired
cbpi.app.logger.info("FlowSensor '{}' calibrate timer expired".format(self.sensor.name))
self.next()
#-------------------------------------------------------------------------------
def finish(self):
# turn off actor
self.actor_off(self.actor)
# notify complete and total pulses
sensor_data = self.sensor.read_sensor_data()
self.notify("Flow Sensor Calibration Complete", self.sensor.name, type="success", timeout=None)
self.notify("Pulse Count: {}".format(sensor_data['count']), self.sensor.name, type="info", timeout=None)
#-------------------------------------------------------------------------------
def reset(self):
self.actor_off(self.actor)
################################################################################
@cbpi.step
class FlowSensorReset(StepBase):
sensor_prop = StepProperty.Sensor("Sensor")
#-------------------------------------------------------------------------------
def init(self):
self.sensor = cbpi.cache.get("sensors")[int(self.sensor_prop)].instance
self.sensor.reset_volume()
self.next()