From 18e09a467d3ba4eab4c018f574a292e9c78f989d Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Wed, 29 Mar 2023 22:53:20 +0100 Subject: [PATCH 1/8] Add rain per hour and day results --- enviro/boards/weather.py | 87 +++++++++++++++++++++------------------- enviro/helpers.py | 4 ++ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index 9b44e13..4187973 100644 --- a/enviro/boards/weather.py +++ b/enviro/boards/weather.py @@ -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,41 @@ def wind_direction(): return closest_index * 45 def rainfall(seconds_since_last): + new_rain_entries = [] amount = 0 + per_hour = 0 + today = 0 now = helpers.timestamp(helpers.datetime_string()) + now_day = helpers.timestamp_day(helpers.datetime_string()) 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 + # process the rain file data for entry in rain_entries: if entry: ts = helpers.timestamp(entry) + tsday = helpers.timestamp_day(entry) + # count how many rain ticks since the last reading if now - ts < seconds_since_last: amount += RAIN_MM_PER_TICK - - os.remove("rain.txt") + # 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 delete older entries + if tsday == now_day: + today += RAIN_MM_PER_TICK + new_rain_entries.append(entry) + + # write out 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 +191,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 +202,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/helpers.py b/enviro/helpers.py index 1503ad4..48b7e62 100644 --- a/enviro/helpers.py +++ b/enviro/helpers.py @@ -24,6 +24,10 @@ def timestamp(dt): second = int(dt[17:19]) return time.mktime((year, month, day, hour, minute, second, 0, 0)) +def timestamp_day(dt): + day = int(dt[8:10]) + return day + def uid(): return "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(*machine.unique_id()) From bf118175319ad1b7d7a934c5397b3e683be25b1b Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Thu, 30 Mar 2023 18:11:43 +0100 Subject: [PATCH 2/8] Capture potential lost readings over midnight --- enviro/boards/weather.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index 4187973..b9fd2d3 100644 --- a/enviro/boards/weather.py +++ b/enviro/boards/weather.py @@ -165,6 +165,11 @@ def rainfall(seconds_since_last): # count how many rain ticks since the last reading if now - ts < seconds_since_last: amount += RAIN_MM_PER_TICK + # Pick up any untracked yesterday data if current reading is a new day + # Techincally this should be yesterday, but capturing in today is much + # less complex than backdating in the readings file from here + 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 From 0157248a714180fa2c8bb9b5140178ec5deda66b Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Sat, 1 Apr 2023 14:10:23 +0100 Subject: [PATCH 3/8] Rain today adjusted to use local time --- enviro/boards/weather.py | 8 +++++--- enviro/config_defaults.py | 6 ++++++ enviro/config_template.py | 3 +++ enviro/helpers.py | 11 ++++++++--- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index b9fd2d3..cddba8d 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 @@ -152,7 +152,8 @@ def rainfall(seconds_since_last): per_hour = 0 today = 0 now = helpers.timestamp(helpers.datetime_string()) - now_day = helpers.timestamp_day(helpers.datetime_string()) + now_day = helpers.timestamp_day(helpers.datetime_string(), config.utc_offset) + logging.info(f"> current day number is {now_day}") if helpers.file_exists("rain.txt"): with open("rain.txt", "r") as rainfile: rain_entries = rainfile.read().split("\n") @@ -161,7 +162,8 @@ def rainfall(seconds_since_last): for entry in rain_entries: if entry: ts = helpers.timestamp(entry) - tsday = helpers.timestamp_day(entry) + tsday = helpers.timestamp_day(entry, config.utc_offset) + logging.info(f"> rain reading day number is {tsday}") # count how many rain ticks since the last reading if now - ts < seconds_since_last: amount += RAIN_MM_PER_TICK diff --git a/enviro/config_defaults.py b/enviro/config_defaults.py index 63a5877..079d406 100644 --- a/enviro/config_defaults.py +++ b/enviro/config_defaults.py @@ -23,6 +23,12 @@ def add_missing_config_settings(): except AttributeError: warn_missing_config_setting("wifi_country") config.wifi_country = "GB" + + try: + config.utc_offset + except AttributeError: + warn_missing_config_setting("utc_offset") + config.utc_offset = 0 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..0cc16a6 100644 --- a/enviro/config_template.py +++ b/enviro/config_template.py @@ -13,6 +13,9 @@ wifi_password = None wifi_country = "GB" +# For local time corrections to daily rain logging (include DST) +utc_offset = 1 + # 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 48b7e62..17c286b 100644 --- a/enviro/helpers.py +++ b/enviro/helpers.py @@ -1,5 +1,5 @@ from enviro.constants import * -import machine, math, os, time +import machine, math, os, time, utime # miscellany # =========================================================================== @@ -24,8 +24,13 @@ def timestamp(dt): second = int(dt[17:19]) return time.mktime((year, month, day, hour, minute, second, 0, 0)) -def timestamp_day(dt): - day = int(dt[8:10]) +# 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(): From 139fcebccfdfe81698112892baaba74f286eb9ea Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Sat, 1 Apr 2023 14:10:35 +0100 Subject: [PATCH 4/8] Updated documentation --- documentation/boards/enviro-weather.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/boards/enviro-weather.md b/documentation/boards/enviro-weather.md index 7b3866d..ce2d77c 100644 --- a/documentation/boards/enviro-weather.md +++ b/documentation/boards/enviro-weather.md @@ -13,11 +13,15 @@ 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 local time (and/or DST) by modifying the utc_offset value in config.py + ## On-board devices - BME280 temperature, pressure, humidity sensor. [View datasheet](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf) From 0c41c6d7d6d1e1a624c58f6da45aa135d0d5aa79 Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Thu, 13 Jul 2023 18:21:56 +0100 Subject: [PATCH 5/8] Add hard coded BST calculation for rain day --- enviro/boards/weather.py | 10 +++++++++- enviro/config_defaults.py | 10 +++++++++- enviro/config_template.py | 8 ++++++-- enviro/helpers.py | 27 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index cddba8d..5817587 100644 --- a/enviro/boards/weather.py +++ b/enviro/boards/weather.py @@ -151,8 +151,16 @@ def rainfall(seconds_since_last): amount = 0 per_hour = 0 today = 0 + offset = 0 + + if config.uk_bst == True: + if helpers.uk_bst(): + offset = 1 + elif config.utc_offset != 0: + offset += config.utc_offset + now = helpers.timestamp(helpers.datetime_string()) - now_day = helpers.timestamp_day(helpers.datetime_string(), config.utc_offset) + now_day = helpers.timestamp_day(helpers.datetime_string(), offset) logging.info(f"> current day number is {now_day}") if helpers.file_exists("rain.txt"): with open("rain.txt", "r") as rainfile: diff --git a/enviro/config_defaults.py b/enviro/config_defaults.py index 079d406..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(): @@ -24,11 +26,17 @@ def add_missing_config_settings(): 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 = 0 + 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 0cc16a6..2f7d727 100644 --- a/enviro/config_template.py +++ b/enviro/config_template.py @@ -13,8 +13,12 @@ wifi_password = None wifi_country = "GB" -# For local time corrections to daily rain logging (include DST) -utc_offset = 1 +# 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 17c286b..bb0a66d 100644 --- a/enviro/helpers.py +++ b/enviro/helpers.py @@ -1,5 +1,6 @@ from enviro.constants import * import machine, math, os, time, utime +from phew import logging # miscellany # =========================================================================== @@ -24,6 +25,32 @@ 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"> Provided 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 From becf50ad651cdc290b1bfb3044ca548d7b1940e0 Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Tue, 29 Aug 2023 14:08:40 +0100 Subject: [PATCH 6/8] Documentation update for uk_bst --- documentation/boards/enviro-weather.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/boards/enviro-weather.md b/documentation/boards/enviro-weather.md index ce2d77c..3a60ce1 100644 --- a/documentation/boards/enviro-weather.md +++ b/documentation/boards/enviro-weather.md @@ -20,7 +20,9 @@ Enviro Weather is a super slimline all in one board for keeping a (weather) eye |Wind Speed|`wind_speed`|metres per second|m/s|`0.45`| |Voltage|`voltage`|volts|V|`4.035`| -The rain today value is adjusted for local time (and/or DST) by modifying the utc_offset value in config.py +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 From 3e30b0a2b1f0b405289d138411e4797fbc85ceb9 Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Tue, 29 Aug 2023 14:26:52 +0100 Subject: [PATCH 7/8] Log message improvement --- enviro/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enviro/helpers.py b/enviro/helpers.py index bb0a66d..bb4a277 100644 --- a/enviro/helpers.py +++ b/enviro/helpers.py @@ -47,7 +47,7 @@ def uk_bst(): if bst_timestamps[year]["start"] < ts and bst_timestamps[year]["end"] > ts: bst = True else: - logging.warn(f"> Provided year is not in BST lookup dictionary: {year}") + logging.warn(f"> Current year is not in BST lookup dictionary: {year}") return bst From b56bf6f8649f9ad616c6ed2b91deed6e3725083b Mon Sep 17 00:00:00 2001 From: Stephen Jefferson Date: Tue, 29 Aug 2023 14:57:02 +0100 Subject: [PATCH 8/8] Improved comments --- enviro/boards/weather.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/enviro/boards/weather.py b/enviro/boards/weather.py index 5817587..9fb7ae0 100644 --- a/enviro/boards/weather.py +++ b/enviro/boards/weather.py @@ -148,47 +148,56 @@ def wind_direction(): def rainfall(seconds_since_last): new_rain_entries = [] - amount = 0 + amount = 0 # rain since last reading per_hour = 0 today = 0 - offset = 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") - # process the rain file data + # 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}") - # count how many rain ticks since the last reading + # populate amount with rain since the last reading if now - ts < seconds_since_last: amount += RAIN_MM_PER_TICK - # Pick up any untracked yesterday data if current reading is a new day - # Techincally this should be yesterday, but capturing in today is much - # less complex than backdating in the readings file from here + # 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 delete older entries + # 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 adjusted rain log + # write out new adjusted rain log with open("rain.txt", "w") as newrainfile: newrainfile.write("\n".join(new_rain_entries))