pax_global_header00006660000000000000000000000064147535645360014534gustar00rootroot0000000000000052 comment=ec10e3db82ea89f123499d33e6fd3739060bf337 pysmarty2-0.10.2/000077500000000000000000000000001475356453600135665ustar00rootroot00000000000000pysmarty2-0.10.2/.gitignore000066400000000000000000000000441475356453600155540ustar00rootroot00000000000000/build/ /dist/ /pysmarty2.egg-info/ pysmarty2-0.10.2/LICENSE000066400000000000000000000020571475356453600145770ustar00rootroot00000000000000MIT License Copyright (c) 2019 Teodor Nicolau Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pysmarty2-0.10.2/MANIFEST.in000066400000000000000000000000741475356453600153250ustar00rootroot00000000000000include pysmarty2/registers/*.json include requirements.txt pysmarty2-0.10.2/README.md000066400000000000000000000013031475356453600150420ustar00rootroot00000000000000# pysmarty2 Python API for Salda Smarty 2X/3X/4X P/V Ventilation Unit. This is used in [Home Assistant](https://home-assistant.io). This package, `pysmarty2`, is a fork of the original [`pysmarty`](https://github.com/z0mbieprocess/pysmarty) created by [z0mbieprocess](https://github.com/z0mbieprocess). The package was renamed to `pysmarty2` to allow for publishing it to PyPI without conflicting with the existing `pysmarty` package. ## Install ```bash pip install pysmarty2 ``` ## Acknowledgments Please give credit to the original author for their work on `pysmarty`. This fork was created to make minor modifications and improvements, but the majority of the work remains the original author's. pysmarty2-0.10.2/mypy.ini000066400000000000000000000000451475356453600152640ustar00rootroot00000000000000[mypy] ignore_missing_imports = True pysmarty2-0.10.2/pylintrc000066400000000000000000000004131475356453600153530ustar00rootroot00000000000000[MESSAGES CONTROL] disable= [REPORTS] reports=no [TYPECHECK] [FORMAT] # Maximum number of characters on a single line. # max-line-length=80 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ [EXCEPTIONS] pysmarty2-0.10.2/pysmarty2/000077500000000000000000000000001475356453600155405ustar00rootroot00000000000000pysmarty2-0.10.2/pysmarty2/__init__.py000066400000000000000000000001451475356453600176510ustar00rootroot00000000000000"""PySmarty2 init.""" from .smarty import Smarty name = "pysmarty2" __all__ = [ 'Smarty' ] pysmarty2-0.10.2/pysmarty2/connection.py000066400000000000000000000016721475356453600202570ustar00rootroot00000000000000"""Modbus Client.""" from pymodbus.client import ModbusTcpClient class Connection: """Modbus Client.""" def __init__(self, host, port, slave=1, loop=None): """Modbus Connection init.""" # pylint: disable=unsubscriptable-object self._client = ModbusTcpClient(host=host, port=port) self._slave = slave @property def client(self): """Get Modbus Client.""" return self._client @property def host(self): """Get Host.""" return self._client.host @property def port(self): """Get Port.""" return self._client.port @property def slave(self): """Get Slave.""" return self._slave def is_connected(self): """Return connection state. Attempt to connect if not already connected.""" if not self._client.is_socket_open(): self._client.connect() return self._client.is_socket_open() pysmarty2-0.10.2/pysmarty2/exceptions.py000066400000000000000000000005031475356453600202710ustar00rootroot00000000000000"""Pysmarty Exceptions.""" class PysmartyBaseException(Exception): """Base Exception for pysmarty.""" class PysmartyException(PysmartyBaseException): """Pysmarty Exception.""" def __init__(self, message, errors): """Exception Init.""" super().__init__(message) self.errors = errors pysmarty2-0.10.2/pysmarty2/registers/000077500000000000000000000000001475356453600175475ustar00rootroot00000000000000pysmarty2-0.10.2/pysmarty2/registers/__init__.py000066400000000000000000000001311475356453600216530ustar00rootroot00000000000000"""Registers module.""" from .registers import Registers __all__ = [ 'Registers' ] pysmarty2-0.10.2/pysmarty2/registers/baseregister.py000066400000000000000000000013531475356453600226020ustar00rootroot00000000000000"""Base Register.""" class BaseRegister: """Smarty Register.""" def __init__(self, connection, **kwargs): self._connection = connection self._register_id = kwargs.get('ID') self._name = kwargs.get('NAME') self._states = kwargs.get('STATES') self._register_type = kwargs.get('register_type') self.state = None self.addr = kwargs.get('ADDR') @property def name(self) -> str: """Get the name of the register.""" return self._name @property def register_type(self) -> str: """Get the register type.""" return self._register_type def get_id(self) -> str: """Get the ID of the register.""" return self._register_id pysmarty2-0.10.2/pysmarty2/registers/coil.py000066400000000000000000000013041475356453600210450ustar00rootroot00000000000000"""Coil.""" from .baseregister import BaseRegister from ..exceptions import PysmartyException class Coil(BaseRegister): """Smart Coil Register.""" def __init__(self, **kwargs): super().__init__(register_type='coil', **kwargs) def update_state(self): """Read Register.""" res = self._connection.client.read_coils( self.addr, slave=self._connection.slave) if not res.isError(): self.state = res.bits[0] def set_state(self, state): """Write Register.""" res = self._connection.client.write_coil( self.addr, state, slave=self._connection.slave) if not res.isError(): self.state = state pysmarty2-0.10.2/pysmarty2/registers/coils.json000066400000000000000000000014251475356453600215550ustar00rootroot00000000000000[ { "ID": "COIL_FILTER_TIMER_RESET", "ADDR": 1, "NAME": "Filters timer reset", "STATES": { "Nothing": 0, "Activate": 1 } }, { "ID": "COIL_DRYNESS_PROTECTION", "ADDR": 3, "NAME": "Dryness protection", "STATES": { "Disabled": 0, "Enabled": 1 } }, { "ID": "COIL_NIGHT_COOLING_FUNCTION", "ADDR": 4, "NAME": "Night cooling function", "STATES": { "Disabled": 0, "Enabled": 1 } }, { "ID": "COIL_INTENSIVE_AIR_FLOW_BOOST", "ADDR": 5, "NAME": "Intensive air flow", "STATES": { "Nothing": 0, "Activate": 1 } } ]pysmarty2-0.10.2/pysmarty2/registers/discrete_input.py000066400000000000000000000007301475356453600231420ustar00rootroot00000000000000"""Discrete Input.""" from .baseregister import BaseRegister class DiscreteInput(BaseRegister): """Smarty Input Register.""" def __init__(self, **kwargs): super().__init__(register_type='discrete_input', **kwargs) def update_state(self): """Read Register.""" res = self._connection.client.read_discrete_inputs( self.addr, slave=self._connection.slave) if not res.isError(): self.state = res.bits[0] pysmarty2-0.10.2/pysmarty2/registers/discrete_inputs.json000066400000000000000000000411751475356453600236560ustar00rootroot00000000000000[ { "ADDR": 1, "ID": "ALARM_ROTOR_BROKEN_BELT_WARNING", "NAME": "Warning! Rotor broken belt alarm", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 2, "ID": "ALARM_FIREPLACE_PROTECTION_ACTIVATED", "NAME": "Alarm! Fireplace protection activated", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 3, "ID": "ALARM_DRYNESS_PROTECTION_ACTIVATED", "NAME": "Warning! Dryness protection activated", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 4, "ID": "ALARM_PLATE_HEAT_EXCHANGER_FROST_PROTECTION_ACTIVATED", "NAME": "Warning! Plate heat exchanger frost protection activated", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 5, "ID": "ALARM_PLATE_HEAT_EXCHANGER_FROST_PROTECTION_SYSTEM_STOPPED", "NAME": "Alarm! Plate heat exchanger frost protection system stopped", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 6, "ID": "ALARM_PLATE_HEAT_EXCHANGER_FROST_PROTECTION_PRESS_RELE", "NAME": "Warning! Plate heat exchanger frost protection (pressure relay)", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 7, "ID": "ALARM_HYDRONIC_HEATER_FROST_PROTECTION_SYSTEM_STOPPED", "NAME": "Alarm! Hydronic heater frost protection. System stopped.", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 8, "ID": "ALARM_TOO_LOW_SUPPLY_TEMPERATURE", "NAME": "Warning! Too low supply temperature", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 9, "ID": "ALARM_TOO_HIGH_SUPPLY_TEMPERATURE", "NAME": "Warning! Too high supply temperature", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 10, "ID": "ALARM_TOO_LOW_SUPPLY_TEMPERATURE_SYSTEM_STOPPED", "NAME": "Alarm! Too low supply temperature. System stopped.", "STATES": { "1": "Active", "0": "Not active" } }, { "ADDR": 11, "ID": "ALARM_TOO_HIGH_SUPPLY_TEMPERATURE_SYSTEM_STOPPED", "NAME": "Alarm! Too high supply temperature. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 12, "ID": "ALARM_CHANGE_SUPPLY_AIR_FILTER_PRESS_RELE_SYSTEM_STOPPED", "NAME": "Warning! Change supply air filter (pressure relay).", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 13, "ID": "ALARM_CHANGE_EXTRACT_AIR_FILTER_PRESS_RELE_SYSTEM_STOPPED", "NAME": "Warning! Change extract air filter (pressure relay).", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 14, "ID": "ALARM_CHANGE_SUPPLY_AND_EXTRACT_FILTERS", "NAME": "Warning! Change supply and extract filters (timeout)", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 15, "ID": "ALARM_F1_FUSE_FAILURE", "NAME": "Alarm! Power supply failure. Please check F1 fuse.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 16, "ID": "ALARM_SUPPLY_AIR_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Supply air temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 17, "ID": "ALARM_EXTRACT_AIR_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Extract air temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 18, "ID": "ALARM_EXHAUST_AIR_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Exhaust air temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 19, "ID": "ALARM_OUTDOOR_AIR_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Outdoor air temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 20, "ID": "ALARM_HYDRONIC_HEATER_WATER_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Hydronic heater water temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 21, "ID": "ALARM_HYDRONIC_PREHEATER_WATER_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Hydronic pre-heater water temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 22, "ID": "ALARM_HYDRONIC_COOLER_WATER_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Hydronic cooler water temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 23, "ID": "ALARM_CONTROL_BOX_TEMPERATURE_SENSOR_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Controller cabinet temperature sensor failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 24, "ID": "ALARM_SUPPLY_AIR_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Supply air temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 25, "ID": "ALARM_EXTRACT_AIR_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Extract air temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 26, "ID": "ALARM_EXHAUST_AIR_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Exhaust air temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 27, "ID": "ALARM_OUTDOOR_AIR_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Outdoor air temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 28, "ID": "ALARM_HYDRONIC_HEATER_WATER_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Hydronic heater water temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 29, "ID": "ALARM_HYDRONIC_PREHEATER_WATER_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Hydronic pre-heater water temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 30, "ID": "ALARM_HYDRONIC_COOLER_WATER_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Hydronic cooler water temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 31, "ID": "ALARM_CONTROL_BOX_TEMPERATURE_SENSOR_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Controller cabinet temperature sensor failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 32, "ID": "ALARM_FIRE_DAMPER_TEST_OK", "NAME": "Fire damper test OK", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 33, "ID": "ALARM_FIRE_DAMPER_TEST_FAILED", "NAME": "Warning! Fire damper test failed", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 34, "ID": "ALARM_HEATER_MANUAL_PROTECTION_SYSTEM_STOPPPED", "NAME": "Alarm! Heater manual protection. System stopped!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 35, "ID": "ALARM_HEATER_AUTOMATIC_PROTECTION", "NAME": "Warning! Heater automatic protection", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 36, "ID": "ALARM_PREHEATER_MANUAL_PROTECTION_SYSTEM_STOPPPED", "NAME": "Alarm! Pre-heater manual protection. System stopped!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 37, "ID": "ALARM_PREHEATER_AUTOMATIC_PROTECTION", "NAME": "Warning! Pre-heater automatic protection", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 38, "ID": "ALARM_SUPPLY_FAN_PROTECTION", "NAME": "Alarm! Supply fan failure", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 39, "ID": "ALARM_EXTRACT_FAN_PROTECTION", "NAME": "Alarm! Extract fan failure", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 40, "ID": "ALARM_DX_COOLER_PROTECTION", "NAME": "Alarm! DX cooler failure", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 41, "ID": "ALARM_FIRE_ALARM", "NAME": "Alarm! Fire", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 42, "ID": "ALARM_SUPPLY_FAN_PRESSURE_PROTECTION_SYSTEM_STOPPPED", "NAME": "Alarm! Supply fan pressure protection. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 43, "ID": "ALARM_EXTRACT_FAN_PRESSURE_PROTECTION_SYSTEM_STOPPPED", "NAME": "Alarm! Extract fan pressure protection. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 44, "ID": "ALARM_INTERNAL", "NAME": "Alarm! Internal system error.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 45, "ID": "ALARM_HEATER_MANUAL_PROTECTION_BOOSTING", "NAME": "Alarm! Heater manual protection. Boosting.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 46, "ID": "ALARM_PREHEATER_MANUAL_PROTECTION_BOOSTING", "NAME": "Alarm! Pre-heater manual protection. Boosting.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 47, "ID": "ALARM_INTERNAL_COMMUNICATION_ERROR", "NAME": "Alarm! Internal communication error", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 48, "ID": "ALARM_DX_COOLER_DEFROSTING", "NAME": "Warning! DX cooler defrosting", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 49, "ID": "ALARM_TOO_HI_3DAYS_RH", "NAME": "Warning! Too high 3 days extract humidity. Increasing air flow.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 50, "ID": "ALARM_TOO_HI_RH", "NAME": "Warning! Too high extract humidity. Boosting.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 51, "ID": "ALARM_ROTOR_BROKEN_BELT_ALARM_SYSTEM_STOPPED", "NAME": "Alarm! Rotor broken belt alarm. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 52, "ID": "ALARM_GAS_HEATER_FAILURE", "NAME": "Warning! Gas heater failure", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 53, "ID": "ALARM_GAS_PREHEATER_FAILURE", "NAME": "Warning! Gas pre-heater failure", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 54, "ID": "ALARM_TOO_HI_CONDENSATION", "NAME": "Warning! Too high condensation level", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 55, "ID": "ALARM_SUPPLY_FAN_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Supply fan failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 56, "ID": "ALARM_EXTRACT_FAN_FAILURE_EMERGENCY_RUN", "NAME": "Warning! Extract fan failure. Emergency run.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 57, "ID": "ALARM_TOO_LOW_SUPPLY_AIR_FLOW", "NAME": "Warning! Too low supply air flow for DX cooler", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 58, "ID": "ALARM_BYPASS_DAMPER_ALARM_SYSTEM_STOPPED", "NAME": "Alarm! Bypass damper failure. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 59, "ID": "ALARM_HEATER_CIRC_PUMP_FAILURE_SYSTEM_STOPPED", "NAME": "Alarm! Hydronic heater/pre-heater circ. pump failute. System stopped.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 60, "ID": "ALARM_HEATER_CIRC_PUMP_FAILURE", "NAME": "Warning! Hydronic heater/pre-heater circ. pump failute.", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 61, "ID": "ALARM_REMOTE_HEATER_ALARM_SYSTEM_STOPPED ", "NAME": "Alarm! Remote heater failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 62, "ID": "ALARM_REMOTE_HEATER_WARNING", "NAME": "Warning! Remote heater failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 63, "ID": "ALARM_REMOTE_PRE_HEATER_ALARM_SYSTEM_STOPPED", "NAME": "Alarm! Remote pre-heater failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 64, "ID": "ALARM_REMOTE_PRE_HEATER_WARNING", "NAME": "Warning! Remote pre-heater failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 65, "ID": "ALARM_REMOTE_COOLER_ALARM_SYSTEM_STOPPED", "NAME": "Alarm! Remote cooler failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 66, "ID": "ALARM_REMOTE_COOLER_WARNING", "NAME": "Warning! Remote cooler failure!", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 67, "ID": "ALARM_FIRE_ALARM2 ", "NAME": "Alarm! Fire protection 2", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 188, "ID": "DI_CRITICAL_ALARM", "NAME": "Any active critical alarm", "STATES": { " 1": "Active", "0": "Not active" } }, { "ADDR": 189, "ID": "DI_WARNING", "NAME": "Any active warning", "STATES": { " 1": "Active", "0": "Not active" } } ]pysmarty2-0.10.2/pysmarty2/registers/holding_register.py000066400000000000000000000025361475356453600234570ustar00rootroot00000000000000"""Holding Register.""" from .baseregister import BaseRegister from ..exceptions import PysmartyException class HoldingRegister(BaseRegister): """Smarty Holding Register.""" def __init__(self, **kwargs): super().__init__(register_type='holding_register', **kwargs) @property def state_name(self): """Register State Name.""" return [k for k, v in self._states.items() if v == self.state][0] def update_state(self): """Read Register.""" res = self._connection.client.read_holding_registers( self.addr, slave=self._connection.slave) if not res.isError(): self.state = res.registers[0] def set_state(self, state): """Write Register.""" if isinstance(state, int): if state not in self._states.values(): raise PysmartyException('Invalid state.', state) value = state else: value = self._states[state] try: res = self._connection.client.write_register( self.addr, value, slave=self._connection.slave) if not res.isError(): self.state = value except KeyError as ex: raise PysmartyException('Invalid state.', ex.args) except Exception as ex: raise PysmartyException('Something went wrong.', ex.args) pysmarty2-0.10.2/pysmarty2/registers/holding_registers.json000066400000000000000000000014431475356453600241570ustar00rootroot00000000000000[ { "ID": "HR_USER_CONFIG_CURRENT_SYSTEM_MODE", "ADDR": 1, "NAME": "Current system mode", "STATES": { "Stand-by": 0, "Building protection": 1, "Economy": 2, "Confort": 3 } }, { "ID": "HR_ALARM_A", "ADDR": 200, "NAME": "Alarm A ()", "STATES": { "no alarms": 0, "alarm": 1 } }, { "ID": "HR_ALARM_B", "ADDR": 201, "NAME": "Alarm B ()", "STATES": { "no warnings": 0, "warning": 1 } }, { "ID": "HR_ALARMS_RESET", "ADDR": 202, "NAME": "Alarms Reset", "STATES": { "nothing": 0, "activate": 1 } } ] pysmarty2-0.10.2/pysmarty2/registers/input_register.py000066400000000000000000000015661475356453600231740ustar00rootroot00000000000000"""Input Register.""" from .baseregister import BaseRegister class InputRegister(BaseRegister): """Smarty Input Register.""" def __init__(self, **kwargs): super().__init__(register_type='input_register', **kwargs) self.multiplier = kwargs.get('MULTIPLIER') self.unit_of_mesurement = kwargs.get('UNIT_OF_MESUREMENT') @property def value(self): """Register State With Multiplier.""" return round(self.state * self.multiplier, 2) if self.state else self.state @property def state_name(self): """Register State Name.""" return self._states.get(str(self.state)) def update_state(self): """Read Register.""" res = self._connection.client.read_input_registers( self.addr, slave=self._connection.slave) if not res.isError(): self.state = res.registers[0] pysmarty2-0.10.2/pysmarty2/registers/input_registers.json000066400000000000000000000047431475356453600237000ustar00rootroot00000000000000[ { "ID": "IR_CURRENT_SYSTEM_STATE", "ADDR": 1, "NAME": "Current system state", "MULTIPLIER": 1, "STATES": { "0": "Stand-by", "1": "Building protection", "2": "Economy", "3": "Comfort", "4": "Emergency run", "5": "Preparing", "6": "Opening dampers", "7": "Boost", "8": "Cooling heaters", "9": "Closing dampers", "10": "Night Cooling", "11": "Critical alarm", "12": "Fire alarm", "13": "Heat exchanger frost protection", "14": "Change filters", "15": "Room RH 3 days average is lower than 30%. Limiting speed", "16": "DX cooler defrosting", "17": "Fire damper testing" } }, { "ID": "IR_SOFTWARE_VERSION", "ADDR": 2, "NAME": "Software version", "MULTIPLIER": 1 }, { "ID": "IR_CONFIGURATION_VERSION", "ADDR": 3, "NAME": "Configuration version", "MULTIPLIER": 1 }, { "ID": "IR_CURRENT_SYSTEM_MODE", "ADDR": 15, "NAME": "Current system mode", "MULTIPLIER": 1, "STATES": { "0": "Stand-by", "1": "Building protection", "2": "Economy", "3": "Comfort", "4": "Boost" } }, { "ID": "IR_SUPPLY_AIR_TEMPERATURE", "ADDR": 18, "NAME": "T1-Supply air temperature", "MULTIPLIER": 0.1, "UNIT_OF_MESUREMENT": "\u00b0C" }, { "ID": "IR_EXTRACT_AIR_TEMPERATURE", "ADDR": 19, "NAME": "T2-Extract air temperature", "MULTIPLIER": 0.1, "UNIT_OF_MESUREMENT": "\u00b0C" }, { "ID": "IR_OUTDOOR_AIR_TEMPERATURE", "ADDR": 21, "NAME": "Outdoor air temperature", "MULTIPLIER": 0.1, "UNIT_OF_MESUREMENT": "\u00b0C" }, { "ID": "IR_FILTERS_TIMER_DAYS_LEFT", "ADDR": 30, "NAME": "Filters timer days left", "MULTIPLIER": 1, "UNIT_OF_MESUREMENT": "day" }, { "ID": "IR_SUPPLY_FAN_SPEED_RPM", "ADDR": 55, "NAME": "Supply fan spreed RPM", "MULTIPLIER": 1, "UNIT_OF_MESUREMENT": "rpm" }, { "ID": "IR_EXTRACT_FAN_SPEED_RPM", "ADDR": 56, "NAME": "Extract fan spreed RPM", "MULTIPLIER": 1, "UNIT_OF_MESUREMENT": "rpm" } ] pysmarty2-0.10.2/pysmarty2/registers/registers.py000066400000000000000000000105641475356453600221360ustar00rootroot00000000000000"""Smarty Modbus Registers.""" import json from os import path from .holding_register import HoldingRegister from .coil import Coil from .discrete_input import DiscreteInput from .input_register import InputRegister THIS_DIR = path.abspath(path.dirname(__file__)) # read/write registers with open(path.join(THIS_DIR, "holding_registers.json"), "r") as f: HOLDING_REGISTERS = json.load(f) # read/write registers with open(path.join(THIS_DIR, "coils.json"), "r") as f: COILS = json.load(f) # read only with open(path.join(THIS_DIR, "discrete_inputs.json"), "r") as f: DISCRETE_INPUTS = json.load(f) # read only with open(path.join(THIS_DIR, "input_registers.json"), "r") as f: INPUT_REGISTERS = json.load(f) class Registers: """ Smarty Registers. MCB 1.21 Modbus Table: http://salda.lt/mcb/downloads/doc/MCB%201.21%20Modbus%20table%202018-05-03.xlsx """ def __init__(self, conn): self._connection = conn self.holding_registers = [HoldingRegister(connection=conn, **r) for r in HOLDING_REGISTERS] self.coils = [Coil(connection=conn, **r) for r in COILS] self.discrete_inputs = [DiscreteInput(connection=conn, **r) for r in DISCRETE_INPUTS] self.input_registers = [InputRegister(connection=conn, **r) for r in INPUT_REGISTERS] self._registers = [self.holding_registers, self.coils, self.discrete_inputs, self.input_registers] def get_register(self, register_id): """Get a register.""" for regs in self._registers: for reg in regs: if reg.get_id() == register_id: return reg return None def update(self) -> bool: """Update all registers.""" return (self._update_holding_registers() and self._update_coils() and self._update_discrete_inputs() and self._update_input_registers()) def _update_holding_registers(self) -> bool: """Read Holding Registers.""" res1 = self._connection.client.read_holding_registers( 1, count=37, slave=self._connection.slave) res2 = self._connection.client.read_holding_registers( 200, count=3, slave=self._connection.slave) if res1.isError() or res2.isError(): return False res = {k: v for k, v in zip(range(1, 37), res1.registers)} res.update({k: v for k, v in zip(range(200, 202), res2.registers)}) update_states(res, self.holding_registers) return True def _update_coils(self) -> bool: """Update Coils.""" res1 = self._connection.client.read_coils( 1, count=9, slave=self._connection.slave) if res1.isError(): return False res = {k: v for k, v in zip(range(1, 10), res1.bits)} update_states(res, self.coils) return True def _update_discrete_inputs(self) -> bool: """Update Discrete Inputs.""" res1 = self._connection.client.read_discrete_inputs( 1, count=67, slave=self._connection.slave) res2 = self._connection.client.read_discrete_inputs( 188, count=2, slave=self._connection.slave) if res1.isError() or res2.isError(): return False res = {k: v for k, v in zip(range(1, 67), res1.bits)} res.update({k: v for k, v in zip(range(188, 190), res2.bits)}) update_states(res, self.discrete_inputs) return True def _update_input_registers(self) -> bool: """Update input registers""" res1 = self._connection.client.read_input_registers( 1, count=66, slave=self._connection.slave) res2 = self._connection.client.read_input_registers( 67, count=66, slave=self._connection.slave) if res1.isError() or res2.isError(): return False res = {k: v for k, v in zip(range(1, 132), res1.registers + res2.registers)} update_states(res, self.input_registers) return True def update_states(res, registers) -> None: """Update Registers state.""" for key, value in res.items(): for reg in registers: if key == reg.addr: reg.state = value pysmarty2-0.10.2/pysmarty2/smarty.py000066400000000000000000000115061475356453600174340ustar00rootroot00000000000000"""Python Smarty.""" from .registers import Registers from .connection import Connection class Smarty: """Smarty Class.""" def __init__(self, host, port=502, slave=1, loop=None): self.connection = Connection(host, port, slave, loop) self._registers = Registers(self.connection) @property def fan_speed(self) -> int: """Get Current Fan Speed.""" register = self._registers.get_register( 'HR_USER_CONFIG_CURRENT_SYSTEM_MODE') return register.state @property def fan_speed_name(self) -> str: """Get Current Fan Speed Name.""" register = self._registers.get_register( 'HR_USER_CONFIG_CURRENT_SYSTEM_MODE') return register.state_name @property def system_state(self) -> str: """Get Current System State.""" register = self._registers.get_register( 'IR_CURRENT_SYSTEM_STATE') return register.state_name @property def filter_timer(self) -> int: """Get Filter Timer Days Left.""" register = self._registers.get_register( 'IR_FILTERS_TIMER_DAYS_LEFT') return register.state @property def supply_fan_speed(self) -> int: """Get Supply Fan Speed (RPM).""" register = self._registers.get_register( 'IR_SUPPLY_FAN_SPEED_RPM') return register.value @property def extract_fan_speed(self) -> int: """Get Extract Fan Speed (RPM).""" register = self._registers.get_register( 'IR_EXTRACT_FAN_SPEED_RPM') return register.value @property def supply_air_temperature(self) -> int: """Get Supply Air Temperature.""" register = self._registers.get_register( 'IR_SUPPLY_AIR_TEMPERATURE') return register.value @property def extract_air_temperature(self) -> int: """Get Extract Air Temperature.""" register = self._registers.get_register( 'IR_EXTRACT_AIR_TEMPERATURE') return register.value @property def outdoor_air_temperature(self) -> int: """Get Outdoor Air Temperature.""" register = self._registers.get_register( 'IR_OUTDOOR_AIR_TEMPERATURE') return register.value @property def alarm(self) -> bool: """Get Alarm.""" register = self._registers.get_register( 'HR_ALARM_A') return bool(register.state) @property def warning(self) -> bool: """Get Warning.""" register = self._registers.get_register( 'HR_ALARM_B') return bool(register.state) @property def boost(self) -> bool: """Get the Boost State.""" register = self._registers.get_register( 'COIL_INTENSIVE_AIR_FLOW_BOOST') return bool(register.state) def get_software_version(self) -> str: """Software version.""" register = self._registers.get_register( 'IR_SOFTWARE_VERSION') return register.state def get_configuration_version(self) -> str: """Configuration version.""" register = self._registers.get_register( 'IR_CONFIGURATION_VERSION') return register.state def update(self) -> bool: """Update registers.""" if self.connection.is_connected(): return self._registers.update() return False def set_fan_speed(self, speed) -> bool: """Set Current Fan Speed.""" if self.connection.is_connected(): register = self._registers.get_register( 'HR_USER_CONFIG_CURRENT_SYSTEM_MODE') register.set_state(speed) return True return False def turn_off(self) -> bool: """Turn off.""" return self.set_fan_speed(0) def turn_on(self, speed) -> bool: """Turn on.""" return self.set_fan_speed(speed) def is_on(self) -> bool: """Get Status.""" return bool(self.fan_speed) def enable_boost(self) -> bool: """Set Intensive Air Flow (limited).""" if self.connection.is_connected(): register = self._registers.get_register( 'COIL_INTENSIVE_AIR_FLOW_BOOST') register.set_state(1) return True return False def disable_boost(self) -> bool: """Set Intensive Air Flow (limited).""" if self.connection.is_connected(): register = self._registers.get_register( 'COIL_INTENSIVE_AIR_FLOW_BOOST') register.set_state(0) return True return False def reset_filters_timer(self) -> bool: """Reset Filter Timer.""" if self.connection.is_connected(): register = self._registers.get_register( 'COIL_FILTER_TIMER_RESET') register.set_state(1) return True return False pysmarty2-0.10.2/requirements.txt000066400000000000000000000000271475356453600170510ustar00rootroot00000000000000pymodbus>=3.6.9,<4.0.0 pysmarty2-0.10.2/setup.py000066400000000000000000000021061475356453600152770ustar00rootroot00000000000000"""Setup for pysmarty2 package.""" import os import setuptools # Read README.md with open("README.md", "r", encoding="utf-8") as fh: LONG_DESCRIPTION = fh.read() # Ensure requirements.txt exists before reading it if os.path.exists("requirements.txt"): with open("requirements.txt", "r", encoding="utf-8") as f: REQUIREMENTS = [line.strip() for line in f if line.strip()] else: REQUIREMENTS = [] setuptools.setup( name="pysmarty2", version="0.10.2", author="Martins Sipenko, Theo Nicolaum", author_email="martins.sipenko@gmail.com", description="Python API for Salda Smarty Modbus TCP", long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", url="https://github.com/martinssipenko/pysmarty2", packages=setuptools.find_packages(), include_package_data=True, install_requires=list(val.strip() for val in open('requirements.txt')), classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], )