#!/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))
