#!/usr/bin/python -u ''' -------------------------------------------------------------------------------- Type: Python 3.x script Author: Milan Toman (milan.v.toman@gmail.com) Description: Weather station (davis vantage vue) collector, coupled with the infamous arduino source on http://wp.spoton.cz/2017/11/24/davis-vantague-vue-arduino-and-a-raspberry-pi-3/ en-fucking-joy TODO: Rainrate going bonkers, check that shite. -------------------------------------------------------------------------------- Import libraries -------------------------------------------------------------------------------- ''' # mandatory import requests import sys import os import re import time import datetime import serial import simplejson as json import influxdb import configparser # optionally, future modules, locally available, I hate dependencies from pprint import pprint _SCRIPT_PATH = os.path.dirname(os.path.abspath(sys.argv[0])) sys.path.append(_SCRIPT_PATH + "/lib") #print(_SCRIPT_PATH + "/lib") #import ventilLogger ''' -------------------------------------------------------------------------------- Define variables -------------------------------------------------------------------------------- ''' config = configparser.ConfigParser() config.read(_SCRIPT_PATH + "/../web/config/plutonium.ini") _VERSION = 2.0 _NAME = u"Vantage Vue Decoding shite" _LOG_DIR = _SCRIPT_PATH + '/log/' _LOG_FILE_ROOT = re.sub(u'./', '', sys.argv[0]) _LOG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.log' _DEBUG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.dbg' _DAVIS_SERIAL_DEVICE = '/dev/ttyUSBdavis' # finite loop implementation, tout for 43200 cycles tout = 0 #_ABS_ZERO = 273.15 _HEIGHT = 455 temp = {} wind = {} humidity = 0 #pressure_adjusted = 0 supercap = 0 solarvolt = 0 rainstate = 0 rainrate = 0 rain = 0 influx_status_write = [] influx_weather_write = [] influx_host = config['InfluxDB']['_influx_host'] influx_port = config['InfluxDB']['_influx_port'] influx_user = config['InfluxDB']['_influx_user'] influx_pwd = config['InfluxDB']['_influx_pwd'] weather_db = config['InfluxDB']['_influx_weather_db'] status_db = config['InfluxDB']['_influx_status_db'] ''' -------------------------------------------------------------------------------- Set up logging - disabled, need to enable this in future -------------------------------------------------------------------------------- ''' ''' -------------------------------------------------------------------------------- Setup arguments and Options - not edited, sample shite -------------------------------------------------------------------------------- ''' ''' -------------------------------------------------------------------------------- Generic, standalone functions -------------------------------------------------------------------------------- ''' # Obvious shit, set up the client class influx_weather_client = influxdb.client.InfluxDBClient( influx_host, influx_port, influx_user, influx_pwd, weather_db ) influx_status_client = influxdb.client.InfluxDBClient( influx_host, influx_port, influx_user, influx_pwd, status_db ) ''' -------------------------------------------------------------------------------- Classes -------------------------------------------------------------------------------- ''' class davisDecoder(object): def __init__(self): __name__ = u'Davis value decoder class' self.height = _HEIGHT self.temp_dict = {} def zero_fill(self, data): binary_data = format(int(data), '08b') msb = binary_data[0:4] lsb = binary_data[4:] result = {"MSB": msb, "LSB": lsb} return result def davis_id(self, header): bin_header = self.zero_fill(header) davis_id = hex(int(bin_header['LSB'][1:], 2)) raw_id = bin_header['MSB'] battery_low = bin_header['LSB'][0] davis_packet_id = hex(int(raw_id, 2)) result = {"davis_id": davis_id, "packet_id": davis_packet_id, "bat_low": battery_low} return result def decode_wind(self, databytes): # wind speed in mph, i suppose. Let's convert it wind_speed = round(float(databytes['windspeed'] * 1.60934), 1) wind_direction_factor = round(float(360)/float(255), 1) wind_direction = databytes['winddir'] wind_direction = float(wind_direction) * wind_direction_factor result = {"speed": wind_speed, "direction": wind_direction} return result def decode_temp(self, temp): temp_f = (float(temp)) / float(160) # in Fahrenheit temp_c = round((temp_f - 32) * float(5)/float(9), 1) result = {"celsius": temp_c, "fahrenheit": temp_f} return result def decode_humidity(self, hum): pass def supercap_decode(self, byte2, byte3): cap = (byte2 << 2) + (byte3 >> 6) result = float(cap / 100.00) return result def solarvolt_decode(self, byte2, byte3): solar = (byte2 << 1) + (byte3 >> 7) result = float(solar) return result def rain_decode(self, rain): result = float(rain & 0x7F) return result def rainrate_decode(self, byte2, byte3): # if byte3(b2 here) is 0xFF, or 255, there is no rain print("b2:{} b3:{} = result:{}".format(byte2, byte3, byte2 + (byte3 >> 4 << 8))) if byte2 == 255: rainstate = 0 rainrate = 0 elif byte2 == 254: rainstate = 1 rainrate = 0.1 else: rainstate = 2 if byte3 > 4: rainrate = 720 / ((byte3 >> 4 << 8) + byte2) else: rainrate = 0 result = {"state": float(rainstate), "rate": float(rainrate)} print(result) return result class DBwriter(object): def __init__(self): __name__ = "Database writer class, Influx" def construct(self, connector, measurement, fields, tags): """ Takes values in a writes them to influxdb requires: list(connector): connector with all ticks to be written at once str(measurement): the measurement ID to be written dict(fields): fields to be written in one tick dict(tags): tags to be written with the fields returns: list(result_connector) """ result_connector = connector result_connector.append({"measurement": measurement, "fields": fields, "tags": tags} ) return result_connector def base_construct(self, base_value_dict): """ Takes values in a writes them to influxdb requires: dict(base_value_dict): { "speed": float(), "direction": float(), "temperature": float(), "humidity": float()} base weather values being sent each time. Wind dir / strength, internal temp / humi returns: list(base_connector) """ base_connector = [ { "measurement": "wind", "fields": { "value": base_value_dict['speed'] }, "tags": { "type": "speed" } }, { "measurement": "wind", "fields": { "value": base_value_dict['direction'] }, "tags": { "type": "direction" } }] return base_connector ''' -------------------------------------------------------------------------------- Main -------------------------------------------------------------------------------- ''' if '__main__': # TODO, make it work for any USB to serial port davis_decoder = davisDecoder() davis_writer = DBwriter() try: with serial.Serial(_DAVIS_SERIAL_DEVICE, 9600) as davis: # Now, let it run a couple times, end and restart via systemd while tout < 400: line = davis.readline() #print(line) davis_data = 0 try: davis_data = eval(line) except SyntaxError as e_syntax: print("ERROR (syntax): {}".format(e_syntax)) except TypeError as e_type: print("ERROR (Type): {}".format(e_type)) except ValueError as e_value: print("ERROR (Type): {}".format(e_value)) if davis_data != 0: # Raw data with every tick raw_header = davis_data['h'] decoded_header = davis_decoder.davis_id(raw_header) davis_unit_id = decoded_header['davis_id'] davis_packet_id = decoded_header['packet_id'] # Wind, mothafucka! raw_windspeed = davis_data['b0'] raw_winddir = davis_data['b1'] wind = davis_decoder.decode_wind({"windspeed": raw_windspeed, "winddir": raw_winddir}) # Get data external to the ISS, from local PCB / internal # sensors and create base values for influx writing #davis_decoder.load_external_data() influx_weather_write = davis_writer.base_construct( { "speed": float(wind['speed']), "direction": float(wind['direction'])} ) # "temperature": float(davis_decoder.inside_temp), # "humidity": float(davis_decoder.inside_hum)} #) # Wind gusts calculation if davis_packet_id == '0x9': raw_gusts = davis_data['b2'] wind.update({"windgust": (raw_gusts * 1.60934)}) influx_weather_write = davis_writer.construct( influx_weather_write, "wind", {"value": float(wind['windgust'])}, {"type": "windgust"} ) # 0x8 -> temperature if davis_packet_id == '0x8': raw_temp = (davis_data['b2'] << 8) + davis_data['b3'] temp_dict = davis_decoder.decode_temp(raw_temp) temp = float(temp_dict['celsius']) #pressure_adjusted = davis_decoder.adjust_pressure(temp) #influx_weather_write = davis_writer.construct( # influx_weather_write, # "temphumi", # {"pressure": float(pressure_adjusted)}, # {"type" : "adjusted"} #) #influx_weather_write = davis_writer.construct( # influx_weather_write, # "temphumi", # {"pressure": float(davis_decoder.pressure)}, # {"type" : "raw"} #) influx_weather_write = davis_writer.construct( influx_weather_write, "temphumi", {"temperature": float(temp)}, {"type": "external"} ) # 0xa -> Humidity if davis_packet_id == '0xa': raw_humidity = (((davis_data['b3'] >> 4) & 0b0011) << 8)\ + davis_data['b2'] humidity = round(int(raw_humidity) / float(10), 1) influx_weather_write = davis_writer.construct( influx_weather_write, "temphumi", {"humidity": float(humidity)}, {"type": "external"} ) # 0x2 -> SuperCap charge if davis_packet_id == '0x2': supercap = davis_decoder.supercap_decode( davis_data['b2'], davis_data['b3'] ) influx_status_write = davis_writer.construct( influx_status_write, "iss", {"voltage": float(supercap)}, {"type": "capacitor"} ) # 0x7 -> SolarPanel Voltage if davis_packet_id == '0x7': solarvolt = davis_decoder.solarvolt_decode( davis_data['b2'], davis_data['b3'] ) influx_status_write = davis_writer.construct( influx_status_write, "iss", {"voltage": float(solarvolt)}, {"type": "solar"} ) # 0xe -> Rain bucket tips -> https://www.carluccio.de/ if davis_packet_id == '0xe': raw_rain = (davis_data['b2']) + (davis_data['b3'] >> 7 << 8) rain = davis_decoder.rain_decode(raw_rain) influx_weather_write = davis_writer.construct( influx_weather_write, "rain", {"value": float(rain)}, {"type": "rain_bucket_tips"} ) # 0x5 -> Rain rate -> https://www.carluccio.de/ if davis_packet_id == '0x5': rainrate_dict = davis_decoder.rainrate_decode( davis_data['b2'], davis_data['b3'] ) if rainrate_dict: rainstate = rainrate_dict['state'] rainrate = rainrate_dict['rate'] influx_weather_write = davis_writer.construct( influx_weather_write, "rain", {"value": float(rainrate_dict['state'])}, {"type": "rainstate"} ) influx_weather_write = davis_writer.construct( influx_weather_write, "rain", {"value": float(rainrate_dict['rate'])}, {"type": "rainrate"} ) else: pass out_id = decoded_header out1 = "temp: {}, wind: {}, humidity: {} ".format( temp, wind, humidity) out2 = "RainState: {}, Rrate {}, Rain Total: {}, Cap:{}, Volt: {}".format( rainstate, rainrate, rain, supercap, solarvolt) out3 = {'RAW': davis_data} #with open('/home/pi/davis.rawdata', 'a+') as fh: # now = datetime.datetime.strftime(datetime.datetime.now(), "%s") # fh.write(now + ";" + str(out4) + "\n") print("\n{} \n{} \n{} \n{}\n".format(out_id, out1, out2, out3)) # Write the whole blob into Influx DB influx_weather_client.write_points(influx_weather_write) influx_status_client.write_points(influx_status_write) tout = tout + 1 time.sleep(1) else: print("No data here, mate.") except serial.serialutil.SerialException as e: print("Serial Error {}".format(e))