diff --git a/documentation/boards/enviro-weather.md b/documentation/boards/enviro-weather.md index 7b3866d..3a60ce1 100644 --- a/documentation/boards/enviro-weather.md +++ b/documentation/boards/enviro-weather.md @@ -13,11 +13,17 @@ Enviro Weather is a super slimline all in one board for keeping a (weather) eye |Air Pressure|`pressure`|hectopascals|hPa|`997.16`| |Luminance|`luminance`|lux|lx|`35`| |Rainfall|`rain`|millimetres|mm|`1.674`| -|Rainfall Average|`rain_per_second`|millimetres per second|mm/s|`1.674`| +|Rainfall Average Second|`rain_per_second`|millimetres per second|mm/s|`1.674`| +|Rainfall Average Hour|`rain_per_hour`|millimetres per hour|mm/h|`1.674`| +|Rainfall Today (local time)|`rain_today`|millimetres accumulated today|mm/s|`1.674`| |Wind Direction|`wind_direction`|angle|°|`45`| |Wind Speed|`wind_speed`|metres per second|m/s|`0.45`| |Voltage|`voltage`|volts|V|`4.035`| +The rain today value is adjusted for DST in the UK by setting uk_bst = True in config.py +For static time zone offsets (not taking account of DST), modify the utc_offset value in config.py +The time zone offset value is ignored if uk_bst = True + ## On-board devices - BME280 temperature, pressure, humidity sensor. [View datasheet](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index 9b44e13..9fb7ae0 100644 --- a/enviro/boards/weather.py +++ b/enviro/boards/weather.py @@ -1,4 +1,4 @@ -import time, math, os +import time, math, os, config from breakout_bme280 import BreakoutBME280 from breakout_ltr559 import BreakoutLTR559 from machine import Pin, PWM @@ -25,6 +25,28 @@ rain_pin = Pin(10, Pin.IN, Pin.PULL_DOWN) last_rain_trigger = False +def log_rain(): + # read the current rain entries + rain_entries = [] + if helpers.file_exists("rain.txt"): + with open("rain.txt", "r") as rainfile: + rain_entries = rainfile.read().split("\n") + + # add new entry + logging.info(f"> add new rain trigger at {helpers.datetime_string()}") + rain_entries.append(helpers.datetime_string()) + + # limit number of entries to 190 - each entry is 21 bytes including + # newline so this keeps the total rain.txt filesize just under one + # filesystem block (4096 bytes) + if len(rain_entries) > 190: + logging.info("Rain log file exceeded 190 entries and was truncated") + rain_entries = rain_entries[-190:] + + # write out adjusted rain log + with open("rain.txt", "w") as rainfile: + rainfile.write("\n".join(rain_entries)) + def startup(reason): global last_rain_trigger import wakeup @@ -33,24 +55,7 @@ def startup(reason): rain_sensor_trigger = wakeup.get_gpio_state() & (1 << 10) if rain_sensor_trigger: - # read the current rain entries - rain_entries = [] - if helpers.file_exists("rain.txt"): - with open("rain.txt", "r") as rainfile: - rain_entries = rainfile.read().split("\n") - - # add new entry - logging.info(f"> add new rain trigger at {helpers.datetime_string()}") - rain_entries.append(helpers.datetime_string()) - - # limit number of entries to 190 - each entry is 21 bytes including - # newline so this keeps the total rain.txt filesize just under one - # filesystem block (4096 bytes) - rain_entries = rain_entries[-190:] - - # write out adjusted rain log - with open("rain.txt", "w") as rainfile: - rainfile.write("\n".join(rain_entries)) + log_rain() last_rain_trigger = True @@ -70,24 +75,7 @@ def check_trigger(): time.sleep(0.05) activity_led(0) - # read the current rain entries - rain_entries = [] - if helpers.file_exists("rain.txt"): - with open("rain.txt", "r") as rainfile: - rain_entries = rainfile.read().split("\n") - - # add new entry - logging.info(f"> add new rain trigger at {helpers.datetime_string()}") - rain_entries.append(helpers.datetime_string()) - - # limit number of entries to 190 - each entry is 21 bytes including - # newline so this keeps the total rain.txt filesize just under one - # filesystem block (4096 bytes) - rain_entries = rain_entries[-190:] - - # write out adjusted rain log - with open("rain.txt", "w") as rainfile: - rainfile.write("\n".join(rain_entries)) + log_rain() last_rain_trigger = rain_sensor_trigger @@ -159,26 +147,65 @@ def wind_direction(): return closest_index * 45 def rainfall(seconds_since_last): - amount = 0 + new_rain_entries = [] + amount = 0 # rain since last reading + per_hour = 0 + today = 0 + offset = 0 # UTC offset hours + + # configure offset variable for UK BST or timezone offset from config file + # and BST lookup function + if config.uk_bst == True: + if helpers.uk_bst(): + offset = 1 + elif config.utc_offset != 0: + offset += config.utc_offset + + # determine current day number and timestamp now = helpers.timestamp(helpers.datetime_string()) + now_day = helpers.timestamp_day(helpers.datetime_string(), offset) + logging.info(f"> current day number is {now_day}") + + # process the rain file data if helpers.file_exists("rain.txt"): with open("rain.txt", "r") as rainfile: rain_entries = rainfile.read().split("\n") - # count how many rain ticks since the last reading + # populate latest, per second, today and last hour readings from rain log + # file, write new rain log file dropping any yesterday readings for entry in rain_entries: if entry: ts = helpers.timestamp(entry) + tsday = helpers.timestamp_day(entry, config.utc_offset) + logging.info(f"> rain reading day number is {tsday}") + # populate amount with rain since the last reading if now - ts < seconds_since_last: amount += RAIN_MM_PER_TICK - - os.remove("rain.txt") + # add any rain ticks from yesterday since the previous reading + # this will misallocate day totals, but will ensure the hourly total + # is correct without introducing complexity backdating yesterday and + # the error will be minimised with frequent readings + # TODO sum yesterday rain and generate a rain_today reading with + # 23:59:59 timestamp of yesterday + if tsday != now_day: + today += RAIN_MM_PER_TICK + # count how many rain ticks in the last hour + if now - ts < 3600: + per_hour += RAIN_MM_PER_TICK + # count how many rain ticks today and drop older entries for new file + if tsday == now_day: + today += RAIN_MM_PER_TICK + new_rain_entries.append(entry) + + # write out new adjusted rain log + with open("rain.txt", "w") as newrainfile: + newrainfile.write("\n".join(new_rain_entries)) per_second = 0 if seconds_since_last > 0: per_second = amount / seconds_since_last - return amount, per_second + return amount, per_second, per_hour, today def get_sensor_readings(seconds_since_last, is_usb_power): # bme280 returns the register contents immediately and then starts a new reading @@ -188,7 +215,7 @@ def get_sensor_readings(seconds_since_last, is_usb_power): bme280_data = bme280.read() ltr_data = ltr559.get_reading() - rain, rain_per_second = rainfall(seconds_since_last) + rain, rain_per_second, rain_per_hour, rain_today = rainfall(seconds_since_last) from ucollections import OrderedDict return OrderedDict({ @@ -199,5 +226,7 @@ def get_sensor_readings(seconds_since_last, is_usb_power): "wind_speed": wind_speed(), "rain": rain, "rain_per_second": rain_per_second, + "rain_per_hour": rain_per_hour, + "rain_today": rain_today, "wind_direction": wind_direction() }) diff --git a/enviro/config_defaults.py b/enviro/config_defaults.py index 63a5877..e980317 100644 --- a/enviro/config_defaults.py +++ b/enviro/config_defaults.py @@ -2,6 +2,8 @@ from phew import logging DEFAULT_USB_POWER_TEMPERATURE_OFFSET = 4.5 +DEFAULT_UTC_OFFSET = 0 +DEFAULT_UK_BST = True def add_missing_config_settings(): @@ -23,6 +25,18 @@ def add_missing_config_settings(): except AttributeError: warn_missing_config_setting("wifi_country") config.wifi_country = "GB" + + try: + config.uk_bst + except AttributeError: + warn_missing_config_setting("uk_bst") + config.uk_bst = DEFAULT_UK_BST + + try: + config.utc_offset + except AttributeError: + warn_missing_config_setting("utc_offset") + config.utc_offset = DEFAULT_UTC_OFFSET def warn_missing_config_setting(setting): logging.warn(f"> config setting '{setting}' missing, please add it to config.py") diff --git a/enviro/config_template.py b/enviro/config_template.py index 11404a9..2f7d727 100644 --- a/enviro/config_template.py +++ b/enviro/config_template.py @@ -13,6 +13,13 @@ wifi_password = None wifi_country = "GB" +# Adjust daily rain day for UK BST +uk_bst = True + +# For local time corrections to daily rain logging other than BST +# Ignored if uk_bst = True +utc_offset = 0 + # how often to wake up and take a reading (in minutes) reading_frequency = 15 diff --git a/enviro/helpers.py b/enviro/helpers.py index 1503ad4..bb4a277 100644 --- a/enviro/helpers.py +++ b/enviro/helpers.py @@ -1,5 +1,6 @@ from enviro.constants import * -import machine, math, os, time +import machine, math, os, time, utime +from phew import logging # miscellany # =========================================================================== @@ -24,6 +25,41 @@ def timestamp(dt): second = int(dt[17:19]) return time.mktime((year, month, day, hour, minute, second, 0, 0)) +def uk_bst(): + # Return True if in UK BST - manually update bst_timestamps {} as needed + dt = datetime_string() + year = int(dt[0:4]) + ts = timestamp(dt) + bst = False + + bst_timestamps = { + 2023: {"start": 1679792400, "end": 1698541200}, + 2024: {"start": 1711846800, "end": 1729990800}, + 2025: {"start": 1743296400, "end": 1761440400}, + 2026: {"start": 1774746000, "end": 1792890000}, + 2027: {"start": 1806195600, "end": 1824944400}, + 2028: {"start": 1837645200, "end": 1856394000}, + 2029: {"start": 1869094800, "end": 1887843600}, + 2030: {"start": 1901149200, "end": 1919293200} + } + + if year in bst_timestamps: + if bst_timestamps[year]["start"] < ts and bst_timestamps[year]["end"] > ts: + bst = True + else: + logging.warn(f"> Current year is not in BST lookup dictionary: {year}") + return bst + + +# Return the day number of your timestamp string accommodating UTC offsets +def timestamp_day(dt, offset_hours): + # Bounce via timestamp to properly calculate hours change + time = timestamp(dt) + time = time + (offset_hours * 3600) + dt = utime.localtime(time) + day = int(dt[2]) + return day + def uid(): return "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*machine.unique_id())