#!/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. influxDB SCHEMA: DB weather measure wind ---------------- value | speed or direction or windgust --------------------------------------- field tag measure temphumi ---------------- temperature | humidity | external, internal | pressure --------------------------------------------------------- field field tag field measure rain ---------------- rain | rate / total / intensity | restart if zero, was it 65535 => before? --------------------------------------------- field tag field(int) DB status ISS measure ---------------- voltage | solar or capacitor | state / lqi / | battery or future_shit | ---------------------------------------------------------------- field tag field tag RasPI system ---------------- usage | disk, mem, cpu, eth, wifi % ------------------------------------ field | tag -------------------------------------------------------------------------------- Import libraries -------------------------------------------------------------------------------- ''' # mandatory import requests import sys import os import re import textwrap import argparse import time import datetime import serial import simplejson as json import influxdb # optionally, future modules, locally available, I hate dependencies from pprint import pprint _SCRIPT_PATH = os.path.dirname(sys.argv[0]) sys.path.append(_SCRIPT_PATH + "/home/pi/test/lib") #print(_SCRIPT_PATH + "/lib") #import ventilLogger ''' -------------------------------------------------------------------------------- Define variables -------------------------------------------------------------------------------- ''' _VERSION = 2.0 _NAME = u"Vantage Vue Decoding shite" # finite loop implementation, tout for 43200 cycles tout = 0 _SERIAL_CONSOLE = '/dev/ttyUSBdavis' _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 = 'localhost' influx_port = 8086 influx_user = 'pi' influx_pwd = 'freedavis' weather_db = 'weather_v2' status_db = 'status' ''' -------------------------------------------------------------------------------- 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 load_external_data(self): # Data external to the ISS self.pressure = float(davis_data['P']) self.inside_temp = round((float(davis_data['Ti'])\ + float(davis_data['Thtu'])) / 2, 2) self.inside_hum = davis_data['Hhtu'] 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 adjust_pressure(self, temp): sh = 0.0065 * self.height base = 1 - (sh) / (temp + sh + _ABS_ZERO) result = round(self.pressure * pow(base, -5.257), 2) return result 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 not rain print("b2:{} b3:{} = result:{}".format(byte2, byte3, byte2 + (byte3 >> 4 << 8))) # No Rain rainstate = 0 if byte2 == 255: rainstate = 0 rainrate = 0 # Light Rain rainstate = 1 elif byte2 == 254: rainstate = 1 rainrate = 0.1 # Heavy rain rainstate = 2 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" } }, { "measurement": "temphumi", "fields": { "temperature": base_value_dict['temperature'], "humidity": base_value_dict['humidity'] }, "tags": { "type": "internal" } }] return base_connector ''' -------------------------------------------------------------------------------- Main -------------------------------------------------------------------------------- ''' if '__main__': ''' 2 = Supercap voltage (Vue only) 3 = ? 4 = UV Index 5 = Rain rate 6 = Solar radiation 7 = Solar Cell output (Vue only) 8 = Temperature 9 = Wind gust a = Humidity e = Rain {'nxt': 64, 'P0': 1020.43, 'lqi': 6, 'b2': 3, 'P': 969.29, 'h': 144, 'Ti': 24.49, 'cnt': 1, 'Hhtu': 28.68, 'b4': 129, 'b5': 247, 'b6': 36, 'b7': 255, 'b0': 1, 'b1': 10, 'hop': 0, 'b3': 225, 'Thtu': 24.28, 'b8': 255, 'b9': 182, 'rssi': 45} ''' # TODO, make it work for any USB to serial port davis_decoder = davisDecoder() davis_writer = DBwriter() try: with serial.Serial(_SERIAL_CONSOLE, 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 ''' This shit ain't working without external sensors 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": "capcaitor"} ) # 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'] << 8) + (davis_data['b3'] >> 7) raw_rain = davis_data['b2'] 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 = "Padj: {}, Praw {}, Tins: {}, Humins: {}".format( pressure_adjusted, davis_decoder.pressure, davis_decoder.inside_temp, davis_decoder.inside_hum) out3 = "RainState: {}, Rrate {}, Rain Total: {}, Cap:{}, Volt: {}".format( rainstate, rainrate, rain, supercap, solarvolt) 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))