Hello all I have got it to work. Http://www.findu.com/cgi-bin/find.cgi?call=FW9765
Only problem is I am getting this error: May 24 18:41:38 raspberrypi weewx[3623] ERROR user.weatherflowudp: Socket timeout waiting for incoming UDP packet! I am publishing to CWOP on an interval of 300ms. I have set the timeout value in the driver for 400ms and still seem to be getting the error. Any ideas on how to correct this? I have attached the information in my driver. Many thanks On Monday, 24 May 2021 at 08:12:56 UTC+1 Cookie wrote: > Thanks for the replies. I didn't pick up on the nuance between the Air+Sky > Weatherflow station and the Tempest. Will give the suggestions a try. With > thanks. > > On Monday, 24 May 2021 at 00:20:11 UTC+1 vince wrote: > >> There are many threads here on this as well as in the Weatherflow forums, >> with working examples people (including me) have posted many times. >> >> The docs for the driver at >> https://github.com/captain-coredump/weatherflow-udp have an example >> weewx.conf snippet for the original Air+Sky WeatherFlow station. You want >> to replace the sensor_map section with the Tempest example that is at >> https://github.com/captain-coredump/weatherflow-udp/blob/master/sample_Tempest_sensor_map >> . >> >> You need to edit those examples to use 'your' Tempest serial number which >> will be of a format similar to the example of ST-00000025 >> >> -- You received this message because you are subscribed to the Google Groups "weewx-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to weewx-user+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-user/776c625c-40ea-499b-b028-d27a8033a294n%40googlegroups.com.
#!/usr/bin/env python # Copyright 2017-2020 Arthur Emerson, vrei...@yahoo.com # Distributed under the terms of the GNU Public License (GPLv3) """ This driver detects different sensors packets broadcast using the WeatherFlow UDP JSON protocol, and it includes a mechanism to filter the incoming data and map the filtered data onto the weewx database schema and identify the type of data from each sensor. Sensors are filtered based on a tuple that identifies uniquely each sensor. A tuple consists of the observation name, a unique identifier for the hardware, and the packet type, separated by periods: <observation_name>.<hardware_id>.<packet_type> The filter and data types are specified in a sensor_map stanza in the driver stanza. For example, on an Air/Sky setup: [WeatherFlowUDP] driver = user.weatherflowudp log_raw_packets = False # udp_address = <broadcast> udp_address = 0.0.0.0 # udp_address = 255.255.255.255 udp_port = 50222 udp_timeout = 400 share_socket = True [[sensor_map]] outTemp = air_temperature.AR-00008830.obs_air outHumidity = relative_humidity.AR-00008830.obs_air pressure = station_pressure.AR-00008830.obs_air lightning_strikes = lightning_strike_count.AR-00008830.obs_air avg_distance = lightning_strike_avg_distance.AR-00008830.obs_air outTempBatteryStatus = battery.AR-00008830.obs_air windSpeed = wind_speed.SK-00008830.rapid_wind windDir = wind_direction.SK-00008830.rapid_wind lux = illuminance.SK-00008830.obs_sky UV = uv.SK-00008830.obs_sky rain = rain_accumulated.SK-00008830.obs_sky windBatteryStatus = battery.SK-00008830.obs_sky radiation = solar_radiation.SK-00008830.obs_sky lightningYYY = distance.AR-00008830.evt_strike lightningZZZ = energy.AR-00008830.evt_strike *** If no sensor_map is specified, no data will be collected. *** For a sample Tempest sensor_map, see the file sample_Tempest_sensor_map on GitHub: https://github.com/captain-coredump/weatherflow-udp/blob/master/sample_Tempest_sensor_map To identify sensors, run the driver directly. For help on the various options, try cd /home/weewx PYTHONPATH=/home/weewx/bin python ./bin/user/weatherflowudp.py --help To identify the various observation_name options, start weewx with this station driver installed and it will write the entire matrix of available observation_names and sensor_types to syslog or wherever weewx is configured to send log info. Apologies for the long observation_names, but I figured that it would be best if I used the documented field names from WeatherFlow's UDP packet specs (v37 at the time of writing) with underscores between words so that the names were consistent with their protocol documentation. See https://weatherflow.github.io/SmartWeather/api/udp.html Options: log_raw_packets = False Enable writing all raw UDP packets received to syslog, or wherever weewx is configured to send log info. Will fill up your logs pretty quickly, so only use it as a debugging tool or to identify sensors. udp_address = <broadcast> # udp_address = 0.0.0.0 # udp_address = 255.255.255.255 This is the broadcast address that we should be listening on for packets. If the driver throws an error on start, try one of the other commented-out options (in order). This seems to be platform-specific. All three work on Debian Linux and my Raspberry Pi, but only 0.0.0.0 works on my Macbook running OS-X or MacOS. Don't ask about Windows, since I don't have a test platform to see if it will even work. udp_port = 50222 The IP port that we should be listening for UDP packets from. WeatherFlow's default is 50222. udp_timeout = 400 The number of seconds that we should wait for an incoming packet on the UDP socket before we give up and log an error into syslog. I cannot determine whether or not weewx cares whether a station driver is non-blocking or blocking, but encountered a situation in testing where the WeatherFlow Hub rebooted for a firmware update and it caused the driver to throw a timeout error and exit. I have no idea what the default timeout value even is, but decided to make it configurable in case it is important to someone else. My default of 90 seconds seems reasonable, with the Air sending observations every 60 seconds. If you are an old-school programmer like me who thinks that computers should wait forever until they receive data, the Python value "None" should disable the timeout. In any case, the driver will just log an error into syslog and keep on processing. It isn't like it is the end of the world if you pick a wrong value, but you may have a better chance of missing packets during the brief error trapping time with a really short duration. share_socket = False Whether or not the UDP socket should be shared with other local programs also listening for WeatherFlow packets. Default is False because I suspect that some obscure Python implementation will have problems sharing the socket. Feel free to set it to True if you have other apps running on your weewx host listening for WF UDP packets. Finally, let me add a thank you to Matthew Wall for the sensor map naming logic that I borrowed from his weewx-SDR station driver code: https://github.com/matthewwall/weewx-sdr I guess that I should also thank David St. John and the "dream team" at WeatherFlow for all of the hard work and forethought that they put into making this weather station a reality. I can't sing enough praises for whoever came up with the idea to send observation packets out live via UDP broadcasts, and think that they should be nominated for a Nobel Prize or something... """ from __future__ import with_statement import json import time import sys from socket import * import weewx.units import weewx.drivers import weewx.wxformulas from weeutil.weeutil import tobool import requests from datetime import datetime import calendar from configobj import ConfigObj # Default settings... DRIVER_VERSION = "1.13" HARDWARE_NAME = "WeatherFlow" DRIVER_NAME = 'WeatherFlowUDP' try: # Test for new-style weewx logging by trying to import weeutil.logger import weeutil.logger import logging log = logging.getLogger(__name__) def logdbg(msg): log.debug(msg) def loginf(msg): log.info(msg) def logwrn(msg): log.warning(msg) def logerr(msg): log.error(msg) except ImportError: # Old-style weewx logging import syslog def logmsg(level, msg): syslog.syslog(level, 'weatherflowudp: %s:' % msg) def logdbg(msg): logmsg(syslog.LOG_DEBUG, msg) def loginf(msg): logmsg(syslog.LOG_INFO, msg) def logwrn(msg): logmsg(syslog.LOG_WARNING, msg) def logerr(msg): logmsg(syslog.LOG_ERR, msg) class DriverException(Exception): pass # Observation record fields... fields = dict() fields['obs_air'] = ('time_epoch', 'station_pressure', 'air_temperature', 'relative_humidity', 'lightning_strike_count', 'lightning_strike_avg_distance', 'battery', 'report_interval') fields['obs_sky'] = ('time_epoch', 'illuminance', 'uv', 'rain_accumulated', 'wind_lull', 'wind_avg', 'wind_gust', 'wind_direction', 'battery', 'report_interval', 'solar_radiation', 'local_day_rain_accumulation', 'precipitation_type', 'wind_sample_interval') fields['rapid_wind'] = ('time_epoch', 'wind_speed', 'wind_direction') fields['evt_precip'] = ('time_epoch',) fields['evt_strike'] = ('time_epoch', 'distance', 'energy') fields['obs_st'] = ('time_epoch', 'wind_lull', 'wind_avg', 'wind_gust', 'wind_direction', 'wind_sample_interval', 'station_pressure', 'air_temperature', 'relative_humidity', 'illuminance', 'uv', 'solar_radiation', 'rain_accumulated', 'precipitation_type', 'lightning_strike_avg_distance', 'lightning_strike_count', 'battery', 'report_interval') def loader(config_dict, engine): return WeatherFlowUDPDriver(config_dict) def mapToWeewxPacket(pkt, sensor_map, isRest, interval = 1): packet = dict() if 'time_epoch' in pkt: packet = { 'dateTime': pkt['time_epoch'], 'usUnits' : weewx.METRICWX } if isRest: packet.update({'interval':interval}) for pkt_weewx, pkt_label in sensor_map.items(): for label in ensureList(pkt_label): if label.endswith('.rest') and isRest: label = label[:-5] elif label.endswith('.udp') and not isRest: label = label[:-4] if label.replace("-","_") in pkt: packet[pkt_weewx] = pkt[label.replace("-","_")] return packet def parseUDPPacket(pkt, calculator = None): packet = dict() if 'serial_number' in pkt: if 'type' in pkt: serial_number = pkt['serial_number'].replace("-","_") pkt_label = serial_number + "." + pkt['type'] for key in pkt: packet[key + "." + pkt_label] = pkt[key] if pkt['type'] in ('obs_air', 'obs_sky', 'obs_st'): packet['time_epoch'] = pkt['obs'][0][0] for key, value in zip(fields[pkt['type']], pkt['obs'][0]): packet[key + "." + pkt_label] = value if key == "battery" and pkt['type'] == 'obs_st' and calculator: calculator.addVoltage(value) mode = calculator.getMode() if mode != None: packet["battery_mode." + pkt_label] = mode elif pkt['type'] == 'rapid_wind': packet['time_epoch'] = pkt['ob'][0] for key, value in zip(fields['rapid_wind'], pkt['ob']): packet[key + "." + pkt_label] = value elif pkt['type'] in ('evt_strike', 'evt_precip'): packet['time_epoch'] = pkt['evt'][0] for key, value in zip(fields[pkt['type']], pkt['evt']): packet[key + "." + pkt_label] = value elif pkt['type'] == 'device_status': packet['time_epoch'] = pkt['timestamp'] elif pkt['type'] == 'hub_status': packet['time_epoch'] = pkt['timestamp'] elif pkt['type'][0:2] == 'X_': packet['time_epoch'] = int(time.time()) elif pkt['type'] not in ('light_debug') : logerr("Unknown packet type: '%s'" % pkt['type']) else: loginf('Corrupt UDP packet? %s' % pkt) else: loginf('Corrupt UDP packet? %s' % pkt) return packet def getStationsUrl(token): return 'https://swd.weatherflow.com/swd/rest/stations?token={token}'.format(token = token) def getObservationsUrl(start, end, token, device_id): return 'https://swd.weatherflow.com/swd/rest/observations/device/{device_id}?token={token}&time_start={start}&time_end={end}'.format(token = token, device_id = device_id, start = start, end = end) def getStationDevices(token): if not token: return dict(), dict() response = requests.get(getStationsUrl(token)) if (response.status_code != 200): raise DriverException("Could not fetch station information from WeatherFlow webservice: {}".format(response)) stations = response.json()["stations"] device_id_dict = dict() device_dict = dict() for station in stations: for device in station["devices"]: if 'serial_number' in device: device_id_dict.update({device["device_id"]:device["serial_number"]}) device_dict.update({device["serial_number"]:device["device_id"]}) return device_id_dict, device_dict def readDataFromWF(start, token, devices, device_dict, batch_size): isFinished = False while not isFinished: # end > calendar.timegm(datetime.utcnow().utctimetuple()): end = start + batch_size - 1 lastTimestamp = None logdbg('Reading from {} to {}'.format(datetime.utcfromtimestamp(start), datetime.utcfromtimestamp(end))) results = list() timestamps = None for device in devices: logdbg('Reading for {} from {} to {}'.format(device, datetime.utcfromtimestamp(start), datetime.utcfromtimestamp(lastTimestamp or end))) response = requests.get(getObservationsUrl(start, lastTimestamp or end, token, device_dict[device])) if (response.status_code != 200): raise DriverException("Could not fetch records from WeatherFlow webservice: {}".format(response)) jsonResponse = response.json() if lastTimestamp == None and jsonResponse['obs'] != None: lastTimestamp = sorted(jsonResponse['obs'], key = lambda i: i[0], reverse = True)[0][0] result = dict() result['device_id'] = jsonResponse['device_id'] result['type'] = jsonResponse['type'] observations = sorted((jsonResponse['obs'] or list()), key = lambda x : x[0]) newTimestamps = [observation[0] for observation in observations] timestamps = timestamps or (timestamps or list()) + list(set(newTimestamps) - set(timestamps or list())) result['obs'] = dict(zip(newTimestamps, observations)) results.append(result) combinedResult = dict() combinedResult['device_ids'] = [result['device_id'] for result in results] combinedResult['types'] = [result['type'] for result in results] combinedResult['obs'] = list() for timestamp in sorted(timestamps): observationsForTimestamp = list() for result in results: if 'obs' in result and timestamp in result['obs']: observationsForTimestamp.append(result['obs'][timestamp]) else: observationsForTimestamp.append(None) combinedResult['obs'].append(observationsForTimestamp) yield combinedResult if end > calendar.timegm(datetime.utcnow().utctimetuple()): isFinished = True else: start = end if lastTimestamp == None else lastTimestamp + 1 def parseRestPacket(pkt, device_id_dict, calculator): label_list = list() pos = 0 for device_id in pkt['device_ids']: label_list.append(device_id_dict[device_id].replace("-","_") + "." + pkt['types'][pos]) pos += 1 fields_list = list() for type in pkt['types']: fields_list.append(fields[type]) for observations in pkt['obs']: packet = dict() pos = 0 for observation in observations: if observation == None: continue packet['time_epoch'] = observation[0] for key, value in zip(fields_list[pos], observation): packet[key + "." + label_list[pos]] = value if key == "battery" and label_list[pos].endswith(".obs_st"): calculator.addVoltage(value) mode = calculator.getMode() if mode != None: packet["battery_mode." + label_list[pos]] = mode pos += 1 yield packet def getDevices(devicesList, devices, token, printIt=False): if not token: return list() devicesList = ensureList(devicesList) result = list() for device in devicesList: if device != '': if device in devices: result.append(device.strip().upper()) else: warning('Configured device {} is unknown. Skipped.'.format(device), printIt) if not result: raise DriverException("None of the configured devices ({}) were available for the given API-token. Aborting.".format(', '.join(devicesList))) return result def isString(input): try: basestring except NameError: basestring = str return isinstance(input, basestring) def ensureList(inputList): if isString(inputList): return [inputList] else: return inputList def getSensorMap(devices, device_id_dict, printIt=False): fieldsDictionary = { 'ST-': { 'evt_strike.udp': { # 'lightning_energy': 'energy', # 'lightning_distance': 'distance', }, 'rapid_wind.udp': { 'windSpeed': 'wind_speed', 'windDir': 'wind_direction' }, 'obs_st.rest': { # 'lightning_strike_count': 'lightning_strike_count', # 'lightning_distance': 'lightning_strike_avg_distance', 'windSpeed': 'wind_avg', 'windDir': 'wind_direction', 'windGust': 'wind_gust', }, 'obs_st': { 'outTemp': 'air_temperature', 'outHumidity': 'relative_humidity', 'pressure': 'station_pressure', 'lightning_strike_count': 'lightning_strike_count', 'lightning_distance': 'lightning_strike_avg_distance', 'outTempBatteryStatus': 'battery', 'luminosity': 'illuminance', 'UV': 'uv', 'rain': 'rain_accumulated', 'radiation': 'solar_radiation', 'batteryStatus1': 'battery_mode' }, 'device_status': { 'signal1': 'rssi', 'signal2': 'hub_rssi' } }, 'AR-': { 'obs_air': { 'outTemp': 'air_temperature', 'outHumidity': 'relative_humidity', 'pressure': 'station_pressure', 'lightning_strike_count': 'lightning_strike_count', 'lightning_distance': 'lightning_strike_avg_distance', 'outTempBatteryStatus': 'battery' } }, 'SK-': { 'obs_sky': { 'windSpeed': 'wind_avg', 'windDir': 'wind_direction', 'windGust': 'wind_gust', 'luminosity': 'illuminance', 'UV': 'uv', 'rain': 'rain_accumulated', 'windBatteryStatus': 'battery', 'radiation': 'solar_radiation' } }, 'HB-': { 'hub_status': { 'signal3': 'rssi' } } } configObj = ConfigObj() configObj['sensor_map'] = {} devices.reverse() for device in devices: if device not in device_id_dict.values(): warning('Unknown device {}, skipping'.format(device), printIt) continue typeString = device[0:3] if typeString not in fieldsDictionary: warning('Unknown type for device {}' % device, printIt) continue errors = False for packageType in fieldsDictionary[typeString]: fields = fieldsDictionary[typeString][packageType] for field in fields: mapping = '{}.{}.{}'.format(fields[field], device, packageType) if field in configObj['sensor_map'].dict(): existingMapping = configObj['sensor_map'][field] hasUdp = isString(existingMapping) and (packageType.endswith('.udp') or existingMapping.endswith('.udp')) hasRest = isString(existingMapping) and (packageType.endswith('.rest') or existingMapping.endswith('rest')) if hasUdp and hasRest: mapping = [existingMapping, mapping] else: errors = True warning('Cannot map field {} to {} as it is already set to \'{}\''.format(field, device, configObj['sensor_map'][field]), printIt) configObj['sensor_map'].update({field: mapping}) if errors: warning('Mapping errors occurred. You should probably configure a manual sensor-map', printIt) return configObj['sensor_map'] def getHardwareName(devices): typeDict = { 'ST-':'Tempest', 'AR-':'Air', 'SK-':'Sky' } result = '' for device in devices: if device[:3] in typeDict: result = '%s/%s' % (result, typeDict[device[:3]]) if result != '' else typeDict[device[:3]] return '%s %s' % (HARDWARE_NAME, result) def warning(warning, printIt): if printIt: print('Warning: {}'.format(warning)) else: logwrn(warning) class WeatherFlowUDPDriver(weewx.drivers.AbstractDevice): def __init__(self, all_dict): loginf('driver version is %s' % DRIVER_VERSION) stn_dict = all_dict[DRIVER_NAME] std_dict = all_dict['StdArchive'] self._log_raw_packets = tobool(stn_dict.get('log_raw_packets', False)) self._udp_address = stn_dict.get('udp_address', '<broadcast>') self._udp_port = int(stn_dict.get('udp_port', 50222)) self._udp_timeout = int(stn_dict.get('udp_timeout', 90)) self._share_socket = tobool(stn_dict.get('share_socket', False)) self._sensor_map = stn_dict.get('sensor_map', None) self._token = stn_dict.get('token', '') self._batch_size = int(stn_dict.get('batch_size', 24 * 60 * 60)) self._device_id_dict, self._device_dict = getStationDevices(self._token) self._devices = getDevices(stn_dict.get('devices', list(self._device_dict.keys())), self._device_dict.keys(), self._token) self._rest_enabled = tobool(stn_dict.get('rest_enabled', True)) self._archive_interval = int(std_dict.get('archive_interval', 60)) self._loopHiLo = tobool(std_dict.get('loop_hilo', True)) self._archive_delay = int(std_dict.get('archive_delay', 15)) if self._sensor_map == None: self._sensor_map = getSensorMap(self._devices, self._device_id_dict) self._calculator = BatteryModeCalculator() loginf('sensor map is %s' % self._sensor_map) loginf('*** Sensor names per packet type') for pkt_type in fields: loginf('packet %s: %s' % (pkt_type,fields[pkt_type])) def hardware_name(self): return getHardwareName(self._devices) def genLoopPackets(self): for udp_packet in self.gen_udp_packets(): m2 = parseUDPPacket(udp_packet, self._calculator) m3 = mapToWeewxPacket(m2, self._sensor_map, False) if len(m3) > 2: logdbg('Import from UDP: %s' % datetime.utcfromtimestamp(m3['dateTime'])) yield m3 def gen_udp_packets(self): """Yield raw UDP packets""" loginf('Listening for UDP broadcasts to IP address %s on port %s, with timeout %s and share_socket %s...' % (self._udp_address,self._udp_port,self._udp_timeout,self._share_socket)) sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) try: if self._share_socket: sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind((self._udp_address,self._udp_port)) sock.settimeout(self._udp_timeout) while True: try: m0, host_info = sock.recvfrom(1024) except timeout: logerr('Socket timeout waiting for incoming UDP packet!') else: # Decode the JSON. Some base stations have emitted datagrams that are not pure UTF-8, so # be prepared to catch the exception. try: m1 = json.loads(m0) except UnicodeDecodeError: loginf("Unable to decode packet %s" % m0) else: if self._log_raw_packets: loginf('raw packet: %s' % m1) yield m1 finally: sock.close() def genStartupRecords(self, since_ts): if since_ts == None: since_ts = int(time.time()) - 365 * 24 * 60 * 60 archivePeriod = None if self._token != "" and self._rest_enabled: loginf('Reading from {}'.format(datetime.utcfromtimestamp(since_ts))) for packet in readDataFromWF(since_ts + 1, self._token, self._devices, self._device_dict, self._batch_size): for observation in parseRestPacket(packet, self._device_id_dict, self._calculator): m3 = mapToWeewxPacket(observation, self._sensor_map, True, int((self._archive_interval + 59) / 60)) if len(m3) > 3: logdbg('Import from REST %s' % datetime.utcfromtimestamp(m3['dateTime'])) if self._archive_interval <= 60: yield m3 else: # Use an accumulator and treat REST API data like loop data if not archivePeriod: # init archivePeriod archivePeriod = ArchivePeriod(weeutil.weeutil.startOfInterval(m3['dateTime'], self._archive_interval), self._archive_interval, self._archive_delay) if m3['dateTime'] >= archivePeriod._end_archive_delay_ts: archivePeriod.startNextArchiveInterval(weeutil.weeutil.startOfInterval(m3['dateTime'], self._archive_interval)) # Try adding the API packet to the existing accumulator. If the # timestamp is outside the timespan of the accumulator, an exception # will be thrown try: archivePeriod.addRecord(m3, add_hilo=self._loopHiLo) except weewx.accum.OutOfSpan: # Shuffle accumulators: archivePeriod.startNextArchiveInterval() # Try again: archivePeriod.addRecord(m3, add_hilo=self._loopHiLo) archive_record = archivePeriod.getPreviousRecord() if archive_record: logdbg('Archiving accumulated data from REST %s' % archivePeriod._start_archive_period_ts) yield archive_record if archivePeriod: archive_record = archivePeriod.getRecord() if archive_record: # return record from last processed accumulator yield archive_record else: loginf('Skipped fetching from REST API') class ArchivePeriod: def __init__(self, start_archive_period_ts, archive_interval, archive_delay): self._start_archive_period_ts = start_archive_period_ts self._archive_interval = archive_interval self._archive_delay = archive_delay self._end_archive_period_ts = self._start_archive_period_ts + self._archive_interval if (self._end_archive_period_ts > time.time()): self._end_archive_period_ts = int(time.time()) self._end_archive_delay_ts = self._end_archive_period_ts + self._archive_delay self._accumulator = weewx.accum.Accum(weeutil.weeutil.TimeSpan(self._start_archive_period_ts, self._end_archive_period_ts)) self._old_accumulator = None def startNextArchiveInterval(self, start_archive_period_ts): self._start_archive_period_ts = start_archive_period_ts self._end_archive_period_ts = self._start_archive_period_ts + self._archive_interval if (self._end_archive_period_ts > time.time()): self_end_archive_period_ts = int(time.time()) self._end_archive_delay_ts = self._end_archive_period_ts + self._archive_delay (self._old_accumulator, self._accumulator) = \ (self._accumulator, weewx.accum.Accum(weeutil.weeutil.TimeSpan(self._start_archive_period_ts, self._end_archive_period_ts))) def addRecord(self, record, add_hilo=True): self._accumulator.addRecord(record, add_hilo) def getPreviousRecord(self): if (self._old_accumulator): record = self._old_accumulator.getRecord() self._old_accumulator = None return record def getRecord(self): return self._accumulator.getRecord() class BatteryModeCalculator: def __init__(self): self._list = list() def addVoltage(self, value): self._list.append(value) if len(self._list) > 10: self._list.pop(0) def __isCharging(self): if len(self._list) == 10: firstValue = sum(self._list[0:5]) secondValue = sum(self._list[5:10]) return secondValue > firstValue else: return False def getMode(self): if len(self._list) == 0: return None currentValue = sum(self._list[-5:]) / len(self._list[-5:]) if self.__isCharging(): if currentValue >= 2.455: return 0 elif currentValue >= 2.41: return 1 elif currentValue >= 2.375: return 2 else: return 3 else: if currentValue <= 2.355: return 3 elif currentValue <= 2.39: return 2 elif currentValue <= 2.415: return 1 else: return 0 if __name__ == '__main__': import optparse import weeutil.logger from weeutil.weeutil import to_sorted_string weewx.debug = 2 weeutil.logger.setup('weatherflow', {}) usage = """Usage: python -m weatherflow --help python -m weatherflow --version python -m weatherflow --create-sensor-map --token=TOKEN [--devices=DEVICES] python -m weatherflow [--host=HOST] [--port=PORT] [--timeout=TIMEOUT] [--share-socket] [--hide-raw] [--hide-parsed]""" parser = optparse.OptionParser(usage=usage) parser.add_option('--version', action='store_true', help='Display driver version') parser.add_option('--address', default='255.255.255.255', help='UDP address to use. Default is "255.255.255.255".', metavar="ADDR") parser.add_option('--port', type="int", default=50222, help='Socket port to use. Default is "50222"', metavar="PORT") parser.add_option('--timeout', type="int", default=20, help="How long to wait for a packet.") parser.add_option('--share-socket', default=False, action="store_true", help="Allow another process to access the port.") parser.add_option('--hide-raw', default=False, action='store_true', help="Do not show raw UDP packets.") parser.add_option('--hide-parsed', default=False, action='store_true', help="Do not show parsed UDP packets.") parser.add_option('--create-sensor-map', action='store_true', help="Generate a sensor-map.") parser.add_option('--token', help="Provide API token for WeatherFlow API.", metavar="TOKEN") parser.add_option('--devices', help='Provide devices (comma-separated) to use for the sensor-map.', metavar='DEVICES', default='') (options, args) = parser.parse_args() if options.version: print("Weatherflow driver version %s" % DRIVER_VERSION) exit(0) if options.create_sensor_map: if not options.token: print('Please provide an API token with the --token=TOKEN option') exit(1) try: device_id_dict, device_dict = getStationDevices(options.token) devicesList = [s.strip() for s in options.devices.split(',')] if ',' in options.devices else options.devices if len(options.devices) > 0 else list(device_dict.keys()) devices = getDevices(devicesList, device_dict.keys(), options.token, True) sensor_map = getSensorMap(devices, device_id_dict, True) print('Sensor map:') print('') print(' [[sensor_map]]') for key in sensor_map.keys(): if isinstance(sensor_map[key], list): print(' {} = {}'.format(key, ', '.join(sensor_map[key]))) else: print(' {} = {}'.format(key, sensor_map[key])) print('') print('You can copy the above into your weewx.conf file directly after your [WeatherFlowUDP] section') exit(0) except DriverException as ex: print('Error: {}'.format(ex)) exit(1) print("Using address '%s' on port %d" % (options.address, options.port)) config_dict = { 'WeatherFlowUDP': { 'address': options.address, 'port': options.port, 'timeout': options.timeout, 'share_socket': options.share_socket, }, 'StdArchive' : { } } device = loader(config_dict, None) for pkt in device.gen_udp_packets(): if not options.hide_raw: print('raw:', pkt) parsed = parseUDPPacket(pkt) if not options.hide_parsed: print('parsed:', to_sorted_string(parsed))