Guest User

vueiss.py

a guest
Aug 16th, 2021
137
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.32 KB | None | 0 0
  1. ##
  2. ##This program is free software; you can redistribute it and/or modify it under
  3. ##the terms of the GNU General Public License as published by the Free Software
  4. ##Foundation; either version 2 of the License, or (at your option) any later
  5. ##version.
  6. ##
  7. ##This program is distributed in the hope that it will be useful, but WITHOUT
  8. ##ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. ##FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  10. ##details.
  11. """Driver for Vantage Vue @ DIY datalogger"""
  12.  
  13. from __future__ import with_statement
  14. import time
  15. import syslog
  16. import MySQLdb
  17. try:
  18.     import queue
  19. except ImportError:
  20.     import Queue as queue
  21.  
  22. from math import sin, cos, pi, acos, pow, exp, log
  23. from numpy import array
  24. from numpy.linalg import norm
  25.  
  26. import weecfg
  27. import weedb
  28. import weeutil.weeutil
  29. import weewx.drivers
  30. import weewx.manager
  31.  
  32. def logmsg(msg):
  33.     syslog.syslog(syslog.LOG_INFO, 'vueiss: %s' % msg)
  34.  
  35. POLYNOMIAL = 0x1021
  36. PRESET = 0
  37.  
  38. def _initial(c):
  39.     crc = 0
  40.     c = c << 8
  41.     for _ in range(8):
  42.         if (crc ^ c) & 0x8000:
  43.             crc = (crc << 1) ^ POLYNOMIAL
  44.         else:
  45.             crc = crc << 1
  46.         c = c << 1
  47.     return crc
  48.  
  49. _tab = [ _initial(i) for i in range(256) ]
  50.  
  51. def _update_crc(crc, c):
  52.     cc = 0xff & c
  53.  
  54.     tmp = (crc >> 8) ^ cc
  55.     crc = (crc << 8) ^ _tab[tmp & 0xff]
  56.     crc = crc & 0xffff
  57.  
  58.     return crc
  59.  
  60. # Calculates the crc
  61. def crc(data):
  62.     crc = PRESET
  63.     try:
  64.         for idx in range(2, 10):
  65.             crc = _update_crc(crc, int(data[idx], 16))
  66.     except:
  67.         pass
  68.     return crc
  69.  
  70. # Calculates the saturation vapour pressure (see dwd)
  71. def calc_svp(temperature):
  72.     if not temperature:
  73.         return None
  74.  
  75.     C1   = 6.10780
  76.     C2_P = 17.08085
  77.     C2_N = 17.84362
  78.     C3_P = 234.175
  79.     C3_N = 245.425
  80.  
  81.     C2 = C2_P if temperature >= 0 else C2_N
  82.     C3 = C3_P if temperature >= 0 else C3_N
  83.  
  84.     return C1 * exp(C2 * temperature / (C3 + temperature))
  85.  
  86. # Calculates the dewpoint (see dwd)
  87. def calc_dewpoint(temperature, humidity):
  88.     if not temperature or not humidity:
  89.         return None
  90.  
  91.     C1   = 6.10780
  92.     C2_P = 17.08085
  93.     C2_N = 17.84362
  94.     C3_P = 234.175
  95.     C3_N = 245.425
  96.  
  97.     C2 = C2_P if temperature >= 0 else C2_N
  98.     C3 = C3_P if temperature >= 0 else C3_N
  99.  
  100.     svp = calc_svp(temperature)
  101.  
  102.     tmp = log(0.01 * humidity * svp / C1)
  103.     dt = C3 * tmp / (C2 - tmp)
  104.  
  105.     if temperature >= 0 and dt < 0.0:
  106.         dt = C3_N * tmp / (C2_N - tmp)
  107.  
  108.     if dt > temperature:
  109.         dt = temperature
  110.  
  111.     return round(dt, 1)
  112.  
  113. # wind data 1-min-average
  114. class WindData(object):
  115.  
  116.     def __init__(self):
  117.         self.windVektor = array([0.0, 0.0])
  118.         self.windSpeed = 0
  119.         self.windCount = 0
  120.  
  121.     def reset(self):
  122.         self.windVektor = array([0.0, 0.0])
  123.         self.windSpeed = 0
  124.         self.windCount = 0
  125.  
  126.     def add(self, data):
  127.         speed = int(data[3], 16) * 0.44704
  128.         direction = (int(data[4], 16) << 2) | (int(data[6], 16) & 0x02)
  129.         direction = 360 if direction > 1024 or direction <= 0 else int(round(direction * 360.0 / 1024.0))
  130.         rad = pi * (90.0 - direction) / 180.0
  131.  
  132.         self.windSpeed += speed
  133.         self.windVektor += (speed * array([cos(rad), sin(rad)]))
  134.         self.windCount += 1
  135.  
  136.     def get(self):
  137.         if self.windCount == 0:
  138.             return None
  139.  
  140.         direction = None
  141.         length = norm(self.windVektor)
  142.         speed = self.windSpeed / self.windCount
  143.         if speed > 0 and length > 0.01:
  144.             rad = acos(self.windVektor[0] / length)
  145.             if self.windVektor[1] < 0:
  146.                 rad = 2*pi - rad
  147.  
  148.             direction = 90.0 - 180.0 * rad / pi
  149.             if direction < 0:
  150.                 direction += 360.0
  151.  
  152.         return [speed, direction]
  153.  
  154. # wind data floating N-min-average
  155. class WindDataN(object):
  156.  
  157.     def __init__(self, N):
  158.         self.N = N
  159.         self.pos = 0
  160.         self.array = [WindData() for _ in range(self.N)]
  161.  
  162.     def reset(self):
  163.         self.pos = (self.pos + 1) % self.N
  164.         self.array[self.pos].reset()
  165.  
  166.     def add(self, data):
  167.         for i in range(self.N):
  168.             self.array[i].add(data)
  169.  
  170.     def get(self):
  171.         return self.array[(self.pos + 1) % self.N].get()
  172.  
  173. # rain data
  174. class RainData(object):
  175.  
  176.     def __init__(self):
  177.         self.rainSum = 0
  178.         self.rainTicks = None
  179.  
  180.     def reset(self):
  181.         self.rainSum = 0
  182.  
  183.     def add(self, data):
  184.         ticks = int(data[5], 16) & 0x7f
  185.         if self.rainTicks != None and ticks > self.rainTicks:
  186.             self.rainSum += (ticks - self.rainTicks) * 0.2001
  187.             self.rainTicks = ticks
  188.         elif self.rainTicks != None and ticks < self.rainTicks:
  189.             self.rainSum += (128 + ticks - self.rainTicks) * 0.2001
  190.             self.rainTicks = ticks
  191.         self.rainTicks = ticks
  192.  
  193.     def get(self):
  194.         return self.rainSum
  195.  
  196. # temperatur data 1-min-average
  197. class TemperatureData(object):
  198.  
  199.     def __init__(self):
  200.         self.temp = 0
  201.         self.tempCount = 0
  202.  
  203.     def reset(self):
  204.         self.temp = 0
  205.         self.tempCount = 0
  206.  
  207.     def add(self, data):
  208.         value = int(data[5], 16) * 256 + int(data[6], 16)
  209.         value = value - 65536 if value > 32767 else value
  210.         tempAkt = (value/160.0 - 32.0)*5.0/9.0
  211.         self.temp += tempAkt
  212.         self.tempCount += 1
  213.  
  214.     def get(self):
  215.         return self.temp / self.tempCount if self.tempCount > 0 else None
  216.  
  217. # temperatur data floating N-min-average
  218. class TemperatureDataN(object):
  219.  
  220.     def __init__(self, N):
  221.         self.N = N
  222.         self.pos = 0
  223.         self.array = [TemperatureData() for _ in range(self.N)]
  224.  
  225.     def reset(self):
  226.         self.pos = (self.pos + 1) % self.N
  227.         self.array[self.pos].reset()
  228.  
  229.     def add(self, data):
  230.         for i in range(self.N):
  231.             self.array[i].add(data)
  232.  
  233.     def get(self):
  234.         return self.array[(self.pos + 1) % self.N].get()
  235.  
  236. # humity data 1-min-average
  237. class HumityData(object):
  238.  
  239.     def __init__(self):
  240.         self.humidity = 0
  241.         self.humidityCount = 0
  242.  
  243.     def reset(self):
  244.         self.humidity = 0
  245.         self.humidityCount = 0
  246.  
  247.     def add(self, data):
  248.         value = ((int(data[6], 16) >> 4) << 8) + int(data[5], 16)
  249.         humidityAkt = value * 1.01 / 10.0
  250.         humidityAkt = 100 if humidityAkt > 100 else humidityAkt
  251.         self.humidity += humidityAkt
  252.         self.humidityCount += 1
  253.  
  254.     def get(self):
  255.         return self.humidity / self.humidityCount if self.humidityCount > 0 else None
  256.  
  257. # humity data floating N-min-average
  258. class HumityDataN(object):
  259.  
  260.     def __init__(self, N):
  261.         self.N = N
  262.         self.pos = 0
  263.         self.array = [HumityData() for _ in range(self.N)]
  264.  
  265.     def reset(self):
  266.         self.pos = (self.pos + 1) % self.N
  267.         self.array[self.pos].reset()
  268.  
  269.     def add(self, data):
  270.         for i in range(self.N):
  271.             self.array[i].add(data)
  272.  
  273.     def get(self):
  274.         return self.array[(self.pos + 1) % self.N].get()
  275.  
  276. # wind gust data
  277. class WindGustData(object):
  278.  
  279.     def __init__(self):
  280.         self.windGust = 0
  281.         self.windGustCount = 0
  282.  
  283.     def reset(self):
  284.         self.windGust = 0
  285.         self.windGustCount = 0
  286.  
  287.     def add(self, data):
  288.         _windGust = int(data[5], 16) * 0.44704
  289.         if _windGust > self.windGust:
  290.             self.windGust = _windGust
  291.         self.windGustCount += 1
  292.  
  293.     def get(self):
  294.         return self.windGust if self.windGustCount > 0 or self.windGust > 0 else None
  295.  
  296. # barometer data
  297. class BarometerData(object):
  298.  
  299.     def __init__(self, height):
  300.         self.height = height
  301.         self.barometer = 0
  302.         self.barometerCount = 0
  303.  
  304.     def reset(self):
  305.         self.barometer = 0
  306.         self.barometerCount = 0
  307.  
  308.     def add(self, data):
  309.         if data[0] == 'A':
  310.             self.barometer += float(data[4])/100.0
  311.         else:
  312.             self.barometer += pow(pow(float(data[4])/100.0, 0.1902614) + 8.417168e-05 * self.height, 5.255927)
  313.         self.barometerCount += 1
  314.  
  315.     def get(self):
  316.         return self.barometer / self.barometerCount if self.barometerCount > 0 else None
  317.  
  318. # barometer floating N-min-average
  319. class BarometerDataN(object):
  320.  
  321.     def __init__(self, height, N):
  322.         self.N = N
  323.         self.pos = 0
  324.         self.array = [BarometerData(height) for _ in range(self.N)]
  325.  
  326.     def reset(self):
  327.         self.pos = (self.pos + 1) % self.N
  328.         self.array[self.pos].reset()
  329.  
  330.     def add(self, data):
  331.         for i in range(self.N):
  332.             self.array[i].add(data)
  333.  
  334.     def get(self):
  335.         return self.array[(self.pos + 1) % self.N].get()
  336.  
  337. class StationParser(object):
  338.  
  339.     def __init__(self):
  340.         # initialize data objects
  341.         self.barometer_data = BarometerDataN(310.8, 10)
  342.         self.wind_data = WindDataN(10)
  343.         self.rain_data = RainData()
  344.         self.temp_data = TemperatureDataN(5)
  345.         self.humidy_data = HumityDataN(5)
  346.         self.gust_data = WindGustData()
  347.         self.packet_time = None
  348.  
  349.     def parse(self, data, data_time):
  350.         if not data or (data[0] != 'A' and data[0] != 'B' and data[0] != 'I'):
  351.             return None
  352.  
  353.         if data[0] == 'A' or data[0] == 'B':
  354.             self.barometer_data.add(data)
  355.             new_packet_time = data_time - data_time % 60
  356.             if self.packet_time != new_packet_time:
  357.                 self.packet_time = new_packet_time
  358.  
  359.                 packet = {}
  360.                 packet['dateTime'] = self.packet_time
  361.  
  362.                 _barometer = self.barometer_data.get()
  363.                 packet['barometer'] = round(_barometer, 1) if _barometer else None
  364.  
  365.                 wind_info = self.wind_data.get()
  366.                 _windSpeed = wind_info[0] if wind_info else None
  367.                 packet['windSpeed'] = round(_windSpeed, 2) if _windSpeed else None
  368.                 _windSpeed = wind_info[1] if wind_info else None
  369.                 packet['windDir'] = round(_windSpeed, 0) if _windSpeed else None
  370.  
  371.                 _windGust = self.gust_data.get()
  372.                 packet['windGust'] = round(_windGust, 2) if _windGust else None
  373.  
  374.                 packet['rain'] = self.rain_data.get()
  375.  
  376.                 _outTemp = self.temp_data.get()
  377.                 packet['outTemp'] = round(_outTemp, 1) if _outTemp else None
  378.  
  379.                 _outHumidity = self.humidy_data.get()
  380.                 packet['outHumidity'] = round(_outHumidity, 0) if _outHumidity else None
  381.  
  382.                 packet['dewpoint'] = calc_dewpoint(_outTemp, _outHumidity)
  383.  
  384.                 # reset data
  385.                 self.barometer_data.reset()
  386.                 self.wind_data.reset()
  387.                 self.rain_data.reset()
  388.                 self.temp_data.reset()
  389.                 self.gust_data.reset()
  390.                 self.humidy_data.reset()
  391.  
  392.                 return packet
  393.             return None
  394.  
  395.         if crc(data) == 0:
  396.             self.wind_data.add(data)
  397.  
  398.             sensor_id = StationParser.sensor(data)
  399.             if sensor_id == 'N':
  400.                 self.rain_data.add(data)
  401.  
  402.             elif sensor_id == 'T' :
  403.                 self.temp_data.add(data)
  404.  
  405.             elif sensor_id == 'G' :
  406.                 self.gust_data.add(data)
  407.  
  408.             elif sensor_id == 'H':
  409.                 self.humidy_data.add(data)
  410.  
  411.         return None
  412.  
  413.     @staticmethod
  414.     def sensor(data):
  415.         # returns the sensor id
  416.         if not data or len(data) < 10 or len(data[2]) < 1:
  417.             return None
  418.  
  419.         ch = data[2][0]
  420.         if ch == '2':
  421.             return 'V'
  422.         if ch == '5':
  423.             return 'R'
  424.         if ch == '7':
  425.             return 'S'
  426.         if ch == '8':
  427.             return 'T'
  428.         if ch == '9':
  429.             return 'G'
  430.         if ch == 'A':
  431.             return 'H'
  432.         if ch == 'E':
  433.             return 'N'
  434.  
  435.         return 'I'
  436.  
  437. DRIVER_NAME = 'VueISS'
  438. DRIVER_VERSION = "2.3"
  439.  
  440. def loader(config_dict, engine):
  441.  
  442.     station = VueISS(config_dict)
  443.     return station
  444.  
  445. class VueISS(weewx.drivers.AbstractDevice):
  446.     """Vantage Vue @ Meteostick database"""
  447.  
  448.     def __init__(self, config_dict):
  449.         """Initialize the station
  450.        """
  451.  
  452.         self.parser = StationParser()
  453.  
  454.         self.config_dict = config_dict
  455.  
  456.         self.the_time = 0
  457.         self.old_time = 0
  458.  
  459.         self.packets = queue.Queue()
  460.  
  461.         with weewx.manager.open_manager_with_config(self.config_dict, 'wx_binding') as dbmanager:
  462.             with weedb.Transaction(dbmanager.connection) as cursor:
  463.                 cursor.execute("SELECT dateTime FROM last_sensor")
  464.                 for row in cursor:
  465.                    self.the_time = int(row[0])
  466.  
  467.                 DELTA = 600000
  468.                 if self.the_time - DELTA >= 0:
  469.                     cursor.execute("SELECT dateTime,data FROM sensor WHERE dateTime>=%d AND dateTime<=%d ORDER BY dateTime ASC LIMIT 500" % (self.the_time - DELTA, self.the_time))
  470.                     for (data_time, strdata) in cursor:
  471.                         data = strdata.split()
  472.                         self.parser.parse(data, data_time/1000)
  473.  
  474.                 logmsg("Starting with %d" % (self.the_time/1000))
  475.  
  476.     def genLoopPackets(self):
  477.  
  478.         while True:
  479.             with weewx.manager.open_manager_with_config(self.config_dict, 'wx_binding') as dbmanager:
  480.                 with weedb.Transaction(dbmanager.connection) as cursor:
  481.                     cursor.execute("SELECT dateTime,data FROM sensor WHERE dateTime>%d ORDER BY dateTime ASC LIMIT 5000" % (self.the_time))
  482.                     for (self.the_time, strdata) in cursor:
  483.                         data = strdata.split()
  484.                         values = self.parser.parse(data, self.the_time/1000)
  485.                         if values:
  486.                             packet = {'usUnits' : weewx.METRICWX }
  487.                             packet.update(values)
  488.                             self.packets.put(packet)
  489.                             logmsg("Yield packet (%d)" % (self.the_time/1000))
  490.                             yield packet
  491.  
  492.                     if self.old_time != self.the_time:
  493.                         self.old_time = self.the_time
  494.                         logmsg("Remember last timestamp %d" % (self.old_time))
  495.                         cursor.execute("UPDATE last_sensor SET dateTime=%d" % (self.old_time))
  496.  
  497.             time.sleep(15.0)
  498.  
  499.     def genArchiveRecords(self, lastgood_ts):
  500.         while not self.packets.empty():
  501.             packet = self.packets.get()
  502.             if packet['dateTime'] > lastgood_ts:
  503.                 packet['interval'] = 1
  504.                 yield packet
  505.  
  506.     @property
  507.     def hardware_name(self):
  508.         return "Davis Vue ISS"
  509.  
  510.     # The driver archive interval is 60 seconds.
  511.     @property
  512.     def archive_interval(self):
  513.         return 60
  514.  
  515.     def getTime(self):
  516.         return self.the_time/1000
  517.  
  518. def confeditor_loader():
  519.     return VueISSConfEditor()
  520.  
  521. class VueISSConfEditor(weewx.drivers.AbstractConfEditor):
  522.     @property
  523.     def default_stanza(self):
  524.         return """
  525. [VueISS]
  526.    # This section is for the weewx vue iss weather station
  527.  
  528.    # The driver to use:
  529.    driver = user.drivers.vueiss
  530. """
  531.  
  532. if __name__ == "__main__":
  533.     station = VueISS()
  534.     for packet in station.genLoopPackets():
  535.         print(weeutil.weeutil.timestamp_to_string(packet['dateTime']), packet)
  536.  
Advertisement
Add Comment
Please, Sign In to add comment