diff --git a/.gitignore b/.gitignore index cc81706..0c2c0e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +**/VE + +#VS CODE files +.vscode + # Persistence prst.* diff --git a/bot/commands/alias.py b/bot/commands/alias.py index 5e94ed6..ec6fb4b 100644 --- a/bot/commands/alias.py +++ b/bot/commands/alias.py @@ -4,8 +4,8 @@ FIRST = range(1) class Alias(BotFunc): """create a new command for command-paths you often use""" - def __init__(self, dispatcher, prst): - super().__init__(prst) + def __init__(self, dispatcher, db): + super().__init__(db) self.dispatcher = dispatcher # do not interact with him yet! @@ -25,7 +25,6 @@ class Alias(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() test = self.dispatcher print(self.dispatcher.handlers[0]) keyboard = [ @@ -34,6 +33,7 @@ class Alias(BotFunc): [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 diff --git a/bot/commands/clock.py b/bot/commands/clock.py index 9e33bdd..b08d570 100644 --- a/bot/commands/clock.py +++ b/bot/commands/clock.py @@ -37,7 +37,6 @@ class Clock(BotFunc): return handler def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() keyboard = [ [InlineKeyboardButton("Make a wake-light", callback_data="wake")], [InlineKeyboardButton("Blink as alarm", callback_data="alarm")], @@ -208,4 +207,12 @@ class Clock(BotFunc): update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.") self.clock.run(output,(number, duration)) - return ConversationHandler.END \ No newline at end of file + 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 0fed99b..71d3869 100644 --- a/bot/commands/help.py +++ b/bot/commands/help.py @@ -6,8 +6,8 @@ FIRST, EXECUTE = range(2) class Help(BotFunc): """Shows the functions and their usage""" - def __init__(self, prst): - super().__init__(prst) + def __init__(self, db): + super().__init__(db) self.available_commands = {} @@ -39,7 +39,6 @@ class Help(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() keyboard = [ [ InlineKeyboardButton("All commands", callback_data="all"), @@ -47,6 +46,7 @@ class Help(BotFunc): ] ] 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: diff --git a/bot/commands/lists.py b/bot/commands/lists.py index 5c4a476..214b42d 100644 --- a/bot/commands/lists.py +++ b/bot/commands/lists.py @@ -9,8 +9,8 @@ NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5) class Lists(BotFunc): """Create and edit lists""" - def __init__(self, prst): - super().__init__(prst) + def __init__(self, db): + super().__init__(db) self.current_name = "" @@ -40,10 +40,12 @@ class Lists(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() - keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in self.persistence["global"]["lists"]] + [[InlineKeyboardButton("New list", callback_data="new")]] + 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 @@ -94,16 +96,16 @@ class Lists(BotFunc): def new_listname(self, update: Update, context: CallbackContext) -> None: name = update.message.text - if name not in self.persistence["global"]["lists"]: - self.persistence["global"]["lists"][name] = [] - + 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 - else: - update.message.reply_text("Oh no! That list already exists") + except Exception as e: + update.message.reply_text("Oh no! Encountered exception: {}".format(e)) return ConversationHandler.END @@ -117,7 +119,10 @@ class Lists(BotFunc): def list_remove(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(self.persistence["global"]["lists"][self.current_name])] + 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) @@ -127,7 +132,7 @@ class Lists(BotFunc): def list_clear(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - self.persistence["global"]["lists"][self.current_name] = [] + 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) @@ -137,7 +142,7 @@ class Lists(BotFunc): def list_delete(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - self.persistence["global"]["lists"].pop(self.current_name, None) + 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 @@ -145,7 +150,9 @@ class Lists(BotFunc): def list_print(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - content = "\n".join(self.persistence["global"]["lists"][self.current_name]) + it = self.db.lists.get(self.db.lists.name == self.current_name) + content = it.content.split("<-->") + content = "\n".join(content) 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) @@ -153,11 +160,16 @@ class Lists(BotFunc): def list_add_item(self, update: Update, context: CallbackContext) -> None: - name = update.message.text - self.persistence["global"]["lists"][self.current_name] += [name] + item = update.message.text + it = self.db.lists.get(self.db.lists.name == self.current_name) + sl = it.content + print("SLSLLLL: {} -- {}".format(sl, type(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 " + name, reply_markup=reply_markup) + update.message.reply_text("Added " + item, reply_markup=reply_markup) return ACTION @@ -166,9 +178,13 @@ class Lists(BotFunc): ind = int(query.data) query.answer() - old = self.persistence["global"]["lists"][self.current_name] + it = self.db.lists.get(self.db.lists.name == self.current_name) + old = it.content.split("<-->") + # todo make better + name = old.pop(ind) - self.persistence["global"]["lists"][self.current_name] = old + 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) diff --git a/bot/commands/plaintext.py b/bot/commands/plaintext.py index 6ff8a0d..55a74be 100644 --- a/bot/commands/plaintext.py +++ b/bot/commands/plaintext.py @@ -3,13 +3,16 @@ from .template import * class Plain(BotFunc): """Not a command: just keeps logs and usage_data""" - def __init__(self, prst): - super().__init__(prst) + 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() - super().increase_counter("receive_activity") \ No newline at end of file + super().log_activity( + read = True, + send = False, + execute = False + ) diff --git a/bot/commands/reddit.py b/bot/commands/reddit.py index bfaadeb..a0f66f0 100644 --- a/bot/commands/reddit.py +++ b/bot/commands/reddit.py @@ -24,9 +24,10 @@ class Joke(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() + 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 @@ -71,7 +72,6 @@ class Meme(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() keyboard = [ [InlineKeyboardButton("General", callback_data="memes"),], @@ -81,6 +81,7 @@ class Meme(BotFunc): [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 @@ -105,6 +106,7 @@ class Meme(BotFunc): 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.") diff --git a/bot/commands/search.py b/bot/commands/search.py index 76c2cf8..2038f24 100644 --- a/bot/commands/search.py +++ b/bot/commands/search.py @@ -5,8 +5,8 @@ SEARCH, MORE = range(2) class Search(BotFunc): """Browse the web for a topic.""" - def __init__(self, api, prst): - super().__init__(prst) + def __init__(self, api, db): + super().__init__(db) self.available_commands = {} self.api = api @@ -25,7 +25,6 @@ class Search(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() update.message.reply_text("What are we searching?") return SEARCH @@ -43,6 +42,7 @@ class Search(BotFunc): message = first["text"] + "\n(" + first["url"] + ")\n\n" update.message.reply_text(text = message, reply_markup=reply_markup) + super().log_activity(read = True, execute = True, send = True) return MORE @@ -55,4 +55,5 @@ class Search(BotFunc): 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 c762414..44e2875 100644 --- a/bot/commands/status.py +++ b/bot/commands/status.py @@ -13,8 +13,8 @@ FIRST = 1 class Status(BotFunc): """Shows a short status of the program.""" - def __init__(self, name, version, prst): - super().__init__(prst) + def __init__(self, name, version, db): + super().__init__(db) self.start_time = datetime.datetime.now() self.name = name self.version = version @@ -33,7 +33,6 @@ class Status(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() keyboard = [ [ InlineKeyboardButton("And the log?", callback_data="full"), @@ -75,6 +74,8 @@ class Status(BotFunc): 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 @@ -85,6 +86,7 @@ class Status(BotFunc): with open("persistence/complete.log") as l: query.message.reply_document(l) + super().log_activity(read = False, execute = False, send = True) return ConversationHandler.END diff --git a/bot/commands/template.py b/bot/commands/template.py index ba97c8b..33477fc 100644 --- a/bot/commands/template.py +++ b/bot/commands/template.py @@ -1,4 +1,5 @@ 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 ( @@ -15,19 +16,21 @@ import datetime class BotFunc(): """Base class for a specific bot-functionality""" - def __init__(self, prst): + def __init__(self, db): self.logger = logging.getLogger(__name__) - self.persistence = prst + self.db = db - def entry_point(self): - self.increase_counter("execute_activity") + def log_activity(self, **kwargs): + # mark that a new command has been executed + data = self.db.chats( + time=datetime.datetime.now(), + **kwargs + ) + # kwargs can look like + # receive=True, + # execute=True, + # send=False, + data.save() - def increase_counter(self, counter_name): - current_hour = int(datetime.datetime.now().timestamp() // 3600) - if len(self.persistence["bot"][counter_name]["hour"]) == 0 or current_hour != self.persistence["bot"][counter_name]["hour"][-1]: - self.persistence["bot"][counter_name]["hour"].append(current_hour) - self.persistence["bot"][counter_name]["count"].append(1) - else: - self.persistence["bot"][counter_name]["count"][-1] += 1 \ No newline at end of file diff --git a/bot/commands/weather.py b/bot/commands/weather.py index f36d90e..e524303 100644 --- a/bot/commands/weather.py +++ b/bot/commands/weather.py @@ -6,9 +6,9 @@ FIRST = 1 class Weather(BotFunc): """Shows a weatherforecast for a given location""" - def __init__(self, api, prst): + def __init__(self, api, db): """initialize api and persistence""" - super().__init__(prst) + super().__init__(db) self.api = api self.city = "" @@ -31,7 +31,6 @@ class Weather(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: """Reacts the call of the command. Prints the first buttons""" - super().entry_point() keyboard = [ [ InlineKeyboardButton("Zürich", callback_data="city-zurich"), @@ -78,6 +77,7 @@ class Weather(BotFunc): text = "Weather: \n\n" + weather, parse_mode = ParseMode.HTML ) + super().log_activity(read = True, execute = True, send = True) return ConversationHandler.END diff --git a/bot/commands/zvv.py b/bot/commands/zvv.py index 436bf07..6c7cef1 100644 --- a/bot/commands/zvv.py +++ b/bot/commands/zvv.py @@ -8,8 +8,8 @@ START, DEST = range(2) class Zvv(BotFunc): """Connects to the swiss travel-api to get public transport routes""" - def __init__(self, prst): - super().__init__(prst) + def __init__(self, db): + super().__init__(db) self.start = "" self.dest = "" pass @@ -27,7 +27,6 @@ class Zvv(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: - super().entry_point() update.message.reply_text("What is the start point?") return START @@ -43,7 +42,8 @@ class Zvv(BotFunc): loc = update.message.text self.dest = loc route = self.get_result() - update.message.reply_text("Her are the routes I've got:\n" + route) + update.message.reply_text("Here are the routes I've got:\n" + route) + super().log_activity(read=True, execute=True, send=True) return ConversationHandler.END diff --git a/bot/main.py b/bot/main.py index 13738c1..721c50a 100644 --- a/bot/main.py +++ b/bot/main.py @@ -16,7 +16,7 @@ class ChatBot(): -> 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 + # added by the launcher, we have self.modules (dict) and persistence and db self.name = name self.version = version @@ -36,22 +36,22 @@ class ChatBot(): def add_commands(self): # Mark modules as available - prst = self.persistence - self.help_module = self.commands.help.Help(prst) + db = self.db + self.help_module = self.commands.help.Help(db) self.sub_modules = { - "weather": self.commands.weather.Weather(self.api_weather, prst), + "weather": self.commands.weather.Weather(self.api_weather, db), "help" : self.help_module, - "status" : self.commands.status.Status(self.name, self.version, prst), - "zvv" : self.commands.zvv.Zvv(prst), - "list" : self.commands.lists.Lists(prst), - # "alias" : commands.alias.Alias(self.dispatcher, prst), - "joke" : self.commands.reddit.Joke(self.api_reddit, prst), - "meme" : self.commands.reddit.Meme(self.api_reddit, prst), - # "news" : self.commands.reddit.News(self.api_reddit, prst), - "search" : self.commands.search.Search(self.api_search, prst), + "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(prst) # for handling non-command messages that should simply contribute to statistics + "plaintext" : self.commands.plaintext.Plain(db) # for handling non-command messages that should simply contribute to statistics } # must be a class that has a method create_handler @@ -61,7 +61,7 @@ class ChatBot(): self.help_module.add_commands(self.sub_modules) def start(self): - self.sub_modules = {"clock" : self.commands.clock.Clock(self.persistence, self.modules["clock"], self.api_art)} + self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)} self.add_commands() self.telegram.start_polling() # self.telegram.idle() diff --git a/broadcast/b_in.py b/broadcast/b_in.py index 363a344..0ad2545 100644 --- a/broadcast/b_in.py +++ b/broadcast/b_in.py @@ -15,6 +15,11 @@ class FetchUpdates: 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) @@ -30,15 +35,16 @@ class FetchUpdates: def fetch_data(self): - 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 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: @@ -60,6 +66,6 @@ class FetchUpdates: if result.pop("status") == "ok": ret = result except: - logger.error("Bad api call for method {}.".format(url[:url.rfind("/")])) + logger.error("Bad api call for method {}.".format(url[url.rfind("/"):])) return ret diff --git a/broadcast/b_out.py b/broadcast/b_out.py index 0569653..79fe36e 100644 --- a/broadcast/b_out.py +++ b/broadcast/b_out.py @@ -73,6 +73,6 @@ class BroadcastUpdates: "status" : "ok", **kwargs } - print(ret) + return jsonify(ret) diff --git a/client.py b/client.py index b40ac6a..8926156 100644 --- a/client.py +++ b/client.py @@ -14,7 +14,7 @@ class ReceiverLauncher(launcher.Launcher): # active: periodically takes readouts self.clock_hardware_module = c_out.ClockFace() # active: periodically calls fetcher - self.receive_module = b_in.FetchUpdates(server_ip="192.168.1.110", port="1111") + self.receive_module = b_in.FetchUpdates(server_ip="localhost", port="1111") # passive: fetches data on demand super().__init__( diff --git a/clock/c_back.py b/clock/c_back.py index 508d30c..0b7923e 100644 --- a/clock/c_back.py +++ b/clock/c_back.py @@ -22,9 +22,7 @@ class ClockBackend: - def clock_loop(self): - print("looping") - + def clock_loop(self): t = int(datetime.datetime.now().strftime("%H%M")) if t % 5 == 0: diff --git a/clock/c_in.py b/clock/c_in.py index f7bb2dd..be7afe1 100644 --- a/clock/c_in.py +++ b/clock/c_in.py @@ -13,7 +13,7 @@ class SensorReadout: self.sensor_modules = { # we already call them, they are objects and not classes anymore "temperature" : hardware.sensors.TemperatureModule(), "humidity" : hardware.sensors.HumidityModule(), - "brightness" : hardware.sensors.BrightnessModule(), + "luminosity" : hardware.sensors.BrightnessModule(), # more to come? } @@ -21,23 +21,36 @@ class SensorReadout: helpers.timer.RepeatedTimer(300, self.spread_measure) def spread_measure(self): - results = dict((el,[]) for el in self.sensor_modules.keys()) # create an empty dict with a list for each readout-type + 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() - results[name].append(measure) + 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.save_results(results) + # self.persistence["clock"]["sensors"]["time"] += [current_minute] - - 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] + # 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 2788acb..fbcfc85 100644 --- a/clock/c_out.py +++ b/clock/c_out.py @@ -13,21 +13,17 @@ class ClockFace: """""" # 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): - helpers.timer.RepeatedTimer(60, self.clock_loop) - # schedule for in 60 seconds - self.clock_loop() - # run once now - # TODO start as a thread + # helpers.timer.RepeatedTimer(60, self.clock_loop) + # # schedule for in 60 seconds + Thread(target = self.clock_loop).start() # TODO Turn off when button pressed? @@ -38,13 +34,18 @@ class ClockFace: t_minutes = int(datetime.datetime.now().strftime("%H%M")) has_queue, data = self.modules["receive"].fetch_data() + self.set_brightness() if data == {}: matrices = self.MOP.get_fallback() else: matrices = [np.asarray(d).astype(int) for d in data["matrices"]] - - self.IO.put(matrices) + + if not self.kill_output: + self.IO.put(matrices) + else: + z = np.zeros((16,16,3)) + self.IO.put([z,z,z]) if has_queue: tnext = 1 @@ -59,3 +60,14 @@ class ClockFace: time.sleep(max(delta.total_seconds(), 0)) self.clock_loop() + + def set_brightness(self): + """Kill the brightness at night""" + + is_WE = datetime.datetime.now().weekday() > 4 + now = int(datetime.datetime.now().strftime("%H%M")) + if (is_WE and (now > 1000 and now < 2200)) or ((not is_WE) and (now > 830 and now < 2130)): + self.kill_output = False + else: + self.kill_output = True + diff --git a/clock/hardware/sensors.py b/clock/hardware/sensors.py index e9fe8f4..2021dea 100644 --- a/clock/hardware/sensors.py +++ b/clock/hardware/sensors.py @@ -5,17 +5,23 @@ logger = logging.getLogger(__name__) class TempSim: """Simulates a temperature for running on windows""" temperature = 23 # return a celsius value - humidity = 0.3 + humidity = 30 + + +class LightSim: + def input(self, *args): + return 1 + + class SensorModule: def __init__(self): logger.info("Using module " + self.__class__.__name__) - -class LightSim: - def input(self, *args): - return 1 + + +## Real sensors! try: import board import adafruit_dht diff --git a/launcher.py b/launcher.py index 01c88cc..350a4ca 100644 --- a/launcher.py +++ b/launcher.py @@ -1,4 +1,4 @@ -from persistence import p_io +from persistence import p_io, p_out import logging import os @@ -24,6 +24,8 @@ class Launcher: def __init__(self, **modules): """""" self.persistence = p_io.PersistentDict("persistence/prst.json") + self.db = p_out.DBLogging() + self.logger = logging.getLogger(__name__) self.logger.info(self.__class__.__name__ + " initialized") @@ -42,6 +44,7 @@ class Launcher: self.logger.info("Starting module "+ module.__class__.__name__) module.modules = self.modules module.persistence = self.persistence + module.db = self.db module.start() diff --git a/persistence/models.py b/persistence/models.py index 48da2e4..ba9c9cb 100644 --- a/persistence/models.py +++ b/persistence/models.py @@ -1,16 +1,40 @@ from peewee import * +import datetime +import logging +logger = logging.getLogger(__name__) +from threading import Thread -#db = SqliteDatabase('data.db') -db = MySQLDatabase("AIO_sensors", host="192.168.1.101", port=3306, user="pi", passwd="supersecret") -# whyyy? - -class Metric(Model): - time = DateTimeField() +from . import keys +dbk = keys.db_keys +db = PostgresqlDatabase(dbk["name"], user=dbk["username"], password=dbk["password"], host=dbk["url"], port=dbk["port"], autorollback=True) + + + +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__)) + print(e) + # db.atomic().rollback() + + +class Metric(DBModel): + time = DateTimeField() + + + + class SensorMetric(Metric): # this is a continuous metric temperature = IntegerField() @@ -19,11 +43,16 @@ class SensorMetric(Metric): class ChatMetric(Metric): - # this gets cumulated over one hour (or one day, or...) - activity = CharField() + read = BooleanField() + send = BooleanField() + execute = BooleanField() class ErrorMetric(Metric): # same as above - error = CharField() + error = TextField() + +class List(DBModel): + name = CharField(unique=True) + content = TextField() # unlimited length, use to serialise list into \ No newline at end of file diff --git a/persistence/p_out.py b/persistence/p_out.py index 1957d98..8680dbd 100644 --- a/persistence/p_out.py +++ b/persistence/p_out.py @@ -1,32 +1,58 @@ -from models import db -from models import * -import datetime as dt -from random import randint +from . import models -def create_tables(): - with db: - db.create_tables([SensorMetric, ChatMetric, ErrorMetric]) +class DBLogging: + """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): + # with self.db as db: + # db.create_tables([self.sensors, self.chats, self.errors, self.lists]) -create_tables() -# read from json, excel, txt ... whatever -now = dt.datetime.timestamp(dt.datetime.now()) -for i in range(1000): - with db: - sensor_data = SensorMetric.create( - time = now + i, - temperature = 23, - humidity = 30 + randint(0,20), - luminosity = 1 - ) - chat = ChatMetric( - time = now + i, - activity = "Hello world" - ) - errors = ErrorMetric( - time = now + i, - error = "Could not load module" - ) \ No newline at end of file + + + + + + + + + + + + + +# writin to the db gets handled through the model directly + +# create_tables() +# # read from json, excel, txt ... whatever +# now = dt.datetime.timestamp(dt.datetime.now()) + + +# for i in range(1000): +# with db: +# sensor_data = SensorMetric.create( +# time = now + i, +# temperature = 23, +# humidity = 30 + randint(0,20), +# luminosity = 1 +# ) +# chat = ChatMetric( +# time = now + i, +# activity = "Hello world" +# ) +# errors = ErrorMetric( +# time = now + i, +# error = "Could not load module" +# ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d269f96 Binary files /dev/null and b/requirements.txt differ diff --git a/server.py b/server.py index 11a2c5b..663bfee 100644 --- a/server.py +++ b/server.py @@ -2,7 +2,7 @@ from bot import main from clock import c_back from broadcast import b_out -from dashboard import d_out +# from dashboard import d_out import launcher @@ -12,9 +12,9 @@ class BroadcastLauncher(launcher.Launcher): """Launcher for all server-side modules. The hard-computations""" def __init__(self): - self.bot_module = main.ChatBot(name="Norbit", version="3.0") # ??? + self.bot_module = main.ChatBot(name="Norbit", version="4.0a") # ??? self.clock_backend_module = c_back.ClockBackend() # threaded through threading.Timer - self.broadcast_module = b_out.BroadcastUpdates(port="1111") # threaded as Thread + self.broadcast_module = b_out.BroadcastUpdates(port="1111") # Thread # self.dashboard_module = d_out.DashBoard(port="80") # ??? threaded as Thread # "sensors" : self.sensors,