|
| 1 | +import os |
| 2 | +import time |
| 3 | +from datetime import datetime |
| 4 | +import logging |
| 5 | +from logging.config import fileConfig |
| 6 | + |
| 7 | +import RPi.GPIO as GPIO |
| 8 | + |
| 9 | +from peewee import * |
| 10 | +from playhouse.shortcuts import RetryOperationalError |
| 11 | + |
| 12 | +from pyfcm import FCMNotification |
| 13 | + |
| 14 | +# wheel circumference in cm |
| 15 | +# e.g., for 2*r = 28cm, d = 2*pi*r |
| 16 | +HAMSTER_WHEEL_CIRCUMFERENCE = 88 |
| 17 | +# min time between revolutions in ms |
| 18 | +HAMSTER_DEBOUNCE = 250 |
| 19 | +# session timeout in s |
| 20 | +HAMSTER_SESSION_TIMEOUT = 60 |
| 21 | + |
| 22 | +# GPIO init |
| 23 | +GPIO_CHANNEL = 10 |
| 24 | +GPIO.setmode(GPIO.BCM) |
| 25 | +GPIO.setup(GPIO_CHANNEL, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) |
| 26 | + |
| 27 | +# FCM stuff |
| 28 | +push_service = FCMNotification(api_key="<FCM API KEY>") |
| 29 | + |
| 30 | +# setup logging |
| 31 | +logging.config.fileConfig(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'logging_config.ini')) |
| 32 | +logger = logging.getLogger(__name__) |
| 33 | + |
| 34 | +# SQL stuff |
| 35 | +SQL_HOST = '<SQL_HOST>' |
| 36 | +SQL_DB = '<SQL_DB>' |
| 37 | +SQL_USER = '<SQL_USER>' |
| 38 | +SQL_PASSWORD = '<SQL_PASSWORD>' |
| 39 | +mysql_db = MySQLDatabase(SQL_DB, host=SQL_HOST, user=SQL_USER, passwd=SQL_PASSWORD) |
| 40 | + |
| 41 | +# Fallback file - written when sql fails |
| 42 | +FALLBACK_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fallback.log') |
| 43 | + |
| 44 | +# SQL table structure |
| 45 | +class BaseModel(Model): |
| 46 | + """A base model that will use our MySQL database""" |
| 47 | + class Meta: |
| 48 | + database = mysql_db |
| 49 | + |
| 50 | +class Hamstersession(BaseModel): |
| 51 | + start = DateTimeField(unique=True, index=True) |
| 52 | + circumference = FloatField() |
| 53 | + duration = FloatField() |
| 54 | + distance = FloatField() |
| 55 | + |
| 56 | +# Create table if it doesn't exist |
| 57 | +if not Hamstersession.table_exists(): |
| 58 | + Hamstersession.create_table() |
| 59 | + print('Table Hamstersession created') |
| 60 | + |
| 61 | + |
| 62 | +# no. of revolutions |
| 63 | +revolutions = 0 |
| 64 | +# timestamp |
| 65 | +last_revo = 0 |
| 66 | +session_start = 0 |
| 67 | + |
| 68 | +def post_notification(data_message): |
| 69 | + result = push_service.notify_topic_subscribers( |
| 70 | + topic_name = "news", |
| 71 | + data_message = data_message |
| 72 | + ) |
| 73 | + |
| 74 | + |
| 75 | +def finish_session(session_end): |
| 76 | + wstart = datetime.fromtimestamp(session_start) |
| 77 | + wcircumference = HAMSTER_WHEEL_CIRCUMFERENCE |
| 78 | + wduration = session_end - session_start |
| 79 | + wdistance = revolutions * HAMSTER_WHEEL_CIRCUMFERENCE |
| 80 | + |
| 81 | + logger.info('Session finished') |
| 82 | + logger.info('Start: {0}'.format(wstart)) |
| 83 | + logger.info('Duration: {0}s'.format(wduration)) |
| 84 | + logger.info('Distance: {0}m'.format(wdistance/100.0)) |
| 85 | + logger.info('Revolutions: {0}'.format(revolutions)) |
| 86 | + |
| 87 | + data_message = { |
| 88 | + "event": "session_finished", |
| 89 | + "start": int(session_start*1000), |
| 90 | + "duration": "{0:.1f}".format(wduration/60.0), |
| 91 | + "distance": "{0:.1f}".format(wdistance/100.0), |
| 92 | + "revolutions": revolutions |
| 93 | + } |
| 94 | + post_notification(data_message=data_message) |
| 95 | + |
| 96 | + try: |
| 97 | + #mysql_db.connect() |
| 98 | + mysql_db.get_conn().ping(True) |
| 99 | + hamstersession = Hamstersession.create( |
| 100 | + start = wstart, |
| 101 | + circumference = wcircumference, |
| 102 | + duration = wduration, |
| 103 | + distance = wdistance) |
| 104 | + hamstersession.save() |
| 105 | + mysql_db.close() |
| 106 | + except Exception as e: |
| 107 | + logger.error('MySQL error: {0}'.format(e)) |
| 108 | + with open(FALLBACK_FILE, 'a') as fd: |
| 109 | + fd.write('start: {0}\n'.format(wstart)) |
| 110 | + fd.write('circumference: {0}\n'.format(wcircumference)) |
| 111 | + fd.write('duration: {0}\n'.format(wduration)) |
| 112 | + fd.write('distance: {0}\n'.format(wdistance)) |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | +def start_session(session_start): |
| 117 | + data_message = { |
| 118 | + "event": "session_started", |
| 119 | + "start": int(session_start*1000) |
| 120 | + } |
| 121 | + post_notification(data_message=data_message) |
| 122 | + |
| 123 | + |
| 124 | +logger.info('HamTrack initialized') |
| 125 | +# main loop |
| 126 | +try: |
| 127 | + while 1: |
| 128 | + channel = GPIO.wait_for_edge(GPIO_CHANNEL, |
| 129 | + GPIO.RISING, |
| 130 | + timeout=HAMSTER_SESSION_TIMEOUT*1000) |
| 131 | + now = time.time() |
| 132 | + if channel is None: |
| 133 | + # Timeout: End session |
| 134 | + if revolutions >= 5: |
| 135 | + finish_session(now - HAMSTER_SESSION_TIMEOUT) |
| 136 | + if revolutions < 5 and session_start != 0: |
| 137 | + logger.info('Session aborted - no activity') |
| 138 | + revolutions = 0 |
| 139 | + session_start = 0 |
| 140 | + else: |
| 141 | + # Debounce |
| 142 | + if now - last_revo > HAMSTER_DEBOUNCE/1000.0: |
| 143 | + # Revolution detected |
| 144 | + logger.debug('Revolution detected. dt={0}'.format(now-last_revo)) |
| 145 | + # Start new session if none is running |
| 146 | + last_revo = now |
| 147 | + if session_start == 0: |
| 148 | + session_start = now |
| 149 | + logger.info('Session started') |
| 150 | + else: |
| 151 | + revolutions += 1 |
| 152 | + if revolutions == 5: |
| 153 | + start_session(session_start) |
| 154 | + |
| 155 | +# except RuntimeError as e: |
| 156 | +# syslog.syslog(e) |
| 157 | +except KeyboardInterrupt: |
| 158 | + pass |
| 159 | + |
| 160 | +GPIO.cleanup() |
0 commit comments