This commit is contained in:
Remy Moll 2022-01-01 14:51:13 +01:00
parent bf4b02902b
commit f468332843
52 changed files with 3272 additions and 3419 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "sql_as_rest_api"]
path = sql_as_rest_api
url = https://github.com/moll-re/sql_as_rest_api

View File

@ -6,18 +6,29 @@ Just like AIO-coolers, this little program aims to tackle many problems at once.
## What it mainly does
* chat-bot (via telegram)
* clock and basic display (via LED-Matrix (size to taste))
* dashboard (via external browser)
* measure ambient temperatures
* Logging of the previous actions
### Chatbot
Periodically calls the telegram api and reacts to sent commands. Also handles basic calls to the hardware: it allows you to control certain aspects of the clock.
TODO: advanced analytics of the chat (grafana)
## Clock
### Clock
Server/Client which send/receive the output to show on the clock. Normally this is just a combination of time + weather. But special output can be triggered by the user.
## Dashboard
Shows basic info of the program and other useful things.
### Ambient measurements
Logs temperature, luminosity, humidity to a remote database. This information is then displayed in `moll.re`.
TODO: show advanced host stats (cpu/mem/...)
TODO: Log relevant worker info such as cpu activity and network connectivity.
## Submodules
This program makes use of git submodules, namely `sql_as_rest_api`. This implies additional steps when cloning this repo:
* CLone **this** repo to your machine.
* Enter the repo
* Type `git submodule init` which creates a `.gitmodules` file
* Type `git submodule update` which fetches the newest version of these submodules
TODO Describe dev process

View File

@ -1,8 +1,5 @@
from .template import *
import datetime
import requests
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
@ -41,6 +38,7 @@ class Lists(BotFunc):
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
# TODO Change DB
lists = self.db.lists.select()
sl = [l.name for l in lists]
keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]

View File

@ -62,6 +62,7 @@ class Status(BotFunc):
u = str(self.get_ngrok_url())
message += "URL: [" + u + "](" + u + ")\n"
# TODO new DB
tot_r = self.db.chats.select().where(self.db.chats.read == True).count()
message += "Total messages read: `{}`\n".format(tot_r)

View File

@ -1,5 +1,4 @@
import logging
import datetime
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
from telegram.ext import (
@ -21,20 +20,20 @@ class BotFunc():
self.db = db
def log_activity(self, **kwargs):
# mark that a new command has been executed
try:
data = self.db.chats(
time=datetime.datetime.now(),
**kwargs
)
# kwargs can look like
# receive=True,
# execute=True,
# send=False,
data.save()
except Exception as e:
self.logger.error("sql error: {}".format(e))
# def log_activity(self, **kwargs):
# # mark that a new command has been executed
# try:
# data = self.db.chats(
# time=datetime.datetime.now(),
# **kwargs
# )
# # kwargs can look like
# # receive=True,
# # execute=True,
# # send=False,
# data.save()
# except Exception as e:
# self.logger.error("sql error: {}".format(e))
def entry_point(self, update: Update, context: CallbackContext) -> None:
if update.message.text:

View File

@ -1,44 +1,26 @@
from datetime import datetime
from peewee import *
# from playhouse.pool import PooledMySQLDatabase
from playhouse.shortcuts import ReconnectMixin
import logging
import json
logger = logging.getLogger(__name__)
def create_tables(db):
db.create_tables([SensorMetric, ChatMetric, ErrorMetric, List])
db = DatabaseProxy()
# set the nature of the db at runtime
class ReconnectDataBase(ReconnectMixin, MySQLDatabase):
pass
class DBModel(Model):
# specific to the above DB
class Meta:
database = db
def save(self):
# fail-safe writing of the db-object. Usually threaded because the caller is threaded
try:
# db.connect()
super().save()
# db.close()
except Exception as e:
logger.error("Could not write to db. Dropping content of {}".format(self.__class__.__name__))
logger.error(e)
# db.atomic().rollback()
# def get(self, *query, **filters):
# try:
# return super().get(*query, **filters)
# except Exception as e:
# logger.error("Error while executing get: {}".format(e))
# print(query, filters)
class Metric(DBModel):
time = DateTimeField()
time = DateTimeField(default = datetime.now())
### Actual metrics:
@ -48,24 +30,26 @@ class SensorMetric(Metric):
temperature = IntegerField()
humidity = IntegerField()
luminosity = IntegerField()
default = {"temperature": 100, "humidity": 100, "luminosity": 100}
class ChatMetric(Metric):
read = BooleanField()
send = BooleanField()
execute = BooleanField()
default = {"read": False, "send": False, "execute": False}
class ErrorMetric(Metric):
# same as above
error = TextField()
default = {"error": "SQL connection broke off"}
class List(DBModel):
name = CharField(unique=True)
content = TextField() # unlimited length, use to serialise list into
default = {"content": "SQL connection broke off"}
@property
def content_as_list(self):
return json.loads(self.content)
def set_content(self, list_content):
self.content = json.dumps(list_content)

View File

@ -1,116 +0,0 @@
from . import models
from peewee import *
# from playhouse.pool import PooledMySQLDatabase
from playhouse.shortcuts import ReconnectMixin
import inspect
from . import keys
dbk = keys.db_keys
class ReconnectDataBase(ReconnectMixin, MySQLDatabase):
pass
class DBConnector:
"""Create a connection to a remote database and log some quantities that will be visualized otherwhere"""
def __init__(self):
self.db = models.db
self.sensors = models.SensorMetric
self.chats = models.ChatMetric
self.errors = models.ErrorMetric
self.lists = models.List
self.create_tables()
def create_tables(self):
self.db.create_tables([self.sensors, self.chats, self.errors, self.lists])
class DataBaseConnector:
def __init__(self) -> None:
self.db_object = models.ReconnectDataBase(
dbk["name"],
user=dbk["username"],
password=dbk["password"],
host=dbk["url"],
port=dbk["port"],
autorollback=True
)
models.db.initialize(self.db_object)
# self.sensors = models.SensorMetric
# self.chats = models.ChatMetric
# self.errors = models.ErrorMetric
# self.lists = models.List
## Set as property methods instead
self.db_object.create_tables([self.sensors, self.chats, self.errors, self.lists])
@property
def sensors(self):
self.connect_first()
return models.SensorMetric
@property
def chats(self):
self.connect_first()
return models.ChatMetric
@property
def errors(self):
self.connect_first()
return models.ErrorMetric
@property
def lists(self):
self.connect_first()
return models.List
def connect_first(self):
# if self.db_object.is_closed():
# self.db_object.connect()
self.db_object.connect(reuse_if_open=True)
# def auto_reconnect(func, *args, **kwargs):
# return func
# def classwide_decorator(decorator):
# def decorate(cls):
# for attr in inspect.getmembers(cls, inspect.ismethod): # there's propably a better way to do this
# # TODO: filter init
# print(attr)
# if callable(getattr(cls, attr)):
# setattr(cls, attr, decorator(getattr(cls, attr)))
# return cls
# return decorate
# # apply auto_reconnect to every method so that every method first checks the db connection and reconnects if necessary
# @classwide_decorator(auto_reconnect)
# class DataBaseConnector(ReconnectMixin, MySQLDatabase):
# def __init__(self, *args, **kwargs):
# super().__init__(
# dbk["name"],
# user=dbk["username"],
# password=dbk["password"],
# host=dbk["url"],
# port=dbk["port"],
# autorollback=True,
# *args, **kwargs)
# models.db.initialize(self)
# self.sensors = models.SensorMetric
# self.chats = models.ChatMetric
# self.errors = models.ErrorMetric
# self.lists = models.List
# self.create_tables([self.sensors, self.chats, self.errors, self.lists])
# def m1(self): pass
# def m2(self, x): pass

Binary file not shown.

@ -1 +0,0 @@
Subproject commit 1e24c1491d2e80ce9d915b20f434c1e10fab3156

20
test.py
View File

@ -1,20 +0,0 @@
class MyResource:
def __enter__(self):
print('Entering context.')
return self
def __exit__(self, *exc):
print('EXITING context.')
def try_me(self):
print("I work")
def fun():
with MyResource() as a:
print('Returning inside with-statement.')
return a
print('Returning outside with-statement.')
t =fun()
t.try_me()