#!/usr/bin/env python3
# create data for live graphs from a SMA webbox
# Copyright Andrew Tridgell 2010
# released under GNU GPL v3 or later

import os, sys, time, socket, hashlib, random, json
from optparse import OptionParser

# parse command line options
parser = OptionParser()
parser.add_option("", "--webbox", help="webbox server name", default=None)
parser.add_option("", "--password", help="webbox user password", default=None)
parser.add_option("", "--verbose", help="be more verbose", action='store_true',
                  default=False)

(opts, args) = parser.parse_args()

if opts.password is None:
    print("You must provide a password")
    sys.exit(1)

if opts.webbox is None:
    print("You must provide a webbox address")
    sys.exit(1)

def rpc_call(call):
    '''make a RPC call to the webbox'''

    call['passwd'] = hashlib.md5(opts.password.encode('utf-8')).hexdigest()
    call['version'] = '1.0'
    call['format'] = "JSON"
    call["id"] = str(random.randint(1, 100000))

    params = json.dumps(call).encode("UTF-16")[2:]

    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    tries = 30
    while tries > 0:
        try:
            udp.bind(("0.0.0.0", 34268))
            break
        except:
            tries -= 1
            time.sleep(1)
            pass
    if tries == 0:
        print("Timed out binding to port 34268")
        sys.exit(1)
    udp.settimeout(10.0)
    udp.connect((opts.webbox, 34268))
    udp.send(params)
    try:
        d = udp.recv(102400)
    except socket.timeout:
        print("timed out talking to %s" % opts.webbox)
        sys.exit(1)
    r = json.loads(d.decode("UTF-16"))
    if int(r['id']) != int(call['id']):
        print("Bad ID received: %s != %s" % (r['id'], call['id']))
    return r


def save_file(fname, v):
    with open(fname, mode='w', encoding='utf-8') as f:
        f.write(str(v))

def append_file(fname, v):
    with open(fname, mode='a', encoding='utf-8') as f:
        f.write(str(v))

def all_values():
    '''get detailed inverter data'''
    v = rpc_call({ "proc" : "GetDevices" })

    devkeys = []
    for a in v['result']['devices']:
        devkeys.append(a['key'])

    if len(devkeys) == 0:
        # no devices to query
        return

    # assume all inverters return the same channels
    chanv = rpc_call({ "proc" : "GetProcessDataChannels",
                       "params" : { "device" : devkeys[0] }})
    chans = chanv['result'][devkeys[0]]

    if opts.verbose:
        print("Channels: ", chans)

    time_str = time.strftime("%H:%M:%S")

    # setup a call to fetch all channels from all devices
    params = { "proc" : "GetProcessData", "params" : { "devices" : [] }}
    for k in devkeys:
        params['params']['devices'].append({ "key" : k, "channels" : chans})
    
    v = rpc_call(params)

    for r in v['result']['devices']:
        k = r['key']
        fname = "%s-%s.csv" % (time.strftime("%Y-%m-%d"), k)
        if not os.path.exists(fname):
            save_file(fname, 'Time,' + ','.join(chans) + '\n')
        vals = {}
        line = time_str
        have_data = False
        for a in r['channels']:
            vals[a['meta']] = a['value']
        for c in chans:
            line += ',' + vals[c]
            if vals[c] != '':
                have_data = True
        if have_data:
            append_file(fname, line + '\n')

all_values()
