Merge pull request #1 from lade043/First-major-contributions
First major contributions in an interim state
This commit was merged in pull request #1.
This commit is contained in:
@@ -127,3 +127,12 @@ dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# Log files of RasperryPie
|
||||
*.log
|
||||
|
||||
# File containing secrets (credentials) and config
|
||||
*.cfg
|
||||
|
||||
|
||||
AwningV2.py
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"cSpell.enabled": false,
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"driver": "SQLite",
|
||||
"name": "database",
|
||||
"database": "${workspaceFolder:RaspberryPie}/RaspberryPie/database.db"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +1,9 @@
|
||||
# RaspberryPie
|
||||
A python application for Raspberry Pi controlling GPIO via gmail and many more features.
|
||||
|
||||
## Features
|
||||
With this application you can control GPIO pins via Email, view a live image taken via the Raspi-Cam and temperature measurements in a Dropbox.
|
||||
But wherefore do you need it? For examle if you have an awning, which shouldn't get wet thus you want to control from far away... And many more, this is just what I use it for.
|
||||
|
||||
## Installation
|
||||
This section will be following.
|
||||
@@ -0,0 +1,4 @@
|
||||
import configparser
|
||||
|
||||
config = configparser.RawConfigParser()
|
||||
config.read("config.cfg") # edit this file for your specific configuration
|
||||
@@ -0,0 +1,104 @@
|
||||
#general imports
|
||||
import datetime
|
||||
import sqlite3
|
||||
import base64
|
||||
|
||||
# imports from Project
|
||||
import RaspberryPie.config as config
|
||||
|
||||
captScribe = None
|
||||
|
||||
def _set_captScribe(_captScribe):
|
||||
global captScribe
|
||||
captScribe = _captScribe
|
||||
|
||||
class BGenSecretary:
|
||||
def __init__(self, database_path, image_path):
|
||||
self.database = database_path
|
||||
self.image_path = image_path
|
||||
|
||||
|
||||
def _record(self, data: dict):
|
||||
connection = None
|
||||
try:
|
||||
connection = sqlite3.connect(self.database)
|
||||
cursor = connection.cursor()
|
||||
for typ in data:
|
||||
time = datetime.datetime.now()
|
||||
data[typ]["time_string"] = str(time)
|
||||
data[typ]["unix_time"] = int(time.timestamp())
|
||||
command = "INSERT INTO {} ({}) VALUES ({})".format(typ, str([i for i in data[typ]])[1:-1], str([data[typ][i] for i in data[typ]])[1:-1])
|
||||
cursor.execute(command)
|
||||
captScribe.info("Inserted data into table '{}' of database".format(typ), "BGenSecretary._record")
|
||||
connection.commit()
|
||||
|
||||
with open(config.config["FileLocations"]["lastChangeFile"], 'w') as f:
|
||||
f.write(str(int(datetime.datetime.now().timestamp())))
|
||||
except sqlite3.Error as e:
|
||||
captScribe.error(str(e), "BGenSecretary._record")
|
||||
finally:
|
||||
if connection is sqlite3.Connection:
|
||||
connection.close()
|
||||
|
||||
def recordPicture(self, picture: bytes):
|
||||
with open("{}/{}.jpg".format(self.image_path, str(datetime.datetime.now())), "w") as f:
|
||||
f.write(picture)
|
||||
|
||||
self._record({"picture": {"picture": base64.encodebytes(picture).decode('ascii').replace("\n", "")}})
|
||||
|
||||
def recordAir(self, data):
|
||||
self._record({"air": {"temperature": data[0], "humidity": data[1]}})
|
||||
|
||||
def _fetchSingle(self, table, time: datetime.datetime):
|
||||
unix_time = int(time.timestamp())
|
||||
command = "SELECT * FROM {} ORDER BY ABS(unix_time - {}) ASC LIMIT 1".format(table, unix_time)
|
||||
connection, entry = None
|
||||
try:
|
||||
connection = sqlite3.connect(self.database)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(command)
|
||||
entry = cursor.fetchone()
|
||||
captScribe.info("Fetched entry for {} from {}".format(str(unix_time), table), "BGenSecretary._fetchSingle")
|
||||
except sqlite3.Error as e:
|
||||
captScribe.error(str(e), "BGenSecretary._fetchSingle")
|
||||
finally:
|
||||
if connection is sqlite3.Connection:
|
||||
connection.close()
|
||||
return tuple(entry) if entry is sqlite3.Row else None
|
||||
|
||||
def _fetchMultiple(self, table, end:datetime.datetime, start: datetime.datetime):
|
||||
unix_time_end = int(end.timestamp())
|
||||
unix_time_start = int(start.timestamp())
|
||||
command = "SELECT * FROM {} WHERE unix_time BETWEEN {} AND {}".format(unix_time_start, unix_time_end)
|
||||
connection, entries = None
|
||||
try:
|
||||
connection = sqlite3.connect(self.database)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(command)
|
||||
entries = cursor.fetchall()
|
||||
captScribe.info("Fetched entries for {} from {}".format(str((unix_time_start, unix_time_end)), table), "BGenSecretary._fetchSingle")
|
||||
except sqlite3.Error as e:
|
||||
captScribe.error(str(e), "BGenSecretary._fetchSingle")
|
||||
finally:
|
||||
if connection is sqlite3.Connection:
|
||||
connection.close()
|
||||
return [tuple(entry) for entry in entries] if entries is not None else None
|
||||
|
||||
def fetchAir(self, time=datetime.datetime.now(), start_time=None): # time is either one point or the end point if start is given
|
||||
if not start_time:
|
||||
entry = self._fetchSingle("air", time)
|
||||
return {"unix_time": entry[0], "time_string": entry[1], "temperature": entry[2], "humidity": entry[3]}
|
||||
else:
|
||||
entries = self._fetchMultiple("air", time, start_time)
|
||||
return {"unix_time": [entry[0] for entry in entries], "time_string": [entry[1] for entry in entries], "temperature": [entry[2] for entry in entries], "humidity": [entry[3] for entry in entries]}
|
||||
|
||||
def fetchPicture(self, time=datetime.datetime.now(), start_time=None):
|
||||
if not start_time:
|
||||
entry = self._fetchSingle("picture", time)
|
||||
return {"unix_time": entry[0], "time_string": entry[1], "picture": entry[2]}
|
||||
else:
|
||||
entries = self._fetchMultiple("picture", time, start_time)
|
||||
return {"unix_time": [entry[0] for entry in entries], "time_string": [entry[1] for entry in entries], "picture": [entry[2] for entry in entries]}
|
||||
|
||||
|
||||
bGenSecretary = BGenSecretary(config.config["File Locations"]["database_file"], config.config["File Locations"],["image_path"])
|
||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
import datetime
|
||||
import RaspberryPie.config as config
|
||||
import RaspberryPie.dataHandling as dataHandling
|
||||
|
||||
def time(): return ""
|
||||
|
||||
def weatherUrls(): return [config.config["WeatherUrls"][i] for i in config.config["WeatherUrls"]]
|
||||
|
||||
def getLatestAirData(): return dataHandling.bGenSecretary.fetchAir()
|
||||
|
||||
def getLatestPicture(): return dataHandling.bGenSecretary.fetchAir()
|
||||
|
||||
def getDataRangeAir(start: int, end: int): return dataHandling.bGenSecretary.fetchAir(datetime.datetime.utcfromtimestamp(end), datetime.datetime.utcfromtimestamp(start))
|
||||
|
||||
def getDataRangePictures(start: int, end: int): return dataHandling.bGenSecretary.fetchPicture(datetime.datetime.utcfromtimestamp(end), datetime.datetime.utcfromtimestamp(start))
|
||||
|
||||
def getSingleAirData(time: int): return dataHandling.bGenSecretary.fetchAir(datetime.datetime.utcfromtimestamp(time))
|
||||
|
||||
def getSinglePicture(time: int): return dataHandling.bGenSecretary.fetchPicture(datetime.datetime.utcfromtimestamp(time))
|
||||
|
||||
def lastChange():
|
||||
with open(config.config["FileLocations"]["lastChangeFile"], 'r') as f:
|
||||
return int(f.read())
|
||||
@@ -0,0 +1,61 @@
|
||||
from flask import Flask, render_template, make_response
|
||||
import json
|
||||
import signal
|
||||
|
||||
import RaspberryPie.flaskAddons as fa
|
||||
|
||||
app = Flask(__name__)
|
||||
@app.route("/")
|
||||
def index():
|
||||
latestAirData = fa.getLatestAirData()
|
||||
latestPicture = fa.getLatestPicture()
|
||||
return render_template("index.html",
|
||||
WeatherTime = latestAirData["time_string"],
|
||||
temp = latestAirData["temperature"],
|
||||
humi = latestAirData["humidity"],
|
||||
weather_urls=fa.weatherUrls(),
|
||||
PictureTime = latestPicture["time_string"],
|
||||
picture = latestPicture["picture"])
|
||||
|
||||
|
||||
@app.route("/data/<start>/<end>/")
|
||||
def airData(start, end):
|
||||
data = fa.getDataRangeAir(int(start), int(end))
|
||||
return json.dumps(data)
|
||||
|
||||
@app.route("/picture/<time>/")
|
||||
def picture(time):
|
||||
if time == "latest":
|
||||
return json.dumps(fa.getLatestPicture())
|
||||
else:
|
||||
return json.dumps(fa.getLatestPicture(int(time)))
|
||||
|
||||
@app.route("/initiate/picture/")
|
||||
def inititatePicture():
|
||||
pass
|
||||
|
||||
@app.route("/inititate/air/")
|
||||
def ininitiateAir():
|
||||
pass
|
||||
|
||||
@app.route("/initiate/gpio/<state>/")
|
||||
def ininitisteGPIO(state):
|
||||
try:
|
||||
state = int(state)
|
||||
except:
|
||||
state = state
|
||||
|
||||
if state == "open" or state == 0:
|
||||
return "Opened", 200
|
||||
elif state == "close" or state == 1:
|
||||
return "Closed", 200
|
||||
return "state not found", 404
|
||||
|
||||
|
||||
@app.route("/initiate/shutdown/")
|
||||
def initiateShutdown():
|
||||
pass
|
||||
|
||||
@app.route("/lastChange/")
|
||||
def lastChange():
|
||||
return str(fa.lastChange())
|
||||
@@ -0,0 +1,9 @@
|
||||
#! /usr/bin/python3
|
||||
|
||||
import logging
|
||||
import sys
|
||||
logging.basicConfig(stream=sys.stderr)
|
||||
sys.path.insert(0, '/home/pi/RaspberryPie/RaspberryPie/')
|
||||
from flask_backend import app as application
|
||||
|
||||
application.secret_key = "Whatever"
|
||||
@@ -0,0 +1,130 @@
|
||||
#general imports
|
||||
from RaspberryPie.logger import CaptScribe
|
||||
import time
|
||||
import datetime
|
||||
|
||||
# imports for hardware
|
||||
import Adafruit_DHT
|
||||
from picamera import PiCamera
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# imports from project
|
||||
import RaspberryPie.config as config
|
||||
import RaspberryPie.dataHandling as dataHandling
|
||||
|
||||
captScribe = None
|
||||
|
||||
def _set_captScribe(_captScribe):
|
||||
global captScribe
|
||||
captScribe = _captScribe
|
||||
dataHandling._set_captScribe(_captScribe)
|
||||
|
||||
|
||||
class MajGenGPIOController:
|
||||
def __init__(self, pin_open, pin_close, delta_switchOff):
|
||||
self.pinOpen = pin_open
|
||||
self.pinClose = pin_close
|
||||
self.deltaOff = delta_switchOff
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(self.pinOpen, GPIO.OUT)
|
||||
GPIO.setup(self.pinClose, GPIO.OUT)
|
||||
GPIO.output(self.pinOpen, GPIO.HIGH)
|
||||
GPIO.output(self.pinClose, GPIO.HIGH)
|
||||
|
||||
def finalMission(self):
|
||||
self.kill()
|
||||
GPIO.cleanup()
|
||||
|
||||
def close(self):
|
||||
self.kill()
|
||||
GPIO.output(self.pinClose, GPIO.LOW)
|
||||
GPIO.output(self.pinOpen, GPIO.HIGH)
|
||||
captScribe.info("GPIO set to closing", "MajGenGPIOControl.close")
|
||||
|
||||
def open(self):
|
||||
self.kill()
|
||||
GPIO.output(self.pinClose, GPIO.HIGH)
|
||||
GPIO.output(self.pinOpen, GPIO.LOW)
|
||||
captScribe.info("GPIO set to opening", "MajGenGPIOControl.open")
|
||||
|
||||
def kill(self):
|
||||
GPIO.output(self.pinClose, GPIO.HIGH)
|
||||
GPIO.output(self.pinOpen, GPIO.HIGH)
|
||||
time.sleep(.5) # waiting, so relay definetly has switched
|
||||
captScribe.info("GPIO switched off", "MajGenGPIOControl.kill")
|
||||
|
||||
def delayedSwitchOff(self):
|
||||
time.sleep(self.deltaOff)
|
||||
self.kill()
|
||||
|
||||
class MajGenObserver:
|
||||
class DictatingMachine: # just a file like object
|
||||
def __init__(self):
|
||||
self.captured = ""
|
||||
def write(self, text):
|
||||
self.captured = text
|
||||
def __str__(self):
|
||||
return self.captured
|
||||
|
||||
def __init__(self, schedule_timing):
|
||||
self.schedule_timing = schedule_timing
|
||||
|
||||
def get_schedule(self):
|
||||
return self.schedule_timing
|
||||
|
||||
class MajGenAirChecker(MajGenObserver):
|
||||
def __init__(self, schedule_timing=None):
|
||||
if not schedule_timing:
|
||||
schedule_timing = config.config["Timing"]["environmentdelta"]
|
||||
super().__init__(schedule_timing)
|
||||
|
||||
def execute():
|
||||
sensor = Adafruit_DHT.DHT22
|
||||
pin = int(config.config["Hardware"]["dhtpin"])
|
||||
humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
|
||||
if humidity is None or temperature is None:
|
||||
captScribe.warning("Error while reading DHT22 sensor. The received values are: Humidity: {}, Temperature: {}".format(str(humidity), str(temperature)), "MajGenObserver.get_sensorData")
|
||||
humidity, temperature = (-1, -274) # if you need any numbers, why don't take impossible ones, to show error
|
||||
return humidity, temperature
|
||||
|
||||
class MajGenVisualObserver(MajGenObserver):
|
||||
def __init__(self, schedule_timing=None):
|
||||
if not schedule_timing:
|
||||
schedule_timing = config.config["Timing"]["picturedelta"]
|
||||
super().__init__(schedule_timing)
|
||||
|
||||
def execute():
|
||||
camera = PiCamera()
|
||||
majGensDictatingMachine = super.DictatingMachine()
|
||||
camera.start_preview()
|
||||
time.sleep(5) # letting camera adjust to environment (exposure and wb)
|
||||
camera.capture(majGensDictatingMachine, format="jpeg")
|
||||
camera.stop_preview()
|
||||
return str(majGensDictatingMachine)
|
||||
|
||||
class MajGenAirRecoder(MajGenAirChecker):
|
||||
def __init__(self, dataCollector, schedule_timing=None):
|
||||
self.dataCollector = dataCollector
|
||||
super().__init__(schedule_timing=schedule_timing)
|
||||
|
||||
def execute(self):
|
||||
data = super.execute()
|
||||
self.dataCollector.recordeAir({"air": (str(datetime.datetime.now()), data)})
|
||||
|
||||
class MajGenVisualRecoder(MajGenVisualObserver):
|
||||
def __init__(self, dataCollector, schedule_timing=None):
|
||||
self.dataCollector = dataCollector
|
||||
super().__init__(schedule_timing=schedule_timing)
|
||||
|
||||
def execute(self):
|
||||
data = super.execute()
|
||||
self.dataCollector.recordePicture({"picture": (str(datetime.datetime.now()), data)})
|
||||
|
||||
|
||||
|
||||
majGenGPIOController = MajGenGPIOController(config.config["Hardware"]["pinopen"], config.config["Hardware"]["pinclose"], config.config["Timing"]["gpioswitchoff"])
|
||||
majGenVisualObserver = MajGenVisualObserver()
|
||||
majGenAirChecker = MajGenAirChecker()
|
||||
majGenAirRecoder = MajGenAirRecoder(dataHandling.bGenSecretary)
|
||||
majGenVisualRecoder = MajGenVisualRecoder(dataHandling.bGenSecretary)
|
||||
@@ -0,0 +1,126 @@
|
||||
import dropbox
|
||||
import requests
|
||||
import socket
|
||||
import html2text
|
||||
import imaplib
|
||||
import email
|
||||
import datetime
|
||||
|
||||
|
||||
import RaspberryPie.config as config
|
||||
|
||||
captScribe = None
|
||||
|
||||
def _set_captScribe(_captScribe):
|
||||
global captScribe
|
||||
captScribe = _captScribe
|
||||
|
||||
class MajGenApiCom:
|
||||
class Telegram:
|
||||
def __init__(self, id, subject, sender, time, message):
|
||||
self.id = id
|
||||
self.subject = subject
|
||||
self.sender = sender
|
||||
self.time = time
|
||||
self.message = message
|
||||
def __str__(self):
|
||||
return "({}){}: {} at {}: {}".format(self.id, self.sender, self.subject, self.time, self.message)
|
||||
def delete(self):
|
||||
MajGenApiCom.delete_mail(id)
|
||||
|
||||
def __init__(self, email, dropbox):
|
||||
self.email = email
|
||||
self.dropbox = dropbox
|
||||
self.mail_config = config.config["Mail"]
|
||||
|
||||
def upload_dropbox(self, filename, content):
|
||||
try:
|
||||
dbx = dropbox.Dropbox(self.dropbox)
|
||||
if type(content) != bytes:
|
||||
content = content.encode('utf-8')
|
||||
dbx.files_upload(content, filename, mode=dropbox.files.WriteMode.overwrite)
|
||||
captScribe.info("Uploaded file: {}".format(filename), "MajGenApiCom.upload_dropbox")
|
||||
except (dropbox.dropbox.ApiError, dropbox.dropbox.AuthError, dropbox.dropbox.BadInputError,
|
||||
dropbox.dropbox.HttpError, dropbox.dropbox.InternalServerError, dropbox.dropbox.PathRootError,
|
||||
dropbox.dropbox.RateLimitError, requests.exceptions.ReadTimeout, requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.Timeout, requests.exceptions.ConnectionError) as dropbox_error:
|
||||
captScribe.error("{} occured, while trying to upload {}.".format(str(dropbox_error), filename), "MajGenApiCom.upload_dropbox")
|
||||
|
||||
def get_emails(self):
|
||||
mail_box = []
|
||||
imap = None
|
||||
|
||||
if not "@" + self.mail_config["url"] in self.email["address"]:
|
||||
add_url = True
|
||||
else:
|
||||
add_url = False
|
||||
try:
|
||||
imap = imaplib.IMAP4_SSL(self.mail_config["smtpserver"])
|
||||
complete_mail_adress = lambda: self.email["address"] + "@" + self.mail_config["url"] if add_url else self.email["address"]
|
||||
imap.login(complete_mail_adress(), self.email["password"])
|
||||
imap.select("inbox")
|
||||
|
||||
type, data = imap.search(None, 'ALL')
|
||||
ids = data[0]
|
||||
for id in reversed(ids.split()):
|
||||
text = ""
|
||||
typ, content = imap.fetch(id, '(RFC822)')
|
||||
|
||||
for part in content:
|
||||
if isinstance(part, tuple):
|
||||
message = email.message_from_string(part[1].decode('utf-8'))
|
||||
# credit for next 7 lines: https://stackoverflow.com/users/1105597/jury
|
||||
for part in message.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
if part.get_content_maintype() == 'text':
|
||||
# reading as HTML (not plain text)
|
||||
_html = part.get_payload(decode = True)
|
||||
text = html2text.html2text(_html)
|
||||
|
||||
telegram = self.Telegram(id, email.Header.decode_header(message["Subject"])[0][0],
|
||||
email.utils.parseaddr(message['From']),
|
||||
datetime.fromtimestamp(email.utils.mktime_tz(email.utils.parsedate_tz(message['Date']))),
|
||||
text)
|
||||
mail_box.append(telegram)
|
||||
captScribe.info("Gotten email: {}".format(str(telegram)), "MajGenApiCom.get_emails")
|
||||
except (imaplib.IMAP4.error, socket.error) as imap_error:
|
||||
captScribe.error("Getting emails failed with: {}.".format(str(imap_error)), "MajGenApiCom.get_emails")
|
||||
|
||||
finally:
|
||||
try:
|
||||
if imap:
|
||||
imap.close()
|
||||
imap.logout()
|
||||
except:
|
||||
captScribe.error("Logout of imap failed.", "MajGenApiCom.get_emails")
|
||||
|
||||
def delete_mail(self, id):
|
||||
imap = None
|
||||
|
||||
if not "@" + self.mail_config["url"] in self.email["address"]:
|
||||
add_url = True
|
||||
else:
|
||||
add_url = False
|
||||
try:
|
||||
imap = imaplib.IMAP4_SSL(self.mail_config["smtpserver"])
|
||||
complete_mail_adress = lambda: self.email["address"] + "@" + self.mail_config["url"] if add_url else self.email["address"]
|
||||
imap.login(complete_mail_adress(), self.email["password"])
|
||||
imap.select("inbox")
|
||||
|
||||
imap.store(id, '+FLAGS', '\\DELETED')
|
||||
captScribe.info("Deleted email with id {}".format(id), "MajGenApiCom.delete_mail")
|
||||
|
||||
except (imaplib.IMAP4.error, socket.error) as imap_error:
|
||||
captScribe.error("Getting emails failed with: {}.".format(str(imap_error)), "MajGenApiCom.delete_mail")
|
||||
|
||||
finally:
|
||||
try:
|
||||
imap.expunge()
|
||||
imap.close()
|
||||
imap.logout()
|
||||
except:
|
||||
captScribe.error("Logout of imap failed.", "MajGenApiCom.delete_mail")
|
||||
|
||||
|
||||
majGenApiCom = MajGenApiCom({"address": config.config["Secrets"]["emailaddress"], "password": config.config["Secrets"]["emailpassword"]}, config.config["Secrets"]["dropboxtoken"])
|
||||
@@ -0,0 +1,50 @@
|
||||
var chrtArea = document.getElementById("EnvironmentChart").getContext('2d');
|
||||
var chrt = new Chart(chrtArea, {
|
||||
type: 'line',
|
||||
data: {},
|
||||
options: {
|
||||
scales: {
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
maxTicksLimit: 8
|
||||
}
|
||||
}],
|
||||
yAxes:[{
|
||||
id: "Temp",
|
||||
type: "linear",
|
||||
position: "left"
|
||||
}, {
|
||||
id: "Humi",
|
||||
type: "linear",
|
||||
position: "right"
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getEnvironmentData(range){
|
||||
var response = fetch("/data/environment/" + range[0] + "/" + range[1])
|
||||
var labels = [];
|
||||
response.labels.forEach(element => {
|
||||
labels.push(unixToHuman(element))
|
||||
});
|
||||
return {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Temperature",
|
||||
yAxisID: "Temp",
|
||||
data: response.temp
|
||||
},
|
||||
{
|
||||
label: "Humidity",
|
||||
yAxisID: "Humi",
|
||||
data: response.humi
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function unixToHuman(unix){
|
||||
var str = new Date(unix).toLocaleTimeString()
|
||||
return str
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# general imports
|
||||
import logging
|
||||
|
||||
|
||||
class CaptScribe:
|
||||
def __init__(self, log_info, log_error):
|
||||
self.logfile_info = log_info
|
||||
self.logfile_error = log_error
|
||||
self.loggers = [logging.getLogger("log_info"), logging.getLogger("log_error")]
|
||||
self.formatters = [logging.Formatter("%(asctime)s : %(level), %(message)s"), logging.Formatter("%(asctime)s : %(message)s: %(sinfo)")]
|
||||
self.handlers = [logging.TimedRotatingFileHandler(self.logfile_info, when='midnight', interval=1), logging.TimedRotatingFileHandler(self.logfile_error, when='midnight', interval=1)]
|
||||
for i, handler in enumerate(self.handlers):
|
||||
handler.setFormatter(self.formatters[i])
|
||||
|
||||
for i, logger in self.loggers:
|
||||
logger.addHandler(self.handlers[i])
|
||||
|
||||
self.loggers[0].level = logging.INFO
|
||||
self.loggers[1].level = logging.ERROR
|
||||
|
||||
def critical(self, msg, func=""):
|
||||
for logger in self.loggers:
|
||||
logger.critical(func.upper() + ": " + msg)
|
||||
|
||||
def error(self, msg, func=""):
|
||||
for logger in self.loggers:
|
||||
logger.error(func.upper() + ": " + msg)
|
||||
|
||||
def warning(self, msg, func=""):
|
||||
for logger in self.loggers:
|
||||
logger.warning(func.upper() + ": " + msg)
|
||||
|
||||
def info(self, msg, func=""):
|
||||
for logger in self.loggers:
|
||||
logger.info(func.upper() + ": " + msg)
|
||||
|
||||
def debug(self, msg, func=""):
|
||||
for logger in self.loggers:
|
||||
logger.debug(func.upper() + ": " + msg)
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,101 @@
|
||||
# general imports
|
||||
import os
|
||||
import time
|
||||
import numpy as np
|
||||
import datetime
|
||||
|
||||
"""
|
||||
All classes are ranked in their hierarchy, like this (ranks of the US Marines):
|
||||
Capt << BGen << MajGen << LtGen << Gen
|
||||
"""
|
||||
# imports from project
|
||||
import RaspberryPie.tasks as tasks
|
||||
import RaspberryPie.hardwareControl as hardwareControl
|
||||
import RaspberryPie.config as config
|
||||
import RaspberryPie.logger as logger
|
||||
import RaspberryPie.internetHandling as internetHandling
|
||||
|
||||
captScribe = logger.CaptScribe(config.config.config["File Locations"]["logfile_info"], config.config.config["File Locations"]["logfile_error"])
|
||||
internetHandling._set_captScribe(captScribe)
|
||||
hardwareControl._set_captScribe(captScribe)
|
||||
|
||||
|
||||
ltGenInterpreter, genScheduler = None
|
||||
|
||||
tasks = [
|
||||
tasks.Task(hardwareControl.majGenGPIOController.open, lambda msg: 1 if ("auf" == msg.subject.lower() or "open" == msg.subject.lower()) else 0),
|
||||
tasks.Task(hardwareControl.majGenGPIOController.close, lambda msg: 1 if ("zu" == msg.subject.lower() or "close" == msg.subject.lower()) else 0)
|
||||
]
|
||||
|
||||
|
||||
class LtGenInterpreter:
|
||||
def __init__(self, tasks: list, communicator: internetHandling.MajGenApiCom, schedule_timing=None):
|
||||
if not schedule_timing:
|
||||
schedule_timing = config.config.config["Timing"]["maildelta"]
|
||||
self.schedule_timing
|
||||
self.tasks = tasks
|
||||
self.communicator = communicator
|
||||
|
||||
def get_schedule(self):
|
||||
return self.schedule_timing
|
||||
|
||||
def execute(self):
|
||||
messages = self.communicator.get_emails()
|
||||
for message in messages:
|
||||
for task in self.tasks:
|
||||
if task.test(message):
|
||||
task.function()
|
||||
|
||||
class GenScheduler:
|
||||
def __init__(self, subordinates: list):
|
||||
self.subordinates = subordinates
|
||||
self.schedule = {}
|
||||
self.schedule_delta = {}
|
||||
for subordinate in self.subordinates:
|
||||
self.schedule[subordinate] = list(datetime.datetime.now())
|
||||
self.schedule_delta[subordinate] = list(subordinate.get_schedule())
|
||||
self.time = datetime.datetime.now
|
||||
|
||||
def execute(self):
|
||||
while True:
|
||||
try:
|
||||
for subordinate in self.schedule:
|
||||
if self.schedule[subordinate][np.argmin(self.schedule[subordinate])] <= self.time():
|
||||
subordinate.execute()
|
||||
self.schedule[subordinate][np.argmin(self.schedule[subordinate])] += self.schedule_delta[subordinate][np.argmin(self.schedule[subordinate])]
|
||||
|
||||
time_list = lambda: [self.schedule[subordinate][np.argmin(self.schedule[subordinate])] for subordinate in self.schedule]
|
||||
sleep_duration = time_list()[np.argmin(time_list())]
|
||||
time.sleep(sleep_duration)
|
||||
except Exception as e:
|
||||
captScribe.critical("A not expected error occured: {}".format(str(e)), "GenScheduler.execute")
|
||||
|
||||
def initiate_shutdown(self, reboot=True):
|
||||
hardwareControl.majGenGPIOController.finalMission()
|
||||
reboot_str = lambda: " -r" if reboot else ""
|
||||
os.system("shutdown{} 0".format(reboot_str()))
|
||||
|
||||
|
||||
def init():
|
||||
global ltGenInterpreter, genScheduler
|
||||
ltGenInterpreter = LtGenInterpreter(tasks, internetHandling.majGenApiCom)
|
||||
genScheduler = GenScheduler((ltGenInterpreter, hardwareControl.majGenVisualRecorder, hardwareControl.majGenAirRecoder))
|
||||
|
||||
|
||||
def main():
|
||||
suspend_shutdown = False
|
||||
if genScheduler is GenScheduler:
|
||||
try:
|
||||
genScheduler.execute()
|
||||
except Exception as e:
|
||||
captScribe.critical(str(e), "main()")
|
||||
if e is KeyboardInterrupt:
|
||||
suspend_shutdown = True
|
||||
finally:
|
||||
if not suspend_shutdown:
|
||||
genScheduler.initiate_shutdown()
|
||||
print("The programm has finished. For more information see the log files.")
|
||||
|
||||
|
||||
init()
|
||||
main()
|
||||
@@ -0,0 +1,7 @@
|
||||
requests==2.22.0
|
||||
numpy==1.18.4
|
||||
Adafruit_DHT==1.4.0
|
||||
dropbox==10.4.1
|
||||
html2text==2020.1.16
|
||||
picamera==1.13
|
||||
RPi.GPIO==0.7.0
|
||||
@@ -0,0 +1,8 @@
|
||||
from RaspberryPie.internetHandling import MajGenApiCom
|
||||
|
||||
class tasks.Task:
|
||||
def __init__(self, function, check):
|
||||
self.function = function
|
||||
self.check = check
|
||||
def test(self, email: MajGenApiCom.Telegram):
|
||||
return self.check(email)
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RaspberryPie</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js" integrity="sha512-G8JE1Xbr0egZE5gNGyUm1fF764iHVfRXshIoUWCTPAbKkkItp/6qal5YAHXrxEu4HNfPTQs6HOu3D5vCGS1j3w==" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js" integrity="sha512-vBmx0N/uQOXznm/Nbkp7h0P1RfLSj0HQrFSzV8m7rOGyj30fYAOKHYvCNez+yM8IrfnW0TCodDEjRqf6fodf/Q==" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.css" integrity="sha512-SUJFImtiT87gVCOXl3aGC00zfDl6ggYAw5+oheJvRJ8KBXZrr/TMISSdVJ5bBarbQDRC2pR5Kto3xTR0kpZInA==" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" integrity="sha512-/zs32ZEJh+/EO2N1b0PEdoA10JkdC3zJ8L5FTiQu82LR9S/rOQNfQN7U59U9BC12swNeRAz3HSzIL2vpp4fv3w==" crossorigin="anonymous" />
|
||||
<script src="https://raw.githubusercontent.com/lade043/RaspberryPie/master/RaspberryPie/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>RaspberryPie</h1>
|
||||
<h2>Current Weather</h2>
|
||||
<p><a id="WeatherTime">{{ WeatherTime }}</a><br>Temperature: <a id="Temperature">{{ temp }}</a>, Humidity: <a id="Humidity">{{ humi }}</a></p>
|
||||
<canvas id="EnvironmentChart" width="400" height="200"></canvas>
|
||||
<h2>Weather Forecast</h2>
|
||||
<canvas id="ForecastChart" width="400" height="200"></canvas>
|
||||
<h2>Third party weather providers</h2>
|
||||
{% for image in weather_urls %}
|
||||
<img src="{{ image }}">
|
||||
<h2>Webcam</h2>
|
||||
<a id="PictureTime">{{ PictureTime }}</a><br>
|
||||
<img id="Webcam" src="data:image/jpg;base64,{{ picture }}">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user