Moved brewfiles to source folder & add /source to sys path from __init__
This commit is contained in:
5
source/__init__.py
Normal file
5
source/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
print('at init')
|
||||
|
||||
import sys
|
||||
sys.path.append('/home/pi/brewLogger/source')
|
||||
print('added to source')
|
||||
49
source/brewCamera.py
Normal file
49
source/brewCamera.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
import time
|
||||
import picamera
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
from logger import logger
|
||||
|
||||
class BrewCamera():
|
||||
def __init__(self, interval=10):
|
||||
self.lastCaptureTimestamp = None
|
||||
self.interval = interval
|
||||
self.warmupTime = 0.3
|
||||
|
||||
def spawnBackgroundCapture(self):
|
||||
thread = threading.Thread(target=self.captureOnIntervalForever, args=())
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
logger.info("spawned camera capture daemon at interval: {}".format(self.interval))
|
||||
|
||||
def captureOnIntervalForever(self):
|
||||
while True:
|
||||
time.sleep(self.interval - self.warmupTime)
|
||||
self.capture()
|
||||
|
||||
def capture(self):
|
||||
try:
|
||||
logger.debug('Capturing image')
|
||||
with picamera.PiCamera() as camera:
|
||||
camera.resolution = (1297, 972)
|
||||
camera.rotation = 180
|
||||
camera.annotate_background = picamera.Color('black')
|
||||
camera.annotate_text_size = 50 # (values 6 to 160, default is 32)
|
||||
camera.annotate_text = datetime.now().strftime('%A %d %b %Y %H:%M:%S')
|
||||
|
||||
# Camera warm-up time
|
||||
time.sleep(self.warmupTime)
|
||||
camera.capture('assets/foo.jpg')
|
||||
self.lastCaptureTime = datetime.now()
|
||||
os.replace('assets/foo.jpg', 'assets/capture.jpg')
|
||||
|
||||
except picamera.exc.PiCameraMMALError as error:
|
||||
logger.error('Picamera MMAL exception. Retrying picture in 1 second', es={
|
||||
error: str(error),
|
||||
exception: error.__class__.__name__
|
||||
})
|
||||
time.sleep(1)
|
||||
self.capture()
|
||||
|
||||
62
source/brewRelay.py
Normal file
62
source/brewRelay.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import RPi.GPIO as GPIO
|
||||
from logger import logger
|
||||
from utils import getConfig
|
||||
import sqlite3
|
||||
|
||||
class BrewRelay():
|
||||
def __init__(self, pin, controls):
|
||||
# GPIO.setmode(GPIO.BOARD)
|
||||
self.pin = pin
|
||||
self.controls = controls
|
||||
|
||||
config = getConfig()
|
||||
self.conn = sqlite3.connect(config['database']['name'], check_same_thread=False)
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
GPIO.setup(self.pin, GPIO.OUT)
|
||||
self.set(self.state, True)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
query = 'select state from relay where pin = {}'.format(self.pin)
|
||||
self.cur.execute(query)
|
||||
|
||||
return True if self.cur.fetchone()[0] == 1 else False
|
||||
|
||||
def saveStateToDB(self, state):
|
||||
query = 'update relay set state = {} where pin = {}'
|
||||
self.cur.execute(query.format(state, self.pin))
|
||||
self.conn.commit()
|
||||
|
||||
def set(self, state, setup=False):
|
||||
GPIO.output(self.pin, not state) # for some reason this is negated
|
||||
if setup is False:
|
||||
logger.info('Relay toggled', es={'relayState': state, 'relayType': self.controls})
|
||||
else:
|
||||
logger.info('Resuming relay state', es={'relayState': state, 'relayType': self.controls})
|
||||
|
||||
self.saveStateToDB(state)
|
||||
|
||||
def toggle(self):
|
||||
self.set(not state)
|
||||
|
||||
@staticmethod
|
||||
def fromYaml(loader, node):
|
||||
return BrewRelay(**loader.construct_mapping(node))
|
||||
|
||||
@staticmethod
|
||||
def getRelayByWhatItControls(relays, controls):
|
||||
return next(( relay for relay in relays if relay.controls == controls), None)
|
||||
|
||||
def __exit__(self):
|
||||
self.conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
brewRelay = BrewRelay()
|
||||
|
||||
import time
|
||||
while True:
|
||||
print('toggling!')
|
||||
brewRelay.toggle()
|
||||
time.sleep(1)
|
||||
|
||||
192
source/brewSensor.py
Normal file
192
source/brewSensor.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import bme680
|
||||
import adafruit_dht
|
||||
import board
|
||||
import threading
|
||||
import time
|
||||
|
||||
from logger import logger
|
||||
|
||||
class BrewSensor():
|
||||
def __init__(self, location, interval=2):
|
||||
self.location = location
|
||||
self.interval = interval
|
||||
|
||||
@staticmethod
|
||||
def getSensorByItsLocation(sensors, location):
|
||||
return next(( sensor for sensor in sensors if sensor.location == location), None)
|
||||
|
||||
|
||||
class DHT11Sensor(BrewSensor):
|
||||
def __init__(self, pin, location, interval):
|
||||
super().__init__(location, interval)
|
||||
self.pin = pin
|
||||
self.sensor = adafruit_dht.DHT11(board.D17)
|
||||
|
||||
@property
|
||||
def temp(self):
|
||||
try:
|
||||
return self.sensor.temperature
|
||||
except RuntimeError as error:
|
||||
timeout = 2
|
||||
telemetry = {
|
||||
'location': self.location,
|
||||
'error': str(error),
|
||||
'exception': error.__class__.__name__
|
||||
}
|
||||
logger.error('DHT sensor got invalid checksum, trying again in {} seconds.'.format(timeout), es=telemetry)
|
||||
time.sleep(timeout)
|
||||
return self.temp
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
return self.sensor.humidity
|
||||
|
||||
def logReadings(self):
|
||||
telemetry = {
|
||||
'temperature': self.temp,
|
||||
'humidity': self.humidity,
|
||||
'location': self.location
|
||||
}
|
||||
|
||||
logger.info("Sensor readings", es=telemetry)
|
||||
return
|
||||
|
||||
def spawnBackgroundSensorLog(self):
|
||||
thread = threading.Thread(target=self.logSensorOnIntervalForever, args=())
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
logger.info("spawned background sensor {} log at interval: {}".format(self.location, self.interval))
|
||||
|
||||
def logSensorOnIntervalForever(self):
|
||||
try:
|
||||
self.logReadings()
|
||||
except RuntimeError as error:
|
||||
logger.error('Sensor log daemon failed, sleeping and trying again', es={
|
||||
'location': self.location,
|
||||
'error': str(error),
|
||||
'exception': error.__class__.__name__
|
||||
})
|
||||
time.sleep(2)
|
||||
|
||||
time.sleep(self.interval)
|
||||
self.logSensorOnIntervalForever()
|
||||
|
||||
@staticmethod
|
||||
def fromYaml(loader, node):
|
||||
return DHT11Sensor(**loader.construct_mapping(node))
|
||||
|
||||
class BCM600Sensor(BrewSensor):
|
||||
def __init__(self, location, interval):
|
||||
super().__init__(location, interval)
|
||||
|
||||
self.sensor = bme680.BME680()
|
||||
self.sensor.set_humidity_oversample(bme680.OS_2X)
|
||||
self.setupSensors()
|
||||
self.lastSensorRead = time.time()
|
||||
|
||||
def setupSensors(self):
|
||||
self.sensor.set_pressure_oversample(bme680.OS_4X)
|
||||
self.sensor.set_temperature_oversample(bme680.OS_8X)
|
||||
self.sensor.set_filter(bme680.FILTER_SIZE_3)
|
||||
|
||||
self.sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
|
||||
self.sensor.set_gas_heater_temperature(320)
|
||||
self.sensor.set_gas_heater_duration(150)
|
||||
self.sensor.select_gas_heater_profile(0)
|
||||
|
||||
def read(self):
|
||||
self.lastSensorRead = time.time()
|
||||
return self.sensor.get_sensor_data()
|
||||
|
||||
def logReadings(self, detailed):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
|
||||
telemetry = {
|
||||
'temperature': self.temp,
|
||||
'pressure': self.pressure,
|
||||
'humidity': self.humidity,
|
||||
'location': self.location
|
||||
}
|
||||
|
||||
if detailed:
|
||||
telemetry['gasResistance'] = self.gasResistance
|
||||
telemetry['stableHeat'] = self.stableHeat
|
||||
|
||||
logger.info("Sensor readings", es=telemetry)
|
||||
return
|
||||
|
||||
def saveToFile(self, filename):
|
||||
with open(filename, "w") as file:
|
||||
file.write("{}".format(self.temp))
|
||||
|
||||
def spawnBackgroundSensorLog(self):
|
||||
thread = threading.Thread(target=self.logSensorOnIntervalForever, args=())
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
logger.info("spawned background sensor {} log at interval: {}".format(self.location, self.interval))
|
||||
|
||||
def logSensorOnIntervalForever(self):
|
||||
try:
|
||||
self.logReadings(detailed=True)
|
||||
except exception as error:
|
||||
logger.error('Sensor log daemon failed, sleeping and trying again', es={
|
||||
'location': self.location,
|
||||
'error': str(error),
|
||||
'exception': error.__class__.__name__
|
||||
})
|
||||
time.sleep(2)
|
||||
|
||||
time.sleep(self.interval)
|
||||
self.logSensorOnIntervalForever()
|
||||
|
||||
@property
|
||||
def needToUpdateReadings(self):
|
||||
return time.time() - self.lastSensorRead > 1
|
||||
|
||||
@property
|
||||
def temp(self):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
return self.sensor.data.temperature
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
return self.sensor.data.pressure
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
return self.sensor.data.humidity
|
||||
|
||||
@property
|
||||
def gasResistance(self):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
return self.sensor.data.gas_resistance
|
||||
|
||||
@property
|
||||
def stableHeat(self):
|
||||
if self.needToUpdateReadings:
|
||||
self.read()
|
||||
return self.sensor.data.heat_stable
|
||||
|
||||
@staticmethod
|
||||
def fromYaml(loader, node):
|
||||
return BCM600Sensor(**loader.construct_mapping(node))
|
||||
|
||||
def __repr__(self):
|
||||
return "{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH".format(self.temp, self.pressure, self.humidity)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# brewSensor = DHT11Sensor(13, 'outside', 30)
|
||||
brewSensor = BCM600Sensor('inside', 2)
|
||||
|
||||
while True:
|
||||
print(brewSensor.temp)
|
||||
time.sleep(1)
|
||||
|
||||
79
source/logger.py
Normal file
79
source/logger.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/bin/usr/python3
|
||||
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
import urllib.request
|
||||
|
||||
from utils import getConfig
|
||||
|
||||
config = getConfig()
|
||||
LOGGER_NAME = config['logger']['name']
|
||||
esHost = config['elastic']['host']
|
||||
esPort = config['elastic']['port']
|
||||
|
||||
class ESHandler(logging.Handler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.host = kwargs.get('host')
|
||||
self.port = kwargs.get('port') or 9200
|
||||
self.date = date.today()
|
||||
self.sessionID = uuid.uuid4()
|
||||
|
||||
logging.StreamHandler.__init__(self)
|
||||
|
||||
def emit(self, record):
|
||||
self.format(record)
|
||||
timestamp = datetime.fromtimestamp(record.created).strftime('%Y-%m-%dT%H:%M:%S.%f+02:00')
|
||||
|
||||
indexURL = 'http://{}:{}/{}-{}/_doc'.format(self.host, self.port, LOGGER_NAME, self.date.strftime('%Y.%m.%d'))
|
||||
|
||||
doc = {
|
||||
'severity': record.levelname,
|
||||
'message': record.message,
|
||||
'@timestamp': timestamp,
|
||||
'sessionID': str(self.sessionID)
|
||||
}
|
||||
|
||||
if hasattr(record, 'es'):
|
||||
for param in record.es.values():
|
||||
if ': {}'.format(param) in record.message:
|
||||
doc['message'] = record.message.replace(': {}'.format(str(param)), '')
|
||||
|
||||
doc = {**record.es, **doc}
|
||||
|
||||
payload = json.dumps(doc).encode('utf8')
|
||||
req = urllib.request.Request(indexURL, data=payload,
|
||||
headers={'content-type': 'application/json'})
|
||||
response = urllib.request.urlopen(req)
|
||||
response = response.read().decode('utf8')
|
||||
return response
|
||||
|
||||
class ElasticFieldParameterAdapter(logging.LoggerAdapter):
|
||||
def __init__(self, logger, extra={}):
|
||||
super().__init__(logger, extra)
|
||||
|
||||
def process(self, msg, kwargs):
|
||||
if kwargs == {}:
|
||||
return (msg, kwargs)
|
||||
extra = kwargs.get("extra", {})
|
||||
extra.update({"es": kwargs.pop("es", True)})
|
||||
kwargs["extra"] = extra
|
||||
return (msg, kwargs)
|
||||
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.WARNING)
|
||||
|
||||
eh = ESHandler(host=esHost, port=esPort)
|
||||
eh.setLevel(logging.DEBUG)
|
||||
|
||||
formatter = logging.Formatter('%(asctime)s %(levelname)8s | %(message)s')
|
||||
logger.addHandler(ch)
|
||||
logger.addHandler(eh)
|
||||
logger = ElasticFieldParameterAdapter(logger)
|
||||
|
||||
|
||||
22
source/utils.py
Normal file
22
source/utils.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/bin/usr/python3
|
||||
import os
|
||||
import yaml
|
||||
|
||||
def loadYaml(filePath):
|
||||
with open(filePath, "r") as stream:
|
||||
try:
|
||||
return yaml.safe_load(stream)
|
||||
except yaml.YAMLError as exception:
|
||||
print('Error: {} is unparsable'.format(filePath))
|
||||
print(exception)
|
||||
|
||||
def getConfig():
|
||||
pwd = os.path.dirname(os.path.abspath(__file__))
|
||||
path = os.path.join(pwd,'../', 'config.yaml')
|
||||
|
||||
if not os.path.isfile(path):
|
||||
print('Please fill out and rename config file. Check README for more info.')
|
||||
exit(0)
|
||||
|
||||
return loadYaml(path)
|
||||
|
||||
Reference in New Issue
Block a user