''' Test a VantagePro IP by doing a DMP command ''' tcp_port = 22222 tcp_send_delay = 1 timeout = 5 import socket import struct import time import sys try: hostname = sys.argv[1] except: print "usage: test_ip ip-address" exit(1) print "Using host ", hostname # These are used for serial ports #port = '/dev/ttyUSB0' #baudrate=19200 #import serial class WeeWxIOError(IOError): """Base class of exceptions thrown when encountering an I/O error with the console.""" class WakeupError(WeeWxIOError): """Exception thrown when unable to wake up the console""" class CRCError(WeeWxIOError): """Exception thrown when unable to pass a CRC check.""" class RetriesExceeded(WeeWxIOError): """Exception thrown when max retries exceeded.""" class UnknownArchiveType(StandardError): """Exception thrown after reading an unrecognized archive type.""" # A few handy constants: _ack = chr(0x06) _resend = chr(0x15) # NB: The Davis documentation gives this code as 0x21, but it's actually decimal 21 class EthernetWrapper(object): """Ethernet connection""" def __init__(self, host, port, timeout, tcp_send_delay): self.host = host self.port = port self.timeout = timeout self.tcp_send_delay = tcp_send_delay def wakeup_console(self, max_tries=3, wait_before_retry=1.2): """Wake up a Davis VantagePro console. If unsuccessful, an exception of type weewx.WakeupError is thrown""" # Wake up the console. Try up to max_tries times for unused_count in xrange(max_tries) : try: # Clear out any pending input or output characters: self.flush_output() self.flush_input() # It can be hard to get the console's attention, particularly # when in the middle of a LOOP command. Send a whole bunch of line feeds, # then flush everything, then look for the \n\r acknowledgment self.write('\n\n\n') time.sleep(0.5) self.flush_input() self.write('\n') _resp = self.read(2) if _resp == '\n\r': print "successfully woke up console" return print "Unable to wake up console... sleeping" time.sleep(wait_before_retry) print "Unable to wake up console... retrying" except WeeWxIOError: pass print "Unable to wake up console" raise WakeupError("Unable to wake up VantagePro console") def send_data(self, data): """Send data to the Davis console, waiting for an acknowledging If the is not received, no retry is attempted. Instead, an exception of type weewx.WeeWxIOError is raised data: The data to send, as a string""" self.write(data) # Look for the acknowledging ACK character _resp = self.read() if _resp != _ack: print "No received from console" raise WeeWxIOError("No received from VantagePro console") def send_data_with_crc16(self, data, max_tries=3) : """Send data to the Davis console along with a CRC check, waiting for an acknowledging . If none received, resend up to max_tries times. data: The data to send, as a string""" #Calculate the crc for the data: _crc = crc16(data) # ...and pack that on to the end of the data in big-endian order: _data_with_crc = data + struct.pack(">H", _crc) # Retry up to max_tries times: for unused_count in xrange(max_tries): try: self.write(_data_with_crc) # Look for the acknowledgment. _resp = self.read() if _resp == _ack: return except WeeWxIOError: pass print "Unable to pass CRC16 check while sending data" raise CRCError("Unable to pass CRC16 check while sending data to VantagePro console") def send_command(self, command, max_tries=3, wait_before_retry=1.2): """Send a command to the console, then look for the string 'OK' in the response. Any response from the console is split on \n\r characters and returned as a list.""" for unused_count in xrange(max_tries): try : self.wakeup_console(max_tries=max_tries, wait_before_retry=wait_before_retry) self.write(command) # Takes a bit for the VP to react and fill up the buffer. Sleep for a half sec: time.sleep(0.5) # Can't use function serial.readline() because the VP responds with \n\r, not just \n. # So, instead find how many bytes are waiting and fetch them all nc = self.queued_bytes() _buffer = self.read(nc) # Split the buffer on the newlines _buffer_list = _buffer.strip().split('\n\r') # The first member should be the 'OK' in the VP response if _buffer_list[0] == 'OK' : # Return the rest: return _buffer_list[1:] except WeeWxIOError: # Caught an error. Keep trying... continue print "Max retries exceeded while sending command %s" % command raise RetriesExceeded("Max retries exceeded while sending command %s" % command) def get_data_with_crc16(self, nbytes, prompt=None, max_tries=3) : """Get a packet of data and do a CRC16 check on it, asking for retransmit if necessary. It is guaranteed that the length of the returned data will be of the requested length. An exception of type CRCError will be thrown if the data cannot pass the CRC test in the requested number of retries. nbytes: The number of bytes (including the 2 byte CRC) to get. prompt: Any string to be sent before requesting the data. Default=None max_tries: Number of tries before giving up. Default=3 returns: the packet data as a string. The last 2 bytes will be the CRC""" if prompt: self.write(prompt) first_time = True for unused_count in xrange(max_tries): try: if not first_time: self.write(_resend) _buffer = self.read(nbytes) if crc16(_buffer) == 0 : return _buffer except WeeWxIOError: pass first_time = False print "Unable to pass CRC16 check while getting data" raise CRCError("Unable to pass CRC16 check while getting data") def openPort(self): try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(self.timeout) self.socket.connect((self.host, self.port)) except: print "Unable to connect to ethernet host %s on port %d." % (self.host, self.port) raise def closePort(self): try: # This will cancel any pending loop: self.wakeup_console(max_tries=1) except: pass self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() def flush_input(self): """Flush the input buffer from WeatherLinkIP""" try: # This is a bit of a hack, but there is no analogue to pyserial's flushInput() self.socket.settimeout(0) self.read(4096) except: pass finally: self.socket.settimeout(self.timeout) def flush_output(self): """Flush the output buffer to WeatherLinkIP This function does nothing as there should never be anything left in the buffer when using socket.sendall()""" pass def queued_bytes(self): """Determine how many bytes are in the buffer""" length = 0 try: self.socket.settimeout(0) length = len(self.socket.recv(8192, socket.MSG_PEEK)) except socket.error: pass finally: self.socket.settimeout(self.timeout) return length def read(self, chars=1): """Read bytes from WeatherLinkIP""" try: _buffer = self.socket.recv(chars) N = len(_buffer) if N != chars: raise WeeWxIOError("Expected to read %d chars; got %d instead" % (chars, N)) return _buffer except: raise WeeWxIOError("Socket read error") def write(self, data): """Write to a WeatherLinkIP""" try: self.socket.sendall(data) time.sleep(self.tcp_send_delay) except: raise WeeWxIOError("Socket write error") #=============================================================================== # VantagePro class #=============================================================================== class VantagePro(object): """Class that represents a connection to a VantagePro console. The connection will be opened after initialization""" def __init__(self, **vp_dict) : """Initialize an object of type VantagePro. """ self.wait_before_retry= float(vp_dict.get('wait_before_retry', 1.2)) self.max_tries = int(vp_dict.get('max_tries' , 4)) # Get the port: self.port = EthernetWrapper(hostname, tcp_port, timeout, tcp_send_delay) #self.port = SerialWrapper(port, baudrate, timeout) # Open it up: self.port.openPort() def closePort(self): """Close the connection to the console. """ self.port.closePort() def genArchivePackets(self): """A generator function to return all archive packets on a VP2. yields: a sequence of dictionaries containing the data """ # Wake up the console... self.port.wakeup_console() # ... request a dump... self.port.send_data('DMP\n') # Cycle through the pages... for ipage in range(512): # ... get a page of archive data _page = self.port.get_data_with_crc16(267, prompt=_ack, max_tries=self.max_tries) # Now extract each record from the page for _index in xrange(5) : # If the console has been recently initialized, there will # be unused records, which are filled with 0xff. Detect this # by looking at the first 4 bytes (the date and time): if _page[1+52*_index:5+52*_index] == 4*chr(0xff) : yield None # Unpack the raw archive packet: _packet = unpackArchivePacket(_page[1+52*_index:53+52*_index]) timestamp = _archive_datetime(_packet) yield timestamp #=============================================================================== # UTILITIES #=============================================================================== def timestamp_to_string(ts): """Return a string formatted from the timestamp """ if ts: return "%s (%d)" % (time.strftime("%Y-%m-%d %H:%M:%S %Z", time.localtime(ts)), ts) else: return "****** N/A ******** ( N/A )" # Tuples of all the types held in a VantagePro2 Rev A or Rev B archive packet in their native order. vp2archA =('date_stamp', 'time_stamp', 'outTemp', 'highOutTemp', 'lowOutTemp', 'rain', 'rainRate', 'barometer', 'radiation', 'number_of_wind_samples', 'inTemp', 'inHumidity', 'outHumidity', 'windSpeed', 'windGust', 'windGustDir', 'windDir', 'UV', 'ET', 'soilMoist1', 'soilMoist2', 'soilMoist3', 'soilMoist4', 'soilTemp1', 'soilTemp2', 'soilTemp3','soilTemp4', 'leafWet1', 'leafWet2', 'leafWet3', 'leafWet4', 'extraTemp1', 'extraTemp2', 'extraHumid1', 'extraHumid2', 'readClosed', 'readOpened') vp2archB =('date_stamp', 'time_stamp', 'outTemp', 'highOutTemp', 'lowOutTemp', 'rain', 'rainRate', 'barometer', 'radiation', 'number_of_wind_samples', 'inTemp', 'inHumidity', 'outHumidity', 'windSpeed', 'windGust', 'windGustDir', 'windDir', 'UV', 'ET', 'highRadiation', 'highUV', 'forecastRule', 'leafTemp1', 'leafTemp2', 'leafWet1', 'leafWet2', 'soilTemp1', 'soilTemp2', 'soilTemp3','soilTemp4', 'download_record_type', 'extraHumid1', 'extraHumid2', 'extraTemp1', 'extraTemp2', 'extraTemp3', 'soilMoist1', 'soilMoist2', 'soilMoist3', 'soilMoist4') archive_format_revA = struct.Struct("> 9, # year (0x01e0 & datestamp) >> 5, # month (0x001f & datestamp), # day timestamp // 100, # hour timestamp % 100, # minute 0, # second 0, 0, -1) # Convert to epoch time: try: ts = int(time.mktime(time_tuple)) except (OverflowError, ValueError): ts = None return ts import array _table=( 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, # 0x00 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, # 0x08 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, # 0x10 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, # 0x18 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, # 0x20 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, # 0x28 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, # 0x30 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, # 0x38 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, # 0x40 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, # 0x48 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, # 0x50 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, # 0x58 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, # 0x60 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, # 0x68 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, # 0x70 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, # 0x78 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, # 0x80 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, # 0x88 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, # 0x90 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, # 0x98 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, # 0xA0 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, # 0xA8 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, # 0xB0 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, # 0xB8 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, # 0xC0 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, # 0xC8 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, # 0xD0 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, # 0xD8 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, # 0xE0 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, # 0xE8 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, # 0xF0 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, # 0xF8 ) table = array.array('H',_table) def crc16(string, crc=0): """ Calculate CRC16 sum""" for ch in string: crc = (table[((crc>>8)^ord(ch)) & 0xff] ^ (crc<<8)) & 0xffff return crc #=============================================================================== # SerialWrapper #=============================================================================== #class SerialWrapper(object): # """Wraps a serial connection returned from package serial""" # # def __init__(self, port, baudrate, timeout): # self.port = port # self.baudrate = baudrate # self.timeout = timeout # # # #=============================================================================== # # Primitives for working with the Davis Console # #=============================================================================== # # def wakeup_console(self, max_tries=3, wait_before_retry=1.2): # """Wake up a Davis VantagePro console. # # If unsuccessful, an exception of type weewx.WakeupError is thrown""" # # # Wake up the console. Try up to max_tries times # for unused_count in xrange(max_tries) : # try: # # Clear out any pending input or output characters: # self.flush_output() # self.flush_input() # # It can be hard to get the console's attention, particularly # # when in the middle of a LOOP command. Send a whole bunch of line feeds, # # then flush everything, then look for the \n\r acknowledgment # self.write('\n\n\n') # time.sleep(0.5) # self.flush_input() # self.write('\n') # _resp = self.read(2) # if _resp == '\n\r': # print "successfully woke up console" # return # print "Unable to wake up console... sleeping" # time.sleep(wait_before_retry) # print "Unable to wake up console... retrying" # except WeeWxIOError: # pass # # print "Unable to wake up console" # raise WakeupError("Unable to wake up VantagePro console") # # def send_data(self, data): # """Send data to the Davis console, waiting for an acknowledging # # If the is not received, no retry is attempted. Instead, an exception # of type weewx.WeeWxIOError is raised # # data: The data to send, as a string""" # # self.write(data) # # # Look for the acknowledging ACK character # _resp = self.read() # if _resp != _ack: # print "No received from console" # raise WeeWxIOError("No received from VantagePro console") # # def send_data_with_crc16(self, data, max_tries=3) : # """Send data to the Davis console along with a CRC check, waiting for an acknowledging . # If none received, resend up to max_tries times. # # data: The data to send, as a string""" # # #Calculate the crc for the data: # _crc = crc16(data) # # # ...and pack that on to the end of the data in big-endian order: # _data_with_crc = data + struct.pack(">H", _crc) # # # Retry up to max_tries times: # for unused_count in xrange(max_tries): # try: # self.write(_data_with_crc) # # Look for the acknowledgment. # _resp = self.read() # if _resp == _ack: # return # except WeeWxIOError: # pass # # print "VantagePro: Unable to pass CRC16 check while sending data" # raise CRCError("Unable to pass CRC16 check while sending data to VantagePro console") # # def send_command(self, command, max_tries=3, wait_before_retry=1.2): # """Send a command to the console, then look for the string 'OK' in the response. # # Any response from the console is split on \n\r characters and returned as a list.""" # # for unused_count in xrange(max_tries): # try : # self.wakeup_console(max_tries=max_tries, wait_before_retry=wait_before_retry) # # self.write(command) # # Takes a bit for the VP to react and fill up the buffer. Sleep for a half sec: # time.sleep(0.5) # # Can't use function serial.readline() because the VP responds with \n\r, not just \n. # # So, instead find how many bytes are waiting and fetch them all # nc = self.queued_bytes() # _buffer = self.read(nc) # # Split the buffer on the newlines # _buffer_list = _buffer.strip().split('\n\r') # # The first member should be the 'OK' in the VP response # if _buffer_list[0] == 'OK' : # # Return the rest: # return _buffer_list[1:] # # except WeeWxIOError: # # Caught an error. Keep trying... # continue # # print "Max retries exceeded while sending command %s" % command # raise RetriesExceeded("Max retries exceeded while sending command %s" % command) # # # def get_data_with_crc16(self, nbytes, prompt=None, max_tries=3) : # """Get a packet of data and do a CRC16 check on it, asking for retransmit if necessary. # # It is guaranteed that the length of the returned data will be of the requested length. # An exception of type CRCError will be thrown if the data cannot pass the CRC test # in the requested number of retries. # # nbytes: The number of bytes (including the 2 byte CRC) to get. # # prompt: Any string to be sent before requesting the data. Default=None # # max_tries: Number of tries before giving up. Default=3 # # returns: the packet data as a string. The last 2 bytes will be the CRC""" # if prompt: # self.write(prompt) # # first_time = True # # for unused_count in xrange(max_tries): # try: # if not first_time: # self.write(_resend) # _buffer = self.read(nbytes) # if crc16(_buffer) == 0 : # return _buffer # except WeeWxIOError: # pass # first_time = False # # print "Unable to pass CRC16 check while getting data" # raise CRCError("Unable to pass CRC16 check while getting data") # # def flush_input(self): # self.serial_port.flushInput() # # def flush_output(self): # self.serial_port.flushOutput() # # def queued_bytes(self): # return self.serial_port.inWaiting() # # def read(self, chars=1): # _buffer = self.serial_port.read(chars) # N = len(_buffer) # if N != chars: # raise WeeWxIOError("Expected to read %d chars; got %d instead" % (chars, N)) # return _buffer # # def write(self, data): # N = self.serial_port.write(data) # # Python version 2.5 and earlier returns 'None', so it cannot be used to test for completion. # if N is not None and N != len(data): # raise WeeWxIOError("Expected to write %d chars; sent %d instead" % (len(data), N)) # # def openPort(self): # # Open up the port and store it # self.serial_port = serial.Serial(self.port, self.baudrate, timeout=self.timeout) # # def closePort(self): # try: # # This will cancel any pending loop: # self.wakeup_console(max_tries=1) # except: # pass # self.serial_port.close() #=============================================================================== # Main test #=============================================================================== vp = VantagePro() N=N_none=0 t1=time.time() try: for ts in vp.genArchivePackets(): print timestamp_to_string(ts) if ts: N += 1 else: N_none += 1 except Exception, e: print "Exception:", e else: print "Loop terminated normally" T = time.time() - t1 print "Timestamps received: ", N print "Nulls received: ", N_none print "Elapsed time (secs): ", T