diff options
Diffstat (limited to 'inverter0/main.py')
-rw-r--r-- | inverter0/main.py | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/inverter0/main.py b/inverter0/main.py new file mode 100644 index 0000000..3dfe996 --- /dev/null +++ b/inverter0/main.py @@ -0,0 +1,243 @@ +import machine + +# RS-485 / comlynx +u1 = machine.UART(1) +u1.init(19200) + +# RS-485 Driver Enable flag +de = machine.Pin(32, machine.Pin.OUT) +de.value(0) # disable driver + +# Neoway M590 +u2 = machine.UART(2) +u2.init(115200) + + +import comlynx +import time + +SOURCE_ADDRESS = comlynx.Address(0x01, 0x01, 0x02) +INVERTER_ADDRESS = comlynx.Address(0x01, 0x01, 0x3b) #3d + +def write(data): + try: + de.value(1) # enable write + u1.write(data) + time.sleep_ms(int(len(data))) # very rough estimation + finally: + de.value(0) # disable write + + +ping = comlynx.PingRequest(comlynx.Address(0,0,2), comlynx.Address.broadcast()) + +def communicate(message): + data = bytes(tuple(message)) + print("Send: {} => {}".format(message, data)) + u1.read() # empty buf + write(data) + request = u1.read() + if request != data: + raise Exception("Unexpected comm error") + time.sleep_ms(100) + response = u1.read() + print("Received: {} => ".format(response), end='') + msg = comlynx.Message.unpack(response) + print(str(msg)) + return msg + +class Point: + def __init__(self, name): + self.name = name + self.min = None + self.max = None + self.last = None + def update(self, value): + self.min = min(self.min or value, value) + self.max = max(self.max or value, value) + self.last = value + def clear(self): + self.min = self.max = self.last = None + + +values = list(map(Point, [ + 'PVVoltage1', 'PVVoltage2', 'PVVoltage3', + 'PVCurrent1', 'PVCurrent2', 'PVCurrent3', + 'GridVoltage', 'GridFrequency', 'LatestEvent', + 'LatestEventModule', 'SmoothedInstantEnergyProduction'])) +error = None + +def update(): + global error + try: + for point in values: + print("Update: {}".format(point.name)) + attrs = getattr(comlynx.Parameters.ULX, point.name) + message = comlynx.CanRequest(SOURCE_ADDRESS, INVERTER_ADDRESS, *attrs) + reply = communicate(message) + point.update(reply.value()) + error = None + except Exception as e: + print("Error: {}".format(e)) + error = str(e) + +def reset(): + print("Error detected, reset in 30s") + time.sleep(30) + print("Reset SIM now") + u2.write("AT+CFUN=15\r\n"); time.sleep(1); print(u2.read()) + machine.reset() + + +def sim_command(command, expect): + while u2.any(): + data = u2.readline().strip() + print(data) + + print("> {}".format(command)) + u2.write(command + b"\r\n") + + data = u2.readline() + start = time.time() + while time.time() - start < 5: + if data is not None: + data = data.strip() + print("< {}".format(data)) + if data.startswith(expect): + break + elif data == b"ERROR": + reset() + data = u2.readline() + +ip_address = None + +def wait_connection(): + is_connected = False + start = time.time() + while not is_connected: + if time.time() - start > 60: + reset() + u2.write(b"AT+XIIC?\r\n") + time.sleep_ms(500) + while u2.any() > 0: + line = u2.readline() + print("< {}".format(line)) + if line.startswith("+XIIC: 1"): + print("PPP connection established") + is_connected = True + +def sim_connect(): + global ip_address + # Connect to cellular network + sim_command(b"AT+CREG=1", b"OK") + is_registered = False + start = time.time() + while not is_registered: + if time.time() - start > 120: + reset() + u2.write(b"AT+CREG?\r\n") + time.sleep_ms(500) + while u2.any() > 0: + line = u2.readline() + print("< {}".format(line)) + if line.startswith("+CREG: 1,1") or line.startswith("+CREG: 1,5") or \ + line.startswith("+CREG: 0,1") or line.startswith("+CREG: 0,5"): + print("Cell network status: registered") + is_registered = True + + sim_command(b"AT+XISP=0", b"OK") + sim_command(b"AT+CGDCONT=1,\"IP\",\"TM\"", b"OK") + sim_command(b"AT+XIIC=1", b"OK") + + wait_connection() + + sim_command(b"AT", b"OK") + time.sleep(1) + + start = time.time() + while ip_address is None: + if time.time() - start > 60: + reset() + u2.write(b"AT+DNS=\"www.localnet.cc\"\r\n") + time.sleep(1) + while u2.any() > 0: + line = u2.readline().strip() + if line.startswith(b"+DNS:") and len(line) > 8: + ip_address = line[5:] + + print("Connect done") + +def tcp_send(data): + CHUNK_SIZE = 512 + wait_connection() + sim_command(b"AT+TCPSETUP=0," + ip_address + b",80", b"+TCPSETUP") + try: + chunks = [data[i:i+CHUNK_SIZE] for i in range(0, len(data), CHUNK_SIZE)] + for chunk in chunks: + u2.write(b"AT+TCPSEND=0," + str(len(chunk)).encode('ascii') + b"\r\n") + start = time.time() + while True: + if time.time() - start > 10: + print("Timeout waiting for tcpsend") + reset() + line = u2.readline() + print("< {}".format(line)) + if line is not None and line.startswith('>'): + break + elif line is not None and line.startswith('+TCPSEND:Error'): + raise Exception("TCPSEND failed: {}".format(line)) + else: + time.sleep_ms(200) + + u2.write(chunk) + u2.write(b"\r") + time.sleep(1) + print("< {}".format(u2.read())) + finally: + sim_command(b"AT+TCPCLOSE=0", b"+TCPCLOSE") + +def post(data): + global config + secret = config['CONFIG_GUARD_SECRET'] + req = 'POST /guard/write/{}?db=data HTTP/1.1\r\nContent-Type: application/x-www-urlencoded\r\nContent-Length: {}\r\nHost: localnet.cc\r\n\r\n{}'.format( + secret, len(data), data) + tcp_send(req.encode('utf-8')) + +def send_data(post_func): + global error + data = "" + if error is not None: + data += "inverter0.error value=\"{}\"".format(error.replace('"', '_')) + else: + for point in values: + data += "inverter0.{} min={},max={},last={}\n".format( + point.name, point.min, point.max, point.last) + point.clear() + post_func(data) + error = None + +def loop(): + last_post = time.time() - 590 + try: + while True: + update() + for point in values: + print("{}: last={} min={} max={}".format(point.name, point.last, point.min, point.max)) + + if time.time() - last_post > 600: + try: + send_data(post) + last_post = time.time() + except Exception as e: + print(e) + + print("Time to next send: {}".format(time.time() - last_post * -1)) + time.sleep(30) + + except Exception as e: + print(str(e)) + reset() + +print("main.py: Start loop in 5s") +time.sleep(5) +sim_connect() +loop() |