From 6d69aedfd4a80f671c5a7f1ba48ce8d30a8d28c8 Mon Sep 17 00:00:00 2001 From: Chris Straffon Date: Sun, 20 Jan 2019 10:12:14 +0000 Subject: [PATCH 1/2] Added stubbing capability (for testing without rpi & monitor) Added config parser to dynamically reload config without restarting the service --- src/conf/conf_helper.py | 10 +++++++ src/conf/rpi-eco-light.ini | 32 +++++++++++++++++++++ src/db_comms.py | 10 ++++--- src/db_comms_stub.py | 25 +++++++++++++++++ src/energy_usage_light.py | 57 ++++++++++++++++++++++++-------------- src/lighting_stub.py | 53 +++++++++++++++++++++++++++++++++++ src/rpi-eco-light.py | 16 +++++------ 7 files changed, 170 insertions(+), 33 deletions(-) create mode 100644 src/conf/conf_helper.py create mode 100644 src/conf/rpi-eco-light.ini create mode 100644 src/db_comms_stub.py create mode 100644 src/lighting_stub.py diff --git a/src/conf/conf_helper.py b/src/conf/conf_helper.py new file mode 100644 index 0000000..d3e2c6c --- /dev/null +++ b/src/conf/conf_helper.py @@ -0,0 +1,10 @@ +import ConfigParser + +class ConfigHelper: + def __init__(self): + self._config_file_location = "conf/rpi-eco-light.ini" + + def get_current_config(self): + config = ConfigParser.ConfigParser() + config.read(self._config_file_location) + return config diff --git a/src/conf/rpi-eco-light.ini b/src/conf/rpi-eco-light.ini new file mode 100644 index 0000000..8c6bdfb --- /dev/null +++ b/src/conf/rpi-eco-light.ini @@ -0,0 +1,32 @@ +[service] +log_level = DEBUG +update_interval_in_sec = 1 +db_path = /opt/eagleowl + +[monitor] +#Can be either 'KW' or 'PENCE' +type = KW +cost_per_hour_in_pence = 13.556 + +[levels] +#KW Values +#Blue +level_1 = 0.6 +#Green +level_2 = 0.74 +#Yellow +level_3 = 1.1 +#Orange +level_4 = 1.47 +#Red + +#Pence Values +##Blue +#level_1 = 8 +##Green +#level_2 = 10 +##Yellow +#level_3 = 15 +##Orange +#level_4 = 20 +##Red diff --git a/src/db_comms.py b/src/db_comms.py index 0c8ccdf..1d8eabb 100644 --- a/src/db_comms.py +++ b/src/db_comms.py @@ -4,16 +4,17 @@ class DBComms: - def __init__(self, db_location): + def __init__(self, config_helper): self._logger = logging.getLogger(self.__class__.__name__) - self._db_location = db_location + self._config_helper = config_helper self._live_location = ".live" self._last_update_time = None self._update_checks = 0 self._max_update_checks = 10 def get_current_kw(self): - with open("{}/{}".format(self._db_location, + current_config = self._config_helper.get_current_config() + with open("{}/{}".format(current_config.get('service','db_path'), self._live_location), "r") as live_file: line = live_file.readline() @@ -27,7 +28,8 @@ def get_current_kw(self): def check_comms_status(self): comms_good = True - temp_last_update_time = time.ctime(os.path.getmtime(self._db_location + "/" + self._live_location)) + current_config = self._config_helper.get_current_config() + temp_last_update_time = time.ctime(os.path.getmtime(current_config.get('service','db_path') + "/" + self._live_location)) if not self._last_update_time: # not initialised self._last_update_time = temp_last_update_time diff --git a/src/db_comms_stub.py b/src/db_comms_stub.py new file mode 100644 index 0000000..e7f46c6 --- /dev/null +++ b/src/db_comms_stub.py @@ -0,0 +1,25 @@ +import os +import time +import logging + + +class DBComms: + def __init__(self, config_helper): + self._logger = logging.getLogger(self.__class__.__name__) + self._config_helper = config_helper + self._live_location = ".live" + self._last_update_time = None + self._update_checks = 0 + self._max_update_checks = 10 + self._current_kw = 0.3 + + def get_current_kw(self): + ret_val = self._current_kw + self._current_kw += 0.1 + if self._current_kw > 1.8: + self._current_kw = 0.3 + return ret_val + + def check_comms_status(self): + comms_good = True + return comms_good diff --git a/src/energy_usage_light.py b/src/energy_usage_light.py index 98b1a6d..165dbde 100755 --- a/src/energy_usage_light.py +++ b/src/energy_usage_light.py @@ -1,36 +1,48 @@ import logging -from db_comms import DBComms -from lighting import Lighting +#from db_comms import DBComms +#from lighting import Lighting +from db_comms_stub import DBComms +from lighting_stub import Lighting logger = logging.getLogger(__name__) -def kw_to_rgb(kw, pence_per_hour): +def kw_to_rgb(kw, current_config): """ Converts Power (KW) to a RGB value :param kw: power - :param pence_per_hour: the cost in pence per kw per hour + :param current_config: the current configuration file :return: a dict for RGB values """ - kw_in_pence = pence_per_hour * kw - logger.debug("KW in pence: %s" % kw_in_pence) - if kw_in_pence >= 20: - logger.debug(">= 20 = RED") + lighting_value = kw + if (current_config.get('monitor','type') == "PENCE"): + lighting_value = current_config.getfloat('monitor','cost_per_hour_in_pence') * kw + logger.debug("KW in pence: %s" % lighting_value) + else: + logger.debug("KW: %s" % lighting_value) + + level_1 = current_config.getfloat('levels','level_1') + level_2 = current_config.getfloat('levels','level_2') + level_3 = current_config.getfloat('levels','level_3') + level_4 = current_config.getfloat('levels','level_4') + + if lighting_value >= level_4: + logger.debug(">= %.2f = RED",level_4) rgb_dict = Lighting.RED - elif kw_in_pence >= 15: - logger.debug("< 20 && >= 15 = ORANGE") + elif lighting_value >= level_3: + logger.debug("< %.2f && >= %.2f = ORANGE",level_4, level_3) rgb_dict = Lighting.ORANGE - elif kw_in_pence >= 10: - logger.debug("< 15 && >= 10 = YELLOW") + elif lighting_value >= level_2: + logger.debug("< %.2f && >= %.2f = YELLOW",level_3,level_2) rgb_dict = Lighting.YELLOW - elif kw_in_pence >= 8: - logger.debug("< 10 && >= 8 = GREEN") + elif lighting_value >= level_1: + logger.debug("< %.2f && >= %.2f = GREEN",level_2, level_1) rgb_dict = Lighting.GREEN - elif 0 > kw_in_pence < 8: - logger.debug("< 8 = BLUE") + elif lighting_value < level_1 and lighting_value > 0: + logger.debug("< %.2f = BLUE",level_1) rgb_dict = Lighting.BLUE - elif kw_in_pence == 0: + elif lighting_value == 0: logger.debug("==0 = PURPLE (No value yet)") rgb_dict = Lighting.PURPLE else: @@ -40,15 +52,18 @@ def kw_to_rgb(kw, pence_per_hour): class EnergyUsageLight(object): - def __init__(self, db_path, pence_per_hour): - self._comms = DBComms(db_path) + def __init__(self, config_helper): + self._config_helper = config_helper + current_config = config_helper.get_current_config() + self._comms = DBComms(config_helper) self._light = Lighting() - self._pence_per_hour = pence_per_hour def update(self): + current_config = self._config_helper.get_current_config() + if not self._comms.check_comms_status(): self._light.set_error() else: kw = self._comms.get_current_kw() - rgb_dict = kw_to_rgb(kw, self._pence_per_hour) + rgb_dict = kw_to_rgb(kw, current_config) self._light.set_light(rgb_dict) diff --git a/src/lighting_stub.py b/src/lighting_stub.py new file mode 100644 index 0000000..76f0a16 --- /dev/null +++ b/src/lighting_stub.py @@ -0,0 +1,53 @@ +import time +import logging + +class Lighting: + BLUE = (0, 0, 255) + GREEN = (0, 255, 0) + YELLOW = (177, 142, 52) + ORANGE = (255, 165, 0) + RED = (255, 0, 0) + PURPLE = (148, 0, 211) + + def __init__(self): + self._logger = logging.getLogger(self.__class__.__name__) + self._current_value = self.BLUE + pixels = self._set_whole_grid(self._current_value) + + def set_light(self, target_value): + set_to_value = self._current_value + while set_to_value != target_value: + set_to_value = self._fade_between_rgb(set_to_value, target_value,1) + pixels = self._set_whole_grid(set_to_value) + time.sleep(0.01) + self._current_value = set_to_value + + def set_error(self): + self._current_value = self.PURPLE + self._logger.error("Light set to error state") + + def _set_whole_grid(self, rgb_val): + pixels = [] + for _ in range(8): + pixels.append([]) + for _ in range(8): + pixels[len(pixels)-1].append(rgb_val) + return pixels + + def _fade_between_rgb(self, current_rgb, desired_rgb, change_step=2): + r = 0 + g = 0 + b = 0 + for i in range(0, change_step): + r = self._inc_dec(current_rgb[0], desired_rgb[0]) + g = self._inc_dec(current_rgb[1], desired_rgb[1]) + b = self._inc_dec(current_rgb[2], desired_rgb[2]) + return r, g, b + + def _inc_dec(self, curr_val, des_val): + ret_val = curr_val + if curr_val < des_val: + ret_val += 1 + elif curr_val > des_val: + ret_val -= 1 + return ret_val diff --git a/src/rpi-eco-light.py b/src/rpi-eco-light.py index d7a1f75..d1222e1 100755 --- a/src/rpi-eco-light.py +++ b/src/rpi-eco-light.py @@ -8,30 +8,30 @@ import logging.config from conf.logging import LOGGING +from conf.conf_helper import ConfigHelper from energy_usage_light import EnergyUsageLight - -DB_PATH = "/opt/eagleowl" -SLEEP_TIME_IN_SECS = 5 -COST_PER_HOUR_IN_PENCE = 13.37 - logging.config.dictConfig(LOGGING) logger = logging.getLogger(__name__) +config_helper = ConfigHelper() def signal_term_handler(signal, frame): logger.fatal("Handling SIGTERM") sys.exit(0) - if __name__ == "__main__": logger.info("Starting RPi ECO Light") signal.signal(signal.SIGINT, signal_term_handler) - usage_light = EnergyUsageLight(DB_PATH, COST_PER_HOUR_IN_PENCE) + usage_light = EnergyUsageLight(config_helper) while True: + current_config = config_helper.get_current_config() + LOGGING['handlers']['console']['level'] = current_config.get('service','log_level') + logging.config.dictConfig(LOGGING) + usage_light.update() #Flush the stdout each time round the loop sys.stdout.flush() #Sleep for a bit before the next update - time.sleep(SLEEP_TIME_IN_SECS) + time.sleep(float(current_config.get('service','update_interval_in_sec'))) From dff056a1d39231369d94403c8d100031e25e16de Mon Sep 17 00:00:00 2001 From: Chris Straffon Date: Sat, 2 Feb 2019 09:40:13 +0000 Subject: [PATCH 2/2] Added more useful debug when value isn't updated and fixed hardcoded path for config Switched comments to ;s --- src/conf/conf_helper.py | 4 +++- src/conf/rpi-eco-light.ini | 39 ++++++++++++++++++++------------------ src/db_comms.py | 3 +++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/conf/conf_helper.py b/src/conf/conf_helper.py index d3e2c6c..f116797 100644 --- a/src/conf/conf_helper.py +++ b/src/conf/conf_helper.py @@ -1,8 +1,10 @@ import ConfigParser +import os class ConfigHelper: def __init__(self): - self._config_file_location = "conf/rpi-eco-light.ini" + dir_path = os.path.dirname(__file__) + self._config_file_location = dir_path+"/rpi-eco-light.ini" def get_current_config(self): config = ConfigParser.ConfigParser() diff --git a/src/conf/rpi-eco-light.ini b/src/conf/rpi-eco-light.ini index 8c6bdfb..e1e03f2 100644 --- a/src/conf/rpi-eco-light.ini +++ b/src/conf/rpi-eco-light.ini @@ -1,32 +1,35 @@ +;'s are for comments + [service] log_level = DEBUG -update_interval_in_sec = 1 +update_interval_in_sec = 5 db_path = /opt/eagleowl +max_update_checks = 20 [monitor] -#Can be either 'KW' or 'PENCE' +;Can be either 'KW' or 'PENCE' type = KW cost_per_hour_in_pence = 13.556 [levels] -#KW Values -#Blue +;KW Values +;Blue level_1 = 0.6 -#Green +;Green level_2 = 0.74 -#Yellow +;Yellow level_3 = 1.1 -#Orange +;Orange level_4 = 1.47 -#Red +;Red -#Pence Values -##Blue -#level_1 = 8 -##Green -#level_2 = 10 -##Yellow -#level_3 = 15 -##Orange -#level_4 = 20 -##Red +;Pence Values +;;Blue +;level_1 = 8 +;;Green +;level_2 = 10 +;;Yellow +;level_3 = 15 +;;Orange +;level_4 = 20 +;;Red diff --git a/src/db_comms.py b/src/db_comms.py index 1d8eabb..73d7ce6 100644 --- a/src/db_comms.py +++ b/src/db_comms.py @@ -29,6 +29,7 @@ def get_current_kw(self): def check_comms_status(self): comms_good = True current_config = self._config_helper.get_current_config() + self._max_update_checks = current_config.get('service','max_update_checks') temp_last_update_time = time.ctime(os.path.getmtime(current_config.get('service','db_path') + "/" + self._live_location)) if not self._last_update_time: # not initialised @@ -45,4 +46,6 @@ def check_comms_status(self): # We've reached our max attempts to query - something's gone wrong self._logger.error("Max update checks reached, last update time: %s" % temp_last_update_time) comms_good = False + else: + self._logger.debug("Value has not been updated in %d queries, max queries before error: %d", self._update_checks, self._max_update_checks) return comms_good