diff --git a/bin/wee_config_vantage b/bin/wee_config_vantage index 61b27f78..2a2dcd03 100755 --- a/bin/wee_config_vantage +++ b/bin/wee_config_vantage @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python # # Copyright (c) 2012 Tom Keffer # @@ -13,7 +13,6 @@ import optparse import sys import syslog -import time import weewx.drivers.vantage import weeutil.weeutil @@ -23,6 +22,8 @@ description = """Configures the Davis Vantage weather station.""" usage="""%prog: [config_file] [--help] [--info] [--clear] [--set-interval=SECONDS] [--set-altitude=FEET] [--set-barometer=inHg] [--set-bucket=CODE] [--set-rain-year-start=MM] + [--set-calibration=VARIABLE,OFFSET] + [--set-transmitter-type=CHANNEL,TYPE,TEMP,HUM] [--set-time] [--set-dst=[AUTO|ON|OFF]] [--set-tz-code=TZCODE] [--set-tz-offset=HHMM] [--set-lamp=[ON|OFF]] [--dump] [--logger_summary=FILE] [--start | --stop]""" @@ -57,6 +58,15 @@ def main(): "Specify '0' for 0.01 inches; '1' for 0.2 MM; '2' for 0.1 MM") parser.add_option("--set-rain-year-start", type=int, dest="set_rain_year_start", metavar="MM", help="Set the rain year start (1=Jan, 2=Feb, etc.).") + parser.add_option("--set-calibration", type=str, dest="set_calibration", metavar="VARIABLE,OFFSET", + help="Set the onboard calibration for VARIABLE "\ + "(inTemp, outTemp, extraTemp<1-7>, inHumid, outHumid, extraHumid<1-7>, "\ + "soilTemp<1-4>, leafTemp<1-4>, windDir) to OFFSET (Fahrenheit, %, degrees)") + parser.add_option("--set-transmitter-type", type=str, dest="set_transmitter_type", metavar="CHANNEL,TYPE,TEMP,HUM", + help="Set the transmitter type for CHANNEL (1-8), "\ + "TYPE (0=iss, 1=temp, 2=hum, 3=temp_hum, 4=wind, "\ + "5=rain, 6=leaf, 7=soil, 8=leaf_soil, 9=sensorlink, 10=none), "\ + "as extra TEMP station and extra HUM station (both 1-7, if applicable)") parser.add_option("--set-time", action="store_true", dest="set_time", help="Set the onboard clock to the current time.") parser.add_option("--set-dst", dest="set_dst", help="Set DST to 'ON', 'OFF', or 'AUTO'", metavar="AUTO|ON|OFF") parser.add_option("--set-tz-code", type=int, dest="set_tz_code", @@ -98,6 +108,10 @@ def main(): set_bucket(station, options.set_bucket) if options.set_rain_year_start is not None: set_rain_year_start(station, options.set_rain_year_start) + if options.set_calibration is not None: + set_calibration(station, options.set_calibration) + if options.set_transmitter_type is not None: + set_transmitter_type(station, options.set_transmitter_type) if options.set_time: set_time(station) if options.set_dst: @@ -181,6 +195,23 @@ def info(station, dest=sys.stdout): except: pass + # Add transmitter types for each channel, if we can: + transmitter_list = None + try: + transmitter_list = station.getStnTransmitters() + print >>dest, " TRANSMITTERS: " + for transmitter_id in range(0, 8): + transmitter_type = transmitter_list[transmitter_id]["transmitter_type"] + print >>dest, " Channel %d: %s %s" % ( + transmitter_id + 1, transmitter_type, + "(active)" if transmitter_list[transmitter_id]["listen"] == 1 else "(ignored)") + if transmitter_type in ['temp', 'temp_hum']: + print >>dest, " As extra temperature %d" % transmitter_list[transmitter_id]["temp"] + if transmitter_type in ['hum', 'temp_hum']: + print >>dest, " As extra humidity %d" % transmitter_list[transmitter_id]["hum"] + print >>dest, "" + except: + pass # Add reception statistics if we can: try: @@ -212,6 +243,46 @@ def info(station, dest=sys.stdout): except: pass + # Add temperature/humidity/wind calibration if we can. + try: + calibration_dict = station.getStnCalibration() + print >> dest, """ OTHER CALIBRATION DATA: + Wind direction: %(wind)+.0f deg + Inside Temperature: %(inTemp)+.1f F + Inside Humidity: %(inHumid)+.0f%% + Outside Temperature: %(outTemp)+.1f F + Outside Humidity: %(outHumid)+.0f%%""" % calibration_dict + if transmitter_list is not None: + # Only print the calibrations for channels that we are listening to. + for extraTemp in range(1, 8): + for transmitter_id in range(0, 8): + transmitter_type = transmitter_list[transmitter_id]["transmitter_type"] + if transmitter_type in ['temp', 'temp_hum'] and \ + extraTemp == transmitter_list[transmitter_id]["temp"]: + print >> dest, " Extra Temperature %d: %+.1f F" \ + % (extraTemp, calibration_dict["extraTemp%d" % extraTemp]) + for extraHumid in range(1, 8): + for transmitter_id in range(0, 8): + transmitter_type = transmitter_list[transmitter_id]["transmitter_type"] + if transmitter_type in ['hum', 'temp_hum'] and \ + extraHumid == transmitter_list[transmitter_id]["hum"]: + print >> dest, " Extra Humidity %d: %+.1f F" \ + % (extraHumid, calibration_dict["extraHumid%d" % extraHumid]) + for transmitter_id in range(0, 8): + transmitter_type = transmitter_list[transmitter_id]["transmitter_type"] + if transmitter_type in ['soil', 'leaf_soil']: + for soil in range(1, 5): + print >> dest, " Soil Temperature %d: %+.1f F" \ + % (soil, calibration_dict["soilTemp%d" % soil]) + for transmitter_id in range(0, 8): + transmitter_type = transmitter_list[transmitter_id]["transmitter_type"] + if transmitter_type in ['leaf', 'leaf_soil']: + for leaf in range(1, 5): + print >> dest, " Leaf Temperature %d: %+.1f F" % (leaf, calibration_dict["leafTemp%d" % leaf]) + print >> dest, "" + except: + raise + def set_interval(station, new_interval_seconds): """Set the console archive interval.""" @@ -328,6 +399,100 @@ def set_rain_year_start(station, rain_year_start): elif ans == 'n': print "Nothing done." +def set_calibration(station, calibration_list): + """Set the on-board calibration for a temperature, humidity or wind direction variable.""" + (variable, offset_str) = calibration_list.split(',') + # These variables may be calibrated. + temp_variables = ['inTemp', 'outTemp' ] + \ + ['extraTemp%d' % i for i in range(1, 8)] + \ + ['soilTemp%d' % i for i in range(1, 5)] + \ + ['leafTemp%d' % i for i in range(1, 5)] + + humid_variables = ['inHumid', 'outHumid'] + \ + ['extraHumid%d' % i for i in range(1, 8)] + + # Wind direction can also be calibrated. + if variable == "windDir": + offset = int(offset_str) + if not -359 < offset < 359: + print >>sys.stderr, "Wind direction calibration %d is out of range." % (offset) + else: + ans = None + while ans not in ['y', 'n']: + print "Proceeding will set calibration for wind direction to %+d." % (offset) + ans = raw_input("Are you sure you want to proceed (y/n)? ") + if ans == 'y' : + try: + station.setCalibrationWindDir(offset) + except StandardError, e: + print >>sys.stderr, "Unable to set new wind calibration. Reason:\n\t****", e + else: + print "Wind direction calibration now set to %+d." % (offset) + elif variable in temp_variables: + offset = float(offset_str) + if not -12.8 < offset < 12.7: + print >>sys.stderr, "Temperature calibration %+.1f is out of range." % (offset) + else: + ans = None + while ans not in ['y', 'n']: + print "Proceeding will set calibration for temperature %s to %.1f." % (variable, offset) + ans = raw_input("Are you sure you want to proceed (y/n)? ") + if ans == 'y' : + try: + station.setCalibrationTemp(variable, offset) + except StandardError, e: + print >>sys.stderr, "Unable to set new temperature calibration. Reason:\n\t****", e + else: + print "Temperature calibration %s now set to %+.1f." % (variable, offset) + elif variable in humid_variables: + offset = int(offset_str) + if not -100 < offset < 100: + print >>sys.stderr, "Humidity calibration %+d is out of range." % (offset) + else: + ans = None + while ans not in ['y', 'n']: + print "Proceeding will set calibration for humidity %s to %+d." % (variable, offset) + ans = raw_input("Are you sure you want to proceed (y/n)? ") + if ans == 'y' : + try: + station.setCalibrationHumid(variable, offset) + except StandardError, e: + print >>sys.stderr, "Unable to set new humidity calibration. Reason:\n\t****", e + else: + print "Humidity calibration %s now set to %+d." % (variable, offset) + else: + print >>sys.stderr, "Unknown variable %s" % (variable) + +def set_transmitter_type(station, transmitter_list): + """Set the transmitter type for one of the eight channels.""" + + transmitter_list = map((lambda x: int(x) if x != "" else None), transmitter_list.split(',')) + channel = transmitter_list[0] + transmitter_type = transmitter_list[1] + extra_temp = transmitter_list[2] if len(transmitter_list) > 2 else None + extra_hum = transmitter_list[3] if len(transmitter_list) > 3 else None + + transmitter_type_name = weewx.drivers.vantage.Vantage.transmitter_type_dict[transmitter_type] + if transmitter_type_name in ['temp', 'temp_hum'] and extra_temp not in range(1,8): + print "Transmitter type %s requires extra_temp in range 1-7'" % transmitter_type_name + return + if transmitter_type_name in ['hum', 'temp_hum'] and extra_hum not in range(1,8): + print "Transmitter type %s requires extra_hum in range 1-7'" % transmitter_type_name + return + ans = None + while ans not in ['y', 'n']: + print "Proceeding will set channel %d to type %d (%s)." % (channel, transmitter_type, transmitter_type_name) + ans = raw_input("Are you sure you want to proceed (y/n)? ") + if ans == 'y' : + try: + station.setTransmitterType(channel, transmitter_type, extra_temp, extra_hum) + except StandardError, e: + print >>sys.stderr, "Unable to set transmitter type. Reason:\n\t****", e + else: + print "Transmitter %d now set to type %d." % (channel, transmitter_type) + else: + print "Nothing done." + def set_time(station): print "Setting time on console..." station.setTime() diff --git a/bin/weewx/drivers/vantage.py b/bin/weewx/drivers/vantage.py index 47c974d1..c8454b83 100644 --- a/bin/weewx/drivers/vantage.py +++ b/bin/weewx/drivers/vantage.py @@ -338,6 +338,9 @@ class Vantage(weewx.abstractstation.AbstractStation): wind_unit_dict = {0:'mile_per_hour', 1:'meter_per_second', 2:'km_per_hour', 3:'knot'} wind_cup_dict = {0:'small', 1:'large'} rain_bucket_dict = {0: "0.01 inches", 1: "0.2 MM", 2: "0.1 MM"} + transmitter_type_dict = {0: 'iss', 1: 'temp', 2: 'hum', 3: 'temp_hum', 4: 'wind', + 5: 'rain', 6: 'leaf', 7: 'soil', 8: 'leaf_soil', + 9: 'sensorlink', 10: 'none'} def __init__(self, **vp_dict) : """Initialize an object of type Vantage. @@ -371,12 +374,13 @@ class Vantage(weewx.abstractstation.AbstractStation): """ # TODO: These values should really be retrieved dynamically from the VP: - self.iss_id = int(vp_dict.get('iss_id', 1)) self.model_type = 2 # = 1 for original VantagePro, = 2 for VP2 # These come from the configuration dictionary: self.wait_before_retry= float(vp_dict.get('wait_before_retry', 1.2)) self.max_tries = int(vp_dict.get('max_tries' , 4)) + self.iss_id = vp_dict.get('iss_id', None) + if self.iss_id is not None: self.iss_id = int(self.iss_id) self.save_monthRain = None self.max_dst_jump = 7200 @@ -789,6 +793,100 @@ class Vantage(weewx.abstractstation.AbstractStation): syslog.syslog(syslog.LOG_NOTICE, "vantage: Lamp set to '%s'" % onoff) + def setTransmitterType(self, new_channel, new_transmitter_type, new_extra_temp, new_extra_hum): + """Set the transmitter type for one of the eight channels.""" + + # Default value just for tidiness. + new_temp_hum_bits = 0xFF + + # Check arguments are consistent. + if new_channel not in range(0, 8): + raise weewx.ViolatedPrecondition("Invalid channel %d" % new_channel) + if new_transmitter_type not in range(0, 11): + raise weewx.ViolatedPrecondition("Invalid transmitter type %d" % new_transmitter_type) + if Vantage.transmitter_type_dict[new_transmitter_type] in ['temp', 'temp_hum']: + if new_extra_temp not in range(1, 9): + raise weewx.ViolatedPrecondition("Invalid extra temperature number %d" % new_extra_temp) + # Extra temp is origin 0. + new_temp_hum_bits = new_temp_hum_bits & 0xF0 | (new_extra_temp - 1) + if Vantage.transmitter_type_dict[new_transmitter_type] in ['hum', 'temp_hum']: + if new_extra_hum not in range(1, 9): + raise weewx.ViolatedPrecondition("Invalid extra humidity number %d" % new_extra_hum) + # Extra humidity is origin 1. + new_temp_hum_bits = new_temp_hum_bits & 0x0F | (new_extra_hum << 4) + + # Preserve top nibble, is related to repeaters. + old_type_bits = self._getEEPROM_value(0x19 + (new_channel-1)*2)[0] + new_type_bits = old_type_bits & 0xF0 | new_transmitter_type + # Transmitter type 10 is "none"; turn off listening too. + usetx = 1 if new_transmitter_type != 10 else 0 + old_usetx_bits = self._getEEPROM_value(0x17)[0] + new_usetx_bits = old_usetx_bits & ~(1 << (new_channel - 1)) | usetx * (1 << new_channel - 1) + + # Tell the console to put two bytes in hex location 0x19 or 0x1B or ... depending on channel. + self.port.send_data("EEBWR %X 02\n" % (0x19 + (new_channel-1)*2)) + # Follow it up with the data: + self.port.send_data_with_crc16(chr(new_type_bits) + chr(new_temp_hum_bits), max_tries=1) + # Tell the console to put one byte in hex location 0x17 + self.port.send_data("EEBWR 17 01\n") + # Follow it up with the data: + self.port.send_data_with_crc16(chr(new_usetx_bits), max_tries=1) + # Then call NEWSETUP to get it to stick: + self.port.send_data("NEWSETUP\n") + + self._setup() + syslog.syslog(syslog.LOG_NOTICE, "vantage: Transmitter type for channel %d set to %d (%s)" %( + new_channel, new_transmitter_type, Vantage.transmitter_type_dict[new_transmitter_type])) + + def setCalibrationWindDir(self, offset): + """Set the on-board wind direction calibration.""" + if offset < -359 or offset > 359: + raise weewx.ViolatedPrecondition("Offset %d out of range [-359, 359]." % offset) + bytes = struct.pack(" 12.7: + raise weewx.ViolatedPrecondition("Offset %.1f out of range [-12.8, 12.7]." % offset) + byte = struct.pack("b", int(round(offset*10))) + variable_dict = { 'outTemp': 0x34 } + for i in range(1, 8): variable_dict['extraTemp%d' % i] = 0x34 + i + for i in range(1, 5): variable_dict['soilTemp%d' % i] = 0x3B + i + for i in range(1, 5): variable_dict['leafTemp%d' % i] = 0x3F + i + if variable == "inTemp": + # Inside temp is special, needs ones' complement in next byte. + complement_byte = struct.pack("B", ~int(round(offset*10)) & 0xFF) + self.port.send_data("EEBWR 32 02\n") + self.port.send_data_with_crc16(byte + complement_byte, max_tries=1) + elif variable in variable_dict: + # Other variables are just sent as-is. + self.port.send_data("EEBWR %X 01\n" % variable_dict[variable]) + self.port.send_data_with_crc16(byte, max_tries=1) + else: + raise weewx.ViolatedPrecondition("Variable name %s not known" % variable) + syslog.syslog(syslog.LOG_NOTICE, "vantage: Temperature calibration %s set to %.1f" % (variable, offset)) + + def setCalibrationHumid(self, variable, offset): + """Set an on-board humidity calibration.""" + # Offset is in percentage points. + if offset < -100 or offset > 100: + raise weewx.ViolatedPrecondition("Offset %d out of range [-100, 100]." % offset) + byte = struct.pack("b", offset) + variable_dict = { 'inHumid': 0x44, 'outHumid': 0x45 } + for i in range(1, 8): variable_dict['extraHumid%d' % i] = 0x45 + i + if variable in variable_dict: + self.port.send_data("EEBWR %X 01\n" % variable_dict[variable]) + self.port.send_data_with_crc16(byte, max_tries=1) + else: + raise weewx.ViolatedPrecondition("Variable name %s not known" % variable) + syslog.syslog(syslog.LOG_NOTICE, "vantage: Humidity calibration %s set to %d" % (variable, offset)) + def clearLog(self): """Clear the internal archive memory in the Vantage.""" for unused_count in xrange(self.max_tries): @@ -858,6 +956,75 @@ class Vantage(weewx.abstractstation.AbstractStation): return (stnlat, stnlon, man_or_auto, dst, gmt_or_zone, zone_code, gmt_offset) + def getStnTransmitters(self): + """ Get the types of transmitters on the eight channels.""" + + transmitters = [ ] + use_tx = self._getEEPROM_value(0x17)[0] + transmitter_data_2 = self._getEEPROM_value(0x19, "8H") + + transmitter_type_list = [Vantage.transmitter_type_dict[ushort & 0x0F] for ushort in transmitter_data_2] + listen_list = [(use_tx >> transmitter_id) & 1 for transmitter_id in range(8)] + + + transmitter_data = self._getEEPROM_value(0x19, "16B") + + for transmitter_id in range(8): + transmitter_type = Vantage.transmitter_type_dict[transmitter_data[transmitter_id * 2] & 0x0F] + transmitter = {"transmitter_type" : transmitter_type, + "listen" : (use_tx >> transmitter_id) & 1 } + if transmitter_type in ['temp', 'temp_hum']: + # Extra temperature is origin 0. + transmitter['temp'] = (transmitter_data[transmitter_id * 2 + 1] & 0xF) + 1 + if transmitter_type in ['hum', 'temp_hum']: + # Extra humidity is origin 1. + transmitter['hum'] = transmitter_data[transmitter_id * 2 + 1] >> 4 + transmitters.append(transmitter) + return transmitters + + def getStnCalibration(self): + """ Get the temperature/humidity/wind calibrations built into the console. """ + (inTemp, inTempComp, outTemp, + extraTemp1, extraTemp2, extraTemp3, extraTemp4, extraTemp5, extraTemp6, extraTemp7, + soilTemp1, soilTemp2, soilTemp3, soilTemp4, leafTemp1, leafTemp2, leafTemp3, leafTemp4, + inHumid, + outHumid, extraHumid1, extraHumid2, extraHumid3, extraHumid4, extraHumid5, extraHumid6, extraHumid7, + wind) = self._getEEPROM_value(0x32, "<27bh") + # inTempComp is 1's complement of inTemp. + if inTemp + inTempComp != -1: + syslog.syslog(syslog.LOG_ERR, "vantage: Inconsistent EEPROM calibration values") + return None; + # Temperatures are in tenths of a degree F; Humidity in 1 percent. + return { + "inTemp": inTemp / 10, + "outTemp": outTemp / 10, + "extraTemp1": extraTemp1 / 10, + "extraTemp2": extraTemp2 / 10, + "extraTemp3": extraTemp3 / 10, + "extraTemp4": extraTemp4 / 10, + "extraTemp5": extraTemp5 / 10, + "extraTemp6": extraTemp6 / 10, + "extraTemp7": extraTemp7 / 10, + "soilTemp1": soilTemp1 / 10, + "soilTemp2": soilTemp2 / 10, + "soilTemp3": soilTemp3 / 10, + "soilTemp4": soilTemp4 / 10, + "leafTemp1": leafTemp1 / 10, + "leafTemp2": leafTemp2 / 10, + "leafTemp3": leafTemp3 / 10, + "leafTemp4": leafTemp4 / 10, + "inHumid": inHumid, + "outHumid": outHumid, + "extraHumid1": extraHumid1, + "extraHumid2": extraHumid2, + "extraHumid3": extraHumid3, + "extraHumid4": extraHumid4, + "extraHumid5": extraHumid5, + "extraHumid6": extraHumid6, + "extraHumid7": extraHumid7, + "wind": wind + } + def startLogger(self): self.port.send_command("START\n") @@ -928,6 +1095,29 @@ class Vantage(weewx.abstractstation.AbstractStation): _loop_map['monthRain'] = _loop_map['yearRain'] = _val100 _loop_map['rainRate'] = _big_val100 + # Try to guess the ISS ID for gauging reception strength. + if self.iss_id is None: + stations = self.getStnTransmitters() + # Wind retransmitter is best candidate. + for station_id in range(0, 8): + if stations[station_id]['transmitter_type'] == 'wind': + self.iss_id = station_id + 1 # Origin 1. + break + else: + # ISS is next best candidate. + for station_id in range(0, 8): + if stations[station_id]['transmitter_type'] == 'iss': + self.iss_id = station_id + 1 # Origin 1. + break + else: + # On Vue, can use VP2 ISS, which reports as "rain" + for station_id in range(0, 8): + if stations[station_id]['transmitter_type'] == 'rain': + self.iss_id = station_id + 1 # Origin 1. + break + else: + self.iss_id = 1 # Pick a reasonable default. + def _getEEPROM_value(self, offset, v_format="B"): """Return a list of values from the EEPROM starting at a specified offset, using a specified format"""