diff options
Diffstat (limited to 'src/test/test_inverter_monitor.py')
-rwxr-xr-x | src/test/test_inverter_monitor.py | 376 |
1 files changed, 0 insertions, 376 deletions
diff --git a/src/test/test_inverter_monitor.py b/src/test/test_inverter_monitor.py deleted file mode 100755 index d9b63d3..0000000 --- a/src/test/test_inverter_monitor.py +++ /dev/null @@ -1,376 +0,0 @@ -#!/usr/bin/env python3 -import cmd -import time -import logging -import socket -import sys -import threading -import os.path -sys.path.extend([ - os.path.realpath( - os.path.join(os.path.dirname(os.path.join(__file__)), '..', '..') - ) -]) - -from enum import Enum, auto -from typing import Optional -from src.home.util import stringify -from src.home.config import config -from src.home.inverter import ( - wrapper_instance as inverter, - - InverterMonitor, - ChargingEvent, - BatteryState, - BatteryPowerDirection, -) - - -def monitor_charging(event: ChargingEvent, **kwargs) -> None: - msg = 'event: ' + event.name - if event == ChargingEvent.AC_CURRENT_CHANGED: - msg += f' (current={kwargs["current"]})' - evt_logger.info(msg) - - -def monitor_battery(state: BatteryState, v: float, load_watts: int) -> None: - evt_logger.info(f'bat: {state.name}, v: {v}, load_watts: {load_watts}') - - -def monitor_error(error: str) -> None: - evt_logger.warning('error: ' + error) - - -class InverterTestShell(cmd.Cmd): - intro = 'Welcome to the test shell. Type help or ? to list commands.\n' - prompt = '(test) ' - file = None - - def do_connect_ac(self, arg): - server.connect_ac() - - def do_disconnect_ac(self, arg): - server.disconnect_ac() - - def do_pd_charge(self, arg): - server.set_pd(BatteryPowerDirection.CHARGING) - - def do_pd_nothing(self, arg): - server.set_pd(BatteryPowerDirection.DO_NOTHING) - - def do_pd_discharge(self, arg): - server.set_pd(BatteryPowerDirection.DISCHARGING) - - -class ChargerMode(Enum): - NONE = auto() - CHARGING = auto() - - -class ChargerEmulator(threading.Thread): - def __init__(self): - super().__init__() - self.setName('ChargerEmulator') - - self.logger = logging.getLogger('charger') - self.interrupted = False - self.mode = ChargerMode.NONE - - self.pd = None - self.ac_connected = False - self.mppt_connected = False - - def run(self): - while not self.interrupted: - if self.pd == BatteryPowerDirection.CHARGING\ - and self.ac_connected\ - and not self.mppt_connected: - - v = server._get_voltage() + 0.02 - self.logger.info('incrementing voltage') - server.set_voltage(v) - - time.sleep(2) - - def stop(self): - self.interrupted = True - - def setmode(self, mode: ChargerMode): - self.mode = mode - - def ac_changed(self, connected: bool): - self.ac_connected = connected - - def mppt_changed(self, connected: bool): - self.mppt_connected = connected - - def current_changed(self, amps): - # FIXME - # this method is not being called and voltage is not changing] - # when current changes - v = None - if amps == 2: - v = 49 - elif amps == 10: - v = 51 - elif amps == 20: - v = 52.5 - elif amps == 30: - v = 53.5 - elif amps == 40: - v = 54.5 - if v is not None: - self.logger.info(f'setting voltage {v}') - server.set_voltage(v) - - def pd_changed(self, pd: BatteryPowerDirection): - self.pd = pd - - -class InverterEmulator(threading.Thread): - def __init__(self, host: str, port: int): - super().__init__() - self.setName('InverterEmulatorServer') - self.lock = threading.Lock() - - self.status = {"grid_voltage": {"unit": "V", "value": 0.0}, - "grid_freq": {"unit": "Hz", "value": 0.0}, - "ac_output_voltage": {"unit": "V", "value": 230.0}, - "ac_output_freq": {"unit": "Hz", "value": 50.0}, - "ac_output_apparent_power": {"unit": "VA", "value": 92}, - "ac_output_active_power": {"unit": "Wh", "value": 30}, - "output_load_percent": {"unit": "%", "value": 1}, - "battery_voltage": {"unit": "V", "value": 48.4}, - "battery_voltage_scc": {"unit": "V", "value": 0.0}, - "battery_voltage_scc2": {"unit": "V", "value": 0.0}, - "battery_discharging_current": {"unit": "A", "value": 0}, - "battery_charging_current": {"unit": "A", "value": 0}, - "battery_capacity": {"unit": "%", "value": 62}, - "inverter_heat_sink_temp": {"unit": "°C", "value": 8}, - "mppt1_charger_temp": {"unit": "°C", "value": 0}, - "mppt2_charger_temp": {"unit": "°C", "value": 0}, - "pv1_input_power": {"unit": "Wh", "value": 0}, - "pv2_input_power": {"unit": "Wh", "value": 0}, - "pv1_input_voltage": {"unit": "V", "value": 0.0}, - "pv2_input_voltage": {"unit": "V", "value": 0.0}, - "configuration_status": "Default", - "mppt1_charger_status": "Abnormal", - "mppt2_charger_status": "Abnormal", - "load_connected": "Connected", - "battery_power_direction": "Discharge", - "dc_ac_power_direction": "DC/AC", - "line_power_direction": "Do nothing", - "local_parallel_id": 0} - self.rated = {"ac_input_rating_voltage": {"unit": "V", "value": 230.0}, - "ac_input_rating_current": {"unit": "A", "value": 21.7}, - "ac_output_rating_voltage": {"unit": "V", "value": 230.0}, - "ac_output_rating_freq": {"unit": "Hz", "value": 50.0}, - "ac_output_rating_current": {"unit": "A", "value": 21.7}, - "ac_output_rating_apparent_power": {"unit": "VA", "value": 5000}, - "ac_output_rating_active_power": {"unit": "Wh", "value": 5000}, - "battery_rating_voltage": {"unit": "V", "value": 48.0}, - "battery_recharge_voltage": {"unit": "V", "value": 51.0}, - "battery_redischarge_voltage": {"unit": "V", "value": 58.0}, - "battery_under_voltage": {"unit": "V", "value": 42.0}, - "battery_bulk_voltage": {"unit": "V", "value": 57.6}, - "battery_float_voltage": {"unit": "V", "value": 54.0}, - "battery_type": "User", - "max_charging_current": {"unit": "A", "value": 60}, - "max_ac_charging_current": {"unit": "A", "value": 10}, - "input_voltage_range": "Appliance", - "output_source_priority": "Parallel output", - "charge_source_priority": "Solar-and-Utility", - "parallel_max_num": 6, - "machine_type": "Off-Grid-Tie", - "topology": "Transformer-less", - "output_model_setting": "Single module", - "solar_power_priority": "Load-Battery-Utility", - "mppt": "2"} - - self.host = host - self.port = port - self.interrupted = False - self.logger = logging.getLogger('srv') - - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.sock.bind((self.host, self.port)) - - def run(self): - self.sock.listen(5) - - while not self.interrupted: - conn, address = self.sock.accept() - - alive = True - while alive: - try: - buf = conn.recv(2048) - message = buf.decode().strip() - except OSError as exc: - self.logger.error('failed to recv()') - self.logger.exception(exc) - - alive = False - - try: - conn.close() - except: - pass - - continue # exit the loop - - self.logger.log(0, f'< {message}') - - if message.strip() == '': - continue - - if message == 'format json': - # self.logger.info(f'got {message}') - self.reply_ok(conn) - - elif message.startswith('exec '): - command = message[5:].split() - args = command[1:] - command = command[0] - - if command == 'get-allowed-ac-charging-currents': - self.reply_ok(conn, [2, 10, 20, 30, 40, 50, 60]) - elif command == 'get-status': - self.reply_ok(conn, self._get_status()) - elif command == 'get-rated': - self.reply_ok(conn, self._get_rated()) - elif command == 'set-max-ac-charging-current': - self.set_ac_current(args[1]) - self.reply_ok(conn, 1) - else: - raise ValueError('unsupported command: ' + command) - else: - raise ValueError('unexpected request: ' + message) - - def reply_ok(self, connection, data=None): - buf = 'ok' + '\r\n' - if data: - if not isinstance(data, str): - data = stringify({'result': 'ok', 'data': data}) - buf += data + '\r\n' - buf += '\r\n' - self.logger.log(0, f'> {buf.strip()}') - connection.sendall(buf.encode()) - - def _get_status(self) -> dict: - with self.lock: - return self.status - - def _get_rated(self) -> dict: - with self.lock: - return self.rated - - def _get_voltage(self) -> float: - with self.lock: - return self.status['battery_voltage']['value'] - - def stop(self): - self.interrupted = True - self.sock.close() - - def connect_ac(self): - with self.lock: - self.status['grid_voltage']['value'] = 230 - self.status['grid_freq']['value'] = 50 - charger.ac_changed(True) - - def disconnect_ac(self): - with self.lock: - self.status['grid_voltage']['value'] = 0 - self.status['grid_freq']['value'] = 0 - #self.status['battery_voltage']['value'] = 48.4 # revert to initial value - charger.ac_changed(False) - - def connect_mppt(self): - with self.lock: - self.status['pv1_input_power']['value'] = 1 - self.status['pv1_input_voltage']['value'] = 50 - self.status['mppt1_charger_status'] = 'Charging' - charger.mppt_changed(True) - - def disconnect_mppt(self): - with self.lock: - self.status['pv1_input_power']['value'] = 0 - self.status['pv1_input_voltage']['value'] = 0 - self.status['mppt1_charger_status'] = 'Abnormal' - charger.mppt_changed(False) - - def set_voltage(self, v: float): - with self.lock: - self.status['battery_voltage']['value'] = v - - def set_ac_current(self, amps): - with self.lock: - self.rated['max_ac_charging_current']['value'] = amps - charger.current_changed(amps) - - def set_pd(self, pd: BatteryPowerDirection): - if pd == BatteryPowerDirection.CHARGING: - val = 'Charge' - elif pd == BatteryPowerDirection.DISCHARGING: - val = 'Discharge' - else: - val = 'Do nothing' - with self.lock: - self.status['battery_power_direction'] = val - charger.pd_changed(pd) - - -logger = logging.getLogger(__name__) -evt_logger = logging.getLogger('evt') -server: Optional[InverterEmulator] = None -charger: Optional[ChargerEmulator] = None - - -def main(): - global server, charger - - # start fake inverterd server - try: - server = InverterEmulator(host=config['inverter']['host'], - port=config['inverter']['port']) - server.start() - except OSError as e: - logger.error('failed to start server') - logger.exception(e) - return - logger.info('server started') - - # start charger thread - charger = ChargerEmulator() - charger.start() - - # init inverterd wrapper - inverter.init(host=config['inverter']['host'], - port=config['inverter']['port']) - - # start monitor - mon = InverterMonitor() - mon.set_charging_event_handler(monitor_charging) - mon.set_battery_event_handler(monitor_battery) - mon.set_error_handler(monitor_error) - mon.start() - logger.info('monitor started') - - try: - InverterTestShell().cmdloop() - - server.join() - mon.join() - charger.join() - - except KeyboardInterrupt: - server.stop() - mon.stop() - charger.stop() - - -if __name__ == '__main__': - config.load('test_inverter_monitor') - main() |