416 lines
16 KiB
Python
Executable File
416 lines
16 KiB
Python
Executable File
#!/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
|
|
|
|
# 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
|
|
--------------------------------------------------------------------------------
|
|
'''
|
|
_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 = 'localhost'
|
|
influx_port = 8086
|
|
influx_user = 'pi'
|
|
influx_pwd = 'Ventil6996'
|
|
weather_db = 'weather_v2'
|
|
status_db = 'status'
|
|
|
|
'''
|
|
--------------------------------------------------------------------------------
|
|
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))
|