t
This commit is contained in:
parent
bf4b02902b
commit
f468332843
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "sql_as_rest_api"]
|
|
||||||
path = sql_as_rest_api
|
|
||||||
url = https://github.com/moll-re/sql_as_rest_api
|
|
23
README.md
23
README.md
@ -6,18 +6,29 @@ Just like AIO-coolers, this little program aims to tackle many problems at once.
|
|||||||
## What it mainly does
|
## What it mainly does
|
||||||
* chat-bot (via telegram)
|
* chat-bot (via telegram)
|
||||||
* clock and basic display (via LED-Matrix (size to taste))
|
* clock and basic display (via LED-Matrix (size to taste))
|
||||||
* dashboard (via external browser)
|
* measure ambient temperatures
|
||||||
|
* Logging of the previous actions
|
||||||
|
|
||||||
|
|
||||||
### Chatbot
|
### 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.
|
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.
|
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
|
### Ambient measurements
|
||||||
Shows basic info of the program and other useful things.
|
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
|
@ -1,8 +1,5 @@
|
|||||||
from .template import *
|
from .template import *
|
||||||
|
|
||||||
import datetime
|
|
||||||
import requests
|
|
||||||
|
|
||||||
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
|
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +38,7 @@ class Lists(BotFunc):
|
|||||||
|
|
||||||
def entry_point(self, update: Update, context: CallbackContext) -> None:
|
def entry_point(self, update: Update, context: CallbackContext) -> None:
|
||||||
super().entry_point(update, context)
|
super().entry_point(update, context)
|
||||||
|
# TODO Change DB
|
||||||
lists = self.db.lists.select()
|
lists = self.db.lists.select()
|
||||||
sl = [l.name for l in lists]
|
sl = [l.name for l in lists]
|
||||||
keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]
|
keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]
|
||||||
|
@ -62,6 +62,7 @@ class Status(BotFunc):
|
|||||||
u = str(self.get_ngrok_url())
|
u = str(self.get_ngrok_url())
|
||||||
message += "URL: [" + u + "](" + u + ")\n"
|
message += "URL: [" + u + "](" + u + ")\n"
|
||||||
|
|
||||||
|
# TODO new DB
|
||||||
tot_r = self.db.chats.select().where(self.db.chats.read == True).count()
|
tot_r = self.db.chats.select().where(self.db.chats.read == True).count()
|
||||||
message += "Total messages read: `{}`\n".format(tot_r)
|
message += "Total messages read: `{}`\n".format(tot_r)
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import datetime
|
|
||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
|
||||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
|
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
@ -21,20 +20,20 @@ class BotFunc():
|
|||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
|
||||||
def log_activity(self, **kwargs):
|
# def log_activity(self, **kwargs):
|
||||||
# mark that a new command has been executed
|
# # mark that a new command has been executed
|
||||||
try:
|
# try:
|
||||||
data = self.db.chats(
|
# data = self.db.chats(
|
||||||
time=datetime.datetime.now(),
|
# time=datetime.datetime.now(),
|
||||||
**kwargs
|
# **kwargs
|
||||||
)
|
# )
|
||||||
# kwargs can look like
|
# # kwargs can look like
|
||||||
# receive=True,
|
# # receive=True,
|
||||||
# execute=True,
|
# # execute=True,
|
||||||
# send=False,
|
# # send=False,
|
||||||
data.save()
|
# data.save()
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
self.logger.error("sql error: {}".format(e))
|
# self.logger.error("sql error: {}".format(e))
|
||||||
|
|
||||||
def entry_point(self, update: Update, context: CallbackContext) -> None:
|
def entry_point(self, update: Update, context: CallbackContext) -> None:
|
||||||
if update.message.text:
|
if update.message.text:
|
||||||
|
@ -1,44 +1,26 @@
|
|||||||
|
from datetime import datetime
|
||||||
from peewee import *
|
from peewee import *
|
||||||
# from playhouse.pool import PooledMySQLDatabase
|
|
||||||
from playhouse.shortcuts import ReconnectMixin
|
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_tables(db):
|
||||||
|
db.create_tables([SensorMetric, ChatMetric, ErrorMetric, List])
|
||||||
|
|
||||||
db = DatabaseProxy()
|
db = DatabaseProxy()
|
||||||
# set the nature of the db at runtime
|
# set the nature of the db at runtime
|
||||||
|
|
||||||
class ReconnectDataBase(ReconnectMixin, MySQLDatabase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DBModel(Model):
|
class DBModel(Model):
|
||||||
# specific to the above DB
|
# specific to the above DB
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
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):
|
class Metric(DBModel):
|
||||||
time = DateTimeField()
|
time = DateTimeField(default = datetime.now())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Actual metrics:
|
### Actual metrics:
|
||||||
@ -48,24 +30,26 @@ class SensorMetric(Metric):
|
|||||||
temperature = IntegerField()
|
temperature = IntegerField()
|
||||||
humidity = IntegerField()
|
humidity = IntegerField()
|
||||||
luminosity = IntegerField()
|
luminosity = IntegerField()
|
||||||
default = {"temperature": 100, "humidity": 100, "luminosity": 100}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMetric(Metric):
|
class ChatMetric(Metric):
|
||||||
read = BooleanField()
|
read = BooleanField()
|
||||||
send = BooleanField()
|
send = BooleanField()
|
||||||
execute = BooleanField()
|
execute = BooleanField()
|
||||||
default = {"read": False, "send": False, "execute": False}
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorMetric(Metric):
|
class ErrorMetric(Metric):
|
||||||
# same as above
|
# same as above
|
||||||
error = TextField()
|
error = TextField()
|
||||||
default = {"error": "SQL connection broke off"}
|
|
||||||
|
|
||||||
|
|
||||||
class List(DBModel):
|
class List(DBModel):
|
||||||
name = CharField(unique=True)
|
name = CharField(unique=True)
|
||||||
content = TextField() # unlimited length, use to serialise list into
|
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)
|
@ -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
|
|
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
Subproject commit 1e24c1491d2e80ce9d915b20f434c1e10fab3156
|
|
20
test.py
20
test.py
@ -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()
|
|
Loading…
x
Reference in New Issue
Block a user