Skip to content

Commit 762bc07

Browse files
committed
Squashed commit of the following:
Ran code through black for formatting added raspberry pi to valuelogger supported devices list Kill update thread on shutdown and return single value or list if more than one Getting RPi code working for temperature sensors lowered priory of debug message sand added sin wave to simulated data synthesis first draft of a ValueLogger class with simulated device This devcie has some discussion in issue #272
1 parent b6ddfa0 commit 762bc07

File tree

5 files changed

+238
-4
lines changed

5 files changed

+238
-4
lines changed

doc/architecture/supported-devices.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ DigitalIO
7272

7373
- Raspberry Pi (:class:`microsocpe.digitalio.raspberrypi`)
7474

75+
76+
ValueLogger
77+
===========
78+
79+
- Raspberry Pi (:class:`microsocpe.valuelogger.raspberrypi`)
80+
includes support for the MCP9808 and TSYS01 I2C temperature sensors
81+
82+
7583
Other
7684
=====
7785

microscope/abc.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,3 +1668,33 @@ def read_all_lines(self):
16681668
for i in range(self._numLines):
16691669
readarray[i] = self.read_line(i)
16701670
return readarray
1671+
1672+
1673+
class ValueLogger(DataDevice, metaclass=abc.ABCMeta):
1674+
"""ABC for Value logging device.
1675+
1676+
Value logging Devices utilise the DataDevice infrastrucrure to send
1677+
values to a receiving client. A typical example of data is temperature
1678+
measurements.
1679+
1680+
Args:
1681+
numSensors: total number of measurements.
1682+
1683+
"""
1684+
1685+
def __init__(self, numSensors: int, **kwargs) -> None:
1686+
super().__init__(**kwargs)
1687+
if numSensors < 1:
1688+
raise ValueError(
1689+
"NumSensors must be a positive number (was %d)" % numSensors
1690+
)
1691+
self._numSensors = numSensors
1692+
1693+
@abc.abstractmethod
1694+
def initialize(self):
1695+
"""Inialize sensors to start sending data back."""
1696+
raise NotImplementedError()
1697+
1698+
def get_num_sensors(self):
1699+
"""Returns the number of sensors lines present in this instance"""
1700+
return self._numSensors

microscope/digitalio/raspberrypi.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import typing
3030
import logging
3131
import queue
32-
3332
import microscope.abc
3433

3534
import RPi.GPIO as GPIO
@@ -102,9 +101,7 @@ def register_HW_interupt(self, line):
102101
def HW_trigger(self, pin):
103102
state = GPIO.input(pin)
104103
line = self._gpioMap.index(pin)
105-
print(pin, state, line)
106104
self.inputQ.put((line, state))
107-
print(self.inputQ.empty())
108105

109106
def get_IO_state(self, line: int) -> bool:
110107
# returns
@@ -148,7 +145,6 @@ def _fetch_data(self):
148145
if self.inputQ.empty():
149146
return None
150147
(line, state) = self.inputQ.get()
151-
# print(self.inputQ.get())
152148
_logger.info("Line %d chnaged to %s" % (line, str(state)))
153149
return (line, state)
154150

microscope/simulators/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import logging
3030
import random
3131
import time
32+
import math
3233
import typing
3334
from typing import Tuple
3435

@@ -534,3 +535,39 @@ def _do_enable(self):
534535
# raise exception if line <0 or line>num_lines
535536
# read all lines to return True,Flase if readable and None if an output
536537
#
538+
539+
540+
class SimulatedValueLogger(microscope.abc.ValueLogger):
541+
def __init__(self, **kwargs):
542+
super().__init__(**kwargs)
543+
self._cache = [None] * self._numSensors
544+
self.lastDataTime = time.time()
545+
546+
def initialize(self):
547+
# init simulated sensors
548+
for i in range(self._numSensors):
549+
self._cache[i] = 20 + i
550+
551+
# functions required as we are DataDevice returning data to the server.
552+
def _fetch_data(self):
553+
if (time.time() - self.lastDataTime) > 5.0:
554+
for i in range(self._numSensors):
555+
self._cache[i] = (
556+
19.5
557+
+ i
558+
+ 5 * math.sin(self.lastDataTime / 100)
559+
+ random.random()
560+
)
561+
_logger.debug("Sensors %d returns %s" % (i, self._cache[i]))
562+
self.lastDataTime = time.time()
563+
return self._cache
564+
return None
565+
566+
def abort(self):
567+
pass
568+
569+
def _do_enable(self):
570+
return True
571+
572+
def _do_shutdown(self) -> None:
573+
pass
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
3+
## Copyright (C) 2020 David Miguel Susano Pinto <carandraug@gmail.com>
4+
## Copyright (C) 2023 Ian Dobbie <ian.dobbie@jhu.edu>
5+
##
6+
##
7+
## This file is part of Microscope.
8+
##
9+
## Microscope is free software: you can redistribute it and/or modify
10+
## it under the terms of the GNU General Public License as published by
11+
## the Free Software Foundation, either version 3 of the License, or
12+
## (at your option) any later version.
13+
##
14+
## Microscope is distributed in the hope that it will be useful,
15+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
## GNU General Public License for more details.
18+
##
19+
## You should have received a copy of the GNU General Public License
20+
## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
21+
22+
"""Raspberry Pi Value Logger module.
23+
"""
24+
25+
import contextlib
26+
import re
27+
import threading
28+
import time
29+
import typing
30+
import logging
31+
import queue
32+
import Adafruit_MCP9808.MCP9808 as MCP9808
33+
34+
# library for TSYS01 sensor
35+
# import TSYS01.TSYS01 as TSYS01
36+
37+
import microscope.abc
38+
39+
40+
# Support for async digital IO control on the Raspberryy Pi.
41+
# Currently supports digital input and output via GPIO lines
42+
43+
44+
# Use BCM GPIO references (naming convention for GPIO pins from Broadcom)
45+
# instead of physical pin numbers on the Raspberry Pi board
46+
47+
_logger = logging.getLogger(__name__)
48+
49+
50+
class RPiValueLogger(microscope.abc.ValueLogger):
51+
"""ValueLogger device for a Raspberry Pi with support for
52+
MCP9808 and TSYS01 I2C thermometer chips."""
53+
54+
def __init__(self, sensors=[], **kwargs):
55+
super().__init__(**kwargs)
56+
# setup Q for fetching data.
57+
self.inputQ = queue.Queue()
58+
self._sensors = []
59+
for sensor in sensors:
60+
sensor_type, i2c_address = sensor
61+
print(
62+
"adding sensor: " + sensor_type + " Adress: %d " % i2c_address
63+
)
64+
if sensor_type == "MCP9808":
65+
self._sensors.append(MCP9808.MCP9808(address=i2c_address))
66+
# starts the last one added
67+
self._sensors[-1].begin()
68+
print(self._sensors[-1].readTempC())
69+
elif sensor_type == "TSYS01":
70+
self._sensors.append(TSYS01.TSYS01(address=i2c_address))
71+
print(self._sensors[-1].readTempC())
72+
self.initialize()
73+
74+
def initialize(self):
75+
self.updatePeriod = 1.0
76+
self.readsPerUpdate = 10
77+
# Open and start all temp sensors
78+
# A thread to record periodic temperature readings
79+
# This reads temperatures and logs them
80+
if self._sensors:
81+
# only strart thread if we have a sensor
82+
self.statusThread = threading.Thread(target=self.updateTemps)
83+
self.stopEvent = threading.Event()
84+
self.statusThread.Daemon = True
85+
self.statusThread.start()
86+
87+
def debug_ret_Q(self):
88+
if not self.inputQ.empty():
89+
return self.inputQ.get()
90+
91+
# functions required for a data device.
92+
def _fetch_data(self):
93+
# need to return data fetched from interupt driven state chnages.
94+
if self.inputQ.empty():
95+
return None
96+
temps = self.inputQ.get()
97+
if len(temps) == 1:
98+
outtemps = temps[0]
99+
else:
100+
outtemps = temps
101+
# print(self.inputQ.get())
102+
_logger.debug("Temp readings are %s" % str(outtemps))
103+
return outtemps
104+
105+
def abort(self):
106+
pass
107+
108+
def _do_enable(self):
109+
return True
110+
111+
def _do_shutdown(self) -> None:
112+
# need to kill threads.
113+
self.stopEvent.set()
114+
115+
# return the list of current temperatures.
116+
117+
def get_temperature(self):
118+
return self.temperature
119+
120+
# function to change updatePeriod
121+
def tempUpdatePeriod(self, period):
122+
self.updatePeriod = period
123+
124+
# function to change readsPerUpdate
125+
def tempReadsPerUpdate(self, reads):
126+
self.readsPerUpdate = reads
127+
128+
# needs to be re-written to push data into a queue which _fetch_data can
129+
# then send out to the server.
130+
131+
# function to read temperature at set update frequency.
132+
# runs in a separate thread.
133+
def updateTemps(self):
134+
"""Runs in a separate thread publish status updates."""
135+
self.temperature = [None] * len(self._sensors)
136+
tempave = [None] * len(self._sensors)
137+
138+
# self.create_rotating_log()
139+
140+
if len(self._sensors) == 0:
141+
return ()
142+
143+
while True:
144+
if self.stopEvent.is_set():
145+
break
146+
# zero the new averages.
147+
for i in range(len(self._sensors)):
148+
tempave[i] = 0.0
149+
# take readsPerUpdate measurements and average to reduce digitisation
150+
# errors and give better accuracy.
151+
for i in range(int(self.readsPerUpdate)):
152+
for i in range(len(self._sensors)):
153+
try:
154+
tempave[i] += self._sensors[i].readTempC()
155+
except:
156+
localTemperature = None
157+
time.sleep(self.updatePeriod / self.readsPerUpdate)
158+
for i in range(len(self._sensors)):
159+
self.temperature[i] = tempave[i] / self.readsPerUpdate
160+
_logger.debug(
161+
"Temperature-%s = %s" % (i, self.temperature[i])
162+
)
163+
self.inputQ.put(self.temperature)

0 commit comments

Comments
 (0)