diff --git a/.gitignore b/.gitignore
index 0c2c0e0..af442cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,145 +1,145 @@
-**/VE
-
-#VS CODE files
-.vscode
-
-# Persistence
-prst.*
-
-log.txt
-# API-Key (keep secret at all times)
-keys.py
-
-# Cookies
-.google-cookie
-
-
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# 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.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# pipenv
-# 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
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
+**/VE
+
+#VS CODE files
+.vscode
+
+# Persistence
+prst.*
+
+log.txt
+# API-Key (keep secret at all times)
+keys.py
+
+# Cookies
+.google-cookie
+
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# 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.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# 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
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 19a3a41..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "sql_as_rest_api"]
- path = sql_as_rest_api
- url = https://github.com/moll-re/sql_as_rest_api
diff --git a/README.md b/README.md
index 455bbd5..4f8c049 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,34 @@
-# AIO
-
-Just like AIO-coolers, this little program aims to tackle many problems at once.
-
-
-## What it mainly does
-* chat-bot (via telegram)
-* clock and basic display (via LED-Matrix (size to taste))
-* dashboard (via external browser)
-
-
-### 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
-Server/Client which send/receive the output to show on the clock. Normally this is just a combination of time + weather. But special output can be triggered by the user.
-
-## Dashboard
-Shows basic info of the program and other useful things.
-
-TODO: show advanced host stats (cpu/mem/...)
+# AIO
+
+Just like AIO-coolers, this little program aims to tackle many problems at once.
+
+
+## What it mainly does
+* chat-bot (via telegram)
+* clock and basic display (via LED-Matrix (size to taste))
+* 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.
+
+
+### 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.
+
+### Ambient measurements
+Logs temperature, luminosity, humidity to a remote database. This information is then displayed in `moll.re`.
+
+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
\ No newline at end of file
diff --git a/bot/__init__.py b/bot/__init__.py
index dcf2c80..9a5984d 100644
--- a/bot/__init__.py
+++ b/bot/__init__.py
@@ -1 +1 @@
-# Placeholder
+# Placeholder
diff --git a/bot/api/__init__.py b/bot/api/__init__.py
index afd5954..97cff3a 100644
--- a/bot/api/__init__.py
+++ b/bot/api/__init__.py
@@ -1,6 +1,6 @@
-from . import keys
-from . import reddit
-from . import weather
-from . import reddit
-from . import search
+from . import keys
+from . import reddit
+from . import weather
+from . import reddit
+from . import search
from . import metmuseum
\ No newline at end of file
diff --git a/bot/api/metmuseum.py b/bot/api/metmuseum.py
index c2807e3..7044ae6 100644
--- a/bot/api/metmuseum.py
+++ b/bot/api/metmuseum.py
@@ -1,39 +1,39 @@
-import requests
-import random
-from PIL import Image
-import io
-
-class ArtFetch:
- def __init__(self):
- self.base_url = "https://collectionapi.metmuseum.org/"
- self.objects = self.fetch_objects() # chosen set of images to select randomly
-
-
- def fetch_objects(self):
- """We restrict ourselves to a few domains."""
- # fetch all departements
- t = requests.get(self.base_url + "public/collection/v1/departments").json()
- deps = t["departments"]
- keep_id = []
- for d in deps:
- 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":
- keep_id.append(str(d["departmentId"]))
- # fetch artworks listed under these departments
- data = {"departmentIds" : "|".join(keep_id)}
- t = requests.get(self.base_url + "public/collection/v1/objects",params=data).json()
- # num = t["total"]
- ids = t["objectIDs"]
- return ids
-
- def get_random_art(self):
- """Returns an image object of a randomly selected artwork"""
- # fetch the artwork's url
- r_id = self.objects[random.randint(0,len(self.objects))]
- t = requests.get(self.base_url + "public/collection/v1/objects/" + str(r_id)).json()
- im_url = t["primaryImageSmall"]
- # download the image
- resp = requests.get(im_url)
- img = Image.open(io.BytesIO(resp.content))
-
- return img
+import requests
+import random
+from PIL import Image
+import io
+
+class ArtFetch:
+ def __init__(self):
+ self.base_url = "https://collectionapi.metmuseum.org/"
+ self.objects = self.fetch_objects() # chosen set of images to select randomly
+
+
+ def fetch_objects(self):
+ """We restrict ourselves to a few domains."""
+ # fetch all departements
+ t = requests.get(self.base_url + "public/collection/v1/departments").json()
+ deps = t["departments"]
+ keep_id = []
+ for d in deps:
+ 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":
+ keep_id.append(str(d["departmentId"]))
+ # fetch artworks listed under these departments
+ data = {"departmentIds" : "|".join(keep_id)}
+ t = requests.get(self.base_url + "public/collection/v1/objects",params=data).json()
+ # num = t["total"]
+ ids = t["objectIDs"]
+ return ids
+
+ def get_random_art(self):
+ """Returns an image object of a randomly selected artwork"""
+ # fetch the artwork's url
+ r_id = self.objects[random.randint(0,len(self.objects))]
+ t = requests.get(self.base_url + "public/collection/v1/objects/" + str(r_id)).json()
+ im_url = t["primaryImageSmall"]
+ # download the image
+ resp = requests.get(im_url)
+ img = Image.open(io.BytesIO(resp.content))
+
+ return img
diff --git a/bot/api/reddit.py b/bot/api/reddit.py
index 2cd5112..657037a 100644
--- a/bot/api/reddit.py
+++ b/bot/api/reddit.py
@@ -1,56 +1,56 @@
-import praw
-
-
-
-class RedditFetch():
- def __init__(self, key):
- 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"):
- if return_type == "text":
- posts = []
- try:
- for submission in self.stream.subreddit(subreddit).top(limit=number):
- p = {}
- if not submission.stickied:
- p["title"] = submission.title
- p["content"] = submission.selftext
- posts.append(p)
- return posts
- except:
- return []
- else:
- images = []
- try:
- for submission in self.stream.subreddit(subreddit).top(limit=number):
- if not submission.stickied:
- t = {"image": submission.url, "caption": submission.title}
- images.append(t)
- return images
- except:
- return []
-
-
- def get_random_rising(self, subreddit, number, return_type="text"):
- if return_type == "text":
- posts = []
- try:
- for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
- p = {}
- if not submission.stickied:
- p["title"] = submission.title
- p["content"] = submission.selftext
- posts.append(p)
- return posts
- except:
- return []
- else:
- images = []
- try:
- for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
- if not submission.stickied:
- t = {"image": submission.url, "caption": submission.title}
- images.append(t)
- return images
- except:
- return []
+import praw
+
+
+
+class RedditFetch():
+ def __init__(self, key):
+ 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"):
+ if return_type == "text":
+ posts = []
+ try:
+ for submission in self.stream.subreddit(subreddit).top(limit=number):
+ p = {}
+ if not submission.stickied:
+ p["title"] = submission.title
+ p["content"] = submission.selftext
+ posts.append(p)
+ return posts
+ except:
+ return []
+ else:
+ images = []
+ try:
+ for submission in self.stream.subreddit(subreddit).top(limit=number):
+ if not submission.stickied:
+ t = {"image": submission.url, "caption": submission.title}
+ images.append(t)
+ return images
+ except:
+ return []
+
+
+ def get_random_rising(self, subreddit, number, return_type="text"):
+ if return_type == "text":
+ posts = []
+ try:
+ for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
+ p = {}
+ if not submission.stickied:
+ p["title"] = submission.title
+ p["content"] = submission.selftext
+ posts.append(p)
+ return posts
+ except:
+ return []
+ else:
+ images = []
+ try:
+ for submission in self.stream.subreddit(subreddit).random_rising(limit=number):
+ if not submission.stickied:
+ t = {"image": submission.url, "caption": submission.title}
+ images.append(t)
+ return images
+ except:
+ return []
diff --git a/bot/api/search.py b/bot/api/search.py
index adf1ee6..7140053 100644
--- a/bot/api/search.py
+++ b/bot/api/search.py
@@ -1,21 +1,21 @@
-import duckduckpy
-
-class WebSearch():
- def __init__(self):
- self.search = duckduckpy.query
-
- def get_result(self, query):
- try:
- res = []
- response = self.search(query, container = "dict")["related_topics"]
- for r in response:
- if "text" in r:
- res.append({
- "text" : r["text"],
- "url": r["first_url"]
- })
- except:
- res = ["Connection error"]
- return res
-
+import duckduckpy
+
+class WebSearch():
+ def __init__(self):
+ self.search = duckduckpy.query
+
+ def get_result(self, query):
+ try:
+ res = []
+ response = self.search(query, container = "dict")["related_topics"]
+ for r in response:
+ if "text" in r:
+ res.append({
+ "text" : r["text"],
+ "url": r["first_url"]
+ })
+ except:
+ res = ["Connection error"]
+ return res
+
# TODO: this api has more potential. Extract images or quick facts!
\ No newline at end of file
diff --git a/bot/api/weather.py b/bot/api/weather.py
index 66dc921..d5e92fc 100644
--- a/bot/api/weather.py
+++ b/bot/api/weather.py
@@ -1,87 +1,87 @@
-import requests
-import datetime
-
-import logging
-logger = logging.getLogger(__name__)
-class WeatherFetch():
- def __init__(self, key):
- self.last_fetch = datetime.datetime.fromtimestamp(0)
- self.last_weather = ""
- self.calls = 0
-
- self.url = "https://api.openweathermap.org/data/2.5/onecall?"
- self.key = key
-
- def show_weather(self, location):
- delta = datetime.datetime.now() - self.last_fetch
- 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"}
- self.calls += 1
- logger.info("Just fetched weather. ({}th time)".format(self.calls))
- # today = datetime.datetime.today().weekday()
- # days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
-
- try:
- 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",}
- now = weather["current"]
- ret_weather = []
- ret_weather.append({
- "short" : now["weather"][0]["main"],
- "temps" : [int(now["temp"])]
- })
- weather_days = weather["daily"]
- for i, day in enumerate(weather_days):
- ret_weather.append({
- "short" : day["weather"][0]["main"],
- "temps" : [int(day["temp"]["min"]),int(day["temp"]["max"])]
- })
- except:
- ret_weather = []
-
-
- # now = weather["current"]
- # message = "Now: " + categories[now["weather"][0]["main"]] + "\n"
- # message += ":thermometer: " + str(int(now["temp"])) + "°\n\n"
-
- # weather_days = weather["daily"]
-
- # for i, day in enumerate(weather_days):
- # if i == 0:
- # message += "" + "Today" + ": " + categories[day["weather"][0]["main"]] + "\n"
- # else:
- # message += "" + days[(today + i + 1) % 7] + ": " + 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"
- # except:
- # message = "Query failed, it's my fault, I'm sorry :sad:"
-
- self.last_weather = ret_weather
- self.last_fetch = datetime.datetime.now()
- else:
- ret_weather = self.last_weather
-
- return ret_weather
-
- # def get_weather_by_city(self, city):
- # loc = get_coords_from_city(self, city)
- # weather = self.show_weather(loc)
- # return weather
-
-
- # def get_coords_from_city(self, city):
- # url = "https://devru-latitude-longitude-find-v1.p.rapidapi.com/latlon.php"
- # data = {"location": city}
- # headers = {
- # "x-rapidapi-key" : "d4e0ab7ab3mshd5dde5a282649e0p11fd98jsnc93afd98e3aa",
- # "x-rapidapi-host" : "devru-latitude-longitude-find-v1.p.rapidapi.com",
- # }
-
- # #try:
- # resp = requests.request("GET", url, headers=headers, params=data)
- # result = resp.text
- # #except:
- # # result = "???"
- # return result
-
+import requests
+import datetime
+
+import logging
+logger = logging.getLogger(__name__)
+class WeatherFetch():
+ def __init__(self, key):
+ self.last_fetch = datetime.datetime.fromtimestamp(0)
+ self.last_weather = ""
+ self.calls = 0
+
+ self.url = "https://api.openweathermap.org/data/2.5/onecall?"
+ self.key = key
+
+ def show_weather(self, location):
+ delta = datetime.datetime.now() - self.last_fetch
+ 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"}
+ self.calls += 1
+ logger.info("Just fetched weather. ({}th time)".format(self.calls))
+ # today = datetime.datetime.today().weekday()
+ # days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
+
+ try:
+ 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",}
+ now = weather["current"]
+ ret_weather = []
+ ret_weather.append({
+ "short" : now["weather"][0]["main"],
+ "temps" : [int(now["temp"])]
+ })
+ weather_days = weather["daily"]
+ for i, day in enumerate(weather_days):
+ ret_weather.append({
+ "short" : day["weather"][0]["main"],
+ "temps" : [int(day["temp"]["min"]),int(day["temp"]["max"])]
+ })
+ except:
+ ret_weather = []
+
+
+ # now = weather["current"]
+ # message = "Now: " + categories[now["weather"][0]["main"]] + "\n"
+ # message += ":thermometer: " + str(int(now["temp"])) + "°\n\n"
+
+ # weather_days = weather["daily"]
+
+ # for i, day in enumerate(weather_days):
+ # if i == 0:
+ # message += "" + "Today" + ": " + categories[day["weather"][0]["main"]] + "\n"
+ # else:
+ # message += "" + days[(today + i + 1) % 7] + ": " + 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"
+ # except:
+ # message = "Query failed, it's my fault, I'm sorry :sad:"
+
+ self.last_weather = ret_weather
+ self.last_fetch = datetime.datetime.now()
+ else:
+ ret_weather = self.last_weather
+
+ return ret_weather
+
+ # def get_weather_by_city(self, city):
+ # loc = get_coords_from_city(self, city)
+ # weather = self.show_weather(loc)
+ # return weather
+
+
+ # def get_coords_from_city(self, city):
+ # url = "https://devru-latitude-longitude-find-v1.p.rapidapi.com/latlon.php"
+ # data = {"location": city}
+ # headers = {
+ # "x-rapidapi-key" : "d4e0ab7ab3mshd5dde5a282649e0p11fd98jsnc93afd98e3aa",
+ # "x-rapidapi-host" : "devru-latitude-longitude-find-v1.p.rapidapi.com",
+ # }
+
+ # #try:
+ # resp = requests.request("GET", url, headers=headers, params=data)
+ # result = resp.text
+ # #except:
+ # # result = "???"
+ # return result
+
diff --git a/bot/commands/__init__.py b/bot/commands/__init__.py
index 7483e3d..45af0cd 100644
--- a/bot/commands/__init__.py
+++ b/bot/commands/__init__.py
@@ -1,2 +1,2 @@
-# Placeholder
-from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search
+# Placeholder
+from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search
diff --git a/bot/commands/alias.py b/bot/commands/alias.py
index 4528201..adc4da0 100644
--- a/bot/commands/alias.py
+++ b/bot/commands/alias.py
@@ -1,64 +1,64 @@
-from .template import *
-
-FIRST = range(1)
-class Alias(BotFunc):
- """create a new command for command-paths you often use"""
-
- def __init__(self, dispatcher, db):
- super().__init__(db)
- self.dispatcher = dispatcher
- # do not interact with him yet!
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('alias', self.entry_point)],
- states={
- FIRST: [
- CallbackQueryHandler(self.print_all, pattern="^all$"),
- CallbackQueryHandler(self.create_alias, pattern="^new$"),
- CallbackQueryHandler(self.delete_alias, pattern='^delete$'),
- ]
- },
- fallbacks=[CommandHandler('alias', self.entry_point)],
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- test = self.dispatcher
- keyboard = [
- [InlineKeyboardButton("All aliases", callback_data="all")],
- [InlineKeyboardButton("Create new alias", callback_data="new")],
- [InlineKeyboardButton("Delete alias", callback_data="delete")],
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- super().log_activity(receive=True, execute=False, send=True)
- update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
- return FIRST
-
-
- def print_all(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- all_alias = ""
- for k in self.persistence["bot"]["aliases"]:
- all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
-
- query.edit_message_text(text="List of all commands:\n" + all_alias)
- return ConversationHandler.END
-
-
- def create_alias(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- all_alias = ""
- for k in self.persistence["bot"]["aliases"]:
- all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
-
- query.edit_message_text(text="List of all commands:\n" + all_alias)
- return ConversationHandler.END
-
- def delete_alias(self, update: Update, context: CallbackContext) -> None:
+from .template import *
+
+FIRST = range(1)
+class Alias(BotFunc):
+ """create a new command for command-paths you often use"""
+
+ def __init__(self, dispatcher, db):
+ super().__init__(db)
+ self.dispatcher = dispatcher
+ # do not interact with him yet!
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('alias', self.entry_point)],
+ states={
+ FIRST: [
+ CallbackQueryHandler(self.print_all, pattern="^all$"),
+ CallbackQueryHandler(self.create_alias, pattern="^new$"),
+ CallbackQueryHandler(self.delete_alias, pattern='^delete$'),
+ ]
+ },
+ fallbacks=[CommandHandler('alias', self.entry_point)],
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ test = self.dispatcher
+ keyboard = [
+ [InlineKeyboardButton("All aliases", callback_data="all")],
+ [InlineKeyboardButton("Create new alias", callback_data="new")],
+ [InlineKeyboardButton("Delete alias", callback_data="delete")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ super().log_activity(receive=True, execute=False, send=True)
+ update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
+ return FIRST
+
+
+ def print_all(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ all_alias = ""
+ for k in self.persistence["bot"]["aliases"]:
+ all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
+
+ query.edit_message_text(text="List of all commands:\n" + all_alias)
+ return ConversationHandler.END
+
+
+ def create_alias(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ all_alias = ""
+ for k in self.persistence["bot"]["aliases"]:
+ all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
+
+ query.edit_message_text(text="List of all commands:\n" + all_alias)
+ return ConversationHandler.END
+
+ def delete_alias(self, update: Update, context: CallbackContext) -> None:
return ConversationHandler.END
\ No newline at end of file
diff --git a/bot/commands/clock.py b/bot/commands/clock.py
index b08d570..34dad77 100644
--- a/bot/commands/clock.py
+++ b/bot/commands/clock.py
@@ -1,218 +1,218 @@
-from .template import *
-import time
-import numpy
-from PIL import Image
-import io
-
-CHOOSE, ADDARG = range(2)
-MESSAGE, WAKE, ALARM, IMAGE, ART = range(3,8)
-
-class Clock(BotFunc):
- """pass on commands to clock-module"""
- def __init__(self, prst, clock_module, art_api):
- super().__init__(prst)
- self.clock = clock_module
- self.api_art = art_api
-
- def create_handler(self):
- handler = ConversationHandler(
- entry_points=[CommandHandler("clock", self.entry_point)],
- states={
- CHOOSE : [
- CallbackQueryHandler(self.wake_light, pattern="^wake$"),
- CallbackQueryHandler(self.alarm_blink, pattern="^alarm$"),
- CallbackQueryHandler(self.show_message, pattern="^message$"),
- CallbackQueryHandler(self.show_image, pattern="^image$"),
- CallbackQueryHandler(self.art_gallery, pattern="^gallery$"),
- ],
- ADDARG : [MessageHandler(Filters.text, callback=self.get_arg1)],
- MESSAGE: [MessageHandler(Filters.text, callback=self.exec_show_message)],
- WAKE : [MessageHandler(Filters.text, callback=self.exec_wake_light)],
- ALARM : [MessageHandler(Filters.text, callback=self.exec_alarm_blink)],
- IMAGE : [MessageHandler(Filters.photo, callback=self.exec_show_image)],
- ART : [MessageHandler(Filters.text, callback=self.exec_art_gallery)],
- },
- fallbacks=[CommandHandler('clock', self.entry_point)],
- )
- return handler
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- keyboard = [
- [InlineKeyboardButton("Make a wake-light", callback_data="wake")],
- [InlineKeyboardButton("Blink as alarm", callback_data="alarm")],
- [InlineKeyboardButton("Show a message", callback_data="message")],
- [InlineKeyboardButton("Show an image", callback_data="image")],
- [InlineKeyboardButton("Art gallery!", callback_data="gallery")],
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
- return CHOOSE
-
-
- def wake_light(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- query.edit_message_text("Ok. How long should the color cycle last? (In seconds)")
- return WAKE
-
- def alarm_blink(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- query.edit_message_text("Ok. How long should it blink? (In seconds)")
- self.next_state = {ALARM : "What frequency (Hertz)"}
- return ADDARG
-
- def show_message(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- query.edit_message_text("Ok. What message will I show?")
- return MESSAGE
-
- def show_image(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- query.edit_message_text("How long (in minutes) should the image be displayed?")
- self.next_state = {IMAGE : "Please send me the photo to display."}
- return ADDARG
-
- def art_gallery(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- 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?"}
- return ADDARG
-
- def get_arg1(self, update: Update, context: CallbackContext) -> None:
- a = update.message.text
- self.additional_argument = a
- update.message.reply_text("Furthermore: "+ list(self.next_state.values())[0])
- return list(self.next_state.keys())[0]
-
-
-
-
-###### actually running clock actions
- def exec_wake_light(self, update: Update, context: CallbackContext) -> None:
- duration = update.message.text
-
- def output(duration):
- self.clock.set_brightness(value=1)
- start_color = numpy.array([153, 0, 51])
- end_color = numpy.array([255, 255, 0])
- col_show = numpy.zeros((*self.clock.shape, 3))
- col_show[:,:,...] = start_color
-
- gradient = end_color - start_color
- # 20 steps should be fine => sleep_time = duration / 20
- for i in range(20):
- ct = i/20 * gradient
- col_show[:,:,...] = [int(x) for x in ct+start_color]
- self.clock.IO.put(col_show)
- time.sleep(int(duration) / 20)
-
- self.clock.run(output,(duration,))
- return ConversationHandler.END
-
-
- def exec_alarm_blink(self, update: Update, context: CallbackContext) -> None:
- duration = self.additional_argument
- frequency = update.message.text
-
- def output(duration, frequency):
- self.clock.set_brightness(value=1)
- duration = int(duration)
- frequency = int(frequency)
- n = duration * frequency / 2
- empty = numpy.zeros((*self.clock.shape,3))
- red = empty.copy()
- red[...,0] = 255
- for i in range(int(n)):
- self.clock.IO.put(red)
- time.sleep(1/frequency)
- self.clock.IO.put(empty)
- time.sleep(1/frequency)
-
- if not(duration == 0 or frequency == 0):
- update.message.reply_text("Now blinking")
- self.clock.run(output,(duration, frequency))
- return ConversationHandler.END
-
-
-
- def exec_show_image(self, update: Update, context: CallbackContext) -> None:
- duration = self.additional_argument
- i = update.message.photo
- img = update.message.photo[0]
- bot = img.bot
- id = img.file_id
-
- file = bot.getFile(id).download_as_bytearray()
- width = self.clock.shape[1]
- height = self.clock.shape[0]
-
- img = Image.open(io.BytesIO(file))
- im_height = img.height
- im_width = img.width
-
- scalex = im_width // width
- scaley = im_height // height
- scale = min(scalex, scaley)
-
- t = img.resize((width, height),box=(0,0,width*scale,height*scale))
- a = numpy.asarray(t)
-
- def output(image, duration):
- self.clock.IO.put(image)
- time.sleep(int(duration) * 60)
-
- self.clock.run(output,(a, duration))
- return ConversationHandler.END
-
-
- def exec_show_message(self, update: Update, context: CallbackContext) -> None:
- message_str = update.message.text
- update.message.reply_text("Now showing: " + message_str)
- self.clock.run(self.clock.text_scroll,(message_str,))
- return ConversationHandler.END
-
-
- def exec_art_gallery(self, update: Update, context: CallbackContext) -> None:
- duration = float(self.additional_argument)
- number = int(update.message.text)
-
- def output(number, duration):
- for i in range(number):
- img = self.api_art.get_random_art() # returns an PIL.Image object
- im_height = img.height
- im_width = img.width
-
- width = self.clock.shape[1]
- height = self.clock.shape[0]
-
- scalex = im_width // width
- scaley = im_height // height
- scale = min(scalex, scaley)
-
- t = img.resize((width, height),box=(0,0,width*scale,height*scale))
- a = numpy.asarray(t)
- self.clock.IO.put(a)
-
- time.sleep(duration*3600 / number)
-
-
- update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.")
- self.clock.run(output,(number, duration))
- return ConversationHandler.END
-
-
-
-
-
-
-
+from .template import *
+import time
+import numpy
+from PIL import Image
+import io
+
+CHOOSE, ADDARG = range(2)
+MESSAGE, WAKE, ALARM, IMAGE, ART = range(3,8)
+
+class Clock(BotFunc):
+ """pass on commands to clock-module"""
+ def __init__(self, prst, clock_module, art_api):
+ super().__init__(prst)
+ self.clock = clock_module
+ self.api_art = art_api
+
+ def create_handler(self):
+ handler = ConversationHandler(
+ entry_points=[CommandHandler("clock", self.entry_point)],
+ states={
+ CHOOSE : [
+ CallbackQueryHandler(self.wake_light, pattern="^wake$"),
+ CallbackQueryHandler(self.alarm_blink, pattern="^alarm$"),
+ CallbackQueryHandler(self.show_message, pattern="^message$"),
+ CallbackQueryHandler(self.show_image, pattern="^image$"),
+ CallbackQueryHandler(self.art_gallery, pattern="^gallery$"),
+ ],
+ ADDARG : [MessageHandler(Filters.text, callback=self.get_arg1)],
+ MESSAGE: [MessageHandler(Filters.text, callback=self.exec_show_message)],
+ WAKE : [MessageHandler(Filters.text, callback=self.exec_wake_light)],
+ ALARM : [MessageHandler(Filters.text, callback=self.exec_alarm_blink)],
+ IMAGE : [MessageHandler(Filters.photo, callback=self.exec_show_image)],
+ ART : [MessageHandler(Filters.text, callback=self.exec_art_gallery)],
+ },
+ fallbacks=[CommandHandler('clock', self.entry_point)],
+ )
+ return handler
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ keyboard = [
+ [InlineKeyboardButton("Make a wake-light", callback_data="wake")],
+ [InlineKeyboardButton("Blink as alarm", callback_data="alarm")],
+ [InlineKeyboardButton("Show a message", callback_data="message")],
+ [InlineKeyboardButton("Show an image", callback_data="image")],
+ [InlineKeyboardButton("Art gallery!", callback_data="gallery")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
+ return CHOOSE
+
+
+ def wake_light(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ query.edit_message_text("Ok. How long should the color cycle last? (In seconds)")
+ return WAKE
+
+ def alarm_blink(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ query.edit_message_text("Ok. How long should it blink? (In seconds)")
+ self.next_state = {ALARM : "What frequency (Hertz)"}
+ return ADDARG
+
+ def show_message(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ query.edit_message_text("Ok. What message will I show?")
+ return MESSAGE
+
+ def show_image(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ query.edit_message_text("How long (in minutes) should the image be displayed?")
+ self.next_state = {IMAGE : "Please send me the photo to display."}
+ return ADDARG
+
+ def art_gallery(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ 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?"}
+ return ADDARG
+
+ def get_arg1(self, update: Update, context: CallbackContext) -> None:
+ a = update.message.text
+ self.additional_argument = a
+ update.message.reply_text("Furthermore: "+ list(self.next_state.values())[0])
+ return list(self.next_state.keys())[0]
+
+
+
+
+###### actually running clock actions
+ def exec_wake_light(self, update: Update, context: CallbackContext) -> None:
+ duration = update.message.text
+
+ def output(duration):
+ self.clock.set_brightness(value=1)
+ start_color = numpy.array([153, 0, 51])
+ end_color = numpy.array([255, 255, 0])
+ col_show = numpy.zeros((*self.clock.shape, 3))
+ col_show[:,:,...] = start_color
+
+ gradient = end_color - start_color
+ # 20 steps should be fine => sleep_time = duration / 20
+ for i in range(20):
+ ct = i/20 * gradient
+ col_show[:,:,...] = [int(x) for x in ct+start_color]
+ self.clock.IO.put(col_show)
+ time.sleep(int(duration) / 20)
+
+ self.clock.run(output,(duration,))
+ return ConversationHandler.END
+
+
+ def exec_alarm_blink(self, update: Update, context: CallbackContext) -> None:
+ duration = self.additional_argument
+ frequency = update.message.text
+
+ def output(duration, frequency):
+ self.clock.set_brightness(value=1)
+ duration = int(duration)
+ frequency = int(frequency)
+ n = duration * frequency / 2
+ empty = numpy.zeros((*self.clock.shape,3))
+ red = empty.copy()
+ red[...,0] = 255
+ for i in range(int(n)):
+ self.clock.IO.put(red)
+ time.sleep(1/frequency)
+ self.clock.IO.put(empty)
+ time.sleep(1/frequency)
+
+ if not(duration == 0 or frequency == 0):
+ update.message.reply_text("Now blinking")
+ self.clock.run(output,(duration, frequency))
+ return ConversationHandler.END
+
+
+
+ def exec_show_image(self, update: Update, context: CallbackContext) -> None:
+ duration = self.additional_argument
+ i = update.message.photo
+ img = update.message.photo[0]
+ bot = img.bot
+ id = img.file_id
+
+ file = bot.getFile(id).download_as_bytearray()
+ width = self.clock.shape[1]
+ height = self.clock.shape[0]
+
+ img = Image.open(io.BytesIO(file))
+ im_height = img.height
+ im_width = img.width
+
+ scalex = im_width // width
+ scaley = im_height // height
+ scale = min(scalex, scaley)
+
+ t = img.resize((width, height),box=(0,0,width*scale,height*scale))
+ a = numpy.asarray(t)
+
+ def output(image, duration):
+ self.clock.IO.put(image)
+ time.sleep(int(duration) * 60)
+
+ self.clock.run(output,(a, duration))
+ return ConversationHandler.END
+
+
+ def exec_show_message(self, update: Update, context: CallbackContext) -> None:
+ message_str = update.message.text
+ update.message.reply_text("Now showing: " + message_str)
+ self.clock.run(self.clock.text_scroll,(message_str,))
+ return ConversationHandler.END
+
+
+ def exec_art_gallery(self, update: Update, context: CallbackContext) -> None:
+ duration = float(self.additional_argument)
+ number = int(update.message.text)
+
+ def output(number, duration):
+ for i in range(number):
+ img = self.api_art.get_random_art() # returns an PIL.Image object
+ im_height = img.height
+ im_width = img.width
+
+ width = self.clock.shape[1]
+ height = self.clock.shape[0]
+
+ scalex = im_width // width
+ scaley = im_height // height
+ scale = min(scalex, scaley)
+
+ t = img.resize((width, height),box=(0,0,width*scale,height*scale))
+ a = numpy.asarray(t)
+ self.clock.IO.put(a)
+
+ time.sleep(duration*3600 / number)
+
+
+ update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.")
+ self.clock.run(output,(number, duration))
+ return ConversationHandler.END
+
+
+
+
+
+
+
# TODO FIx this to work with the new backend
\ No newline at end of file
diff --git a/bot/commands/help.py b/bot/commands/help.py
index 6f7ccc6..a5291a0 100644
--- a/bot/commands/help.py
+++ b/bot/commands/help.py
@@ -1,128 +1,128 @@
-from .template import *
-
-FIRST, EXECUTE = range(2)
-
-
-class Help(BotFunc):
- """Shows the functions and their usage"""
-
- def __init__(self, db):
- super().__init__(db)
- self.available_commands = {}
-
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('help', self.entry_point)],
- states={
- FIRST: [
- CallbackQueryHandler(self.print_all, pattern="^all$"),
- CallbackQueryHandler(self.choose_specific, pattern="^specific$"),
- CallbackQueryHandler(self.print_one, pattern='func-'),
- ],
- EXECUTE :[CallbackQueryHandler(self.execute_now)],
- # ConversationHandler.TIMEOUT : [
- # CallbackQueryHandler(self.timeout)
- # ]
- },
- fallbacks=[CommandHandler('help', self.entry_point)],
- conversation_timeout=15,
- )
- return conv_handler
-
- def add_commands(self, commands):
- # commands is a dict {"name": class}
- for k in commands:
- if k != "plaintext":
- self.available_commands[k] = commands[k].__doc__
-
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
-
- keyboard = [
- [
- InlineKeyboardButton("All commands", callback_data="all"),
- InlineKeyboardButton("Just one", callback_data="specific"),
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
- if update.message:
- update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
- else:
- update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup)
- return FIRST
-
-
- def print_all(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
- all_cmd = ""
- for h in self.available_commands:
- 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)
- return ConversationHandler.END
-
-
- def choose_specific(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
-
- keyboard = [[InlineKeyboardButton(k, callback_data="func-" + k)] for k in self.available_commands]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- query.edit_message_text(
- text="What command should be printed?", reply_markup=reply_markup
- )
- return FIRST
-
-
- def print_one(self, update: Update, context: CallbackContext) -> None:
- """Show new choice of buttons"""
- query = update.callback_query
- name = query.data.replace("func-", "")
- query.answer()
-
- message = name + ": `" + self.available_commands[name] + "`"
-
- keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- query.edit_message_text(
- text= message,
- reply_markup = reply_markup,
- parse_mode = ParseMode.MARKDOWN_V2
- )
- return EXECUTE
-
-
- def execute_now(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- name = query.data
- query.answer()
- funcs = context.dispatcher.handlers[0]
- for func in funcs:
- if name == func.entry_points[0].command[0]:
- break
- callback = func.entry_points[0].handle_update
- callback(update, context.dispatcher, check_result=True, context=context)
- return ConversationHandler.END
-
-
- def timeout(self, update: Update, context: CallbackContext) -> None:
- """For dying conversation. Currently unused."""
-
- query = update.callback_query
- name = query.data.replace("func-", "")
- query.answer()
-
- message = name + ": `" + self.available_commands[name] + "`"
- query.edit_message_text(
- text= "Timed out...",
- parse_mode = ParseMode.MARKDOWN_V2
- )
+from .template import *
+
+FIRST, EXECUTE = range(2)
+
+
+class Help(BotFunc):
+ """Shows the functions and their usage"""
+
+ def __init__(self, db):
+ super().__init__(db)
+ self.available_commands = {}
+
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('help', self.entry_point)],
+ states={
+ FIRST: [
+ CallbackQueryHandler(self.print_all, pattern="^all$"),
+ CallbackQueryHandler(self.choose_specific, pattern="^specific$"),
+ CallbackQueryHandler(self.print_one, pattern='func-'),
+ ],
+ EXECUTE :[CallbackQueryHandler(self.execute_now)],
+ # ConversationHandler.TIMEOUT : [
+ # CallbackQueryHandler(self.timeout)
+ # ]
+ },
+ fallbacks=[CommandHandler('help', self.entry_point)],
+ conversation_timeout=15,
+ )
+ return conv_handler
+
+ def add_commands(self, commands):
+ # commands is a dict {"name": class}
+ for k in commands:
+ if k != "plaintext":
+ self.available_commands[k] = commands[k].__doc__
+
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+
+ keyboard = [
+ [
+ InlineKeyboardButton("All commands", callback_data="all"),
+ InlineKeyboardButton("Just one", callback_data="specific"),
+ ]
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
+ if update.message:
+ update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
+ else:
+ update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup)
+ return FIRST
+
+
+ def print_all(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+ all_cmd = ""
+ for h in self.available_commands:
+ 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)
+ return ConversationHandler.END
+
+
+ def choose_specific(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+
+ keyboard = [[InlineKeyboardButton(k, callback_data="func-" + k)] for k in self.available_commands]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ query.edit_message_text(
+ text="What command should be printed?", reply_markup=reply_markup
+ )
+ return FIRST
+
+
+ def print_one(self, update: Update, context: CallbackContext) -> None:
+ """Show new choice of buttons"""
+ query = update.callback_query
+ name = query.data.replace("func-", "")
+ query.answer()
+
+ message = name + ": `" + self.available_commands[name] + "`"
+
+ keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ query.edit_message_text(
+ text= message,
+ reply_markup = reply_markup,
+ parse_mode = ParseMode.MARKDOWN_V2
+ )
+ return EXECUTE
+
+
+ def execute_now(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ name = query.data
+ query.answer()
+ funcs = context.dispatcher.handlers[0]
+ for func in funcs:
+ if name == func.entry_points[0].command[0]:
+ break
+ callback = func.entry_points[0].handle_update
+ callback(update, context.dispatcher, check_result=True, context=context)
+ return ConversationHandler.END
+
+
+ def timeout(self, update: Update, context: CallbackContext) -> None:
+ """For dying conversation. Currently unused."""
+
+ query = update.callback_query
+ name = query.data.replace("func-", "")
+ query.answer()
+
+ message = name + ": `" + self.available_commands[name] + "`"
+ query.edit_message_text(
+ text= "Timed out...",
+ parse_mode = ParseMode.MARKDOWN_V2
+ )
return ConversationHandler.END
\ No newline at end of file
diff --git a/bot/commands/lists.py b/bot/commands/lists.py
index 5355701..ea4a1d4 100644
--- a/bot/commands/lists.py
+++ b/bot/commands/lists.py
@@ -1,200 +1,198 @@
-from .template import *
-
-import datetime
-import requests
-
-NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
-
-
-class Lists(BotFunc):
- """Create and edit lists"""
-
- def __init__(self, db):
- super().__init__(db)
- self.current_name = ""
-
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('list', self.entry_point)],
- states={
- NAME: [
- CallbackQueryHandler(self.choose_list, pattern="^list-"),
- CallbackQueryHandler(self.new_list, pattern="^new$"),
- ],
- NEW : [MessageHandler(Filters.text, callback=self.new_listname)],
- ACTION: [
- CallbackQueryHandler(self.list_add, pattern="^add$"),
- CallbackQueryHandler(self.list_remove, pattern="^remove$"),
- CallbackQueryHandler(self.list_clear, pattern="^clear$"),
- CallbackQueryHandler(self.list_delete, pattern="^delete$"),
- CallbackQueryHandler(self.list_print, pattern="^print$"),
- CallbackQueryHandler(self.list_menu, pattern="^overview$"),
- ],
- ITEMADD : [MessageHandler(Filters.text, callback=self.list_add_item)],
- ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)]
- },
- fallbacks=[CommandHandler('list', self.entry_point)],
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- lists = self.db.lists.select()
- sl = [l.name for l in lists]
- keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]
-
- reply_markup = InlineKeyboardMarkup(keyboard)
- super().log_activity(read=True, execute=False, send=True)
- 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
- data = query.data
- name = data.replace("list-","")
- query.answer()
- self.current_name = name
-
- keyboard = [
- [InlineKeyboardButton("Add item", callback_data="add")],
- [InlineKeyboardButton("Remove item", callback_data="remove")],
- [InlineKeyboardButton("Clear list", callback_data="clear")],
- [InlineKeyboardButton("Print list", callback_data="print")],
- [InlineKeyboardButton("Delete list", callback_data="delete")],
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- 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
- query.answer()
-
- keyboard = [
- [InlineKeyboardButton("Add item", callback_data="add")],
- [InlineKeyboardButton("Remove item", callback_data="remove")],
- [InlineKeyboardButton("Clear list", callback_data="clear")],
- [InlineKeyboardButton("Print list", callback_data="print")],
- [InlineKeyboardButton("Delete list", callback_data="delete")],
- ]
- 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
-
-
- def new_list(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
- 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
- try:
- data = self.db.lists(name=name, content="")
- data.save()
- keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
- reply_markup = InlineKeyboardMarkup(keyboard)
- self.current_name = name
- update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
- return ACTION
- except Exception as e:
- 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
- query.answer()
- 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
- query.answer()
- 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)
-
- 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
- query.answer()
- self.db.lists.update(content="").where(self.db.lists.name == self.current_name).execute()
- keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
- reply_markup = InlineKeyboardMarkup(keyboard)
- 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
- query.answer()
- self.db.lists.delete().where(self.db.lists.name == self.current_name).execute()
- 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
- query.answer()
- it = self.db.lists.get(self.db.lists.name == self.current_name)
- if it:
- content = it.content.split("<-->")
- content = "\n".join(content)
- else:
- content = "List empty"
-
- keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
- reply_markup = InlineKeyboardMarkup(keyboard)
- 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
- it = self.db.lists.get(self.db.lists.name == self.current_name)
- if it:
- sl = it.content
- else:
- sl = ""
- 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)
- 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
- ind = int(query.data)
- query.answer()
-
- it = self.db.lists.get(self.db.lists.name == self.current_name)
- old = it.content.split("<-->")
- # todo make better
-
- name = old.pop(ind)
- 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)
-
- query.edit_message_text("Removed " + name, reply_markup=reply_markup)
- return ACTION
+from .template import *
+
+NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
+
+
+class Lists(BotFunc):
+ """Create and edit lists"""
+
+ def __init__(self, db):
+ super().__init__(db)
+ self.current_name = ""
+
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('list', self.entry_point)],
+ states={
+ NAME: [
+ CallbackQueryHandler(self.choose_list, pattern="^list-"),
+ CallbackQueryHandler(self.new_list, pattern="^new$"),
+ ],
+ NEW : [MessageHandler(Filters.text, callback=self.new_listname)],
+ ACTION: [
+ CallbackQueryHandler(self.list_add, pattern="^add$"),
+ CallbackQueryHandler(self.list_remove, pattern="^remove$"),
+ CallbackQueryHandler(self.list_clear, pattern="^clear$"),
+ CallbackQueryHandler(self.list_delete, pattern="^delete$"),
+ CallbackQueryHandler(self.list_print, pattern="^print$"),
+ CallbackQueryHandler(self.list_menu, pattern="^overview$"),
+ ],
+ ITEMADD : [MessageHandler(Filters.text, callback=self.list_add_item)],
+ ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)]
+ },
+ 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
+ lists = self.db.lists.select()
+ sl = [l.name for l in lists]
+ keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]]
+
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ super().log_activity(read=True, execute=False, send=True)
+ 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
+ data = query.data
+ name = data.replace("list-","")
+ query.answer()
+ self.current_name = name
+
+ keyboard = [
+ [InlineKeyboardButton("Add item", callback_data="add")],
+ [InlineKeyboardButton("Remove item", callback_data="remove")],
+ [InlineKeyboardButton("Clear list", callback_data="clear")],
+ [InlineKeyboardButton("Print list", callback_data="print")],
+ [InlineKeyboardButton("Delete list", callback_data="delete")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ 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
+ query.answer()
+
+ keyboard = [
+ [InlineKeyboardButton("Add item", callback_data="add")],
+ [InlineKeyboardButton("Remove item", callback_data="remove")],
+ [InlineKeyboardButton("Clear list", callback_data="clear")],
+ [InlineKeyboardButton("Print list", callback_data="print")],
+ [InlineKeyboardButton("Delete list", callback_data="delete")],
+ ]
+ 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
+
+
+ def new_list(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+ 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
+ try:
+ data = self.db.lists(name=name, content="")
+ data.save()
+ keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ self.current_name = name
+ update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
+ return ACTION
+ except Exception as e:
+ 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
+ query.answer()
+ 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
+ query.answer()
+ 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)
+
+ 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
+ query.answer()
+ self.db.lists.update(content="").where(self.db.lists.name == self.current_name).execute()
+ keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ 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
+ query.answer()
+ self.db.lists.delete().where(self.db.lists.name == self.current_name).execute()
+ 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
+ query.answer()
+ it = self.db.lists.get(self.db.lists.name == self.current_name)
+ if it:
+ content = it.content.split("<-->")
+ content = "\n".join(content)
+ else:
+ content = "List empty"
+
+ keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ 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
+ it = self.db.lists.get(self.db.lists.name == self.current_name)
+ if it:
+ sl = it.content
+ else:
+ sl = ""
+ 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)
+ 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
+ ind = int(query.data)
+ query.answer()
+
+ it = self.db.lists.get(self.db.lists.name == self.current_name)
+ old = it.content.split("<-->")
+ # todo make better
+
+ name = old.pop(ind)
+ 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)
+
+ query.edit_message_text("Removed " + name, reply_markup=reply_markup)
+ return ACTION
diff --git a/bot/commands/plaintext.py b/bot/commands/plaintext.py
index 60e4942..9cc1e74 100644
--- a/bot/commands/plaintext.py
+++ b/bot/commands/plaintext.py
@@ -1,19 +1,19 @@
-from .template import *
-
-
-class Plain(BotFunc):
- """Not a command: just keeps logs and usage_data"""
- def __init__(self, db):
- super().__init__(db)
-
- def create_handler(self):
- h = MessageHandler(Filters.text, callback=self.add_to_log)
- return h
-
- def add_to_log(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- super().log_activity(
- read = True,
- send = False,
- execute = False
- )
+from .template import *
+
+
+class Plain(BotFunc):
+ """Not a command: just keeps logs and usage_data"""
+ def __init__(self, db):
+ super().__init__(db)
+
+ def create_handler(self):
+ h = MessageHandler(Filters.text, callback=self.add_to_log)
+ return h
+
+ def add_to_log(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ super().log_activity(
+ read = True,
+ send = False,
+ execute = False
+ )
diff --git a/bot/commands/reddit.py b/bot/commands/reddit.py
index cab6dc4..00fd1e3 100644
--- a/bot/commands/reddit.py
+++ b/bot/commands/reddit.py
@@ -1,179 +1,179 @@
-from re import U
-from .template import *
-
-
-CHOOSE_NUM = 1
-class Joke(BotFunc):
- """Tells a joke from reddit."""
-
- def __init__(self, api, db):
- super().__init__(db)
- self.available_commands = {}
- self.api = api
-
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('joke', self.entry_point)],
- states={
- CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),],
- },
- fallbacks=[CommandHandler('joke', self.entry_point)],
- # conversation_timeout=5,
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]]
- reply_markup = InlineKeyboardMarkup(keyboard)
- 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)
- return CHOOSE_NUM
-
-
- def get_jokes(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- number = int(query.data)
- query.answer()
- jokes = self.api.get_random_rising("jokes", number, "text")
- # formating
- message = ""
- for j in jokes:
- message += "" + j["title"] + " \n" + j["content"] + "\n\n"
- if message == "":
- message += "Could not fetch jokes."
- query.edit_message_text(text = message, parse_mode = ParseMode.HTML)
- return ConversationHandler.END
-
-
-
-
-CHOOSE_TOPIC = 0
-class Meme(BotFunc):
- """Gets the latest memes from reddit"""
-
- def __init__(self, api, db):
- super().__init__(db)
- self.available_commands = {}
- self.api = api
-
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('meme', self.entry_point)],
- states={
- CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
- CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)],
- },
- fallbacks=[CommandHandler('meme', self.entry_point)],
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
-
- keyboard = [
- [InlineKeyboardButton("General", callback_data="memes"),],
- [InlineKeyboardButton("Dank memes", callback_data="dankmemes"),],
- [InlineKeyboardButton("Maths", callback_data="mathmemes"),],
- [InlineKeyboardButton("Physics", callback_data="physicsmemes"),],
- [InlineKeyboardButton("Biology", callback_data="biologymemes"),],
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- 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)
- return CHOOSE_TOPIC
-
-
- def choose_topic(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- d = query.data
- query.answer()
-
- keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
- reply_markup = InlineKeyboardMarkup(keyboard)
- query.edit_message_text("How many memes?", reply_markup=reply_markup)
- return CHOOSE_NUM
-
-
- def get_memes(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- data = query.data.split("-")
- query.answer()
-
- memes = self.api.get_random_rising(data[0], int(data[1]), "photo")
- if len(memes) != 0:
- for m in memes:
- 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"])
- else:
- update.effective_chat.send_message("Sorry, the meme won't yeet.")
- return ConversationHandler.END
-
-
-
-
-# class News(BotFunc):
-# """Gets the latest news from reddit"""
-
-# def __init__(self, api, prst):
-# super().__init__(prst)
-# self.available_commands = {}
-# self.api = api
-
-
-# def create_handler(self):
-# conv_handler = ConversationHandler(
-# entry_points=[CommandHandler('news', self.entry_point)],
-# states={
-# CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
-# CHOOSE_NUM :[CallbackQueryHandler(self.get_news)],
-# },
-# fallbacks=[CommandHandler('news', self.entry_point)],
-# )
-# return conv_handler
-
-
-# def entry_point(self, update: Update, context: CallbackContext) -> None:
-# super().entry_point()
-
-# keyboard = [
-# [InlineKeyboardButton("World", callback_data="worldnews"),],
-# [InlineKeyboardButton("Germany", callback_data="germannews"),],
-# [InlineKeyboardButton("France", callback_data="francenews"),],
-# [InlineKeyboardButton("Europe", callback_data="eunews"),],
-# [InlineKeyboardButton("USA", callback_data="usanews"),],
-# ]
-# reply_markup = InlineKeyboardMarkup(keyboard)
-# update.message.reply_text("What kind of news?", reply_markup=reply_markup)
-# return CHOOSE_TOPIC
-
-
-# def choose_topic(self, update: Update, context: CallbackContext) -> None:
-# super().entry_point()
-# query = update.callback_query
-# d = query.data
-# query.answer()
-
-# keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
-# reply_markup = InlineKeyboardMarkup(keyboard)
-# query.edit_message_text("How many entries?", reply_markup=reply_markup)
-# return CHOOSE_NUM
-
-
-# def get_news(self, update: Update, context: CallbackContext) -> None:
-# query = update.callback_query
-# data = query.data.split("-")
-# query.answer()
-# #try:
-# news = self.api.get_top(data[0], data[1], "text")
-# # formating
-# message = ""
-# for j in news:
-# message += "" + j["title"] + " \n" + j["content"] + "\n\n"
-# if message == "":
-# message += "Could not fetch news."
-# query.edit_message_text(news, paresemode=ParseMode.HTML)
+from re import U
+from .template import *
+
+
+CHOOSE_NUM = 1
+class Joke(BotFunc):
+ """Tells a joke from reddit."""
+
+ def __init__(self, api, db):
+ super().__init__(db)
+ self.available_commands = {}
+ self.api = api
+
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('joke', self.entry_point)],
+ states={
+ CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),],
+ },
+ fallbacks=[CommandHandler('joke', self.entry_point)],
+ # conversation_timeout=5,
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ 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)
+ return CHOOSE_NUM
+
+
+ def get_jokes(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ number = int(query.data)
+ query.answer()
+ jokes = self.api.get_random_rising("jokes", number, "text")
+ # formating
+ message = ""
+ for j in jokes:
+ message += "" + j["title"] + " \n" + j["content"] + "\n\n"
+ if message == "":
+ message += "Could not fetch jokes."
+ query.edit_message_text(text = message, parse_mode = ParseMode.HTML)
+ return ConversationHandler.END
+
+
+
+
+CHOOSE_TOPIC = 0
+class Meme(BotFunc):
+ """Gets the latest memes from reddit"""
+
+ def __init__(self, api, db):
+ super().__init__(db)
+ self.available_commands = {}
+ self.api = api
+
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('meme', self.entry_point)],
+ states={
+ CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
+ CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)],
+ },
+ fallbacks=[CommandHandler('meme', self.entry_point)],
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+
+ keyboard = [
+ [InlineKeyboardButton("General", callback_data="memes"),],
+ [InlineKeyboardButton("Dank memes", callback_data="dankmemes"),],
+ [InlineKeyboardButton("Maths", callback_data="mathmemes"),],
+ [InlineKeyboardButton("Physics", callback_data="physicsmemes"),],
+ [InlineKeyboardButton("Biology", callback_data="biologymemes"),],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ 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)
+ return CHOOSE_TOPIC
+
+
+ def choose_topic(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ d = query.data
+ query.answer()
+
+ keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ query.edit_message_text("How many memes?", reply_markup=reply_markup)
+ return CHOOSE_NUM
+
+
+ def get_memes(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ data = query.data.split("-")
+ query.answer()
+
+ memes = self.api.get_random_rising(data[0], int(data[1]), "photo")
+ if len(memes) != 0:
+ for m in memes:
+ 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"])
+ else:
+ update.effective_chat.send_message("Sorry, the meme won't yeet.")
+ return ConversationHandler.END
+
+
+
+
+# class News(BotFunc):
+# """Gets the latest news from reddit"""
+
+# def __init__(self, api, prst):
+# super().__init__(prst)
+# self.available_commands = {}
+# self.api = api
+
+
+# def create_handler(self):
+# conv_handler = ConversationHandler(
+# entry_points=[CommandHandler('news', self.entry_point)],
+# states={
+# CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
+# CHOOSE_NUM :[CallbackQueryHandler(self.get_news)],
+# },
+# fallbacks=[CommandHandler('news', self.entry_point)],
+# )
+# return conv_handler
+
+
+# def entry_point(self, update: Update, context: CallbackContext) -> None:
+# super().entry_point()
+
+# keyboard = [
+# [InlineKeyboardButton("World", callback_data="worldnews"),],
+# [InlineKeyboardButton("Germany", callback_data="germannews"),],
+# [InlineKeyboardButton("France", callback_data="francenews"),],
+# [InlineKeyboardButton("Europe", callback_data="eunews"),],
+# [InlineKeyboardButton("USA", callback_data="usanews"),],
+# ]
+# reply_markup = InlineKeyboardMarkup(keyboard)
+# update.message.reply_text("What kind of news?", reply_markup=reply_markup)
+# return CHOOSE_TOPIC
+
+
+# def choose_topic(self, update: Update, context: CallbackContext) -> None:
+# super().entry_point()
+# query = update.callback_query
+# d = query.data
+# query.answer()
+
+# keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
+# reply_markup = InlineKeyboardMarkup(keyboard)
+# query.edit_message_text("How many entries?", reply_markup=reply_markup)
+# return CHOOSE_NUM
+
+
+# def get_news(self, update: Update, context: CallbackContext) -> None:
+# query = update.callback_query
+# data = query.data.split("-")
+# query.answer()
+# #try:
+# news = self.api.get_top(data[0], data[1], "text")
+# # formating
+# message = ""
+# for j in news:
+# message += "" + j["title"] + " \n" + j["content"] + "\n\n"
+# if message == "":
+# message += "Could not fetch news."
+# query.edit_message_text(news, paresemode=ParseMode.HTML)
# return ConversationHandler.END
\ No newline at end of file
diff --git a/bot/commands/search.py b/bot/commands/search.py
index ae91270..9632d13 100644
--- a/bot/commands/search.py
+++ b/bot/commands/search.py
@@ -1,61 +1,61 @@
-from .template import *
-
-
-SEARCH, MORE = range(2)
-class Search(BotFunc):
- """Browse the web for a topic."""
-
- def __init__(self, api, db):
- super().__init__(db)
- self.available_commands = {}
- self.api = api
-
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('search', self.entry_point)],
- states={
- SEARCH: [MessageHandler(Filters.text, self.get_results),],
- MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),],
- },
- fallbacks=[CommandHandler('search', self.entry_point)],
- conversation_timeout=20,
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- update.message.reply_text("What are we searching?")
- return SEARCH
-
-
- def get_results(self, update: Update, context: CallbackContext) -> None:
- search = update.message.text
- results = self.api.get_result(search)
- keyboard = [[InlineKeyboardButton("More!", callback_data="more")]]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- # formating
- self.results = results
- if results:
- first = results[0]
- message = first["text"] + "\n(" + first["url"] + ")\n\n"
- else:
- message = "No results for search query."
- update.message.reply_text(text = message, reply_markup=reply_markup)
- super().log_activity(read = True, execute = True, send = True)
- return MORE
-
-
- def show_more(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- query.answer()
-
- message = ""
- for r in self.results:
- message += r["text"] + "\n(" + r["url"] + ")\n\n"
-
- query.edit_message_text(message)
- super().log_activity(read = False, execute = False, send = True)
+from .template import *
+
+
+SEARCH, MORE = range(2)
+class Search(BotFunc):
+ """Browse the web for a topic."""
+
+ def __init__(self, api, db):
+ super().__init__(db)
+ self.available_commands = {}
+ self.api = api
+
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('search', self.entry_point)],
+ states={
+ SEARCH: [MessageHandler(Filters.text, self.get_results),],
+ MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),],
+ },
+ fallbacks=[CommandHandler('search', self.entry_point)],
+ conversation_timeout=20,
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ update.message.reply_text("What are we searching?")
+ return SEARCH
+
+
+ def get_results(self, update: Update, context: CallbackContext) -> None:
+ search = update.message.text
+ results = self.api.get_result(search)
+ keyboard = [[InlineKeyboardButton("More!", callback_data="more")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ # formating
+ self.results = results
+ if results:
+ first = results[0]
+ message = first["text"] + "\n(" + first["url"] + ")\n\n"
+ else:
+ message = "No results for search query."
+ update.message.reply_text(text = message, reply_markup=reply_markup)
+ super().log_activity(read = True, execute = True, send = True)
+ return MORE
+
+
+ def show_more(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ query.answer()
+
+ message = ""
+ for r in self.results:
+ message += r["text"] + "\n(" + r["url"] + ")\n\n"
+
+ query.edit_message_text(message)
+ super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END
\ No newline at end of file
diff --git a/bot/commands/status.py b/bot/commands/status.py
index bbd4f34..2960511 100644
--- a/bot/commands/status.py
+++ b/bot/commands/status.py
@@ -1,105 +1,106 @@
-from .template import *
-
-import datetime
-import requests
-import socket
-import numpy as np
-import os
-import json
-
-
-FIRST = 1
-
-class Status(BotFunc):
- """Shows a short status of the program."""
-
- def __init__(self, name, version, db):
- super().__init__(db)
- self.start_time = datetime.datetime.now()
- self.name = name
- self.version = version
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('status', self.entry_point)],
- states={
- FIRST: [
- CallbackQueryHandler(self.send_log, pattern="^full$"),
- ]
- },
- fallbacks=[CommandHandler('status', self.entry_point)],
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- keyboard = [
- [
- InlineKeyboardButton("And the log?", callback_data="full"),
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- delta = str(datetime.datetime.now() - self.start_time)
- message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
-
- try:
- ip = requests.get('https://api.ipify.org').text
- with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
- s.connect(('8.8.8.8', 80))
- (addr, port) = s.getsockname()
- local_ips = addr
- except:
- ip = "not fetchable"
- local_ips = "not fetchable"
-
- message += "Status: Running 🟢\n"
- message += "Uptime: `" + delta[:delta.rfind(".")] + "`\n"
- # message += "Reboots: `" + str(self.persistence["global"]["reboots"]) + "`\n"
- message += "IP (public): `" + ip + "`\n"
- message += "IP (private): `" + str(local_ips) + "`\n"
- u = str(self.get_ngrok_url())
- message += "URL: [" + u + "](" + u + ")\n"
-
- 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_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)
- 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
-
-
- def send_log(self, update: Update, context: CallbackContext) -> None:
- query = update.callback_query
- wanted = query.data.replace("status-","")
- query.answer()
- with open("persistence/server.log") as l:
- query.message.reply_document(l)
-
- super().log_activity(read = False, execute = False, send = True)
- return ConversationHandler.END
-
-
- def get_ngrok_url(self):
- try:
- url = "http://localhost:4040/api/tunnels/"
- res = requests.get(url)
- res_unicode = res.content.decode("utf-8")
- res_json = json.loads(res_unicode)
- for i in res_json["tunnels"]:
- if i['name'] == 'command_line':
- return i['public_url']
- break
- except:
+from .template import *
+
+import datetime
+import requests
+import socket
+import numpy as np
+import os
+import json
+
+
+FIRST = 1
+
+class Status(BotFunc):
+ """Shows a short status of the program."""
+
+ def __init__(self, name, version, db):
+ super().__init__(db)
+ self.start_time = datetime.datetime.now()
+ self.name = name
+ self.version = version
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('status', self.entry_point)],
+ states={
+ FIRST: [
+ CallbackQueryHandler(self.send_log, pattern="^full$"),
+ ]
+ },
+ fallbacks=[CommandHandler('status', self.entry_point)],
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ keyboard = [
+ [
+ InlineKeyboardButton("And the log?", callback_data="full"),
+ ]
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ delta = str(datetime.datetime.now() - self.start_time)
+ message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
+
+ try:
+ ip = requests.get('https://api.ipify.org').text
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
+ s.connect(('8.8.8.8', 80))
+ (addr, port) = s.getsockname()
+ local_ips = addr
+ except:
+ ip = "not fetchable"
+ local_ips = "not fetchable"
+
+ message += "Status: Running 🟢\n"
+ message += "Uptime: `" + delta[:delta.rfind(".")] + "`\n"
+ # message += "Reboots: `" + str(self.persistence["global"]["reboots"]) + "`\n"
+ message += "IP (public): `" + ip + "`\n"
+ message += "IP (private): `" + str(local_ips) + "`\n"
+ u = str(self.get_ngrok_url())
+ message += "URL: [" + u + "](" + u + ")\n"
+
+ # TODO new DB
+ tot_r = self.db.chats.select().where(self.db.chats.read == True).count()
+ message += "Total messages read: `{}`\n".format(tot_r)
+
+ 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)
+
+ if update.message:
+ update.message.reply_text(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
+
+
+ def send_log(self, update: Update, context: CallbackContext) -> None:
+ query = update.callback_query
+ wanted = query.data.replace("status-","")
+ query.answer()
+ with open("persistence/server.log") as l:
+ query.message.reply_document(l)
+
+ super().log_activity(read = False, execute = False, send = True)
+ return ConversationHandler.END
+
+
+ def get_ngrok_url(self):
+ try:
+ url = "http://localhost:4040/api/tunnels/"
+ res = requests.get(url)
+ res_unicode = res.content.decode("utf-8")
+ res_json = json.loads(res_unicode)
+ for i in res_json["tunnels"]:
+ if i['name'] == 'command_line':
+ return i['public_url']
+ break
+ except:
return "Not available"
\ No newline at end of file
diff --git a/bot/commands/template.py b/bot/commands/template.py
index ad174e4..cc9d502 100644
--- a/bot/commands/template.py
+++ b/bot/commands/template.py
@@ -1,44 +1,43 @@
-import logging
-import datetime
-from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
-from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
-from telegram.ext import (
- Updater,
- CommandHandler,
- CallbackQueryHandler,
- ConversationHandler,
- CallbackContext,
-)
-
-
-import datetime
-
-
-class BotFunc():
- """Base class for a specific bot-functionality"""
- def __init__(self, db):
- self.logger = logging.getLogger(__name__)
- self.db = db
-
-
- def log_activity(self, **kwargs):
- # mark that a new command has been executed
- try:
- data = self.db.chats(
- time=datetime.datetime.now(),
- **kwargs
- )
- # kwargs can look like
- # receive=True,
- # execute=True,
- # send=False,
- data.save()
- except Exception as e:
- self.logger.error("sql error: {}".format(e))
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- if update.message.text:
- self.logger.info("Chat said: {}".format(update.message.text))
- else:
- self.logger.info("Chat said: {}".format(update.message))
-
+import logging
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
+from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
+from telegram.ext import (
+ Updater,
+ CommandHandler,
+ CallbackQueryHandler,
+ ConversationHandler,
+ CallbackContext,
+)
+
+
+import datetime
+
+
+class BotFunc():
+ """Base class for a specific bot-functionality"""
+ def __init__(self, db):
+ self.logger = logging.getLogger(__name__)
+ self.db = db
+
+
+ # def log_activity(self, **kwargs):
+ # # mark that a new command has been executed
+ # try:
+ # data = self.db.chats(
+ # time=datetime.datetime.now(),
+ # **kwargs
+ # )
+ # # kwargs can look like
+ # # receive=True,
+ # # execute=True,
+ # # send=False,
+ # data.save()
+ # except Exception as e:
+ # self.logger.error("sql error: {}".format(e))
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ if update.message.text:
+ self.logger.info("Chat said: {}".format(update.message.text))
+ else:
+ self.logger.info("Chat said: {}".format(update.message))
+
diff --git a/bot/commands/weather.py b/bot/commands/weather.py
index dea6789..0712893 100644
--- a/bot/commands/weather.py
+++ b/bot/commands/weather.py
@@ -1,117 +1,117 @@
-from .template import *
-
-import datetime
-
-FIRST = 1
-
-class Weather(BotFunc):
- """Shows a weatherforecast for a given location"""
- def __init__(self, api, db):
- """initialize api and persistence"""
- super().__init__(db)
- self.api = api
- self.city = ""
-
-
- def create_handler(self):
- """returns the handlers with button-logic"""
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('weather', self.entry_point)],
- states={
- FIRST: [
- CallbackQueryHandler(self.choose_city, pattern="^city-"),
- CallbackQueryHandler(self.choose_time, pattern="^time-"),
- ]
- },
- fallbacks=[CommandHandler('weather', self.entry_point)],
- )
-
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- """Reacts the call of the command. Prints the first buttons"""
- keyboard = [
- [
- InlineKeyboardButton("Zürich", callback_data="city-zurich"),
- InlineKeyboardButton("Freiburg", callback_data="city-freiburg"),
- InlineKeyboardButton("Mulhouse", callback_data="city-mulhouse"),
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- if update.message:
- update.message.reply_text("Which city?", reply_markup=reply_markup)
- else:
- update.callback_query.edit_message_text("Which city", reply_markup=reply_markup)
- return FIRST
-
-
- def choose_city(self, update: Update, context: CallbackContext) -> None:
- """Prompt same text & keyboard as `start` does but not as new message"""
- # Get CallbackQuery from Update
- query = update.callback_query
- data = query.data
- self.city = data.replace("city-","")
- query.answer()
- keyboard = [
- [
- InlineKeyboardButton("Now", callback_data="time-now"),
- InlineKeyboardButton("Tomorrow", callback_data="time-tomorrow"),
- InlineKeyboardButton("7 days", callback_data="time-7"),
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
- query.edit_message_text(
- text = "Which time?", reply_markup=reply_markup
- )
- return FIRST
-
-
- def choose_time(self, update: Update, context: CallbackContext) -> None:
- """Show new choice of buttons"""
- query = update.callback_query
- query.answer()
- forecast_time = query.data.replace("time-","")
- weather = self.get_weather(self.city, forecast_time)
- query.edit_message_text(
- text = "Weather: \n\n" + weather,
- parse_mode = ParseMode.HTML
- )
- super().log_activity(read = True, execute = True, send = True)
- return ConversationHandler.END
-
-
- def get_weather(self, city, forecast_time) -> None:
- """get the weather that matches the given params"""
- locations = {"freiburg": [47.9990, 7.8421], "zurich": [47.3769, 8.5417], "mulhouse": [47.7508, 7.3359]}
-
- 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",}
- days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
- today = datetime.datetime.today().weekday()
- weather = self.api.show_weather(city)
- message = ""
- if forecast_time == "now" or forecast_time == "7":
- now = weather.pop(0)
- message += "Now: " + categories[now["short"]] + "\n"
- message += "🌡" + str(now["temps"][0]) + "°\n\n"
- tod = weather.pop(0)
- message += "" + "Today" + ": " + categories[tod["short"]] + "\n"
- message += "🌡 ❄ " + str(tod["temps"][0]) + "° , 🌡 🔥 " + str(tod["temps"][1]) + "°\n\n"
-
- if forecast_time == "tomorrow" or forecast_time == "7":
- if forecast_time == "tomorrow": # previous statement was not executed: tomorrow is at weather[2]
- tom = weather.pop(2)
- else:
- tom = weather.pop(0)
- message += "" + "Tomorrow" + ": " + categories[tom["short"]] + "\n"
- message += "🌡 ❄ " + str(tom["temps"][0]) + "° , 🌡 🔥 " + str(tom["temps"][1]) + "°\n\n"
-
- if forecast_time == "7":
- for i, day in enumerate(weather):
- message += "" + days[(today + i + 2) % 7] + ": " + categories[day["short"]] + "\n"
- message += "🌡 ❄ " + str(day["temps"][0]) + "° , 🌡 🔥 " + str(day["temps"][1]) + "°\n\n"
-
- return message
+from .template import *
+
+import datetime
+
+FIRST = 1
+
+class Weather(BotFunc):
+ """Shows a weatherforecast for a given location"""
+ def __init__(self, api, db):
+ """initialize api and persistence"""
+ super().__init__(db)
+ self.api = api
+ self.city = ""
+
+
+ def create_handler(self):
+ """returns the handlers with button-logic"""
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('weather', self.entry_point)],
+ states={
+ FIRST: [
+ CallbackQueryHandler(self.choose_city, pattern="^city-"),
+ CallbackQueryHandler(self.choose_time, pattern="^time-"),
+ ]
+ },
+ fallbacks=[CommandHandler('weather', self.entry_point)],
+ )
+
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ """Reacts the call of the command. Prints the first buttons"""
+ keyboard = [
+ [
+ InlineKeyboardButton("Zürich", callback_data="city-zurich"),
+ InlineKeyboardButton("Freiburg", callback_data="city-freiburg"),
+ InlineKeyboardButton("Mulhouse", callback_data="city-mulhouse"),
+ ]
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ if update.message:
+ update.message.reply_text("Which city?", reply_markup=reply_markup)
+ else:
+ update.callback_query.edit_message_text("Which city", reply_markup=reply_markup)
+ return FIRST
+
+
+ def choose_city(self, update: Update, context: CallbackContext) -> None:
+ """Prompt same text & keyboard as `start` does but not as new message"""
+ # Get CallbackQuery from Update
+ query = update.callback_query
+ data = query.data
+ self.city = data.replace("city-","")
+ query.answer()
+ keyboard = [
+ [
+ InlineKeyboardButton("Now", callback_data="time-now"),
+ InlineKeyboardButton("Tomorrow", callback_data="time-tomorrow"),
+ InlineKeyboardButton("7 days", callback_data="time-7"),
+ ]
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ query.edit_message_text(
+ text = "Which time?", reply_markup=reply_markup
+ )
+ return FIRST
+
+
+ def choose_time(self, update: Update, context: CallbackContext) -> None:
+ """Show new choice of buttons"""
+ query = update.callback_query
+ query.answer()
+ forecast_time = query.data.replace("time-","")
+ weather = self.get_weather(self.city, forecast_time)
+ query.edit_message_text(
+ text = "Weather: \n\n" + weather,
+ parse_mode = ParseMode.HTML
+ )
+ super().log_activity(read = True, execute = True, send = True)
+ return ConversationHandler.END
+
+
+ def get_weather(self, city, forecast_time) -> None:
+ """get the weather that matches the given params"""
+ locations = {"freiburg": [47.9990, 7.8421], "zurich": [47.3769, 8.5417], "mulhouse": [47.7508, 7.3359]}
+
+ 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",}
+ days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
+ today = datetime.datetime.today().weekday()
+ weather = self.api.show_weather(city)
+ message = ""
+ if forecast_time == "now" or forecast_time == "7":
+ now = weather.pop(0)
+ message += "Now: " + categories[now["short"]] + "\n"
+ message += "🌡" + str(now["temps"][0]) + "°\n\n"
+ tod = weather.pop(0)
+ message += "" + "Today" + ": " + categories[tod["short"]] + "\n"
+ message += "🌡 ❄ " + str(tod["temps"][0]) + "° , 🌡 🔥 " + str(tod["temps"][1]) + "°\n\n"
+
+ if forecast_time == "tomorrow" or forecast_time == "7":
+ if forecast_time == "tomorrow": # previous statement was not executed: tomorrow is at weather[2]
+ tom = weather.pop(2)
+ else:
+ tom = weather.pop(0)
+ message += "" + "Tomorrow" + ": " + categories[tom["short"]] + "\n"
+ message += "🌡 ❄ " + str(tom["temps"][0]) + "° , 🌡 🔥 " + str(tom["temps"][1]) + "°\n\n"
+
+ if forecast_time == "7":
+ for i, day in enumerate(weather):
+ message += "" + days[(today + i + 2) % 7] + ": " + categories[day["short"]] + "\n"
+ message += "🌡 ❄ " + str(day["temps"][0]) + "° , 🌡 🔥 " + str(day["temps"][1]) + "°\n\n"
+
+ return message
diff --git a/bot/commands/zvv.py b/bot/commands/zvv.py
index 23a89d9..30d2b77 100644
--- a/bot/commands/zvv.py
+++ b/bot/commands/zvv.py
@@ -1,87 +1,87 @@
-from .template import *
-
-import datetime
-import requests
-
-START, DEST = range(2)
-
-class Zvv(BotFunc):
- """Connects to the swiss travel-api to get public transport routes"""
-
- def __init__(self, db):
- super().__init__(db)
- self.start = ""
- self.dest = ""
- pass
-
- def create_handler(self):
- conv_handler = ConversationHandler(
- entry_points=[CommandHandler('zvv', self.entry_point)],
- states={
- START: [MessageHandler(Filters.text, callback=self.get_start)],
- DEST: [MessageHandler(Filters.text, callback=self.get_dest)]
- },
- fallbacks=[CommandHandler('zvv', self.entry_point)],
- )
- return conv_handler
-
-
- def entry_point(self, update: Update, context: CallbackContext) -> None:
- super().entry_point(update, context)
- update.message.reply_text("What is the start point?")
- return START
-
-
- def get_start(self, update: Update, context: CallbackContext) -> None:
- loc = update.message.text
- self.start = loc
- update.message.reply_text("Ok. Going from " + loc + ", what is the destination?")
- return DEST
-
-
- def get_dest(self, update: Update, context: CallbackContext) -> None:
- loc = update.message.text
- self.dest = loc
- route = self.get_result()
- update.message.reply_text("Here are the routes I've got:\n" + route)
- super().log_activity(read=True, execute=True, send=True)
- return ConversationHandler.END
-
-
- def get_result(self):
- url = "http://transport.opendata.ch/v1/connections"
-
- start = self.start
- dest = self.dest
-
- data = {"from" : start, "to" : dest, "limit" : 2}
- try:
- routes = requests.get(url, params=data).json()
- result = routes["connections"]
- text = result[0]["from"]["station"]["name"] + " ⏩ " + result[0]["to"]["station"]["name"] + "\n\n"
- for con in result:
- 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 += "⏳ " + con["duration"] + "\n"
- text += "🗺️ Route:\n"
-
- for step in con["sections"]:
- 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 += "➡️ 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"
- else:
- text += "Walk."
- text += "\n"
- return text
- except:
- return "Invalid api call."
-
- def number_to_emoji(self, number):
- out = ""
- numbers = ["0️⃣","1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣"]
- for i in str(number):
- out += numbers[int(i)]
+from .template import *
+
+import datetime
+import requests
+
+START, DEST = range(2)
+
+class Zvv(BotFunc):
+ """Connects to the swiss travel-api to get public transport routes"""
+
+ def __init__(self, db):
+ super().__init__(db)
+ self.start = ""
+ self.dest = ""
+ pass
+
+ def create_handler(self):
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('zvv', self.entry_point)],
+ states={
+ START: [MessageHandler(Filters.text, callback=self.get_start)],
+ DEST: [MessageHandler(Filters.text, callback=self.get_dest)]
+ },
+ fallbacks=[CommandHandler('zvv', self.entry_point)],
+ )
+ return conv_handler
+
+
+ def entry_point(self, update: Update, context: CallbackContext) -> None:
+ super().entry_point(update, context)
+ update.message.reply_text("What is the start point?")
+ return START
+
+
+ def get_start(self, update: Update, context: CallbackContext) -> None:
+ loc = update.message.text
+ self.start = loc
+ update.message.reply_text("Ok. Going from " + loc + ", what is the destination?")
+ return DEST
+
+
+ def get_dest(self, update: Update, context: CallbackContext) -> None:
+ loc = update.message.text
+ self.dest = loc
+ route = self.get_result()
+ update.message.reply_text("Here are the routes I've got:\n" + route)
+ super().log_activity(read=True, execute=True, send=True)
+ return ConversationHandler.END
+
+
+ def get_result(self):
+ url = "http://transport.opendata.ch/v1/connections"
+
+ start = self.start
+ dest = self.dest
+
+ data = {"from" : start, "to" : dest, "limit" : 2}
+ try:
+ routes = requests.get(url, params=data).json()
+ result = routes["connections"]
+ text = result[0]["from"]["station"]["name"] + " ⏩ " + result[0]["to"]["station"]["name"] + "\n\n"
+ for con in result:
+ 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 += "⏳ " + con["duration"] + "\n"
+ text += "🗺️ Route:\n"
+
+ for step in con["sections"]:
+ 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 += "➡️ 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"
+ else:
+ text += "Walk."
+ text += "\n"
+ return text
+ except:
+ return "Invalid api call."
+
+ def number_to_emoji(self, number):
+ out = ""
+ numbers = ["0️⃣","1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣"]
+ for i in str(number):
+ out += numbers[int(i)]
return str(out)
\ No newline at end of file
diff --git a/bot/main.py b/bot/main.py
index bc20127..5713f60 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -1,68 +1,68 @@
-from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
-
-from . import api, commands
-
-import logging
-logger = logging.getLogger(__name__)
-
-class ChatBot():
- """better framwork - unites all functions"""
-
- def __init__(self, name, version):
- """Inits the Bot with a few conf. vars
- Args: -> name:str - Name of the bot
- -> version:str - Version number
- -> hw_commands - dict with commands executable by the clock module
- -> prst:dict - persistence (overloaded dict that writes to json file)
- -> logger - logging object to unify log messages
- """
- # added by the launcher, we have self.modules (dict) and persistence and db
-
- self.name = name
- self.version = version
-
- # Import submodules
- self.api_weather = api.weather.WeatherFetch(api.keys.weather_api)
- self.api_reddit = api.reddit.RedditFetch(api.keys.reddit_api)
- self.api_search = api.search.WebSearch()
- self.api_art = api.metmuseum.ArtFetch()
- # and so on
-
- self.telegram = Updater(api.keys.telegram_api, use_context=True)
- self.dispatcher = self.telegram.dispatcher
- self.commands = commands
-
-
-
- def add_commands(self):
- # Mark modules as available
- db = self.db
- self.help_module = self.commands.help.Help(db)
- self.sub_modules = {
- "weather": self.commands.weather.Weather(self.api_weather, db),
- "help" : self.help_module,
- "status" : self.commands.status.Status(self.name, self.version, db),
- "zvv" : self.commands.zvv.Zvv(db),
- "list" : self.commands.lists.Lists(db),
- # "alias" : commands.alias.Alias(self.dispatcher, db),
- "joke" : self.commands.reddit.Joke(self.api_reddit, db),
- "meme" : self.commands.reddit.Meme(self.api_reddit, db),
- # "news" : self.commands.reddit.News(self.api_reddit, 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
- }
- # must be a class that has a method create_handler
-
- for k in self.sub_modules:
- self.dispatcher.add_handler(self.sub_modules[k].create_handler())
-
- self.help_module.add_commands(self.sub_modules)
-
- def start(self):
- self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)}
- self.add_commands()
- self.telegram.start_polling(
- poll_interval=0.2,
- )
- # self.telegram.idle()
+from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
+
+from . import api, commands
+
+import logging
+logger = logging.getLogger(__name__)
+
+class ChatBot():
+ """better framwork - unites all functions"""
+
+ def __init__(self, name, version):
+ """Inits the Bot with a few conf. vars
+ Args: -> name:str - Name of the bot
+ -> version:str - Version number
+ -> hw_commands - dict with commands executable by the clock module
+ -> prst:dict - persistence (overloaded dict that writes to json file)
+ -> logger - logging object to unify log messages
+ """
+ # added by the launcher, we have self.modules (dict) and persistence and db
+
+ self.name = name
+ self.version = version
+
+ # Import submodules
+ self.api_weather = api.weather.WeatherFetch(api.keys.weather_api)
+ self.api_reddit = api.reddit.RedditFetch(api.keys.reddit_api)
+ self.api_search = api.search.WebSearch()
+ self.api_art = api.metmuseum.ArtFetch()
+ # and so on
+
+ self.telegram = Updater(api.keys.telegram_api, use_context=True)
+ self.dispatcher = self.telegram.dispatcher
+ self.commands = commands
+
+
+
+ def add_commands(self):
+ # Mark modules as available
+ db = self.db
+ self.help_module = self.commands.help.Help(db)
+ self.sub_modules = {
+ "weather": self.commands.weather.Weather(self.api_weather, db),
+ "help" : self.help_module,
+ "status" : self.commands.status.Status(self.name, self.version, db),
+ "zvv" : self.commands.zvv.Zvv(db),
+ "list" : self.commands.lists.Lists(db),
+ # "alias" : commands.alias.Alias(self.dispatcher, db),
+ "joke" : self.commands.reddit.Joke(self.api_reddit, db),
+ "meme" : self.commands.reddit.Meme(self.api_reddit, db),
+ # "news" : self.commands.reddit.News(self.api_reddit, 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
+ }
+ # must be a class that has a method create_handler
+
+ for k in self.sub_modules:
+ self.dispatcher.add_handler(self.sub_modules[k].create_handler())
+
+ self.help_module.add_commands(self.sub_modules)
+
+ def start(self):
+ self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)}
+ self.add_commands()
+ self.telegram.start_polling(
+ poll_interval=0.2,
+ )
+ # self.telegram.idle()
diff --git a/broadcast/__init__.py b/broadcast/__init__.py
index dcf2c80..9a5984d 100644
--- a/broadcast/__init__.py
+++ b/broadcast/__init__.py
@@ -1 +1 @@
-# Placeholder
+# Placeholder
diff --git a/broadcast/b_in.py b/broadcast/b_in.py
index 0ad2545..6ed9db1 100644
--- a/broadcast/b_in.py
+++ b/broadcast/b_in.py
@@ -1,71 +1,71 @@
-import requests
-
-import logging
-logger = logging.getLogger(__name__)
-
-class FetchUpdates:
- """Fetches updates from the main server and relays them to the clock"""
-
- def __init__(self, server_ip, port):
- """Both methods return a list as python-object. This should be then converted to a numpy array."""
- # self.server_ip = server_ip
- self.base_url = server_ip + ":" + port
- # self.modules gets added through the caller
- self.update_calls = 0
- self.last_fetch = {}
-
-
- def start(self):
- # dummy for errorless launching
- pass
-
-
- def get_updates(self):
- update_url = "http://" + self.base_url + "/getupdates"
- result = self.call_api(update_url)
-
- return result
-
-
- def get_last(self):
- update_url = "http://" + self.base_url + "/getlast"
- result = self.call_api(update_url)
-
- return result
-
-
- def fetch_data(self):
- try:
- if self.update_calls == 0:
- fetch = self.get_last()
- else:
- fetch = self.get_updates()
- if not fetch["is_new"]:
- fetch = self.last_fetch
- else:
- self.last_fetch = fetch
-
- data = fetch["data"]
- has_queue = fetch["has_queue"]
- except:
- data = {}
- has_queue = False
-
- self.update_calls += 1
-
- return has_queue, data
-
-
-
- def call_api(self, url):
- ret = {}
- try:
- result = requests.get(url)
- result = result.json()
-
- if result.pop("status") == "ok":
- ret = result
- except:
- logger.error("Bad api call for method {}.".format(url[url.rfind("/"):]))
-
- return ret
+import requests
+
+import logging
+logger = logging.getLogger(__name__)
+
+class FetchUpdates:
+ """Fetches updates from the main server and relays them to the clock"""
+
+ def __init__(self, server_ip, port):
+ """Both methods return a list as python-object. This should be then converted to a numpy array."""
+ # self.server_ip = server_ip
+ self.base_url = server_ip + ":" + port
+ # self.modules gets added through the caller
+ self.update_calls = 0
+ self.last_fetch = {}
+
+
+ def start(self):
+ # dummy for errorless launching
+ pass
+
+
+ def get_updates(self):
+ update_url = "http://" + self.base_url + "/getupdates"
+ result = self.call_api(update_url)
+
+ return result
+
+
+ def get_last(self):
+ update_url = "http://" + self.base_url + "/getlast"
+ result = self.call_api(update_url)
+
+ return result
+
+
+ def fetch_data(self):
+ try:
+ if self.update_calls == 0:
+ fetch = self.get_last()
+ else:
+ fetch = self.get_updates()
+ if not fetch["is_new"]:
+ fetch = self.last_fetch
+ else:
+ self.last_fetch = fetch
+
+ data = fetch["data"]
+ has_queue = fetch["has_queue"]
+ except:
+ data = {}
+ has_queue = False
+
+ self.update_calls += 1
+
+ return has_queue, data
+
+
+
+ def call_api(self, url):
+ ret = {}
+ try:
+ result = requests.get(url)
+ result = result.json()
+
+ if result.pop("status") == "ok":
+ ret = result
+ except:
+ logger.error("Bad api call for method {}.".format(url[url.rfind("/"):]))
+
+ return ret
diff --git a/broadcast/b_out.py b/broadcast/b_out.py
index 229778c..9c0927a 100644
--- a/broadcast/b_out.py
+++ b/broadcast/b_out.py
@@ -1,86 +1,86 @@
-import flask
-from flask import request, jsonify
-import numpy as np
-from threading import Thread
-
-
-import logging
-log = logging.getLogger('werkzeug')
-log.setLevel(logging.ERROR)
-# hide the info-messages of each GET-request
-
-
-
-
-class BroadcastUpdates:
- """Broadcasts (out) updates for the hw-handler to be fetched periodically"""
-
- def __init__(self, port):
- """"""
- 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.port = port
-
-
- def start(self):
- t = Thread(target=self.run)
- t.start()
-
-
- def run(self):
- app = flask.Flask(__name__)
-
- @app.route('/getupdates', methods=['GET'])
- def get_updates():
- return self.get_updates()
- @app.route('/getlast', methods=['GET'])
- def get_last():
- return self.get_last()
-
- app.run('0.0.0.0', port=self.port)
-
-
- def get_updates(self):
- try:
- data = self.queue.pop(0)
- self.last_show = data
- is_new = True
- has_queue = len(self.queue) > 0
- except:
- data = ""
- is_new = False
- has_queue = False
-
- return self.generate_response(
- is_new = is_new,
- data = data,
- has_queue = has_queue
- )
-
-
-
- def get_last(self):
- try:
- try:
- data = self.queue[-1]
- self.queue = []
- except: # list empty
- data = self.last_show,
- except:
- data = ""
- return self.generate_response(
- data = data,
- has_queue = False,
- )
-
-
-
- def generate_response(self, **kwargs):
- ret = {
- "status" : "ok",
- **kwargs
- }
-
- return jsonify(ret)
-
+import flask
+from flask import request, jsonify
+import numpy as np
+from threading import Thread
+
+
+import logging
+log = logging.getLogger('werkzeug')
+log.setLevel(logging.ERROR)
+# hide the info-messages of each GET-request
+
+
+
+
+class BroadcastUpdates:
+ """Broadcasts (out) updates for the hw-handler to be fetched periodically"""
+
+ def __init__(self, port):
+ """"""
+ 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.port = port
+
+
+ def start(self):
+ t = Thread(target=self.run)
+ t.start()
+
+
+ def run(self):
+ app = flask.Flask(__name__)
+
+ @app.route('/getupdates', methods=['GET'])
+ def get_updates():
+ return self.get_updates()
+ @app.route('/getlast', methods=['GET'])
+ def get_last():
+ return self.get_last()
+
+ app.run('0.0.0.0', port=self.port)
+
+
+ def get_updates(self):
+ try:
+ data = self.queue.pop(0)
+ self.last_show = data
+ is_new = True
+ has_queue = len(self.queue) > 0
+ except:
+ data = ""
+ is_new = False
+ has_queue = False
+
+ return self.generate_response(
+ is_new = is_new,
+ data = data,
+ has_queue = has_queue
+ )
+
+
+
+ def get_last(self):
+ try:
+ try:
+ data = self.queue[-1]
+ self.queue = []
+ except: # list empty
+ data = self.last_show,
+ except:
+ data = ""
+ return self.generate_response(
+ data = data,
+ has_queue = False,
+ )
+
+
+
+ def generate_response(self, **kwargs):
+ ret = {
+ "status" : "ok",
+ **kwargs
+ }
+
+ return jsonify(ret)
+
diff --git a/client.py b/client.py
index 1ed88b3..a32d2dc 100644
--- a/client.py
+++ b/client.py
@@ -1,43 +1,43 @@
-# functionality
-from clock import c_in, c_out
-from broadcast import b_in
-
-import launcher
-
-
-import logging
-import os
-
-if os.name == "nt":
- # development
- logging.basicConfig(
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
- )
-else:
- logging.basicConfig(
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- level=logging.INFO,
- filename='persistence/client.log',
- )
-
-
-class ReceiverLauncher(launcher.Launcher):
- """Launcher for all server-side modules. The hard-computations"""
- def __init__(self):
-
- self.clock_sensor_module = c_in.SensorReadout()
- # active: periodically takes readouts
- self.clock_hardware_module = c_out.ClockFace()
- # active: periodically calls fetcher
- self.receive_module = b_in.FetchUpdates(server_ip="localhost", port="1111")
- # passive: fetches data on demand
-
- super().__init__(
- sensors = self.clock_sensor_module,
- clock = self.clock_hardware_module,
- receive = self.receive_module
- )
-
-
-
+# functionality
+from clock import c_in, c_out
+from broadcast import b_in
+
+import launcher
+
+
+import logging
+import os
+
+if os.name == "nt":
+ # development
+ logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
+ )
+else:
+ logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO,
+ filename='persistence/client.log',
+ )
+
+
+class ReceiverLauncher(launcher.Launcher):
+ """Launcher for all server-side modules. The hard-computations"""
+ def __init__(self):
+
+ self.clock_sensor_module = c_in.SensorReadout()
+ # active: periodically takes readouts
+ self.clock_hardware_module = c_out.ClockFace()
+ # active: periodically calls fetcher
+ self.receive_module = b_in.FetchUpdates(server_ip="localhost", port="1111")
+ # passive: fetches data on demand
+
+ super().__init__(
+ sensors = self.clock_sensor_module,
+ clock = self.clock_hardware_module,
+ receive = self.receive_module
+ )
+
+
+
ReceiverLauncher()
\ No newline at end of file
diff --git a/clock/__init__.py b/clock/__init__.py
index dcf2c80..9a5984d 100644
--- a/clock/__init__.py
+++ b/clock/__init__.py
@@ -1 +1 @@
-# Placeholder
+# Placeholder
diff --git a/clock/c_back.py b/clock/c_back.py
index 133028a..2d8358e 100644
--- a/clock/c_back.py
+++ b/clock/c_back.py
@@ -1,73 +1,73 @@
-import numpy as np
-import datetime
-
-from . import helpers
-
-class ClockBackend:
- """Heavy lifting of matrix operations"""
- def __init__(self):
- self.MOP = helpers.computations.MatrixOperations()
-
- self.weather = {"weather":"", "high":"", "low":""}
- self.weather_raw = {}
- self.weather_face_swap = False
-
-
- def start(self):
- self.out = self.modules["broadcast"]
- helpers.timer.RepeatedTimer(15, self.clock_loop)
-
-
- def clock_loop(self):
- t = int(datetime.datetime.now().strftime("%H%M"))
-
- if t % 5 == 0:
- # switch secondary face every 5 minutes
- weather = self.modules["bot"].api_weather.show_weather([47.3769, 8.5417]) # zürich
-
- if weather != self.weather_raw and len(weather) != 0:
- td = weather[1]
- low = td["temps"][0]
- high = td["temps"][1]
- self.weather["weather"] = td["short"]
- self.weather["high"] = high
- self.weather["low"] = low
- elif len(weather) == 0:
- self.weather["weather"] = "error"
- self.weather["high"] = "error"
- self.weather["low"] = "error"
-
- self.weather_face_swap = not self.weather_face_swap
-
- self.send_face()
-
-
-
-
- def send_face(self):
- """Set the clock face (time + weather) by getting updated info - gets called every minute"""
- matrices = self.MOP.clock_face(self.weather)
- if self.weather_face_swap:
- matrices = [matrices[0], matrices[2], matrices[1]]
-
- matrices = [m.tolist() for m in matrices]
- self.out.queue.append({"matrices" : matrices})
-
-
-
-
- # def text_scroll(self, text, color=[[200,200,200]]):
- # pixels = self.MOP.text_converter(text, 12, color)
- # sleep_time = 1 / self.tspeed
- # width = self.shape[1]
- # frames = pixels.shape[1] - width
- # if frames <= 0:
- # frames = 1
-
- # for i in range(frames):
- # visible = pixels[:,i:width+i]
- # self.IO.put(visible*self.brightness)
- # time.sleep(sleep_time)
- # time.sleep(10 * sleep_time)
-
-
+import numpy as np
+import datetime
+
+from . import helpers
+
+class ClockBackend:
+ """Heavy lifting of matrix operations"""
+ def __init__(self):
+ self.MOP = helpers.computations.MatrixOperations()
+
+ self.weather = {"weather":"", "high":"", "low":""}
+ self.weather_raw = {}
+ self.weather_face_swap = False
+
+
+ def start(self):
+ self.out = self.modules["broadcast"]
+ helpers.timer.RepeatedTimer(15, self.clock_loop)
+
+
+ def clock_loop(self):
+ t = int(datetime.datetime.now().strftime("%H%M"))
+
+ if t % 5 == 0:
+ # switch secondary face every 5 minutes
+ weather = self.modules["bot"].api_weather.show_weather([47.3769, 8.5417]) # zürich
+
+ if weather != self.weather_raw and len(weather) != 0:
+ td = weather[1]
+ low = td["temps"][0]
+ high = td["temps"][1]
+ self.weather["weather"] = td["short"]
+ self.weather["high"] = high
+ self.weather["low"] = low
+ elif len(weather) == 0:
+ self.weather["weather"] = "error"
+ self.weather["high"] = "error"
+ self.weather["low"] = "error"
+
+ self.weather_face_swap = not self.weather_face_swap
+
+ self.send_face()
+
+
+
+
+ def send_face(self):
+ """Set the clock face (time + weather) by getting updated info - gets called every minute"""
+ matrices = self.MOP.clock_face(self.weather)
+ if self.weather_face_swap:
+ matrices = [matrices[0], matrices[2], matrices[1]]
+
+ matrices = [m.tolist() for m in matrices]
+ self.out.queue.append({"matrices" : matrices})
+
+
+
+
+ # def text_scroll(self, text, color=[[200,200,200]]):
+ # pixels = self.MOP.text_converter(text, 12, color)
+ # sleep_time = 1 / self.tspeed
+ # width = self.shape[1]
+ # frames = pixels.shape[1] - width
+ # if frames <= 0:
+ # frames = 1
+
+ # for i in range(frames):
+ # visible = pixels[:,i:width+i]
+ # self.IO.put(visible*self.brightness)
+ # time.sleep(sleep_time)
+ # time.sleep(10 * sleep_time)
+
+
diff --git a/clock/c_in.py b/clock/c_in.py
index be7afe1..4122fd9 100644
--- a/clock/c_in.py
+++ b/clock/c_in.py
@@ -1,56 +1,56 @@
-import datetime
-import time
-from threading import Thread, Timer
-
-from . import hardware, helpers
-
-
-class SensorReadout:
- """Overview class for (actual and potential) sensor sources"""
-
- def __init__(self):
- """"""
- self.sensor_modules = { # we already call them, they are objects and not classes anymore
- "temperature" : hardware.sensors.TemperatureModule(),
- "humidity" : hardware.sensors.HumidityModule(),
- "luminosity" : hardware.sensors.BrightnessModule(),
- # more to come?
- }
-
- def start(self):
- helpers.timer.RepeatedTimer(300, self.spread_measure)
-
- def spread_measure(self):
- measurements = dict((el,[]) for el in self.sensor_modules.keys())
- # create an empty dict with a list for each readout-type
-
- for _ in range(5): # number of measures to average out
- for name in self.sensor_modules.keys():
- measure = self.sensor_modules[name].readout()
- measurements[name].append(measure)
- time.sleep(3)
-
- results = {}
- for e in measurements.keys():
- lst = measurements[e]
- results[e] = int(sum(lst) / len(lst))
-
- self.save_results(**results)
-
-
- # def save_results(self, results):
- # current_minute = int(datetime.datetime.now().timestamp() // 60)
-
- # self.persistence["clock"]["sensors"]["time"] += [current_minute]
-
- # for name in results.keys():
- # keep_value = sum(results[name]) / len(results[name])
- # self.persistence["clock"]["sensors"][name] += [keep_value]
-
-
- def save_results(self, **results):
- data = self.db.sensors(
- time=datetime.datetime.now(),
- **results,
- )
- data.save()
+import datetime
+import time
+from threading import Thread, Timer
+
+from . import hardware, helpers
+
+
+class SensorReadout:
+ """Overview class for (actual and potential) sensor sources"""
+
+ def __init__(self):
+ """"""
+ self.sensor_modules = { # we already call them, they are objects and not classes anymore
+ "temperature" : hardware.sensors.TemperatureModule(),
+ "humidity" : hardware.sensors.HumidityModule(),
+ "luminosity" : hardware.sensors.BrightnessModule(),
+ # more to come?
+ }
+
+ def start(self):
+ helpers.timer.RepeatedTimer(300, self.spread_measure)
+
+ def spread_measure(self):
+ measurements = dict((el,[]) for el in self.sensor_modules.keys())
+ # create an empty dict with a list for each readout-type
+
+ for _ in range(5): # number of measures to average out
+ for name in self.sensor_modules.keys():
+ measure = self.sensor_modules[name].readout()
+ measurements[name].append(measure)
+ time.sleep(3)
+
+ results = {}
+ for e in measurements.keys():
+ lst = measurements[e]
+ results[e] = int(sum(lst) / len(lst))
+
+ self.save_results(**results)
+
+
+ # def save_results(self, results):
+ # current_minute = int(datetime.datetime.now().timestamp() // 60)
+
+ # self.persistence["clock"]["sensors"]["time"] += [current_minute]
+
+ # for name in results.keys():
+ # keep_value = sum(results[name]) / len(results[name])
+ # self.persistence["clock"]["sensors"][name] += [keep_value]
+
+
+ def save_results(self, **results):
+ data = self.db.sensors(
+ time=datetime.datetime.now(),
+ **results,
+ )
+ data.save()
diff --git a/clock/c_out.py b/clock/c_out.py
index 8beab4b..52aaea3 100644
--- a/clock/c_out.py
+++ b/clock/c_out.py
@@ -1,65 +1,65 @@
-import datetime
-import time
-from threading import Thread
-import numpy as np
-
-from . import hardware, helpers
-
-
-class ClockFace:
- """Actual functions one might need for a clock"""
-
- def __init__(self):
- """"""
- # added by the launcher, we have self.modules (dict)
-
- self.IO = hardware.led.get_handler()
- self.shape = self.IO.shape # (16,32) for now
- # TODO handle differently!
- self.MOP = helpers.computations.MatrixOperations()
-
- self.kill_output = False
-
- def start(self):
- Thread(target = self.clock_loop).start()
-
-
- def clock_loop(self):
- while True: # TODO: allow this to be exited gracefully
-
- t_start = datetime.datetime.now()
-
- self.set_brightness()
-
- has_queue, data = self.modules["receive"].fetch_data()
- tnext = 1 if has_queue else 30
-
- if data == {}:
- matrices = self.MOP.get_fallback()
- matrices[0][0,0] = [255, 0, 0] # red dot on the top left
- else:
- matrices = [np.asarray(d).astype(int) for d in data["matrices"]]
- matrices[0][0,0] = [0, 255, 0] # green dot on the top left
-
- if not self.kill_output:
- self.IO.put(matrices)
- else:
- z = np.zeros((16,16,3))
- self.IO.put([z,z,z])
-
-
- t_end = datetime.datetime.now()
- delta_planned = datetime.timedelta(seconds = tnext)
- delta = delta_planned - (t_end - t_start)
-
- time.sleep(max(delta.total_seconds(), 0))
-
-
- def set_brightness(self):
- """Kill the brightness at night"""
-
- is_WE = datetime.datetime.now().weekday() > 4
- 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)
-
+import datetime
+import time
+from threading import Thread
+import numpy as np
+
+from . import hardware, helpers
+
+
+class ClockFace:
+ """Actual functions one might need for a clock"""
+
+ def __init__(self):
+ """"""
+ # added by the launcher, we have self.modules (dict)
+
+ self.IO = hardware.led.get_handler()
+ self.shape = self.IO.shape # (16,32) for now
+ # TODO handle differently!
+ self.MOP = helpers.computations.MatrixOperations()
+
+ self.kill_output = False
+
+ def start(self):
+ Thread(target = self.clock_loop).start()
+
+
+ def clock_loop(self):
+ while True: # TODO: allow this to be exited gracefully
+
+ t_start = datetime.datetime.now()
+
+ self.set_brightness()
+
+ has_queue, data = self.modules["receive"].fetch_data()
+ tnext = 1 if has_queue else 30
+
+ if data == {}:
+ matrices = self.MOP.get_fallback()
+ matrices[0][0,0] = [255, 0, 0] # red dot on the top left
+ else:
+ matrices = [np.asarray(d).astype(int) for d in data["matrices"]]
+ matrices[0][0,0] = [0, 255, 0] # green dot on the top left
+
+ if not self.kill_output:
+ self.IO.put(matrices)
+ else:
+ z = np.zeros((16,16,3))
+ self.IO.put([z,z,z])
+
+
+ t_end = datetime.datetime.now()
+ delta_planned = datetime.timedelta(seconds = tnext)
+ delta = delta_planned - (t_end - t_start)
+
+ time.sleep(max(delta.total_seconds(), 0))
+
+
+ def set_brightness(self):
+ """Kill the brightness at night"""
+
+ is_WE = datetime.datetime.now().weekday() > 4
+ 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)
+
diff --git a/clock/hardware/__init__.py b/clock/hardware/__init__.py
index 221ab03..6e92f3e 100644
--- a/clock/hardware/__init__.py
+++ b/clock/hardware/__init__.py
@@ -1,2 +1,2 @@
-# Placeholder
+# Placeholder
from . import led, sensors
\ No newline at end of file
diff --git a/clock/hardware/led.py b/clock/hardware/led.py
index f4a5244..d537eed 100644
--- a/clock/hardware/led.py
+++ b/clock/hardware/led.py
@@ -1,17 +1,17 @@
-from . import unicorn as led
-# or neopixel soon:
-# from . import neopixel as led
-
-def get_handler():
- OUT = led.ClockOut()
- shape = OUT.shape
-
- if led.SETUP_FAIL:
- # we use the sim
- del OUT
- from . import sim
- OUT = sim.ClockOut(shape)
-
- return OUT
-
-
+from . import unicorn as led
+# or neopixel soon:
+# from . import neopixel as led
+
+def get_handler():
+ OUT = led.ClockOut()
+ shape = OUT.shape
+
+ if led.SETUP_FAIL:
+ # we use the sim
+ del OUT
+ from . import sim
+ OUT = sim.ClockOut(shape)
+
+ return OUT
+
+
diff --git a/clock/hardware/neopixel.py b/clock/hardware/neopixel.py
index 67707e2..e6a7c68 100644
--- a/clock/hardware/neopixel.py
+++ b/clock/hardware/neopixel.py
@@ -1,51 +1,51 @@
-import time
-import numpy as np
-import colorsys
-import random
-
-try:
- import rpi_ws281x as ws
-except ImportError:
- from unittest.mock import Mock
- ws = Mock()
- SETUP_FAIL = True
-
-
-class ClockOut:
- def __init__(self):
- self.shape = (45, 20) # H x W
- num = self.shape[0] * self.shape[1]
- pin = 18
- freq = 800000 # maybe higher
- dma = 5
- invert = False
- brightness = 100
- channel = 0
- led_type = None # ??
- self.strip = ws.PixelStrip(num, pin, freq, dma, invert, brightness, channel, led_type)
- self.strip.begin()
-
-
- def put(self, matrix):
- self.render(matrix)
-
-
- def render(self, matrix):
- p = 0
- for i in range(matrix.shape[0]):
- for j in range(matrix.shape[1]):
- col = int(ws.Color(*matrix[i,j]))
-
- self.strip.setPixelColor(p, col)
- p += 1
- self.strip.show()
-
-
-# test = ClockOut()
-# z = np.zeros((30,30, 3), dtype=int)
-# for i in range(30):
-# for j in range(30):
-# z[i, j, ...] = [random.randint(0,255), random.randint(0,255), random.randint(0,255)]
-# test.put(z)
-# #time.sleep(0.1)
-
+import time
+import numpy as np
+import colorsys
+import random
+
+try:
+ import rpi_ws281x as ws
+except ImportError:
+ from unittest.mock import Mock
+ ws = Mock()
+ SETUP_FAIL = True
+
+
+class ClockOut:
+ def __init__(self):
+ self.shape = (45, 20) # H x W
+ num = self.shape[0] * self.shape[1]
+ pin = 18
+ freq = 800000 # maybe higher
+ dma = 5
+ invert = False
+ brightness = 100
+ channel = 0
+ led_type = None # ??
+ self.strip = ws.PixelStrip(num, pin, freq, dma, invert, brightness, channel, led_type)
+ self.strip.begin()
+
+
+ def put(self, matrix):
+ self.render(matrix)
+
+
+ def render(self, matrix):
+ p = 0
+ for i in range(matrix.shape[0]):
+ for j in range(matrix.shape[1]):
+ col = int(ws.Color(*matrix[i,j]))
+
+ self.strip.setPixelColor(p, col)
+ p += 1
+ self.strip.show()
+
+
+# test = ClockOut()
+# z = np.zeros((30,30, 3), dtype=int)
+# for i in range(30):
+# for j in range(30):
+# z[i, j, ...] = [random.randint(0,255), random.randint(0,255), random.randint(0,255)]
+# test.put(z)
+# #time.sleep(0.1)
+
diff --git a/clock/hardware/sensors.py b/clock/hardware/sensors.py
index 5a0a46a..3489606 100644
--- a/clock/hardware/sensors.py
+++ b/clock/hardware/sensors.py
@@ -1,87 +1,87 @@
-import time
-import logging
-logger = logging.getLogger(__name__)
-
-class TempSim:
- """Simulates a temperature for running on windows"""
- temperature = 23 # return a celsius value
- humidity = 30
-
-
-class LightSim:
- def input(self, *args):
- return 1
-
-
-
-
-class SensorModule:
- def __init__(self):
- logger.info("Using module " + self.__class__.__name__)
-
-
-
-## Real sensors!
-try:
- import board
- import adafruit_dht
- dht11 = adafruit_dht.DHT11(board.D18)
- import RPi.GPIO as GPIO
- GPIO.setmode(GPIO.BCM)
- GPIO.setup(4, GPIO.IN)
-except ImportError:
- logger.warn("Simulating sensor modules")
- dht11 = TempSim()
- GPIO = LightSim()
-
-
-class TemperatureModule(SensorModule):
- """Takes readouts from the DHT 11
- Returns: temperature"""
- def __init__(self):
- super().__init__()
- self.device = dht11
-
- def readout(self):
- try:
- temp = self.device.temperature
- except:
- time.sleep(1)
- try:
- temp = self.device.temperature
- except:
- temp = -1
-
- return temp
-
-class HumidityModule(SensorModule):
- """Takes readouts from the DHT 11
- Returns: humidity"""
- def __init__(self):
- super().__init__()
- self.device = dht11
-
- def readout(self):
- try:
- hum = self.device.humidity
- except:
- time.sleep(1)
- try:
- hum = self.device.humidity
- except:
- hum = -1
-
- return hum
-
-class BrightnessModule(SensorModule):
- """Returns one for HIGH and zero for LOW"""
- def __init__(self):
- super().__init__()
-
- def readout(self):
- # The sensor is reversed: 0 when bright and 1 if dark
- light = GPIO.input(4)
- if light == 0:
- return 1
- else:
- return 0
+import time
+import logging
+logger = logging.getLogger(__name__)
+
+class TempSim:
+ """Simulates a temperature for running on windows"""
+ temperature = 23 # return a celsius value
+ humidity = 30
+
+
+class LightSim:
+ def input(self, *args):
+ return 1
+
+
+
+
+class SensorModule:
+ def __init__(self):
+ logger.info("Using module " + self.__class__.__name__)
+
+
+
+## Real sensors!
+try:
+ import board
+ import adafruit_dht
+ dht11 = adafruit_dht.DHT11(board.D18)
+ import RPi.GPIO as GPIO
+ GPIO.setmode(GPIO.BCM)
+ GPIO.setup(4, GPIO.IN)
+except ImportError:
+ logger.warn("Simulating sensor modules")
+ dht11 = TempSim()
+ GPIO = LightSim()
+
+
+class TemperatureModule(SensorModule):
+ """Takes readouts from the DHT 11
+ Returns: temperature"""
+ def __init__(self):
+ super().__init__()
+ self.device = dht11
+
+ def readout(self):
+ try:
+ temp = self.device.temperature
+ except:
+ time.sleep(1)
+ try:
+ temp = self.device.temperature
+ except:
+ temp = -1
+
+ return temp
+
+class HumidityModule(SensorModule):
+ """Takes readouts from the DHT 11
+ Returns: humidity"""
+ def __init__(self):
+ super().__init__()
+ self.device = dht11
+
+ def readout(self):
+ try:
+ hum = self.device.humidity
+ except:
+ time.sleep(1)
+ try:
+ hum = self.device.humidity
+ except:
+ hum = -1
+
+ return hum
+
+class BrightnessModule(SensorModule):
+ """Returns one for HIGH and zero for LOW"""
+ def __init__(self):
+ super().__init__()
+
+ def readout(self):
+ # The sensor is reversed: 0 when bright and 1 if dark
+ light = GPIO.input(4)
+ if light == 0:
+ return 1
+ else:
+ return 0
diff --git a/clock/hardware/sim.py b/clock/hardware/sim.py
index a43a21f..362445c 100644
--- a/clock/hardware/sim.py
+++ b/clock/hardware/sim.py
@@ -1,55 +1,55 @@
-import sys
-import colorsys
-import pygame.gfxdraw
-import time
-import pygame
-import numpy as np
-
-class ClockOut:
- """Creates a drawable window in case the real hardware is not accessible. For development"""
- def __init__(self, shape):
- self.pixel_size = 20
-
- self.shape = shape
- self.pixels = np.zeros((*shape,3), dtype=int)
- self.WIDTH = shape[1]
- self.HEIGHT = shape[0]
- self.window_width = self.WIDTH * self.pixel_size
- self.window_height = self.HEIGHT * self.pixel_size
-
- pygame.init()
- pygame.display.set_caption("Unicorn HAT simulator")
- self.screen = pygame.display.set_mode([self.window_width, self.window_height])
-
-
- def put(self, matrices):
- self.screen.fill((0, 0, 0))
- for event in pygame.event.get(): # User did something
- if event.type == pygame.QUIT:
- pygame.quit()
- sys.exit()
-
- if self.shape == (16, 32):
- matrix = np.concatenate((matrices[0], matrices[1]), axis=1)
-
- self.pixels = matrix
- self.draw_pixels()
-
- pygame.display.flip()
- pygame.event.pump()
-
-
- def draw_pixels(self):
- p = self.pixel_size
-
- r = int(p / 4)
- for i in range(self.HEIGHT):
- for j in range(self.WIDTH):
- w_x = int(j * p + p / 2)
- #w_y = int((self.HEIGHT - 1 - y) * p + p / 2)
- w_y = int(i * p + p / 2)
- color = self.pixels[i,j,:]
- color = color.astype("int")
-
- pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color)
- pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color)
+import sys
+import colorsys
+import pygame.gfxdraw
+import time
+import pygame
+import numpy as np
+
+class ClockOut:
+ """Creates a drawable window in case the real hardware is not accessible. For development"""
+ def __init__(self, shape):
+ self.pixel_size = 20
+
+ self.shape = shape
+ self.pixels = np.zeros((*shape,3), dtype=int)
+ self.WIDTH = shape[1]
+ self.HEIGHT = shape[0]
+ self.window_width = self.WIDTH * self.pixel_size
+ self.window_height = self.HEIGHT * self.pixel_size
+
+ pygame.init()
+ pygame.display.set_caption("Unicorn HAT simulator")
+ self.screen = pygame.display.set_mode([self.window_width, self.window_height])
+
+
+ def put(self, matrices):
+ self.screen.fill((0, 0, 0))
+ for event in pygame.event.get(): # User did something
+ if event.type == pygame.QUIT:
+ pygame.quit()
+ sys.exit()
+
+ if self.shape == (16, 32):
+ matrix = np.concatenate((matrices[0], matrices[1]), axis=1)
+
+ self.pixels = matrix
+ self.draw_pixels()
+
+ pygame.display.flip()
+ pygame.event.pump()
+
+
+ def draw_pixels(self):
+ p = self.pixel_size
+
+ r = int(p / 4)
+ for i in range(self.HEIGHT):
+ for j in range(self.WIDTH):
+ w_x = int(j * p + p / 2)
+ #w_y = int((self.HEIGHT - 1 - y) * p + p / 2)
+ w_y = int(i * p + p / 2)
+ color = self.pixels[i,j,:]
+ color = color.astype("int")
+
+ pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color)
+ pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color)
diff --git a/clock/hardware/unicorn.py b/clock/hardware/unicorn.py
index 5b6728d..35f4bbe 100644
--- a/clock/hardware/unicorn.py
+++ b/clock/hardware/unicorn.py
@@ -1,96 +1,96 @@
-import colorsys
-import time
-import numpy as np
-try:
- import RPi.GPIO as GPIO
- SETUP_FAIL = False
-except ImportError:
- from unittest.mock import Mock
- GPIO = Mock()
- SETUP_FAIL = True
-
-
-class ClockOut:
- def __init__(self):
- self.PIN_CLK = 11
- ##################################
- # Hardcoded vaules:
- # GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
- self.PINS_DAT = [10, 22]
- self.PIN_CS = 8
- # for data transmission
- self.SOF = 0x72
- self.DELAY = 1.0/120
- # shape of 2 unicorn hats
- self.shape = (16, 32)
- ##################################
-
- GPIO.setmode(GPIO.BCM)
- GPIO.setwarnings(False)
- GPIO.setup(self.PIN_CS, GPIO.OUT, initial=GPIO.HIGH)
- GPIO.setup(self.PIN_CLK, GPIO.OUT, initial=GPIO.LOW)
- GPIO.setup(self.PINS_DAT, GPIO.OUT, initial=GPIO.LOW)
-
- self.HEIGHT = self.shape[0] #16
- self.WIDTH = self.shape[1] #32
-
- self.reset_clock()
-
-
- def reset_clock(self):
- GPIO.output(self.PIN_CS, GPIO.LOW)
- time.sleep(0.00001)
- GPIO.output(self.PIN_CS, GPIO.HIGH)
-
-
- def spi_write(self, buf1, buf2):
- GPIO.output(self.PIN_CS, GPIO.LOW)
-
- self.spi_write_byte(self.SOF, self.SOF)
-
- for x in range(len(buf1)):
- b1, b2= buf1[x], buf2[x]
- self.spi_write_byte(b1, b2)
-
-
- GPIO.output(self.PIN_CS, GPIO.HIGH)
-
-
- def spi_write_byte(self, b1, b2):
- for x in range(8):
- GPIO.output(self.PINS_DAT[0], b1 & 0b10000000)
- GPIO.output(self.PINS_DAT[1], b2 & 0b10000000)
- GPIO.output(self.PIN_CLK, GPIO.HIGH)
-
- b1 <<= 1
- b2 <<= 1
- #time.sleep(0.00000001)
- GPIO.output(self.PIN_CLK, GPIO.LOW)
-
-
- def put(self, matrices):
- """Sets a height x width matrix directly"""
- self.reset_clock()
- matrix = np.concatenate((matrices[0], matrices[1]), axis=1) # or 1??
- self.show(matrix)
-
-
- def clear(self):
- """Clear the buffer."""
- zero = np.zero((self.HEIGHT, self. WIDTH))
- self.put(zero)
-
-
- def show(self, matrix):
- """Output the contents of the buffer to Unicorn HAT HD."""
- ##########################################################
- ## Change to desire
- buff2 = np.rot90(matrix[:self.HEIGHT,:16],3)
- buff1 = np.rot90(matrix[:self.HEIGHT,16:32],1)
- ##########################################################
- # separated these are: 16x16x3 arrays
- buff1, buff2 = [(x.reshape(768)).astype(np.uint8).tolist() for x in (buff1, buff2)]
-
- self.spi_write(buff1, buff2)
-
- time.sleep(self.DELAY)
+import colorsys
+import time
+import numpy as np
+try:
+ import RPi.GPIO as GPIO
+ SETUP_FAIL = False
+except ImportError:
+ from unittest.mock import Mock
+ GPIO = Mock()
+ SETUP_FAIL = True
+
+
+class ClockOut:
+ def __init__(self):
+ self.PIN_CLK = 11
+ ##################################
+ # Hardcoded vaules:
+ # GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
+ self.PINS_DAT = [10, 22]
+ self.PIN_CS = 8
+ # for data transmission
+ self.SOF = 0x72
+ self.DELAY = 1.0/120
+ # shape of 2 unicorn hats
+ self.shape = (16, 32)
+ ##################################
+
+ GPIO.setmode(GPIO.BCM)
+ GPIO.setwarnings(False)
+ GPIO.setup(self.PIN_CS, GPIO.OUT, initial=GPIO.HIGH)
+ GPIO.setup(self.PIN_CLK, GPIO.OUT, initial=GPIO.LOW)
+ GPIO.setup(self.PINS_DAT, GPIO.OUT, initial=GPIO.LOW)
+
+ self.HEIGHT = self.shape[0] #16
+ self.WIDTH = self.shape[1] #32
+
+ self.reset_clock()
+
+
+ def reset_clock(self):
+ GPIO.output(self.PIN_CS, GPIO.LOW)
+ time.sleep(0.00001)
+ GPIO.output(self.PIN_CS, GPIO.HIGH)
+
+
+ def spi_write(self, buf1, buf2):
+ GPIO.output(self.PIN_CS, GPIO.LOW)
+
+ self.spi_write_byte(self.SOF, self.SOF)
+
+ for x in range(len(buf1)):
+ b1, b2= buf1[x], buf2[x]
+ self.spi_write_byte(b1, b2)
+
+
+ GPIO.output(self.PIN_CS, GPIO.HIGH)
+
+
+ def spi_write_byte(self, b1, b2):
+ for x in range(8):
+ GPIO.output(self.PINS_DAT[0], b1 & 0b10000000)
+ GPIO.output(self.PINS_DAT[1], b2 & 0b10000000)
+ GPIO.output(self.PIN_CLK, GPIO.HIGH)
+
+ b1 <<= 1
+ b2 <<= 1
+ #time.sleep(0.00000001)
+ GPIO.output(self.PIN_CLK, GPIO.LOW)
+
+
+ def put(self, matrices):
+ """Sets a height x width matrix directly"""
+ self.reset_clock()
+ matrix = np.concatenate((matrices[0], matrices[1]), axis=1) # or 1??
+ self.show(matrix)
+
+
+ def clear(self):
+ """Clear the buffer."""
+ zero = np.zero((self.HEIGHT, self. WIDTH))
+ self.put(zero)
+
+
+ def show(self, matrix):
+ """Output the contents of the buffer to Unicorn HAT HD."""
+ ##########################################################
+ ## Change to desire
+ buff2 = np.rot90(matrix[:self.HEIGHT,:16],3)
+ buff1 = np.rot90(matrix[:self.HEIGHT,16:32],1)
+ ##########################################################
+ # separated these are: 16x16x3 arrays
+ buff1, buff2 = [(x.reshape(768)).astype(np.uint8).tolist() for x in (buff1, buff2)]
+
+ self.spi_write(buff1, buff2)
+
+ time.sleep(self.DELAY)
diff --git a/clock/helpers/computations.py b/clock/helpers/computations.py
index 7484b6e..88adba0 100644
--- a/clock/helpers/computations.py
+++ b/clock/helpers/computations.py
@@ -1,178 +1,178 @@
-from PIL import Image, ImageDraw, ImageFont
-import numpy as np
-import datetime
-import time
-
-# bulky hard-coded values:
-from . import shapes
-digits = shapes.digits
-weather_categories = shapes.weather_categories
-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))
-
-
-
-
-class MatrixOperations():
- """Helper functions to generate frequently-used images"""
- def __init__(self, shape=[16,16]):
- self.shape = shape
- # shape is going to be (16,32) for the moment
- self.primary = [200, 200, 200]
- self.secondary = [10, 200, 10]
- self.error = [200, 10, 10]
-
-
- def time_converter(self, top="", bottom=""):
- """Converts 4-digit time to a 16x16 pixel-matrix
- returns: np.array"""
- # nshape = (self.shape[0], int(self.shape[1]/2))
- nshape = (16, 16)
- pixels = np.zeros(nshape, dtype=np.uint8)
-
- if bottom == "" or top == "":
- top = datetime.datetime.now().strftime("%H")
- bottom = datetime.datetime.now().strftime("%M")
-
- if len(top) < 2:
- top = "0" * (2 - len(top)) + top
- if len(bottom) < 2:
- bottom = "0" * (2 - len(bottom)) + bottom
-
- if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2):
- time_split = 4*["-"]
- elif "error" in top and "error" in bottom:
- time_split = 4*["error"]
- else:
- time_split = [i for i in top] + [i for i in bottom]
-
- if "-1" in top and len(top) != 2:
- time_split = ["-1", top[-1]] + [i for i in bottom]
- if "-1" in bottom and len(bottom) != 2:
- time_split = [i for i in top] + ["-1", bottom[-1]]
-
- for i in range(4):
- x = digit_position[i][0]
- y = digit_position[i][1]
- number = digits[time_split[i]]
- pixels[x: x + 5, y: y + 3] = np.array(number)
-
- return pixels
-
-
- def date_converter(self):
- # nshape = (self.shape[0], int(self.shape[1]/2))
- nshape = (16, 16)
- today = datetime.datetime.today()
- weekday = datetime.datetime.weekday(today)
- # size of the reduced array according to weekday
- size = [2,4,6,8,10,13,16]
-
- pixels = days.copy() #base color background
- 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)
- pixels += lrow
- return pixels
-
-
- def weather_converter(self, name):
- """Fills one half of the screen with weather info."""
- # nshape = (self.shape[0], int(self.shape[1]/2))
- nshape = (16, 16)
- result = np.zeros(nshape)
- cwd = __file__.replace("\\","/") # for windows
- cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is)
- if len(cwd) == 0:
- cwd = "."
- icon_spritesheet = cwd + "/weather-icons.bmp"
-
- icons = Image.open(icon_spritesheet)
- 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"]
- #ordered 1 2 \n 3 4 \ 5 5 ...
- if name == "":
- name = "error"
- name = weather_categories[name]
- try:
- iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2
- # x and y coords
- except:
- return np.zeros((*nshape,3))
-
- icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...]
- return icon_single
-
-
- 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"""
-
- c1 = self.primary
- c2 = self.secondary
- c3 = self.error
- if len(colors) > 0:
- c1 = colors[0]
- if len(colors) > 1:
- c2 = colors[1]
- if len(colors) > 2:
- c3 = colors[2]
- if len(colors) > 3:
- print("Too many colors")
-
- r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int)
- for i in range(matrix.shape[0]):
- for j in range(matrix.shape[1]):
- t = int(matrix[i, j])
- if t == 0:
- r3[i, j, :] = [0,0,0]
- elif t == 1:
- r3[i, j, :] = c1
- elif t == 2:
- r3[i, j, :] = c2
- else:
- r3[i, j, :] = c3
- return r3
-
-
-
- def clock_face(self, weather):
- """weather as a dict"""
- hour = self.time_converter()
- day = self.date_converter()
- face1 = hour + day
- # time + date:
- face1_3d = self.matrix_add_depth(face1)
- # weather icons
- face2_3d = self.weather_converter(weather["weather"])
- # weather temps
- face3 = self.time_converter(top=str(weather["low"]), bottom=str(weather["high"]))
- face3 = np.concatenate((face3[:8,...],2*face3[8:,...]))
- face3_3d = self.matrix_add_depth(face3,[[0, 102, 255],[255, 102, 0]])
-
- return [face1_3d, face2_3d, face3_3d]
-
-
- def text_converter(self, text, height, color):
- """Converts a text to a pixel-matrix
- returns: np.array((16, x, 3))"""
-
- font = ImageFont.truetype("verdanab.ttf", height)
- size = font.getsize(text)
- img = Image.new("1",size,"black")
- draw = ImageDraw.Draw(img)
- draw.text((0, 0), text, "white", font=font)
- pixels = np.array(img, dtype=np.uint8)
- pixels3d = self.matrix_add_depth(pixels, color)
- return pixels3d
-
-
- def get_fallback(self):
- hour = self.time_converter()
- day = self.date_converter()
- face1 = hour + day
- face1_3d = self.matrix_add_depth(face1)
-
- face2_3d = face3_3d = np.zeros((16,16,3))
+from PIL import Image, ImageDraw, ImageFont
+import numpy as np
+import datetime
+import time
+
+# bulky hard-coded values:
+from . import shapes
+digits = shapes.digits
+weather_categories = shapes.weather_categories
+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))
+
+
+
+
+class MatrixOperations():
+ """Helper functions to generate frequently-used images"""
+ def __init__(self, shape=[16,16]):
+ self.shape = shape
+ # shape is going to be (16,32) for the moment
+ self.primary = [200, 200, 200]
+ self.secondary = [10, 200, 10]
+ self.error = [200, 10, 10]
+
+
+ def time_converter(self, top="", bottom=""):
+ """Converts 4-digit time to a 16x16 pixel-matrix
+ returns: np.array"""
+ # nshape = (self.shape[0], int(self.shape[1]/2))
+ nshape = (16, 16)
+ pixels = np.zeros(nshape, dtype=np.uint8)
+
+ if bottom == "" or top == "":
+ top = datetime.datetime.now().strftime("%H")
+ bottom = datetime.datetime.now().strftime("%M")
+
+ if len(top) < 2:
+ top = "0" * (2 - len(top)) + top
+ if len(bottom) < 2:
+ bottom = "0" * (2 - len(bottom)) + bottom
+
+ if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2):
+ time_split = 4*["-"]
+ elif "error" in top and "error" in bottom:
+ time_split = 4*["error"]
+ else:
+ time_split = [i for i in top] + [i for i in bottom]
+
+ if "-1" in top and len(top) != 2:
+ time_split = ["-1", top[-1]] + [i for i in bottom]
+ if "-1" in bottom and len(bottom) != 2:
+ time_split = [i for i in top] + ["-1", bottom[-1]]
+
+ for i in range(4):
+ x = digit_position[i][0]
+ y = digit_position[i][1]
+ number = digits[time_split[i]]
+ pixels[x: x + 5, y: y + 3] = np.array(number)
+
+ return pixels
+
+
+ def date_converter(self):
+ # nshape = (self.shape[0], int(self.shape[1]/2))
+ nshape = (16, 16)
+ today = datetime.datetime.today()
+ weekday = datetime.datetime.weekday(today)
+ # size of the reduced array according to weekday
+ size = [2,4,6,8,10,13,16]
+
+ pixels = days.copy() #base color background
+ 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)
+ pixels += lrow
+ return pixels
+
+
+ def weather_converter(self, name):
+ """Fills one half of the screen with weather info."""
+ # nshape = (self.shape[0], int(self.shape[1]/2))
+ nshape = (16, 16)
+ result = np.zeros(nshape)
+ cwd = __file__.replace("\\","/") # for windows
+ cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is)
+ if len(cwd) == 0:
+ cwd = "."
+ icon_spritesheet = cwd + "/weather-icons.bmp"
+
+ icons = Image.open(icon_spritesheet)
+ 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"]
+ #ordered 1 2 \n 3 4 \ 5 5 ...
+ if name == "":
+ name = "error"
+ name = weather_categories[name]
+ try:
+ iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2
+ # x and y coords
+ except:
+ return np.zeros((*nshape,3))
+
+ icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...]
+ return icon_single
+
+
+ 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"""
+
+ c1 = self.primary
+ c2 = self.secondary
+ c3 = self.error
+ if len(colors) > 0:
+ c1 = colors[0]
+ if len(colors) > 1:
+ c2 = colors[1]
+ if len(colors) > 2:
+ c3 = colors[2]
+ if len(colors) > 3:
+ print("Too many colors")
+
+ r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int)
+ for i in range(matrix.shape[0]):
+ for j in range(matrix.shape[1]):
+ t = int(matrix[i, j])
+ if t == 0:
+ r3[i, j, :] = [0,0,0]
+ elif t == 1:
+ r3[i, j, :] = c1
+ elif t == 2:
+ r3[i, j, :] = c2
+ else:
+ r3[i, j, :] = c3
+ return r3
+
+
+
+ def clock_face(self, weather):
+ """weather as a dict"""
+ hour = self.time_converter()
+ day = self.date_converter()
+ face1 = hour + day
+ # time + date:
+ face1_3d = self.matrix_add_depth(face1)
+ # weather icons
+ face2_3d = self.weather_converter(weather["weather"])
+ # weather temps
+ face3 = self.time_converter(top=str(weather["low"]), bottom=str(weather["high"]))
+ face3 = np.concatenate((face3[:8,...],2*face3[8:,...]))
+ face3_3d = self.matrix_add_depth(face3,[[0, 102, 255],[255, 102, 0]])
+
+ return [face1_3d, face2_3d, face3_3d]
+
+
+ def text_converter(self, text, height, color):
+ """Converts a text to a pixel-matrix
+ returns: np.array((16, x, 3))"""
+
+ font = ImageFont.truetype("verdanab.ttf", height)
+ size = font.getsize(text)
+ img = Image.new("1",size,"black")
+ draw = ImageDraw.Draw(img)
+ draw.text((0, 0), text, "white", font=font)
+ pixels = np.array(img, dtype=np.uint8)
+ pixels3d = self.matrix_add_depth(pixels, color)
+ return pixels3d
+
+
+ def get_fallback(self):
+ hour = self.time_converter()
+ day = self.date_converter()
+ face1 = hour + day
+ face1_3d = self.matrix_add_depth(face1)
+
+ face2_3d = face3_3d = np.zeros((16,16,3))
return [face1_3d, face2_3d, face3_3d]
\ No newline at end of file
diff --git a/clock/helpers/shapes.py b/clock/helpers/shapes.py
index 9e8c108..16d2be6 100644
--- a/clock/helpers/shapes.py
+++ b/clock/helpers/shapes.py
@@ -1,103 +1,103 @@
-import numpy as np
-
-digits = {
- "1" : np.array([
- [0,0,1],
- [0,0,1],
- [0,0,1],
- [0,0,1],
- [0,0,1]]),
- "2" : np.array([
- [1,1,1],
- [0,0,1],
- [1,1,1],
- [1,0,0],
- [1,1,1]]),
- "3" : np.array([
- [1,1,1],
- [0,0,1],
- [1,1,1],
- [0,0,1],
- [1,1,1]]),
- "4" : np.array([
- [1,0,1],
- [1,0,1],
- [1,1,1],
- [0,0,1],
- [0,0,1]]),
- "5" : np.array([
- [1,1,1],
- [1,0,0],
- [1,1,1],
- [0,0,1],
- [1,1,1]]),
- "6" : np.array([
- [1,1,1],
- [1,0,0],
- [1,1,1],
- [1,0,1],
- [1,1,1]]),
- "7" : np.array([
- [1,1,1],
- [0,0,1],
- [0,0,1],
- [0,0,1],
- [0,0,1]]),
- "8" : np.array([
- [1,1,1],
- [1,0,1],
- [1,1,1],
- [1,0,1],
- [1,1,1]]),
- "9" : np.array([
- [1,1,1],
- [1,0,1],
- [1,1,1],
- [0,0,1],
- [1,1,1]]),
- "0" : np.array([
- [1,1,1],
- [1,0,1],
- [1,0,1],
- [1,0,1],
- [1,1,1]]),
- "-" : np.array([
- [0,0,0],
- [0,0,0],
- [1,1,1],
- [0,0,0],
- [0,0,0]]),
- "-1" : np.array([
- [0,0,1],
- [0,0,1],
- [1,1,1],
- [0,0,1],
- [0,0,1]]),
- "error" : np.array([
- [1,0,1],
- [1,0,1],
- [0,1,0],
- [1,0,1],
- [1,0,1]]),
-}
-
-
-weather_categories = {
- "Clouds": "cloud",
- "Rain": "rain and cloud",
- "Thunderstorm": "thunder and cloud",
- "Drizzle": "rain and cloud",
- "Snow": "snow and cloud",
- "Clear": "sun",
- "Mist": "fog and clouds",
- "Smoke": "Smoke",
- "Haze": "Haze",
- "Dust": "Dust",
- "Fog": "fog",
- "Sand": "Sand",
- "Dust": "Dust",
- "Ash": "Ash",
- "Squal": "Squal",
- "Tornado": "Tornado",
- "error" : "moon"
+import numpy as np
+
+digits = {
+ "1" : np.array([
+ [0,0,1],
+ [0,0,1],
+ [0,0,1],
+ [0,0,1],
+ [0,0,1]]),
+ "2" : np.array([
+ [1,1,1],
+ [0,0,1],
+ [1,1,1],
+ [1,0,0],
+ [1,1,1]]),
+ "3" : np.array([
+ [1,1,1],
+ [0,0,1],
+ [1,1,1],
+ [0,0,1],
+ [1,1,1]]),
+ "4" : np.array([
+ [1,0,1],
+ [1,0,1],
+ [1,1,1],
+ [0,0,1],
+ [0,0,1]]),
+ "5" : np.array([
+ [1,1,1],
+ [1,0,0],
+ [1,1,1],
+ [0,0,1],
+ [1,1,1]]),
+ "6" : np.array([
+ [1,1,1],
+ [1,0,0],
+ [1,1,1],
+ [1,0,1],
+ [1,1,1]]),
+ "7" : np.array([
+ [1,1,1],
+ [0,0,1],
+ [0,0,1],
+ [0,0,1],
+ [0,0,1]]),
+ "8" : np.array([
+ [1,1,1],
+ [1,0,1],
+ [1,1,1],
+ [1,0,1],
+ [1,1,1]]),
+ "9" : np.array([
+ [1,1,1],
+ [1,0,1],
+ [1,1,1],
+ [0,0,1],
+ [1,1,1]]),
+ "0" : np.array([
+ [1,1,1],
+ [1,0,1],
+ [1,0,1],
+ [1,0,1],
+ [1,1,1]]),
+ "-" : np.array([
+ [0,0,0],
+ [0,0,0],
+ [1,1,1],
+ [0,0,0],
+ [0,0,0]]),
+ "-1" : np.array([
+ [0,0,1],
+ [0,0,1],
+ [1,1,1],
+ [0,0,1],
+ [0,0,1]]),
+ "error" : np.array([
+ [1,0,1],
+ [1,0,1],
+ [0,1,0],
+ [1,0,1],
+ [1,0,1]]),
+}
+
+
+weather_categories = {
+ "Clouds": "cloud",
+ "Rain": "rain and cloud",
+ "Thunderstorm": "thunder and cloud",
+ "Drizzle": "rain and cloud",
+ "Snow": "snow and cloud",
+ "Clear": "sun",
+ "Mist": "fog and clouds",
+ "Smoke": "Smoke",
+ "Haze": "Haze",
+ "Dust": "Dust",
+ "Fog": "fog",
+ "Sand": "Sand",
+ "Dust": "Dust",
+ "Ash": "Ash",
+ "Squal": "Squal",
+ "Tornado": "Tornado",
+ "error" : "moon"
}
\ No newline at end of file
diff --git a/clock/helpers/timer.py b/clock/helpers/timer.py
index 8be6e7c..f50b054 100644
--- a/clock/helpers/timer.py
+++ b/clock/helpers/timer.py
@@ -1,30 +1,30 @@
-from threading import Timer
-import time
-
-class RepeatedTimer(object):
- def __init__(self, interval, function, *args, **kwargs):
- self._timer = None
- self.interval = interval
- self.function = function
- self.args = args
- self.kwargs = kwargs
- self.is_running = False
- self.next_call = time.time()
- self.start()
-
- def _run(self):
- self.is_running = False
- self.start()
- self.function(*self.args, **self.kwargs)
-
- def start(self):
- if not self.is_running:
- self.next_call += self.interval
- self._timer = Timer(self.next_call - time.time(), self._run)
- self._timer.start()
- self.is_running = True
-
- def stop(self):
- self._timer.cancel()
- self.is_running = False
+from threading import Timer
+import time
+
+class RepeatedTimer(object):
+ def __init__(self, interval, function, *args, **kwargs):
+ self._timer = None
+ self.interval = interval
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.is_running = False
+ self.next_call = time.time()
+ self.start()
+
+ def _run(self):
+ self.is_running = False
+ self.start()
+ self.function(*self.args, **self.kwargs)
+
+ def start(self):
+ if not self.is_running:
+ self.next_call += self.interval
+ self._timer = Timer(self.next_call - time.time(), self._run)
+ self._timer.start()
+ self.is_running = True
+
+ def stop(self):
+ self._timer.cancel()
+ self.is_running = False
\ No newline at end of file
diff --git a/dashboard/__init__.py b/dashboard/__init__.py
index dcf2c80..9a5984d 100644
--- a/dashboard/__init__.py
+++ b/dashboard/__init__.py
@@ -1 +1 @@
-# Placeholder
+# Placeholder
diff --git a/dashboard/d_out.py b/dashboard/d_out.py
index 1a24124..e1f2fcc 100644
--- a/dashboard/d_out.py
+++ b/dashboard/d_out.py
@@ -1,299 +1,299 @@
-import dash
-import dash_bootstrap_components as dbc
-import dash_html_components as html
-import dash_core_components as dcc
-import plotly.graph_objects as go
-from dash.dependencies import Input, Output
-
-import locale
-locale.setlocale(locale.LC_TIME, "de_DE.utf8")
-#from dash.dependencies import Input, Output
-
-import datetime
-import time
-import xmltodict
-
-import requests
-from threading import Thread
-
-
-from . import helpers
-
-class DashBoard():
- """"""
- # added by the launcher, we have self.modules (dict)
-
- def __init__(self, port):
- ## pre-sets
-
- self.inter_margin = "1em"
- self.host_ip = "0.0.0.0"
- self.port = port
-
- ex_css = [dbc.themes.BOOTSTRAP]
- self.app = dash.Dash(__name__, external_stylesheets=ex_css)
- self.app.layout = html.Div([
- html.Div(id = 'layout-update', className = "content", style={"padding":self.inter_margin},),
- dcc.Interval(
- id='interval-component',
- interval=3600*1000, # in milliseconds
- n_intervals=0
- )
- ]#,style={'background-image':'url("static/background.jpg")'}
- )
-
- @self.app.callback(Output('layout-update','children'), Input('interval-component','n_intervals'))
- def update_layout(n):
- self.set_stats()
- kids = [
- self.card_header(),
- dbc.CardColumns([
- # self.card_weather(),
- *self.cards_lists(),
- self.card_bot_stats(),
- self.card_news(),
- self.card_xkcd(),
- self.card_sensor_stats(),
- ])
- ]
- return kids
-
-
- def start(self):
- flaskThread = Thread(target=app.run_server, kwargs={"host": self.host_ip, "port": self.port}).start()
- #self.app.run_server()#, debug=True)
-
-
- def card_header(self):
- today = datetime.date.today().strftime("%A, der %d. %B %Y")
- card = dbc.Card(
- [ dbc.CardImg(src="static/header.jpg", top=True, bottom=False,
- title="Header", alt='Header image'),
- dbc.CardBody([html.H3(today, className="card-title")]),
- ],
- color="dark",
- style = {"width" : "100%", "margin-bottom":self.inter_margin},
- inverse=True,
- )
- return card
-
-
- def cards_lists(self):
- ret = []
- for l in self.persistence["global"]["lists"].keys():
- 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]
- card = dbc.Card(
- [
- dbc.CardBody([
- html.H4("Liste '" + l + "':", className="card-title"),
- dbc.ListGroup(html_content, flush=True, style={"color":"black"})
- ]),
- ],
- color="dark",
- inverse=True,
- )
- ret.append(card)
- return ret
-
-
- def card_bot_stats(self):
- if not self.stat_graph:
- self.set_stats()
-
- card = dbc.Card(
- [
- dbc.CardBody([
- html.H4("Chat-Metriken", className="card-title"),
- dcc.Graph(figure=self.stat_graph, config={'displayModeBar': False})
- ]),
- ],
- color="dark",
- inverse=True,
- )
- return card
-
- def card_sensor_stats(self):
- fig = go.Figure()
- sensors = self.persistence["clock"]["sensors"]
- time = sensors["time"]
- time = [t - time[0] for t in time] # rescale
- for sensor in sensors.keys():
- if sensor != "time":
- fig.add_trace(go.Scatter(x=time, y=sensors[sensor], mode="lines", text=sensor, line=dict(width=4)))
-
- fig.layout.update(
- # xaxis = {
- # 'showgrid': False, # thin lines in the background
- # 'zeroline': False, # thick line at x=0
- # 'visible': False, # numbers below
- # }, # the same for yaxis
- # yaxis = {
- # 'showgrid': False, # thin lines in the background
- # 'zeroline': False, # thick line at x=0
- # 'visible': False, # numbers below
- # }, # the same for yaxis
-
- showlegend=False,
- # margin=dict(l=0, r=0, t=0, b=0),
- # paper_bgcolor='rgba(0,0,0,0)',
- # plot_bgcolor='rgba(0,0,0,0)',
- )
-
- card = dbc.Card(
- [
- dbc.CardBody([
- html.H4("Sensor-Metriken", className="card-title"),
- dcc.Graph(figure=fig, config={'displayModeBar': False})
- ]),
- ],
- color="dark",
- inverse=True,
- )
- return card
-
-
- def card_weather(self):
- def weather_item(name, overview, temps):
- if len(temps) == 2:
- temp = "🌡(❄): " + str(temps[0]) + "° ➡ 🌡(🔥): " + str(temps[1]) + "°"
- else:
- temp = "🌡: " + str(temps[0]) + "°"
- temp_line = html.P(temp, className="mb-1")
-
- it = html.A([
- html.Div([
- html.H5(name, className="mb-1"),
- html.Span(categories[overview], className="badge badge-primary badge-pill")
- ],
- className="d-flex w-100 justify-content-between"),
- temp_line,
- ],
- href="#", className="list-group-item bg-dark list-group-item-action text-light"
- )
-
- return it
-
- 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",}
- today = datetime.datetime.today().weekday()
-
- body = []
-
- try:
- bot = self.modules["bot"]
- content = bot.api_weather.show_weather([47.3769, 8.5417]) # still zürich
-
- wt = content.pop(0)
- body.append(weather_item("Jetzt", wt["short"], wt["temps"]))
-
- for i, day in enumerate(content):
- tmp = []
- if i == 0:
- day_name = "Heute"
- else:
- day_name = days[(today + i) % 7]
-
- body.append(weather_item(day_name, day["short"], day["temps"]))
- body = dbc.ListGroup(body, flush=True, style={"color":"black"})
-
-
- except:
- body.append(html.H6("Konnte nicht geladen werden"))
-
- card = dbc.Card(
- [dbc.CardBody([
- html.H4("Wetter", className="card-title"),
- body])],
- color="dark",
- inverse=True,
- )
- return card
-
-
- def card_news(self):
- try:
- card = dbc.Card([
- dbc.CardBody([html.Iframe(src="https://nzz.ch", style={"border":"none", "min-height":"30em", "width":"100%"})])
- ],
- color="dark",
- inverse=True,
- )
- except:
- card = card = dbc.Card([
- dbc.CardBody([
- html.H4("Could not load NEWS", className="card-title"),
- ])
- ],
- color="dark",
- inverse=True,
- )
- return card
-
-
- def card_xkcd(self):
- try:
- xml = requests.get("https://xkcd.com/atom.xml").content
- feed = xmltodict.parse(xml)
- title = feed["feed"]["entry"][0]["title"]
- img = feed["feed"]["entry"][0]["summary"]["#text"]
- i1 = img.find('"') +1
- i2 = img.find('"', i1+1)
- i3 = img.find('"', i2+1) + 1
- i4 = img.find('"', i3+1)
- img_src = img[i1:i2]
- img_alt = img[i3:i4]
- card = dbc.Card([
- dbc.CardBody([
- html.H4(title, className="card-title"),
- html.Img(src=img_src, style={"width":"100%"}),
- html.P(img_alt)
- ])
- ],
- color="dark",
- inverse=True,
- )
- except:
- card = dbc.Card([
- dbc.CardBody([
- html.H4("Could not load XKCD", className="card-title"),
- ])
- ],
- color="dark",
- inverse=True,
- )
- return card
-
-
- ######### helper:
- def set_stats(self):
- 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"])
- xe, ye = helpers.clean_axis(self.persistence["bot"]["execute_activity"]["hour"], self.persistence["bot"]["execute_activity"]["count"])
-
- 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=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.update_xaxes(showgrid=False)
- fig.update_yaxes(showgrid=False)
- fig.layout.update(
- xaxis = {
- 'showgrid': False, # thin lines in the background
- 'zeroline': False, # thick line at x=0
- 'visible': False, # numbers below
- }, # the same for yaxis
- yaxis = {
- 'showgrid': False, # thin lines in the background
- 'zeroline': False, # thick line at x=0
- 'visible': False, # numbers below
- }, # the same for yaxis
-
- showlegend=False,
- margin=dict(l=0, r=0, t=0, b=0),
- paper_bgcolor='rgba(0,0,0,0)',
- plot_bgcolor='rgba(0,0,0,0)',
- )
-
- self.stat_graph = fig
+import dash
+import dash_bootstrap_components as dbc
+import dash_html_components as html
+import dash_core_components as dcc
+import plotly.graph_objects as go
+from dash.dependencies import Input, Output
+
+import locale
+locale.setlocale(locale.LC_TIME, "de_DE.utf8")
+#from dash.dependencies import Input, Output
+
+import datetime
+import time
+import xmltodict
+
+import requests
+from threading import Thread
+
+
+from . import helpers
+
+class DashBoard():
+ """"""
+ # added by the launcher, we have self.modules (dict)
+
+ def __init__(self, port):
+ ## pre-sets
+
+ self.inter_margin = "1em"
+ self.host_ip = "0.0.0.0"
+ self.port = port
+
+ ex_css = [dbc.themes.BOOTSTRAP]
+ self.app = dash.Dash(__name__, external_stylesheets=ex_css)
+ self.app.layout = html.Div([
+ html.Div(id = 'layout-update', className = "content", style={"padding":self.inter_margin},),
+ dcc.Interval(
+ id='interval-component',
+ interval=3600*1000, # in milliseconds
+ n_intervals=0
+ )
+ ]#,style={'background-image':'url("static/background.jpg")'}
+ )
+
+ @self.app.callback(Output('layout-update','children'), Input('interval-component','n_intervals'))
+ def update_layout(n):
+ self.set_stats()
+ kids = [
+ self.card_header(),
+ dbc.CardColumns([
+ # self.card_weather(),
+ *self.cards_lists(),
+ self.card_bot_stats(),
+ self.card_news(),
+ self.card_xkcd(),
+ self.card_sensor_stats(),
+ ])
+ ]
+ return kids
+
+
+ def start(self):
+ flaskThread = Thread(target=app.run_server, kwargs={"host": self.host_ip, "port": self.port}).start()
+ #self.app.run_server()#, debug=True)
+
+
+ def card_header(self):
+ today = datetime.date.today().strftime("%A, der %d. %B %Y")
+ card = dbc.Card(
+ [ dbc.CardImg(src="static/header.jpg", top=True, bottom=False,
+ title="Header", alt='Header image'),
+ dbc.CardBody([html.H3(today, className="card-title")]),
+ ],
+ color="dark",
+ style = {"width" : "100%", "margin-bottom":self.inter_margin},
+ inverse=True,
+ )
+ return card
+
+
+ def cards_lists(self):
+ ret = []
+ for l in self.persistence["global"]["lists"].keys():
+ 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]
+ card = dbc.Card(
+ [
+ dbc.CardBody([
+ html.H4("Liste '" + l + "':", className="card-title"),
+ dbc.ListGroup(html_content, flush=True, style={"color":"black"})
+ ]),
+ ],
+ color="dark",
+ inverse=True,
+ )
+ ret.append(card)
+ return ret
+
+
+ def card_bot_stats(self):
+ if not self.stat_graph:
+ self.set_stats()
+
+ card = dbc.Card(
+ [
+ dbc.CardBody([
+ html.H4("Chat-Metriken", className="card-title"),
+ dcc.Graph(figure=self.stat_graph, config={'displayModeBar': False})
+ ]),
+ ],
+ color="dark",
+ inverse=True,
+ )
+ return card
+
+ def card_sensor_stats(self):
+ fig = go.Figure()
+ sensors = self.persistence["clock"]["sensors"]
+ time = sensors["time"]
+ time = [t - time[0] for t in time] # rescale
+ for sensor in sensors.keys():
+ if sensor != "time":
+ fig.add_trace(go.Scatter(x=time, y=sensors[sensor], mode="lines", text=sensor, line=dict(width=4)))
+
+ fig.layout.update(
+ # xaxis = {
+ # 'showgrid': False, # thin lines in the background
+ # 'zeroline': False, # thick line at x=0
+ # 'visible': False, # numbers below
+ # }, # the same for yaxis
+ # yaxis = {
+ # 'showgrid': False, # thin lines in the background
+ # 'zeroline': False, # thick line at x=0
+ # 'visible': False, # numbers below
+ # }, # the same for yaxis
+
+ showlegend=False,
+ # margin=dict(l=0, r=0, t=0, b=0),
+ # paper_bgcolor='rgba(0,0,0,0)',
+ # plot_bgcolor='rgba(0,0,0,0)',
+ )
+
+ card = dbc.Card(
+ [
+ dbc.CardBody([
+ html.H4("Sensor-Metriken", className="card-title"),
+ dcc.Graph(figure=fig, config={'displayModeBar': False})
+ ]),
+ ],
+ color="dark",
+ inverse=True,
+ )
+ return card
+
+
+ def card_weather(self):
+ def weather_item(name, overview, temps):
+ if len(temps) == 2:
+ temp = "🌡(❄): " + str(temps[0]) + "° ➡ 🌡(🔥): " + str(temps[1]) + "°"
+ else:
+ temp = "🌡: " + str(temps[0]) + "°"
+ temp_line = html.P(temp, className="mb-1")
+
+ it = html.A([
+ html.Div([
+ html.H5(name, className="mb-1"),
+ html.Span(categories[overview], className="badge badge-primary badge-pill")
+ ],
+ className="d-flex w-100 justify-content-between"),
+ temp_line,
+ ],
+ href="#", className="list-group-item bg-dark list-group-item-action text-light"
+ )
+
+ return it
+
+ 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",}
+ today = datetime.datetime.today().weekday()
+
+ body = []
+
+ try:
+ bot = self.modules["bot"]
+ content = bot.api_weather.show_weather([47.3769, 8.5417]) # still zürich
+
+ wt = content.pop(0)
+ body.append(weather_item("Jetzt", wt["short"], wt["temps"]))
+
+ for i, day in enumerate(content):
+ tmp = []
+ if i == 0:
+ day_name = "Heute"
+ else:
+ day_name = days[(today + i) % 7]
+
+ body.append(weather_item(day_name, day["short"], day["temps"]))
+ body = dbc.ListGroup(body, flush=True, style={"color":"black"})
+
+
+ except:
+ body.append(html.H6("Konnte nicht geladen werden"))
+
+ card = dbc.Card(
+ [dbc.CardBody([
+ html.H4("Wetter", className="card-title"),
+ body])],
+ color="dark",
+ inverse=True,
+ )
+ return card
+
+
+ def card_news(self):
+ try:
+ card = dbc.Card([
+ dbc.CardBody([html.Iframe(src="https://nzz.ch", style={"border":"none", "min-height":"30em", "width":"100%"})])
+ ],
+ color="dark",
+ inverse=True,
+ )
+ except:
+ card = card = dbc.Card([
+ dbc.CardBody([
+ html.H4("Could not load NEWS", className="card-title"),
+ ])
+ ],
+ color="dark",
+ inverse=True,
+ )
+ return card
+
+
+ def card_xkcd(self):
+ try:
+ xml = requests.get("https://xkcd.com/atom.xml").content
+ feed = xmltodict.parse(xml)
+ title = feed["feed"]["entry"][0]["title"]
+ img = feed["feed"]["entry"][0]["summary"]["#text"]
+ i1 = img.find('"') +1
+ i2 = img.find('"', i1+1)
+ i3 = img.find('"', i2+1) + 1
+ i4 = img.find('"', i3+1)
+ img_src = img[i1:i2]
+ img_alt = img[i3:i4]
+ card = dbc.Card([
+ dbc.CardBody([
+ html.H4(title, className="card-title"),
+ html.Img(src=img_src, style={"width":"100%"}),
+ html.P(img_alt)
+ ])
+ ],
+ color="dark",
+ inverse=True,
+ )
+ except:
+ card = dbc.Card([
+ dbc.CardBody([
+ html.H4("Could not load XKCD", className="card-title"),
+ ])
+ ],
+ color="dark",
+ inverse=True,
+ )
+ return card
+
+
+ ######### helper:
+ def set_stats(self):
+ 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"])
+ xe, ye = helpers.clean_axis(self.persistence["bot"]["execute_activity"]["hour"], self.persistence["bot"]["execute_activity"]["count"])
+
+ 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=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.update_xaxes(showgrid=False)
+ fig.update_yaxes(showgrid=False)
+ fig.layout.update(
+ xaxis = {
+ 'showgrid': False, # thin lines in the background
+ 'zeroline': False, # thick line at x=0
+ 'visible': False, # numbers below
+ }, # the same for yaxis
+ yaxis = {
+ 'showgrid': False, # thin lines in the background
+ 'zeroline': False, # thick line at x=0
+ 'visible': False, # numbers below
+ }, # the same for yaxis
+
+ showlegend=False,
+ margin=dict(l=0, r=0, t=0, b=0),
+ paper_bgcolor='rgba(0,0,0,0)',
+ plot_bgcolor='rgba(0,0,0,0)',
+ )
+
+ self.stat_graph = fig
diff --git a/dashboard/helpers.py b/dashboard/helpers.py
index 7a834a7..e32b9f3 100644
--- a/dashboard/helpers.py
+++ b/dashboard/helpers.py
@@ -1,18 +1,18 @@
-
-def clean_axis(x,y):
- """x is the time the point in y was taken"""
- try:
- xn = range(x[0], x[-1]+1)
- yn = []
- count = 0
- for x_i in xn:
- if x_i in x:
- yn.append(y[count])
- count += 1
- else:
- yn.append(0)
- xn = [i - int(x[0]) for i in xn]
- except:
- xn = []
- yn = []
+
+def clean_axis(x,y):
+ """x is the time the point in y was taken"""
+ try:
+ xn = range(x[0], x[-1]+1)
+ yn = []
+ count = 0
+ for x_i in xn:
+ if x_i in x:
+ yn.append(y[count])
+ count += 1
+ else:
+ yn.append(0)
+ xn = [i - int(x[0]) for i in xn]
+ except:
+ xn = []
+ yn = []
return xn, yn
\ No newline at end of file
diff --git a/dashboard/static/test.css b/dashboard/static/test.css
index 5c79840..ee614d6 100644
--- a/dashboard/static/test.css
+++ b/dashboard/static/test.css
@@ -1,33 +1,33 @@
-div.header {
- position: fixed;
- width: 100%;
- height: var(--heading-height);
-}
-
-/* Create angled background with 'before' pseudo-element */
-header::before {
- content: "";
- display: block;
- position: absolute;
- left: 0;
- bottom: 6em;
- width: 100%;
- height: calc(var(--heading-height) + 10em);
- z-index: -1;
- transform: skewY(-3.5deg);
- background:
- 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,
- linear-gradient(#4e4376, #2b5876);
- background-size: cover;
- border-bottom: .2em solid #fff;
-}
-
-h1 {
- font-size: calc(2.8em + 2.6vw);
- font-weight: 500;
- letter-spacing: .01em;
- padding: 6rem 0 0 4.5rem;
- text-shadow: .022em .022em .022em #111;
- color: #fff;
-}
+div.header {
+ position: fixed;
+ width: 100%;
+ height: var(--heading-height);
+}
+
+/* Create angled background with 'before' pseudo-element */
+header::before {
+ content: "";
+ display: block;
+ position: absolute;
+ left: 0;
+ bottom: 6em;
+ width: 100%;
+ height: calc(var(--heading-height) + 10em);
+ z-index: -1;
+ transform: skewY(-3.5deg);
+ background:
+ 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,
+ linear-gradient(#4e4376, #2b5876);
+ background-size: cover;
+ border-bottom: .2em solid #fff;
+}
+
+h1 {
+ font-size: calc(2.8em + 2.6vw);
+ font-weight: 500;
+ letter-spacing: .01em;
+ padding: 6rem 0 0 4.5rem;
+ text-shadow: .022em .022em .022em #111;
+ color: #fff;
+}
diff --git a/launcher.py b/launcher.py
index 35001f6..6790e60 100644
--- a/launcher.py
+++ b/launcher.py
@@ -1,73 +1,73 @@
-import logging
-
-from persistence import p_io, p_out
-
-
-logger = logging.getLogger(__name__)
-
-
-class Launcher:
- """base launcher that launches other submodules"""
-
- def __init__(self, **modules):
- """"""
- self.persistence = p_io.PersistentDict("persistence/prst.json")
- self.db = p_out.DataBaseConnector()
-
- logger.info(self.__class__.__name__ + " initialized")
-
- self.modules = modules
- if len(self.persistence) == 0:
- self.init_persistence()
- self.persistence["global"]["reboots"] += 1
-
- self.launch_modules()
-
-
-
- def launch_modules(self):
-
- for module in self.modules.values():
- logger.info("Starting module "+ module.__class__.__name__)
- module.modules = self.modules
- module.persistence = self.persistence
- module.db = self.db # pooled ie multithreaded
- module.start()
-
-
- def init_persistence(self):
- logger.warning("No persistence found, created a new one")
-
- self.persistence["global"] ={
- "lists" : {},
- "reboots": 0
- }
-
- for m_name in self.modules.keys():
- data = {}
- if m_name == "bot":
- data = {
- "send_activity" : {"hour":[], "count":[]},
- "receive_activity" : {"hour":[], "count":[]},
- "execute_activity" : {"hour":[], "count":[]},
- "log": [],
- "chat_members": {},
- "aliases" : {}
- }
- if m_name == "clock":
- data = {
- "sensors" : {
- "time" : [],
- "temperature":[],
- "humidity":[],
- "brightness" : [],
- }
- }
-
- self.persistence[m_name] = data
-
-
-
-########################################################################
-## Aand liftoff!
-# Launcher()
+import logging
+
+from persistence import p_io, p_out
+
+
+logger = logging.getLogger(__name__)
+
+
+class Launcher:
+ """base launcher that launches other submodules"""
+
+ def __init__(self, **modules):
+ """"""
+ self.persistence = p_io.PersistentDict("persistence/prst.json")
+ self.db = p_out.DataBaseConnector()
+
+ logger.info(self.__class__.__name__ + " initialized")
+
+ self.modules = modules
+ if len(self.persistence) == 0:
+ self.init_persistence()
+ self.persistence["global"]["reboots"] += 1
+
+ self.launch_modules()
+
+
+
+ def launch_modules(self):
+
+ for module in self.modules.values():
+ logger.info("Starting module "+ module.__class__.__name__)
+ module.modules = self.modules
+ module.persistence = self.persistence
+ module.db = self.db # pooled ie multithreaded
+ module.start()
+
+
+ def init_persistence(self):
+ logger.warning("No persistence found, created a new one")
+
+ self.persistence["global"] ={
+ "lists" : {},
+ "reboots": 0
+ }
+
+ for m_name in self.modules.keys():
+ data = {}
+ if m_name == "bot":
+ data = {
+ "send_activity" : {"hour":[], "count":[]},
+ "receive_activity" : {"hour":[], "count":[]},
+ "execute_activity" : {"hour":[], "count":[]},
+ "log": [],
+ "chat_members": {},
+ "aliases" : {}
+ }
+ if m_name == "clock":
+ data = {
+ "sensors" : {
+ "time" : [],
+ "temperature":[],
+ "humidity":[],
+ "brightness" : [],
+ }
+ }
+
+ self.persistence[m_name] = data
+
+
+
+########################################################################
+## Aand liftoff!
+# Launcher()
diff --git a/persistence/README.md b/persistence/README.md
index 1ac5653..064587d 100644
--- a/persistence/README.md
+++ b/persistence/README.md
@@ -1,12 +1,12 @@
-## What is happening here?
-
-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
-* advanced logging and analytics
-
-### 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.
-
-
-### Logging
+## What is happening here?
+
+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
+* advanced logging and analytics
+
+### 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.
+
+
+### Logging
A chunky sqlite-db which periodically gets new entries. From all modules. Ideally this db is then visualized through grafana. WIP
\ No newline at end of file
diff --git a/persistence/models.py b/persistence/models.py
index 17ad086..d03fac9 100644
--- a/persistence/models.py
+++ b/persistence/models.py
@@ -1,71 +1,55 @@
-from peewee import *
-# from playhouse.pool import PooledMySQLDatabase
-from playhouse.shortcuts import ReconnectMixin
-import logging
-logger = logging.getLogger(__name__)
-
-
-
-db = DatabaseProxy()
-# set the nature of the db at runtime
-
-class ReconnectDataBase(ReconnectMixin, MySQLDatabase):
- pass
-
-
-class DBModel(Model):
- # specific to the above DB
- class Meta:
- database = db
-
- def save(self):
- # fail-safe writing of the db-object. Usually threaded because the caller is threaded
- try:
- # db.connect()
- super().save()
- # db.close()
- except Exception as e:
- logger.error("Could not write to db. Dropping content of {}".format(self.__class__.__name__))
- logger.error(e)
- # db.atomic().rollback()
-
- # def get(self, *query, **filters):
- # try:
- # return super().get(*query, **filters)
- # except Exception as e:
- # logger.error("Error while executing get: {}".format(e))
- # print(query, filters)
-
-
-class Metric(DBModel):
- time = DateTimeField()
-
-
-### Actual metrics:
-
-class SensorMetric(Metric):
- # this is a continuous metric
- temperature = IntegerField()
- humidity = IntegerField()
- luminosity = IntegerField()
- default = {"temperature": 100, "humidity": 100, "luminosity": 100}
-
-
-
-class ChatMetric(Metric):
- read = BooleanField()
- send = BooleanField()
- execute = BooleanField()
- default = {"read": False, "send": False, "execute": False}
-
-
-class ErrorMetric(Metric):
- # same as above
- error = TextField()
- default = {"error": "SQL connection broke off"}
-
-
-class List(DBModel):
- name = CharField(unique=True)
- content = TextField() # unlimited length, use to serialise list into
- default = {"content": "SQL connection broke off"}
\ No newline at end of file
+from datetime import datetime
+from peewee import *
+import logging
+import json
+
+logger = logging.getLogger(__name__)
+
+
+def create_tables(db):
+ db.create_tables([SensorMetric, ChatMetric, ErrorMetric, List])
+
+db = DatabaseProxy()
+# set the nature of the db at runtime
+
+
+class DBModel(Model):
+ # specific to the above DB
+ class Meta:
+ database = db
+
+class Metric(DBModel):
+ time = DateTimeField(default = datetime.now())
+
+
+
+### Actual metrics:
+
+class SensorMetric(Metric):
+ # this is a continuous metric
+ temperature = IntegerField()
+ humidity = IntegerField()
+ luminosity = IntegerField()
+
+
+class ChatMetric(Metric):
+ read = BooleanField()
+ send = BooleanField()
+ execute = BooleanField()
+
+
+class ErrorMetric(Metric):
+ # same as above
+ error = TextField()
+
+
+class List(DBModel):
+ name = CharField(unique=True)
+ content = TextField() # unlimited length, use to serialise list into
+
+ @property
+ def content_as_list(self):
+ return json.loads(self.content)
+
+ def set_content(self, list_content):
+ self.content = json.dumps(list_content)
\ No newline at end of file
diff --git a/persistence/p_io.py b/persistence/p_io.py
index a9dac9f..6dcd3c9 100644
--- a/persistence/p_io.py
+++ b/persistence/p_io.py
@@ -1,87 +1,87 @@
-import json
-import os
-
-
-class PersistentDict(dict):
- """Extended dict that writes its content to a file every time a value is changed"""
-
- def __init__(self, file_name, *args, **kwargs):
- """initialization of the dict and of the required files"""
- super().__init__(*args, **kwargs)
-
- self.path = file_name
- self.last_action = ""
- try:
- self.read_dict()
- except:
- with open(self.path, "a") as f:
- f.write("{}")
-
-
- ## helper - functions
- def write_dict(self):
- with open(self.path, "w") as f:
- json.dump(self, f)
- self.last_action = "w"
-
- def read_dict(self):
- with open(self.path) as f:
- tmp = dict(json.load(f))
- for key in tmp:
- super().__setitem__(key, tmp[key])
- self.last_action = "r"
-
-
- ## extended dictionary - logic
- def __setitem__(self, key, value):
- if self.last_action != "r":
- self.read_dict()
- # not sure if the step to read is necessary, but I'll keep it for safety
- super().__setitem__(key,value)
- self.write_dict() # writes 'self' to a json file.
-
- def __getitem__(self, key):
- if self.last_action != "r":
- self.read_dict()
-
- ret_val = super().__getitem__(key)
- if type(ret_val) != int and type(ret_val) != str:
- ret_val = create_struct(type(ret_val), key, self, ret_val)
-
- return ret_val
-
- def clear(self):
- super().clear()
- self.write_dict()
- return {}
-
-
-
-def create_struct(struct_type, own_name, parent_name, *args, **kwargs):
- class HookedStruct(struct_type):
-
- def __init__(self, own_name, parent_name, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.name = own_name
- self.parent = parent_name
-
- def __setitem__(self, *args, **kwargs):
- super().__setitem__(*args, **kwargs)
- self.parent.__setitem__(self.name, self)
-
- def __getitem__(self, *args, **kwargs):
- ret_val = super().__getitem__(*args, **kwargs)
- if type(ret_val) != int and type(ret_val) != str:
- ret_val = create_struct(type(ret_val), args[0], self, ret_val)
- return ret_val
-
- def pop(self, *args):
- retvalue = super().pop(*args)
- self.parent.__setitem__(self.name, self)
- return retvalue
-
- def append(self, *args):
- super().append(*args)
- self.parent.__setitem__(self.name, self)
-
- return HookedStruct(own_name, parent_name, *args, **kwargs)
+import json
+import os
+
+
+class PersistentDict(dict):
+ """Extended dict that writes its content to a file every time a value is changed"""
+
+ def __init__(self, file_name, *args, **kwargs):
+ """initialization of the dict and of the required files"""
+ super().__init__(*args, **kwargs)
+
+ self.path = file_name
+ self.last_action = ""
+ try:
+ self.read_dict()
+ except:
+ with open(self.path, "a") as f:
+ f.write("{}")
+
+
+ ## helper - functions
+ def write_dict(self):
+ with open(self.path, "w") as f:
+ json.dump(self, f)
+ self.last_action = "w"
+
+ def read_dict(self):
+ with open(self.path) as f:
+ tmp = dict(json.load(f))
+ for key in tmp:
+ super().__setitem__(key, tmp[key])
+ self.last_action = "r"
+
+
+ ## extended dictionary - logic
+ def __setitem__(self, key, value):
+ if self.last_action != "r":
+ self.read_dict()
+ # not sure if the step to read is necessary, but I'll keep it for safety
+ super().__setitem__(key,value)
+ self.write_dict() # writes 'self' to a json file.
+
+ def __getitem__(self, key):
+ if self.last_action != "r":
+ self.read_dict()
+
+ ret_val = super().__getitem__(key)
+ if type(ret_val) != int and type(ret_val) != str:
+ ret_val = create_struct(type(ret_val), key, self, ret_val)
+
+ return ret_val
+
+ def clear(self):
+ super().clear()
+ self.write_dict()
+ return {}
+
+
+
+def create_struct(struct_type, own_name, parent_name, *args, **kwargs):
+ class HookedStruct(struct_type):
+
+ def __init__(self, own_name, parent_name, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.name = own_name
+ self.parent = parent_name
+
+ def __setitem__(self, *args, **kwargs):
+ super().__setitem__(*args, **kwargs)
+ self.parent.__setitem__(self.name, self)
+
+ def __getitem__(self, *args, **kwargs):
+ ret_val = super().__getitem__(*args, **kwargs)
+ if type(ret_val) != int and type(ret_val) != str:
+ ret_val = create_struct(type(ret_val), args[0], self, ret_val)
+ return ret_val
+
+ def pop(self, *args):
+ retvalue = super().pop(*args)
+ self.parent.__setitem__(self.name, self)
+ return retvalue
+
+ def append(self, *args):
+ super().append(*args)
+ self.parent.__setitem__(self.name, self)
+
+ return HookedStruct(own_name, parent_name, *args, **kwargs)
diff --git a/persistence/p_out.py b/persistence/p_out.py
deleted file mode 100644
index aa71edf..0000000
--- a/persistence/p_out.py
+++ /dev/null
@@ -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
diff --git a/req_pi.txt b/req_pi.txt
index 4358d48..17ecda2 100644
--- a/req_pi.txt
+++ b/req_pi.txt
@@ -1,17 +1,17 @@
-Adafruit-Blinka==6.10.3
-adafruit-circuitpython-dht==3.6.1
-Adafruit-PlatformDetect==3.14.2
-Adafruit-PureIO==1.1.9
-board==1.0
-importlib-metadata==4.6.0
-pkg-resources==0.0.0
-psycopg2==2.9.1
-psycopg2-binary==2.9.1
-pyftdi==0.53.1
-pyserial==3.5
-pyusb==1.1.1
-rpi-ws281x==4.3.0
-RPi.GPIO==0.7.0
-sysv-ipc==1.1.0
-typing-extensions==3.10.0.0
+Adafruit-Blinka==6.10.3
+adafruit-circuitpython-dht==3.6.1
+Adafruit-PlatformDetect==3.14.2
+Adafruit-PureIO==1.1.9
+board==1.0
+importlib-metadata==4.6.0
+pkg-resources==0.0.0
+psycopg2==2.9.1
+psycopg2-binary==2.9.1
+pyftdi==0.53.1
+pyserial==3.5
+pyusb==1.1.1
+rpi-ws281x==4.3.0
+RPi.GPIO==0.7.0
+sysv-ipc==1.1.0
+typing-extensions==3.10.0.0
zipp==3.4.1
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 886a3d2..66fa993 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/sql_as_rest_api b/sql_as_rest_api
deleted file mode 160000
index 1e24c14..0000000
--- a/sql_as_rest_api
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1e24c1491d2e80ce9d915b20f434c1e10fab3156
diff --git a/test.py b/test.py
deleted file mode 100644
index 0ea607f..0000000
--- a/test.py
+++ /dev/null
@@ -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()
\ No newline at end of file