def read_forecast(): """Read the weather forecast.""" watcher.feed() start_regex = ure.compile(r'timeseries') continue_regex = ure.compile(r'\},\{') hour_regex = ure.compile(r'next_1_hours') precip_regex = ure.compile(r'precipitation_amount\":(\d+\.\d)') date_regex = ure.compile(r'time\":\"(\d+-\d+-\d+)T') # Large chunk size more performant as less regex's run. Must be less than # characters in one timeseries element in the response. chunkSize = 1000 windowSize = chunkSize * 2 rain_today_mm, rain_tomorrow_mm = (0.0, 0.0) window = memoryview(bytearray(windowSize)) emptyChunk = bytes(chunkSize) periodFound, hourFound, precipFound, dateFound = False, False, False, False File.logger().info('%s - Req to: %s', clock.timestamp(), _FORECAST_URL) with requests.get(_FORECAST_URL, headers=secrets.HEADER) as response: File.logger().info('%s - HTTP status: %d', clock.timestamp(), response.status_code) if response.status_code == 200 or response.status_code == 203: for chunk in response.iter_content(chunkSize): # Populate response window window[0:chunkSize] = window[chunkSize:windowSize] if len(chunk) == chunkSize: window[chunkSize:windowSize] = chunk else: # last chunk is short window[chunkSize:windowSize] = emptyChunk window[chunkSize:chunkSize + len(chunk)] = chunk # print(window) windowBytes = bytes(window) # regex requires bytes # Gather precipitation data if continue_regex.search(windowBytes) or\ start_regex.search(windowBytes): periodFound = True if periodFound and hour_regex.search(windowBytes): hourFound = True if periodFound and not dateFound: if (dateGroup := date_regex.search(windowBytes)): dateFound = True date = dateGroup.group(1).decode() if hourFound and not precipFound: if (precipGroup := precip_regex.search(windowBytes)): precipFound = True mm = float(precipGroup.group(1)) if dateFound and precipFound: periodFound, hourFound = False, False dateFound, precipFound = False, False # print(date, mm) if clock.greater_than_tommorow(date): break elif clock.equal_to_today(date): rain_today_mm += mm else: rain_tomorrow_mm += mm
def send(rain_data, battery_volts): """Send weather and system information to Thingspeak.""" watcher.feed() data_tuple = rain_data.get_data() url = _URL.format(key=secrets.THINGSPEAK_API_KEY, *data_tuple, volts=battery_volts, status=int(not rain_data.rainfall_occurring())) File.logger().info('%s - Req to: %s', clock.timestamp(), url) with requests.get(url) as response: File.logger().info('%s - HTTP status: %d', clock.timestamp(), response.status_code) if response.status_code != 200: raise ValueError("HTTP status %d" % response.status_code)
def read_rainfall(): """Read todays rainfall.""" watcher.feed() rain_last_hour_mm, rain_today_mm = (0.0, 0.0) File.logger().info('%s - Req to: %s', clock.timestamp(), _RAIN_URL) with requests.get(_RAIN_URL) as response: File.logger().info('%s - HTTP status: %d', clock.timestamp(), response.status_code) if response.status_code == 200: first_line = True for line in response.iter_lines(): if first_line: first_line = False continue gc.collect() text = line.decode('utf-8', 'ignore').strip() values = text.split(',') if len(values) == 3: day = int(values[1].split('/')[0]) if day == clock.day_of_month(): mm = float(values[2]) rain_today_mm += mm rain_last_hour_mm = mm else: raise ValueError("HTTP status %d" % response.status_code) File.logger().info('%s - Last hour %.1fmm, today %.1fmm', clock.timestamp(), rain_last_hour_mm, rain_today_mm) print('Last hour %.1fmm, today %.1fmm' % (rain_last_hour_mm, rain_today_mm)) return round(rain_last_hour_mm), round(rain_today_mm)
def connect(): """Connect to WiFi.""" secs = WIFI_DELAY start = ticks_ms() sta_if = network.WLAN(network.STA_IF) sta_if.active(True) sta_if.connect(secrets.WIFI_SSID, secrets.WIFI_PASSPHRASE) while secs >= 0 and not sta_if.isconnected(): sleep(CHECK_INTERVAL) secs -= CHECK_INTERVAL if sta_if.isconnected(): File.logger().info('%s - Connected, address: %s in %d ms', clock.timestamp(), sta_if.ifconfig()[0], ticks_diff(ticks_ms(), start)) return True else: sta_if.active(False) File.logger().error('%s - WiFi did not connect' % clock.timestamp()) return False
self.rain_last_hour_mm, self.rain_today_mm = weather def set_from_forecast(self, forecast): """Set rain data from forecast.""" self.rain_forecast_today_mm, self.rain_forecast_tomorrow_mm = forecast def get_rain_data(): """Get the rain data retrieved from the weather service.""" data = RainData() data.set_from_weather(read_rainfall()) data.set_from_forecast(read_forecast()) return data @retry(Exception, tries=6, delay=2, backoff=2, logger=File.logger()) def read_rainfall(): """Read todays rainfall.""" watcher.feed() rain_last_hour_mm, rain_today_mm = (0.0, 0.0) File.logger().info('%s - Req to: %s', clock.timestamp(), _RAIN_URL) with requests.get(_RAIN_URL) as response: File.logger().info('%s - HTTP status: %d', clock.timestamp(), response.status_code) if response.status_code == 200: first_line = True for line in response.iter_lines(): if first_line: first_line = False continue gc.collect()
"""Thingspeak upload.""" from retrier import retry import requests from file_logger import File import watcher import secrets import clock _URL = ('https://api.thingspeak.com/update' '?api_key={key}&field1={}&field2={}&field3={}&field4={}' '&field5={volts}&field6={status}') @retry(Exception, tries=5, delay=2, backoff=2.0, logger=File.logger()) def send(rain_data, battery_volts): """Send weather and system information to Thingspeak.""" watcher.feed() data_tuple = rain_data.get_data() url = _URL.format(key=secrets.THINGSPEAK_API_KEY, *data_tuple, volts=battery_volts, status=int(not rain_data.rainfall_occurring())) File.logger().info('%s - Req to: %s', clock.timestamp(), url) with requests.get(url) as response: File.logger().info('%s - HTTP status: %d', clock.timestamp(), response.status_code) if response.status_code != 200: raise ValueError("HTTP status %d" % response.status_code)
def run(): """Main entry point to execute this program.""" sleep_enabled = _sleep_enabled() try: File.logger().info('%s - Awake: %s', clock.timestamp(), machine.wake_reason()) rainfall = False next_wake = config.RTC_ALARM battery_volts = _battery_voltage() if not sleep_enabled: watcher.disable() if wifi.connect(): _resetConnectCount() rain_data = weather.get_rain_data() rainfall = rain_data.rainfall_occurring() thingspeak.send(rain_data, battery_volts) if rainfall: File.logger().info('%s - System OFF', clock.timestamp()) _system_off() else: File.logger().info('%s - System ON', clock.timestamp()) _system_on() else: if _incrementConnectCount() > 5: # Give up trying to connect to WiFi _resetConnectCount() else: File.logger().info('%s - Set one minute sleep, attempts %d', clock.timestamp(), _getConnectCount()) next_wake = config.SLEEP_ONE_MINUTE except Exception as ex: # Catch exceptions so that device goes back to sleep HTTP calls # fail with exceptions. File.logger().exc(ex, '%s - Error', clock.timestamp()) finally: try: wifi.disconnect() except Exception as ex: File.logger().exc(ex, '%s - WIFI disconnect error', clock.timestamp()) if sleep_enabled: File.logger().info('%s - Sleeping...', clock.timestamp()) File.close_log() _sleep_until(next_wake) else: File.close_log()