-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactivity.py
252 lines (232 loc) · 9.21 KB
/
activity.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
#!/usr/bin/python
# coding: utf8
import pigpio
import time
import kdate
import logging
import threading
import os
import ctypes
#import docopt
#import sys
#import subprocess
log = logging.getLogger("Foosball")
class Activity:
def __init__(self, pi, gpio, power=False, config=False, onVacant=None, onOccupied=None):
self.pi=pi
self.gpio=gpio
self.gpio_power=power
# Setup
self.pi.set_mode(self.gpio, pigpio.INPUT)
self.pi.set_pull_up_down(self.gpio, pigpio.PUD_DOWN)
if self.gpio_power: # If power-gpio supplied, then set it up and turn off
self.pi.set_mode(self.gpio_power, pigpio.OUTPUT)
self.pi.write(self.gpio_power, 0)
# Callback functions on table status change
self.onVacant=onVacant
self.onOccupied=onOccupied
# Configuration variables
self.activity_num_to_occupied = 3
self.activity_time_debounce = .5
self.activity_time_unoccupied = 20
self.activity_time_reset = 4
self.activity_time_reoccupied = None
# Pins which mirrors sensor output and table status
self.outputSensor=False
self.outputStatus=False
# Variables to set heartbeat of this thread and recieve heartbeat of main thread
self.hearttime = time.time()
self.heartmain = 0
# Status variables
self.allwaysOn = 0
self.allwaysOff = 0
self.singleOn = 0
self.table_occupied = False
self.move_time = 0
self.move_num = 0
self.lastactivity = 0
self.lastchange = time.time()
self.buttontime = 0
# This function is called once to start the monitoring of the vibration sensor
# Just runs an infinite loop, where it either waits for activity og vacancy
# Runs external callback function when table status changes
def run(self):
self.tid=ctypes.CDLL('libc.so.6').syscall(224)
log.info("Starting activity sensoring program (tid: %d)" % self.tid)
# If sensor power is a gpio, then turn on sensor
if self.gpio_power:
log.debug("Turning on power to sensor")
self.pi.write(self.gpio_power,1)
# Main loop of activity thread
while True:
if self.table_occupied:
log.debug("Waiting for vacant")
self.wait_for_vacant()
# Did we recieve a stop signal while we were wating?
if self.stopsignal: break;
# OK: Table is now vacant. Call onVacant callback, if it exists
if self.onVacant: self.onVacant()
else:
log.debug("Waiting for occupied")
self.wait_for_occupied()
# Did we recieve a stop signal while we were wating?
if self.stopsignal: break;
# OK: Table is now occupied. Call onOccupied callback, if it exists
if self.onOccupied: self.onOccupied()
# We only get down here when activity thread is asked to quit
# If power is configured. Turn off when exiting
if self.gpio_power:
log.debug("Turning off power to sensor")
self.pi.write(self.gpio_power,0)
self.pi.set_mode(self.gpio_power, pigpio.INPUT)
def setAllwaysOn(self, state=False):
log.info("Setting allwaysOn: %r" % state)
self.allwaysOn=state
self.allwaysOff=False
def setAllwaysOff(self, state=False):
log.info("Setting allwaysOff: %r" % state)
self.allwaysOff=state
self.allwaysOn=False
def turnOn(self):
self.singleOn = True
def start(self):
self.thread=threading.Thread(target=self.run, name="Activity")
self.thread.start()
self.stopsignal=False
def stop(self):
self.stopsignal=True
def click(self):
self.buttontime=time.time()
def heartbeat(self):
self.hearttime=time.time()
# If we haven't heard from main thread in 2 minutes. Exit.
if self.hearttime-self.heartmain > 120:
log.debug("Main thread heartbeat too faint. Stoppong activity thread")
self.stop()
# Table is vacant
# Wait for enough vibration triggers. Log each full hour with no activity
# When ever a vibration is measured, check to see how long time passed
# since last vibration.
def wait_for_occupied(self):
waitbeat=0
while True:
self.heartbeat()
if self.stopsignal:
log.debug("Stop signal - Breaking out of occupied wait")
break
if self.allwaysOff:
time.sleep(3)
continue
# Wait for activity for a few seconds
# Needs to be short (during devel), so we can break out fast on program exit
if self.singleOn or self.allwaysOn or self.pi.wait_for_edge(self.gpio, pigpio.RISING_EDGE, 3):
self.output("sensor",1)
log.debug("Activity detected...")
newtime=time.time()
self.lastactivity=newtime
waitbeat=0
# Too long since last move - reset movements
if newtime-self.move_time > self.activity_time_reset:
log.debug(" long time since motion. Move_num reset...")
self.move_num=0
# This was an extra movement, so increase movement count
self.move_num+=1
log.debug(" Move_num new set to %d" % self.move_num)
# Record time of latest movement
self.move_time=newtime
# If that was move_num movements in a row without "large" breaks,
# ... then set table occupied and return from this function
if self.move_num >= self.activity_num_to_occupied or self.singleOn or self.allwaysOn:
if self.singleOn:
log.info("Table turned on manually")
self.singleOn=False
if self.allwaysOn:
log.info("Table turned on permanently")
log.info("Table occupied - (vacant for %d seconds)" % (newtime-self.lastchange))
self.lastchange=newtime
self.table_occupied=True
# Set output pin to high to indicate table occupied
self.output("status",1)
return(True)
# We need more movements before we think the table is occupied.
# But first sleep a little, tó ignore fast vibrations within a short time
else:
time.sleep(self.activity_time_debounce)
# No vibration detected for 3600 seconds. Let the log file know
else:
waitbeat+=1
if waitbeat%10==0:
if waitbeat==10: log.info("No activity for 30 seconds")
elif waitbeat==100: log.info("No activity for 5 minutes")
elif waitbeat%1200==0: log.info("No activity last hour")
def wait_for_vacant(self):
while True:
self.heartbeat()
if self.stopsignal:
log.debug("Stop signal - Breaking out of occupied wait")
break
if self.allwaysOn:
time.sleep(3)
continue
# Wait a few seconds for activity
if self.allwaysOff==0 and self.pi.wait_for_edge(self.gpio, pigpio.RISING_EDGE, 3):
log.debug("Activity detected... Still occupied")
self.lastactivity=time.time()
self.buttontime=0
self.output("sensor",1)
self.move_num+=1 # Add 1 movement counter - people are still playing
# Sleep a little, to ignore fast vibrations within a short time - no need to run this loop 1000 times/s
time.sleep(3)
else:
# 3 seconds passed with no activity. Check if enough time has passed, if not listen again
# Check time since last activity or last button. If larger than limit, go to vacant
newtime=time.time()
if min(newtime-self.buttontime, newtime-self.lastactivity)<self.activity_time_unoccupied:
continue
# OK: Long activity break. Change table status to unoccupied
self.output("status",0)
log.info("Table vacant - (occupied for %d seconds, %d movements detected)" %
(newtime-self.lastchange, self.move_num))
self.lastchange=newtime
self.table_occupied=False
return(True)
# Define or undefine output GPIOs which mirrors the sensor reading and/or table status
# May be used for instance for LED indicators
def setOutput(self, sensorGPIO, statusGPIO):
if sensorGPIO:
self.outputSensor=sensorGPIO
self.pi.set_mode(self.outputSensor, pigpio.OUTPUT)
self.pi.write(self.outputSensor, 0)
# Don't mirror sensor. If one is defined, remove it.
elif self.outputSensor:
self.pi.set_mode(self.outputSensor, pigpio.INPUT)
self.outputSensor=False
self.sensorcb = None
if statusGPIO:
self.outputStatus=statusGPIO
self.pi.set_mode(self.outputStatus, pigpio.OUTPUT)
self.pi.write(self.outputStatus, self.table_occupied)
# Don't mirror status. If one is defined, remove it.
elif self.outputStatus:
self.pi.set_mode(self.outputStatus, pigpio.INPUT)
self.outputStatus=False
def output(self, sensor, level):
if sensor=="sensor" and self.outputSensor:
log.debug("Sensor output set to %d " % level)
self.pi.write(self.outputSensor,level)
threading.Timer(1.0, self.outputOff).start()
elif sensor=="status" and self.outputStatus:
log.debug("Status output set to %d " % level)
self.pi.write(self.outputStatus,level)
# Trigger function for vibration-sensor going low again.
# Only used if sensor outputs are mirrored to another GPIO
def outputOff(self):
if self.outputSensor:
log.debug("Sensor turned off")
self.pi.write(self.outputSensor,0)
def sensorOn():
if self.gpio_power:
self.pi.write(self.gpio_power,1)
def sensorOff():
if self.gpio_power:
self.pi.write(self.gpio_power,0)