#!/usr/bin/env python3
# Model Dreamlink WH1080
#

import usb.core
import time
import struct
import math
import datetime

VENDOR = 0x1941
PRODUCT = 0x8021
#WIND_DIRS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']


def open_ws():
    '''
    Open a connection to the device, using the PRODUCT and VENDOR information
    @return reference to the device
    '''
    usb_device = usb.core.find(idVendor=VENDOR, idProduct=PRODUCT)

    if usb_device is None:
        raise ValueError('Device not found')

    usb_device.get_active_configuration()

    # If we don't detach the kernel driver we get I/O errors
    if usb_device.is_kernel_driver_active(0):
        usb_device.detach_kernel_driver(0)

    return usb_device


def read_block(device, offset):
    '''
    Read a block of data from the specified device, starting at the given offset.
    @Inputs
    device
        - usb_device
    offset
        - int value
    @Return byte array
    '''

    least_significant_bit = offset & 0xFF
    most_significant_bit = offset >> 8 & 0xFF

    # Construct a binary message
    tbuf = struct.pack('BBBBBBBB',
                       0xA1,
                       most_significant_bit,
                       least_significant_bit,
                       32,
                       0xA1,
                       most_significant_bit,
                       least_significant_bit,
                       32)

    timeout = 1000  # Milliseconds
    retval = device.ctrl_transfer(0x21,  # USB Requesttype
                               0x09,  # USB Request
                               0x200,  # Value
                               0,  # Index
                               tbuf,  # Message
                               timeout)

    return device.read(0x81, 32, timeout)


def main():
    dev = open_ws()
    dev.set_configuration()


    ########### Read data
    # Get the first 32 Bytes of the fixed
    fixed_block = read_block(dev, 0)

    # Check that we have good data
    if (fixed_block[0] != 0x55):
        raise ValueError('Bad data returned')

    # Bytes 31 and 32 when combined create an unsigned short int that tells us where to find the weather data we want
    curpos = struct.unpack('H', fixed_block[30:32])[0]
    current_block = read_block(dev, curpos)

    # Indoor information
    indoor_humidity = current_block[1]
    tlsb = current_block[2]
    tmsb = current_block[3] & 0x7f
    tsign = current_block[3] >> 7
    indoor_temperature = (tmsb * 256 + tlsb) * 0.1
    # Check if temperature is less than zero
    if tsign:
        indoor_temperature *= -1

    # Outdoor information
    outdoor_humidity = current_block[4]
    tlsb = current_block[5]
    tmsb = current_block[6] & 0x7f
    tsign = current_block[6] >> 7
    outdoor_temperature = (tmsb * 256 + tlsb) * 0.1
    # Check if temperature is less than zero
    if tsign:
        outdoor_temperature *= -1

    # Bytes 8 and 9 when combined create an unsigned short int that we multiply by 0.1 to find the absolute pressure
    abs_pressure = struct.unpack('H', current_block[7:9])[0]*0.1

    wind = current_block[9]
    gust = current_block[10]
    wind_extra = current_block[11]
    wind_dir = current_block[12]

    # Bytes 14 and 15  when combined create an unsigned short int
    # that we multiply by 0.3 to find the total rain
    # I'm not confident that this is correct. Neither abs_pressure nor
    # total_rain are returning sane values. In fact total_rain has
    # stayed static despite rainfall
    # Looks like I fixed it. They used fixed_block instead of current_block
    total_rain = struct.unpack('H', current_block[13:15])[0]*0.3

    # Calculate wind speeds
    wind_speed = (wind + ((wind_extra & 0x0F) << 8)) * 0.38  # Was 0.1
    gust_speed = (gust + ((wind_extra & 0xF0) << 4)) * 0.38  # Was 0.1


    ############### print data
    print('{')
    print('"indoor_humidity": "%i",' %indoor_humidity)
    print('"indoor_temperature": "%2.1f",' %indoor_temperature)
    print('"outdoor_humidity": "%i",' %outdoor_humidity)
    print('"outdoor_temperature": "%2.1f",' %outdoor_temperature)
    print('"abs_pressure": "%4.1f",' %abs_pressure)
    print('"total_rain": "%3.1f",' %total_rain)
    print('"wind_speed": "%2.1f",' %wind_speed)
    print('"gust_speed": "%2.1f",' %gust_speed)
    if wind_dir != 128:
        print('"wind_dir_deg": "%s",' %(wind_dir*22.5))
#        print('"wind_dir": "%s",' %WIND_DIRS[wind_dir])
    print('"TS": "%s"' %(datetime.datetime.utcnow().isoformat()))
    print('}')


if __name__ == "__main__":
    main()
