From 10b7c7b267da7114340c1863a2e971ee01402746 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Wed, 19 Jun 2024 13:35:00 +0200 Subject: [PATCH] change Salidomo siganl name to match with Salimax for Venus --- .../dbus-fz-sonick-48tl-with-s3/controller.py | 644 ------------------ .../__init__.py | 0 .../config.py | 6 +- .../opt/dbus-fzsonick-48tl-fork-S3/config.pyc | Bin 0 -> 1475 bytes .../convert.py | 20 + .../dbus-fzsonick-48tl-fork-S3/convert.pyc | Bin 0 -> 6904 bytes .../data.py | 0 .../opt/dbus-fzsonick-48tl-fork-S3/data.pyc | Bin 0 -> 5538 bytes .../dbus-fzsonick-48tl.py | 0 .../ext/velib_python/ve_utils.py | 0 .../ext/velib_python/ve_utils.pyc | Bin 0 -> 8901 bytes .../ext/velib_python/vedbus.py | 0 .../ext/velib_python/vedbus.pyc | Bin 0 -> 21546 bytes .../python_libs/__init__.py} | 0 .../python_libs/__init__.pyc | Bin 0 -> 134 bytes .../python_libs/ie_dbus/__init__.py} | 0 .../python_libs/ie_dbus/__init__.pyc | Bin 0 -> 142 bytes .../python_libs/ie_dbus/dbus_service.py | 54 ++ .../python_libs/ie_dbus/dbus_service.pyc | Bin 0 -> 2657 bytes .../python_libs/ie_dbus/private/__init__.py | 0 .../python_libs/ie_dbus/private/__init__.pyc | Bin 0 -> 150 bytes .../python_libs/ie_dbus/private/datatypes.py | 22 + .../python_libs/ie_dbus/private/datatypes.pyc | Bin 0 -> 883 bytes .../ie_dbus/private/dbus_connection.py | 185 +++++ .../ie_dbus/private/dbus_connection.pyc | Bin 0 -> 8378 bytes .../ie_dbus/private/dbus_daemon.py | 273 ++++++++ .../ie_dbus/private/dbus_daemon.pyc | Bin 0 -> 9390 bytes .../ie_dbus/private}/dbus_types.py | 0 .../ie_dbus/private/dbus_types.pyc | Bin 0 -> 4887 bytes .../ie_dbus/private/message_types.py | 259 +++++++ .../ie_dbus/private/message_types.pyc | Bin 0 -> 9236 bytes .../ie_dbus/private/own_properties.py | 177 +++++ .../ie_dbus/private/own_properties.pyc | Bin 0 -> 6648 bytes .../ie_dbus/private/remote_properties.py | 166 +++++ .../ie_dbus/private/remote_properties.pyc | Bin 0 -> 6523 bytes .../python_libs/ie_dbus/private/settings.py | 89 +++ .../python_libs/ie_dbus/private/settings.pyc | Bin 0 -> 3664 bytes .../ie_dbus/private/ve_constants.py | 11 + .../ie_dbus/private/ve_constants.pyc | Bin 0 -> 609 bytes .../python_libs/ie_utils/__init__.py | 0 .../python_libs/ie_utils/__init__.pyc | Bin 0 -> 143 bytes .../python_libs/ie_utils/filters.py | 73 ++ .../python_libs/ie_utils/filters.pyc | Bin 0 -> 2634 bytes .../python_libs/ie_utils/main_loop.py | 30 + .../python_libs/ie_utils/main_loop.pyc | Bin 0 -> 1069 bytes .../python_libs/ie_utils/mixins.py | 115 ++++ .../python_libs/ie_utils/mixins.pyc | Bin 0 -> 4808 bytes .../python_libs/ie_utils/utils.py | 44 ++ .../python_libs/ie_utils/utils.pyc | Bin 0 -> 1530 bytes .../python_libs/pysnooper/__init__.py | 30 + .../python_libs/pysnooper/pycompat.py | 95 +++ .../python_libs/pysnooper/tracer.py | 498 ++++++++++++++ .../python_libs/pysnooper/utils.py | 98 +++ .../python_libs/pysnooper/variables.py | 133 ++++ .../dbus-fzsonick-48tl-fork-S3/service/down | 0 .../service/log/down | 0 .../service/log/run | 0 .../service/run | 0 .../signals.py | 5 +- .../dbus-fzsonick-48tl-fork-S3/signals.pyc | Bin 0 -> 22470 bytes .../start.sh | 0 61 files changed, 2378 insertions(+), 649 deletions(-) delete mode 100755 firmware/opt/dbus-fz-sonick-48tl-with-s3/controller.py rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/__init__.py (100%) rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/config.py (87%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/config.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/convert.py (87%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/convert.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/data.py (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/data.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/dbus-fzsonick-48tl.py (100%) rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/ext/velib_python/ve_utils.py (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/ve_utils.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/ext/velib_python/vedbus.py (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/vedbus.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3/service/down => dbus-fzsonick-48tl-fork-S3/python_libs/__init__.py} (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/__init__.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3/service/log/down => dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/__init__.py} (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/__init__.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/dbus_service.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/dbus_service.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_daemon.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_daemon.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private}/dbus_types.py (100%) create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_types.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/message_types.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/message_types.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/remote_properties.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/remote_properties.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/settings.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/settings.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/filters.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/filters.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.pyc create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/__init__.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/pycompat.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/tracer.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/utils.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/variables.py create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/service/down create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/service/log/down rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/service/log/run (100%) rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/service/run (100%) rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/signals.py (99%) mode change 100644 => 100755 create mode 100644 firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.pyc rename firmware/opt/{dbus-fz-sonick-48tl-with-s3 => dbus-fzsonick-48tl-fork-S3}/start.sh (100%) diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/controller.py b/firmware/opt/dbus-fz-sonick-48tl-with-s3/controller.py deleted file mode 100755 index 749093592..000000000 --- a/firmware/opt/dbus-fz-sonick-48tl-with-s3/controller.py +++ /dev/null @@ -1,644 +0,0 @@ -#!/usr/bin/python -u -# coding=utf-8 - -import logging -import os -import time -import states as State -import target_type as TargetType - -from random import randint -from python_libs.ie_dbus.dbus_service import DBusService -from python_libs.ie_utils.main_loop import run_on_main_loop - -# noinspection PyUnreachableCode -if False: - from typing import NoReturn, Optional, Any, Iterable, List - -logging.basicConfig(level=logging.INFO) -_log = logging.getLogger(__name__) - -VERSION = '1.0.0' -PRODUCT = 'Controller' - -GRID_SERVICE_PREFIX = 'com.victronenergy.grid.' -BATTERY_SERVICE_PREFIX = 'com.victronenergy.battery.' -INVERTER_SERVICE_PREFIX = 'com.victronenergy.vebus.' -SYSTEM_SERVICE_PREFIX = 'com.victronenergy.system' -HUB4_SERVICE_PREFIX = 'com.victronenergy.hub4' -SETTINGS_SERVICE_PREFIX = 'com.victronenergy.settings' - -UPDATE_PERIOD_MS = 2000 -MAX_POWER_PER_BATTERY = 2500 - -MAX_DAYS_WITHOUT_EOC = 7 -SECONDS_PER_DAY = 24 * 60 * 60 - -GRID_SET_POINT_SETTING = PRODUCT + '/GridSetPoint' -LAST_EOC_SETTING = PRODUCT + '/LastEOC' -CALIBRATION_CHARGE_START_TIME_OF_DAY_SETTING = PRODUCT + '/CalibrationChargeStartTime' - -HEAT_LOSS = 150 # W -P_CONST = 0.5 # W/W - -Epoch = int -Seconds = int - - -def time_now(): - return int(time.time()) - - -class Controller(object): - - def __init__(self, measurement, target, target_type, state): - # type: (float, float, int, int) -> NoReturn - self.target_type = target_type - self.target = target - self.measurement = measurement - self.state = state - - d_p = target - measurement - self.delta = d_p * P_CONST - - @staticmethod - def min(controllers): - # type: (Iterable[Controller]) -> Controller - return min(controllers, key=lambda c: c.delta) - - @staticmethod - def max(controllers): - # type: (Iterable[Controller]) -> Controller - return max(controllers, key=lambda c: c.delta) - - def clamp(self, lower_limit_controllers, upper_limit_controllers): - # type: (List[Controller],List[Controller]) -> Controller - c_min = Controller.min(upper_limit_controllers + [self]) - return Controller.max(lower_limit_controllers + [c_min]) - - -# noinspection PyMethodMayBeStatic -class InnovEnergyController(DBusService): - - def __init__(self): - - super(InnovEnergyController, self).__init__(PRODUCT.lower()) - - self.settings.add_setting(path=LAST_EOC_SETTING, default_value=0) # unix epoch timestamp - self.settings.add_setting(path=GRID_SET_POINT_SETTING, default_value=0) # grid setpoint, Watts - - self.settings.add_setting(path=CALIBRATION_CHARGE_START_TIME_OF_DAY_SETTING, default_value=32400) # 09:00 - - self.own_properties.set('/ProductName', PRODUCT) - self.own_properties.set('/Mgmt/ProcessName', __file__) - self.own_properties.set('/Mgmt/ProcessVersion', VERSION) - self.own_properties.set('/Mgmt/Connection', 'dbus') - self.own_properties.set('/ProductId', PRODUCT) - self.own_properties.set('/FirmwareVersion', VERSION) - self.own_properties.set('/HardwareVersion', VERSION) - self.own_properties.set('/Connected', 1) - self.own_properties.set('/TimeToCalibrationCharge', 'unknown') - self.own_properties.set('/State', 0) - - self.phases = [ - p for p in ['/Hub4/L1/AcPowerSetpoint', '/Hub4/L2/AcPowerSetpoint', '/Hub4/L3/AcPowerSetpoint'] - if self.remote_properties.exists(self.inverter_service + p) - ] - - self.n_phases = len(self.phases) - print ('The system has ' + str(self.n_phases) + ' phase' + ('s' if self.n_phases != 1 else '')) - - self.max_inverter_power = 32700 - # ^ defined in https://github.com/victronenergy/dbus_modbustcp/blob/master/CCGX-Modbus-TCP-register-list.xlsx - - def clamp_power_command(self, value): - # type: (float) -> int - - value = max(value, -self.max_inverter_power) - value = min(value, self.max_inverter_power) - - return int(value) - - def get_service(self, prefix): - # type: (str) -> Optional[unicode] - service = next((s for s in self.available_services if s.startswith(prefix)), None) - - if service is None: - raise Exception('no service matching ' + prefix + '* available') - - return service - - def is_service_available(self, prefix): - # type: (str) -> bool - return next((True for s in self.available_services if s.startswith(prefix)), False) - - @property - def battery_service(self): - # type: () -> Optional[unicode] - return self.get_service(BATTERY_SERVICE_PREFIX) - - @property - def battery_available(self): - # type: () -> bool - return self.is_service_available(BATTERY_SERVICE_PREFIX) - - @property - def grid_service(self): - # type: () -> Optional[unicode] - return self.get_service(GRID_SERVICE_PREFIX) - - @property - def grid_meter_available(self): - # type: () -> bool - return self.is_service_available(GRID_SERVICE_PREFIX) - - @property - def inverter_service(self): - # type: () -> Optional[unicode] - return self.get_service(INVERTER_SERVICE_PREFIX) - - @property - def inverter_available(self): - # type: () -> bool - return self.is_service_available(INVERTER_SERVICE_PREFIX) - - @property - def system_service(self): - # type: () -> Optional[unicode] - return self.get_service(SYSTEM_SERVICE_PREFIX) - - @property - def system_service_available(self): - # type: () -> bool - return self.is_service_available(SYSTEM_SERVICE_PREFIX) - - @property - def hub4_service(self): - # type: () -> Optional[unicode] - return self.get_service(HUB4_SERVICE_PREFIX) - - @property - def hub4_service_available(self): - # type: () -> bool - return self.is_service_available(HUB4_SERVICE_PREFIX) - - @property - def inverter_power_setpoint(self): - # type: () -> float - return sum((self.get_inverter_prop(p) for p in self.phases)) - - def get_battery_prop(self, dbus_path): - # type: (str) -> Any - battery_service = self.battery_service - return self.remote_properties.get(battery_service + dbus_path).value - - def get_grid_prop(self, dbus_path): - # type: (str) -> Any - return self.remote_properties.get(self.grid_service + dbus_path).value - - def get_inverter_prop(self, dbus_path): - # type: (str) -> Any - return self.remote_properties.get(self.inverter_service + dbus_path).value - - def get_system_prop(self, dbus_path): - # type: (str) -> Any - system_service = self.system_service - return self.remote_properties.get(system_service + dbus_path).value - - def get_hub4_prop(self, dbus_path): - # type: (str) -> Any - hub4_service = self.hub4_service - return self.remote_properties.get(hub4_service + dbus_path).value - - def set_settings_prop(self, dbus_path, value): - # type: (str, Any) -> bool - return self.remote_properties.set(SETTINGS_SERVICE_PREFIX + dbus_path, value) - - def set_inverter_prop(self, dbus_path, value): - # type: (str, Any) -> bool - inverter_service = self.inverter_service - return self.remote_properties.set(inverter_service + dbus_path, value) - - @property - def max_battery_charge_power(self): - # type: () -> int - return self.get_battery_prop('/Info/MaxChargePower') - - @property - def max_battery_discharge_power(self): - # type: () -> int - return self.get_battery_prop('/Info/MaxDischargePower') - - @property - def max_configured_charge_power(self): - # type: () -> Optional[int] - max_power = self.settings.get('/Settings/CGwacs/MaxChargePower') - return max_power if max_power >= 0 else None - - @property - def max_configured_discharge_power(self): # unsigned - # type: () -> Optional[int] - max_power = self.settings.get('/Settings/CGwacs/MaxDischargePower') - return max_power if max_power >= 0 else None - - @property - def max_charge_power(self): - # type: () -> int - if self.max_configured_charge_power is None: - return self.max_battery_charge_power - else: - return min(self.max_battery_charge_power, self.max_configured_charge_power) - - @property - def max_discharge_power(self): # unsigned - # type: () -> int - if self.max_configured_discharge_power is None: - return self.max_battery_discharge_power - else: - return min(self.max_battery_discharge_power, self.max_configured_discharge_power) - - def set_inverter_power_setpoint(self, power): - # type: (float) -> NoReturn - - if self.settings.get('/Settings/CGwacs/BatteryLife/State') == 9: - self.settings.set('/Settings/CGwacs/BatteryLife/State', 0) # enables scheduled charge - self.settings.set('/Settings/CGwacs/Hub4Mode', 3) # disable hub4 - self.set_inverter_prop('/Hub4/DisableCharge', 0) - self.set_inverter_prop('/Hub4/DisableFeedIn', 0) - - power = self.clamp_power_command(power / self.n_phases) - for p in self.phases: - self.set_inverter_prop(p, power + randint(-1, 1)) # use randint to force dbus re-send - - def set_controller_state(self, state): - # type: (int) -> NoReturn - self.own_properties.set('/State', state) - - @property - def grid_power(self): - # type: () -> Optional[float] - try: - return self.get_grid_prop('/Ac/Power') - except: - return None - - @property - def battery_cold(self): - # type: () -> bool - return self.get_battery_prop('/IoStatus/BatteryCold') == 1 - - @property - def eoc_reached(self): - # type: () -> bool - if not self.battery_available: - return False - - return min(self.get_battery_prop('/EOCReached')) == 1 - - @property - def battery_power(self): - # type: () -> float - return self.get_battery_prop('/Dc/0/Power') - - @property - def inverter_ac_in_power(self): - # type: () -> float - return self.get_inverter_prop('/Ac/ActiveIn/P') - - @property - def inverter_ac_out_power(self): - # type: () -> float - return self.get_inverter_prop('/Ac/Out/P') - - @property - def soc(self): - # type: () -> float - return self.get_battery_prop('/Soc') - - @property - def n_batteries(self): - # type: () -> int - return self.get_battery_prop('/NbOfBatteries') - - @property - def min_soc(self): - # type: () -> float - return self.settings.get('/Settings/CGwacs/BatteryLife/MinimumSocLimit') - - @property - def should_hold_min_soc(self): - # type: () -> bool - return self.min_soc <= self.soc <= self.min_soc + 5 - - @property - def utc_offset(self): - # type: () -> int - - # stackoverflow.com/a/1301528 - # stackoverflow.com/a/3168394 - - os.environ['TZ'] = self.settings.get('/Settings/System/TimeZone') - time.tzset() - is_dst = time.daylight and time.localtime().tm_isdst > 0 - return -(time.altzone if is_dst else time.timezone) - - @property - def grid_set_point(self): - # type: () -> float - return self.settings.get('/Settings/CGwacs/AcPowerSetPoint') - - @property - def time_to_calibration_charge_str(self): - # type: () -> str - return self.own_properties.get('/TimeToCalibrationCharge').text - - @property - def calibration_charge_deadline(self): - # type: () -> Epoch - - utc_offset = self.utc_offset - ultimate_deadline = self.settings.get(LAST_EOC_SETTING) + MAX_DAYS_WITHOUT_EOC * SECONDS_PER_DAY - midnight_before_udl = int((ultimate_deadline + utc_offset) / SECONDS_PER_DAY) * SECONDS_PER_DAY - utc_offset # round off to last midnight - - dead_line = midnight_before_udl + self.calibration_charge_start_time_of_day - - while dead_line > ultimate_deadline: # should fire at most once, but let's be defensive... - dead_line -= SECONDS_PER_DAY # too late, advance one day - - return dead_line - - @property - def time_to_calibration_charge(self): - # type: () -> Seconds - return self.calibration_charge_deadline - time_now() - - @property - def grid_blackout(self): - # type: () -> bool - return self.get_inverter_prop('/Leds/Mains') < 1 - - @property - def scheduled_charge(self): - # type: () -> bool - return self.get_hub4_prop('/Overrides/ForceCharge') != 0 - - @property - def calibration_charge_start_time_of_day(self): - # type: () -> Seconds - return self.settings.get(CALIBRATION_CHARGE_START_TIME_OF_DAY_SETTING) # seconds since midnight - - @property - def must_do_calibration_charge(self): - # type: () -> bool - return self.time_to_calibration_charge <= 0 - - def controller_charge_to_min_soc(self): - # type: () -> Controller - - return Controller( - measurement=self.battery_power, - target=self.max_charge_power, - target_type=TargetType.BATTERY_DC, - state=State.CHARGE_TO_MIN_SOC - ) - - def controller_hold_min_soc(self): - # type: () -> Controller - - # TODO: explain - - a = -4 * HEAT_LOSS * self.n_batteries - b = -a * (self.min_soc + .5) - - target_dc_power = a * self.soc + b - - return Controller( - measurement = self.battery_power, - target = target_dc_power, - target_type = TargetType.BATTERY_DC, - state = State.HOLD_MIN_SOC - ) - - def controller_calibration_charge(self): - # type: () -> Controller - - return Controller( - measurement = self.battery_power, - target = self.max_charge_power, - target_type = TargetType.BATTERY_DC, - state = State.CALIBRATION_CHARGE - ) - - def controller_limit_discharge_power(self): # signed - # type: () -> Controller - - return Controller( - measurement = self.battery_power, - target = -self.max_discharge_power, # add sign! - target_type = TargetType.BATTERY_DC, - state = State.LIMIT_DISCHARGE_POWER - ) - - def controller_limit_charge_power(self): - # type: () -> Controller - return Controller( - measurement = self.battery_power, - target = self.max_charge_power, - target_type = TargetType.BATTERY_DC, - state = State.LIMIT_CHARGE_POWER - ) - - def controller_optimize_self_consumption(self): - # type: () -> Controller - - return Controller( - measurement = self.grid_power, - target = self.grid_set_point, - target_type = TargetType.GRID_AC, - state = State.OPTIMIZE_SELF_CONSUMPTION - ) - - def controller_heating(self): - # type: () -> Controller - - return Controller( - measurement = self.battery_power, - target = self.max_charge_power, - target_type = TargetType.BATTERY_DC, - state = State.HEATING - ) - - def controller_scheduled_charge(self): - # type: () -> Controller - - return Controller( - measurement = self.battery_power, - target = self.max_charge_power, - target_type = TargetType.BATTERY_DC, - state = State.SCHEDULED_CHARGE - ) - - def controller_no_grid_meter(self): - # type: () -> Controller - - return Controller( - measurement = self.battery_power, - target = self.max_charge_power, - target_type = TargetType.BATTERY_DC, - state = State.NO_GRID_METER_AVAILABLE - ) - - def controller_no_battery(self): - # type: () -> Controller - - return Controller( - measurement = self.inverter_ac_in_power, - target = 0, - target_type = TargetType.INVERTER_AC_IN, - state = State.NO_BATTERY_AVAILABLE - ) - - def controller_bridge_grid_blackout(self): - # type: () -> Controller - - return Controller( - measurement = 0, - target = 0, - target_type = TargetType.GRID_AC, - state = State.BRIDGE_GRID_BLACKOUT - ) - - def update_eoc(self): - - if self.eoc_reached: - print('battery has reached EOC') - self.settings.set(LAST_EOC_SETTING, time_now()) - - self.publish_time_to_calibration_charge() - - def publish_time_to_calibration_charge(self): - - total_seconds = self.time_to_calibration_charge - - if total_seconds <= 0: - time_to_eoc_str = 'now' - else: - total_minutes, seconds = divmod(total_seconds, 60) - total_hours, minutes = divmod(total_minutes, 60) - total_days, hours = divmod(total_hours, 24) - - days_str = (str(total_days) + 'd') if total_days > 0 else '' - hours_str = (str(hours) + 'h') if total_hours > 0 else '' - minutes_str = (str(minutes) + 'm') if total_days == 0 else '' - - time_to_eoc_str = "{0} {1} {2}".format(days_str, hours_str, minutes_str).strip() - - self.own_properties.set('/TimeToCalibrationCharge', time_to_eoc_str) - - def print_system_stats(self, controller): - # type: (Controller) -> NoReturn - - def soc_setpoint(): - if controller.state == State.CALIBRATION_CHARGE or controller.state == State.NO_GRID_METER_AVAILABLE: - return ' => 100%' - if controller.state == State.CHARGE_TO_MIN_SOC: - return ' => ' + str(int(self.min_soc)) + '%' - return '' - - def setpoint(target_type): - if target_type != controller.target_type: - return '' - return ' => ' + str(int(controller.target)) + 'W' - - def p(power): - # type: (Optional[float]) -> str - if power is None: - return ' --- W' - else: - return str(int(power)) + 'W' - - ac_loads = None if self.grid_power is None else self.grid_power - self.inverter_ac_in_power - delta = p(controller.delta) if controller.delta < 0 else '+' + p(controller.delta) - battery_power = self.battery_power if self.battery_available else None - soc_ = str(self.soc) + '%' if self.battery_available else '---' - - print (State.name_of[controller.state]) - print ('') - print ('time to CC: ' + self.time_to_calibration_charge_str) - print (' SOC: ' + soc_ + soc_setpoint()) - print (' grid: ' + p(self.grid_power) + setpoint(TargetType.GRID_AC)) - print (' battery: ' + p(battery_power) + setpoint(TargetType.BATTERY_DC)) - print (' AC in: ' + p(self.inverter_ac_in_power) + ' ' + delta) - print (' AC out: ' + p(self.inverter_ac_out_power)) - print (' AC loads: ' + p(ac_loads)) - - def choose_controller(self): - # type: () -> Controller - - if self.grid_blackout: - return self.controller_bridge_grid_blackout() - - if not self.battery_available: - return self.controller_no_battery() - - if self.battery_cold: - return self.controller_heating() - - if self.scheduled_charge: - return self.controller_scheduled_charge() - - if self.must_do_calibration_charge: - return self.controller_calibration_charge() - - if self.soc < self.min_soc: - return self.controller_charge_to_min_soc() - - if not self.grid_meter_available: - return self.controller_no_grid_meter() - - hold_min_soc = self.controller_hold_min_soc() - limit_discharge_power = self.controller_limit_discharge_power() # signed - - lower_limit = [limit_discharge_power, hold_min_soc] - - # No upper limit. We no longer actively limit charge power. DC/DC Charger inside the BMS will do that for us. - upper_limit = [] - - optimize_self_consumption = self.controller_optimize_self_consumption() - - return optimize_self_consumption.clamp(lower_limit, upper_limit) - - def update(self): - - print('iteration started\n') - - self.update_eoc() - - if self.inverter_available: - - controller = self.choose_controller() - power = self.inverter_ac_in_power + controller.delta - - self.set_inverter_power_setpoint(power) - self.set_controller_state(controller.state) - self.print_system_stats(controller) # for debug - - else: - self.set_controller_state(State.NO_INVERTER_AVAILABLE) - print('inverter not available!') - - print('\niteration finished\n') - - -def main(): - - print('starting ' + __file__) - - with InnovEnergyController() as service: - run_on_main_loop(service.update, UPDATE_PERIOD_MS) - - print(__file__ + ' has shut down') - - -if __name__ == '__main__': - main() diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/__init__.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/__init__.py similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/__init__.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/__init__.py diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/config.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/config.py similarity index 87% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/config.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/config.py index 1d4962daa..dfbc78f16 100755 --- a/firmware/opt/dbus-fz-sonick-48tl-with-s3/config.py +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/config.py @@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3' # S3 Credentials -S3BUCKET = "5-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" -S3KEY = "EXO6bb63d9bbe5f938a68fa444b" -S3SECRET = "A4-5wIjIlAqn-p0cUkQu0f9fBIrX1V5PGTBDwjsrlR8" +S3BUCKET = "10-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" +S3KEY = "EXOa8cc58d2e51e389fed9ccbfa" +S3SECRET = "hofDGMmSSN1OACYXHWRUGdG61mFjBxKC18sF0VpMQgY" diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/config.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/config.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d7d833e2c2cf1bd1e37cbff6fc61df70b0b79d0 GIT binary patch literal 1475 zcmZuxTXWk)6h6+yiG$PJnzp&LHBg2OjBl|MoEe5hw$`pDw&ZGMH{KaWqiAKLU`rWm zlOeqFE6T6nl{esl=l%=c;RnE3DQ#M&E9vYx-?{9$NSgg;c`5z-w?h|h*9`6__(Xpr z;drND48Y#pMmiVOlE=2LW<)y2Ye3LJn(s7Y2ayK3&0nEEdpNzmI0mtwgg@Z zE>^|^yV4J`Y;Nl^$ zN5Jm`+XB7`Y#aCkU_0@-yTDQV7`$2FkKiZF_zCbWU{8T>1Iq#50roNQU0|O8e+=wX z;7@=(lcmzVr&zZCFD`vAg{)XdGW8s85B;%chk<90{lJTs5blKh{3^bZ6t0uDO~%mm}0QNn~rV@=ZRe*nn!h|P%4!C z*9!onpI_nHIz92fKzxJ`uHTMtnb*hu3d;VTB+}9UYZl3gB#mNt=>^x90b-GG#$!kLVUQ1;i5E%da;dP7sg?adu|*<- zh%9x#RO(lv&trYwEmbRr=Z8+7?|*feue!XJKXR+peA#on+Mzo<;3ZG&pe;EwoSN$% z)cC%4Q1&XdqoK!-T=#tF2$|b=;ZSXLCY0)BLum}op1(AEE#5jTPns9?A5I(PTGTA{ zFFP+rgT2qtUZjyk*mP$suyHUBM5y3e2GCR?yQ`AE)*!Z~Q%lhsL~I~Ni5Y{^$L^Y>shx>ci5pGprD77hPfRKgkO6vK zRk36M3%!)Kkb_c5z1NC6*fv^roAgP7_w9~y7F!^vB?+1~wMjPd8{VA>@Im@ zDV7mbp1+I8w@&YMY@>M_jmFx#VqsQ;xDj=Q5?fJKlTaEbt{c+aBrT1i5fy6~Lmoui zO8?GMwzR95nl-QuRgK4xi18RKtwW5S6_;99da7+=FA`~hO`#e~Bx)I5yRKO@9$3dv zNqk(~^>$m4(@G|^iN|TWZuE&x%+|n`qrk$ou6X!iP1i6jvHL$A&e$> Callable[[BatteryStatus], bitmap] + + def get_value(status): + # type: (BatteryStatus) -> bitmap + value = status.modbus_data[register - cfg.BASE_ADDRESS] + + string1_disabled = int((value & 0b00001) != 0) + string2_disabled = int((value & 0b00010) != 0) + string3_disabled = int((value & 0b00100) != 0) + string4_disabled = int((value & 0b01000) != 0) + string5_disabled = int((value & 0b10000) != 0) + n_limb_strings = string1_disabled+string2_disabled+string3_disabled+string4_disabled+string5_disabled + if n_limb_strings>=2: + return True + else: + + return False + return get_value + def return_in_list(ts): return ts diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/convert.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/convert.pyc new file mode 100644 index 0000000000000000000000000000000000000000..394f5df5dd504d25af63aaad80aa4d685ef1e5b3 GIT binary patch literal 6904 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 D(qnyz literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/dbus-fzsonick-48tl.py similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/dbus-fzsonick-48tl.py diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/ext/velib_python/ve_utils.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/ve_utils.py similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/ext/velib_python/ve_utils.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/ve_utils.py diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/ve_utils.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/ve_utils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe772331273b9e2cb7e199a2ba56454ed11b673 GIT binary patch literal 8901 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 literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/ext/velib_python/vedbus.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/vedbus.py similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/ext/velib_python/vedbus.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/vedbus.py diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/vedbus.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/ext/velib_python/vedbus.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d71fb690f3ac1a8bbbd6c93a2330d8bc34bd71f1 GIT binary patch literal 21546 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 literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/service/down b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/__init__.py similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/service/down rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/__init__.py diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/__init__.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..579091fe875bf91eb7a808f1e153bf9cde55a78b GIT binary patch literal 134 zcmZSn%*(|*H!U%l0SXv_v;z1(hWk`FZg1(hWk`FZg NoReturn + + service_name = service_name if service_name.startswith(SERVICE_PREFIX) else SERVICE_PREFIX + service_name + + self._daemon = DBusDaemon(connection_type_or_address) + self.remote_properties = RemoteProperties(self._daemon) + self.own_properties = OwnProperties(self._daemon) + self.own_properties.set('/DeviceInstance', device_instance) # must be set before request_name, sigh + + self.settings = Settings(self._daemon, self.remote_properties) + self.name = service_name + + if service_name is not None: + self._bus_name = self._daemon.request_name(service_name) + _log.info('service name is ' + service_name) + + _log.info('id is ' + self.bus_id) + + @property + def available_services(self): + # type: () -> List[unicode] + return [s.name for s in self._daemon.services] + + @property + def bus_id(self): + # type: () -> unicode + return self._daemon.bus_id diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/dbus_service.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/dbus_service.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ab9d97868c1f424198db4370f4acc67b5b5739a GIT binary patch literal 2657 zcmb_eTW=dh6h5=-OPs`U?ny6zRkdItVGHqs1VWHYrAVn;joXT@1fz|2;*QyiyE9H| zDZG&U4SofW{0#mC@B9Eb=d5#Uk@C>knem*nXJ*dj`_9Jyr#|cdei-&)`YGY>_c-j| zI7Rp;AO=VT1Of!f9>g9bJ_J4_B?wB8lp!b=&wPk0kW?Y47IF#V8Axgn)C#!_@hl{B z5X=>F1>$)~>JZcmxeDd*J@y}=3o4@ z1g=~Sr-63+*NjJPYx^WL>3)`tT!oeWERTv~c+b|@ z?u0tY(n429o2SR=vpgH=+!<{Pb#Y#jIsNXF1>Dfvl`r?Uchxhw`*`oiV#f^WwR0vN z*j5PzSH|f@YO<6a`5~S3T<*xMn@R1)dFse)-&ohDC}~h=+c@l}IGq3t@fbl#w-3O1 zRD2XSQ2tE-B^R%3d1%_T4{4Xqk4#^yG)%NQG_lipGZ|agv@XnD^Vqo8&CBeu(vnhE zaWiN1Wf+h3Zk}g(5!A~6pV~q-@MOQiRR?q3F=?6|=~U-~NhjBFIQgL$;hD(h$TI7X5Rv3bNOtg7H0lI*-OhJ^n)B zL|oP*Un4QCQay07@heuwgAz5kje@-ojby7|*UI5pC;UYy!exa6FIMw;QW2sQ%DLn>i*Am@2 zJDR|=mm)6GzP4O~BF#+#RMXg|B}SQumMtorB=#_RBxTDHj#R|#l4RGG_1PN4VmU*x}nN1RwkrIZ!y(ZC#*J6%AH#$5blGqA>=eC=_1fqG+P*^@ny+NnE>wgGpNPmRB0(ZtFfOn3l+e z0lu0f%HRlCSDJGhoAcP2*tQe%79lSw`I2)?&~l%>Z%%EzJ<82d=yaQV`jqzE2R^@H z!*$*l{osom_PpOlxWiEH-U;QJZ}>_<-cnC5Xet15f}S%`p~j7xV|kg_#M-gTGgVs TFBZ?g-n?HCkNjG>A}aoWKXY80 literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00a1a3eecca65fe3a9482465dafeda73df4bc9c0 GIT binary patch literal 150 zcmZSn%*(|*H!U%l0SXv_v;z1(hWk`FZgjjV09;`rX8-^I literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.py new file mode 100644 index 000000000..282956033 --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.py @@ -0,0 +1,22 @@ +from logging import getLogger + +from python_libs.ie_utils.mixins import Record + +_log = getLogger(__name__) + +# noinspection PyUnreachableCode +if False: + from typing import AnyStr + + +class ServiceInfo(Record): + + # noinspection PyShadowingBuiltins + def __init__(self, name, id, pid, proc_name, cmd): + # type: (AnyStr, AnyStr, int, str, str) -> ServiceInfo + + self.proc_name = proc_name + self.name = name + self.id = id + self.cmd = cmd + self.pid = pid diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/datatypes.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4d25daf20d25df8747c6fa05853c3ca6059b173 GIT binary patch literal 883 zcmcIi&2H2%5FW?bbhi}IiX$(O(?-1`R6rmhRXuo5a8<`jk8X z%s3V00U*hI`RvT^7t5b7&*kI3Y2oRT^u8ern!wN_zyb6EY5{`R8Mq933AF@)0x*XM z`nUotVEU9IB)Bu^*HEuPc#6mYCF}*575o5r0Qg#Oz!b16&!~p|MOP_^#SKA+@pkAs zoR+PEwiLF*WS-aW{k)4)d_kXfI2~<^U;KXfGXbaiKo+hD5V&#bn=02Cs9j3aa$2^G z>?2vYCOGAA0-(81A`!wY5yDj>5>E7xPtGZqTzBP)VpH$qG_=|`J;t2265#^YL{_!! zm-lE*$+eSHK(|+%K&467%s5!FG$q*r;f7c>jIpxb4@dMkb#pbLYv$`}oa48_YiI96 zWl@{^L#W2d9-D}jX<`%S5yO_eIJ?%?+o<&`;^CYC}lw`ilF0%_+h}ZHrWv#l@ literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.py new file mode 100644 index 000000000..54372460a --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.py @@ -0,0 +1,185 @@ +from logging import getLogger + +from _dbus_bindings import Connection, MethodCallMessage, SignalMessage, BUS_DAEMON_NAME, \ + BUS_DAEMON_PATH, BUS_DAEMON_IFACE, NAME_FLAG_DO_NOT_QUEUE, Message, HANDLER_RESULT_HANDLED + +from python_libs.ie_dbus.private.dbus_types import dbus_string, dbus_uint32 +from python_libs.ie_dbus.private.message_types import DBusException +from python_libs.ie_utils.mixins import Disposable + +_log = getLogger(__name__) + +# noinspection PyUnreachableCode +if False: + from typing import List, Optional, Iterable, Callable, Union, NoReturn, AnyStr, Any + from python_libs.ie_dbus.private.dbus_types import DbusType + + +class DbusConnection(Disposable): + """ + A collection of stateless functions operating on a Connection object + """ + + def __init__(self, connection_type_or_address): + # type: (Union[int, AnyStr]) -> NoReturn + + self._address = connection_type_or_address + # noinspection PyProtectedMember + self._connection = Connection._new_for_bus(connection_type_or_address) # it's not disposable + self.chain_disposable(self._connection.close, 'connection ' + self._connection.get_unique_name()) + + @property + def bus_id(self): + return self._connection.get_unique_name() + + def fork(self): + return DbusConnection(self._address) + + def get_ids_and_service_names(self): + # type: () -> Iterable[unicode] + + # noinspection PyTypeChecker + return map(unicode, self.call_daemon_method('ListNames')[0]) + + def get_service_names(self): + # type: () -> Iterable[AnyStr] + + return ( + unicode(name) + for name + in self.get_ids_and_service_names() + if not name.startswith(':') + ) + + def get_service_ids(self): + # type: () -> Iterable[AnyStr] + + return ( + name + for name in self.get_ids_and_service_names() if name.startswith(':')) + + # noinspection PyBroadException + def get_pid_of_service(self, service_name): + # type: (AnyStr) -> Optional[int] + try: + reply = self.call_daemon_method('GetConnectionUnixProcessID', dbus_string(service_name)) + return int(reply[0]) + except: + return None + + def get_id_of_service(self, service_name): + # type: (AnyStr) -> AnyStr + reply = self.call_daemon_method('GetNameOwner', dbus_string(service_name)) + return unicode(reply[0]) + + def call_method(self, service_name, object_path, interface, member, *args): + # type: (AnyStr, AnyStr, Optional[str], str, List[Any]) -> List[Any] + + msg = MethodCallMessage(service_name, object_path, interface, member) + + for arg in args: + msg.append(arg) + + reply = self._connection.send_message_with_reply_and_block(msg) # with py3 we could use asyncio here + DBusException.raise_if_error_reply(reply) + + return reply.get_args_list() # TODO: utf8_strings=True ? + + def send_message(self, msg): + # type: (Message) -> NoReturn + + self._connection.send_message(msg) + + def call_daemon_method(self, method_name, *args): + # type: (AnyStr, Iterable[DbusType])-> List[any] + + return self.call_method(BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE, method_name, *args) + + def request_name(self, service_name): + # type: (AnyStr) -> Disposable + + _log.debug('requesting bus name ' + service_name) + + self.call_daemon_method('RequestName', dbus_string(service_name), dbus_uint32(NAME_FLAG_DO_NOT_QUEUE)) + + def dispose(): + self.call_daemon_method('ReleaseName', dbus_string(service_name)) + + return self.create_dependent_disposable(dispose, 'bus name ' + service_name) + + def broadcast_signal(self, object_path, interface, member, *args): + # type: (AnyStr, AnyStr, AnyStr, List[Any]) -> NoReturn + + msg = SignalMessage(object_path, interface, member) + for arg in args: + msg.append(arg) + + self._connection.send_message(msg) + + def add_message_callback(self, callback, filter_rule, fork=True): + # type: (Callable[[Message], NoReturn], AnyStr, Optional[bool]) -> Disposable + if fork: + return self._add_message_callback_fork(callback, filter_rule) + else: + return self._add_message_callback_no_fork(callback, filter_rule) + + def _add_message_callback_no_fork(self, callback, filter_rule): # TODO: forking for incoming method calls + # type: (Callable[[Message], NoReturn], AnyStr) -> Disposable + + def dispatch(_, msg): + # type: (Connection, Message) -> int + + #_log.info(' ####### got message type=' + str(msg.get_type()) + ' ' + msg.get_path() + '/' + msg.get_member()) + callback(msg) + #_log.debug('DONE') + return HANDLER_RESULT_HANDLED + + msg_filter = self._add_message_filter(dispatch) + match = self._add_match(filter_rule) + + def dispose(): + match.dispose() + msg_filter.dispose() + + return self.create_dependent_disposable(dispose) + + def _add_message_callback_fork(self, callback, filter_rule): + # type: (Callable[[Message], NoReturn], AnyStr) -> Disposable + + forked = self.fork() + _log.debug('forked connection ' + forked.bus_id) + + def dispatch(_, msg): + # type: (Connection, Message) -> int + + # _log.debug('got message type=' + str(msg.get_type()) + ' ' + msg.get_path() + '/' + msg.get_member()) + callback(msg) + return HANDLER_RESULT_HANDLED + + forked._add_message_filter(dispatch) + forked._add_match(filter_rule) + + return self.create_dependent_disposable(forked) + + def _add_message_filter(self, callback): + # type: (Callable[[Connection, Message], int]) -> Disposable + + _log.debug('added filter on ' + self.bus_id) + self._connection.add_message_filter(callback) + + def dispose(): + self._connection.remove_message_filter(callback) + + return self.create_dependent_disposable(dispose, 'message filter on ' + self.bus_id) + + def _add_match(self, filter_rule): + # type: (AnyStr) -> Disposable + + self.call_daemon_method('AddMatch', dbus_string(filter_rule)) + + _log.debug('added match_rule: ' + filter_rule) + + def dispose(): + self.call_daemon_method('RemoveMatch', dbus_string(filter_rule)) + + return self.create_dependent_disposable(dispose, 'Match ' + filter_rule) diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_connection.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d405d74d56975abcc359c8c62eb9aad044706b62 GIT binary patch literal 8378 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 NoReturn + + self._dbus = DbusConnection(connection_type_or_address) + # self._dbus.add_message_callback(lambda _: None, 'type=method_call', fork=False) # sink method calls, TODO + + self._name_changed = self.subscribe_to_signal_message( + self._on_name_owner_changed, + sender_id=BUS_DAEMON_NAME, + object_path=BUS_DAEMON_PATH, + interface=BUS_DAEMON_IFACE, + member='NameOwnerChanged') + + self._services = self._init_services() + + @property + def bus_id(self): + # type: () -> AnyStr + return self._dbus.bus_id + + @property + def services(self): + # type: () -> Iterable[ServiceInfo] + return self._services.itervalues() + + def subscribe_to_signal_message( + self, + callback, + sender_id='*', + sender_name='*', + object_path='*', + interface='*', + member='*', + signature='*'): + # type: (Callable[[MatchedMessage], None], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr]) -> Disposable + + message_filter = MessageFilter( + message_type='signal', + sender_id=sender_id, + sender_name=sender_name, + object_path=object_path, + interface=interface, + member=member, + signature=signature) + + def dispatch(msg): + # type: (Message) -> NoReturn + + resolved_msg = self._resolve_message(msg) + matched = message_filter.match_message(resolved_msg) + + if matched is not None: + callback(matched) + + return self._dbus.add_message_callback(dispatch, message_filter.filter_rule) + + def subscribe_to_method_call_message( + self, + callback, + sender_id='*', + sender_name='*', + object_path='*', + interface='*', + member='*', + signature='*', + destination_id='*', + destination_name='*'): + # type: (Callable[[MatchedMessage], Any], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[AnyStr], Optional[bool]) -> Disposable + + message_filter = MessageFilter( + message_type='method_call', + sender_id=sender_id, + sender_name=sender_name, + object_path=object_path, + interface=interface, + member=member, + signature=signature, + destination_id=destination_id, + destination_name=destination_name) # TODO: eavesdrop logic + + def dispatch(msg): + # type: (Message) -> NoReturn + + if msg.get_type() != 1: + return + + resolved_msg = self._resolve_message(msg) + matched = message_filter.match_message(resolved_msg) + + if matched is None: + reply = ErrorMessage(msg, 'com.victronenergy.method_call_refused', 'refused') + else: + try: + result = callback(matched) + except Exception as e: + # _log.debug('method_call threw an exception ' + str(e)) + # traceback.print_exc() + reply = matched.create_error_reply(e) + else: + reply = matched.create_method_reply(result) + + self._dbus.send_message(reply) + + return self._dbus.add_message_callback(dispatch, message_filter.filter_rule, fork=False) + + def request_name(self, service_name): + # type: (AnyStr) -> Disposable + + return self._dbus.request_name(service_name) + + def call_method(self, service_name, object_path, interface, member, *args): + # type: (AnyStr, AnyStr, AnyStr, AnyStr, Iterable[DbusType]) -> List[Any] + + return self._dbus.call_method(service_name, object_path, interface, member, *args) + + def broadcast_signal(self, object_path, interface, member, *args): + # type: (AnyStr, AnyStr, AnyStr, List[DbusType]) -> NoReturn + + self._dbus.broadcast_signal(object_path, interface, member, *args) + + def get_service_names_of_id(self, service_id): + # type: (str) -> List[AnyStr] + + if service_id is None: + return [] + + return [ + s.name + for s in self.services + if s.id == service_id + ] + + def get_id_for_service_name(self, service_name): + # type: (AnyStr) -> Optional[AnyStr] + + return next((s.id for s in self.services if s.name == service_name), None) + + def exists_service_with_name(self, service_name): + # type: (AnyStr) -> bool + + return self.get_id_for_service_name(service_name) is not None + + def _resolve_message(self, msg): + # type: (Message) -> ResolvedMessage + + sender_id, sender_names = self._resolve_name(msg.get_sender()) + destination_id, destination_names = self._resolve_name(msg.get_destination()) + + return ResolvedMessage(msg, sender_id, sender_names, destination_id, destination_names) + + # noinspection PyShadowingBuiltins + def _resolve_name(self, name): + # type: (str) -> (str, List[str]) + + if name is None: + id = NONE + names = [] + elif name.startswith(':'): + id = name + names = self.get_service_names_of_id(name) + else: + id = self.get_id_for_service_name(name) + names = [name] + + return id, names + + def _on_name_owner_changed(self, msg): + # type: (MatchedMessage) -> NoReturn + + (name, old_id, new_id) = msg.arguments + + old_id = old_id.strip() + new_id = new_id.strip() + name = name.strip() + + if name.startswith(':'): + name = None + + added = old_id == '' and new_id != '' + changed = old_id != '' and new_id != '' + removed = old_id != '' and new_id == '' + + # 'changed' is dispatched as 'removed' followed by 'added' + + if removed or changed: + self._services.pop(old_id, None) + + if added or changed: + service = self._create_service(name, new_id) + self._services[new_id] = service + + # noinspection PyShadowingBuiltins + def _init_services(self): + # type: () -> Dict[str, ServiceInfo] + + services = dict() + + names_and_ids = self._dbus.get_ids_and_service_names() + + ids = set([i for i in names_and_ids if i.startswith(':')]) + names = [n for n in names_and_ids if not n.startswith(':')] + + for service_name in names: + service = self._create_service(service_name) + services[service.id] = service + ids.discard(service.id) + + self._services = services # UGLY, because _create_service below references it. + + for id in ids: + services[id] = self._create_service(id=id) + + return services + + def _search_service_name_by_pid(self, pid): + # type: (int) -> Optional[AnyStr] + return next((s.name for s in self.services if s.pid == pid and s.name != NONE), NONE) + + # noinspection PyShadowingBuiltins + def _create_service(self, name=None, id=None): + # type: (Optional[AnyStr], Optional[AnyStr]) -> ServiceInfo + + id = id or self._dbus.get_id_of_service(name) + pid = self._dbus.get_pid_of_service(id) + proc = self._get_process_name_of_pid(pid) + cmd = self._get_commandline_of_pid(pid) + name = name or self._search_service_name_by_pid(pid) + + return ServiceInfo(name, id, pid, proc, cmd) + + # noinspection PyBroadException + @staticmethod + def _get_process_name_of_pid(service_pid): + # type: (int) -> str + + try: + with open('/proc/{0}/comm'.format(service_pid)) as proc: + return proc.read().replace('\0', ' ').rstrip() + except Exception as _: + return '' + + # noinspection PyBroadException + @staticmethod + def _get_commandline_of_pid(service_pid): + # type: (int) -> str + + try: + with open('/proc/{0}/cmdline'.format(service_pid)) as proc: + return proc.read().replace('\0', ' ').rstrip() + except Exception as _: + return '' diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_daemon.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/dbus_daemon.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54af7f757aac362a4ed5e0c303f1fb283eb762ef GIT binary patch literal 9390 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 str + + if message_type == 1: + return 'method_call' + if message_type == 2: + return 'method_return' + if message_type == 3: + return 'error' + if message_type == 4: + return 'signal' + + return 'invalid' + + +class DBusMessage(Record): + + def __init__(self, msg, sender_id, destination_id): + # type: (Message, str, str) -> NoReturn + + self.sender_id = sender_id + self.destination_id = destination_id + self._msg = msg + + @property + def expects_reply(self): + # type: () -> bool + return not self._msg.get_no_reply() + + @property + def message_type(self): + # type: () -> int + return int(self._msg.get_type()) + + @property + def reply_serial(self): + # type: () -> int + return int(self._msg.get_reply_serial()) + + @property + def object_path(self): + # type: () -> str + return str(self._msg.get_path()) + + @property + def interface(self): + # type: () -> str + return str(self._msg.get_interface()) + + @property + def arguments(self): + # type: () -> List[Any] + return self._msg.get_args_list(utf8_strings=True) + + @property + def signature(self): + # type: () -> str + return str(self._msg.get_signature()) + + @property + def serial(self): + # type: () -> int + return int(self._msg.get_serial()) + + @property + def member(self): + # type: () -> str + return str(self._msg.get_member()) + + def create_method_reply(self, *args): + # type: (List[any]) -> MethodReturnMessage + + if self.message_type != MessageType.method_call: + raise Exception('cannot create a reply for a message that is not a method call') + + reply = MethodReturnMessage(self._msg) + + for arg in args: + reply.append(arg) + + return reply + + def create_error_reply(self, exception): + # type: (Exception) -> ErrorMessage + + if self.message_type != MessageType.method_call: + raise Exception('cannot create an error reply for a message that is not a method call') + + return ErrorMessage(self._msg, 'com.victronenergy.' + exception.__class__.__name__, exception.message) # TODO prefix + + +class ResolvedMessage(DBusMessage): + + def __init__(self, msg, sender_id, sender_names, destination_id, destination_names): + # type: (Message, str, List[str], str, List[str]) -> NoReturn + + super(ResolvedMessage, self).__init__(msg, sender_id, destination_id) + + self.sender_names = sender_names + self.destination_names = destination_names + + +class MatchedMessage(DBusMessage): + + def __init__(self, resolved_msg, sender_name, destination_name): + # type: (ResolvedMessage, str, str) -> NoReturn + + super(MatchedMessage, self).__init__(resolved_msg._msg, resolved_msg.sender_id, resolved_msg.destination_id) + + self.sender_name = sender_name + self.destination_name = destination_name + + +class MessageFilter(Record): + + def __init__( + self, + message_type='*', + sender_id='*', + sender_name='*', + object_path='*', + interface='*', + member='*', + signature='*', + destination_id='*', + destination_name='*', + eavesdrop=False): + + # type: (Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[bool]) -> NoReturn + + self.signature = signature + self.message_type = message_type + + self.member = member + self.interface = interface + self.object_path = object_path + + self.sender_id = sender_id + self.sender_name = sender_name + self.destination_id = destination_id + self.destination_name = destination_name + + self.eavesdrop = eavesdrop + + @staticmethod + def create_filter_rule( + message_type='*', + sender_id='*', + sender_name='*', + object_path='*', + interface='*', + member='*', + destination_id='*', + eavesdrop=False): + # type: (Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],Optional[AnyStr],bool) -> AnyStr + + rules = [] + + def rule(key, value): + if '*' not in value and '?' not in value: + rules.append("%s='%s'" % (key, value)) + + rule('type', message_type) + rule('sender', sender_id if sender_name == '*' and sender_id != '*' else sender_name) + rule('destination', destination_id) + rule('eavesdrop', 'true' if eavesdrop else 'false') + rule('path', object_path) # TODO: endswith *, object namespace + rule('interface', interface) + rule('member', member) + + return ','.join(rules) + + @property + def filter_rule(self): + # type: () -> AnyStr + + return self.create_filter_rule( + message_type=self.message_type, + sender_id=self.sender_id, + sender_name=self.sender_name, + object_path=self.object_path, + interface=self.interface, + member=self.member, + destination_id=self.destination_id, + eavesdrop=self.eavesdrop) + + @staticmethod + def _get_matching_name(names, name_filter): + # type: (Iterable[AnyStr], AnyStr) -> Optional[AnyStr] + + matching_names = ( + name + for name + in names + if glob(name, name_filter) + ) + + return next(matching_names, None) + + def match_message(self, msg): + # type: (ResolvedMessage) -> Optional[MatchedMessage] + + match = \ + glob(msg.object_path, self.object_path) and \ + glob(msg.interface or '', self.interface) and \ + glob(msg.member, self.member) and \ + glob(msg.signature, self.signature) and \ + glob(msg.sender_id, self.sender_id) and \ + glob(msg.destination_id or '', self.destination_id) + + if not match: + return None + + sender_name = self._get_matching_name(msg.sender_names, self.sender_name) + if sender_name is None and self.sender_name != '*': # sender might not have a well known name + return None + + destination_name = self._get_matching_name(msg.destination_names, self.destination_name) + if destination_name is None and self.destination_name != '*': + return None + + return MatchedMessage(msg, sender_name, destination_name) + + +class DBusException(Exception): + + def __init__(self, message): + super(Exception, self).__init__(message) + + @classmethod + def raise_if_error_reply(cls, reply): + # type: (Message) -> Message + + if isinstance(reply, ErrorMessage): + raise DBusException(reply.get_error_name()) + else: + return reply diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/message_types.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/message_types.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27ff707ae96fd851c309909829613f6e3bb4e0d1 GIT binary patch literal 9236 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 a*Rr?lpF>&<>fWotsWYc;o;rfx;eP<3BYtQA literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.py new file mode 100644 index 000000000..76404b25c --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.py @@ -0,0 +1,177 @@ + +from logging import getLogger + +import dbus + +from python_libs.ie_dbus.private.dbus_types import dbus_variant, dbus_string +from python_libs.ie_dbus.private.dbus_daemon import DBusDaemon +from python_libs.ie_dbus.private.message_types import MatchedMessage +from python_libs.ie_dbus.private.ve_constants import GET_TEXT, INTERFACE_BUS_ITEM, PROPERTIES_CHANGED, GET_VALUE, SET_VALUE +from python_libs.ie_utils.mixins import Disposable, Record + +_log = getLogger(__name__) + + +# noinspection PyUnreachableCode +if False: + from typing import Optional, AnyStr, NoReturn, Dict, Any + from python_libs.ie_dbus.private.dbus_types import DbusVariant, DbusString, DbusVariantDict, DbusType + + +class OwnProperty(Record): + + def __init__(self, value, unit='', writable=False): + + str_value = round(value, 2) if isinstance(value, float) else value + + self.text = unicode(str_value) + unit + self.value = value + self.unit = unit + self.writable = writable + + @property + def dbus_dict(self): + # type: () -> dbus.Dictionary + d = { + dbus.String('Text'): dbus_variant(self.text), + dbus.String('Value'): dbus_variant(self.value) + } + return dbus.Dictionary(d, signature='sv') + + @property + def dbus_value(self): + # type: () -> DbusVariant + return dbus_variant(self.value) + + @property + def dbus_text(self): + # type: () -> DbusString + return dbus_string(self.text) + + def update_value(self, value): + # type: (any) -> OwnProperty + return OwnProperty(value, self.unit, self.writable) + + def __iter__(self): + yield self.value + yield self.text + + +class OwnProperties(Disposable): + + _own_properties = None # type: Dict[AnyStr, OwnProperty] + + # noinspection PyProtectedMember + def __init__(self, daemon): + # type: (DBusDaemon) -> NoReturn + + self._daemon = daemon + self._own_properties = dict() + self._method_call_subs = self._daemon.subscribe_to_method_call_message(self._on_method_called) # no filter whatsoever + + def get(self, object_path): + # type: (AnyStr) -> OwnProperty + return self._own_properties[object_path] + + def set(self, object_path, value, unit='', writable=False): + # type: (AnyStr, any, Optional[AnyStr], Optional[bool]) -> bool + + prop = OwnProperty(value, unit, writable) + + if object_path in self._own_properties: + if self._own_properties[object_path] == prop: + return False + + self._own_properties[object_path] = prop + # object_path, interface, member, *args): + self._daemon.broadcast_signal( + object_path, + INTERFACE_BUS_ITEM, + PROPERTIES_CHANGED, + prop.dbus_dict) + + return True + + def _on_method_called(self, message): + # type: (MatchedMessage) -> Any + + # _log.info(str(message.sender_name) + '(' + str(message.sender_id) + ') asked ' + message.member + ' ' + message.object_path) + + if message.member == GET_VALUE: + return self._on_get_value_called(message) + elif message.member == GET_TEXT: + return self._on_get_text_called(message) + elif message.member == SET_VALUE: + return self._on_set_value_called(message) + + def _on_set_value_called(self, message): + # type: (MatchedMessage) -> bool + + path = message.object_path + + if path not in self._own_properties: + raise Exception('property ' + path + ' does not exist') + + prop = self._own_properties[path] + if not prop.writable: + raise Exception('property ' + path + ' is read-only') + + value = message.arguments[0] + + if prop.value == value: + return False + + prop = prop.update_value(value) + self._own_properties[path] = prop + + # object_path, interface, member, *args): + self._daemon.broadcast_signal( + path, + INTERFACE_BUS_ITEM, + PROPERTIES_CHANGED, + prop.dbus_dict) + + return True + + def _on_get_value_called(self, message): + # type: (MatchedMessage) -> DbusType + + path = message.object_path + + if path in self._own_properties: + return self._own_properties[path].dbus_value + + if path.endswith('/'): # "Tree Export" + values = { + dbus.String(k.lstrip('/')): dbus_variant(p.value) + for (k, p) + in self._own_properties.iteritems() + if k.startswith(path) + } + + return dbus.Dictionary(values, signature='sv', variant_level=1) # variant for tree export !! + + raise Exception('property ' + path + ' does not exist') + + def _on_get_text_called(self, message): + # type: (MatchedMessage) -> DbusType + + path = message.object_path + + if path in self._own_properties: + return self._own_properties[message.object_path].dbus_text + + if path.endswith('/'): # "Tree Export" + values = { + dbus.String(k.lstrip('/')): dbus.String(p.text) + for (k, p) + in self._own_properties.iteritems() + if k.startswith(path) + } + return dbus.Dictionary(values, signature='ss', variant_level=1) # variant for tree export !! + + raise Exception('property ' + path + ' does not exist') + + def __contains__(self, object_path): + # type: (AnyStr) -> bool + return object_path in self._own_properties diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/own_properties.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22240575179c62c0bf20865597ce732ad7faf33b GIT binary patch literal 6648 zcmcgw-E$My6+gQx$(FG(V1jWl;AHY4+Qd$#9hwa1=0#6U^|pFlvb!?k))Ai6=_u@t4gctXH+DuNj4#^3E!?t zIw@IQT6N#9NjfFj32B{>Y+72=lFdkKMzWLAIw{$#v}XOh2}w^$c3N7eB|9UnGZNT9 z?3_FYGiSxVCdJ>Ci%Ch(Nj5L7dCAU8>%3$O(pr%0b!ojW*#&7`@cY%3+eK+DN_J6N z7bUwStxJ+!meyqna-3~S>=}6&NcV(%Bl2A27p*1fPK!OQy)%csZ%_`+#z_b&`6vDw z5SF+)$hLJ~^tQKcvEB&THx1I=+AZU~c9FDm=S~beN>?QLwqH4g&efZ{2Dx&WPCo^*O$t7>resUO~Jr@Ouj!%p?}q(~f<>@~}Sl{S577Ev)5NiKmXMgUZx z7aKItBTC9O)K+?Zx02*}Z_nno*xp|$Y}(%caHYTR9`*7#O}5IF#KuJbO20_<+Rm=@ zp5$@g>oBq9GN$ruah!myIG#tn_E(reJDGT ziae03g8`NHU?AkAtIzyI2psCcbj}Exdj544*Tzs>n<_4vbD3=CZNS=MC$#n&Y7*qh zTmaE!JfK>G$Fho0wM7)uR*HR9rn$@&T-!YsNs?dk5_j?5B{WLPR-!0a2o^&XkT>)^BT#ay^V09C^{C2Ga~KwyRb%Y)jvYp%Y1|pGT34N zSr9TVvKjbeY%&WN9zmtPh&lfc(G$(^6wVfL{1Y-qQA;oxGzg-BBgxyDjbk;xIL>YREnsft=STgR_DTdh|d zO>z|F;3265{UcazqLg2QSRdz*Qo)Uh1}X$O1U&Rt8SQM19**%3!9>FwLz<6m^FU~CDNjL7a(-d{hgxqOen2(RyuJ0Y6USgk- zO{tUiER2+UgnA{mZlIJrutfD>I(S)-%>O7V73adxkZ5q`D##eOoY~vju^kup+wReE zu&g;6Qr0_o)xZZB`mGKPhNs|D_5TPJdNU~8r!tW|(@4s*1oF5h#kD~|222(Xs`41w z&^3nUW{!23s}nJJowbNyK?9}LY`M7fEf>ELRahkBL!oKK=Wg=aSCLs=(W3@ zcIjdb(W%kE%mhdc+Q;((Ig9o)@|tTTizJ8sR;SSu9Gh@U+z0T=l2VoEOgJ0N1ry;+ zSPvroO@%K*6prLY|M*)L&qRq&61j9CaEbc zMU&ZV%NAx5RYzwZBXa{Fd}vUcvt2_JxVf=W2EXAT)pWQN$9-Yx7@Yaaf%zVXw9QB4 z4@JGIRA+;kz!w!Tk8lX)zoFrU_zXhzJ`9qckYa^^g(qd24nH~%k^VXm0@$NBl!a%& zKhPV|6^~7h#wSC0aL5%*hGH-o=&z`@4v6+9yCwz~0>9ni_G%oHvQsAu2xGYTaQ3t} zju{vSPqAK7>cbGWRML+wb$hm4%6o3fK21tjGL|eQNWx}Ts!^v>!{fmWpn z{@T-y)mz;t=WiF=yP3^h=?zA0Pa`7@(EOC-MT0XRb93^hxuRJ^&07~-m-&SKG`(Zd z{329*n-6Djc>qPdf-(bd{DQ$Tp7I2ImcIe!JUSPU zy*2SXylE7f{0?_8TqZ*K?Qju8Ag-;z4WI?*wgc+}qV&3f0E}zp^B`;@8Y1>$vw1|` z0m#%ksn&J9Bdl~76NYTk_{zH=P$IDM%TMI%P;j9ke$`W8FTAs;(PiL;%rxO#fgJS= z03fzU5B~oc%CCSypC@xmb&;Ae;c8j9@<{@XX+WaJ%sV9Sf@t_maT`qfn!D z_Oa5w-}DYQMUkV6t$HiG&WlZ_m-RpX12?aL;EEWUw@@)}gJ}LV_|OukcF(2>&+%$w z(Hr*2Dl{F`gwp0+m5161Q%{tv^fKWQ&212$$!m;J*b`TJohelcBcU9fJPdQeZ&6AH zLy!5B;cPe^&Q%tCNEt#$5zeXKjqoc&*QPLf8RY$A0=XgdIxk_I!E%7{o8O0V0Gu$7 z6y{*v+X0;Je};J#TbrMe970;JE#`Cdc^&fY<{rs9`_u^Tu!eGcX#NW~e*w*ET4ibO zW8{B?@OxNl9+3Q;M32m4d>3Mk!GNA|bmSw%>MiX4CzLU)p7vOc23Q?lH#k!R7qAL9 zhYSc#c$g2WHW;)kZRf=b%Q-Pg!GbVpd6Lw}!_w8z3XrwRVIu=dGShl>9yR^tmS5#Ukc2$ivKc*}!tc*}!tc+2{^#^IxE>7xXt;us)axyawW z3_X&&@`?q6R5A81qIEdR== NoReturn + + self._daemon = daemon + self._remote_properties = dict() + + # noinspection PyBroadException + def available_properties(self, service_name): + # type: (unicode) -> List[unicode] + + if not self._daemon.exists_service_with_name(service_name): + return [] + + try: + paths = self._call_remote(service_name=service_name, object_path='/', member=GET_TEXT)[0].keys() + except Exception as _: + return [] + else: + return ['/' + str(path) for path in paths] + + def exists(self, combined_path): + # type: (AnyStr) -> bool + + service_name, object_path, combined_path = self._parse_combined_path(combined_path) + return object_path in self.available_properties(service_name) + + def get(self, combined_path): + # type: (AnyStr) -> RemoteProperty + + service_name, object_path, combined_path = self._parse_combined_path(combined_path) + + if combined_path in self._remote_properties: + cached = self._remote_properties[combined_path] + + # a cached prop might have an unknown text, because its value has been written before, + # but it has never read or updated via property-changed + + if cached.text != _UNKNOWN_TEXT: + return cached + + text = self._get_text(service_name, object_path) + self._remote_properties[combined_path] = RemoteProperty(cached.value, text) + + return self._remote_properties[combined_path] + + prop = self._get_property(service_name, object_path) + self._remote_properties[combined_path] = prop + self._subscribe_to_property_changed(service_name, object_path) + + return prop + + def set(self, combined_path, value): + # type: (AnyStr, any) -> bool + + service_name, object_path, combined_path = self._parse_combined_path(combined_path) + + if combined_path in self._remote_properties: + if self._remote_properties[combined_path].value == value: + return False # property already has the requested value => nothing to do + else: + self._subscribe_to_property_changed(service_name, object_path) + + result = self._call_remote(service_name, object_path, SET_VALUE, dbus_variant(value))[0] + + if result != 0: + raise Exception(service_name + ' refused to set value of ' + object_path + ' to ' + str(value)) + + self._remote_properties[combined_path] = RemoteProperty(value, _UNKNOWN_TEXT) + + return True + + def _subscribe_to_property_changed(self, service_name, object_path): + # type: (unicode, unicode) -> NoReturn + + def callback(msg): + # type: (MatchedMessage) -> NoReturn + prop = RemoteProperty.from_dbus_dict(msg.arguments[0]) + key = msg.sender_name+msg.object_path + self._remote_properties[key] = prop + + signal = self._daemon.subscribe_to_signal_message( + callback=callback, + sender_name=service_name, + object_path=object_path, + interface=INTERFACE_BUS_ITEM, # TODO: <- this could be removed to make it more robust, in theory + member=PROPERTIES_CHANGED) # TODO: OTOH, don't fix if it is not broken + + self.chain_disposable(signal, 'signal subscription on ' + self._daemon.bus_id + ' ' + service_name + object_path) + + def _get_value(self, service_name, object_path): + # type: (unicode, unicode) -> any + + return self._call_remote(service_name, object_path, GET_VALUE)[0] + + def _get_text(self, service_name, object_path): + # type: (unicode, unicode) -> unicode + + result = self._call_remote(service_name, object_path, GET_TEXT)[0] + return unicode(result) + + def _get_property(self, service_name, object_path): + # type: (unicode, unicode) -> RemoteProperty + + value = self._get_value(service_name, object_path) + text = self._get_text(service_name, object_path) + + return RemoteProperty(value, text) + + def _call_remote(self, service_name, object_path, member, *args): + # type: (unicode, unicode, unicode, List[Any]) -> List[Any] + + return self._daemon.call_method(service_name, object_path, INTERFACE_BUS_ITEM, member, *args) + + def _parse_combined_path(self, combined_path): + # type: (str) -> (unicode,unicode,unicode) + + service_name, object_path = combined_path.lstrip('/').split('/', 1) + + if service_name == '': + raise Exception('Failed to parse service name. \ncombined_path must be of the form "service_name/path/to/property"') + if object_path == '': + raise Exception('Failed to parse object path. \ncombined_path must be of the form "service_name/path/to/property"') + + service_name = service_name if service_name.startswith(SERVICE_PREFIX) else SERVICE_PREFIX + service_name + + if not self._daemon.exists_service_with_name(service_name): + raise Exception('there is no service with the name "' + service_name + '" on the bus') + + object_path = '/' + object_path + + return unicode(service_name), unicode(object_path), unicode(service_name + object_path) diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/remote_properties.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/remote_properties.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a710520f00a24c51e0299ff9efe912896cbfe1b GIT binary patch literal 6523 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 any + + path = '/' + path.lstrip('/') + path = path if path.startswith(SETTINGS_PREFIX) else SETTINGS_PREFIX + path + return path + + +class Settings(Record): + + # noinspection PyProtectedMember + def __init__(self, daemon, remote_properties): + # type: (DBusDaemon, RemoteProperties) -> NoReturn + + self._daemon = daemon + self._remote_properties = remote_properties + + # noinspection PyShadowingBuiltins + + def add_setting(self, path, default_value, min=None, max=None, silent=False): + # type: (AnyStr, Union[unicode, int, float], Union[int, float, None], Union[int, float, None], Optional[bool]) -> NoReturn + + path = prepend_settings_prefix(path) + + if isinstance(default_value, int): + item_type = 'i' + elif isinstance(default_value, float): + item_type = 'f' + elif isinstance(default_value, (str, unicode)): + item_type = 's' + else: + raise Exception('Unsupported Settings Type') + + reply = self._daemon.call_method( + SETTINGS_SERVICE, # service_name + '/', # object_path + SETTINGS_INTERFACE, # interface + 'AddSilentSetting' if silent else 'AddSetting', # member, + dbus_string(''), # "group", not used + dbus_string(path), + dbus_variant(default_value), + dbus_string(item_type), + dbus_int_variant(min or 0), + dbus_int_variant(max or 0)) + + if reply[0] != 0: + raise Exception('failed to add setting ' + path) + + def exists(self, path): + # type: (unicode) -> bool + + path = prepend_settings_prefix(path) + return path in self.available_settings + + def get(self, path): + # type: (unicode) -> Union[unicode, int, float] + + path = prepend_settings_prefix(path) + return self._remote_properties.get(SETTINGS_SERVICE + path).value + + def set(self, path, value): + # type: (unicode, Union[unicode, int, float]) -> NoReturn + + path = prepend_settings_prefix(path) + self._remote_properties.set(SETTINGS_SERVICE + path, value) + + @property + def available_settings(self): + # type: () -> [unicode] + return self._remote_properties.available_properties(SETTINGS_SERVICE) + + def __contains__(self, path): + # type: (unicode) -> bool + return self.exists(path) diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/settings.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/settings.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee745a231dc80fe74c035fe7dc0c1766b1952da2 GIT binary patch literal 3664 zcmcImTTdHD6h7r#;fw=$IY2PWlFwzlm23N|!0S zNx@CZRw!7Z>=p&LC|jjqmAst&Y|;r)@D9Z-(k&W#6wkqOO!Q-Lo8o!uciJ3c@(+JH z9*=B`yB%$x7CSq-?9^bI=PrIZHleX)lJD51={(78xEGa4lv{g!YIKXv+v8&VGS#Pc zyjC`yY~bErXRFeKqKw^qj^|Gvjm?utXGQL8c)jAO)z8Xeq)VG<(*a`QSbt-Cd#m?t zKkRR)7h8`v>@|LJYTW8=Z>Y_OY+gKTex^1yw|?kUu?a$co+m|an>^|jO51ViCf|)L z+oRON&4>9x-?+LZx=wsJ%=_sCWNV%fnoZ(z!U$>V;f!awa< z?ACPw;XNr5I?&!e2D)KN*UYc&AL%`4-mC0UYb=PQX8Dz80i&n68sYg1G!>K)F!!X$g2nOzv zf8Be}lU(&DKOMk~4_tf1&Ai}FGZC48B#f+3+`yl%B3CC|NHrAI39qSB?8|@`RAtZv zcjFqysDL?&>tdfL)$ahL0g{h~1R{8Ile@H%Ih2?8C22iLwQcaKFcxYFO~O)W z#UZ}QE5u=RIuzU|>#wry{Bky4Fb0S(43j*uVR)Oz2F{S*@&?F0o}!W@=)ZYjC2mgB z5tEG*P!Y;isQdtlKB`mxXlnQAnC^!Z~==aI=c#e)5 zbjT!V(JHssK9zrp6Z|qRDgFgJ0*&MH?w}l89IqL-X03CDpQ{_($*&WW zMXRJ67i8MK0Od6 zk?z9e1R!z7rc&}gPt9HK$ZuB`y_Scz?6oTGO3U-*3yb?Mddrn1Z-8j0O*jvL&vM=s z{jNu^>r4xz$5QxyIjTol=ZvS)4+rPV4Q+d3%GoA3rz$_!T!>tQ=jw8H@VH?LOjzZaeWN`&PWqZ z$Fp%k8Y*%Hnt}$I6VPU{F8bPL)Mxzg(#gZe0?s#F zP9he}F#Mk9&fiK;?xu3J2*W(ebQp@XhGAC3V=mxO;Y75cO0USBcx^_h(W=WPg#d({ z0;TLT$^L{pt>(3NTXlRFYrW1Lwu$(se9h+D7C<8V z5qD>ePU%3i8;ye0YGskHsN_aQ_5FMLlin*aa+ literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.py new file mode 100644 index 000000000..b9e55df8e --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.py @@ -0,0 +1,11 @@ + +SERVICE_PREFIX = 'com.victronenergy.' +VE_SERVICE_FILTER = SERVICE_PREFIX + '*' +INTERFACE_BUS_ITEM = SERVICE_PREFIX + 'BusItem' +PROPERTIES_CHANGED = 'PropertiesChanged' +GET_VALUE = 'GetValue' +SET_VALUE = 'SetValue' +GET_TEXT = 'GetText' +SETTINGS_SERVICE = 'com.victronenergy.settings' +SETTINGS_INTERFACE = 'com.victronenergy.Settings' +SETTINGS_PREFIX = '/Settings' diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_dbus/private/ve_constants.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a9a532ec377151039c733c7a0f069fc3276e00c GIT binary patch literal 609 zcmZ`%+iJo<5FPKA((6m1Um$%j^u3g-T{U2>B&*22gy?QHpqr2#rG8AmvOmztTCGr; zC6jZ`%+8r*Q}{Zs7e3#^Is80%`aaWZ`V=I908j*Sa4T{aO_WgTxQ%;R-)8_h9|wbCkG?3$P(`5r42+Z*GeTH`h0 zQ5q*I#YmaXGSmwtu}W%B;UrvblqLRwWP2&Y)d!k8!hcPSLX7mn{FA0m`*e+VI|nvw z)QRE+1)q5nx5I*w$GYy+4me@KLG8K&&OFS{=MD*VTU6Q}`+>{ZtCdII+lYDGWq#1< zw}w6Tf|*s1@nF&#jG1Nrk=dgX<7~?D%KCHe4txH=(l({~y?8Xm%hTHZunm&nH<%7l zCr;3bw2rs?OK?)P3U?1qvcqMpgH<#$PNV`k+ZZQFqiu-F*{Wb3YlERi)1<;|#>aIm TH!Jm&{WSJ^%egwO^2z!Jfzy?K literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43a8e20d3221d4255cb8ae8f721aabb2a828be98 GIT binary patch literal 143 zcmZSn%*(|*H!U%l0SXv_v;z1(hWk`FZg NoReturn + + self.value = initial_value + self.length = length + + def update(self, value, length=None): + # type: (float, int) -> float + + if length is not None: + self.length = length + + self.value = (self.value * self.length + value) / (self.length + 1) + + _log.debug('real value: ' + str(value) + ', filtered value: ' + str(self.value)) + + return self.value + + +class DebounceFilter(object): + + def __init__(self, initial_state=None, max_inertia=10): + # type: (Optional[bool], Optional[int]) -> NoReturn + + self._max_inertia = max_inertia + self._inertia = max_inertia + self._state = initial_state + + def reset(self, state=None, max_inertia=None): + # type: (Optional[bool], Optional[int]) -> bool + + self._max_inertia = max_inertia or self._max_inertia + self._inertia = self._max_inertia + self._state = state or self._state + + _log.debug('debounce filter reset: state={0}, inertia={1}'.format(self._state, self._inertia)) + + return self._state + + def flip(self): + # type: () -> bool + self._state = not self._state + self._inertia = self._max_inertia + return self._state + + def update(self, new_state, max_inertia=None): + # type: (bool, int) -> bool + + if max_inertia is not None and max_inertia != self._max_inertia: + return self.reset(new_state, max_inertia) + + if new_state != self._state: + if self._inertia > 0: + self._inertia = self._inertia - 1 + else: + self.flip() + else: + self._inertia = min(self._inertia + 1, self._max_inertia) + + _log.debug('debounce filter update: state={0}, inertia={1}'.format(self._state, self._inertia)) + + return self._state diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/filters.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/filters.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a37e2cde0e79a0c35c51ccc106ea0170b8483c3b GIT binary patch literal 2634 zcmb_eTW=dh6h1p^J8|lekdh`96*cf;L8+;DKwOHrsZU^}iByRQX|;*R?mFvTvpWt% zZJ%0x2ETxx#}9z-JL5}==u={O$1`Wno;jEAJLCB8?XCFFQF$4{Zc|2)3>)%N>g#gchxIFeG8rXeGE<*O9*Xf+}FMEdr9yw)F z_|T2-@YJ_3JTvl4B*7%oiP}6;?+3wQS~*Yix$`Z|a#xJ~lOFB(28P<@BTE&(jUg*C zpQU-C`aK9lhx${@`sK{`v!W=^UE!+nMZa=+dhwt?yYNrSBFVFpx}UjZ?z6n^k2Li9 zJ~o-KB%$Rb>0n;-Sc%)wDj*6pAjXM+kd?ExRENvpB0*UZ^?^i*tRg6AZ)N8Z=PeT6-`=Wjsml8tqKuasIA?Oi-Iy&#MJk4KNO zg>YvbMZR)}9Re%}a}9m=!J1QR_CzvS@`d`Kks*^-q+v=2^+y;1b1mnBCEd!^&VxEk z%)_T2Kfl*qB<$hS2hX)Q_D#HI30BKqX;~}FG-+oVmDM!$mr_bDsWrQg+4ba4396pb z-hwlGQ776ncg-n#0iP9ax>$s?b?~#u;Sx1r<1%nUSgR0k7F)kr^FU?0^yi>1O+`yt_v;u<*$M3}_7o#{4c2JMDazbuzV4w-|7 z{Q8HAn!HY<;ts1vgW7r*T^UA$uHTT!CNH%~Tb1I4>LpagxOPJ;hSL)I@qNtxi?7h? zUJEbB+R5XaXG_gf(MHKI-%>}N2p_lm@CN4sVx z+KFzOZF2{}8}v4?Y1t(04hM~cRJR%jy}$M*y$hr4i#5+dlit|5(QXZT%163`>uikw zG8R+bs3MQj91LgQ@fS1hkSdm337)e8x?P@3+^N@F!19ze`gL}d{->Sz%W#E##q*WI OyKyVJZ94I-{eJ=X6A2Lj literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.py new file mode 100644 index 000000000..ae240be27 --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.py @@ -0,0 +1,30 @@ +from logging import getLogger +import traceback +import gobject + + +# noinspection PyUnreachableCode +if False: + from typing import Callable, NoReturn + +_log = getLogger(__name__) + + +def run_on_main_loop(update_action, update_period): + # type: (Callable[[],NoReturn], int) -> NoReturn + + main_loop = gobject.MainLoop() + + def update(*args, **kwargs): + try: + update_action() + return True + + except Exception as e: + _log.error(e.message) + traceback.print_exc() + main_loop.quit() + return False + + gobject.timeout_add(update_period, update) + main_loop.run() \ No newline at end of file diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/main_loop.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2eda9794320e30d6c6c24cf410b540c009347457 GIT binary patch literal 1069 zcmb_b&2G~`5T3D}pQfoAdO@7H`h>3>3PM5@A(b2)Bv3A9V{eTUd)KVj&`9MJ1mz)k zN*(}aCMfCy)bUPcc4oi%zS$)Fb215kU#T4aKLLHepw)dOQ_zc012hOr1mrk?I)G&$ zIr2kThM*&GuVp8MdH~H3mP2Spup9vz0F9u>up^8kC?fa{u$(Z^A|6na>MuPplIM@e zl-gg~vea%KGZ#N5^HSBdTGd*DxlOh2oyj>J-dWu_*8{8(b^yNse$tC@MO+bn0DKE1 z8^8_1O#r(9@Rq7Wu4ZWqX*icrFfNmOOskud*`C6!;39Qcyf;rF@(7y=xHo`=j6Pr} z!d*u!75xC#LxJWdBk_p;;)8Tzx$~Iy`iRUs*K1M#>N?l0udI>nthOcL&;*^c&X34z zbk`}GksNg@*DIB8Bx+q{e5S8+Ir_P;yd1qyb*E$2Bm2sgogY%P`6@0G(fdg=WTPrW zU2W@6DC*lnd7Y`88O5{#-7_)?rIX4S`$Ze=%59PpUuWxDyQJOvPnIC6RhLvc>wQ&s z$pQ5VrMdYY_>2K(WB@}P;W#+O8BXXE^Grl;X;*8Vd%5_x#O;!)9dov7wC#PSio&xv z*BcR;GTvSCt#*|y(g(CH3PfvqN}-hAgCOom?K6^n5X#f*#$+Iz24}}JjDM$|Wh!A(cGO#&26r^Tnixe?XQQ)#xls`PYy>B(oPo&fdER9n9stu|;3&=iI%U Pl9}NYr_m4}1=H{!6(QyB literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.py new file mode 100644 index 000000000..5e8bf3986 --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.py @@ -0,0 +1,115 @@ +from logging import getLogger +from _dbus_glib_bindings import DBusGMainLoop + +# noinspection PyUnreachableCode +if False: + from typing import Callable, NoReturn, List, AnyStr, Optional, Union + +_log = getLogger(__name__) + + +def nop(*_args): + pass + + +def memoize(fn): + + attr_name = '_memoized_' + fn.__name__ + + def _memoized(self): + if not hasattr(self, attr_name): + setattr(self, attr_name, fn(self)) + return getattr(self, attr_name) + + return _memoized + + +# noinspection PyAttributeOutsideInit +class Disposable(object): + + _dispose_actions = None # type: List[Callable[[],NoReturn]] + + def __enter__(self): + return self + + def __exit__(self, typ, value, tb): + self.dispose() + + def dispose(self): + # type: () -> NoReturn + + while self._dispose_actions: + dispose = self._dispose_actions.pop() + dispose() + + for k, v in self.__dict__.iteritems(): + if isinstance(v, Disposable) and v._dispose_actions: + _log.debug('disposing ' + type(self).__name__ + '.' + k) + v.dispose() + + def chain_disposable(self, dispose, message=None): + # type: (Union[Callable[[],None],Disposable], Optional[AnyStr]) -> NoReturn + + if self._dispose_actions is None: + self._dispose_actions = [] + + if isinstance(dispose, Disposable): + dispose = dispose.dispose + + if message is None: + self._dispose_actions.append(dispose) + return + + def dispose_with_log_msg(): + _log.debug('disposing ' + message) + dispose() + + # _log.debug('new disposable ' + message) + self._dispose_actions.append(dispose_with_log_msg) + + @classmethod + def create(cls, dispose_action, message=None): + # type: (Union[Callable[[],None],Disposable], Optional[AnyStr]) -> Disposable + + disposable = Disposable() + disposable.chain_disposable(dispose_action, message) + return disposable + + def create_dependent_disposable(self, dispose_action, message=None): + # type: (Union[Callable[[],None],Disposable], Optional[AnyStr]) -> Disposable + + disposable = Disposable.create(dispose_action, message) + self.chain_disposable(disposable) + return disposable + + +class Record(object): + + @memoize + def __str__(self): + return self.__class__.__name__ + ' ' + unicode(vars(self)) + + def __repr__(self): + return self.__str__() + + @memoize + def __hash__(self): + return self.__str__().__hash__() + + def __eq__(self, other): + # TODO: improve, iterable vars are not correctly handled + return str(other) == str(self) + + # make readonly + def __setattr__(self, key, value): + # type: (str, any) -> NoReturn + + if not key.startswith('_') and hasattr(self, key): # disallow redefining + raise ValueError(key + ' is read-only' + str(dir())) + + super(Record, self).__setattr__(key, value) + + +class RequiresMainLoop(object): + + main_loop = DBusGMainLoop(set_as_default=True) # initialized only once for all subclasses that need it diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/mixins.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63eb5b4c33cd8299f27edbbdf929a74577dd01a3 GIT binary patch literal 4808 zcmb_g-Etg96+SbwyVB|>lAT}$9EcKEV2dIap$Y@36d|@rZp>2EA~tM=>Ke_oRwK{O ztfps;ML9RfMZzTyz?1M;JOF&(>D`rVE?492`2}dw=2hy{K_tp}B3U+0`#T`;>$j1ditPPE-io6i{7T$^linQ23yEp<2Jbi&^f#=)! za^1odJf&R&IZ1um!ldNAYnMFSMn0H}cJ!`19#9+gD(7sKP8>EDIj+=>tC*0XvQ$`A zq`xKFiM|^JA=bG<&$LzrF}oNsH$Z|HDT&!3;lwJ!&=_;$b3d=5#M|-Bt~%yn;it7{ zsAibehxWXW<_|#}atoP+#7r1|FVXGL_hh>&>JUZBC<5tnYL+0O0>eoxeT38Wf&LP0QGQH}SJ4nPP;10Q>D+Y#bLVZ4{?xZEJ?D3^xur%iQbOhk1|?=Qu2Q`4^z$^1#3NB zm*%#Nb;JUVp;An2ggt^cz@K&mxQi(u(z(i>zP*z3$h5XzyXhyblu0$%Vnv0g=2<$4;mG z3bz&gy@;B>6zvJ!ZYO^HDaI91B)SplwQN)s8huz-&g_HHXbCbA71Qa|RrzaiVdz5h zBaqkB(1}$9bpZ{P~#E3-~- zU8Es`gju*iQnfq{Wqu5z`e|lmrEeLV3;TtvQ@u2;!Z^hWs$=O&=t~=CjZ0hiId@`L zI(?}3|Ek@=R0uoN5kVvBh(N^sAENe(f(cd!U*09R)KX#D{Dfob#Sg(2%ns1T>n6N(`_sq0}Nhjq1*~#sCHv&c*bKd?#2BC|F*W?N}`=LEmV!m>L$-Htq;TLKAQgwB$NjZ zg`d_2yzWsagz}&x9M=kg2KLHNE}o_5@#R z=!KM7AyG6HtaWGBX4r1wW=hGnj9eNUq9SsU1`v3w`E>p{Lj_Pwp$ibhg^GF!QX?)e zR3Toie~a|C@3PFInkdcIxONWeNV3I86?L|rU!ePi-v_GZAJ$cQKA_(fWKa+_t>>L+ zw7_EcJ!Vz<+oq`-^D);^@Vsc$Qhqjdjo}5Pu!D#WUrlg0mtB^)Pu;nB8za|ZRYN2> zL7T(nyU~8EI)p;)r#zX3h~|j-B{{~6`Gkb;UP__d3JzA?A7e|cQhV`Er=Os8wKO&E zY*sYRFTV6MtkZ|Kp&Wh>OFNXB?-qvQ_N6qNpxoApN1i z#{=PP!t$WxUl!20qU$PZKEzIEXd1!2csO8bph$_@X^gtupf4@0Zu6s=w`2UNz#bJ< zjyZ2sNP_m~X$f_Bi=(jSYfM_Bu|gdT*BDM4B4kkWJ#th`ovJU_N2e}p!}ZPRGnDW8 i)K%#EB;2>ZrtDf??MAyvFZw9nO8TAt_N|X^-Toh?tH+Q4 literal 0 HcmV?d00001 diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.py new file mode 100644 index 000000000..87177f9ef --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.py @@ -0,0 +1,44 @@ +from logging import getLogger +import re + +# noinspection PyUnreachableCode +if False: + from typing import Dict + +_log = getLogger(__name__) + + +def make2way(dic): + # type: (Dict) -> Dict + for k, v in dic.items(): + dic[v] = k + + return dic + + +def invert_dict(src_dic): + # type: (Dict) -> Dict + dic = dict() + + for k, v in src_dic.items(): + dic[v] = k + + return dic + + +def enum_file_name_of(path): + # type: (str) -> Dict[int,str] + + """ + This is kinda hacky, but it works :) + The enum file must contain a single enum however! + """ + + path = path[0:-1] if path.endswith('.pyc') else path + pattern = re.compile(r"^\s*(\w+)\s*=\s*(\d+)", re.M) + with open(path, "r") as f: + return { + int(m[1]): m[0] + for m + in pattern.findall(f.read()) + } diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/ie_utils/utils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa21dcd03468603c163d40bde854384752fadc5 GIT binary patch literal 1530 zcmb_c-%k@k5T3oGg{$B%0+B?L!;pB0p~mQg28jv8Cnd2p!Bp@(dfRsC-CnufQk%dN zzWd;_f2DtbezPUuAJEI~+|2Ck?0z%fp8sc|=Kt;_ZMu3qeBWZ2ZxAV7K^ajls3l0S z?NR1Y%OmMi`O-!Bl+93Hp;m>2CX#|=l`gSjmeLveLDZU~wBnS2(yCKxoTwSiViWZj zPb8q(0HQ1H`=Z;G<^IGs1K*x%+crn|80^@^Fq;tH*Xf+-gy<4cN6U2R(S@K3kIn^8 zoO`r=xVCuD1(q z%OcBUSs#pTuh4O(j!j+3cxY8->P~K9Y7VROq%WV3lJPC*rV2scin#4LNDSv1(hC^Y zfdPsuY~p`xfafzRhUWv@GO&qjY}`D{$rwz7t%xnlWj19yhP%O78|-ax5vB%{p(%YP zOUt|1MVN-6lNa1z<-PeVyBKBwG3gyR2D+3F*#s?8pVl-`5?$czf==L?af(luuD`&@ zp%pfNu5-I5v_dIPDqNlu5D;Y)w1?3=NN4HeXNYFRM>1a^YQd+TG9li+(rFU*l6HUm zG&~;KP}y))lzkJv-ry)1N=@14~-4E^@-A95*nqu@Vfb4F_M74RtpSQ7p-qA zd?)laOVGX^nkUiWXmbPKH%?DCHyqna#{}*dlcR}Y9z$R~U>>b9r2#D^S^+s&6SM+g zzdSmv0u(Qt?8OA0TpKR6($>vc4%m9;w#ui~aZ^LmZSHlu$Ok(cung}Lh<$gh9r2}3 z%}CjvJ3%QO5jh;Sbf@{?*jfyvc2ga0MV2{dDU)<^JGq>L1WA}WP^>JqTiux!7JCnw zxr;G(Io3%o= (3, 6): + time_isoformat = datetime_module.time.isoformat +else: + def time_isoformat(time, timespec='microseconds'): + assert isinstance(time, datetime_module.time) + if timespec != 'microseconds': + raise NotImplementedError + result = '{:02d}:{:02d}:{:02d}.{:06d}'.format( + time.hour, time.minute, time.second, time.microsecond + ) + assert len(result) == 15 + return result + + +def timedelta_format(timedelta): + time = (datetime_module.datetime.min + timedelta).time() + return time_isoformat(time, timespec='microseconds') + +def timedelta_parse(s): + hours, minutes, seconds, microseconds = map( + int, + s.replace('.', ':').split(':') + ) + return datetime_module.timedelta(hours=hours, minutes=minutes, + seconds=seconds, + microseconds=microseconds) + diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/tracer.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/tracer.py new file mode 100644 index 000000000..1316f18d2 --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/tracer.py @@ -0,0 +1,498 @@ +# Copyright 2019 Ram Rachum and collaborators. +# This program is distributed under the MIT license. + +import functools +import inspect +import opcode +import os +import sys +import re +import collections +import datetime as datetime_module +import itertools +import threading +import traceback + +from .variables import CommonVariable, Exploding, BaseVariable +from . import utils, pycompat +if pycompat.PY2: + from io import open + + +ipython_filename_pattern = re.compile('^$') + + +def get_local_reprs(frame, watch=(), custom_repr=(), max_length=None, normalize=False): + code = frame.f_code + vars_order = (code.co_varnames + code.co_cellvars + code.co_freevars + + tuple(frame.f_locals.keys())) + + result_items = [(key, utils.get_shortish_repr(value, custom_repr, + max_length, normalize)) + for key, value in frame.f_locals.items()] + result_items.sort(key=lambda key_value: vars_order.index(key_value[0])) + result = collections.OrderedDict(result_items) + + for variable in watch: + result.update(sorted(variable.items(frame, normalize))) + return result + + +class UnavailableSource(object): + def __getitem__(self, i): + return u'SOURCE IS UNAVAILABLE' + + +source_and_path_cache = {} + + +def get_path_and_source_from_frame(frame): + globs = frame.f_globals or {} + module_name = globs.get('__name__') + file_name = frame.f_code.co_filename + cache_key = (module_name, file_name) + try: + return source_and_path_cache[cache_key] + except KeyError: + pass + loader = globs.get('__loader__') + + source = None + if hasattr(loader, 'get_source'): + try: + source = loader.get_source(module_name) + except ImportError: + pass + if source is not None: + source = source.splitlines() + if source is None: + ipython_filename_match = ipython_filename_pattern.match(file_name) + if ipython_filename_match: + entry_number = int(ipython_filename_match.group(1)) + try: + import IPython + ipython_shell = IPython.get_ipython() + ((_, _, source_chunk),) = ipython_shell.history_manager. \ + get_range(0, entry_number, entry_number + 1) + source = source_chunk.splitlines() + except Exception: + pass + else: + try: + with open(file_name, 'rb') as fp: + source = fp.read().splitlines() + except utils.file_reading_errors: + pass + if not source: + # We used to check `if source is None` but I found a rare bug where it + # was empty, but not `None`, so now we check `if not source`. + source = UnavailableSource() + + # If we just read the source from a file, or if the loader did not + # apply tokenize.detect_encoding to decode the source into a + # string, then we should do that ourselves. + if isinstance(source[0], bytes): + encoding = 'utf-8' + for line in source[:2]: + # File coding may be specified. Match pattern from PEP-263 + # (https://www.python.org/dev/peps/pep-0263/) + match = re.search(br'coding[:=]\s*([-\w.]+)', line) + if match: + encoding = match.group(1).decode('ascii') + break + source = [pycompat.text_type(sline, encoding, 'replace') for sline in + source] + + result = (file_name, source) + source_and_path_cache[cache_key] = result + return result + + +def get_write_function(output, overwrite): + is_path = isinstance(output, (pycompat.PathLike, str)) + if overwrite and not is_path: + raise Exception('`overwrite=True` can only be used when writing ' + 'content to file.') + if output is None: + def write(s): + stderr = sys.stderr + try: + stderr.write(s) + except UnicodeEncodeError: + # God damn Python 2 + stderr.write(utils.shitcode(s)) + elif is_path: + return FileWriter(output, overwrite).write + elif callable(output): + write = output + else: + assert isinstance(output, utils.WritableStream) + + def write(s): + output.write(s) + return write + + +class FileWriter(object): + def __init__(self, path, overwrite): + self.path = pycompat.text_type(path) + self.overwrite = overwrite + + def write(self, s): + with open(self.path, 'w' if self.overwrite else 'a', + encoding='utf-8') as output_file: + output_file.write(s) + self.overwrite = False + + +thread_global = threading.local() +DISABLED = bool(os.getenv('PYSNOOPER_DISABLED', '')) + +class Tracer: + ''' + Snoop on the function, writing everything it's doing to stderr. + + This is useful for debugging. + + When you decorate a function with `@pysnooper.snoop()` + or wrap a block of code in `with pysnooper.snoop():`, you'll get a log of + every line that ran in the function and a play-by-play of every local + variable that changed. + + If stderr is not easily accessible for you, you can redirect the output to + a file:: + + @pysnooper.snoop('/my/log/file.log') + + See values of some expressions that aren't local variables:: + + @pysnooper.snoop(watch=('foo.bar', 'self.x["whatever"]')) + + Expand values to see all their attributes or items of lists/dictionaries: + + @pysnooper.snoop(watch_explode=('foo', 'self')) + + (see Advanced Usage in the README for more control) + + Show snoop lines for functions that your function calls:: + + @pysnooper.snoop(depth=2) + + Start all snoop lines with a prefix, to grep for them easily:: + + @pysnooper.snoop(prefix='ZZZ ') + + On multi-threaded apps identify which thread are snooped in output:: + + @pysnooper.snoop(thread_info=True) + + Customize how values are represented as strings:: + + @pysnooper.snoop(custom_repr=((type1, custom_repr_func1), + (condition2, custom_repr_func2), ...)) + + Variables and exceptions get truncated to 100 characters by default. You + can customize that: + + @pysnooper.snoop(max_variable_length=200) + + You can also use `max_variable_length=None` to never truncate them. + + Show timestamps relative to start time rather than wall time:: + + @pysnooper.snoop(relative_time=True) + + ''' + def __init__(self, output=None, watch=(), watch_explode=(), depth=1, + prefix='', overwrite=False, thread_info=False, custom_repr=(), + max_variable_length=100, normalize=False, relative_time=False): + self._write = get_write_function(output, overwrite) + + self.watch = [ + v if isinstance(v, BaseVariable) else CommonVariable(v) + for v in utils.ensure_tuple(watch) + ] + [ + v if isinstance(v, BaseVariable) else Exploding(v) + for v in utils.ensure_tuple(watch_explode) + ] + self.frame_to_local_reprs = {} + self.start_times = {} + self.depth = depth + self.prefix = prefix + self.thread_info = thread_info + self.thread_info_padding = 0 + assert self.depth >= 1 + self.target_codes = set() + self.target_frames = set() + self.thread_local = threading.local() + if len(custom_repr) == 2 and not all(isinstance(x, + pycompat.collections_abc.Iterable) for x in custom_repr): + custom_repr = (custom_repr,) + self.custom_repr = custom_repr + self.last_source_path = None + self.max_variable_length = max_variable_length + self.normalize = normalize + self.relative_time = relative_time + + def __call__(self, function_or_class): + if DISABLED: + return function_or_class + + if inspect.isclass(function_or_class): + return self._wrap_class(function_or_class) + else: + return self._wrap_function(function_or_class) + + def _wrap_class(self, cls): + for attr_name, attr in cls.__dict__.items(): + # Coroutines are functions, but snooping them is not supported + # at the moment + if pycompat.iscoroutinefunction(attr): + continue + + if inspect.isfunction(attr): + setattr(cls, attr_name, self._wrap_function(attr)) + return cls + + def _wrap_function(self, function): + self.target_codes.add(function.__code__) + + @functools.wraps(function) + def simple_wrapper(*args, **kwargs): + with self: + return function(*args, **kwargs) + + @functools.wraps(function) + def generator_wrapper(*args, **kwargs): + gen = function(*args, **kwargs) + method, incoming = gen.send, None + while True: + with self: + try: + outgoing = method(incoming) + except StopIteration: + return + try: + method, incoming = gen.send, (yield outgoing) + except Exception as e: + method, incoming = gen.throw, e + + if pycompat.iscoroutinefunction(function): + raise NotImplementedError + if pycompat.isasyncgenfunction(function): + raise NotImplementedError + elif inspect.isgeneratorfunction(function): + return generator_wrapper + else: + return simple_wrapper + + def write(self, s): + s = u'{self.prefix}{s}\n'.format(**locals()) + self._write(s) + + def __enter__(self): + if DISABLED: + return + calling_frame = inspect.currentframe().f_back + if not self._is_internal_frame(calling_frame): + calling_frame.f_trace = self.trace + self.target_frames.add(calling_frame) + + stack = self.thread_local.__dict__.setdefault( + 'original_trace_functions', [] + ) + stack.append(sys.gettrace()) + self.start_times[calling_frame] = datetime_module.datetime.now() + sys.settrace(self.trace) + + def __exit__(self, exc_type, exc_value, exc_traceback): + if DISABLED: + return + stack = self.thread_local.original_trace_functions + sys.settrace(stack.pop()) + calling_frame = inspect.currentframe().f_back + self.target_frames.discard(calling_frame) + self.frame_to_local_reprs.pop(calling_frame, None) + + ### Writing elapsed time: ############################################# + # # + start_time = self.start_times.pop(calling_frame) + duration = datetime_module.datetime.now() - start_time + elapsed_time_string = pycompat.timedelta_format(duration) + indent = ' ' * 4 * (thread_global.depth + 1) + self.write( + '{indent}Elapsed time: {elapsed_time_string}'.format(**locals()) + ) + # # + ### Finished writing elapsed time. #################################### + + def _is_internal_frame(self, frame): + return frame.f_code.co_filename == Tracer.__enter__.__code__.co_filename + + def set_thread_info_padding(self, thread_info): + current_thread_len = len(thread_info) + self.thread_info_padding = max(self.thread_info_padding, + current_thread_len) + return thread_info.ljust(self.thread_info_padding) + + def trace(self, frame, event, arg): + + ### Checking whether we should trace this line: ####################### + # # + # We should trace this line either if it's in the decorated function, + # or the user asked to go a few levels deeper and we're within that + # number of levels deeper. + + if not (frame.f_code in self.target_codes or frame in self.target_frames): + if self.depth == 1: + # We did the most common and quickest check above, because the + # trace function runs so incredibly often, therefore it's + # crucial to hyper-optimize it for the common case. + return None + elif self._is_internal_frame(frame): + return None + else: + _frame_candidate = frame + for i in range(1, self.depth): + _frame_candidate = _frame_candidate.f_back + if _frame_candidate is None: + return None + elif _frame_candidate.f_code in self.target_codes or _frame_candidate in self.target_frames: + break + else: + return None + + thread_global.__dict__.setdefault('depth', -1) + if event == 'call': + thread_global.depth += 1 + indent = ' ' * 4 * thread_global.depth + + # # + ### Finished checking whether we should trace this line. ############## + + ### Making timestamp: ################################################# + # # + if self.normalize: + timestamp = ' ' * 15 + elif self.relative_time: + try: + start_time = self.start_times[frame] + except KeyError: + start_time = self.start_times[frame] = \ + datetime_module.datetime.now() + duration = datetime_module.datetime.now() - start_time + timestamp = pycompat.timedelta_format(duration) + else: + timestamp = pycompat.time_isoformat( + datetime_module.datetime.now().time(), + timespec='microseconds' + ) + # # + ### Finished making timestamp. ######################################## + + line_no = frame.f_lineno + source_path, source = get_path_and_source_from_frame(frame) + source_path = source_path if not self.normalize else os.path.basename(source_path) + if self.last_source_path != source_path: + self.write(u'{indent}Source path:... {source_path}'. + format(**locals())) + self.last_source_path = source_path + source_line = source[line_no - 1] + thread_info = "" + if self.thread_info: + if self.normalize: + raise NotImplementedError("normalize is not supported with " + "thread_info") + current_thread = threading.current_thread() + thread_info = "{ident}-{name} ".format( + ident=current_thread.ident, name=current_thread.getName()) + thread_info = self.set_thread_info_padding(thread_info) + + ### Reporting newish and modified variables: ########################## + # # + old_local_reprs = self.frame_to_local_reprs.get(frame, {}) + self.frame_to_local_reprs[frame] = local_reprs = \ + get_local_reprs(frame, + watch=self.watch, custom_repr=self.custom_repr, + max_length=self.max_variable_length, + normalize=self.normalize, + ) + + newish_string = ('Starting var:.. ' if event == 'call' else + 'New var:....... ') + + for name, value_repr in local_reprs.items(): + if name not in old_local_reprs: + self.write('{indent}{newish_string}{name} = {value_repr}'.format( + **locals())) + elif old_local_reprs[name] != value_repr: + self.write('{indent}Modified var:.. {name} = {value_repr}'.format( + **locals())) + + # # + ### Finished newish and modified variables. ########################### + + + ### Dealing with misplaced function definition: ####################### + # # + if event == 'call' and source_line.lstrip().startswith('@'): + # If a function decorator is found, skip lines until an actual + # function definition is found. + for candidate_line_no in itertools.count(line_no): + try: + candidate_source_line = source[candidate_line_no - 1] + except IndexError: + # End of source file reached without finding a function + # definition. Fall back to original source line. + break + + if candidate_source_line.lstrip().startswith('def'): + # Found the def line! + line_no = candidate_line_no + source_line = candidate_source_line + break + # # + ### Finished dealing with misplaced function definition. ############## + + # If a call ends due to an exception, we still get a 'return' event + # with arg = None. This seems to be the only way to tell the difference + # https://stackoverflow.com/a/12800909/2482744 + code_byte = frame.f_code.co_code[frame.f_lasti] + if not isinstance(code_byte, int): + code_byte = ord(code_byte) + ended_by_exception = ( + event == 'return' + and arg is None + and (opcode.opname[code_byte] + not in ('RETURN_VALUE', 'YIELD_VALUE')) + ) + + if ended_by_exception: + self.write('{indent}Call ended by exception'. + format(**locals())) + else: + self.write(u'{indent}{timestamp} {thread_info}{event:9} ' + u'{line_no:4} {source_line}'.format(**locals())) + + if event == 'return': + self.frame_to_local_reprs.pop(frame, None) + self.start_times.pop(frame, None) + thread_global.depth -= 1 + + if not ended_by_exception: + return_value_repr = utils.get_shortish_repr(arg, + custom_repr=self.custom_repr, + max_length=self.max_variable_length, + normalize=self.normalize, + ) + self.write('{indent}Return value:.. {return_value_repr}'. + format(**locals())) + + if event == 'exception': + exception = '\n'.join(traceback.format_exception_only(*arg[:2])).strip() + if self.max_variable_length: + exception = utils.truncate(exception, self.max_variable_length) + self.write('{indent}{exception}'. + format(**locals())) + + return self.trace diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/utils.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/utils.py new file mode 100644 index 000000000..ff9b9e855 --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/utils.py @@ -0,0 +1,98 @@ +# Copyright 2019 Ram Rachum and collaborators. +# This program is distributed under the MIT license. + +import abc +import re + +import sys +from .pycompat import ABC, string_types, collections_abc + +def _check_methods(C, *methods): + mro = C.__mro__ + for method in methods: + for B in mro: + if method in B.__dict__: + if B.__dict__[method] is None: + return NotImplemented + break + else: + return NotImplemented + return True + + +class WritableStream(ABC): + @abc.abstractmethod + def write(self, s): + pass + + @classmethod + def __subclasshook__(cls, C): + if cls is WritableStream: + return _check_methods(C, 'write') + return NotImplemented + + + +file_reading_errors = ( + IOError, + OSError, + ValueError # IronPython weirdness. +) + + + +def shitcode(s): + return ''.join( + (c if (0 < ord(c) < 256) else '?') for c in s + ) + + +def get_repr_function(item, custom_repr): + for condition, action in custom_repr: + if isinstance(condition, type): + condition = lambda x, y=condition: isinstance(x, y) + if condition(item): + return action + return repr + + +DEFAULT_REPR_RE = re.compile(r' at 0x[a-f0-9A-F]{4,}') + + +def normalize_repr(item_repr): + """Remove memory address (0x...) from a default python repr""" + return DEFAULT_REPR_RE.sub('', item_repr) + + +def get_shortish_repr(item, custom_repr=(), max_length=None, normalize=False): + repr_function = get_repr_function(item, custom_repr) + try: + r = repr_function(item) + except Exception: + r = 'REPR FAILED' + r = r.replace('\r', '').replace('\n', '') + if normalize: + r = normalize_repr(r) + if max_length: + r = truncate(r, max_length) + return r + + +def truncate(string, max_length): + if (max_length is None) or (len(string) <= max_length): + return string + else: + left = (max_length - 3) // 2 + right = max_length - 3 - left + return u'{}...{}'.format(string[:left], string[-right:]) + + +def ensure_tuple(x): + if isinstance(x, collections_abc.Iterable) and \ + not isinstance(x, string_types): + return tuple(x) + else: + return (x,) + + + diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/variables.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/variables.py new file mode 100644 index 000000000..2229c38aa --- /dev/null +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/python_libs/pysnooper/variables.py @@ -0,0 +1,133 @@ +import itertools +import abc +try: + from collections.abc import Mapping, Sequence +except ImportError: + from collections import Mapping, Sequence +from copy import deepcopy + +from . import utils +from . import pycompat + + +def needs_parentheses(source): + def code(s): + return compile(s, '', 'eval').co_code + + return code('{}.x'.format(source)) != code('({}).x'.format(source)) + + +class BaseVariable(pycompat.ABC): + def __init__(self, source, exclude=()): + self.source = source + self.exclude = utils.ensure_tuple(exclude) + self.code = compile(source, '', 'eval') + if needs_parentheses(source): + self.unambiguous_source = '({})'.format(source) + else: + self.unambiguous_source = source + + def items(self, frame, normalize=False): + try: + main_value = eval(self.code, frame.f_globals or {}, frame.f_locals) + except Exception: + return () + return self._items(main_value, normalize) + + @abc.abstractmethod + def _items(self, key, normalize=False): + raise NotImplementedError + + @property + def _fingerprint(self): + return (type(self), self.source, self.exclude) + + def __hash__(self): + return hash(self._fingerprint) + + def __eq__(self, other): + return (isinstance(other, BaseVariable) and + self._fingerprint == other._fingerprint) + + +class CommonVariable(BaseVariable): + def _items(self, main_value, normalize=False): + result = [(self.source, utils.get_shortish_repr(main_value, normalize=normalize))] + for key in self._safe_keys(main_value): + try: + if key in self.exclude: + continue + value = self._get_value(main_value, key) + except Exception: + continue + result.append(( + '{}{}'.format(self.unambiguous_source, self._format_key(key)), + utils.get_shortish_repr(value) + )) + return result + + def _safe_keys(self, main_value): + try: + for key in self._keys(main_value): + yield key + except Exception: + pass + + def _keys(self, main_value): + return () + + def _format_key(self, key): + raise NotImplementedError + + def _get_value(self, main_value, key): + raise NotImplementedError + + +class Attrs(CommonVariable): + def _keys(self, main_value): + return itertools.chain( + getattr(main_value, '__dict__', ()), + getattr(main_value, '__slots__', ()) + ) + + def _format_key(self, key): + return '.' + key + + def _get_value(self, main_value, key): + return getattr(main_value, key) + + +class Keys(CommonVariable): + def _keys(self, main_value): + return main_value.keys() + + def _format_key(self, key): + return '[{}]'.format(utils.get_shortish_repr(key)) + + def _get_value(self, main_value, key): + return main_value[key] + + +class Indices(Keys): + _slice = slice(None) + + def _keys(self, main_value): + return range(len(main_value))[self._slice] + + def __getitem__(self, item): + assert isinstance(item, slice) + result = deepcopy(self) + result._slice = item + return result + + +class Exploding(BaseVariable): + def _items(self, main_value, normalize=False): + if isinstance(main_value, Mapping): + cls = Keys + elif isinstance(main_value, Sequence): + cls = Indices + else: + cls = Attrs + + return cls(self.source, self.exclude)._items(main_value, normalize) diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/down b/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/down new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/log/down b/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/log/down new file mode 100644 index 000000000..e69de29bb diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/service/log/run b/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/log/run similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/service/log/run rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/service/log/run diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/service/run b/firmware/opt/dbus-fzsonick-48tl-fork-S3/service/run similarity index 100% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/service/run rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/service/run diff --git a/firmware/opt/dbus-fz-sonick-48tl-with-s3/signals.py b/firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.py old mode 100644 new mode 100755 similarity index 99% rename from firmware/opt/dbus-fz-sonick-48tl-with-s3/signals.py rename to firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.py index e35c95603..2f4d16e4f --- a/firmware/opt/dbus-fz-sonick-48tl-with-s3/signals.py +++ b/firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.py @@ -1,7 +1,7 @@ # coding=utf-8 import config as cfg -from convert import mean, read_float, read_led_state, read_bool, count_bits, comma_separated, read_bitmap, return_in_list, first, read_hex_string +from convert import mean, read_float, read_led_state, read_bool, count_bits, comma_separated, read_bitmap, return_in_list, first, read_hex_string,read_limb_string from data import BatterySignal, Battery, LedColor, ServiceSignal, BatteryStatus, LedState, CsvSignal # noinspection PyUnreachableCode @@ -140,7 +140,7 @@ def init_battery_signals(): return [ BatterySignal('/TimeToTOCRequest', max, read_float(register=1052)), - BatterySignal('/EOCReached', return_in_list, read_eoc_reached), + BatterySignal('/IoStatus/EocReached', return_in_list, read_eoc_reached), BatterySignal('/NumOfLimbStrings', return_in_list, limp_strings_value), BatterySignal('/Dc/0/Voltage', mean, get_value=read_voltage(), unit='V'), BatterySignal('/Dc/0/Current', sum, get_value=read_current(), unit='A'), @@ -371,4 +371,5 @@ def read_warning_and_alarm_flags(): CsvSignal('/Battery/Devices/AlarmFlags/LMPA', read_bool(base_register=1005, bit=45)), CsvSignal('/Battery/Devices/AlarmFlags/HEBT', read_bool(base_register=1005, bit=46)), CsvSignal('/Battery/Devices/AlarmFlags/CURM', read_bool(base_register=1005, bit=48)), + CsvSignal('/Battery/Devices/AlarmFlags/2 or more string are disabled',read_limb_string(1059)), ] diff --git a/firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.pyc b/firmware/opt/dbus-fzsonick-48tl-fork-S3/signals.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca9c83919b961ef82e99ff4e56e83785745cf1d9 GIT binary patch literal 22470 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