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

290
.gitignore vendored
View File

@ -1,145 +1,145 @@
**/VE **/VE
#VS CODE files #VS CODE files
.vscode .vscode
# Persistence # Persistence
prst.* prst.*
log.txt log.txt
# API-Key (keep secret at all times) # API-Key (keep secret at all times)
keys.py keys.py
# Cookies # Cookies
.google-cookie .google-cookie
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# C extensions # C extensions
*.so *.so
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/
develop-eggs/ develop-eggs/
dist/ dist/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/ lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/
var/ var/
wheels/ wheels/
pip-wheel-metadata/ pip-wheel-metadata/
share/python-wheels/ share/python-wheels/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
MANIFEST MANIFEST
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest *.manifest
*.spec *.spec
# Installer logs # Installer logs
pip-log.txt pip-log.txt
pip-delete-this-directory.txt pip-delete-this-directory.txt
# Unit test / coverage reports # Unit test / coverage reports
htmlcov/ htmlcov/
.tox/ .tox/
.nox/ .nox/
.coverage .coverage
.coverage.* .coverage.*
.cache .cache
nosetests.xml nosetests.xml
coverage.xml coverage.xml
*.cover *.cover
*.py,cover *.py,cover
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/
# Translations # Translations
*.mo *.mo
*.pot *.pot
# Django stuff: # Django stuff:
*.log *.log
local_settings.py local_settings.py
db.sqlite3 db.sqlite3
db.sqlite3-journal db.sqlite3-journal
# Flask stuff: # Flask stuff:
instance/ instance/
.webassets-cache .webassets-cache
# Scrapy stuff: # Scrapy stuff:
.scrapy .scrapy
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/
# PyBuilder # PyBuilder
target/ target/
# Jupyter Notebook # Jupyter Notebook
.ipynb_checkpoints .ipynb_checkpoints
# IPython # IPython
profile_default/ profile_default/
ipython_config.py ipython_config.py
# pyenv # pyenv
.python-version .python-version
# pipenv # pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies # However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not # having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies. # install all needed dependencies.
#Pipfile.lock #Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow # PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/ __pypackages__/
# Celery stuff # Celery stuff
celerybeat-schedule celerybeat-schedule
celerybeat.pid celerybeat.pid
# SageMath parsed files # SageMath parsed files
*.sage.py *.sage.py
# Environments # Environments
.env .env
.venv .venv
env/ env/
venv/ venv/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
# Spyder project settings # Spyder project settings
.spyderproject .spyderproject
.spyproject .spyproject
# Rope project settings # Rope project settings
.ropeproject .ropeproject
# mkdocs documentation # mkdocs documentation
/site /site
# mypy # mypy
.mypy_cache/ .mypy_cache/
.dmypy.json .dmypy.json
dmypy.json dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/

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

@ -1,23 +1,34 @@
# AIO # AIO
Just like AIO-coolers, this little program aims to tackle many problems at once. 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
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. ### 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. 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

View File

@ -1 +1 @@
# Placeholder # Placeholder

View File

@ -1,6 +1,6 @@
from . import keys from . import keys
from . import reddit from . import reddit
from . import weather from . import weather
from . import reddit from . import reddit
from . import search from . import search
from . import metmuseum from . import metmuseum

View File

@ -1,39 +1,39 @@
import requests import requests
import random import random
from PIL import Image from PIL import Image
import io import io
class ArtFetch: class ArtFetch:
def __init__(self): def __init__(self):
self.base_url = "https://collectionapi.metmuseum.org/" self.base_url = "https://collectionapi.metmuseum.org/"
self.objects = self.fetch_objects() # chosen set of images to select randomly self.objects = self.fetch_objects() # chosen set of images to select randomly
def fetch_objects(self): def fetch_objects(self):
"""We restrict ourselves to a few domains.""" """We restrict ourselves to a few domains."""
# fetch all departements # fetch all departements
t = requests.get(self.base_url + "public/collection/v1/departments").json() t = requests.get(self.base_url + "public/collection/v1/departments").json()
deps = t["departments"] deps = t["departments"]
keep_id = [] keep_id = []
for d in deps: for d in deps:
name = d["displayName"] name = d["displayName"]
if name == "American Decorative Arts" or name == "Arts of Africa, Oceania, and the Americas" or name == "Asian Art" or name == "European Paintings": if name == "American Decorative Arts" or name == "Arts of Africa, Oceania, and the Americas" or name == "Asian Art" or name == "European Paintings":
keep_id.append(str(d["departmentId"])) keep_id.append(str(d["departmentId"]))
# fetch artworks listed under these departments # fetch artworks listed under these departments
data = {"departmentIds" : "|".join(keep_id)} data = {"departmentIds" : "|".join(keep_id)}
t = requests.get(self.base_url + "public/collection/v1/objects",params=data).json() t = requests.get(self.base_url + "public/collection/v1/objects",params=data).json()
# num = t["total"] # num = t["total"]
ids = t["objectIDs"] ids = t["objectIDs"]
return ids return ids
def get_random_art(self): def get_random_art(self):
"""Returns an image object of a randomly selected artwork""" """Returns an image object of a randomly selected artwork"""
# fetch the artwork's url # fetch the artwork's url
r_id = self.objects[random.randint(0,len(self.objects))] r_id = self.objects[random.randint(0,len(self.objects))]
t = requests.get(self.base_url + "public/collection/v1/objects/" + str(r_id)).json() t = requests.get(self.base_url + "public/collection/v1/objects/" + str(r_id)).json()
im_url = t["primaryImageSmall"] im_url = t["primaryImageSmall"]
# download the image # download the image
resp = requests.get(im_url) resp = requests.get(im_url)
img = Image.open(io.BytesIO(resp.content)) img = Image.open(io.BytesIO(resp.content))
return img return img

View File

@ -1,56 +1,56 @@
import praw import praw
class RedditFetch(): class RedditFetch():
def __init__(self, key): def __init__(self, key):
self.stream = praw.Reddit(client_id = key["id"], client_secret = key["secret"], user_agent=key["user_agent"]) self.stream = praw.Reddit(client_id = key["id"], client_secret = key["secret"], user_agent=key["user_agent"])
def get_top(self, subreddit, number, return_type="text"): def get_top(self, subreddit, number, return_type="text"):
if return_type == "text": if return_type == "text":
posts = [] posts = []
try: try:
for submission in self.stream.subreddit(subreddit).top(limit=number): for submission in self.stream.subreddit(subreddit).top(limit=number):
p = {} p = {}
if not submission.stickied: if not submission.stickied:
p["title"] = submission.title p["title"] = submission.title
p["content"] = submission.selftext p["content"] = submission.selftext
posts.append(p) posts.append(p)
return posts return posts
except: except:
return [] return []
else: else:
images = [] images = []
try: try:
for submission in self.stream.subreddit(subreddit).top(limit=number): for submission in self.stream.subreddit(subreddit).top(limit=number):
if not submission.stickied: if not submission.stickied:
t = {"image": submission.url, "caption": submission.title} t = {"image": submission.url, "caption": submission.title}
images.append(t) images.append(t)
return images return images
except: except:
return [] return []
def get_random_rising(self, subreddit, number, return_type="text"): def get_random_rising(self, subreddit, number, return_type="text"):
if return_type == "text": if return_type == "text":
posts = [] posts = []
try: try:
for submission in self.stream.subreddit(subreddit).random_rising(limit=number): for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
p = {} p = {}
if not submission.stickied: if not submission.stickied:
p["title"] = submission.title p["title"] = submission.title
p["content"] = submission.selftext p["content"] = submission.selftext
posts.append(p) posts.append(p)
return posts return posts
except: except:
return [] return []
else: else:
images = [] images = []
try: try:
for submission in self.stream.subreddit(subreddit).random_rising(limit=number): for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
if not submission.stickied: if not submission.stickied:
t = {"image": submission.url, "caption": submission.title} t = {"image": submission.url, "caption": submission.title}
images.append(t) images.append(t)
return images return images
except: except:
return [] return []

View File

@ -1,21 +1,21 @@
import duckduckpy import duckduckpy
class WebSearch(): class WebSearch():
def __init__(self): def __init__(self):
self.search = duckduckpy.query self.search = duckduckpy.query
def get_result(self, query): def get_result(self, query):
try: try:
res = [] res = []
response = self.search(query, container = "dict")["related_topics"] response = self.search(query, container = "dict")["related_topics"]
for r in response: for r in response:
if "text" in r: if "text" in r:
res.append({ res.append({
"text" : r["text"], "text" : r["text"],
"url": r["first_url"] "url": r["first_url"]
}) })
except: except:
res = ["Connection error"] res = ["Connection error"]
return res return res
# TODO: this api has more potential. Extract images or quick facts! # TODO: this api has more potential. Extract images or quick facts!

View File

@ -1,87 +1,87 @@
import requests import requests
import datetime import datetime
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WeatherFetch(): class WeatherFetch():
def __init__(self, key): def __init__(self, key):
self.last_fetch = datetime.datetime.fromtimestamp(0) self.last_fetch = datetime.datetime.fromtimestamp(0)
self.last_weather = "" self.last_weather = ""
self.calls = 0 self.calls = 0
self.url = "https://api.openweathermap.org/data/2.5/onecall?" self.url = "https://api.openweathermap.org/data/2.5/onecall?"
self.key = key self.key = key
def show_weather(self, location): def show_weather(self, location):
delta = datetime.datetime.now() - self.last_fetch delta = datetime.datetime.now() - self.last_fetch
if delta.total_seconds()/60 > 60 or "\n" not in self.last_weather: # 1 hour passed: if delta.total_seconds()/60 > 60 or "\n" not in self.last_weather: # 1 hour passed:
data = {"lat" : location[0], "lon" : location[1], "exclude" : "minutely,hourly", "appid" : self.key, "units" : "metric"} data = {"lat" : location[0], "lon" : location[1], "exclude" : "minutely,hourly", "appid" : self.key, "units" : "metric"}
self.calls += 1 self.calls += 1
logger.info("Just fetched weather. ({}th time)".format(self.calls)) logger.info("Just fetched weather. ({}th time)".format(self.calls))
# today = datetime.datetime.today().weekday() # today = datetime.datetime.today().weekday()
# days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] # days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
try: try:
weather = requests.get(self.url,params=data).json() weather = requests.get(self.url,params=data).json()
# categories = {"Clouds": ":cloud:", "Rain": ":cloud_with_rain:", "Thunderstorm": "thunder_cloud_rain", "Drizzle": ":droplet:", "Snow": ":cloud_snow:", "Clear": ":sun:", "Mist": "Mist", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",} # categories = {"Clouds": ":cloud:", "Rain": ":cloud_with_rain:", "Thunderstorm": "thunder_cloud_rain", "Drizzle": ":droplet:", "Snow": ":cloud_snow:", "Clear": ":sun:", "Mist": "Mist", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",}
now = weather["current"] now = weather["current"]
ret_weather = [] ret_weather = []
ret_weather.append({ ret_weather.append({
"short" : now["weather"][0]["main"], "short" : now["weather"][0]["main"],
"temps" : [int(now["temp"])] "temps" : [int(now["temp"])]
}) })
weather_days = weather["daily"] weather_days = weather["daily"]
for i, day in enumerate(weather_days): for i, day in enumerate(weather_days):
ret_weather.append({ ret_weather.append({
"short" : day["weather"][0]["main"], "short" : day["weather"][0]["main"],
"temps" : [int(day["temp"]["min"]),int(day["temp"]["max"])] "temps" : [int(day["temp"]["min"]),int(day["temp"]["max"])]
}) })
except: except:
ret_weather = [] ret_weather = []
# now = weather["current"] # now = weather["current"]
# message = "<b>Now:</b> " + categories[now["weather"][0]["main"]] + "\n" # message = "<b>Now:</b> " + categories[now["weather"][0]["main"]] + "\n"
# message += ":thermometer: " + str(int(now["temp"])) + "°\n\n" # message += ":thermometer: " + str(int(now["temp"])) + "°\n\n"
# weather_days = weather["daily"] # weather_days = weather["daily"]
# for i, day in enumerate(weather_days): # for i, day in enumerate(weather_days):
# if i == 0: # if i == 0:
# message += "<b>" + "Today" + ":</b> " + categories[day["weather"][0]["main"]] + "\n" # message += "<b>" + "Today" + ":</b> " + categories[day["weather"][0]["main"]] + "\n"
# else: # else:
# message += "<b>" + days[(today + i + 1) % 7] + ":</b> " + categories[day["weather"][0]["main"]] + "\n" # message += "<b>" + days[(today + i + 1) % 7] + ":</b> " + categories[day["weather"][0]["main"]] + "\n"
# message += ":thermometer: :fast_down_button: " + str(int(day["temp"]["min"])) + "° , :thermometer: :fast_up_button: " + str(int(day["temp"]["max"])) + "°\n\n" # message += ":thermometer: :fast_down_button: " + str(int(day["temp"]["min"])) + "° , :thermometer: :fast_up_button: " + str(int(day["temp"]["max"])) + "°\n\n"
# except: # except:
# message = "Query failed, it's my fault, I'm sorry :sad:" # message = "Query failed, it's my fault, I'm sorry :sad:"
self.last_weather = ret_weather self.last_weather = ret_weather
self.last_fetch = datetime.datetime.now() self.last_fetch = datetime.datetime.now()
else: else:
ret_weather = self.last_weather ret_weather = self.last_weather
return ret_weather return ret_weather
# def get_weather_by_city(self, city): # def get_weather_by_city(self, city):
# loc = get_coords_from_city(self, city) # loc = get_coords_from_city(self, city)
# weather = self.show_weather(loc) # weather = self.show_weather(loc)
# return weather # return weather
# def get_coords_from_city(self, city): # def get_coords_from_city(self, city):
# url = "https://devru-latitude-longitude-find-v1.p.rapidapi.com/latlon.php" # url = "https://devru-latitude-longitude-find-v1.p.rapidapi.com/latlon.php"
# data = {"location": city} # data = {"location": city}
# headers = { # headers = {
# "x-rapidapi-key" : "d4e0ab7ab3mshd5dde5a282649e0p11fd98jsnc93afd98e3aa", # "x-rapidapi-key" : "d4e0ab7ab3mshd5dde5a282649e0p11fd98jsnc93afd98e3aa",
# "x-rapidapi-host" : "devru-latitude-longitude-find-v1.p.rapidapi.com", # "x-rapidapi-host" : "devru-latitude-longitude-find-v1.p.rapidapi.com",
# } # }
# #try: # #try:
# resp = requests.request("GET", url, headers=headers, params=data) # resp = requests.request("GET", url, headers=headers, params=data)
# result = resp.text # result = resp.text
# #except: # #except:
# # result = "???" # # result = "???"
# return result # return result

View File

@ -1,2 +1,2 @@
# Placeholder # Placeholder
from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search

View File

@ -1,64 +1,64 @@
from .template import * from .template import *
FIRST = range(1) FIRST = range(1)
class Alias(BotFunc): class Alias(BotFunc):
"""create a new command for command-paths you often use""" """create a new command for command-paths you often use"""
def __init__(self, dispatcher, db): def __init__(self, dispatcher, db):
super().__init__(db) super().__init__(db)
self.dispatcher = dispatcher self.dispatcher = dispatcher
# do not interact with him yet! # do not interact with him yet!
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('alias', self.entry_point)], entry_points=[CommandHandler('alias', self.entry_point)],
states={ states={
FIRST: [ FIRST: [
CallbackQueryHandler(self.print_all, pattern="^all$"), CallbackQueryHandler(self.print_all, pattern="^all$"),
CallbackQueryHandler(self.create_alias, pattern="^new$"), CallbackQueryHandler(self.create_alias, pattern="^new$"),
CallbackQueryHandler(self.delete_alias, pattern='^delete$'), CallbackQueryHandler(self.delete_alias, pattern='^delete$'),
] ]
}, },
fallbacks=[CommandHandler('alias', self.entry_point)], fallbacks=[CommandHandler('alias', self.entry_point)],
) )
return conv_handler return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None: def entry_point(self, update: Update, context: CallbackContext) -> None:
test = self.dispatcher test = self.dispatcher
keyboard = [ keyboard = [
[InlineKeyboardButton("All aliases", callback_data="all")], [InlineKeyboardButton("All aliases", callback_data="all")],
[InlineKeyboardButton("Create new alias", callback_data="new")], [InlineKeyboardButton("Create new alias", callback_data="new")],
[InlineKeyboardButton("Delete alias", callback_data="delete")], [InlineKeyboardButton("Delete alias", callback_data="delete")],
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(receive=True, execute=False, send=True) super().log_activity(receive=True, execute=False, send=True)
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup) update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
return FIRST return FIRST
def print_all(self, update: Update, context: CallbackContext) -> None: def print_all(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
all_alias = "" all_alias = ""
for k in self.persistence["bot"]["aliases"]: for k in self.persistence["bot"]["aliases"]:
all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n" all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
query.edit_message_text(text="List of all commands:\n" + all_alias) query.edit_message_text(text="List of all commands:\n" + all_alias)
return ConversationHandler.END return ConversationHandler.END
def create_alias(self, update: Update, context: CallbackContext) -> None: def create_alias(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
all_alias = "" all_alias = ""
for k in self.persistence["bot"]["aliases"]: for k in self.persistence["bot"]["aliases"]:
all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n" all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
query.edit_message_text(text="List of all commands:\n" + all_alias) query.edit_message_text(text="List of all commands:\n" + all_alias)
return ConversationHandler.END return ConversationHandler.END
def delete_alias(self, update: Update, context: CallbackContext) -> None: def delete_alias(self, update: Update, context: CallbackContext) -> None:
return ConversationHandler.END return ConversationHandler.END

View File

@ -1,218 +1,218 @@
from .template import * from .template import *
import time import time
import numpy import numpy
from PIL import Image from PIL import Image
import io import io
CHOOSE, ADDARG = range(2) CHOOSE, ADDARG = range(2)
MESSAGE, WAKE, ALARM, IMAGE, ART = range(3,8) MESSAGE, WAKE, ALARM, IMAGE, ART = range(3,8)
class Clock(BotFunc): class Clock(BotFunc):
"""pass on commands to clock-module""" """pass on commands to clock-module"""
def __init__(self, prst, clock_module, art_api): def __init__(self, prst, clock_module, art_api):
super().__init__(prst) super().__init__(prst)
self.clock = clock_module self.clock = clock_module
self.api_art = art_api self.api_art = art_api
def create_handler(self): def create_handler(self):
handler = ConversationHandler( handler = ConversationHandler(
entry_points=[CommandHandler("clock", self.entry_point)], entry_points=[CommandHandler("clock", self.entry_point)],
states={ states={
CHOOSE : [ CHOOSE : [
CallbackQueryHandler(self.wake_light, pattern="^wake$"), CallbackQueryHandler(self.wake_light, pattern="^wake$"),
CallbackQueryHandler(self.alarm_blink, pattern="^alarm$"), CallbackQueryHandler(self.alarm_blink, pattern="^alarm$"),
CallbackQueryHandler(self.show_message, pattern="^message$"), CallbackQueryHandler(self.show_message, pattern="^message$"),
CallbackQueryHandler(self.show_image, pattern="^image$"), CallbackQueryHandler(self.show_image, pattern="^image$"),
CallbackQueryHandler(self.art_gallery, pattern="^gallery$"), CallbackQueryHandler(self.art_gallery, pattern="^gallery$"),
], ],
ADDARG : [MessageHandler(Filters.text, callback=self.get_arg1)], ADDARG : [MessageHandler(Filters.text, callback=self.get_arg1)],
MESSAGE: [MessageHandler(Filters.text, callback=self.exec_show_message)], MESSAGE: [MessageHandler(Filters.text, callback=self.exec_show_message)],
WAKE : [MessageHandler(Filters.text, callback=self.exec_wake_light)], WAKE : [MessageHandler(Filters.text, callback=self.exec_wake_light)],
ALARM : [MessageHandler(Filters.text, callback=self.exec_alarm_blink)], ALARM : [MessageHandler(Filters.text, callback=self.exec_alarm_blink)],
IMAGE : [MessageHandler(Filters.photo, callback=self.exec_show_image)], IMAGE : [MessageHandler(Filters.photo, callback=self.exec_show_image)],
ART : [MessageHandler(Filters.text, callback=self.exec_art_gallery)], ART : [MessageHandler(Filters.text, callback=self.exec_art_gallery)],
}, },
fallbacks=[CommandHandler('clock', self.entry_point)], fallbacks=[CommandHandler('clock', self.entry_point)],
) )
return handler return handler
def entry_point(self, update: Update, context: CallbackContext) -> None: def entry_point(self, update: Update, context: CallbackContext) -> None:
keyboard = [ keyboard = [
[InlineKeyboardButton("Make a wake-light", callback_data="wake")], [InlineKeyboardButton("Make a wake-light", callback_data="wake")],
[InlineKeyboardButton("Blink as alarm", callback_data="alarm")], [InlineKeyboardButton("Blink as alarm", callback_data="alarm")],
[InlineKeyboardButton("Show a message", callback_data="message")], [InlineKeyboardButton("Show a message", callback_data="message")],
[InlineKeyboardButton("Show an image", callback_data="image")], [InlineKeyboardButton("Show an image", callback_data="image")],
[InlineKeyboardButton("Art gallery!", callback_data="gallery")], [InlineKeyboardButton("Art gallery!", callback_data="gallery")],
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup) update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
return CHOOSE return CHOOSE
def wake_light(self, update: Update, context: CallbackContext) -> None: def wake_light(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
query.edit_message_text("Ok. How long should the color cycle last? (In seconds)") query.edit_message_text("Ok. How long should the color cycle last? (In seconds)")
return WAKE return WAKE
def alarm_blink(self, update: Update, context: CallbackContext) -> None: def alarm_blink(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
query.edit_message_text("Ok. How long should it blink? (In seconds)") query.edit_message_text("Ok. How long should it blink? (In seconds)")
self.next_state = {ALARM : "What frequency (Hertz)"} self.next_state = {ALARM : "What frequency (Hertz)"}
return ADDARG return ADDARG
def show_message(self, update: Update, context: CallbackContext) -> None: def show_message(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
query.edit_message_text("Ok. What message will I show?") query.edit_message_text("Ok. What message will I show?")
return MESSAGE return MESSAGE
def show_image(self, update: Update, context: CallbackContext) -> None: def show_image(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
query.edit_message_text("How long (in minutes) should the image be displayed?") query.edit_message_text("How long (in minutes) should the image be displayed?")
self.next_state = {IMAGE : "Please send me the photo to display."} self.next_state = {IMAGE : "Please send me the photo to display."}
return ADDARG return ADDARG
def art_gallery(self, update: Update, context: CallbackContext) -> None: def art_gallery(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
query.edit_message_text("Ok. How long should we display art? (in hours") query.edit_message_text("Ok. How long should we display art? (in hours")
self.next_state = {ART : "And how many artworks would you like to see during that time?"} self.next_state = {ART : "And how many artworks would you like to see during that time?"}
return ADDARG return ADDARG
def get_arg1(self, update: Update, context: CallbackContext) -> None: def get_arg1(self, update: Update, context: CallbackContext) -> None:
a = update.message.text a = update.message.text
self.additional_argument = a self.additional_argument = a
update.message.reply_text("Furthermore: "+ list(self.next_state.values())[0]) update.message.reply_text("Furthermore: "+ list(self.next_state.values())[0])
return list(self.next_state.keys())[0] return list(self.next_state.keys())[0]
###### actually running clock actions ###### actually running clock actions
def exec_wake_light(self, update: Update, context: CallbackContext) -> None: def exec_wake_light(self, update: Update, context: CallbackContext) -> None:
duration = update.message.text duration = update.message.text
def output(duration): def output(duration):
self.clock.set_brightness(value=1) self.clock.set_brightness(value=1)
start_color = numpy.array([153, 0, 51]) start_color = numpy.array([153, 0, 51])
end_color = numpy.array([255, 255, 0]) end_color = numpy.array([255, 255, 0])
col_show = numpy.zeros((*self.clock.shape, 3)) col_show = numpy.zeros((*self.clock.shape, 3))
col_show[:,:,...] = start_color col_show[:,:,...] = start_color
gradient = end_color - start_color gradient = end_color - start_color
# 20 steps should be fine => sleep_time = duration / 20 # 20 steps should be fine => sleep_time = duration / 20
for i in range(20): for i in range(20):
ct = i/20 * gradient ct = i/20 * gradient
col_show[:,:,...] = [int(x) for x in ct+start_color] col_show[:,:,...] = [int(x) for x in ct+start_color]
self.clock.IO.put(col_show) self.clock.IO.put(col_show)
time.sleep(int(duration) / 20) time.sleep(int(duration) / 20)
self.clock.run(output,(duration,)) self.clock.run(output,(duration,))
return ConversationHandler.END return ConversationHandler.END
def exec_alarm_blink(self, update: Update, context: CallbackContext) -> None: def exec_alarm_blink(self, update: Update, context: CallbackContext) -> None:
duration = self.additional_argument duration = self.additional_argument
frequency = update.message.text frequency = update.message.text
def output(duration, frequency): def output(duration, frequency):
self.clock.set_brightness(value=1) self.clock.set_brightness(value=1)
duration = int(duration) duration = int(duration)
frequency = int(frequency) frequency = int(frequency)
n = duration * frequency / 2 n = duration * frequency / 2
empty = numpy.zeros((*self.clock.shape,3)) empty = numpy.zeros((*self.clock.shape,3))
red = empty.copy() red = empty.copy()
red[...,0] = 255 red[...,0] = 255
for i in range(int(n)): for i in range(int(n)):
self.clock.IO.put(red) self.clock.IO.put(red)
time.sleep(1/frequency) time.sleep(1/frequency)
self.clock.IO.put(empty) self.clock.IO.put(empty)
time.sleep(1/frequency) time.sleep(1/frequency)
if not(duration == 0 or frequency == 0): if not(duration == 0 or frequency == 0):
update.message.reply_text("Now blinking") update.message.reply_text("Now blinking")
self.clock.run(output,(duration, frequency)) self.clock.run(output,(duration, frequency))
return ConversationHandler.END return ConversationHandler.END
def exec_show_image(self, update: Update, context: CallbackContext) -> None: def exec_show_image(self, update: Update, context: CallbackContext) -> None:
duration = self.additional_argument duration = self.additional_argument
i = update.message.photo i = update.message.photo
img = update.message.photo[0] img = update.message.photo[0]
bot = img.bot bot = img.bot
id = img.file_id id = img.file_id
file = bot.getFile(id).download_as_bytearray() file = bot.getFile(id).download_as_bytearray()
width = self.clock.shape[1] width = self.clock.shape[1]
height = self.clock.shape[0] height = self.clock.shape[0]
img = Image.open(io.BytesIO(file)) img = Image.open(io.BytesIO(file))
im_height = img.height im_height = img.height
im_width = img.width im_width = img.width
scalex = im_width // width scalex = im_width // width
scaley = im_height // height scaley = im_height // height
scale = min(scalex, scaley) scale = min(scalex, scaley)
t = img.resize((width, height),box=(0,0,width*scale,height*scale)) t = img.resize((width, height),box=(0,0,width*scale,height*scale))
a = numpy.asarray(t) a = numpy.asarray(t)
def output(image, duration): def output(image, duration):
self.clock.IO.put(image) self.clock.IO.put(image)
time.sleep(int(duration) * 60) time.sleep(int(duration) * 60)
self.clock.run(output,(a, duration)) self.clock.run(output,(a, duration))
return ConversationHandler.END return ConversationHandler.END
def exec_show_message(self, update: Update, context: CallbackContext) -> None: def exec_show_message(self, update: Update, context: CallbackContext) -> None:
message_str = update.message.text message_str = update.message.text
update.message.reply_text("Now showing: " + message_str) update.message.reply_text("Now showing: " + message_str)
self.clock.run(self.clock.text_scroll,(message_str,)) self.clock.run(self.clock.text_scroll,(message_str,))
return ConversationHandler.END return ConversationHandler.END
def exec_art_gallery(self, update: Update, context: CallbackContext) -> None: def exec_art_gallery(self, update: Update, context: CallbackContext) -> None:
duration = float(self.additional_argument) duration = float(self.additional_argument)
number = int(update.message.text) number = int(update.message.text)
def output(number, duration): def output(number, duration):
for i in range(number): for i in range(number):
img = self.api_art.get_random_art() # returns an PIL.Image object img = self.api_art.get_random_art() # returns an PIL.Image object
im_height = img.height im_height = img.height
im_width = img.width im_width = img.width
width = self.clock.shape[1] width = self.clock.shape[1]
height = self.clock.shape[0] height = self.clock.shape[0]
scalex = im_width // width scalex = im_width // width
scaley = im_height // height scaley = im_height // height
scale = min(scalex, scaley) scale = min(scalex, scaley)
t = img.resize((width, height),box=(0,0,width*scale,height*scale)) t = img.resize((width, height),box=(0,0,width*scale,height*scale))
a = numpy.asarray(t) a = numpy.asarray(t)
self.clock.IO.put(a) self.clock.IO.put(a)
time.sleep(duration*3600 / number) time.sleep(duration*3600 / number)
update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.") update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.")
self.clock.run(output,(number, duration)) self.clock.run(output,(number, duration))
return ConversationHandler.END return ConversationHandler.END
# TODO FIx this to work with the new backend # TODO FIx this to work with the new backend

View File

@ -1,128 +1,128 @@
from .template import * from .template import *
FIRST, EXECUTE = range(2) FIRST, EXECUTE = range(2)
class Help(BotFunc): class Help(BotFunc):
"""Shows the functions and their usage""" """Shows the functions and their usage"""
def __init__(self, db): def __init__(self, db):
super().__init__(db) super().__init__(db)
self.available_commands = {} self.available_commands = {}
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('help', self.entry_point)], entry_points=[CommandHandler('help', self.entry_point)],
states={ states={
FIRST: [ FIRST: [
CallbackQueryHandler(self.print_all, pattern="^all$"), CallbackQueryHandler(self.print_all, pattern="^all$"),
CallbackQueryHandler(self.choose_specific, pattern="^specific$"), CallbackQueryHandler(self.choose_specific, pattern="^specific$"),
CallbackQueryHandler(self.print_one, pattern='func-'), CallbackQueryHandler(self.print_one, pattern='func-'),
], ],
EXECUTE :[CallbackQueryHandler(self.execute_now)], EXECUTE :[CallbackQueryHandler(self.execute_now)],
# ConversationHandler.TIMEOUT : [ # ConversationHandler.TIMEOUT : [
# CallbackQueryHandler(self.timeout) # CallbackQueryHandler(self.timeout)
# ] # ]
}, },
fallbacks=[CommandHandler('help', self.entry_point)], fallbacks=[CommandHandler('help', self.entry_point)],
conversation_timeout=15, conversation_timeout=15,
) )
return conv_handler return conv_handler
def add_commands(self, commands): def add_commands(self, commands):
# commands is a dict {"name": class} # commands is a dict {"name": class}
for k in commands: for k in commands:
if k != "plaintext": if k != "plaintext":
self.available_commands[k] = commands[k].__doc__ self.available_commands[k] = commands[k].__doc__
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)
keyboard = [ keyboard = [
[ [
InlineKeyboardButton("All commands", callback_data="all"), InlineKeyboardButton("All commands", callback_data="all"),
InlineKeyboardButton("Just one", callback_data="specific"), InlineKeyboardButton("Just one", callback_data="specific"),
] ]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
if update.message: if update.message:
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup) update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
else: else:
update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup) update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup)
return FIRST return FIRST
def print_all(self, update: Update, context: CallbackContext) -> None: def print_all(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
all_cmd = "" all_cmd = ""
for h in self.available_commands: for h in self.available_commands:
all_cmd += "{} - `{}`\n".format(h, self.available_commands[h]) all_cmd += "{} - `{}`\n".format(h, self.available_commands[h])
query.edit_message_text(text="List of all commands:\n" + all_cmd, parse_mode = ParseMode.MARKDOWN) query.edit_message_text(text="List of all commands:\n" + all_cmd, parse_mode = ParseMode.MARKDOWN)
return ConversationHandler.END return ConversationHandler.END
def choose_specific(self, update: Update, context: CallbackContext) -> None: def choose_specific(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
keyboard = [[InlineKeyboardButton(k, callback_data="func-" + k)] for k in self.available_commands] keyboard = [[InlineKeyboardButton(k, callback_data="func-" + k)] for k in self.available_commands]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text( query.edit_message_text(
text="What command should be printed?", reply_markup=reply_markup text="What command should be printed?", reply_markup=reply_markup
) )
return FIRST return FIRST
def print_one(self, update: Update, context: CallbackContext) -> None: def print_one(self, update: Update, context: CallbackContext) -> None:
"""Show new choice of buttons""" """Show new choice of buttons"""
query = update.callback_query query = update.callback_query
name = query.data.replace("func-", "") name = query.data.replace("func-", "")
query.answer() query.answer()
message = name + ": `" + self.available_commands[name] + "`" message = name + ": `" + self.available_commands[name] + "`"
keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]] keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text( query.edit_message_text(
text= message, text= message,
reply_markup = reply_markup, reply_markup = reply_markup,
parse_mode = ParseMode.MARKDOWN_V2 parse_mode = ParseMode.MARKDOWN_V2
) )
return EXECUTE return EXECUTE
def execute_now(self, update: Update, context: CallbackContext) -> None: def execute_now(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
name = query.data name = query.data
query.answer() query.answer()
funcs = context.dispatcher.handlers[0] funcs = context.dispatcher.handlers[0]
for func in funcs: for func in funcs:
if name == func.entry_points[0].command[0]: if name == func.entry_points[0].command[0]:
break break
callback = func.entry_points[0].handle_update callback = func.entry_points[0].handle_update
callback(update, context.dispatcher, check_result=True, context=context) callback(update, context.dispatcher, check_result=True, context=context)
return ConversationHandler.END return ConversationHandler.END
def timeout(self, update: Update, context: CallbackContext) -> None: def timeout(self, update: Update, context: CallbackContext) -> None:
"""For dying conversation. Currently unused.""" """For dying conversation. Currently unused."""
query = update.callback_query query = update.callback_query
name = query.data.replace("func-", "") name = query.data.replace("func-", "")
query.answer() query.answer()
message = name + ": `" + self.available_commands[name] + "`" message = name + ": `" + self.available_commands[name] + "`"
query.edit_message_text( query.edit_message_text(
text= "Timed out...", text= "Timed out...",
parse_mode = ParseMode.MARKDOWN_V2 parse_mode = ParseMode.MARKDOWN_V2
) )
return ConversationHandler.END return ConversationHandler.END

View File

@ -1,200 +1,198 @@
from .template import * from .template import *
import datetime NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
import requests
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5) class Lists(BotFunc):
"""Create and edit lists"""
class Lists(BotFunc): def __init__(self, db):
"""Create and edit lists""" super().__init__(db)
self.current_name = ""
def __init__(self, db):
super().__init__(db)
self.current_name = "" def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('list', self.entry_point)],
def create_handler(self): states={
conv_handler = ConversationHandler( NAME: [
entry_points=[CommandHandler('list', self.entry_point)], CallbackQueryHandler(self.choose_list, pattern="^list-"),
states={ CallbackQueryHandler(self.new_list, pattern="^new$"),
NAME: [ ],
CallbackQueryHandler(self.choose_list, pattern="^list-"), NEW : [MessageHandler(Filters.text, callback=self.new_listname)],
CallbackQueryHandler(self.new_list, pattern="^new$"), ACTION: [
], CallbackQueryHandler(self.list_add, pattern="^add$"),
NEW : [MessageHandler(Filters.text, callback=self.new_listname)], CallbackQueryHandler(self.list_remove, pattern="^remove$"),
ACTION: [ CallbackQueryHandler(self.list_clear, pattern="^clear$"),
CallbackQueryHandler(self.list_add, pattern="^add$"), CallbackQueryHandler(self.list_delete, pattern="^delete$"),
CallbackQueryHandler(self.list_remove, pattern="^remove$"), CallbackQueryHandler(self.list_print, pattern="^print$"),
CallbackQueryHandler(self.list_clear, pattern="^clear$"), CallbackQueryHandler(self.list_menu, pattern="^overview$"),
CallbackQueryHandler(self.list_delete, pattern="^delete$"), ],
CallbackQueryHandler(self.list_print, pattern="^print$"), ITEMADD : [MessageHandler(Filters.text, callback=self.list_add_item)],
CallbackQueryHandler(self.list_menu, pattern="^overview$"), ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)]
], },
ITEMADD : [MessageHandler(Filters.text, callback=self.list_add_item)], fallbacks=[CommandHandler('list', self.entry_point)],
ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)] )
}, return conv_handler
fallbacks=[CommandHandler('list', self.entry_point)],
)
return conv_handler def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
# TODO Change DB
def entry_point(self, update: Update, context: CallbackContext) -> None: lists = self.db.lists.select()
super().entry_point(update, context) sl = [l.name for l in lists]
lists = self.db.lists.select() keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]
sl = [l.name for l in lists]
keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]] reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=False, send=True)
reply_markup = InlineKeyboardMarkup(keyboard) update.message.reply_text(text="Here are the existing lists. You can also create a new one:", reply_markup=reply_markup)
super().log_activity(read=True, execute=False, send=True) return NAME
update.message.reply_text(text="Here are the existing lists. You can also create a new one:", reply_markup=reply_markup)
return NAME
def choose_list(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def choose_list(self, update: Update, context: CallbackContext) -> None: data = query.data
query = update.callback_query name = data.replace("list-","")
data = query.data query.answer()
name = data.replace("list-","") self.current_name = name
query.answer()
self.current_name = name keyboard = [
[InlineKeyboardButton("Add item", callback_data="add")],
keyboard = [ [InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Add item", callback_data="add")], [InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Remove item", callback_data="remove")], [InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Clear list", callback_data="clear")], [InlineKeyboardButton("Delete list", callback_data="delete")],
[InlineKeyboardButton("Print list", callback_data="print")], ]
[InlineKeyboardButton("Delete list", callback_data="delete")], reply_markup = InlineKeyboardMarkup(keyboard)
]
reply_markup = InlineKeyboardMarkup(keyboard) query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
def list_menu(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_menu(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query
query.answer() keyboard = [
[InlineKeyboardButton("Add item", callback_data="add")],
keyboard = [ [InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Add item", callback_data="add")], [InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Remove item", callback_data="remove")], [InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Clear list", callback_data="clear")], [InlineKeyboardButton("Delete list", callback_data="delete")],
[InlineKeyboardButton("Print list", callback_data="print")], ]
[InlineKeyboardButton("Delete list", callback_data="delete")], reply_markup = InlineKeyboardMarkup(keyboard)
]
reply_markup = InlineKeyboardMarkup(keyboard) query.edit_message_text("Very well. For " + self.current_name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
query.edit_message_text("Very well. For " + self.current_name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
def new_list(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def new_list(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query query.edit_message_text("What's the name of the new list?")
query.answer() return NEW
query.edit_message_text("What's the name of the new list?")
return NEW
def new_listname(self, update: Update, context: CallbackContext) -> None:
name = update.message.text
def new_listname(self, update: Update, context: CallbackContext) -> None: try:
name = update.message.text data = self.db.lists(name=name, content="")
try: data.save()
data = self.db.lists(name=name, content="") keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
data.save() reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]] self.current_name = name
reply_markup = InlineKeyboardMarkup(keyboard) update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
self.current_name = name return ACTION
update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup) except Exception as e:
return ACTION update.message.reply_text("Oh no! Encountered exception: {}".format(e))
except Exception as e: return ConversationHandler.END
update.message.reply_text("Oh no! Encountered exception: {}".format(e))
return ConversationHandler.END
def list_add(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_add(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query query.edit_message_text("What would you like to add?")
query.answer() return ITEMADD
query.edit_message_text("What would you like to add?")
return ITEMADD
def list_remove(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_remove(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query it = self.db.lists.get(self.db.lists.name == self.current_name)
query.answer() sl = it.content.split("<-->")
it = self.db.lists.get(self.db.lists.name == self.current_name)
sl = it.content.split("<-->") keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(sl)]
reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(sl)]
reply_markup = InlineKeyboardMarkup(keyboard) query.edit_message_text("Which item would you like to remove?", reply_markup = reply_markup)
return ITEMREMOVE
query.edit_message_text("Which item would you like to remove?", reply_markup = reply_markup)
return ITEMREMOVE
def list_clear(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_clear(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query self.db.lists.update(content="").where(self.db.lists.name == self.current_name).execute()
query.answer() keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
self.db.lists.update(content="").where(self.db.lists.name == self.current_name).execute() reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] query.edit_message_text("List " + self.current_name + " cleared", reply_markup=reply_markup)
reply_markup = InlineKeyboardMarkup(keyboard) return ACTION
query.edit_message_text("List " + self.current_name + " cleared", reply_markup=reply_markup)
return ACTION
def list_delete(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_delete(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query self.db.lists.delete().where(self.db.lists.name == self.current_name).execute()
query.answer() query.edit_message_text("List " + self.current_name + " deleted")
self.db.lists.delete().where(self.db.lists.name == self.current_name).execute() return ConversationHandler.END
query.edit_message_text("List " + self.current_name + " deleted")
return ConversationHandler.END
def list_print(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_print(self, update: Update, context: CallbackContext) -> None: query.answer()
query = update.callback_query it = self.db.lists.get(self.db.lists.name == self.current_name)
query.answer() if it:
it = self.db.lists.get(self.db.lists.name == self.current_name) content = it.content.split("<-->")
if it: content = "\n".join(content)
content = it.content.split("<-->") else:
content = "\n".join(content) content = "List empty"
else:
content = "List empty" keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] query.edit_message_text("Content of " + self.current_name + ":\n" + content, reply_markup=reply_markup)
reply_markup = InlineKeyboardMarkup(keyboard) return ACTION
query.edit_message_text("Content of " + self.current_name + ":\n" + content, reply_markup=reply_markup)
return ACTION
def list_add_item(self, update: Update, context: CallbackContext) -> None:
item = update.message.text
def list_add_item(self, update: Update, context: CallbackContext) -> None: it = self.db.lists.get(self.db.lists.name == self.current_name)
item = update.message.text if it:
it = self.db.lists.get(self.db.lists.name == self.current_name) sl = it.content
if it: else:
sl = it.content sl = ""
else: sl += item + "<-->"
sl = "" self.db.lists.update(content=sl).where(self.db.lists.name == self.current_name).execute()
sl += item + "<-->"
self.db.lists.update(content=sl).where(self.db.lists.name == self.current_name).execute() keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] update.message.reply_text("Added " + item, reply_markup=reply_markup)
reply_markup = InlineKeyboardMarkup(keyboard) return ACTION
update.message.reply_text("Added " + item, reply_markup=reply_markup)
return ACTION
def list_remove_index(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
def list_remove_index(self, update: Update, context: CallbackContext) -> None: ind = int(query.data)
query = update.callback_query query.answer()
ind = int(query.data)
query.answer() it = self.db.lists.get(self.db.lists.name == self.current_name)
old = it.content.split("<-->")
it = self.db.lists.get(self.db.lists.name == self.current_name) # todo make better
old = it.content.split("<-->")
# todo make better name = old.pop(ind)
new = "<-->".join(old)
name = old.pop(ind) self.db.lists.update(content=new).where(self.db.lists.name == self.current_name).execute()
new = "<-->".join(old)
self.db.lists.update(content=new).where(self.db.lists.name == self.current_name).execute() keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) query.edit_message_text("Removed " + name, reply_markup=reply_markup)
return ACTION
query.edit_message_text("Removed " + name, reply_markup=reply_markup)
return ACTION

View File

@ -1,19 +1,19 @@
from .template import * from .template import *
class Plain(BotFunc): class Plain(BotFunc):
"""Not a command: just keeps logs and usage_data""" """Not a command: just keeps logs and usage_data"""
def __init__(self, db): def __init__(self, db):
super().__init__(db) super().__init__(db)
def create_handler(self): def create_handler(self):
h = MessageHandler(Filters.text, callback=self.add_to_log) h = MessageHandler(Filters.text, callback=self.add_to_log)
return h return h
def add_to_log(self, update: Update, context: CallbackContext) -> None: def add_to_log(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context) super().entry_point(update, context)
super().log_activity( super().log_activity(
read = True, read = True,
send = False, send = False,
execute = False execute = False
) )

View File

@ -1,179 +1,179 @@
from re import U from re import U
from .template import * from .template import *
CHOOSE_NUM = 1 CHOOSE_NUM = 1
class Joke(BotFunc): class Joke(BotFunc):
"""Tells a joke from reddit.""" """Tells a joke from reddit."""
def __init__(self, api, db): def __init__(self, api, db):
super().__init__(db) super().__init__(db)
self.available_commands = {} self.available_commands = {}
self.api = api self.api = api
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('joke', self.entry_point)], entry_points=[CommandHandler('joke', self.entry_point)],
states={ states={
CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),], CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),],
}, },
fallbacks=[CommandHandler('joke', self.entry_point)], fallbacks=[CommandHandler('joke', self.entry_point)],
# conversation_timeout=5, # conversation_timeout=5,
) )
return conv_handler return conv_handler
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)
keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]] keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
update.message.reply_text("How many jokes?", reply_markup=reply_markup) update.message.reply_text("How many jokes?", reply_markup=reply_markup)
return CHOOSE_NUM return CHOOSE_NUM
def get_jokes(self, update: Update, context: CallbackContext) -> None: def get_jokes(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
number = int(query.data) number = int(query.data)
query.answer() query.answer()
jokes = self.api.get_random_rising("jokes", number, "text") jokes = self.api.get_random_rising("jokes", number, "text")
# formating # formating
message = "" message = ""
for j in jokes: for j in jokes:
message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n" message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n"
if message == "": if message == "":
message += "Could not fetch jokes." message += "Could not fetch jokes."
query.edit_message_text(text = message, parse_mode = ParseMode.HTML) query.edit_message_text(text = message, parse_mode = ParseMode.HTML)
return ConversationHandler.END return ConversationHandler.END
CHOOSE_TOPIC = 0 CHOOSE_TOPIC = 0
class Meme(BotFunc): class Meme(BotFunc):
"""Gets the latest memes from reddit""" """Gets the latest memes from reddit"""
def __init__(self, api, db): def __init__(self, api, db):
super().__init__(db) super().__init__(db)
self.available_commands = {} self.available_commands = {}
self.api = api self.api = api
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('meme', self.entry_point)], entry_points=[CommandHandler('meme', self.entry_point)],
states={ states={
CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)], CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)], CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)],
}, },
fallbacks=[CommandHandler('meme', self.entry_point)], fallbacks=[CommandHandler('meme', self.entry_point)],
) )
return conv_handler return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None: def entry_point(self, update: Update, context: CallbackContext) -> None:
keyboard = [ keyboard = [
[InlineKeyboardButton("General", callback_data="memes"),], [InlineKeyboardButton("General", callback_data="memes"),],
[InlineKeyboardButton("Dank memes", callback_data="dankmemes"),], [InlineKeyboardButton("Dank memes", callback_data="dankmemes"),],
[InlineKeyboardButton("Maths", callback_data="mathmemes"),], [InlineKeyboardButton("Maths", callback_data="mathmemes"),],
[InlineKeyboardButton("Physics", callback_data="physicsmemes"),], [InlineKeyboardButton("Physics", callback_data="physicsmemes"),],
[InlineKeyboardButton("Biology", callback_data="biologymemes"),], [InlineKeyboardButton("Biology", callback_data="biologymemes"),],
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
update.message.reply_text("What kind of memes?", reply_markup=reply_markup) update.message.reply_text("What kind of memes?", reply_markup=reply_markup)
return CHOOSE_TOPIC return CHOOSE_TOPIC
def choose_topic(self, update: Update, context: CallbackContext) -> None: def choose_topic(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
d = query.data d = query.data
query.answer() query.answer()
keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]] keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("How many memes?", reply_markup=reply_markup) query.edit_message_text("How many memes?", reply_markup=reply_markup)
return CHOOSE_NUM return CHOOSE_NUM
def get_memes(self, update: Update, context: CallbackContext) -> None: def get_memes(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
data = query.data.split("-") data = query.data.split("-")
query.answer() query.answer()
memes = self.api.get_random_rising(data[0], int(data[1]), "photo") memes = self.api.get_random_rising(data[0], int(data[1]), "photo")
if len(memes) != 0: if len(memes) != 0:
for m in memes: for m in memes:
super().log_activity(read=False, execute=False, send=True) # we just sent an additional message super().log_activity(read=False, execute=False, send=True) # we just sent an additional message
update.effective_chat.send_photo(photo = m["image"],caption = m["caption"]) update.effective_chat.send_photo(photo = m["image"],caption = m["caption"])
else: else:
update.effective_chat.send_message("Sorry, the meme won't yeet.") update.effective_chat.send_message("Sorry, the meme won't yeet.")
return ConversationHandler.END return ConversationHandler.END
# class News(BotFunc): # class News(BotFunc):
# """Gets the latest news from reddit""" # """Gets the latest news from reddit"""
# def __init__(self, api, prst): # def __init__(self, api, prst):
# super().__init__(prst) # super().__init__(prst)
# self.available_commands = {} # self.available_commands = {}
# self.api = api # self.api = api
# def create_handler(self): # def create_handler(self):
# conv_handler = ConversationHandler( # conv_handler = ConversationHandler(
# entry_points=[CommandHandler('news', self.entry_point)], # entry_points=[CommandHandler('news', self.entry_point)],
# states={ # states={
# CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)], # CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
# CHOOSE_NUM :[CallbackQueryHandler(self.get_news)], # CHOOSE_NUM :[CallbackQueryHandler(self.get_news)],
# }, # },
# fallbacks=[CommandHandler('news', self.entry_point)], # fallbacks=[CommandHandler('news', self.entry_point)],
# ) # )
# return conv_handler # return conv_handler
# def entry_point(self, update: Update, context: CallbackContext) -> None: # def entry_point(self, update: Update, context: CallbackContext) -> None:
# super().entry_point() # super().entry_point()
# keyboard = [ # keyboard = [
# [InlineKeyboardButton("World", callback_data="worldnews"),], # [InlineKeyboardButton("World", callback_data="worldnews"),],
# [InlineKeyboardButton("Germany", callback_data="germannews"),], # [InlineKeyboardButton("Germany", callback_data="germannews"),],
# [InlineKeyboardButton("France", callback_data="francenews"),], # [InlineKeyboardButton("France", callback_data="francenews"),],
# [InlineKeyboardButton("Europe", callback_data="eunews"),], # [InlineKeyboardButton("Europe", callback_data="eunews"),],
# [InlineKeyboardButton("USA", callback_data="usanews"),], # [InlineKeyboardButton("USA", callback_data="usanews"),],
# ] # ]
# reply_markup = InlineKeyboardMarkup(keyboard) # reply_markup = InlineKeyboardMarkup(keyboard)
# update.message.reply_text("What kind of news?", reply_markup=reply_markup) # update.message.reply_text("What kind of news?", reply_markup=reply_markup)
# return CHOOSE_TOPIC # return CHOOSE_TOPIC
# def choose_topic(self, update: Update, context: CallbackContext) -> None: # def choose_topic(self, update: Update, context: CallbackContext) -> None:
# super().entry_point() # super().entry_point()
# query = update.callback_query # query = update.callback_query
# d = query.data # d = query.data
# query.answer() # query.answer()
# keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]] # keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
# reply_markup = InlineKeyboardMarkup(keyboard) # reply_markup = InlineKeyboardMarkup(keyboard)
# query.edit_message_text("How many entries?", reply_markup=reply_markup) # query.edit_message_text("How many entries?", reply_markup=reply_markup)
# return CHOOSE_NUM # return CHOOSE_NUM
# def get_news(self, update: Update, context: CallbackContext) -> None: # def get_news(self, update: Update, context: CallbackContext) -> None:
# query = update.callback_query # query = update.callback_query
# data = query.data.split("-") # data = query.data.split("-")
# query.answer() # query.answer()
# #try: # #try:
# news = self.api.get_top(data[0], data[1], "text") # news = self.api.get_top(data[0], data[1], "text")
# # formating # # formating
# message = "" # message = ""
# for j in news: # for j in news:
# message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n" # message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n"
# if message == "": # if message == "":
# message += "Could not fetch news." # message += "Could not fetch news."
# query.edit_message_text(news, paresemode=ParseMode.HTML) # query.edit_message_text(news, paresemode=ParseMode.HTML)
# return ConversationHandler.END # return ConversationHandler.END

View File

@ -1,61 +1,61 @@
from .template import * from .template import *
SEARCH, MORE = range(2) SEARCH, MORE = range(2)
class Search(BotFunc): class Search(BotFunc):
"""Browse the web for a topic.""" """Browse the web for a topic."""
def __init__(self, api, db): def __init__(self, api, db):
super().__init__(db) super().__init__(db)
self.available_commands = {} self.available_commands = {}
self.api = api self.api = api
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('search', self.entry_point)], entry_points=[CommandHandler('search', self.entry_point)],
states={ states={
SEARCH: [MessageHandler(Filters.text, self.get_results),], SEARCH: [MessageHandler(Filters.text, self.get_results),],
MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),], MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),],
}, },
fallbacks=[CommandHandler('search', self.entry_point)], fallbacks=[CommandHandler('search', self.entry_point)],
conversation_timeout=20, conversation_timeout=20,
) )
return conv_handler return conv_handler
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)
update.message.reply_text("What are we searching?") update.message.reply_text("What are we searching?")
return SEARCH return SEARCH
def get_results(self, update: Update, context: CallbackContext) -> None: def get_results(self, update: Update, context: CallbackContext) -> None:
search = update.message.text search = update.message.text
results = self.api.get_result(search) results = self.api.get_result(search)
keyboard = [[InlineKeyboardButton("More!", callback_data="more")]] keyboard = [[InlineKeyboardButton("More!", callback_data="more")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
# formating # formating
self.results = results self.results = results
if results: if results:
first = results[0] first = results[0]
message = first["text"] + "\n(" + first["url"] + ")\n\n" message = first["text"] + "\n(" + first["url"] + ")\n\n"
else: else:
message = "No results for search query." message = "No results for search query."
update.message.reply_text(text = message, reply_markup=reply_markup) update.message.reply_text(text = message, reply_markup=reply_markup)
super().log_activity(read = True, execute = True, send = True) super().log_activity(read = True, execute = True, send = True)
return MORE return MORE
def show_more(self, update: Update, context: CallbackContext) -> None: def show_more(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
query.answer() query.answer()
message = "" message = ""
for r in self.results: for r in self.results:
message += r["text"] + "\n(" + r["url"] + ")\n\n" message += r["text"] + "\n(" + r["url"] + ")\n\n"
query.edit_message_text(message) query.edit_message_text(message)
super().log_activity(read = False, execute = False, send = True) super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END return ConversationHandler.END

View File

@ -1,105 +1,106 @@
from .template import * from .template import *
import datetime import datetime
import requests import requests
import socket import socket
import numpy as np import numpy as np
import os import os
import json import json
FIRST = 1 FIRST = 1
class Status(BotFunc): class Status(BotFunc):
"""Shows a short status of the program.""" """Shows a short status of the program."""
def __init__(self, name, version, db): def __init__(self, name, version, db):
super().__init__(db) super().__init__(db)
self.start_time = datetime.datetime.now() self.start_time = datetime.datetime.now()
self.name = name self.name = name
self.version = version self.version = version
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('status', self.entry_point)], entry_points=[CommandHandler('status', self.entry_point)],
states={ states={
FIRST: [ FIRST: [
CallbackQueryHandler(self.send_log, pattern="^full$"), CallbackQueryHandler(self.send_log, pattern="^full$"),
] ]
}, },
fallbacks=[CommandHandler('status', self.entry_point)], fallbacks=[CommandHandler('status', self.entry_point)],
) )
return conv_handler return conv_handler
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)
keyboard = [ keyboard = [
[ [
InlineKeyboardButton("And the log?", callback_data="full"), InlineKeyboardButton("And the log?", callback_data="full"),
] ]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
delta = str(datetime.datetime.now() - self.start_time) delta = str(datetime.datetime.now() - self.start_time)
message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n" message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
try: try:
ip = requests.get('https://api.ipify.org').text ip = requests.get('https://api.ipify.org').text
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect(('8.8.8.8', 80)) s.connect(('8.8.8.8', 80))
(addr, port) = s.getsockname() (addr, port) = s.getsockname()
local_ips = addr local_ips = addr
except: except:
ip = "not fetchable" ip = "not fetchable"
local_ips = "not fetchable" local_ips = "not fetchable"
message += "Status: Running 🟢\n" message += "Status: Running 🟢\n"
message += "Uptime: `" + delta[:delta.rfind(".")] + "`\n" message += "Uptime: `" + delta[:delta.rfind(".")] + "`\n"
# message += "Reboots: `" + str(self.persistence["global"]["reboots"]) + "`\n" # message += "Reboots: `" + str(self.persistence["global"]["reboots"]) + "`\n"
message += "IP (public): `" + ip + "`\n" message += "IP (public): `" + ip + "`\n"
message += "IP (private): `" + str(local_ips) + "`\n" message += "IP (private): `" + str(local_ips) + "`\n"
u = str(self.get_ngrok_url()) u = str(self.get_ngrok_url())
message += "URL: [" + u + "](" + u + ")\n" message += "URL: [" + u + "](" + u + ")\n"
tot_r = self.db.chats.select().where(self.db.chats.read == True).count() # TODO new DB
message += "Total messages read: `{}`\n".format(tot_r) tot_r = self.db.chats.select().where(self.db.chats.read == True).count()
message += "Total messages read: `{}`\n".format(tot_r)
tot_s = self.db.chats.select().where(self.db.chats.send == True).count()
message += "Total messages sent: `{}`\n".format(tot_s) tot_s = self.db.chats.select().where(self.db.chats.send == True).count()
message += "Total messages sent: `{}`\n".format(tot_s)
tot_e = self.db.chats.select().where(self.db.chats.execute == True).count()
message += "Total commands executed: `{}`\n".format(tot_e) tot_e = self.db.chats.select().where(self.db.chats.execute == True).count()
message += "Total commands executed: `{}`\n".format(tot_e)
if update.message:
update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) if update.message:
else: update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) else:
update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
super().log_activity(read = True, execute = True, send = True)
return FIRST super().log_activity(read = True, execute = True, send = True)
return FIRST
def send_log(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query def send_log(self, update: Update, context: CallbackContext) -> None:
wanted = query.data.replace("status-","") query = update.callback_query
query.answer() wanted = query.data.replace("status-","")
with open("persistence/server.log") as l: query.answer()
query.message.reply_document(l) with open("persistence/server.log") as l:
query.message.reply_document(l)
super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END
def get_ngrok_url(self):
try: def get_ngrok_url(self):
url = "http://localhost:4040/api/tunnels/" try:
res = requests.get(url) url = "http://localhost:4040/api/tunnels/"
res_unicode = res.content.decode("utf-8") res = requests.get(url)
res_json = json.loads(res_unicode) res_unicode = res.content.decode("utf-8")
for i in res_json["tunnels"]: res_json = json.loads(res_unicode)
if i['name'] == 'command_line': for i in res_json["tunnels"]:
return i['public_url'] if i['name'] == 'command_line':
break return i['public_url']
except: break
except:
return "Not available" return "Not available"

View File

@ -1,44 +1,43 @@
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 ( Updater,
Updater, CommandHandler,
CommandHandler, CallbackQueryHandler,
CallbackQueryHandler, ConversationHandler,
ConversationHandler, CallbackContext,
CallbackContext, )
)
import datetime
import datetime
class BotFunc():
class BotFunc(): """Base class for a specific bot-functionality"""
"""Base class for a specific bot-functionality""" def __init__(self, db):
def __init__(self, db): self.logger = logging.getLogger(__name__)
self.logger = logging.getLogger(__name__) 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: self.logger.info("Chat said: {}".format(update.message.text))
self.logger.info("Chat said: {}".format(update.message.text)) else:
else: self.logger.info("Chat said: {}".format(update.message))
self.logger.info("Chat said: {}".format(update.message))

View File

@ -1,117 +1,117 @@
from .template import * from .template import *
import datetime import datetime
FIRST = 1 FIRST = 1
class Weather(BotFunc): class Weather(BotFunc):
"""Shows a weatherforecast for a given location""" """Shows a weatherforecast for a given location"""
def __init__(self, api, db): def __init__(self, api, db):
"""initialize api and persistence""" """initialize api and persistence"""
super().__init__(db) super().__init__(db)
self.api = api self.api = api
self.city = "" self.city = ""
def create_handler(self): def create_handler(self):
"""returns the handlers with button-logic""" """returns the handlers with button-logic"""
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('weather', self.entry_point)], entry_points=[CommandHandler('weather', self.entry_point)],
states={ states={
FIRST: [ FIRST: [
CallbackQueryHandler(self.choose_city, pattern="^city-"), CallbackQueryHandler(self.choose_city, pattern="^city-"),
CallbackQueryHandler(self.choose_time, pattern="^time-"), CallbackQueryHandler(self.choose_time, pattern="^time-"),
] ]
}, },
fallbacks=[CommandHandler('weather', self.entry_point)], fallbacks=[CommandHandler('weather', self.entry_point)],
) )
return conv_handler return conv_handler
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)
"""Reacts the call of the command. Prints the first buttons""" """Reacts the call of the command. Prints the first buttons"""
keyboard = [ keyboard = [
[ [
InlineKeyboardButton("Zürich", callback_data="city-zurich"), InlineKeyboardButton("Zürich", callback_data="city-zurich"),
InlineKeyboardButton("Freiburg", callback_data="city-freiburg"), InlineKeyboardButton("Freiburg", callback_data="city-freiburg"),
InlineKeyboardButton("Mulhouse", callback_data="city-mulhouse"), InlineKeyboardButton("Mulhouse", callback_data="city-mulhouse"),
] ]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
if update.message: if update.message:
update.message.reply_text("Which city?", reply_markup=reply_markup) update.message.reply_text("Which city?", reply_markup=reply_markup)
else: else:
update.callback_query.edit_message_text("Which city", reply_markup=reply_markup) update.callback_query.edit_message_text("Which city", reply_markup=reply_markup)
return FIRST return FIRST
def choose_city(self, update: Update, context: CallbackContext) -> None: def choose_city(self, update: Update, context: CallbackContext) -> None:
"""Prompt same text & keyboard as `start` does but not as new message""" """Prompt same text & keyboard as `start` does but not as new message"""
# Get CallbackQuery from Update # Get CallbackQuery from Update
query = update.callback_query query = update.callback_query
data = query.data data = query.data
self.city = data.replace("city-","") self.city = data.replace("city-","")
query.answer() query.answer()
keyboard = [ keyboard = [
[ [
InlineKeyboardButton("Now", callback_data="time-now"), InlineKeyboardButton("Now", callback_data="time-now"),
InlineKeyboardButton("Tomorrow", callback_data="time-tomorrow"), InlineKeyboardButton("Tomorrow", callback_data="time-tomorrow"),
InlineKeyboardButton("7 days", callback_data="time-7"), InlineKeyboardButton("7 days", callback_data="time-7"),
] ]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text( query.edit_message_text(
text = "Which time?", reply_markup=reply_markup text = "Which time?", reply_markup=reply_markup
) )
return FIRST return FIRST
def choose_time(self, update: Update, context: CallbackContext) -> None: def choose_time(self, update: Update, context: CallbackContext) -> None:
"""Show new choice of buttons""" """Show new choice of buttons"""
query = update.callback_query query = update.callback_query
query.answer() query.answer()
forecast_time = query.data.replace("time-","") forecast_time = query.data.replace("time-","")
weather = self.get_weather(self.city, forecast_time) weather = self.get_weather(self.city, forecast_time)
query.edit_message_text( query.edit_message_text(
text = "Weather: \n\n" + weather, text = "Weather: \n\n" + weather,
parse_mode = ParseMode.HTML parse_mode = ParseMode.HTML
) )
super().log_activity(read = True, execute = True, send = True) super().log_activity(read = True, execute = True, send = True)
return ConversationHandler.END return ConversationHandler.END
def get_weather(self, city, forecast_time) -> None: def get_weather(self, city, forecast_time) -> None:
"""get the weather that matches the given params""" """get the weather that matches the given params"""
locations = {"freiburg": [47.9990, 7.8421], "zurich": [47.3769, 8.5417], "mulhouse": [47.7508, 7.3359]} locations = {"freiburg": [47.9990, 7.8421], "zurich": [47.3769, 8.5417], "mulhouse": [47.7508, 7.3359]}
city = locations[city] city = locations[city]
categories = {"Clouds": "", "Rain": "🌧", "Thunderstorm": "🌩", "Drizzle": ":droplet:", "Snow": "", "Clear": "", "Mist": "🌫", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",} categories = {"Clouds": "", "Rain": "🌧", "Thunderstorm": "🌩", "Drizzle": ":droplet:", "Snow": "", "Clear": "", "Mist": "🌫", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",}
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
today = datetime.datetime.today().weekday() today = datetime.datetime.today().weekday()
weather = self.api.show_weather(city) weather = self.api.show_weather(city)
message = "" message = ""
if forecast_time == "now" or forecast_time == "7": if forecast_time == "now" or forecast_time == "7":
now = weather.pop(0) now = weather.pop(0)
message += "<b>Now:</b> " + categories[now["short"]] + "\n" message += "<b>Now:</b> " + categories[now["short"]] + "\n"
message += "🌡" + str(now["temps"][0]) + "°\n\n" message += "🌡" + str(now["temps"][0]) + "°\n\n"
tod = weather.pop(0) tod = weather.pop(0)
message += "<b>" + "Today" + ":</b> " + categories[tod["short"]] + "\n" message += "<b>" + "Today" + ":</b> " + categories[tod["short"]] + "\n"
message += "🌡 ❄ " + str(tod["temps"][0]) + "° , 🌡 🔥 " + str(tod["temps"][1]) + "°\n\n" message += "🌡 ❄ " + str(tod["temps"][0]) + "° , 🌡 🔥 " + str(tod["temps"][1]) + "°\n\n"
if forecast_time == "tomorrow" or forecast_time == "7": if forecast_time == "tomorrow" or forecast_time == "7":
if forecast_time == "tomorrow": # previous statement was not executed: tomorrow is at weather[2] if forecast_time == "tomorrow": # previous statement was not executed: tomorrow is at weather[2]
tom = weather.pop(2) tom = weather.pop(2)
else: else:
tom = weather.pop(0) tom = weather.pop(0)
message += "<b>" + "Tomorrow" + ":</b> " + categories[tom["short"]] + "\n" message += "<b>" + "Tomorrow" + ":</b> " + categories[tom["short"]] + "\n"
message += "🌡 ❄ " + str(tom["temps"][0]) + "° , 🌡 🔥 " + str(tom["temps"][1]) + "°\n\n" message += "🌡 ❄ " + str(tom["temps"][0]) + "° , 🌡 🔥 " + str(tom["temps"][1]) + "°\n\n"
if forecast_time == "7": if forecast_time == "7":
for i, day in enumerate(weather): for i, day in enumerate(weather):
message += "<b>" + days[(today + i + 2) % 7] + ":</b> " + categories[day["short"]] + "\n" message += "<b>" + days[(today + i + 2) % 7] + ":</b> " + categories[day["short"]] + "\n"
message += "🌡 ❄ " + str(day["temps"][0]) + "° , 🌡 🔥 " + str(day["temps"][1]) + "°\n\n" message += "🌡 ❄ " + str(day["temps"][0]) + "° , 🌡 🔥 " + str(day["temps"][1]) + "°\n\n"
return message return message

View File

@ -1,87 +1,87 @@
from .template import * from .template import *
import datetime import datetime
import requests import requests
START, DEST = range(2) START, DEST = range(2)
class Zvv(BotFunc): class Zvv(BotFunc):
"""Connects to the swiss travel-api to get public transport routes""" """Connects to the swiss travel-api to get public transport routes"""
def __init__(self, db): def __init__(self, db):
super().__init__(db) super().__init__(db)
self.start = "" self.start = ""
self.dest = "" self.dest = ""
pass pass
def create_handler(self): def create_handler(self):
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler('zvv', self.entry_point)], entry_points=[CommandHandler('zvv', self.entry_point)],
states={ states={
START: [MessageHandler(Filters.text, callback=self.get_start)], START: [MessageHandler(Filters.text, callback=self.get_start)],
DEST: [MessageHandler(Filters.text, callback=self.get_dest)] DEST: [MessageHandler(Filters.text, callback=self.get_dest)]
}, },
fallbacks=[CommandHandler('zvv', self.entry_point)], fallbacks=[CommandHandler('zvv', self.entry_point)],
) )
return conv_handler return conv_handler
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)
update.message.reply_text("What is the start point?") update.message.reply_text("What is the start point?")
return START return START
def get_start(self, update: Update, context: CallbackContext) -> None: def get_start(self, update: Update, context: CallbackContext) -> None:
loc = update.message.text loc = update.message.text
self.start = loc self.start = loc
update.message.reply_text("Ok. Going from " + loc + ", what is the destination?") update.message.reply_text("Ok. Going from " + loc + ", what is the destination?")
return DEST return DEST
def get_dest(self, update: Update, context: CallbackContext) -> None: def get_dest(self, update: Update, context: CallbackContext) -> None:
loc = update.message.text loc = update.message.text
self.dest = loc self.dest = loc
route = self.get_result() route = self.get_result()
update.message.reply_text("Here are the routes I've got:\n" + route) update.message.reply_text("Here are the routes I've got:\n" + route)
super().log_activity(read=True, execute=True, send=True) super().log_activity(read=True, execute=True, send=True)
return ConversationHandler.END return ConversationHandler.END
def get_result(self): def get_result(self):
url = "http://transport.opendata.ch/v1/connections" url = "http://transport.opendata.ch/v1/connections"
start = self.start start = self.start
dest = self.dest dest = self.dest
data = {"from" : start, "to" : dest, "limit" : 2} data = {"from" : start, "to" : dest, "limit" : 2}
try: try:
routes = requests.get(url, params=data).json() routes = requests.get(url, params=data).json()
result = routes["connections"] result = routes["connections"]
text = result[0]["from"]["station"]["name"] + "" + result[0]["to"]["station"]["name"] + "\n\n" text = result[0]["from"]["station"]["name"] + "" + result[0]["to"]["station"]["name"] + "\n\n"
for con in result: for con in result:
text += "Start: " + datetime.datetime.fromtimestamp(int(con["from"]["departureTimestamp"])).strftime("%d/%m - %H:%M") + "\n" text += "Start: " + datetime.datetime.fromtimestamp(int(con["from"]["departureTimestamp"])).strftime("%d/%m - %H:%M") + "\n"
text += "🏁 " + datetime.datetime.fromtimestamp(int(con["to"]["arrivalTimestamp"])).strftime("%d/%m - %H:%M") + "\n" text += "🏁 " + datetime.datetime.fromtimestamp(int(con["to"]["arrivalTimestamp"])).strftime("%d/%m - %H:%M") + "\n"
text += "" + con["duration"] + "\n" text += "" + con["duration"] + "\n"
text += "🗺️ Route:\n" text += "🗺️ Route:\n"
for step in con["sections"]: for step in con["sections"]:
if step["journey"] != None: if step["journey"] != None:
text += step["journey"]["passList"][0]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][0]["departureTimestamp"])).strftime("%H:%M") + ")\n" text += step["journey"]["passList"][0]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][0]["departureTimestamp"])).strftime("%H:%M") + ")\n"
text += "➡️ Linie " + self.number_to_emoji(step["journey"]["number"]) + "\n" text += "➡️ Linie " + self.number_to_emoji(step["journey"]["number"]) + "\n"
text += step["journey"]["passList"][-1]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][-1]["arrivalTimestamp"])).strftime("%H:%M") +")\n" text += step["journey"]["passList"][-1]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][-1]["arrivalTimestamp"])).strftime("%H:%M") +")\n"
else: else:
text += "Walk." text += "Walk."
text += "\n" text += "\n"
return text return text
except: except:
return "Invalid api call." return "Invalid api call."
def number_to_emoji(self, number): def number_to_emoji(self, number):
out = "" out = ""
numbers = ["0","1","2","3","4","5","6","7","8","9"] numbers = ["0","1","2","3","4","5","6","7","8","9"]
for i in str(number): for i in str(number):
out += numbers[int(i)] out += numbers[int(i)]
return str(out) return str(out)

View File

@ -1,68 +1,68 @@
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
from . import api, commands from . import api, commands
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ChatBot(): class ChatBot():
"""better framwork - unites all functions""" """better framwork - unites all functions"""
def __init__(self, name, version): def __init__(self, name, version):
"""Inits the Bot with a few conf. vars """Inits the Bot with a few conf. vars
Args: -> name:str - Name of the bot Args: -> name:str - Name of the bot
-> version:str - Version number -> version:str - Version number
-> hw_commands - dict with commands executable by the clock module -> hw_commands - dict with commands executable by the clock module
-> prst:dict - persistence (overloaded dict that writes to json file) -> prst:dict - persistence (overloaded dict that writes to json file)
-> logger - logging object to unify log messages -> logger - logging object to unify log messages
""" """
# added by the launcher, we have self.modules (dict) and persistence and db # added by the launcher, we have self.modules (dict) and persistence and db
self.name = name self.name = name
self.version = version self.version = version
# Import submodules # Import submodules
self.api_weather = api.weather.WeatherFetch(api.keys.weather_api) self.api_weather = api.weather.WeatherFetch(api.keys.weather_api)
self.api_reddit = api.reddit.RedditFetch(api.keys.reddit_api) self.api_reddit = api.reddit.RedditFetch(api.keys.reddit_api)
self.api_search = api.search.WebSearch() self.api_search = api.search.WebSearch()
self.api_art = api.metmuseum.ArtFetch() self.api_art = api.metmuseum.ArtFetch()
# and so on # and so on
self.telegram = Updater(api.keys.telegram_api, use_context=True) self.telegram = Updater(api.keys.telegram_api, use_context=True)
self.dispatcher = self.telegram.dispatcher self.dispatcher = self.telegram.dispatcher
self.commands = commands self.commands = commands
def add_commands(self): def add_commands(self):
# Mark modules as available # Mark modules as available
db = self.db db = self.db
self.help_module = self.commands.help.Help(db) self.help_module = self.commands.help.Help(db)
self.sub_modules = { self.sub_modules = {
"weather": self.commands.weather.Weather(self.api_weather, db), "weather": self.commands.weather.Weather(self.api_weather, db),
"help" : self.help_module, "help" : self.help_module,
"status" : self.commands.status.Status(self.name, self.version, db), "status" : self.commands.status.Status(self.name, self.version, db),
"zvv" : self.commands.zvv.Zvv(db), "zvv" : self.commands.zvv.Zvv(db),
"list" : self.commands.lists.Lists(db), "list" : self.commands.lists.Lists(db),
# "alias" : commands.alias.Alias(self.dispatcher, db), # "alias" : commands.alias.Alias(self.dispatcher, db),
"joke" : self.commands.reddit.Joke(self.api_reddit, db), "joke" : self.commands.reddit.Joke(self.api_reddit, db),
"meme" : self.commands.reddit.Meme(self.api_reddit, db), "meme" : self.commands.reddit.Meme(self.api_reddit, db),
# "news" : self.commands.reddit.News(self.api_reddit, db), # "news" : self.commands.reddit.News(self.api_reddit, db),
"search" : self.commands.search.Search(self.api_search, db), "search" : self.commands.search.Search(self.api_search, db),
# ... # ...
"plaintext" : self.commands.plaintext.Plain(db) # for handling non-command messages that should simply contribute to statistics "plaintext" : self.commands.plaintext.Plain(db) # for handling non-command messages that should simply contribute to statistics
} }
# must be a class that has a method create_handler # must be a class that has a method create_handler
for k in self.sub_modules: for k in self.sub_modules:
self.dispatcher.add_handler(self.sub_modules[k].create_handler()) self.dispatcher.add_handler(self.sub_modules[k].create_handler())
self.help_module.add_commands(self.sub_modules) self.help_module.add_commands(self.sub_modules)
def start(self): def start(self):
self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)} self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)}
self.add_commands() self.add_commands()
self.telegram.start_polling( self.telegram.start_polling(
poll_interval=0.2, poll_interval=0.2,
) )
# self.telegram.idle() # self.telegram.idle()

View File

@ -1 +1 @@
# Placeholder # Placeholder

View File

@ -1,71 +1,71 @@
import requests import requests
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FetchUpdates: class FetchUpdates:
"""Fetches updates from the main server and relays them to the clock""" """Fetches updates from the main server and relays them to the clock"""
def __init__(self, server_ip, port): def __init__(self, server_ip, port):
"""Both methods return a list as python-object. This should be then converted to a numpy array.""" """Both methods return a list as python-object. This should be then converted to a numpy array."""
# self.server_ip = server_ip # self.server_ip = server_ip
self.base_url = server_ip + ":" + port self.base_url = server_ip + ":" + port
# self.modules gets added through the caller # self.modules gets added through the caller
self.update_calls = 0 self.update_calls = 0
self.last_fetch = {} self.last_fetch = {}
def start(self): def start(self):
# dummy for errorless launching # dummy for errorless launching
pass pass
def get_updates(self): def get_updates(self):
update_url = "http://" + self.base_url + "/getupdates" update_url = "http://" + self.base_url + "/getupdates"
result = self.call_api(update_url) result = self.call_api(update_url)
return result return result
def get_last(self): def get_last(self):
update_url = "http://" + self.base_url + "/getlast" update_url = "http://" + self.base_url + "/getlast"
result = self.call_api(update_url) result = self.call_api(update_url)
return result return result
def fetch_data(self): def fetch_data(self):
try: try:
if self.update_calls == 0: if self.update_calls == 0:
fetch = self.get_last() fetch = self.get_last()
else: else:
fetch = self.get_updates() fetch = self.get_updates()
if not fetch["is_new"]: if not fetch["is_new"]:
fetch = self.last_fetch fetch = self.last_fetch
else: else:
self.last_fetch = fetch self.last_fetch = fetch
data = fetch["data"] data = fetch["data"]
has_queue = fetch["has_queue"] has_queue = fetch["has_queue"]
except: except:
data = {} data = {}
has_queue = False has_queue = False
self.update_calls += 1 self.update_calls += 1
return has_queue, data return has_queue, data
def call_api(self, url): def call_api(self, url):
ret = {} ret = {}
try: try:
result = requests.get(url) result = requests.get(url)
result = result.json() result = result.json()
if result.pop("status") == "ok": if result.pop("status") == "ok":
ret = result ret = result
except: except:
logger.error("Bad api call for method {}.".format(url[url.rfind("/"):])) logger.error("Bad api call for method {}.".format(url[url.rfind("/"):]))
return ret return ret

View File

@ -1,86 +1,86 @@
import flask import flask
from flask import request, jsonify from flask import request, jsonify
import numpy as np import numpy as np
from threading import Thread from threading import Thread
import logging import logging
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR) log.setLevel(logging.ERROR)
# hide the info-messages of each GET-request # hide the info-messages of each GET-request
class BroadcastUpdates: class BroadcastUpdates:
"""Broadcasts (out) updates for the hw-handler to be fetched periodically""" """Broadcasts (out) updates for the hw-handler to be fetched periodically"""
def __init__(self, port): def __init__(self, port):
"""""" """"""
self.last_show = "" self.last_show = ""
self.queue = [] #[{"matrices" : [np.full((16,16,3), 10).tolist(), np.full((16,16,3), 100).tolist(), np.full((16,16,3), 200).tolist()]} for _ in range(4)] self.queue = [] #[{"matrices" : [np.full((16,16,3), 10).tolist(), np.full((16,16,3), 100).tolist(), np.full((16,16,3), 200).tolist()]} for _ in range(4)]
self.port = port self.port = port
def start(self): def start(self):
t = Thread(target=self.run) t = Thread(target=self.run)
t.start() t.start()
def run(self): def run(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
@app.route('/getupdates', methods=['GET']) @app.route('/getupdates', methods=['GET'])
def get_updates(): def get_updates():
return self.get_updates() return self.get_updates()
@app.route('/getlast', methods=['GET']) @app.route('/getlast', methods=['GET'])
def get_last(): def get_last():
return self.get_last() return self.get_last()
app.run('0.0.0.0', port=self.port) app.run('0.0.0.0', port=self.port)
def get_updates(self): def get_updates(self):
try: try:
data = self.queue.pop(0) data = self.queue.pop(0)
self.last_show = data self.last_show = data
is_new = True is_new = True
has_queue = len(self.queue) > 0 has_queue = len(self.queue) > 0
except: except:
data = "" data = ""
is_new = False is_new = False
has_queue = False has_queue = False
return self.generate_response( return self.generate_response(
is_new = is_new, is_new = is_new,
data = data, data = data,
has_queue = has_queue has_queue = has_queue
) )
def get_last(self): def get_last(self):
try: try:
try: try:
data = self.queue[-1] data = self.queue[-1]
self.queue = [] self.queue = []
except: # list empty except: # list empty
data = self.last_show, data = self.last_show,
except: except:
data = "" data = ""
return self.generate_response( return self.generate_response(
data = data, data = data,
has_queue = False, has_queue = False,
) )
def generate_response(self, **kwargs): def generate_response(self, **kwargs):
ret = { ret = {
"status" : "ok", "status" : "ok",
**kwargs **kwargs
} }
return jsonify(ret) return jsonify(ret)

View File

@ -1,43 +1,43 @@
# functionality # functionality
from clock import c_in, c_out from clock import c_in, c_out
from broadcast import b_in from broadcast import b_in
import launcher import launcher
import logging import logging
import os import os
if os.name == "nt": if os.name == "nt":
# development # development
logging.basicConfig( logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
) )
else: else:
logging.basicConfig( logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO, level=logging.INFO,
filename='persistence/client.log', filename='persistence/client.log',
) )
class ReceiverLauncher(launcher.Launcher): class ReceiverLauncher(launcher.Launcher):
"""Launcher for all server-side modules. The hard-computations""" """Launcher for all server-side modules. The hard-computations"""
def __init__(self): def __init__(self):
self.clock_sensor_module = c_in.SensorReadout() self.clock_sensor_module = c_in.SensorReadout()
# active: periodically takes readouts # active: periodically takes readouts
self.clock_hardware_module = c_out.ClockFace() self.clock_hardware_module = c_out.ClockFace()
# active: periodically calls fetcher # active: periodically calls fetcher
self.receive_module = b_in.FetchUpdates(server_ip="localhost", port="1111") self.receive_module = b_in.FetchUpdates(server_ip="localhost", port="1111")
# passive: fetches data on demand # passive: fetches data on demand
super().__init__( super().__init__(
sensors = self.clock_sensor_module, sensors = self.clock_sensor_module,
clock = self.clock_hardware_module, clock = self.clock_hardware_module,
receive = self.receive_module receive = self.receive_module
) )
ReceiverLauncher() ReceiverLauncher()

View File

@ -1 +1 @@
# Placeholder # Placeholder

View File

@ -1,73 +1,73 @@
import numpy as np import numpy as np
import datetime import datetime
from . import helpers from . import helpers
class ClockBackend: class ClockBackend:
"""Heavy lifting of matrix operations""" """Heavy lifting of matrix operations"""
def __init__(self): def __init__(self):
self.MOP = helpers.computations.MatrixOperations() self.MOP = helpers.computations.MatrixOperations()
self.weather = {"weather":"", "high":"", "low":""} self.weather = {"weather":"", "high":"", "low":""}
self.weather_raw = {} self.weather_raw = {}
self.weather_face_swap = False self.weather_face_swap = False
def start(self): def start(self):
self.out = self.modules["broadcast"] self.out = self.modules["broadcast"]
helpers.timer.RepeatedTimer(15, self.clock_loop) helpers.timer.RepeatedTimer(15, self.clock_loop)
def clock_loop(self): def clock_loop(self):
t = int(datetime.datetime.now().strftime("%H%M")) t = int(datetime.datetime.now().strftime("%H%M"))
if t % 5 == 0: if t % 5 == 0:
# switch secondary face every 5 minutes # switch secondary face every 5 minutes
weather = self.modules["bot"].api_weather.show_weather([47.3769, 8.5417]) # zürich weather = self.modules["bot"].api_weather.show_weather([47.3769, 8.5417]) # zürich
if weather != self.weather_raw and len(weather) != 0: if weather != self.weather_raw and len(weather) != 0:
td = weather[1] td = weather[1]
low = td["temps"][0] low = td["temps"][0]
high = td["temps"][1] high = td["temps"][1]
self.weather["weather"] = td["short"] self.weather["weather"] = td["short"]
self.weather["high"] = high self.weather["high"] = high
self.weather["low"] = low self.weather["low"] = low
elif len(weather) == 0: elif len(weather) == 0:
self.weather["weather"] = "error" self.weather["weather"] = "error"
self.weather["high"] = "error" self.weather["high"] = "error"
self.weather["low"] = "error" self.weather["low"] = "error"
self.weather_face_swap = not self.weather_face_swap self.weather_face_swap = not self.weather_face_swap
self.send_face() self.send_face()
def send_face(self): def send_face(self):
"""Set the clock face (time + weather) by getting updated info - gets called every minute""" """Set the clock face (time + weather) by getting updated info - gets called every minute"""
matrices = self.MOP.clock_face(self.weather) matrices = self.MOP.clock_face(self.weather)
if self.weather_face_swap: if self.weather_face_swap:
matrices = [matrices[0], matrices[2], matrices[1]] matrices = [matrices[0], matrices[2], matrices[1]]
matrices = [m.tolist() for m in matrices] matrices = [m.tolist() for m in matrices]
self.out.queue.append({"matrices" : matrices}) self.out.queue.append({"matrices" : matrices})
# def text_scroll(self, text, color=[[200,200,200]]): # def text_scroll(self, text, color=[[200,200,200]]):
# pixels = self.MOP.text_converter(text, 12, color) # pixels = self.MOP.text_converter(text, 12, color)
# sleep_time = 1 / self.tspeed # sleep_time = 1 / self.tspeed
# width = self.shape[1] # width = self.shape[1]
# frames = pixels.shape[1] - width # frames = pixels.shape[1] - width
# if frames <= 0: # if frames <= 0:
# frames = 1 # frames = 1
# for i in range(frames): # for i in range(frames):
# visible = pixels[:,i:width+i] # visible = pixels[:,i:width+i]
# self.IO.put(visible*self.brightness) # self.IO.put(visible*self.brightness)
# time.sleep(sleep_time) # time.sleep(sleep_time)
# time.sleep(10 * sleep_time) # time.sleep(10 * sleep_time)

View File

@ -1,56 +1,56 @@
import datetime import datetime
import time import time
from threading import Thread, Timer from threading import Thread, Timer
from . import hardware, helpers from . import hardware, helpers
class SensorReadout: class SensorReadout:
"""Overview class for (actual and potential) sensor sources""" """Overview class for (actual and potential) sensor sources"""
def __init__(self): def __init__(self):
"""""" """"""
self.sensor_modules = { # we already call them, they are objects and not classes anymore self.sensor_modules = { # we already call them, they are objects and not classes anymore
"temperature" : hardware.sensors.TemperatureModule(), "temperature" : hardware.sensors.TemperatureModule(),
"humidity" : hardware.sensors.HumidityModule(), "humidity" : hardware.sensors.HumidityModule(),
"luminosity" : hardware.sensors.BrightnessModule(), "luminosity" : hardware.sensors.BrightnessModule(),
# more to come? # more to come?
} }
def start(self): def start(self):
helpers.timer.RepeatedTimer(300, self.spread_measure) helpers.timer.RepeatedTimer(300, self.spread_measure)
def spread_measure(self): def spread_measure(self):
measurements = dict((el,[]) for el in self.sensor_modules.keys()) measurements = dict((el,[]) for el in self.sensor_modules.keys())
# create an empty dict with a list for each readout-type # create an empty dict with a list for each readout-type
for _ in range(5): # number of measures to average out for _ in range(5): # number of measures to average out
for name in self.sensor_modules.keys(): for name in self.sensor_modules.keys():
measure = self.sensor_modules[name].readout() measure = self.sensor_modules[name].readout()
measurements[name].append(measure) measurements[name].append(measure)
time.sleep(3) time.sleep(3)
results = {} results = {}
for e in measurements.keys(): for e in measurements.keys():
lst = measurements[e] lst = measurements[e]
results[e] = int(sum(lst) / len(lst)) results[e] = int(sum(lst) / len(lst))
self.save_results(**results) self.save_results(**results)
# def save_results(self, results): # def save_results(self, results):
# current_minute = int(datetime.datetime.now().timestamp() // 60) # current_minute = int(datetime.datetime.now().timestamp() // 60)
# self.persistence["clock"]["sensors"]["time"] += [current_minute] # self.persistence["clock"]["sensors"]["time"] += [current_minute]
# for name in results.keys(): # for name in results.keys():
# keep_value = sum(results[name]) / len(results[name]) # keep_value = sum(results[name]) / len(results[name])
# self.persistence["clock"]["sensors"][name] += [keep_value] # self.persistence["clock"]["sensors"][name] += [keep_value]
def save_results(self, **results): def save_results(self, **results):
data = self.db.sensors( data = self.db.sensors(
time=datetime.datetime.now(), time=datetime.datetime.now(),
**results, **results,
) )
data.save() data.save()

View File

@ -1,65 +1,65 @@
import datetime import datetime
import time import time
from threading import Thread from threading import Thread
import numpy as np import numpy as np
from . import hardware, helpers from . import hardware, helpers
class ClockFace: class ClockFace:
"""Actual functions one might need for a clock""" """Actual functions one might need for a clock"""
def __init__(self): def __init__(self):
"""""" """"""
# added by the launcher, we have self.modules (dict) # added by the launcher, we have self.modules (dict)
self.IO = hardware.led.get_handler() self.IO = hardware.led.get_handler()
self.shape = self.IO.shape # (16,32) for now self.shape = self.IO.shape # (16,32) for now
# TODO handle differently! # TODO handle differently!
self.MOP = helpers.computations.MatrixOperations() self.MOP = helpers.computations.MatrixOperations()
self.kill_output = False self.kill_output = False
def start(self): def start(self):
Thread(target = self.clock_loop).start() Thread(target = self.clock_loop).start()
def clock_loop(self): def clock_loop(self):
while True: # TODO: allow this to be exited gracefully while True: # TODO: allow this to be exited gracefully
t_start = datetime.datetime.now() t_start = datetime.datetime.now()
self.set_brightness() self.set_brightness()
has_queue, data = self.modules["receive"].fetch_data() has_queue, data = self.modules["receive"].fetch_data()
tnext = 1 if has_queue else 30 tnext = 1 if has_queue else 30
if data == {}: if data == {}:
matrices = self.MOP.get_fallback() matrices = self.MOP.get_fallback()
matrices[0][0,0] = [255, 0, 0] # red dot on the top left matrices[0][0,0] = [255, 0, 0] # red dot on the top left
else: else:
matrices = [np.asarray(d).astype(int) for d in data["matrices"]] matrices = [np.asarray(d).astype(int) for d in data["matrices"]]
matrices[0][0,0] = [0, 255, 0] # green dot on the top left matrices[0][0,0] = [0, 255, 0] # green dot on the top left
if not self.kill_output: if not self.kill_output:
self.IO.put(matrices) self.IO.put(matrices)
else: else:
z = np.zeros((16,16,3)) z = np.zeros((16,16,3))
self.IO.put([z,z,z]) self.IO.put([z,z,z])
t_end = datetime.datetime.now() t_end = datetime.datetime.now()
delta_planned = datetime.timedelta(seconds = tnext) delta_planned = datetime.timedelta(seconds = tnext)
delta = delta_planned - (t_end - t_start) delta = delta_planned - (t_end - t_start)
time.sleep(max(delta.total_seconds(), 0)) time.sleep(max(delta.total_seconds(), 0))
def set_brightness(self): def set_brightness(self):
"""Kill the brightness at night""" """Kill the brightness at night"""
is_WE = datetime.datetime.now().weekday() > 4 is_WE = datetime.datetime.now().weekday() > 4
now = int(datetime.datetime.now().strftime("%H%M")) now = int(datetime.datetime.now().strftime("%H%M"))
self.kill_output = (now < 1000 or now > 2200) if is_WE else (now < 830 or now > 2130) self.kill_output = (now < 1000 or now > 2200) if is_WE else (now < 830 or now > 2130)

View File

@ -1,2 +1,2 @@
# Placeholder # Placeholder
from . import led, sensors from . import led, sensors

View File

@ -1,17 +1,17 @@
from . import unicorn as led from . import unicorn as led
# or neopixel soon: # or neopixel soon:
# from . import neopixel as led # from . import neopixel as led
def get_handler(): def get_handler():
OUT = led.ClockOut() OUT = led.ClockOut()
shape = OUT.shape shape = OUT.shape
if led.SETUP_FAIL: if led.SETUP_FAIL:
# we use the sim # we use the sim
del OUT del OUT
from . import sim from . import sim
OUT = sim.ClockOut(shape) OUT = sim.ClockOut(shape)
return OUT return OUT

View File

@ -1,51 +1,51 @@
import time import time
import numpy as np import numpy as np
import colorsys import colorsys
import random import random
try: try:
import rpi_ws281x as ws import rpi_ws281x as ws
except ImportError: except ImportError:
from unittest.mock import Mock from unittest.mock import Mock
ws = Mock() ws = Mock()
SETUP_FAIL = True SETUP_FAIL = True
class ClockOut: class ClockOut:
def __init__(self): def __init__(self):
self.shape = (45, 20) # H x W self.shape = (45, 20) # H x W
num = self.shape[0] * self.shape[1] num = self.shape[0] * self.shape[1]
pin = 18 pin = 18
freq = 800000 # maybe higher freq = 800000 # maybe higher
dma = 5 dma = 5
invert = False invert = False
brightness = 100 brightness = 100
channel = 0 channel = 0
led_type = None # ?? led_type = None # ??
self.strip = ws.PixelStrip(num, pin, freq, dma, invert, brightness, channel, led_type) self.strip = ws.PixelStrip(num, pin, freq, dma, invert, brightness, channel, led_type)
self.strip.begin() self.strip.begin()
def put(self, matrix): def put(self, matrix):
self.render(matrix) self.render(matrix)
def render(self, matrix): def render(self, matrix):
p = 0 p = 0
for i in range(matrix.shape[0]): for i in range(matrix.shape[0]):
for j in range(matrix.shape[1]): for j in range(matrix.shape[1]):
col = int(ws.Color(*matrix[i,j])) col = int(ws.Color(*matrix[i,j]))
self.strip.setPixelColor(p, col) self.strip.setPixelColor(p, col)
p += 1 p += 1
self.strip.show() self.strip.show()
# test = ClockOut() # test = ClockOut()
# z = np.zeros((30,30, 3), dtype=int) # z = np.zeros((30,30, 3), dtype=int)
# for i in range(30): # for i in range(30):
# for j in range(30): # for j in range(30):
# z[i, j, ...] = [random.randint(0,255), random.randint(0,255), random.randint(0,255)] # z[i, j, ...] = [random.randint(0,255), random.randint(0,255), random.randint(0,255)]
# test.put(z) # test.put(z)
# #time.sleep(0.1) # #time.sleep(0.1)

View File

@ -1,87 +1,87 @@
import time import time
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TempSim: class TempSim:
"""Simulates a temperature for running on windows""" """Simulates a temperature for running on windows"""
temperature = 23 # return a celsius value temperature = 23 # return a celsius value
humidity = 30 humidity = 30
class LightSim: class LightSim:
def input(self, *args): def input(self, *args):
return 1 return 1
class SensorModule: class SensorModule:
def __init__(self): def __init__(self):
logger.info("Using module " + self.__class__.__name__) logger.info("Using module " + self.__class__.__name__)
## Real sensors! ## Real sensors!
try: try:
import board import board
import adafruit_dht import adafruit_dht
dht11 = adafruit_dht.DHT11(board.D18) dht11 = adafruit_dht.DHT11(board.D18)
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.IN) GPIO.setup(4, GPIO.IN)
except ImportError: except ImportError:
logger.warn("Simulating sensor modules") logger.warn("Simulating sensor modules")
dht11 = TempSim() dht11 = TempSim()
GPIO = LightSim() GPIO = LightSim()
class TemperatureModule(SensorModule): class TemperatureModule(SensorModule):
"""Takes readouts from the DHT 11 """Takes readouts from the DHT 11
Returns: temperature""" Returns: temperature"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.device = dht11 self.device = dht11
def readout(self): def readout(self):
try: try:
temp = self.device.temperature temp = self.device.temperature
except: except:
time.sleep(1) time.sleep(1)
try: try:
temp = self.device.temperature temp = self.device.temperature
except: except:
temp = -1 temp = -1
return temp return temp
class HumidityModule(SensorModule): class HumidityModule(SensorModule):
"""Takes readouts from the DHT 11 """Takes readouts from the DHT 11
Returns: humidity""" Returns: humidity"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.device = dht11 self.device = dht11
def readout(self): def readout(self):
try: try:
hum = self.device.humidity hum = self.device.humidity
except: except:
time.sleep(1) time.sleep(1)
try: try:
hum = self.device.humidity hum = self.device.humidity
except: except:
hum = -1 hum = -1
return hum return hum
class BrightnessModule(SensorModule): class BrightnessModule(SensorModule):
"""Returns one for HIGH and zero for LOW""" """Returns one for HIGH and zero for LOW"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def readout(self): def readout(self):
# The sensor is reversed: 0 when bright and 1 if dark # The sensor is reversed: 0 when bright and 1 if dark
light = GPIO.input(4) light = GPIO.input(4)
if light == 0: if light == 0:
return 1 return 1
else: else:
return 0 return 0

View File

@ -1,55 +1,55 @@
import sys import sys
import colorsys import colorsys
import pygame.gfxdraw import pygame.gfxdraw
import time import time
import pygame import pygame
import numpy as np import numpy as np
class ClockOut: class ClockOut:
"""Creates a drawable window in case the real hardware is not accessible. For development""" """Creates a drawable window in case the real hardware is not accessible. For development"""
def __init__(self, shape): def __init__(self, shape):
self.pixel_size = 20 self.pixel_size = 20
self.shape = shape self.shape = shape
self.pixels = np.zeros((*shape,3), dtype=int) self.pixels = np.zeros((*shape,3), dtype=int)
self.WIDTH = shape[1] self.WIDTH = shape[1]
self.HEIGHT = shape[0] self.HEIGHT = shape[0]
self.window_width = self.WIDTH * self.pixel_size self.window_width = self.WIDTH * self.pixel_size
self.window_height = self.HEIGHT * self.pixel_size self.window_height = self.HEIGHT * self.pixel_size
pygame.init() pygame.init()
pygame.display.set_caption("Unicorn HAT simulator") pygame.display.set_caption("Unicorn HAT simulator")
self.screen = pygame.display.set_mode([self.window_width, self.window_height]) self.screen = pygame.display.set_mode([self.window_width, self.window_height])
def put(self, matrices): def put(self, matrices):
self.screen.fill((0, 0, 0)) self.screen.fill((0, 0, 0))
for event in pygame.event.get(): # User did something for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
pygame.quit() pygame.quit()
sys.exit() sys.exit()
if self.shape == (16, 32): if self.shape == (16, 32):
matrix = np.concatenate((matrices[0], matrices[1]), axis=1) matrix = np.concatenate((matrices[0], matrices[1]), axis=1)
self.pixels = matrix self.pixels = matrix
self.draw_pixels() self.draw_pixels()
pygame.display.flip() pygame.display.flip()
pygame.event.pump() pygame.event.pump()
def draw_pixels(self): def draw_pixels(self):
p = self.pixel_size p = self.pixel_size
r = int(p / 4) r = int(p / 4)
for i in range(self.HEIGHT): for i in range(self.HEIGHT):
for j in range(self.WIDTH): for j in range(self.WIDTH):
w_x = int(j * p + p / 2) w_x = int(j * p + p / 2)
#w_y = int((self.HEIGHT - 1 - y) * p + p / 2) #w_y = int((self.HEIGHT - 1 - y) * p + p / 2)
w_y = int(i * p + p / 2) w_y = int(i * p + p / 2)
color = self.pixels[i,j,:] color = self.pixels[i,j,:]
color = color.astype("int") color = color.astype("int")
pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color) pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color)
pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color) pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color)

View File

@ -1,96 +1,96 @@
import colorsys import colorsys
import time import time
import numpy as np import numpy as np
try: try:
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
SETUP_FAIL = False SETUP_FAIL = False
except ImportError: except ImportError:
from unittest.mock import Mock from unittest.mock import Mock
GPIO = Mock() GPIO = Mock()
SETUP_FAIL = True SETUP_FAIL = True
class ClockOut: class ClockOut:
def __init__(self): def __init__(self):
self.PIN_CLK = 11 self.PIN_CLK = 11
################################## ##################################
# Hardcoded vaules: # Hardcoded vaules:
# GPIO Pins for the actual signal. The other ones are for signal clocks and resets. # GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
self.PINS_DAT = [10, 22] self.PINS_DAT = [10, 22]
self.PIN_CS = 8 self.PIN_CS = 8
# for data transmission # for data transmission
self.SOF = 0x72 self.SOF = 0x72
self.DELAY = 1.0/120 self.DELAY = 1.0/120
# shape of 2 unicorn hats # shape of 2 unicorn hats
self.shape = (16, 32) self.shape = (16, 32)
################################## ##################################
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False) GPIO.setwarnings(False)
GPIO.setup(self.PIN_CS, GPIO.OUT, initial=GPIO.HIGH) GPIO.setup(self.PIN_CS, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(self.PIN_CLK, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(self.PIN_CLK, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(self.PINS_DAT, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(self.PINS_DAT, GPIO.OUT, initial=GPIO.LOW)
self.HEIGHT = self.shape[0] #16 self.HEIGHT = self.shape[0] #16
self.WIDTH = self.shape[1] #32 self.WIDTH = self.shape[1] #32
self.reset_clock() self.reset_clock()
def reset_clock(self): def reset_clock(self):
GPIO.output(self.PIN_CS, GPIO.LOW) GPIO.output(self.PIN_CS, GPIO.LOW)
time.sleep(0.00001) time.sleep(0.00001)
GPIO.output(self.PIN_CS, GPIO.HIGH) GPIO.output(self.PIN_CS, GPIO.HIGH)
def spi_write(self, buf1, buf2): def spi_write(self, buf1, buf2):
GPIO.output(self.PIN_CS, GPIO.LOW) GPIO.output(self.PIN_CS, GPIO.LOW)
self.spi_write_byte(self.SOF, self.SOF) self.spi_write_byte(self.SOF, self.SOF)
for x in range(len(buf1)): for x in range(len(buf1)):
b1, b2= buf1[x], buf2[x] b1, b2= buf1[x], buf2[x]
self.spi_write_byte(b1, b2) self.spi_write_byte(b1, b2)
GPIO.output(self.PIN_CS, GPIO.HIGH) GPIO.output(self.PIN_CS, GPIO.HIGH)
def spi_write_byte(self, b1, b2): def spi_write_byte(self, b1, b2):
for x in range(8): for x in range(8):
GPIO.output(self.PINS_DAT[0], b1 & 0b10000000) GPIO.output(self.PINS_DAT[0], b1 & 0b10000000)
GPIO.output(self.PINS_DAT[1], b2 & 0b10000000) GPIO.output(self.PINS_DAT[1], b2 & 0b10000000)
GPIO.output(self.PIN_CLK, GPIO.HIGH) GPIO.output(self.PIN_CLK, GPIO.HIGH)
b1 <<= 1 b1 <<= 1
b2 <<= 1 b2 <<= 1
#time.sleep(0.00000001) #time.sleep(0.00000001)
GPIO.output(self.PIN_CLK, GPIO.LOW) GPIO.output(self.PIN_CLK, GPIO.LOW)
def put(self, matrices): def put(self, matrices):
"""Sets a height x width matrix directly""" """Sets a height x width matrix directly"""
self.reset_clock() self.reset_clock()
matrix = np.concatenate((matrices[0], matrices[1]), axis=1) # or 1?? matrix = np.concatenate((matrices[0], matrices[1]), axis=1) # or 1??
self.show(matrix) self.show(matrix)
def clear(self): def clear(self):
"""Clear the buffer.""" """Clear the buffer."""
zero = np.zero((self.HEIGHT, self. WIDTH)) zero = np.zero((self.HEIGHT, self. WIDTH))
self.put(zero) self.put(zero)
def show(self, matrix): def show(self, matrix):
"""Output the contents of the buffer to Unicorn HAT HD.""" """Output the contents of the buffer to Unicorn HAT HD."""
########################################################## ##########################################################
## Change to desire ## Change to desire
buff2 = np.rot90(matrix[:self.HEIGHT,:16],3) buff2 = np.rot90(matrix[:self.HEIGHT,:16],3)
buff1 = np.rot90(matrix[:self.HEIGHT,16:32],1) buff1 = np.rot90(matrix[:self.HEIGHT,16:32],1)
########################################################## ##########################################################
# separated these are: 16x16x3 arrays # separated these are: 16x16x3 arrays
buff1, buff2 = [(x.reshape(768)).astype(np.uint8).tolist() for x in (buff1, buff2)] buff1, buff2 = [(x.reshape(768)).astype(np.uint8).tolist() for x in (buff1, buff2)]
self.spi_write(buff1, buff2) self.spi_write(buff1, buff2)
time.sleep(self.DELAY) time.sleep(self.DELAY)

View File

@ -1,178 +1,178 @@
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
import numpy as np import numpy as np
import datetime import datetime
import time import time
# bulky hard-coded values: # bulky hard-coded values:
from . import shapes from . import shapes
digits = shapes.digits digits = shapes.digits
weather_categories = shapes.weather_categories weather_categories = shapes.weather_categories
digit_position = [[2,4], [2,10], [9,4], [9,10]] digit_position = [[2,4], [2,10], [9,4], [9,10]]
days = np.append(np.zeros((15,16)), np.array([0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,1])).reshape((16,16)) days = np.append(np.zeros((15,16)), np.array([0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,1])).reshape((16,16))
class MatrixOperations(): class MatrixOperations():
"""Helper functions to generate frequently-used images""" """Helper functions to generate frequently-used images"""
def __init__(self, shape=[16,16]): def __init__(self, shape=[16,16]):
self.shape = shape self.shape = shape
# shape is going to be (16,32) for the moment # shape is going to be (16,32) for the moment
self.primary = [200, 200, 200] self.primary = [200, 200, 200]
self.secondary = [10, 200, 10] self.secondary = [10, 200, 10]
self.error = [200, 10, 10] self.error = [200, 10, 10]
def time_converter(self, top="", bottom=""): def time_converter(self, top="", bottom=""):
"""Converts 4-digit time to a 16x16 pixel-matrix """Converts 4-digit time to a 16x16 pixel-matrix
returns: np.array""" returns: np.array"""
# nshape = (self.shape[0], int(self.shape[1]/2)) # nshape = (self.shape[0], int(self.shape[1]/2))
nshape = (16, 16) nshape = (16, 16)
pixels = np.zeros(nshape, dtype=np.uint8) pixels = np.zeros(nshape, dtype=np.uint8)
if bottom == "" or top == "": if bottom == "" or top == "":
top = datetime.datetime.now().strftime("%H") top = datetime.datetime.now().strftime("%H")
bottom = datetime.datetime.now().strftime("%M") bottom = datetime.datetime.now().strftime("%M")
if len(top) < 2: if len(top) < 2:
top = "0" * (2 - len(top)) + top top = "0" * (2 - len(top)) + top
if len(bottom) < 2: if len(bottom) < 2:
bottom = "0" * (2 - len(bottom)) + bottom bottom = "0" * (2 - len(bottom)) + bottom
if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2): if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2):
time_split = 4*["-"] time_split = 4*["-"]
elif "error" in top and "error" in bottom: elif "error" in top and "error" in bottom:
time_split = 4*["error"] time_split = 4*["error"]
else: else:
time_split = [i for i in top] + [i for i in bottom] time_split = [i for i in top] + [i for i in bottom]
if "-1" in top and len(top) != 2: if "-1" in top and len(top) != 2:
time_split = ["-1", top[-1]] + [i for i in bottom] time_split = ["-1", top[-1]] + [i for i in bottom]
if "-1" in bottom and len(bottom) != 2: if "-1" in bottom and len(bottom) != 2:
time_split = [i for i in top] + ["-1", bottom[-1]] time_split = [i for i in top] + ["-1", bottom[-1]]
for i in range(4): for i in range(4):
x = digit_position[i][0] x = digit_position[i][0]
y = digit_position[i][1] y = digit_position[i][1]
number = digits[time_split[i]] number = digits[time_split[i]]
pixels[x: x + 5, y: y + 3] = np.array(number) pixels[x: x + 5, y: y + 3] = np.array(number)
return pixels return pixels
def date_converter(self): def date_converter(self):
# nshape = (self.shape[0], int(self.shape[1]/2)) # nshape = (self.shape[0], int(self.shape[1]/2))
nshape = (16, 16) nshape = (16, 16)
today = datetime.datetime.today() today = datetime.datetime.today()
weekday = datetime.datetime.weekday(today) weekday = datetime.datetime.weekday(today)
# size of the reduced array according to weekday # size of the reduced array according to weekday
size = [2,4,6,8,10,13,16] size = [2,4,6,8,10,13,16]
pixels = days.copy() #base color background pixels = days.copy() #base color background
lrow = np.append(pixels[15,:size[weekday]], [0 for i in range(16 - size[weekday])]) lrow = np.append(pixels[15,:size[weekday]], [0 for i in range(16 - size[weekday])])
lrow = np.append(np.zeros((15,16)), lrow).reshape(nshape) lrow = np.append(np.zeros((15,16)), lrow).reshape(nshape)
pixels += lrow pixels += lrow
return pixels return pixels
def weather_converter(self, name): def weather_converter(self, name):
"""Fills one half of the screen with weather info.""" """Fills one half of the screen with weather info."""
# nshape = (self.shape[0], int(self.shape[1]/2)) # nshape = (self.shape[0], int(self.shape[1]/2))
nshape = (16, 16) nshape = (16, 16)
result = np.zeros(nshape) result = np.zeros(nshape)
cwd = __file__.replace("\\","/") # for windows cwd = __file__.replace("\\","/") # for windows
cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is) cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is)
if len(cwd) == 0: if len(cwd) == 0:
cwd = "." cwd = "."
icon_spritesheet = cwd + "/weather-icons.bmp" icon_spritesheet = cwd + "/weather-icons.bmp"
icons = Image.open(icon_spritesheet) icons = Image.open(icon_spritesheet)
icons_full = np.array(icons) icons_full = np.array(icons)
icon_loc = ["sun","moon","sun and clouds", "moon and clouds", "cloud","fog and clouds","2 clouds", "3 clouds", "rain and cloud", "rain and clouds", "rain and cloud and sun", "rain and cloud and moon", "thunder and cloud", "thunder and cloud and moon", "snow and cloud", "snow and cloud and moon", "fog","fog night"] icon_loc = ["sun","moon","sun and clouds", "moon and clouds", "cloud","fog and clouds","2 clouds", "3 clouds", "rain and cloud", "rain and clouds", "rain and cloud and sun", "rain and cloud and moon", "thunder and cloud", "thunder and cloud and moon", "snow and cloud", "snow and cloud and moon", "fog","fog night"]
#ordered 1 2 \n 3 4 \ 5 5 ... #ordered 1 2 \n 3 4 \ 5 5 ...
if name == "": if name == "":
name = "error" name = "error"
name = weather_categories[name] name = weather_categories[name]
try: try:
iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2 iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2
# x and y coords # x and y coords
except: except:
return np.zeros((*nshape,3)) return np.zeros((*nshape,3))
icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...] icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...]
return icon_single return icon_single
def matrix_add_depth(self, matrix, colors = []): def matrix_add_depth(self, matrix, colors = []):
"""transforms a 2d-array with 0,1,2 to a 3d-array with the rgb values for primary and secondary color""" """transforms a 2d-array with 0,1,2 to a 3d-array with the rgb values for primary and secondary color"""
c1 = self.primary c1 = self.primary
c2 = self.secondary c2 = self.secondary
c3 = self.error c3 = self.error
if len(colors) > 0: if len(colors) > 0:
c1 = colors[0] c1 = colors[0]
if len(colors) > 1: if len(colors) > 1:
c2 = colors[1] c2 = colors[1]
if len(colors) > 2: if len(colors) > 2:
c3 = colors[2] c3 = colors[2]
if len(colors) > 3: if len(colors) > 3:
print("Too many colors") print("Too many colors")
r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int) r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int)
for i in range(matrix.shape[0]): for i in range(matrix.shape[0]):
for j in range(matrix.shape[1]): for j in range(matrix.shape[1]):
t = int(matrix[i, j]) t = int(matrix[i, j])
if t == 0: if t == 0:
r3[i, j, :] = [0,0,0] r3[i, j, :] = [0,0,0]
elif t == 1: elif t == 1:
r3[i, j, :] = c1 r3[i, j, :] = c1
elif t == 2: elif t == 2:
r3[i, j, :] = c2 r3[i, j, :] = c2
else: else:
r3[i, j, :] = c3 r3[i, j, :] = c3
return r3 return r3
def clock_face(self, weather): def clock_face(self, weather):
"""weather as a dict""" """weather as a dict"""
hour = self.time_converter() hour = self.time_converter()
day = self.date_converter() day = self.date_converter()
face1 = hour + day face1 = hour + day
# time + date: # time + date:
face1_3d = self.matrix_add_depth(face1) face1_3d = self.matrix_add_depth(face1)
# weather icons # weather icons
face2_3d = self.weather_converter(weather["weather"]) face2_3d = self.weather_converter(weather["weather"])
# weather temps # weather temps
face3 = self.time_converter(top=str(weather["low"]), bottom=str(weather["high"])) face3 = self.time_converter(top=str(weather["low"]), bottom=str(weather["high"]))
face3 = np.concatenate((face3[:8,...],2*face3[8:,...])) face3 = np.concatenate((face3[:8,...],2*face3[8:,...]))
face3_3d = self.matrix_add_depth(face3,[[0, 102, 255],[255, 102, 0]]) face3_3d = self.matrix_add_depth(face3,[[0, 102, 255],[255, 102, 0]])
return [face1_3d, face2_3d, face3_3d] return [face1_3d, face2_3d, face3_3d]
def text_converter(self, text, height, color): def text_converter(self, text, height, color):
"""Converts a text to a pixel-matrix """Converts a text to a pixel-matrix
returns: np.array((16, x, 3))""" returns: np.array((16, x, 3))"""
font = ImageFont.truetype("verdanab.ttf", height) font = ImageFont.truetype("verdanab.ttf", height)
size = font.getsize(text) size = font.getsize(text)
img = Image.new("1",size,"black") img = Image.new("1",size,"black")
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
draw.text((0, 0), text, "white", font=font) draw.text((0, 0), text, "white", font=font)
pixels = np.array(img, dtype=np.uint8) pixels = np.array(img, dtype=np.uint8)
pixels3d = self.matrix_add_depth(pixels, color) pixels3d = self.matrix_add_depth(pixels, color)
return pixels3d return pixels3d
def get_fallback(self): def get_fallback(self):
hour = self.time_converter() hour = self.time_converter()
day = self.date_converter() day = self.date_converter()
face1 = hour + day face1 = hour + day
face1_3d = self.matrix_add_depth(face1) face1_3d = self.matrix_add_depth(face1)
face2_3d = face3_3d = np.zeros((16,16,3)) face2_3d = face3_3d = np.zeros((16,16,3))
return [face1_3d, face2_3d, face3_3d] return [face1_3d, face2_3d, face3_3d]

View File

@ -1,103 +1,103 @@
import numpy as np import numpy as np
digits = { digits = {
"1" : np.array([ "1" : np.array([
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[0,0,1]]), [0,0,1]]),
"2" : np.array([ "2" : np.array([
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[1,1,1], [1,1,1],
[1,0,0], [1,0,0],
[1,1,1]]), [1,1,1]]),
"3" : np.array([ "3" : np.array([
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[1,1,1]]), [1,1,1]]),
"4" : np.array([ "4" : np.array([
[1,0,1], [1,0,1],
[1,0,1], [1,0,1],
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[0,0,1]]), [0,0,1]]),
"5" : np.array([ "5" : np.array([
[1,1,1], [1,1,1],
[1,0,0], [1,0,0],
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[1,1,1]]), [1,1,1]]),
"6" : np.array([ "6" : np.array([
[1,1,1], [1,1,1],
[1,0,0], [1,0,0],
[1,1,1], [1,1,1],
[1,0,1], [1,0,1],
[1,1,1]]), [1,1,1]]),
"7" : np.array([ "7" : np.array([
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[0,0,1]]), [0,0,1]]),
"8" : np.array([ "8" : np.array([
[1,1,1], [1,1,1],
[1,0,1], [1,0,1],
[1,1,1], [1,1,1],
[1,0,1], [1,0,1],
[1,1,1]]), [1,1,1]]),
"9" : np.array([ "9" : np.array([
[1,1,1], [1,1,1],
[1,0,1], [1,0,1],
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[1,1,1]]), [1,1,1]]),
"0" : np.array([ "0" : np.array([
[1,1,1], [1,1,1],
[1,0,1], [1,0,1],
[1,0,1], [1,0,1],
[1,0,1], [1,0,1],
[1,1,1]]), [1,1,1]]),
"-" : np.array([ "-" : np.array([
[0,0,0], [0,0,0],
[0,0,0], [0,0,0],
[1,1,1], [1,1,1],
[0,0,0], [0,0,0],
[0,0,0]]), [0,0,0]]),
"-1" : np.array([ "-1" : np.array([
[0,0,1], [0,0,1],
[0,0,1], [0,0,1],
[1,1,1], [1,1,1],
[0,0,1], [0,0,1],
[0,0,1]]), [0,0,1]]),
"error" : np.array([ "error" : np.array([
[1,0,1], [1,0,1],
[1,0,1], [1,0,1],
[0,1,0], [0,1,0],
[1,0,1], [1,0,1],
[1,0,1]]), [1,0,1]]),
} }
weather_categories = { weather_categories = {
"Clouds": "cloud", "Clouds": "cloud",
"Rain": "rain and cloud", "Rain": "rain and cloud",
"Thunderstorm": "thunder and cloud", "Thunderstorm": "thunder and cloud",
"Drizzle": "rain and cloud", "Drizzle": "rain and cloud",
"Snow": "snow and cloud", "Snow": "snow and cloud",
"Clear": "sun", "Clear": "sun",
"Mist": "fog and clouds", "Mist": "fog and clouds",
"Smoke": "Smoke", "Smoke": "Smoke",
"Haze": "Haze", "Haze": "Haze",
"Dust": "Dust", "Dust": "Dust",
"Fog": "fog", "Fog": "fog",
"Sand": "Sand", "Sand": "Sand",
"Dust": "Dust", "Dust": "Dust",
"Ash": "Ash", "Ash": "Ash",
"Squal": "Squal", "Squal": "Squal",
"Tornado": "Tornado", "Tornado": "Tornado",
"error" : "moon" "error" : "moon"
} }

View File

@ -1,30 +1,30 @@
from threading import Timer from threading import Timer
import time import time
class RepeatedTimer(object): class RepeatedTimer(object):
def __init__(self, interval, function, *args, **kwargs): def __init__(self, interval, function, *args, **kwargs):
self._timer = None self._timer = None
self.interval = interval self.interval = interval
self.function = function self.function = function
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self.is_running = False self.is_running = False
self.next_call = time.time() self.next_call = time.time()
self.start() self.start()
def _run(self): def _run(self):
self.is_running = False self.is_running = False
self.start() self.start()
self.function(*self.args, **self.kwargs) self.function(*self.args, **self.kwargs)
def start(self): def start(self):
if not self.is_running: if not self.is_running:
self.next_call += self.interval self.next_call += self.interval
self._timer = Timer(self.next_call - time.time(), self._run) self._timer = Timer(self.next_call - time.time(), self._run)
self._timer.start() self._timer.start()
self.is_running = True self.is_running = True
def stop(self): def stop(self):
self._timer.cancel() self._timer.cancel()
self.is_running = False self.is_running = False

View File

@ -1 +1 @@
# Placeholder # Placeholder

View File

@ -1,299 +1,299 @@
import dash import dash
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import dash_html_components as html import dash_html_components as html
import dash_core_components as dcc import dash_core_components as dcc
import plotly.graph_objects as go import plotly.graph_objects as go
from dash.dependencies import Input, Output from dash.dependencies import Input, Output
import locale import locale
locale.setlocale(locale.LC_TIME, "de_DE.utf8") locale.setlocale(locale.LC_TIME, "de_DE.utf8")
#from dash.dependencies import Input, Output #from dash.dependencies import Input, Output
import datetime import datetime
import time import time
import xmltodict import xmltodict
import requests import requests
from threading import Thread from threading import Thread
from . import helpers from . import helpers
class DashBoard(): class DashBoard():
"""""" """"""
# added by the launcher, we have self.modules (dict) # added by the launcher, we have self.modules (dict)
def __init__(self, port): def __init__(self, port):
## pre-sets ## pre-sets
self.inter_margin = "1em" self.inter_margin = "1em"
self.host_ip = "0.0.0.0" self.host_ip = "0.0.0.0"
self.port = port self.port = port
ex_css = [dbc.themes.BOOTSTRAP] ex_css = [dbc.themes.BOOTSTRAP]
self.app = dash.Dash(__name__, external_stylesheets=ex_css) self.app = dash.Dash(__name__, external_stylesheets=ex_css)
self.app.layout = html.Div([ self.app.layout = html.Div([
html.Div(id = 'layout-update', className = "content", style={"padding":self.inter_margin},), html.Div(id = 'layout-update', className = "content", style={"padding":self.inter_margin},),
dcc.Interval( dcc.Interval(
id='interval-component', id='interval-component',
interval=3600*1000, # in milliseconds interval=3600*1000, # in milliseconds
n_intervals=0 n_intervals=0
) )
]#,style={'background-image':'url("static/background.jpg")'} ]#,style={'background-image':'url("static/background.jpg")'}
) )
@self.app.callback(Output('layout-update','children'), Input('interval-component','n_intervals')) @self.app.callback(Output('layout-update','children'), Input('interval-component','n_intervals'))
def update_layout(n): def update_layout(n):
self.set_stats() self.set_stats()
kids = [ kids = [
self.card_header(), self.card_header(),
dbc.CardColumns([ dbc.CardColumns([
# self.card_weather(), # self.card_weather(),
*self.cards_lists(), *self.cards_lists(),
self.card_bot_stats(), self.card_bot_stats(),
self.card_news(), self.card_news(),
self.card_xkcd(), self.card_xkcd(),
self.card_sensor_stats(), self.card_sensor_stats(),
]) ])
] ]
return kids return kids
def start(self): def start(self):
flaskThread = Thread(target=app.run_server, kwargs={"host": self.host_ip, "port": self.port}).start() flaskThread = Thread(target=app.run_server, kwargs={"host": self.host_ip, "port": self.port}).start()
#self.app.run_server()#, debug=True) #self.app.run_server()#, debug=True)
def card_header(self): def card_header(self):
today = datetime.date.today().strftime("%A, der %d. %B %Y") today = datetime.date.today().strftime("%A, der %d. %B %Y")
card = dbc.Card( card = dbc.Card(
[ dbc.CardImg(src="static/header.jpg", top=True, bottom=False, [ dbc.CardImg(src="static/header.jpg", top=True, bottom=False,
title="Header", alt='Header image'), title="Header", alt='Header image'),
dbc.CardBody([html.H3(today, className="card-title")]), dbc.CardBody([html.H3(today, className="card-title")]),
], ],
color="dark", color="dark",
style = {"width" : "100%", "margin-bottom":self.inter_margin}, style = {"width" : "100%", "margin-bottom":self.inter_margin},
inverse=True, inverse=True,
) )
return card return card
def cards_lists(self): def cards_lists(self):
ret = [] ret = []
for l in self.persistence["global"]["lists"].keys(): for l in self.persistence["global"]["lists"].keys():
l_content = self.persistence["global"]["lists"][l] l_content = self.persistence["global"]["lists"][l]
html_content = [html.A(t, href="#", className="list-group-item bg-dark list-group-item-action text-light") for t in l_content] html_content = [html.A(t, href="#", className="list-group-item bg-dark list-group-item-action text-light") for t in l_content]
card = dbc.Card( card = dbc.Card(
[ [
dbc.CardBody([ dbc.CardBody([
html.H4("Liste '" + l + "':", className="card-title"), html.H4("Liste '" + l + "':", className="card-title"),
dbc.ListGroup(html_content, flush=True, style={"color":"black"}) dbc.ListGroup(html_content, flush=True, style={"color":"black"})
]), ]),
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
ret.append(card) ret.append(card)
return ret return ret
def card_bot_stats(self): def card_bot_stats(self):
if not self.stat_graph: if not self.stat_graph:
self.set_stats() self.set_stats()
card = dbc.Card( card = dbc.Card(
[ [
dbc.CardBody([ dbc.CardBody([
html.H4("Chat-Metriken", className="card-title"), html.H4("Chat-Metriken", className="card-title"),
dcc.Graph(figure=self.stat_graph, config={'displayModeBar': False}) dcc.Graph(figure=self.stat_graph, config={'displayModeBar': False})
]), ]),
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
return card return card
def card_sensor_stats(self): def card_sensor_stats(self):
fig = go.Figure() fig = go.Figure()
sensors = self.persistence["clock"]["sensors"] sensors = self.persistence["clock"]["sensors"]
time = sensors["time"] time = sensors["time"]
time = [t - time[0] for t in time] # rescale time = [t - time[0] for t in time] # rescale
for sensor in sensors.keys(): for sensor in sensors.keys():
if sensor != "time": if sensor != "time":
fig.add_trace(go.Scatter(x=time, y=sensors[sensor], mode="lines", text=sensor, line=dict(width=4))) fig.add_trace(go.Scatter(x=time, y=sensors[sensor], mode="lines", text=sensor, line=dict(width=4)))
fig.layout.update( fig.layout.update(
# xaxis = { # xaxis = {
# 'showgrid': False, # thin lines in the background # 'showgrid': False, # thin lines in the background
# 'zeroline': False, # thick line at x=0 # 'zeroline': False, # thick line at x=0
# 'visible': False, # numbers below # 'visible': False, # numbers below
# }, # the same for yaxis # }, # the same for yaxis
# yaxis = { # yaxis = {
# 'showgrid': False, # thin lines in the background # 'showgrid': False, # thin lines in the background
# 'zeroline': False, # thick line at x=0 # 'zeroline': False, # thick line at x=0
# 'visible': False, # numbers below # 'visible': False, # numbers below
# }, # the same for yaxis # }, # the same for yaxis
showlegend=False, showlegend=False,
# margin=dict(l=0, r=0, t=0, b=0), # margin=dict(l=0, r=0, t=0, b=0),
# paper_bgcolor='rgba(0,0,0,0)', # paper_bgcolor='rgba(0,0,0,0)',
# plot_bgcolor='rgba(0,0,0,0)', # plot_bgcolor='rgba(0,0,0,0)',
) )
card = dbc.Card( card = dbc.Card(
[ [
dbc.CardBody([ dbc.CardBody([
html.H4("Sensor-Metriken", className="card-title"), html.H4("Sensor-Metriken", className="card-title"),
dcc.Graph(figure=fig, config={'displayModeBar': False}) dcc.Graph(figure=fig, config={'displayModeBar': False})
]), ]),
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
return card return card
def card_weather(self): def card_weather(self):
def weather_item(name, overview, temps): def weather_item(name, overview, temps):
if len(temps) == 2: if len(temps) == 2:
temp = "🌡(❄): " + str(temps[0]) + "° ➡ 🌡(🔥): " + str(temps[1]) + "°" temp = "🌡(❄): " + str(temps[0]) + "° ➡ 🌡(🔥): " + str(temps[1]) + "°"
else: else:
temp = "🌡: " + str(temps[0]) + "°" temp = "🌡: " + str(temps[0]) + "°"
temp_line = html.P(temp, className="mb-1") temp_line = html.P(temp, className="mb-1")
it = html.A([ it = html.A([
html.Div([ html.Div([
html.H5(name, className="mb-1"), html.H5(name, className="mb-1"),
html.Span(categories[overview], className="badge badge-primary badge-pill") html.Span(categories[overview], className="badge badge-primary badge-pill")
], ],
className="d-flex w-100 justify-content-between"), className="d-flex w-100 justify-content-between"),
temp_line, temp_line,
], ],
href="#", className="list-group-item bg-dark list-group-item-action text-light" href="#", className="list-group-item bg-dark list-group-item-action text-light"
) )
return it return it
days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
categories = {"Clouds": "", "Rain": "🌧", "Thunderstorm": "🌩", "Drizzle": ":droplet:", "Snow": "", "Clear": "", "Mist": "🌫", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",} categories = {"Clouds": "", "Rain": "🌧", "Thunderstorm": "🌩", "Drizzle": ":droplet:", "Snow": "", "Clear": "", "Mist": "🌫", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",}
today = datetime.datetime.today().weekday() today = datetime.datetime.today().weekday()
body = [] body = []
try: try:
bot = self.modules["bot"] bot = self.modules["bot"]
content = bot.api_weather.show_weather([47.3769, 8.5417]) # still zürich content = bot.api_weather.show_weather([47.3769, 8.5417]) # still zürich
wt = content.pop(0) wt = content.pop(0)
body.append(weather_item("Jetzt", wt["short"], wt["temps"])) body.append(weather_item("Jetzt", wt["short"], wt["temps"]))
for i, day in enumerate(content): for i, day in enumerate(content):
tmp = [] tmp = []
if i == 0: if i == 0:
day_name = "Heute" day_name = "Heute"
else: else:
day_name = days[(today + i) % 7] day_name = days[(today + i) % 7]
body.append(weather_item(day_name, day["short"], day["temps"])) body.append(weather_item(day_name, day["short"], day["temps"]))
body = dbc.ListGroup(body, flush=True, style={"color":"black"}) body = dbc.ListGroup(body, flush=True, style={"color":"black"})
except: except:
body.append(html.H6("Konnte nicht geladen werden")) body.append(html.H6("Konnte nicht geladen werden"))
card = dbc.Card( card = dbc.Card(
[dbc.CardBody([ [dbc.CardBody([
html.H4("Wetter", className="card-title"), html.H4("Wetter", className="card-title"),
body])], body])],
color="dark", color="dark",
inverse=True, inverse=True,
) )
return card return card
def card_news(self): def card_news(self):
try: try:
card = dbc.Card([ card = dbc.Card([
dbc.CardBody([html.Iframe(src="https://nzz.ch", style={"border":"none", "min-height":"30em", "width":"100%"})]) dbc.CardBody([html.Iframe(src="https://nzz.ch", style={"border":"none", "min-height":"30em", "width":"100%"})])
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
except: except:
card = card = dbc.Card([ card = card = dbc.Card([
dbc.CardBody([ dbc.CardBody([
html.H4("Could not load NEWS", className="card-title"), html.H4("Could not load NEWS", className="card-title"),
]) ])
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
return card return card
def card_xkcd(self): def card_xkcd(self):
try: try:
xml = requests.get("https://xkcd.com/atom.xml").content xml = requests.get("https://xkcd.com/atom.xml").content
feed = xmltodict.parse(xml) feed = xmltodict.parse(xml)
title = feed["feed"]["entry"][0]["title"] title = feed["feed"]["entry"][0]["title"]
img = feed["feed"]["entry"][0]["summary"]["#text"] img = feed["feed"]["entry"][0]["summary"]["#text"]
i1 = img.find('"') +1 i1 = img.find('"') +1
i2 = img.find('"', i1+1) i2 = img.find('"', i1+1)
i3 = img.find('"', i2+1) + 1 i3 = img.find('"', i2+1) + 1
i4 = img.find('"', i3+1) i4 = img.find('"', i3+1)
img_src = img[i1:i2] img_src = img[i1:i2]
img_alt = img[i3:i4] img_alt = img[i3:i4]
card = dbc.Card([ card = dbc.Card([
dbc.CardBody([ dbc.CardBody([
html.H4(title, className="card-title"), html.H4(title, className="card-title"),
html.Img(src=img_src, style={"width":"100%"}), html.Img(src=img_src, style={"width":"100%"}),
html.P(img_alt) html.P(img_alt)
]) ])
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
except: except:
card = dbc.Card([ card = dbc.Card([
dbc.CardBody([ dbc.CardBody([
html.H4("Could not load XKCD", className="card-title"), html.H4("Could not load XKCD", className="card-title"),
]) ])
], ],
color="dark", color="dark",
inverse=True, inverse=True,
) )
return card return card
######### helper: ######### helper:
def set_stats(self): def set_stats(self):
xs, ys = helpers.clean_axis(self.persistence["bot"]["send_activity"]["hour"], self.persistence["bot"]["send_activity"]["count"]) xs, ys = helpers.clean_axis(self.persistence["bot"]["send_activity"]["hour"], self.persistence["bot"]["send_activity"]["count"])
xr, yr = helpers.clean_axis(self.persistence["bot"]["receive_activity"]["hour"], self.persistence["bot"]["receive_activity"]["count"]) xr, yr = helpers.clean_axis(self.persistence["bot"]["receive_activity"]["hour"], self.persistence["bot"]["receive_activity"]["count"])
xe, ye = helpers.clean_axis(self.persistence["bot"]["execute_activity"]["hour"], self.persistence["bot"]["execute_activity"]["count"]) xe, ye = helpers.clean_axis(self.persistence["bot"]["execute_activity"]["hour"], self.persistence["bot"]["execute_activity"]["count"])
fig = go.Figure() fig = go.Figure()
fig.add_trace(go.Scatter(x=xr, y=yr, mode="lines", text="Gelesen", line=dict(width=4))) fig.add_trace(go.Scatter(x=xr, y=yr, mode="lines", text="Gelesen", line=dict(width=4)))
fig.add_trace(go.Scatter(x=xs, y=ys, mode="lines", text="Gesendet", line=dict(width=4))) fig.add_trace(go.Scatter(x=xs, y=ys, mode="lines", text="Gesendet", line=dict(width=4)))
fig.add_trace(go.Scatter(x=xe, y=ye, mode="lines", text="Ausgeführt", line=dict(width=4))) fig.add_trace(go.Scatter(x=xe, y=ye, mode="lines", text="Ausgeführt", line=dict(width=4)))
fig.update_xaxes(showgrid=False) fig.update_xaxes(showgrid=False)
fig.update_yaxes(showgrid=False) fig.update_yaxes(showgrid=False)
fig.layout.update( fig.layout.update(
xaxis = { xaxis = {
'showgrid': False, # thin lines in the background 'showgrid': False, # thin lines in the background
'zeroline': False, # thick line at x=0 'zeroline': False, # thick line at x=0
'visible': False, # numbers below 'visible': False, # numbers below
}, # the same for yaxis }, # the same for yaxis
yaxis = { yaxis = {
'showgrid': False, # thin lines in the background 'showgrid': False, # thin lines in the background
'zeroline': False, # thick line at x=0 'zeroline': False, # thick line at x=0
'visible': False, # numbers below 'visible': False, # numbers below
}, # the same for yaxis }, # the same for yaxis
showlegend=False, showlegend=False,
margin=dict(l=0, r=0, t=0, b=0), margin=dict(l=0, r=0, t=0, b=0),
paper_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
) )
self.stat_graph = fig self.stat_graph = fig

View File

@ -1,18 +1,18 @@
def clean_axis(x,y): def clean_axis(x,y):
"""x is the time the point in y was taken""" """x is the time the point in y was taken"""
try: try:
xn = range(x[0], x[-1]+1) xn = range(x[0], x[-1]+1)
yn = [] yn = []
count = 0 count = 0
for x_i in xn: for x_i in xn:
if x_i in x: if x_i in x:
yn.append(y[count]) yn.append(y[count])
count += 1 count += 1
else: else:
yn.append(0) yn.append(0)
xn = [i - int(x[0]) for i in xn] xn = [i - int(x[0]) for i in xn]
except: except:
xn = [] xn = []
yn = [] yn = []
return xn, yn return xn, yn

View File

@ -1,33 +1,33 @@
div.header { div.header {
position: fixed; position: fixed;
width: 100%; width: 100%;
height: var(--heading-height); height: var(--heading-height);
} }
/* Create angled background with 'before' pseudo-element */ /* Create angled background with 'before' pseudo-element */
header::before { header::before {
content: ""; content: "";
display: block; display: block;
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 6em; bottom: 6em;
width: 100%; width: 100%;
height: calc(var(--heading-height) + 10em); height: calc(var(--heading-height) + 10em);
z-index: -1; z-index: -1;
transform: skewY(-3.5deg); transform: skewY(-3.5deg);
background: background:
linear-gradient(rgba(0,0,0,.6), rgba(0,0,0,.6)), linear-gradient(rgba(0,0,0,.6), rgba(0,0,0,.6)),
url(https://images.unsplash.com/photo-1495464101292-552d0b52fe41?auto=format&fit=crop&w=1350&q=80) no-repeat center, url(https://images.unsplash.com/photo-1495464101292-552d0b52fe41?auto=format&fit=crop&w=1350&q=80) no-repeat center,
linear-gradient(#4e4376, #2b5876); linear-gradient(#4e4376, #2b5876);
background-size: cover; background-size: cover;
border-bottom: .2em solid #fff; border-bottom: .2em solid #fff;
} }
h1 { h1 {
font-size: calc(2.8em + 2.6vw); font-size: calc(2.8em + 2.6vw);
font-weight: 500; font-weight: 500;
letter-spacing: .01em; letter-spacing: .01em;
padding: 6rem 0 0 4.5rem; padding: 6rem 0 0 4.5rem;
text-shadow: .022em .022em .022em #111; text-shadow: .022em .022em .022em #111;
color: #fff; color: #fff;
} }

View File

@ -1,73 +1,73 @@
import logging import logging
from persistence import p_io, p_out from persistence import p_io, p_out
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Launcher: class Launcher:
"""base launcher that launches other submodules""" """base launcher that launches other submodules"""
def __init__(self, **modules): def __init__(self, **modules):
"""""" """"""
self.persistence = p_io.PersistentDict("persistence/prst.json") self.persistence = p_io.PersistentDict("persistence/prst.json")
self.db = p_out.DataBaseConnector() self.db = p_out.DataBaseConnector()
logger.info(self.__class__.__name__ + " initialized") logger.info(self.__class__.__name__ + " initialized")
self.modules = modules self.modules = modules
if len(self.persistence) == 0: if len(self.persistence) == 0:
self.init_persistence() self.init_persistence()
self.persistence["global"]["reboots"] += 1 self.persistence["global"]["reboots"] += 1
self.launch_modules() self.launch_modules()
def launch_modules(self): def launch_modules(self):
for module in self.modules.values(): for module in self.modules.values():
logger.info("Starting module "+ module.__class__.__name__) logger.info("Starting module "+ module.__class__.__name__)
module.modules = self.modules module.modules = self.modules
module.persistence = self.persistence module.persistence = self.persistence
module.db = self.db # pooled ie multithreaded module.db = self.db # pooled ie multithreaded
module.start() module.start()
def init_persistence(self): def init_persistence(self):
logger.warning("No persistence found, created a new one") logger.warning("No persistence found, created a new one")
self.persistence["global"] ={ self.persistence["global"] ={
"lists" : {}, "lists" : {},
"reboots": 0 "reboots": 0
} }
for m_name in self.modules.keys(): for m_name in self.modules.keys():
data = {} data = {}
if m_name == "bot": if m_name == "bot":
data = { data = {
"send_activity" : {"hour":[], "count":[]}, "send_activity" : {"hour":[], "count":[]},
"receive_activity" : {"hour":[], "count":[]}, "receive_activity" : {"hour":[], "count":[]},
"execute_activity" : {"hour":[], "count":[]}, "execute_activity" : {"hour":[], "count":[]},
"log": [], "log": [],
"chat_members": {}, "chat_members": {},
"aliases" : {} "aliases" : {}
} }
if m_name == "clock": if m_name == "clock":
data = { data = {
"sensors" : { "sensors" : {
"time" : [], "time" : [],
"temperature":[], "temperature":[],
"humidity":[], "humidity":[],
"brightness" : [], "brightness" : [],
} }
} }
self.persistence[m_name] = data self.persistence[m_name] = data
######################################################################## ########################################################################
## Aand liftoff! ## Aand liftoff!
# Launcher() # Launcher()

View File

@ -1,12 +1,12 @@
## What is happening here? ## What is happening here?
This "persistence"-module aims to standardize 2 things: This "persistence"-module aims to standardize 2 things:
* the creation of a common set of variables that survives a potential (let's face it, likely) crash * the creation of a common set of variables that survives a potential (let's face it, likely) crash
* advanced logging and analytics * advanced logging and analytics
### Common variables ### Common variables
These are saved as a json file and are handled internally as a dict. Each change in the dict triggers a write to the file. These are saved as a json file and are handled internally as a dict. Each change in the dict triggers a write to the file.
### Logging ### Logging
A chunky sqlite-db which periodically gets new entries. From all modules. Ideally this db is then visualized through grafana. WIP A chunky sqlite-db which periodically gets new entries. From all modules. Ideally this db is then visualized through grafana. WIP

View File

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

View File

@ -1,87 +1,87 @@
import json import json
import os import os
class PersistentDict(dict): class PersistentDict(dict):
"""Extended dict that writes its content to a file every time a value is changed""" """Extended dict that writes its content to a file every time a value is changed"""
def __init__(self, file_name, *args, **kwargs): def __init__(self, file_name, *args, **kwargs):
"""initialization of the dict and of the required files""" """initialization of the dict and of the required files"""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.path = file_name self.path = file_name
self.last_action = "" self.last_action = ""
try: try:
self.read_dict() self.read_dict()
except: except:
with open(self.path, "a") as f: with open(self.path, "a") as f:
f.write("{}") f.write("{}")
## helper - functions ## helper - functions
def write_dict(self): def write_dict(self):
with open(self.path, "w") as f: with open(self.path, "w") as f:
json.dump(self, f) json.dump(self, f)
self.last_action = "w" self.last_action = "w"
def read_dict(self): def read_dict(self):
with open(self.path) as f: with open(self.path) as f:
tmp = dict(json.load(f)) tmp = dict(json.load(f))
for key in tmp: for key in tmp:
super().__setitem__(key, tmp[key]) super().__setitem__(key, tmp[key])
self.last_action = "r" self.last_action = "r"
## extended dictionary - logic ## extended dictionary - logic
def __setitem__(self, key, value): def __setitem__(self, key, value):
if self.last_action != "r": if self.last_action != "r":
self.read_dict() self.read_dict()
# not sure if the step to read is necessary, but I'll keep it for safety # not sure if the step to read is necessary, but I'll keep it for safety
super().__setitem__(key,value) super().__setitem__(key,value)
self.write_dict() # writes 'self' to a json file. self.write_dict() # writes 'self' to a json file.
def __getitem__(self, key): def __getitem__(self, key):
if self.last_action != "r": if self.last_action != "r":
self.read_dict() self.read_dict()
ret_val = super().__getitem__(key) ret_val = super().__getitem__(key)
if type(ret_val) != int and type(ret_val) != str: if type(ret_val) != int and type(ret_val) != str:
ret_val = create_struct(type(ret_val), key, self, ret_val) ret_val = create_struct(type(ret_val), key, self, ret_val)
return ret_val return ret_val
def clear(self): def clear(self):
super().clear() super().clear()
self.write_dict() self.write_dict()
return {} return {}
def create_struct(struct_type, own_name, parent_name, *args, **kwargs): def create_struct(struct_type, own_name, parent_name, *args, **kwargs):
class HookedStruct(struct_type): class HookedStruct(struct_type):
def __init__(self, own_name, parent_name, *args, **kwargs): def __init__(self, own_name, parent_name, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.name = own_name self.name = own_name
self.parent = parent_name self.parent = parent_name
def __setitem__(self, *args, **kwargs): def __setitem__(self, *args, **kwargs):
super().__setitem__(*args, **kwargs) super().__setitem__(*args, **kwargs)
self.parent.__setitem__(self.name, self) self.parent.__setitem__(self.name, self)
def __getitem__(self, *args, **kwargs): def __getitem__(self, *args, **kwargs):
ret_val = super().__getitem__(*args, **kwargs) ret_val = super().__getitem__(*args, **kwargs)
if type(ret_val) != int and type(ret_val) != str: if type(ret_val) != int and type(ret_val) != str:
ret_val = create_struct(type(ret_val), args[0], self, ret_val) ret_val = create_struct(type(ret_val), args[0], self, ret_val)
return ret_val return ret_val
def pop(self, *args): def pop(self, *args):
retvalue = super().pop(*args) retvalue = super().pop(*args)
self.parent.__setitem__(self.name, self) self.parent.__setitem__(self.name, self)
return retvalue return retvalue
def append(self, *args): def append(self, *args):
super().append(*args) super().append(*args)
self.parent.__setitem__(self.name, self) self.parent.__setitem__(self.name, self)
return HookedStruct(own_name, parent_name, *args, **kwargs) return HookedStruct(own_name, parent_name, *args, **kwargs)

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

View File

@ -1,17 +1,17 @@
Adafruit-Blinka==6.10.3 Adafruit-Blinka==6.10.3
adafruit-circuitpython-dht==3.6.1 adafruit-circuitpython-dht==3.6.1
Adafruit-PlatformDetect==3.14.2 Adafruit-PlatformDetect==3.14.2
Adafruit-PureIO==1.1.9 Adafruit-PureIO==1.1.9
board==1.0 board==1.0
importlib-metadata==4.6.0 importlib-metadata==4.6.0
pkg-resources==0.0.0 pkg-resources==0.0.0
psycopg2==2.9.1 psycopg2==2.9.1
psycopg2-binary==2.9.1 psycopg2-binary==2.9.1
pyftdi==0.53.1 pyftdi==0.53.1
pyserial==3.5 pyserial==3.5
pyusb==1.1.1 pyusb==1.1.1
rpi-ws281x==4.3.0 rpi-ws281x==4.3.0
RPi.GPIO==0.7.0 RPi.GPIO==0.7.0
sysv-ipc==1.1.0 sysv-ipc==1.1.0
typing-extensions==3.10.0.0 typing-extensions==3.10.0.0
zipp==3.4.1 zipp==3.4.1

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()