#!/usr/bin/env python3

# This script can be used to configure the Inverter IDs
# on an Envertech EnverBridge device. This is a replacement
# for the "SetID" programm provided as a Win32 executable at
# http://www.envertec.com/uploads/bigfiles/Set%20ID.zip
# Tested online with the EnverBridge 202 and a EVT 560 module
# inverter.
#
# Logic seems the be the following:
# Take the inverter id, append the char 9 to 16 of the 16 char
# serial number. Do this for every pair of serial numbers.
# Sent this string in one UDP paket to the broadcast address on
# port 8765. Wait for a response paket to the broadcast address
# on port 8764.

import argparse
import socket
import sys
import re

parser = argparse.ArgumentParser()
parser.add_argument("-b",
                    "--bid",
                    action="store",
                    type=int,
                    dest="bid",
                    required=True,
                    help="Serial Number of your EnverBridge")
parser.add_argument("miids", nargs="+", help="MIIDs of your MicroInverter")
args = parser.parse_args()


def validateMIID(miids):
    error = False
    idpattern = re.compile(r"^CN[0-9]{14}$")
    for id in miids:
        print("Validating MIID", id)
        if not idpattern.search(id):
            print("Error: Invalid MIID", id)
            error = True

    if error:
        print("Validation error, check your MIIDs, exiting")
        sys.exit(23)


def buildMIIDList(miids):
    msg = ""
    for id in miids:
        msg = msg + id[8:16]
    return msg


validateMIID(args.miids)

# assemble message string and convert to bytes
message = str(args.bid) + buildMIIDList(args.miids)
message = message.encode()
print('Sending to EnverBridge with ID', args.bid)

# Create a UDP client socket w/ broadcast
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)

# Create a UDP server socket we bind to to listen for broadcasts
ssock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ssock.bind(('', 8764))

try:
    # Send data via broadcast to port 8765
    print('Sending {!r}'.format(message))
    server_address = ('<broadcast>', 8765)
    sent = sock.sendto(message, server_address)

    # Receive response on port 8764
    print('Waiting to receive')
    data, server = ssock.recvfrom(8764)
    print('Received {!r}'.format(data))

finally:
    print('Closing sockets')
    sock.close()
    ssock.close()
