Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a010d874ac | ||
|
|
f0222d750f | ||
|
|
43de00aacc | ||
|
|
0e3067dc26 | ||
|
|
86ef22006d | ||
|
|
1278f8e3e7 | ||
|
|
e2685d76ba | ||
|
|
81ec2f755c | ||
|
|
4272dc62fe | ||
|
|
2bb38b9ccc | ||
|
|
48ef003f22 | ||
|
|
cce625dabb | ||
|
|
ef1e0a0f79 | ||
|
|
df2985c964 | ||
|
|
0a3fca4590 |
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
python-packages:
|
||||
patterns:
|
||||
- "*"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -173,3 +173,5 @@ cython_debug/
|
||||
prot.csv
|
||||
helpertils/serial.py
|
||||
helpertils/test.py
|
||||
helpertils/socker.py
|
||||
helpertils/messagesFound.txt
|
||||
|
||||
@@ -30,29 +30,10 @@ class IndentFormatter(logging.Formatter):
|
||||
|
||||
|
||||
def __init__( self, fmt=None, datefmt=None ):
|
||||
"""
|
||||
Initializes the CustomLogger instance.
|
||||
Args:
|
||||
fmt (str, optional): The format string for the log messages. Defaults to None.
|
||||
datefmt (str, optional): The format string for the date in log messages. Defaults to None.
|
||||
Attributes:
|
||||
baseline (int): The baseline stack depth when the logger is initialized.
|
||||
"""
|
||||
logging.Formatter.__init__(self, fmt, datefmt)
|
||||
self.baseline = len(inspect.stack())
|
||||
|
||||
def format( self, rec ):
|
||||
"""
|
||||
Formats the log record by adding indentation and function name.
|
||||
This method customizes the log record by adding an indentation level
|
||||
based on the current stack depth and includes the name of the function
|
||||
from which the log call was made. It then uses the base Formatter class
|
||||
to format the record and returns the formatted string.
|
||||
Args:
|
||||
rec (logging.LogRecord): The log record to be formatted.
|
||||
Returns:
|
||||
str: The formatted log record string.
|
||||
"""
|
||||
log_fmt = self.FORMATS.get(rec.levelno)
|
||||
formatter = logging.Formatter(log_fmt)
|
||||
|
||||
@@ -63,13 +44,6 @@ class IndentFormatter(logging.Formatter):
|
||||
del rec.indent; del rec.function
|
||||
return out
|
||||
|
||||
class MessageProcessorFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
# Suppress INFO level messages from MessageProcessor.py
|
||||
if record.levelno == logging.INFO and record.pathname.endswith("MessageProcessor.py"):
|
||||
return False
|
||||
return True
|
||||
|
||||
# The following code sets up a custom logger with indentation support.
|
||||
# It creates a custom formatter, a logger instance, and a stream handler.
|
||||
# The custom formatter is set to the handler, which is then added to the logger.
|
||||
@@ -83,23 +57,5 @@ logger.addHandler(handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
def setDebugMode():
|
||||
"""
|
||||
Set the logging level to DEBUG and log a message indicating that debug mode is enabled.
|
||||
This function sets the logging level of the logger to DEBUG, which means that all messages
|
||||
at the DEBUG level and above will be logged. It also logs a debug message to indicate that
|
||||
debug mode has been activated.
|
||||
"""
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.debug("Debug mode is on...")
|
||||
|
||||
def setSilent():
|
||||
"""
|
||||
Sets the logger to silent mode, where only messages at the ERROR level or higher are displayed.
|
||||
If the current logging level is not 'DEBUG', this function will log an informational message
|
||||
indicating that silent mode is being activated and then set the logging level to ERROR.
|
||||
"""
|
||||
if logger.level != logging.DEBUG:
|
||||
logger.info("Silent Mode is turning on, only Messages at Level ERROR or higher are displayed")
|
||||
#logger.setLevel(logging.ERROR)
|
||||
# Add the filter to suppress INFO level messages from MessageProcessor.py
|
||||
logger.addFilter(MessageProcessorFilter())
|
||||
@@ -24,34 +24,12 @@ class EHSArguments:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Create and return a new instance of the class, ensuring that only one instance exists (singleton pattern).
|
||||
This method overrides the default behavior of object creation to implement the singleton pattern.
|
||||
It checks if an instance of the class already exists; if not, it creates a new instance and marks it as uninitialized.
|
||||
Args:
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
Returns:
|
||||
EHSArguments: The singleton instance of the EHSArguments class.
|
||||
"""
|
||||
|
||||
if not cls._instance:
|
||||
cls._instance = super(EHSArguments, cls).__new__(cls, *args, **kwargs)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the EHSArguments class, parses command-line arguments, and sets up configuration.
|
||||
This method performs the following steps:
|
||||
1. Checks if the class has already been initialized to prevent re-initialization.
|
||||
2. Sets up an argument parser to handle command-line arguments.
|
||||
3. Parses the command-line arguments and validates them.
|
||||
4. Checks if the specified config file and dump file exist.
|
||||
5. Sets the class attributes based on the parsed arguments.
|
||||
Raises:
|
||||
ArgumentException: If the required arguments are not provided or if the specified files do not exist.
|
||||
"""
|
||||
if self._initialized:
|
||||
return
|
||||
self._initialized = True
|
||||
|
||||
168
EHSConfig.py
168
EHSConfig.py
@@ -2,6 +2,7 @@ from EHSExceptions import ConfigException
|
||||
from EHSArguments import EHSArguments
|
||||
import yaml
|
||||
import os
|
||||
import re
|
||||
|
||||
from CustomLogger import logger
|
||||
|
||||
@@ -10,60 +11,25 @@ class EHSConfig():
|
||||
Singleton class to handle the configuration for the EHS Sentinel application.
|
||||
This class reads configuration parameters from a YAML file and validates them.
|
||||
It ensures that only one instance of the configuration exists throughout the application.
|
||||
Attributes:
|
||||
MQTT (dict): Configuration parameters for MQTT.
|
||||
GENERAL (dict): General configuration parameters.
|
||||
SERIAL (dict): Configuration parameters for serial communication.
|
||||
NASA_REPO (dict): Configuration parameters for NASA repository.
|
||||
Methods:
|
||||
__new__(cls, *args, **kwargs): Ensures only one instance of the class is created.
|
||||
__init__(self, *args, **kwargs): Initializes the configuration by reading and validating the YAML file.
|
||||
validate(self): Validates the configuration parameters.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
MQTT = None
|
||||
GENERAL = None
|
||||
SERIAL = None
|
||||
TCP = None
|
||||
NASA_REPO = None
|
||||
LOGGING = {}
|
||||
POLLING = None
|
||||
NASA_VAL_STORE = {}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Create a new instance of the EHSConfig class if one does not already exist.
|
||||
This method ensures that only one instance of the EHSConfig class is created
|
||||
(singleton pattern). If an instance already exists, it returns the existing instance.
|
||||
Args:
|
||||
cls: The class being instantiated.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
Returns:
|
||||
EHSConfig: The single instance of the EHSConfig class.
|
||||
"""
|
||||
|
||||
if not cls._instance:
|
||||
cls._instance = super(EHSConfig, cls).__new__(cls, *args, **kwargs)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the EHSConfig instance.
|
||||
This method initializes the EHSConfig instance by loading configuration
|
||||
settings from a YAML file specified in the EHSArguments. It ensures that
|
||||
the initialization process is only performed once by checking the
|
||||
_initialized attribute. If the instance is already initialized, the method
|
||||
returns immediately. Otherwise, it proceeds to load the configuration and
|
||||
validate it.
|
||||
Args:
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
Attributes:
|
||||
args (EHSArguments): An instance of EHSArguments containing the
|
||||
configuration file path.
|
||||
MQTT (dict): MQTT configuration settings loaded from the YAML file.
|
||||
GENERAL (dict): General configuration settings loaded from the YAML file.
|
||||
SERIAL (dict): Serial configuration settings loaded from the YAML file.
|
||||
"""
|
||||
if self._initialized:
|
||||
return
|
||||
self._initialized = True
|
||||
@@ -76,40 +42,96 @@ class EHSConfig():
|
||||
config = yaml.safe_load(file)
|
||||
self.MQTT = config.get('mqtt')
|
||||
self.GENERAL = config.get('general')
|
||||
self.SERIAL = config.get('serial')
|
||||
|
||||
if 'tcp' in config:
|
||||
self.TCP = config.get('tcp')
|
||||
|
||||
if 'serial' in config:
|
||||
self.SERIAL = config.get('serial')
|
||||
|
||||
if 'logging' in config:
|
||||
self.LOGGING = config.get('logging')
|
||||
else:
|
||||
self.LOGGING = {}
|
||||
|
||||
if 'polling' in config:
|
||||
self.POLLING = config.get('polling')
|
||||
|
||||
logger.debug(f"Configuration loaded: {config}")
|
||||
|
||||
|
||||
self.validate()
|
||||
|
||||
|
||||
def parse_time_string(self, time_str: str) -> int:
|
||||
match = re.match(r'^(\d+)([smh])$', time_str.strip(), re.IGNORECASE)
|
||||
if not match:
|
||||
raise ValueError("Invalid time format. Use '10s', '10m', or '10h'.")
|
||||
|
||||
value, unit = int(match.group(1)), match.group(2).lower()
|
||||
|
||||
conversion_factors = {
|
||||
's': 1, # seconds
|
||||
'm': 60, # minutes
|
||||
'h': 3600 # hours
|
||||
}
|
||||
|
||||
return value * conversion_factors[unit]
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Validates the configuration parameters for the EHS Sentinel application.
|
||||
This method checks the presence and validity of various configuration parameters
|
||||
such as NASA repository file, serial device, baudrate, MQTT broker URL, broker port,
|
||||
and MQTT credentials. It raises a ConfigException if any required parameter is missing
|
||||
or invalid. Additionally, it sets default values for optional parameters if they are not provided.
|
||||
Raises:
|
||||
ConfigException: If any required configuration parameter is missing or invalid.
|
||||
"""
|
||||
if os.path.isfile(self.GENERAL['nasaRepositoryFile']):
|
||||
with open(self.GENERAL['nasaRepositoryFile'], mode='r') as file:
|
||||
self.NASA_REPO = yaml.safe_load(file)
|
||||
else:
|
||||
raise ConfigException(argument=self.GENERAL['nasaRepositoryFile'], message="NASA Respository File is missing")
|
||||
|
||||
if 'silentMode' not in self.GENERAL:
|
||||
self.GENERAL['silentMode'] = True
|
||||
|
||||
if 'protocolFile' not in self.GENERAL:
|
||||
self.GENERAL['protocolFile'] = None
|
||||
|
||||
if 'device' not in self.SERIAL:
|
||||
raise ConfigException(argument=self.SERIAL['device'], message="serial device config parameter is missing")
|
||||
|
||||
if 'baudrate' not in self.SERIAL:
|
||||
raise ConfigException(argument=self.SERIAL['baudrate'], message="serial baudrate config parameter is missing")
|
||||
|
||||
if 'allowControl' not in self.GENERAL:
|
||||
self.GENERAL['allowControl'] = False
|
||||
|
||||
if self.SERIAL is None and self.TCP is None:
|
||||
raise ConfigException(argument="", message="define tcp or serial config parms")
|
||||
|
||||
if self.SERIAL is not None and self.TCP is not None:
|
||||
raise ConfigException(argument="", message="you cannot define tcp and serial please define only one")
|
||||
|
||||
if self.SERIAL is not None:
|
||||
if 'device' not in self.SERIAL:
|
||||
raise ConfigException(argument=self.SERIAL['device'], message="serial device config parameter is missing")
|
||||
|
||||
if 'baudrate' not in self.SERIAL:
|
||||
raise ConfigException(argument=self.SERIAL['baudrate'], message="serial baudrate config parameter is missing")
|
||||
|
||||
if self.TCP is not None:
|
||||
if 'ip' not in self.TCP:
|
||||
raise ConfigException(argument=self.TCP['ip'], message="tcp ip config parameter is missing")
|
||||
|
||||
if 'port' not in self.TCP:
|
||||
raise ConfigException(argument=self.TCP['port'], message="tcp port config parameter is missing")
|
||||
|
||||
if self.POLLING is not None:
|
||||
if 'fetch_interval' not in self.POLLING:
|
||||
raise ConfigException(argument='', message="fetch_interval in polling parameter is missing")
|
||||
|
||||
if 'groups' not in self.POLLING:
|
||||
raise ConfigException(argument='', message="groups in polling parameter is missing")
|
||||
|
||||
if 'fetch_interval' in self.POLLING and 'groups' in self.POLLING:
|
||||
for poller in self.POLLING['fetch_interval']:
|
||||
if poller['name'] not in self.POLLING['groups']:
|
||||
raise ConfigException(argument=poller['name'], message="Groupname from fetch_interval not defined in groups: ")
|
||||
if 'schedule' in poller:
|
||||
try:
|
||||
poller['schedule'] = self.parse_time_string(poller['schedule'])
|
||||
except ValueError as e:
|
||||
raise ConfigException(argument=poller['schedule'], message="schedule value from fetch_interval couldn't be validated, use format 10s, 10m or 10h")
|
||||
|
||||
for group in self.POLLING['groups']:
|
||||
for ele in self.POLLING['groups'][group]:
|
||||
if ele not in self.NASA_REPO:
|
||||
raise ConfigException(argument=ele, message="Element from group not in NASA Repository")
|
||||
|
||||
if 'broker-url' not in self.MQTT:
|
||||
raise ConfigException(argument=self.MQTT['broker-url'], message="mqtt broker-url config parameter is missing")
|
||||
|
||||
@@ -132,4 +154,30 @@ class EHSConfig():
|
||||
raise ConfigException(argument=self.SERIAL['device'], message="mqtt user parameter is missing")
|
||||
|
||||
if 'password' not in self.MQTT and 'user' in self.MQTT:
|
||||
raise ConfigException(argument=self.SERIAL['device'], message="mqtt password parameter is missing")
|
||||
raise ConfigException(argument=self.SERIAL['device'], message="mqtt password parameter is missing")
|
||||
|
||||
if 'messageNotFound' not in self.LOGGING:
|
||||
self.LOGGING['messageNotFound'] = False
|
||||
|
||||
if 'invalidPacket' not in self.LOGGING:
|
||||
self.LOGGING['invalidPacket'] = False
|
||||
|
||||
if 'deviceAdded' not in self.LOGGING:
|
||||
self.LOGGING['deviceAdded'] = True
|
||||
|
||||
if 'packetNotFromIndoorOutdoor' not in self.LOGGING:
|
||||
self.LOGGING['packetNotFromIndoorOutdoor'] = False
|
||||
|
||||
if 'proccessedMessage' not in self.LOGGING:
|
||||
self.LOGGING['proccessedMessage'] = False
|
||||
|
||||
if 'pollerMessage' not in self.LOGGING:
|
||||
self.LOGGING['pollerMessage'] = False
|
||||
|
||||
if 'controlMessage' not in self.LOGGING:
|
||||
self.LOGGING['controlMessage'] = False
|
||||
|
||||
logger.info(f"Logging Config:")
|
||||
for key, value in self.LOGGING.items():
|
||||
logger.info(f" {key}: {value}")
|
||||
|
||||
@@ -46,8 +46,8 @@ class ArgumentException(EHSException):
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.argument} -> {self.message}'
|
||||
|
||||
class InvalidMessageTypeException(EHSException):
|
||||
|
||||
class SkipInvalidPacketException(EHSException):
|
||||
"""Exception raised for invalid message types.
|
||||
|
||||
Attributes:
|
||||
@@ -55,10 +55,9 @@ class InvalidMessageTypeException(EHSException):
|
||||
message -- explanation of the error
|
||||
"""
|
||||
|
||||
def __init__(self, message_type, message="Invalid message type provided"):
|
||||
self.message_type = message_type
|
||||
def __init__(self, message="Invalid message type provided"):
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.message_type} -> {self.message}'
|
||||
return f'{self.message}'
|
||||
404
MQTTClient.py
404
MQTTClient.py
@@ -10,28 +10,13 @@ import gmqtt
|
||||
from CustomLogger import logger
|
||||
from EHSArguments import EHSArguments
|
||||
from EHSConfig import EHSConfig
|
||||
from MessageProducer import MessageProducer
|
||||
|
||||
class MQTTClient:
|
||||
"""
|
||||
MQTTClient is a singleton class that manages the connection to an MQTT broker and handles
|
||||
publishing and subscribing to topics. It is designed to work with Home Assistant for
|
||||
auto-discovery of devices and sensors.
|
||||
|
||||
Attributes:
|
||||
_instance (MQTTClient): The single instance of the MQTTClient class.
|
||||
STOP (asyncio.Event): Event to signal stopping the MQTT client.
|
||||
DEVICE_ID (str): The device ID used for MQTT topics.
|
||||
config (EHSConfig): Configuration object for the MQTT client.
|
||||
args (EHSArguments): Arguments object for the MQTT client.
|
||||
broker (str): URL of the MQTT broker.
|
||||
port (int): Port of the MQTT broker.
|
||||
client_id (str): Client ID for the MQTT client.
|
||||
client (gmqtt.Client): MQTT client instance.
|
||||
topicPrefix (str): Prefix for MQTT topics.
|
||||
homeAssistantAutoDiscoverTopic (str): Topic for Home Assistant auto-discovery.
|
||||
useCamelCaseTopicNames (bool): Flag to use camel case for topic names.
|
||||
known_topics (list): List to keep track of known topics.
|
||||
known_devices_topic (str): Dedicated topic for storing known topics.
|
||||
MQTTClient is a singleton class that manages the connection and communication with an MQTT broker.
|
||||
It handles the initialization, connection, subscription, and message publishing for the MQTT client.
|
||||
The class also supports Home Assistant auto-discovery and maintains a list of known devices.
|
||||
"""
|
||||
_instance = None
|
||||
STOP = asyncio.Event()
|
||||
@@ -39,19 +24,6 @@ class MQTTClient:
|
||||
DEVICE_ID = "samsung_ehssentinel"
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Create a new instance of the MQTTClient class if one does not already exist.
|
||||
This method ensures that the MQTTClient class follows the Singleton design pattern,
|
||||
meaning only one instance of the class can exist at any given time. If an instance
|
||||
already exists, it returns the existing instance. Otherwise, it creates a new instance
|
||||
and sets the _initialized attribute to False.
|
||||
Args:
|
||||
cls (type): The class being instantiated.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
Returns:
|
||||
MQTTClient: The single instance of the MQTTClient class.
|
||||
"""
|
||||
|
||||
if not cls._instance:
|
||||
cls._instance = super(MQTTClient, cls).__new__(cls)
|
||||
@@ -59,38 +31,17 @@ class MQTTClient:
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the MQTTClient instance.
|
||||
This constructor initializes the MQTT client with the configuration
|
||||
provided by the EHSConfig and EHSArguments classes. It sets up the
|
||||
MQTT broker connection details, client ID, and authentication credentials
|
||||
if provided. It also assigns callback functions for various MQTT events
|
||||
such as connect, disconnect, message, and subscribe. Additionally, it
|
||||
initializes the topic prefix, Home Assistant auto-discover topic, and
|
||||
topic naming convention.
|
||||
Attributes:
|
||||
config (EHSConfig): Configuration object for the MQTT client.
|
||||
args (EHSArguments): Argument parser object for the MQTT client.
|
||||
broker (str): URL of the MQTT broker.
|
||||
port (int): Port number of the MQTT broker.
|
||||
client_id (str): Client ID for the MQTT connection.
|
||||
client (gmqtt.Client): gmqtt client instance.
|
||||
topicPrefix (str): Prefix for MQTT topics.
|
||||
homeAssistantAutoDiscoverTopic (str): Topic for Home Assistant auto-discovery.
|
||||
useCamelCaseTopicNames (bool): Flag to use camel case for topic names.
|
||||
known_topics (list): List to keep track of known topics.
|
||||
known_devices_topic (str): Dedicated topic for storing known topics.
|
||||
"""
|
||||
|
||||
if self._initialized:
|
||||
return
|
||||
self.config = EHSConfig()
|
||||
self.args = EHSArguments()
|
||||
self.message_producer = None
|
||||
self._initialized = True
|
||||
self.broker = self.config.MQTT['broker-url']
|
||||
self.port = self.config.MQTT['broker-port']
|
||||
self.client_id = self.config.MQTT['client-id']
|
||||
self.client = gmqtt.Client(self.client_id, logger=logger)
|
||||
self.client = gmqtt.Client(self.client_id)
|
||||
self.client.on_connect = self.on_connect
|
||||
self.client.on_disconnect = self.on_disconnect
|
||||
self.client.on_message = self.on_message
|
||||
@@ -106,19 +57,6 @@ class MQTTClient:
|
||||
self.known_devices_topic = "known/devices" # Dedicated topic for storing known topics
|
||||
|
||||
async def connect(self):
|
||||
"""
|
||||
Asynchronously connects to the MQTT broker and optionally clears the known devices topic.
|
||||
This method logs the connection attempt, connects to the MQTT broker using the specified
|
||||
broker address and port, and sets the keepalive interval. If the CLEAN_KNOWN_DEVICES
|
||||
argument is set to True, it publishes an empty message to the known devices topic to clear it.
|
||||
Args:
|
||||
None
|
||||
Returns:
|
||||
None
|
||||
Raises:
|
||||
Any exceptions raised by the underlying MQTT client library during connection.
|
||||
"""
|
||||
|
||||
logger.info("[MQTT] Connecting to broker...")
|
||||
await self.client.connect(self.broker, self.port, keepalive=60, version=gmqtt.constants.MQTTv311)
|
||||
|
||||
@@ -126,91 +64,51 @@ class MQTTClient:
|
||||
self._publish(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", " ", retain=True)
|
||||
logger.info("Known Devices Topic have been cleared")
|
||||
|
||||
def subscribe_known_topics(self):
|
||||
"""
|
||||
Subscribes the MQTT client to known topics.
|
||||
This method subscribes the MQTT client to two specific topics:
|
||||
1. A topic for known devices, constructed using the topic prefix and known devices topic.
|
||||
2. A status topic for Home Assistant auto-discovery.
|
||||
The subscription QoS (Quality of Service) level for both topics is set to 1.
|
||||
Logging:
|
||||
Logs an informational message indicating that the client is subscribing to known devices topic.
|
||||
Raises:
|
||||
Any exceptions raised by the gmqtt.Subscription or self.client.subscribe methods.
|
||||
"""
|
||||
|
||||
def subscribe_known_topics(self):
|
||||
logger.info("Subscribe to known devices topic")
|
||||
self.client.subscribe(
|
||||
[
|
||||
sublist = [
|
||||
gmqtt.Subscription(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", 1),
|
||||
gmqtt.Subscription(f"{self.homeAssistantAutoDiscoverTopic}/status", 1)
|
||||
]
|
||||
)
|
||||
if self.config.GENERAL['allowControl']:
|
||||
sublist.append(gmqtt.Subscription(f"{self.topicPrefix.replace('/', '')}/entity/+/set", 1))
|
||||
|
||||
self.client.subscribe(sublist)
|
||||
|
||||
def on_subscribe(self, client, mid, qos, properties):
|
||||
"""
|
||||
Callback function that is called when the client subscribes to a topic.
|
||||
Args:
|
||||
client (paho.mqtt.client.Client): The client instance for this callback.
|
||||
mid (int): The message ID for the subscribe request.
|
||||
qos (int): The Quality of Service level for the subscription.
|
||||
properties (paho.mqtt.properties.Properties): The properties associated with the subscription.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
logger.debug('SUBSCRIBED')
|
||||
|
||||
def on_message(self, client, topic, payload, qos, properties):
|
||||
"""
|
||||
Callback function that is triggered when a message is received on a subscribed topic.
|
||||
Args:
|
||||
client (paho.mqtt.client.Client): The MQTT client instance.
|
||||
topic (str): The topic that the message was received on.
|
||||
payload (bytes): The message payload.
|
||||
qos (int): The quality of service level of the message.
|
||||
properties (paho.mqtt.properties.Properties): The properties of the message.
|
||||
Behavior:
|
||||
- If the topic matches the known devices topic, updates the known devices set with the retained message.
|
||||
- If the topic matches the Home Assistant auto-discover status topic, logs the status message and clears the known devices topic.
|
||||
"""
|
||||
|
||||
def on_message(self, client, topic, payload, qos, properties):
|
||||
if self.known_devices_topic in topic:
|
||||
# Update the known devices set with the retained message
|
||||
self.known_topics = list(filter(None, [x.strip() for x in payload.decode().split(",")]))
|
||||
if properties['retain'] == True:
|
||||
if self.config.LOGGING['deviceAdded']:
|
||||
logger.info(f"Loaded devices from known devices Topic:")
|
||||
|
||||
for idx, devname in enumerate(self.known_topics, start=1):
|
||||
logger.info(f"Device no. {idx:<3}: {devname} ")
|
||||
else:
|
||||
logger.debug(f"Loaded devices from known devices Topic:")
|
||||
for idx, devname in enumerate(self.known_topics):
|
||||
logger.debug(f"Device added no. {idx:<3}: {devname} ")
|
||||
|
||||
|
||||
if f"{self.homeAssistantAutoDiscoverTopic}/status" == topic:
|
||||
logger.info(f"HASS Status Messages {topic} received: {payload.decode()}")
|
||||
self._publish(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", " ", retain=True)
|
||||
logger.info("Known Devices Topic have been cleared")
|
||||
|
||||
def refresh_known_devices(self, devname):
|
||||
"""
|
||||
Refreshes the list of known devices by publishing the updated list to the MQTT topic.
|
||||
Args:
|
||||
devname (str): The name of the device to be refreshed.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
self._publish(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", ",".join(self.known_topics), retain=True)
|
||||
if payload.decode() == "online":
|
||||
self._publish(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", " ", retain=True)
|
||||
logger.info("Known Devices Topic have been cleared")
|
||||
self.clear_hass()
|
||||
logger.info("All configuration from HASS has been resetet")
|
||||
|
||||
if topic.startswith(f"{self.topicPrefix.replace('/', '')}/entity"):
|
||||
logger.info(f"HASS Set Entity Messages {topic} received: {payload.decode()}")
|
||||
parts = topic.split("/")
|
||||
if self.message_producer is None:
|
||||
self.message_producer = MessageProducer(None)
|
||||
asyncio.create_task(self.message_producer.write_request(parts[2], payload.decode(), read_request_after=True))
|
||||
|
||||
def on_connect(self, client, flags, rc, properties):
|
||||
"""
|
||||
Callback function for when the client receives a CONNACK response from the server.
|
||||
Args:
|
||||
client (paho.mqtt.client.Client): The client instance for this callback.
|
||||
flags (dict): Response flags sent by the broker.
|
||||
rc (int): The connection result.
|
||||
properties (paho.mqtt.properties.Properties): The properties associated with the connection.
|
||||
Returns:
|
||||
None
|
||||
Logs:
|
||||
- Info: When connected successfully with result code 0.
|
||||
- Error: When failed to connect with a non-zero result code.
|
||||
"""
|
||||
|
||||
if rc == 0:
|
||||
logger.info(f"Connected to MQTT with result code {rc}")
|
||||
if len(self.homeAssistantAutoDiscoverTopic) > 0:
|
||||
@@ -218,19 +116,7 @@ class MQTTClient:
|
||||
else:
|
||||
logger.error(f"Failed to connect, return code {rc}")
|
||||
|
||||
def on_disconnect(self, client, packet, exc=None):
|
||||
"""
|
||||
Callback function that is called when the client disconnects from the MQTT broker.
|
||||
Args:
|
||||
client (paho.mqtt.client.Client): The MQTT client instance that disconnected.
|
||||
packet (paho.mqtt.client.MQTTMessage): The MQTT message packet received during disconnection.
|
||||
exc (Exception, optional): The exception that caused the disconnection, if any. Defaults to None.
|
||||
Logs:
|
||||
Logs an info message indicating disconnection.
|
||||
Logs a warning message indicating an unexpected disconnection and attempts to reconnect.
|
||||
Logs an error message if reconnection fails and retries every 5 seconds.
|
||||
"""
|
||||
|
||||
def on_disconnect(self, client, packet, exc=None):
|
||||
logger.info(f"Disconnected with result code ")
|
||||
logger.warning("Unexpected disconnection. Reconnecting...")
|
||||
while True:
|
||||
@@ -241,48 +127,33 @@ class MQTTClient:
|
||||
logger.error(f"Reconnection failed: {e}")
|
||||
time.sleep(5)
|
||||
|
||||
def _publish(self, topic, payload, qos=0, retain=False):
|
||||
"""
|
||||
Publish a message to a specified MQTT topic.
|
||||
Args:
|
||||
topic (str): The MQTT topic to publish to.
|
||||
payload (str): The message payload to publish.
|
||||
qos (int, optional): The Quality of Service level for message delivery. Defaults to 0.
|
||||
retain (bool, optional): If True, the message will be retained by the broker. Defaults to False.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
def _publish(self, topic, payload, qos=0, retain=False):
|
||||
logger.debug(f"MQTT Publish Topic: {topic} payload: {payload}")
|
||||
self.client.publish(f"{topic}", payload, qos, retain)
|
||||
#time.sleep(0.1)
|
||||
|
||||
def refresh_known_devices(self, devname):
|
||||
self.known_topics.append(devname)
|
||||
if self.config.LOGGING['deviceAdded']:
|
||||
logger.info(f"Device added no. {len(self.known_topics):<3}: {devname} ")
|
||||
else:
|
||||
logger.debug(f"Device added no. {len(self.known_topics):<3}: {devname} ")
|
||||
self._publish(f"{self.topicPrefix.replace('/', '')}/{self.known_devices_topic}", ",".join(self.known_topics), retain=True)
|
||||
|
||||
def publish_message(self, name, value):
|
||||
"""
|
||||
Publishes a message to an MQTT topic.
|
||||
This method normalizes the given name, determines the appropriate MQTT topic,
|
||||
and publishes the provided value to that topic. If Home Assistant auto-discovery
|
||||
is enabled, it will also handle the auto-discovery configuration.
|
||||
Args:
|
||||
name (str): The name of the sensor or device.
|
||||
value (int, float, bool, str): The value to be published. If the value is a float,
|
||||
it will be rounded to two decimal places.
|
||||
Raises:
|
||||
KeyError: If the name is not found in the NASA_REPO configuration.
|
||||
"""
|
||||
|
||||
async def publish_message(self, name, value):
|
||||
newname = f"{self._normalize_name(name)}"
|
||||
|
||||
if len(self.homeAssistantAutoDiscoverTopic) > 0:
|
||||
sensor_type = "sensor"
|
||||
if 'enum' in self.config.NASA_REPO[name]:
|
||||
enum = [*self.config.NASA_REPO[name]['enum'].values()]
|
||||
if all([en.lower() in ['on', 'off'] for en in enum]):
|
||||
sensor_type = "binary_sensor"
|
||||
topicname = f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/{sensor_type}/{self.DEVICE_ID}_{newname.lower()}/state"
|
||||
if name not in self.known_topics:
|
||||
self.auto_discover_hass(topicname, name, newname, sensor_type)
|
||||
|
||||
if name not in self.known_topics:
|
||||
self.auto_discover_hass(name)
|
||||
self.refresh_known_devices(name)
|
||||
|
||||
if self.config.NASA_REPO[name]['hass_opts']['writable']:
|
||||
sensor_type = self.config.NASA_REPO[name]['hass_opts']['platform']['type']
|
||||
else:
|
||||
sensor_type = self.config.NASA_REPO[name]['hass_opts']['default_platform']
|
||||
topicname = f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/{sensor_type}/{self.DEVICE_ID}_{newname.lower()}/state"
|
||||
else:
|
||||
topicname = f"{self.topicPrefix.replace('/', '')}/{newname}"
|
||||
|
||||
@@ -291,78 +162,24 @@ class MQTTClient:
|
||||
|
||||
self._publish(topicname, value, qos=2, retain=False)
|
||||
|
||||
def auto_discover_hass(self, topicname, nameraw, namenorm, sensor_type):
|
||||
"""
|
||||
Automatically discovers and configures Home Assistant entities for the MQTT client.
|
||||
This function creates and publishes a configuration payload for Home Assistant's MQTT discovery.
|
||||
It supports both sensor and binary sensor types, and sets appropriate attributes based on the
|
||||
provided sensor type and unit of measurement.
|
||||
Args:
|
||||
topicname (str): The MQTT topic name.
|
||||
nameraw (str): The raw name of the sensor.
|
||||
namenorm (str): The normalized name of the sensor.
|
||||
sensor_type (str): The type of the sensor (e.g., "sensor" or "binary_sensor").
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
def clear_hass(self):
|
||||
entities = {}
|
||||
for nasa in self.config.NASA_REPO:
|
||||
namenorm = self._normalize_name(nasa)
|
||||
if self.config.NASA_REPO[nasa]['hass_opts']['writable']:
|
||||
sensor_type = self.config.NASA_REPO[nasa]['hass_opts']['platform']['type']
|
||||
else:
|
||||
sensor_type = self.config.NASA_REPO[nasa]['hass_opts']['default_platform']
|
||||
entities[namenorm] = {"platform": sensor_type}
|
||||
|
||||
entity = { namenorm: {
|
||||
"name": f"{namenorm}",
|
||||
"object_id": f"{self.DEVICE_ID}_{namenorm.lower()}",
|
||||
"unique_id": f"{self.DEVICE_ID}_{nameraw.lower()}",
|
||||
"platform": sensor_type,
|
||||
"value_template": "{{ value }}",
|
||||
"state_topic": f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/{sensor_type}/{self.DEVICE_ID}_{namenorm.lower()}/state",
|
||||
}
|
||||
}
|
||||
|
||||
if sensor_type == "sensor":
|
||||
if len(self.config.NASA_REPO[nameraw]['unit']) > 0:
|
||||
entity[namenorm]['unit_of_measurement'] = self.config.NASA_REPO[nameraw]['unit']
|
||||
if entity[namenorm]['unit_of_measurement'] == "\u00b0C":
|
||||
entity[namenorm]['device_class'] = "temperature"
|
||||
elif entity[namenorm]['unit_of_measurement'] == '%':
|
||||
entity[namenorm]['device_class'] = "power_factor"
|
||||
elif entity[namenorm]['unit_of_measurement'] == 'kW':
|
||||
entity[namenorm]['device_class'] = "power"
|
||||
elif entity[namenorm]['unit_of_measurement'] == 'rpm':
|
||||
entity[namenorm]['state_class'] = "measurement"
|
||||
elif entity[namenorm]['unit_of_measurement'] == 'bar':
|
||||
entity[namenorm]['device_class'] = "pressure"
|
||||
elif entity[namenorm]['unit_of_measurement'] == 'HP':
|
||||
entity[namenorm]['device_class'] = "power"
|
||||
elif entity[namenorm]['unit_of_measurement'] == 'hz':
|
||||
entity[namenorm]['device_class'] = "frequency"
|
||||
else:
|
||||
entity[namenorm]['device_class'] = None
|
||||
else:
|
||||
entity[namenorm]['payload_on'] = "ON"
|
||||
entity[namenorm]['payload_off'] = "OFF"
|
||||
|
||||
if 'state_class' in self.config.NASA_REPO[nameraw]:
|
||||
entity[namenorm]['state_class'] = self.config.NASA_REPO[nameraw]['state_class']
|
||||
|
||||
if 'device_class' in self.config.NASA_REPO[nameraw]:
|
||||
entity[namenorm]['device_class'] = self.config.NASA_REPO[nameraw]['device_class']
|
||||
|
||||
|
||||
device = {
|
||||
"device": {
|
||||
"identifiers": self.DEVICE_ID,
|
||||
"name": "Samsung EHS",
|
||||
"manufacturer": "Samsung",
|
||||
"model": "Mono HQ Quiet",
|
||||
"sw_version": "1.0.0"
|
||||
},
|
||||
"origin": {
|
||||
"name": "EHS-Sentinel",
|
||||
"support_url": "https://github.com/echoDaveD/EHS-Sentinel"
|
||||
},
|
||||
"components": entity,
|
||||
"device": self._get_device(),
|
||||
"origin": self._get_origin(),
|
||||
"components": entities,
|
||||
"qos": 2
|
||||
}
|
||||
|
||||
logger.debug(f"Auto Discovery HomeAssistant Message: ")
|
||||
logger.debug(f"Auto Discovery HomeAssistant Clear Message: ")
|
||||
logger.debug(f"{device}")
|
||||
|
||||
self._publish(f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/device/{self.DEVICE_ID}/config",
|
||||
@@ -370,23 +187,80 @@ class MQTTClient:
|
||||
qos=2,
|
||||
retain=True)
|
||||
|
||||
self.known_topics.append(nameraw)
|
||||
self.refresh_known_devices(nameraw)
|
||||
def auto_discover_hass(self, name):
|
||||
entity = {}
|
||||
namenorm = self._normalize_name(name)
|
||||
entity = {
|
||||
"name": f"{namenorm}",
|
||||
"object_id": f"{self.DEVICE_ID}_{namenorm.lower()}",
|
||||
"unique_id": f"{self.DEVICE_ID}_{name.lower()}",
|
||||
"force_update": True,
|
||||
#"expire_after": 86400, # 1 day (24h * 60m * 60s)
|
||||
"value_template": "{{ value }}"
|
||||
#"value_template": "{{ value if value | length > 0 else 'unavailable' }}",
|
||||
}
|
||||
if self.config.NASA_REPO[name]['hass_opts']['writable'] and self.config.GENERAL['allowControl']:
|
||||
sensor_type = self.config.NASA_REPO[name]['hass_opts']['platform']['type']
|
||||
if sensor_type == 'select':
|
||||
entity['options'] = self.config.NASA_REPO[name]['hass_opts']['platform']['options']
|
||||
if sensor_type == 'number':
|
||||
entity['mode'] = self.config.NASA_REPO[name]['hass_opts']['platform']['mode']
|
||||
entity['min'] = self.config.NASA_REPO[name]['hass_opts']['platform']['min']
|
||||
entity['max'] = self.config.NASA_REPO[name]['hass_opts']['platform']['max']
|
||||
if 'step' in self.config.NASA_REPO[name]['hass_opts']['platform']:
|
||||
entity['step'] = self.config.NASA_REPO[name]['hass_opts']['platform']['step']
|
||||
|
||||
entity['command_topic'] = f"{self.topicPrefix.replace('/', '')}/entity/{name}/set"
|
||||
entity['optimistic'] = False
|
||||
else:
|
||||
sensor_type = self.config.NASA_REPO[name]['hass_opts']['default_platform']
|
||||
|
||||
if 'unit' in self.config.NASA_REPO[name]['hass_opts']:
|
||||
entity['unit_of_measurement'] = self.config.NASA_REPO[name]['hass_opts']['unit']
|
||||
|
||||
entity['platform'] = sensor_type
|
||||
entity['state_topic'] = f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/{sensor_type}/{self.DEVICE_ID}_{namenorm.lower()}/state"
|
||||
|
||||
if 'payload_off' in self.config.NASA_REPO[name]['hass_opts']['platform']:
|
||||
entity['payload_off'] = "OFF"
|
||||
if 'payload_on' in self.config.NASA_REPO[name]['hass_opts']['platform']:
|
||||
entity['payload_on'] = "ON"
|
||||
if 'state_class' in self.config.NASA_REPO[name]['hass_opts']:
|
||||
entity['state_class'] = self.config.NASA_REPO[name]['hass_opts']['state_class']
|
||||
if 'device_class' in self.config.NASA_REPO[name]['hass_opts']:
|
||||
entity['device_class'] = self.config.NASA_REPO[name]['hass_opts']['device_class']
|
||||
|
||||
device = {
|
||||
"device": self._get_device(),
|
||||
"origin": self._get_origin(),
|
||||
"qos": 2
|
||||
}
|
||||
device.update(entity)
|
||||
|
||||
logger.debug(f"Auto Discovery HomeAssistant Message: ")
|
||||
logger.debug(f"{device}")
|
||||
|
||||
self._publish(f"{self.config.MQTT['homeAssistantAutoDiscoverTopic']}/{sensor_type}/{self.DEVICE_ID}_{name.lower()}/config",
|
||||
json.dumps(device, ensure_ascii=False),
|
||||
qos=2,
|
||||
retain=True)
|
||||
|
||||
def _get_device(self):
|
||||
return {
|
||||
"identifiers": self.DEVICE_ID,
|
||||
"name": "Samsung EHS",
|
||||
"manufacturer": "Samsung",
|
||||
"model": "Mono HQ Quiet",
|
||||
"sw_version": "1.0.0"
|
||||
}
|
||||
|
||||
def _get_origin(self):
|
||||
return {
|
||||
"name": "EHS-Sentinel",
|
||||
"support_url": "https://github.com/echoDaveD/EHS-Sentinel"
|
||||
}
|
||||
|
||||
def _normalize_name(self, name):
|
||||
"""
|
||||
Normalize the given name based on the specified naming convention.
|
||||
If `useCamelCaseTopicNames` is True, the function will:
|
||||
- Remove any of the following prefixes from the name: 'ENUM_', 'LVAR_', 'NASA_', 'VAR_'.
|
||||
- Convert the name to CamelCase format.
|
||||
If `useCamelCaseTopicNames` is False, the function will return the name as is.
|
||||
Args:
|
||||
name (str): The name to be normalized.
|
||||
Returns:
|
||||
str: The normalized name.
|
||||
"""
|
||||
|
||||
if self.useCamelCaseTopicNames:
|
||||
prefix_to_remove = ['ENUM_', 'LVAR_', 'NASA_', 'VAR_']
|
||||
# remove unnecessary prefixes of name
|
||||
|
||||
@@ -2,7 +2,7 @@ import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
import yaml
|
||||
from CustomLogger import logger, setSilent
|
||||
from CustomLogger import logger
|
||||
from EHSArguments import EHSArguments
|
||||
from EHSConfig import EHSConfig
|
||||
from EHSExceptions import MessageWarningException
|
||||
@@ -14,158 +14,133 @@ from NASAPacket import NASAPacket
|
||||
class MessageProcessor:
|
||||
"""
|
||||
The MessageProcessor class is responsible for handling and processing incoming messages for the EHS-Sentinel system.
|
||||
It follows the singleton pattern to ensure only one instance is created. The class provides methods to process
|
||||
messages, extract submessages, search for message definitions in a configuration repository, and determine the
|
||||
value of message payloads based on predefined rules. It also includes logging for debugging and tracing the
|
||||
The class provides methods to process messages, extract submessages, search for message definitions in a configuration repository,
|
||||
and determine the value of message payloads based on predefined rules. It also includes logging for debugging and tracing the
|
||||
message processing steps.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Create a new instance of the class if one does not already exist.
|
||||
This method ensures that only one instance of the class is created (singleton pattern).
|
||||
If an instance already exists, it returns the existing instance.
|
||||
Args:
|
||||
cls (type): The class being instantiated.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
Returns:
|
||||
MessageProcessor: The single instance of the MessageProcessor class.
|
||||
"""
|
||||
if not cls._instance:
|
||||
cls._instance = super(MessageProcessor, cls).__new__(cls, *args, **kwargs)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the MessageProcessor instance.
|
||||
This constructor checks if the instance has already been initialized to prevent reinitialization.
|
||||
If not initialized, it sets the _initialized flag to True, logs the initialization process,
|
||||
and initializes the configuration and argument handling components.
|
||||
Attributes:
|
||||
_initialized (bool): A flag indicating whether the instance has been initialized.
|
||||
config (EHSConfig): An instance of the EHSConfig class for configuration management.
|
||||
args (EHSArguments): An instance of the EHSArguments class for argument handling.
|
||||
"""
|
||||
if self._initialized:
|
||||
return
|
||||
self._initialized = True
|
||||
logger.debug("init MessageProcessor")
|
||||
self.config = EHSConfig()
|
||||
self.args = EHSArguments()
|
||||
self.mqtt = MQTTClient()
|
||||
self.NASA_VAL_STORE = {}
|
||||
|
||||
def process_message(self, packet: NASAPacket):
|
||||
"""
|
||||
Processes an incoming packet .
|
||||
Args:
|
||||
message (list): A list of integers representing the message bytes.
|
||||
Raises:
|
||||
MessageWarningException: If the message is invalid due to missing end byte, incorrect length, or other processing errors.
|
||||
Logs:
|
||||
Various debug and info logs to trace the processing steps, including packet size, raw and hex message content, source address, capacity, and extracted message details.
|
||||
"""
|
||||
async def process_message(self, packet: NASAPacket):
|
||||
for msg in packet.packet_messages:
|
||||
hexmsg = hex(msg.packet_message)
|
||||
hexmsg = f"0x{msg.packet_message:04x}" #hex(msg.packet_message)
|
||||
msgname = self.search_nasa_table(hexmsg)
|
||||
if msgname is not None:
|
||||
try:
|
||||
msgvalue = self.determine_value(msg.packet_payload, msgname)
|
||||
msgvalue = self.determine_value(msg.packet_payload, msgname, msg.packet_message_type)
|
||||
except Exception as e:
|
||||
raise MessageWarningException(argument=msg['payload'], message=f"Value of {hexmsg:<6} couldn't be determinate, skip Message {e}")
|
||||
self.protocolMessage(msg, msgname, msgvalue)
|
||||
raise MessageWarningException(argument=f"{msg.packet_payload}/{[hex(x) for x in msg.packet_payload]}", message=f"Value of {hexmsg} couldn't be determinate, skip Message {e}")
|
||||
await self.protocolMessage(msg, msgname, msgvalue)
|
||||
else:
|
||||
logger.debug(f"Message not Found in NASA repository: {hexmsg:<6} Type: {msg.packet_message_type} Payload: {msg.packet_payload}")
|
||||
packedval = int.from_bytes(msg.packet_payload, byteorder='big', signed=True)
|
||||
if self.config.LOGGING['messageNotFound']:
|
||||
logger.info(f"Message not Found in NASA repository: {hexmsg:<6} Type: {msg.packet_message_type} Payload: {msg.packet_payload} = {packedval}")
|
||||
else:
|
||||
logger.debug(f"Message not Found in NASA repository: {hexmsg:<6} Type: {msg.packet_message_type} Payload: {msg.packet_payload} = {packedval}")
|
||||
|
||||
def protocolMessage(self, msg: NASAMessage, msgname, msgvalue):
|
||||
"""
|
||||
Processes a protocol message by logging, writing to a protocol file, publishing via MQTT,
|
||||
and updating internal value store. Additionally, it calculates and processes specific
|
||||
derived values based on certain message names.
|
||||
Args:
|
||||
msg (NASAMessage): The NASA message object containing packet information.
|
||||
msgname (str): The name of the message.
|
||||
msgvalue (Any): The value of the message.
|
||||
Side Effects:
|
||||
- Logs the message details.
|
||||
- Appends the message details to a protocol file if configured.
|
||||
- Publishes the message via MQTT.
|
||||
- Updates the internal NASA value store with the message value.
|
||||
- Calculates and processes derived values for specific message names.
|
||||
"""
|
||||
async def protocolMessage(self, msg: NASAMessage, msgname, msgvalue):
|
||||
|
||||
logger.info(f"Message number: {hex(msg.packet_message):<6} {msgname:<50} Type: {msg.packet_message_type} Payload: {msgvalue}")
|
||||
if self.config.LOGGING['proccessedMessage']:
|
||||
logger.info(f"Message number: {hex(msg.packet_message):<6} {msgname:<50} Type: {msg.packet_message_type} Payload: {msgvalue} ({msg.packet_payload})")
|
||||
else:
|
||||
logger.debug(f"Message number: {hex(msg.packet_message):<6} {msgname:<50} Type: {msg.packet_message_type} Payload: {msgvalue}")
|
||||
|
||||
if self.config.GENERAL['protocolFile'] is not None:
|
||||
with open(self.config.GENERAL['protocolFile'], "a") as protWriter:
|
||||
protWriter.write(f"{hex(msg.packet_message):<6},{msg.packet_message_type},{msgname:<50},{msgvalue}\n")
|
||||
|
||||
self.mqtt.publish_message(msgname, msgvalue)
|
||||
await self.mqtt.publish_message(msgname, msgvalue)
|
||||
|
||||
self.NASA_VAL_STORE[msgname] = msgvalue
|
||||
self.config.NASA_VAL_STORE[msgname] = msgvalue
|
||||
|
||||
if msgname in ['NASA_OUTDOOR_TW2_TEMP', 'NASA_OUTDOOR_TW1_TEMP', 'VAR_IN_FLOW_SENSOR_CALC']:
|
||||
if all(k in self.NASA_VAL_STORE for k in ['NASA_OUTDOOR_TW2_TEMP', 'NASA_OUTDOOR_TW1_TEMP', 'VAR_IN_FLOW_SENSOR_CALC']):
|
||||
self.protocolMessage(NASAMessage(packet_message=0x9999, packet_message_type=1),
|
||||
"NASA_EHSSENTINEL_HEAT_OUTPUT",
|
||||
round(
|
||||
(
|
||||
(self.NASA_VAL_STORE['NASA_OUTDOOR_TW2_TEMP'] - self.NASA_VAL_STORE['NASA_OUTDOOR_TW1_TEMP']) *
|
||||
(self.NASA_VAL_STORE['VAR_IN_FLOW_SENSOR_CALC']/60)
|
||||
* 4190
|
||||
), 4))
|
||||
if all(k in self.config.NASA_VAL_STORE for k in ['NASA_OUTDOOR_TW2_TEMP', 'NASA_OUTDOOR_TW1_TEMP', 'VAR_IN_FLOW_SENSOR_CALC']):
|
||||
value = round(
|
||||
abs(
|
||||
(self.config.NASA_VAL_STORE['NASA_OUTDOOR_TW2_TEMP'] - self.config.NASA_VAL_STORE['NASA_OUTDOOR_TW1_TEMP']) *
|
||||
(self.config.NASA_VAL_STORE['VAR_IN_FLOW_SENSOR_CALC']/60)
|
||||
* 4190
|
||||
) , 4
|
||||
)
|
||||
if (value < 15000 and value > 0): # only if heater output between 0 und 15000 W
|
||||
await self.protocolMessage(NASAMessage(packet_message=0x9999, packet_message_type=1, packet_payload=[0]),
|
||||
"NASA_EHSSENTINEL_HEAT_OUTPUT",
|
||||
value
|
||||
)
|
||||
|
||||
if msgname in ('NASA_EHSSENTINEL_HEAT_OUTPUT', 'NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT'):
|
||||
if all(k in self.NASA_VAL_STORE for k in ['NASA_EHSSENTINEL_HEAT_OUTPUT', 'NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT']):
|
||||
self.protocolMessage(NASAMessage(packet_message=0x9998, packet_message_type=1),
|
||||
"NASA_EHSSENTINEL_COP",
|
||||
round((self.NASA_VAL_STORE['NASA_EHSSENTINEL_HEAT_OUTPUT'] / self.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT']/1000.), 3))
|
||||
if all(k in self.config.NASA_VAL_STORE for k in ['NASA_EHSSENTINEL_HEAT_OUTPUT', 'NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT']):
|
||||
if (self.config.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT'] > 0):
|
||||
value = round((self.config.NASA_VAL_STORE['NASA_EHSSENTINEL_HEAT_OUTPUT'] / self.config.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT']/1000.), 3)
|
||||
if (value < 20 and value > 0):
|
||||
await self.protocolMessage(NASAMessage(packet_message=0x9998, packet_message_type=1, packet_payload=[0]),
|
||||
"NASA_EHSSENTINEL_COP",
|
||||
value
|
||||
)
|
||||
|
||||
if msgname in ('NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM', 'LVAR_IN_TOTAL_GENERATED_POWER'):
|
||||
if all(k in self.config.NASA_VAL_STORE for k in ['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM', 'LVAR_IN_TOTAL_GENERATED_POWER']):
|
||||
if (self.config.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM'] > 0):
|
||||
value = round(self.config.NASA_VAL_STORE['LVAR_IN_TOTAL_GENERATED_POWER'] / self.config.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM'], 3)
|
||||
|
||||
if (value < 20 and value > 0):
|
||||
await self.protocolMessage(NASAMessage(packet_message=0x9997, packet_message_type=1, packet_payload=[0]),
|
||||
"NASA_EHSSENTINEL_TOTAL_COP",
|
||||
value
|
||||
)
|
||||
|
||||
def search_nasa_table(self, address):
|
||||
"""
|
||||
Searches for a specific address in the NASA_REPO configuration and returns the corresponding key.
|
||||
Args:
|
||||
address (str): The address to search for in the NASA_REPO.
|
||||
Returns:
|
||||
str: The key associated with the given address if found, otherwise None.
|
||||
"""
|
||||
for key, value in self.config.NASA_REPO.items():
|
||||
if value['address'].lower() == address:
|
||||
return key
|
||||
|
||||
def determine_value(self, rawvalue, msgname):
|
||||
"""
|
||||
Determines the processed value from a raw byte input based on the message name configuration.
|
||||
Args:
|
||||
rawvalue (bytes): The raw byte value to be processed.
|
||||
msgname (str): The name of the message which determines the processing rules.
|
||||
Returns:
|
||||
float or str: The processed value, which could be a numerical value or an enumerated string.
|
||||
Raises:
|
||||
Warning: Logs a warning if the arithmetic function cannot be applied and uses the raw value instead.
|
||||
"""
|
||||
arithmetic = self.config.NASA_REPO[msgname]['arithmetic'].replace("value", 'packed_value')
|
||||
|
||||
def is_valid_rawvalue(self, rawvalue: bytes) -> bool:
|
||||
return all(0x20 <= b <= 0x7E or b in (0x00, 0xFF) for b in rawvalue)
|
||||
|
||||
packed_value = int.from_bytes(rawvalue, byteorder='big', signed=True)
|
||||
def determine_value(self, rawvalue, msgname, packet_message_type):
|
||||
if packet_message_type == 3:
|
||||
value = ""
|
||||
|
||||
if len(arithmetic) > 0:
|
||||
try:
|
||||
value = eval(arithmetic)
|
||||
except Exception as e:
|
||||
logger.warning(f"Arithmetic Function couldn't been applied, using raw value: arithmetic = {arithmetic} {e}")
|
||||
value = packed_value
|
||||
else:
|
||||
value = packed_value
|
||||
|
||||
if self.config.NASA_REPO[msgname]['type'] == 'ENUM':
|
||||
if 'enum' in self.config.NASA_REPO[msgname]:
|
||||
value = self.config.NASA_REPO[msgname]['enum'][int.from_bytes(rawvalue, byteorder='big')].upper()
|
||||
if self.is_valid_rawvalue(rawvalue[1:-1]):
|
||||
for byte in rawvalue[1:-1]:
|
||||
if byte != 0x00 and byte != 0xFF:
|
||||
char = chr(byte) if 32 <= byte <= 126 else f"{byte}"
|
||||
value += char
|
||||
else:
|
||||
value += " "
|
||||
value = value.strip()
|
||||
else:
|
||||
value = f"Unknown enum value: {value}"
|
||||
value = "".join([f"{int(x)}" for x in rawvalue])
|
||||
|
||||
#logger.info(f"{msgname} Structure: {rawvalue} type of {value}")
|
||||
else:
|
||||
if 'arithmetic' in self.config.NASA_REPO[msgname]:
|
||||
arithmetic = self.config.NASA_REPO[msgname]['arithmetic'].replace("value", 'packed_value')
|
||||
else:
|
||||
arithmetic = ''
|
||||
|
||||
packed_value = int.from_bytes(rawvalue, byteorder='big', signed=True)
|
||||
|
||||
if len(arithmetic) > 0:
|
||||
try:
|
||||
value = eval(arithmetic)
|
||||
except Exception as e:
|
||||
logger.warning(f"Arithmetic Function couldn't been applied for Message {msgname}, using raw value: arithmetic = {arithmetic} {e} {packed_value} {rawvalue}")
|
||||
value = packed_value
|
||||
else:
|
||||
value = packed_value
|
||||
|
||||
value = round(value, 3)
|
||||
|
||||
if 'type' in self.config.NASA_REPO[msgname]:
|
||||
if self.config.NASA_REPO[msgname]['type'] == 'ENUM':
|
||||
if 'enum' in self.config.NASA_REPO[msgname]:
|
||||
value = self.config.NASA_REPO[msgname]['enum'][int.from_bytes(rawvalue, byteorder='big')]
|
||||
else:
|
||||
value = f"Unknown enum value: {value}"
|
||||
|
||||
return value
|
||||
|
||||
152
MessageProducer.py
Normal file
152
MessageProducer.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from CustomLogger import logger
|
||||
from EHSArguments import EHSArguments
|
||||
from EHSConfig import EHSConfig
|
||||
from EHSExceptions import MessageWarningException
|
||||
import asyncio
|
||||
|
||||
from NASAMessage import NASAMessage
|
||||
from NASAPacket import NASAPacket, AddressClassEnum, PacketType, DataType
|
||||
|
||||
class MessageProducer:
|
||||
"""
|
||||
The MessageProducer class is responsible for sending messages to the EHS-Sentinel system.
|
||||
It follows the singleton pattern to ensure only one instance is created. The class provides methods to request and write
|
||||
messages and transforme the value of message payloads based on predefined rules. It also includes logging for debugging and tracing the
|
||||
message producing steps.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_CHUNKSIZE = 10 # message requests list will be split into this chunks, experience have shown that more then 10 are too much for an packet
|
||||
writer = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance:
|
||||
cls._instance = super(MessageProducer, cls).__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, writer: asyncio.StreamWriter):
|
||||
if self._initialized:
|
||||
return
|
||||
self._initialized = True
|
||||
self.writer = writer
|
||||
self.config = EHSConfig()
|
||||
|
||||
async def read_request(self, list_of_messages: list):
|
||||
chunks = [list_of_messages[i:i + self._CHUNKSIZE] for i in range(0, len(list_of_messages), self._CHUNKSIZE)]
|
||||
for chunk in chunks:
|
||||
await asyncio.sleep(0.5)
|
||||
nasa_packet = self._build_default_read_packet()
|
||||
nasa_packet.set_packet_messages([self._build_message(x) for x in chunk])
|
||||
await self._write_packet_to_serial(nasa_packet)
|
||||
|
||||
if self.config.LOGGING['pollerMessage']:
|
||||
logger.info(f"Polling following NASAPacket: {nasa_packet}")
|
||||
else:
|
||||
logger.debug(f"Sent data NASAPacket: {nasa_packet}")
|
||||
|
||||
async def write_request(self, message: str, value: str | int, read_request_after=False):
|
||||
nasa_packet = self._build_default_request_packet()
|
||||
nasa_packet.set_packet_messages([self._build_message(message.strip(), self._decode_value(message.strip(), value.strip()))])
|
||||
nasa_packet.to_raw()
|
||||
if self.config.LOGGING['controlMessage']:
|
||||
logger.info(f"Write request for {message} with value: {value}")
|
||||
logger.info(f"Sending NASA packet: {nasa_packet}")
|
||||
else:
|
||||
logger.debug(f"Write request for {message} with value: {value}")
|
||||
logger.debug(f"Sending NASA packet: {nasa_packet}")
|
||||
await self._write_packet_to_serial(nasa_packet)
|
||||
await asyncio.sleep(1)
|
||||
await self.read_request([message])
|
||||
|
||||
def _search_nasa_enumkey_for_value(self, message, value):
|
||||
if 'type' in self.config.NASA_REPO[message] and self.config.NASA_REPO[message]['type'] == 'ENUM':
|
||||
for key, val in self.config.NASA_REPO[message]['enum'].items():
|
||||
if val == value:
|
||||
return key
|
||||
|
||||
return None
|
||||
|
||||
def is_number(self, s):
|
||||
return s.replace('+','',1).replace('-','',1).replace('.','',1).isdigit()
|
||||
|
||||
def _decode_value(self, message, value) -> int:
|
||||
enumval = self._search_nasa_enumkey_for_value(message, value)
|
||||
if enumval is None:
|
||||
if self.is_number(value):
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError as e:
|
||||
value = float(value)
|
||||
|
||||
if 'reverse-arithmetic' in self.config.NASA_REPO[message]:
|
||||
arithmetic = self.config.NASA_REPO[message]['reverse-arithmetic']
|
||||
else:
|
||||
arithmetic = ''
|
||||
if len(arithmetic) > 0:
|
||||
try:
|
||||
return int(eval(arithmetic))
|
||||
except Exception as e:
|
||||
logger.warning(f"Arithmetic Function couldn't been applied for Message {message}, using raw value: reverse-arithmetic = {arithmetic} {e} {value}")
|
||||
return value
|
||||
else:
|
||||
value = int(enumval)
|
||||
|
||||
return value
|
||||
|
||||
def _build_message(self, message, value=None) -> NASAMessage:
|
||||
tmpmsg = NASAMessage()
|
||||
tmpmsg.set_packet_message(self._extract_address(message))
|
||||
if value is None:
|
||||
value = 0
|
||||
if tmpmsg.packet_message_type == 0:
|
||||
value_raw = value.to_bytes(1, byteorder='big', signed=True)
|
||||
elif tmpmsg.packet_message_type == 1:
|
||||
value_raw = value.to_bytes(2, byteorder='big', signed=True)
|
||||
elif tmpmsg.packet_message_type == 2:
|
||||
value_raw = value.to_bytes(4, byteorder='big', signed=True)
|
||||
else:
|
||||
raise MessageWarningException(argument=tmpmsg.packet_message_type, message=f"Unknown Type for {message} type:")
|
||||
tmpmsg.set_packet_payload_raw(value_raw)
|
||||
return tmpmsg
|
||||
|
||||
def _extract_address(self, messagename) -> int:
|
||||
return int(self.config.NASA_REPO[messagename]['address'], 16)
|
||||
|
||||
def _build_default_read_packet(self) -> NASAPacket:
|
||||
nasa_msg = NASAPacket()
|
||||
nasa_msg.set_packet_source_address_class(AddressClassEnum.JIGTester)
|
||||
nasa_msg.set_packet_source_channel(255)
|
||||
nasa_msg.set_packet_source_address(0)
|
||||
nasa_msg.set_packet_dest_address_class(AddressClassEnum.BroadcastSetLayer)
|
||||
nasa_msg.set_packet_dest_channel(0)
|
||||
nasa_msg.set_packet_dest_address(32)
|
||||
nasa_msg.set_packet_information(True)
|
||||
nasa_msg.set_packet_version(2)
|
||||
nasa_msg.set_packet_retry_count(0)
|
||||
nasa_msg.set_packet_type(PacketType.Normal)
|
||||
nasa_msg.set_packet_data_type(DataType.Read)
|
||||
nasa_msg.set_packet_number(166)
|
||||
return nasa_msg
|
||||
|
||||
def _build_default_request_packet(self) -> NASAPacket:
|
||||
nasa_msg = NASAPacket()
|
||||
nasa_msg.set_packet_source_address_class(AddressClassEnum.JIGTester)
|
||||
nasa_msg.set_packet_source_channel(0)
|
||||
nasa_msg.set_packet_source_address(255)
|
||||
nasa_msg.set_packet_dest_address_class(AddressClassEnum.Indoor)
|
||||
nasa_msg.set_packet_dest_channel(0)
|
||||
nasa_msg.set_packet_dest_address(0)
|
||||
nasa_msg.set_packet_information(True)
|
||||
nasa_msg.set_packet_version(2)
|
||||
nasa_msg.set_packet_retry_count(0)
|
||||
nasa_msg.set_packet_type(PacketType.Normal)
|
||||
nasa_msg.set_packet_data_type(DataType.Request)
|
||||
nasa_msg.set_packet_number(166)
|
||||
return nasa_msg
|
||||
|
||||
async def _write_packet_to_serial(self, packet: NASAPacket):
|
||||
final_packet = packet.to_raw()
|
||||
self.writer.write(final_packet)
|
||||
await self.writer.drain()
|
||||
|
||||
@@ -2,58 +2,69 @@
|
||||
class NASAMessage:
|
||||
"""
|
||||
A class to represent a NASA message.
|
||||
Attributes
|
||||
----------
|
||||
packet_message : int
|
||||
The message packet identifier.
|
||||
packet_message_type : int
|
||||
The type of the message packet.
|
||||
packet_payload : bytes
|
||||
The payload of the message packet in bytes.
|
||||
Methods
|
||||
-------
|
||||
__str__():
|
||||
Returns a string representation of the NASAMessage instance.
|
||||
__repr__():
|
||||
Returns a string representation of the NASAMessage instance.
|
||||
"""
|
||||
def __init__(self, packet_message=0x000, packet_message_type=0, packet_payload=[0]):
|
||||
"""
|
||||
Constructs all the necessary attributes for the NASAMessage object.
|
||||
Parameters
|
||||
----------
|
||||
packet_message : int, optional
|
||||
The message packet identifier (default is 0x000).
|
||||
packet_message_type : int, optional
|
||||
The type of the message packet (default is 0).
|
||||
packet_payload : list, optional
|
||||
The payload of the message packet as a list of integers (default is [0]).
|
||||
"""
|
||||
"""
|
||||
Returns a string representation of the NASAMessage instance.
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A string representation of the NASAMessage instance.
|
||||
"""
|
||||
"""
|
||||
Returns a string representation of the NASAMessage instance.
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A string representation of the NASAMessage instance.
|
||||
"""
|
||||
|
||||
def __init__(self, packet_message=0x000, packet_message_type=0, packet_payload=[0]):
|
||||
self.packet_message: int = packet_message
|
||||
self.packet_message_type: int = packet_message_type
|
||||
self.packet_payload: bytes = bytes([int(hex(x), 16) for x in packet_payload])
|
||||
|
||||
|
||||
def set_packet_message(self, value: int):
|
||||
self.packet_message = value
|
||||
self.packet_message_type = (value & 1536) >> 9
|
||||
|
||||
def set_packet_message_type(self, value: int):
|
||||
self.packet_message_type = value
|
||||
|
||||
def set_packet_payload(self, value: list):
|
||||
self.packet_payload = bytes([int(hex(x), 16) for x in value])
|
||||
|
||||
def set_packet_payload_raw(self, value: bytes):
|
||||
self.packet_payload = value
|
||||
|
||||
def to_raw(self) -> bytearray:
|
||||
|
||||
message_number_reconstructed = (self.packet_message_type << 9) | (self.packet_message & 0x1FF)
|
||||
|
||||
# Extract the original bytes from message_number
|
||||
msg_rest_0 = (self.packet_message >> 8) & 0xFF # Upper 8 bits
|
||||
msg_rest_1 = self.packet_message & 0xFF # Lower 8 bits
|
||||
msgpayload = int.from_bytes(self.packet_payload, byteorder='big', signed=True)
|
||||
if self.packet_message_type == 0:
|
||||
return [
|
||||
msg_rest_0,
|
||||
msg_rest_1,
|
||||
msgpayload & 0xFF
|
||||
]
|
||||
elif self.packet_message_type == 1:
|
||||
return [
|
||||
msg_rest_0,
|
||||
msg_rest_1,
|
||||
(msgpayload >> 8) & 0xFF,
|
||||
msgpayload & 0xFF
|
||||
]
|
||||
elif self.packet_message_type == 2:
|
||||
return [
|
||||
msg_rest_0,
|
||||
msg_rest_1,
|
||||
(msgpayload >> 24) & 0xFF,
|
||||
(msgpayload >> 16) & 0xFF,
|
||||
(msgpayload >> 8) & 0xFF,
|
||||
msgpayload & 0xFF
|
||||
]
|
||||
elif self.packet_message_type == 3:
|
||||
return [
|
||||
msg_rest_0,
|
||||
msg_rest_1,
|
||||
*[(msgpayload >> (8 * i)) & 0xFF for i in reversed(range(len(self.packet_payload)))]
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"NASAMessage(\n"
|
||||
f" packet_message={self.packet_message} ({hex(self.packet_message)}),\n"
|
||||
f" packet_message={self.packet_message} ({hex(self.packet_message)}) ({[x for x in bytearray(self.packet_message.to_bytes(2))]}),\n"
|
||||
f" packet_message_type={self.packet_message_type} ({hex(self.packet_message_type)}),\n"
|
||||
f" packet_payload={self.packet_payload} ({self.packet_payload.hex()})\n"
|
||||
f" packet_payload={self.packet_payload} ({self.packet_payload.hex()}) ({[int(x) for x in self.packet_payload]})\n"
|
||||
f")"
|
||||
)
|
||||
|
||||
|
||||
237
NASAPacket.py
237
NASAPacket.py
@@ -1,37 +1,13 @@
|
||||
from enum import Enum
|
||||
from NASAMessage import NASAMessage
|
||||
from EHSExceptions import SkipInvalidPacketException
|
||||
import binascii
|
||||
import struct
|
||||
from CustomLogger import logger
|
||||
|
||||
class AddressClassEnum(Enum):
|
||||
"""
|
||||
Enum class representing various address classes for NASA packets.
|
||||
Attributes:
|
||||
Outdoor (int): Address class for outdoor units (0x10).
|
||||
HTU (int): Address class for HTU units (0x11).
|
||||
Indoor (int): Address class for indoor units (0x20).
|
||||
ERV (int): Address class for ERV units (0x30).
|
||||
Diffuser (int): Address class for diffuser units (0x35).
|
||||
MCU (int): Address class for MCU units (0x38).
|
||||
RMC (int): Address class for RMC units (0x40).
|
||||
WiredRemote (int): Address class for wired remote units (0x50).
|
||||
PIM (int): Address class for PIM units (0x58).
|
||||
SIM (int): Address class for SIM units (0x59).
|
||||
Peak (int): Address class for peak units (0x5A).
|
||||
PowerDivider (int): Address class for power divider units (0x5B).
|
||||
OnOffController (int): Address class for on/off controller units (0x60).
|
||||
WiFiKit (int): Address class for WiFi kit units (0x62).
|
||||
CentralController (int): Address class for central controller units (0x65).
|
||||
DMS (int): Address class for DMS units (0x6A).
|
||||
JIGTester (int): Address class for JIG tester units (0x80).
|
||||
BroadcastSelfLayer (int): Address class for broadcast self layer (0xB0).
|
||||
BroadcastControlLayer (int): Address class for broadcast control layer (0xB1).
|
||||
BroadcastSetLayer (int): Address class for broadcast set layer (0xB2).
|
||||
BroadcastCS (int): Address class for broadcast CS (0xB3).
|
||||
BroadcastControlAndSetLayer (int): Address class for broadcast control and set layer (0xB3).
|
||||
BroadcastModuleLayer (int): Address class for broadcast module layer (0xB4).
|
||||
BroadcastCSM (int): Address class for broadcast CSM (0xB7).
|
||||
BroadcastLocalLayer (int): Address class for broadcast local layer (0xB8).
|
||||
BroadcastCSML (int): Address class for broadcast CSML (0xBF).
|
||||
Undefined (int): Address class for undefined units (0xFF).
|
||||
"""
|
||||
|
||||
Outdoor = 0x10
|
||||
@@ -65,12 +41,6 @@ class AddressClassEnum(Enum):
|
||||
class PacketType(Enum):
|
||||
"""
|
||||
Enum class representing different types of packets in the EHS-Sentinel system.
|
||||
Attributes:
|
||||
StandBy (int): Represents a standby packet type with a value of 0.
|
||||
Normal (int): Represents a normal packet type with a value of 1.
|
||||
Gathering (int): Represents a gathering packet type with a value of 2.
|
||||
Install (int): Represents an install packet type with a value of 3.
|
||||
Download (int): Represents a download packet type with a value of 4.
|
||||
"""
|
||||
|
||||
StandBy = 0
|
||||
@@ -82,15 +52,6 @@ class PacketType(Enum):
|
||||
class DataType(Enum):
|
||||
"""
|
||||
Enum representing different types of data operations.
|
||||
Attributes:
|
||||
Undefined (int): Represents an undefined data type (0).
|
||||
Read (int): Represents a read operation (1).
|
||||
Write (int): Represents a write operation (2).
|
||||
Request (int): Represents a request operation (3).
|
||||
Notification (int): Represents a notification operation (4).
|
||||
Response (int): Represents a response operation (5).
|
||||
Ack (int): Represents an acknowledgment (6).
|
||||
Nack (int): Represents a negative acknowledgment (7).
|
||||
"""
|
||||
|
||||
Undefined = 0
|
||||
@@ -105,56 +66,6 @@ class DataType(Enum):
|
||||
class NASAPacket:
|
||||
"""
|
||||
A class to represent a NASA Packet.
|
||||
Attributes
|
||||
----------
|
||||
_packet_raw : bytearray
|
||||
Raw packet data.
|
||||
packet_start : int
|
||||
Start byte of the packet.
|
||||
packet_size : int
|
||||
Size of the packet.
|
||||
packet_source_address_class : AddressClassEnum
|
||||
Source address class of the packet.
|
||||
packet_source_channel : int
|
||||
Source channel of the packet.
|
||||
packet_source_address : int
|
||||
Source address of the packet.
|
||||
packet_dest_address_class : AddressClassEnum
|
||||
Destination address class of the packet.
|
||||
packet_dest_channel : int
|
||||
Destination channel of the packet.
|
||||
packet_dest_address : int
|
||||
Destination address of the packet.
|
||||
packet_information : int
|
||||
Information field of the packet.
|
||||
packet_version : int
|
||||
Version of the packet.
|
||||
packet_retry_count : int
|
||||
Retry count of the packet.
|
||||
packet_type : PacketType
|
||||
Type of the packet.
|
||||
packet_data_type : DataType
|
||||
Data type of the packet.
|
||||
packet_number : int
|
||||
Number of the packet.
|
||||
packet_capacity : int
|
||||
Capacity of the packet.
|
||||
packet_messages : list[NASAMessage]
|
||||
List of messages in the packet.
|
||||
packet_crc16 : int
|
||||
CRC16 checksum of the packet.
|
||||
packet_end : int
|
||||
End byte of the packet.
|
||||
Methods
|
||||
-------
|
||||
parse(packet: bytearray):
|
||||
Parses the given packet data.
|
||||
_extract_messages(depth: int, capacity: int, msg_rest: bytearray, return_list: list):
|
||||
Recursively extracts messages from the packet.
|
||||
__str__():
|
||||
Returns a string representation of the NASAPacket.
|
||||
__repr__():
|
||||
Returns a string representation of the NASAPacket.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -179,43 +90,30 @@ class NASAPacket:
|
||||
self.packet_end: int = None
|
||||
|
||||
def parse(self, packet: bytearray):
|
||||
"""
|
||||
Parses a given bytearray packet and extracts various fields into the object's attributes.
|
||||
Args:
|
||||
packet (bytearray): The packet to be parsed.
|
||||
Raises:
|
||||
ValueError: If the packet length is less than 14 bytes.
|
||||
Attributes:
|
||||
packet_start (int): The start byte of the packet.
|
||||
packet_size (int): The size of the packet.
|
||||
packet_source_address_class (AddressClassEnum): The source address class of the packet.
|
||||
packet_source_channel (int): The source channel of the packet.
|
||||
packet_source_address (int): The source address of the packet.
|
||||
packet_dest_address_class (AddressClassEnum): The destination address class of the packet.
|
||||
packet_dest_channel (int): The destination channel of the packet.
|
||||
packet_dest_address (int): The destination address of the packet.
|
||||
packet_information (bool): Information flag of the packet.
|
||||
packet_version (int): Version of the packet.
|
||||
packet_retry_count (int): Retry count of the packet.
|
||||
packet_type (PacketType): Type of the packet.
|
||||
packet_data_type (DataType): Data type of the packet.
|
||||
packet_number (int): Number of the packet.
|
||||
packet_capacity (int): Capacity of the packet.
|
||||
packet_crc16 (int): CRC16 checksum of the packet.
|
||||
packet_end (int): The end byte of the packet.
|
||||
packet_messages (list): Extracted messages from the packet.
|
||||
"""
|
||||
|
||||
self._packet_raw = packet
|
||||
if len(packet) < 14:
|
||||
raise ValueError("Data too short to be a valid NASAPacket")
|
||||
|
||||
crc_checkusm=binascii.crc_hqx(bytearray(packet[3:-3]), 0)
|
||||
|
||||
self.packet_start = packet[0]
|
||||
self.packet_size = ((packet[1] << 8) | packet[2])
|
||||
self.packet_source_address_class = AddressClassEnum(packet[3])
|
||||
|
||||
if self.packet_size+2 != len(packet):
|
||||
logger.info(f"length not correct {self.packet_size+2} -> {len(packet)}")
|
||||
logger.info(f"{packet.hex()}")
|
||||
logger.info(f"{hex(packet[self.packet_size+1])}")
|
||||
|
||||
try:
|
||||
self.packet_source_address_class = AddressClassEnum(packet[3])
|
||||
except ValueError as e:
|
||||
raise SkipInvalidPacketException(f"Source Adress Class out of enum {packet[3]}")
|
||||
self.packet_source_channel = packet[4]
|
||||
self.packet_source_address = packet[5]
|
||||
self.packet_dest_address_class = AddressClassEnum(packet[6])
|
||||
try:
|
||||
self.packet_dest_address_class = AddressClassEnum(packet[6])
|
||||
except ValueError as e:
|
||||
raise SkipInvalidPacketException(f"Destination Adress Class out of enum {packet[6]}")
|
||||
self.packet_dest_channel = packet[7]
|
||||
self.packet_dest_address = packet[8]
|
||||
self.packet_information = (int(packet[9]) & 128) >> 7 == 1
|
||||
@@ -225,25 +123,14 @@ class NASAPacket:
|
||||
self.packet_data_type = DataType(int(packet[10]) & 15)
|
||||
self.packet_number = packet[11]
|
||||
self.packet_capacity = packet[12]
|
||||
self.packet_crc16 = ((packet[-3] << 8) | packet[-2]) + 2
|
||||
self.packet_crc16 = ((packet[-3] << 8) | packet[-2]) # + 2
|
||||
self.packet_end = packet[-1]
|
||||
self.packet_messages = self._extract_messages(0, self.packet_capacity, packet[13:-3], [])
|
||||
|
||||
def _extract_messages(self, depth: int, capacity: int, msg_rest: bytearray, return_list: list):
|
||||
"""
|
||||
Recursively extracts messages from a bytearray and appends them to a list.
|
||||
Args:
|
||||
depth (int): The current depth of recursion.
|
||||
capacity (int): The maximum allowed depth of recursion.
|
||||
msg_rest (bytearray): The remaining bytes to be processed.
|
||||
return_list (list): The list to which extracted messages are appended.
|
||||
Returns:
|
||||
list: The list of extracted messages.
|
||||
Raises:
|
||||
ValueError: If the message type is unknown, the capacity is invalid for a structure type message,
|
||||
or the payload size exceeds 255 bytes.
|
||||
"""
|
||||
if crc_checkusm != self.packet_crc16:
|
||||
raise SkipInvalidPacketException(f"Checksum for package could not be validated. Calculated: {crc_checkusm} in packet: {self.packet_crc16}: packet:{self}")
|
||||
|
||||
def _extract_messages(self, depth: int, capacity: int, msg_rest: bytearray, return_list: list):
|
||||
if depth > capacity or len(msg_rest) <= 2:
|
||||
return return_list
|
||||
|
||||
@@ -259,9 +146,9 @@ class NASAPacket:
|
||||
elif message_type == 3:
|
||||
payload_size = len(msg_rest)
|
||||
if capacity != 1:
|
||||
raise ValueError(message="Message with structure type must have capacity of 1.")
|
||||
raise SkipInvalidPacketException("Message with structure type must have capacity of 1.")
|
||||
else:
|
||||
raise ValueError(message=f"Mssage type unknown: {message_type}")
|
||||
raise ValueError(f"Mssage type unknown: {message_type}")
|
||||
|
||||
payload = msg_rest[2:2 + payload_size]
|
||||
if len(payload) > 255:
|
||||
@@ -302,6 +189,78 @@ class NASAPacket:
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
# Setter methods
|
||||
def set_packet_source_address_class(self, value: AddressClassEnum):
|
||||
self.packet_source_address_class = value
|
||||
|
||||
def set_packet_source_channel(self, value: int):
|
||||
self.packet_source_channel = value
|
||||
|
||||
def set_packet_source_address(self, value: int):
|
||||
self.packet_source_address = value
|
||||
|
||||
def set_packet_dest_address_class(self, value: AddressClassEnum):
|
||||
self.packet_dest_address_class = value
|
||||
|
||||
def set_packet_dest_channel(self, value: int):
|
||||
self.packet_dest_channel = value
|
||||
|
||||
def set_packet_dest_address(self, value: int):
|
||||
self.packet_dest_address = value
|
||||
|
||||
def set_packet_information(self, value: bool):
|
||||
self.packet_information = value
|
||||
|
||||
def set_packet_version(self, value: int):
|
||||
self.packet_version = value
|
||||
|
||||
def set_packet_retry_count(self, value: int):
|
||||
self.packet_retry_count = value
|
||||
|
||||
def set_packet_type(self, value: PacketType):
|
||||
self.packet_type = value
|
||||
|
||||
def set_packet_data_type(self, value: DataType):
|
||||
self.packet_data_type = value
|
||||
|
||||
def set_packet_number(self, value: int):
|
||||
self.packet_number = value
|
||||
|
||||
def set_packet_messages(self, value: list[NASAMessage]):
|
||||
self.packet_messages = value
|
||||
|
||||
def to_raw(self) -> bytearray:
|
||||
self.packet_start = 50
|
||||
self.packet_end = 52
|
||||
|
||||
|
||||
packet = bytearray()
|
||||
packet.append(int(self.packet_start))
|
||||
packet.append(0)
|
||||
packet.append(0)
|
||||
packet.append(self.packet_source_address_class.value)
|
||||
packet.append(self.packet_source_channel)
|
||||
packet.append(self.packet_source_address)
|
||||
packet.append(self.packet_dest_address_class.value)
|
||||
packet.append(self.packet_dest_channel)
|
||||
packet.append(self.packet_dest_address)
|
||||
packet.append((self.packet_information << 7) | (self.packet_version << 5) | (self.packet_retry_count << 3))
|
||||
packet.append((self.packet_type.value << 4) | self.packet_data_type.value)
|
||||
packet.append(self.packet_number)
|
||||
packet.append(len(self.packet_messages))
|
||||
# Add messages to the packet
|
||||
for msg in self.packet_messages:
|
||||
for msg_pack in msg.to_raw():
|
||||
packet.append(msg_pack)
|
||||
self.packet_capacity = len(self.packet_messages)
|
||||
self.packet_size = len(packet)+2+2
|
||||
packet[1] = (self.packet_size >> 8) & 0xFF
|
||||
packet[2] = self.packet_size & 0xFF
|
||||
self.packet_crc16=binascii.crc_hqx(packet[3:], 0)
|
||||
final_packet = struct.pack(">BH", packet[0], len(packet[1:])+2) + packet[3:] + struct.pack(">HB", self.packet_crc16, 0x34)
|
||||
return final_packet
|
||||
|
||||
# Example usage:
|
||||
# packet = NASAPacket()
|
||||
# packet.parse(bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]))
|
||||
|
||||
392
README.md
392
README.md
@@ -7,6 +7,21 @@ Since extending these was often too difficult, I have written a script here whic
|
||||
|
||||
In addition, a few data points are generated from others, such as COP and Heat Output.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
You need an MQTT Broker.
|
||||
For Homeassistant you need the MQTT Plugin there with enabled Auto Discovery with Discovery Topic Prefix and Birth-Messages on Discovery Topic Prefix with subtopic "status" with text "online".
|
||||
EHS-Sentinel subscribes <hass_discovery_prefix>/status Topic and if it receive an "online", then it cleans his intern known-devices topic and send the Auto Discovery Config again for any Measurment for Home Assistant.
|
||||
|
||||
# Upgrade instructions
|
||||
|
||||
1. Stop EHS-Sentinel
|
||||
2. *Optional* If you are using HASS: Delete the MQTT Device
|
||||
3. git pull the new release or download and extract the release zip file
|
||||
4. Look into Release Notes if there are some new configurations and check if you have to ajust your configfile
|
||||
5. Start EHS-Sentinel (I recommend to use `--clean-known-devices` on the start so EHS-Sentinel will send Configuration messages for HASS Auto Discovery after every startup.)
|
||||
6. *Optional* If you are using HASS: and not use the `--clean-known-devices` Parm on Startup, send a birthmessage manualy or restart the MQTT Adapter in HASS.
|
||||
|
||||
# Installation
|
||||
|
||||
## Simple
|
||||
@@ -17,7 +32,9 @@ In addition, a few data points are generated from others, such as COP and Heat O
|
||||
`pip install -r requirements.txt`
|
||||
3. Copy the `data/config.yml` and provide your Configuration
|
||||
4. Start the Application:
|
||||
`python3 startEHSSentinel.py --configfile config.yml`
|
||||
`python3 startEHSSentinel.py --configfile config.yml --clean-known-devices`
|
||||
|
||||
I recommend to use `--clean-known-devices` on the start so EHS-Sentinel will send Configuration messages for HASS Autodiscovery after every startup.
|
||||
|
||||
## Systemd Service
|
||||
|
||||
@@ -30,7 +47,9 @@ In addition, a few data points are generated from others, such as COP and Heat O
|
||||
|
||||
`ExecStart = python3 <Path of the script you want to run>` <- provide here to path to your folder where startEHSSentinel.py is
|
||||
|
||||
sample: `ExecStart = python3 /root/EHS-Sentinel/startEHSSentinel.py --configfile /root/EHS-Sentinel/config.yml`
|
||||
sample: `ExecStart = python3 /root/EHS-Sentinel/startEHSSentinel.py --configfile /root/EHS-Sentinel/config.yml --clean-known-devices`
|
||||
|
||||
I recommend to use `--clean-known-devices` on the start so EHS-Sentinel will send Configuration messages for HASS Autodiscovery after every startup.`
|
||||
|
||||
5. Change your `config.yml` to absolut paths:
|
||||
`nasaRepositoryFile: /root/EHS-Sentinel/data/NasaRepository.yml`
|
||||
@@ -54,8 +73,95 @@ In addition, a few data points are generated from others, such as COP and Heat O
|
||||
`journalctl | grep ehsSentinel`
|
||||
|
||||
|
||||
# Configuration
|
||||
## Venv Installation (recommendet)
|
||||
|
||||
In general, it is recommended to work with a virtual environment (venvs) in python to be independent of other projects.
|
||||
Some Distributions like debian 12 dont allow to use system wide pip package installation, so you have to use venv.
|
||||
|
||||
1. Just clone the repository
|
||||
`git clone https://github.com/echoDaveD/EHS-Sentinel`
|
||||
|
||||
2. Install python venv
|
||||
`apt install python3.11-venv` <- modify your python verison here
|
||||
|
||||
3. Create Python venv
|
||||
`python3 -m venv EHS-Sentinel`
|
||||
|
||||
4. change diractory
|
||||
`cd EHS-Sentinel`
|
||||
|
||||
5. activate venv
|
||||
`source bin/activate`
|
||||
|
||||
6. Install the requierments
|
||||
`pip install -r requirements.txt`
|
||||
|
||||
7. Copy the `data/config.yml` and provide your Configuration
|
||||
|
||||
8. get path of venv python executable
|
||||
`which python3` <- copy the output
|
||||
|
||||
9. Change to ehs-sentinel.service file as followed:
|
||||
|
||||
`ExecStart = <path to python3> <Path of the script you want to run>` <- provide here to path to your folder where startEHSSentinel.py is
|
||||
|
||||
sample: `ExecStart = /root/EHS-Sentinel/bin/python3 /root/EHS-Sentinel/startEHSSentinel.py --configfile /root/EHS-Sentinel/config.yml --clean-known-devices`
|
||||
|
||||
I recommend to use `--clean-known-devices` on the start so EHS-Sentinel will send Configuration messages for HASS Autodiscovery after every startup.
|
||||
|
||||
10. Change your `config.yml` to absolut paths:
|
||||
`nasaRepositoryFile: /root/EHS-Sentinel/data/NasaRepository.yml`
|
||||
|
||||
11. Deactivate venv
|
||||
`dactivate`
|
||||
|
||||
12. Copy the service File to your systemd folder:
|
||||
`cp ehs-sentinel.service /etc/systemd/system`
|
||||
|
||||
13. Enable the new service
|
||||
`systemctl enable ehs-sentinel`
|
||||
|
||||
14. Reload deamon
|
||||
`systemctl daemon-reload`
|
||||
|
||||
15. Start the Service
|
||||
`systemctl start ehs-sentinel`
|
||||
|
||||
16. check if anything is fine
|
||||
`systemctl status ehs-sentinel`
|
||||
|
||||
17. If your want to check the journal logs
|
||||
`journalctl | grep ehsSentinel`
|
||||
|
||||
|
||||
# Home Assistant Dashboard
|
||||
|
||||
There are two rudimentary dashboard templates for Homeassistant,
|
||||
Read Only [ressources/dashboard_readonly_template.yaml](ressources/dashboard_readonly_template.yaml)
|
||||
|
||||
Control mode [ressources/dashboard_controlmode_template.yaml](ressources/dashboard_controlmode_template.yaml)
|
||||
|
||||
If you have good ideas and want to extend this feel free to create an issue or pull request, thanks!
|
||||
|
||||
## Read Only Mode
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
## Control Mode
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
## Command-Line Arguments
|
||||
|
||||
@@ -104,18 +210,52 @@ The `config.yml` file contains configuration settings for the EHS-Sentinel proje
|
||||
|
||||
- **nasaRepositoryFile**: Path to the NASA repository file.
|
||||
- Default: `data/NasaRepository.yml`
|
||||
- **silentMode**: Boolean flag to enable or disable silent mode. In Silent Mode only Logmessages above WARNING are printed out (for production use to not spam your systemlog)
|
||||
- Default: `True`
|
||||
- **protocolFile**: Path to the protocol file. (not set in Sample config.yml)
|
||||
- Example: `prot.csv`
|
||||
- **allowControl**: Allows EHS-Sentinel to Control the Heatpump. HASS Entities are writable, EHS-Sentinel listents to set Topic and write published values to th Modbus Interface.
|
||||
The Set Topic have following pattern: <topicPrefix>/entity/<NASA_NAME>/set sample: ehsSentinel/ENUM_IN_SG_READY_MODE_STATE/set
|
||||
- Default: `False`
|
||||
|
||||
> [!CAUTION]
|
||||
> This functionality requires that EHS-Sentinel actively communicates with
|
||||
> the Samsung EHS, so EHS-Sentinel intervenes here in the Modbus data
|
||||
> traffic between the components (it sends its own messages).
|
||||
> The activation of this functionality is exclusively at your own risk.
|
||||
> I assume no liability for any damage caused.
|
||||
|
||||
### Logging Settings
|
||||
|
||||
- **deviceAdded**: Set to true will log when new device is added to known Devices (and discover to HASS).
|
||||
- Default: `True`
|
||||
- **messageNotFound**: Set to true will log when a received message was not found in NasaRepository
|
||||
- Default: `False`
|
||||
- **packetNotFromIndoorOutdoor**: Set to true will log when a message not from Indoor/Outdoor unit was received
|
||||
- Default: `False`
|
||||
- **proccessedMessage**: set to true, prints out a summary of which massage was processed and its value
|
||||
- Default: `False`
|
||||
- **pollerMessage**: set to true, prints out detailed poller NASAPackets
|
||||
- Default: `False`
|
||||
- **controlMessage**: set to true, prints out detailed control Message NASAPackets
|
||||
- Default: `False`
|
||||
- **invalidPacket**: set to true, prints out invalid packets like length not ok or end byte not 0x34...
|
||||
- Default: `False`
|
||||
|
||||
### Serial Connection Settings
|
||||
cannot be defined with TCP parm...
|
||||
|
||||
- **device**: The serial device URL.
|
||||
- Example: `/dev/ttyUSB0`
|
||||
- **baudrate**: The baud rate for the serial connection.
|
||||
- Example: `9600`
|
||||
|
||||
### TCP Settings
|
||||
cannot be defined with SERIAL parm...
|
||||
|
||||
- **ip**: The ip of rs485 to ETH Adapter.
|
||||
- Example: `168.192.2.200`
|
||||
- **port**: The port of rs485 to ETH Adapter.
|
||||
- Example: `4196`
|
||||
|
||||
### MQTT Broker Settings
|
||||
|
||||
- **broker-url**: The URL of the MQTT broker.
|
||||
@@ -135,16 +275,56 @@ The `config.yml` file contains configuration settings for the EHS-Sentinel proje
|
||||
- **topicPrefix**: The prefix to use for MQTT topics. (Is used when homeassistant is not set or empty)
|
||||
- Example: `ehsSentinel`
|
||||
|
||||
### Poller Configuration
|
||||
> [!CAUTION]
|
||||
> This functionality requires that EHS-Sentinel actively communicates with
|
||||
> the Samsung EHS, so EHS-Sentinel intervenes here in the Modbus data
|
||||
> traffic between the components (it sends its own messages).
|
||||
> The activation of this functionality is exclusively at your own risk.
|
||||
> I assume no liability for any damage caused.
|
||||
|
||||
Experience has shown that the write function (required for poller) only works with a rts486 to ETH adapter, with a USB adapter no value could be written successfully so far.
|
||||
|
||||
With the Poller Configuration, values can be actively polled cyclically from the Samsung. All FSV values are already predefined in the sample Config. The pollers only need to be enabled.
|
||||
|
||||
The data points are defined in the groups section, the group is then enabled in the fetch_interval and the schedule is entered (10h, 10m, 10s are valid units).
|
||||
|
||||
- **fetch_interval**: The ip of rs485 to ETH Adapter.
|
||||
- Example: `168.192.2.200`
|
||||
|
||||
***name***: Name of the Group from groups section
|
||||
- Example: `fsv10xx`
|
||||
|
||||
***enabled***: True or False, true to enable this poller
|
||||
- Example: `True`
|
||||
|
||||
***schedule***: Time of often teh Values should be polled, be carefully do not poll to often. Valid units are `h` for hours, `m` for minutes and `s` for seconds
|
||||
- Example: `10h`
|
||||
|
||||
- **groups**: A list of groups, the with the Measurements to be polled, name can be freely assigned.
|
||||
- Example: `fsv10xx`
|
||||
|
||||
***fsv10xx***: A list wiht Measurements name, can be taken from the NASARepository
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```yaml
|
||||
general:
|
||||
nasaRepositoryFile: data/NasaRepository.yml
|
||||
silentMode: False
|
||||
protocolFile: prot.csv
|
||||
serial:
|
||||
device: /dev/ttyUSB0
|
||||
baudrate: 9600
|
||||
allowControl: False
|
||||
# protocolFile: prot.csv
|
||||
logging:
|
||||
deviceAdded: True
|
||||
messageNotFound: False
|
||||
packetNotFromIndoorOutdoor: False
|
||||
proccessedMessage: False
|
||||
pollerMessage: False
|
||||
#serial:
|
||||
# device: /dev/ttyUSB0
|
||||
# baudrate: 9600
|
||||
tcp:
|
||||
ip: 168.192.2.200
|
||||
port: 4196
|
||||
mqtt:
|
||||
broker-url: 123.45.6.69
|
||||
broker-port: 1883
|
||||
@@ -154,6 +334,15 @@ mqtt:
|
||||
homeAssistantAutoDiscoverTopic: "homeassistant"
|
||||
useCamelCaseTopicNames: True
|
||||
topicPrefix: ehsSentinel
|
||||
polling:
|
||||
fetch_interval:
|
||||
- name: fsv10xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
groups:
|
||||
fsv10xx:
|
||||
- VAR_IN_FSV_1011
|
||||
- VAR_IN_FSV_1012
|
||||
```
|
||||
|
||||
# Debugging
|
||||
@@ -189,5 +378,186 @@ if you want to see how many uniquie Messages have been collected in the Dumpfile
|
||||
|
||||
# Changelog
|
||||
|
||||
### v1.0.0 - 2025-03-13
|
||||
- EHS-Sentinel has been heavily modified to incorporate the control mechanism
|
||||
- The read-in behavior of the modbus registers has been revised from chunks to single byte
|
||||
- MessageProcessor now runs asynchronously
|
||||
- MessageProducer added which takes over the writing communication with the WP
|
||||
- Configuration of HASS entities has moved from hardcoded to NASA Repository
|
||||
- NASA Repository has been fundamentally changed
|
||||
- All FSV Values, NASA_POWER, VAR_IN_TEMP_WATER_LAW_TARGET_F, NASA_INDOOR_OPMODE are allowed for writing mode
|
||||
- NASA_OUTDOOR_DEFROST_STEP DEFROST STEP 10 (b'0xa') added
|
||||
- ENUM_IN_SG_READY_MODE_STATE ACTIVE (b'0x2') added
|
||||
- New configuration point allowControl to allow control of the Samsung EHS heat pump (deactivated by default).
|
||||
|
||||
> [!CAUTION]
|
||||
> This functionality requires that EHS-Sentinel actively communicates with
|
||||
> the Samsung EHS, so EHS-Sentinel intervenes here in the Modbus data
|
||||
> traffic between the components (it sends its own messages).
|
||||
> The activation of this functionality is exclusively at your own risk.
|
||||
> I assume no liability for any damage caused.
|
||||
|
||||
- new configuration points in logging
|
||||
- controlMessage (default False) to print out the controlled mesagges
|
||||
- invalidPacket (default False) prints out invalid messages (length not ok, x34 not at end...)
|
||||
- Dashboard template has been split, [ressources/dashboard_readonly_template.yaml](ressources/dashboard_readonly_template.yaml) is for readonly mode and the [ressources/dashboard_controlmode_template.yaml](ressources/dashboard_controlmode_template.yaml) for control mode
|
||||
|
||||
### v0.3.0 - 2025-02-27
|
||||
- Added poller functionality. EHS-Sentinel can now actively request values via Modbus
|
||||
- fetch_intervals and groups can be defined in the config file
|
||||
- default group and pollers are in the sampelconfig
|
||||
|
||||
> [!CAUTION]
|
||||
> This functionality requires that EHS-Sentinel actively communicates with
|
||||
> the Samsung EHS, so EHS-Sentinel intervenes here in the Modbus data
|
||||
> traffic between the components (it sends its own messages).
|
||||
> The activation of this functionality is exclusively at your own risk.
|
||||
> I assume no liability for any damage caused.
|
||||
|
||||
- added a homeassistant dashboard.yaml with default Dashboard
|
||||
- edited Measurement
|
||||
- ENUM_IN_FSV_5061 add enums
|
||||
- ENUM_IN_FSV_5094 correct enum values
|
||||
- ENUM_IN_PV_CONTACT_STATE correct enum values
|
||||
- added units for multiple Measurements
|
||||
- Rename some Measurements:
|
||||
- NASA_INDOOR_COOL_MAX_SETTEMP_WATEROUT -> VAR_IN_FSV_1011
|
||||
- NASA_INDOOR_COOL_MIN_SETTEMP_WATEROUT -> VAR_IN_FSV_1012
|
||||
- NASA_INDOOR_COOL_MAX_SETTEMP_ROOM -> VAR_IN_FSV_1021
|
||||
- NASA_INDOOR_COOL_MIN_SETTEMP_ROOM -> VAR_IN_FSV_1022
|
||||
- NASA_INDOOR_HEAT_MAX_SETTEMP_WATEROUT -> VAR_IN_FSV_1031
|
||||
- NASA_INDOOR_HEAT_MIN_SETTEMP_WATEROUT -> VAR_IN_FSV_1032
|
||||
- NASA_INDOOR_HEAT_MAX_SETTEMP_ROOM -> VAR_IN_FSV_1041
|
||||
- NASA_INDOOR_HEAT_MIN_SETTEMP_ROOM -> VAR_IN_FSV_1042
|
||||
- NASA_DHW_MAX_SETTEMPLIMIT -> VAR_IN_FSV_1051
|
||||
- NASA_DHW_MIN_SETTEMPLIMIT -> VAR_IN_FSV_1052
|
||||
- NASA_USE_DHW_THERMOSTAT -> ENUM_IN_FSV_3061
|
||||
- NASA_USE_BOOSTER_HEATER -> ENUM_IN_FSV_3031
|
||||
- NASA_ENABLE_DHW -> ENUM_IN_FSV_3011
|
||||
- NASA_USE_THERMOSTAT!1 -> ENUM_IN_FSV_2091
|
||||
- NASA_USE_THERMOSTAT2 -> ENUM_IN_FSV_2092
|
||||
|
||||
### v0.2.2 - 2025-02-24
|
||||
- Support for rs485 to ETH Adapter, tcp options instead of serial port are possible now
|
||||
|
||||
### v0.2.1 - 2025-02-22
|
||||
- limit NASA_EHSSENTINEL_COP and NASA_EHSSENTINEL_TOTAL_COP to values between 0 and 20
|
||||
|
||||
### v0.2.0 - 2025-02-22
|
||||
- improved MQTT Auto Discovery Config Messages
|
||||
- NASAPacket and NASAMessage are now bidirectional, can decode and encode Packets
|
||||
- Improved data quality
|
||||
- Added crc16 Checksum check for any Packet to reduce incorrect value changes
|
||||
- Only Packets from outdoor/Indoor Units are processed
|
||||
- Following warnings moved to SkipInvalidPacketException and from warning to debug log level to reduce log entries
|
||||
- Source Adress Class out of enum
|
||||
- Destination Adress Class out of enum
|
||||
- Checksum for package could not be validatet calculated
|
||||
- Message with structure type must have capacity of 1.
|
||||
- removed silentNode config property (new logging section is replacing its functionality but more granular)
|
||||
- added new logging config property to allow to turn on/off additional info log entries
|
||||
- deviceAdded set to true (default) will log when new device is added to known Devices (and discover to HASS)
|
||||
- messageNotFound set to true (false is default) will log when a received message was not found in NasaRepository
|
||||
- packetNotFromIndoorOutdoor set to true (false is default) will log when a message not from Indoor/Outdoor unit was received
|
||||
- proccessedMessage set to true(false is default) prints out a summary of which massage was processed and its value
|
||||
- NASA_EHSSENTINEL_HEAT_OUTPUT limited to values between 0 and 15000 to reduce false values between temp statsu changes
|
||||
- Added new Measurements
|
||||
- 0x4423 LVAR_IN_MINUTES_SINCE_INSTALLATION
|
||||
- 0x4424 LVAR_IN_MINUTES_ACTIVE
|
||||
- 0x4426 LVAR_IN_GENERATED_POWER_LAST_MINUTE
|
||||
- 0x4427 LVAR_IN_TOTAL_GENERATED_POWER
|
||||
- 0x0997 NASA_EHSSENTINEL_TOTAL_COP = LVAR_IN_TOTAL_GENERATED_POWER / NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM
|
||||
- NASA Repository, measurements enums completed
|
||||
- ENUM_IN_FSV_3041: enum edited
|
||||
- ENUM_IN_FSV_3071: enum edited
|
||||
- ENUM_IN_FSV_4021: enum edited
|
||||
- ENUM_IN_FSV_4041: enum edited
|
||||
- ENUM_IN_FSV_4051: enum edited
|
||||
- ENUM_IN_FSV_4053: enum edited
|
||||
- ENUM_IN_FSV_5022: enum edited
|
||||
- ENUM_IN_FSV_5042: enum edited
|
||||
- ENUM_IN_FSV_5081: enum edited
|
||||
- ENUM_IN_FSV_5091: enum edited
|
||||
- ENUM_IN_FSV_2093: enum edited
|
||||
- ENUM_IN_FSV_2094: enum edited
|
||||
- VAR_IN_FSV_2011: desc edited
|
||||
- VAR_IN_FSV_2012: desc edited
|
||||
- VAR_IN_FSV_2021: desc edited
|
||||
- VAR_IN_FSV_2022: desc edited
|
||||
- VAR_IN_FSV_2031: desc edited
|
||||
- VAR_IN_FSV_2032: desc edited
|
||||
- ENUM_IN_FSV_2041: desc edited
|
||||
- VAR_IN_FSV_2051: desc edited
|
||||
- VAR_IN_FSV_2052: desc edited
|
||||
- VAR_IN_FSV_2061: desc edited
|
||||
- VAR_IN_FSV_2062: desc edited
|
||||
- VAR_IN_FSV_2071: desc edited
|
||||
- VAR_IN_FSV_2072: desc edited
|
||||
- ENUM_IN_FSV_2093: desc edited
|
||||
- VAR_IN_FSV_3021: desc edited
|
||||
- VAR_IN_FSV_3022: desc edited
|
||||
- VAR_IN_FSV_3023: desc edited
|
||||
- VAR_IN_FSV_3024: desc edited
|
||||
- VAR_IN_FSV_3025: desc edited
|
||||
- VAR_IN_FSV_3026: desc edited
|
||||
- VAR_IN_FSV_3032: desc edited
|
||||
- VAR_IN_FSV_3033: desc edited
|
||||
- VAR_IN_FSV_3041: desc edited
|
||||
- VAR_IN_FSV_3042: desc edited
|
||||
- VAR_IN_FSV_3043: desc edited
|
||||
- VAR_IN_FSV_3044: desc edited
|
||||
- VAR_IN_FSV_3045: desc edited
|
||||
- VAR_IN_FSV_3046: desc edited
|
||||
- ENUM_IN_FSV_3051: desc edited
|
||||
- ENUM_IN_FSV_3052: desc edited
|
||||
- ENUM_IN_FSV_3071: desc edited
|
||||
- ENUM_IN_FSV_3081: desc edited
|
||||
- ENUM_IN_FSV_3082: desc edited
|
||||
- ENUM_IN_FSV_3083: desc edited
|
||||
- VAR_IN_FSV_4011: desc edited
|
||||
- VAR_IN_FSV_4012: desc edited
|
||||
- VAR_IN_FSV_4013: desc edited
|
||||
- VAR_IN_FSV_4021: desc edited
|
||||
- VAR_IN_FSV_4022: desc edited
|
||||
- VAR_IN_FSV_4023: desc edited
|
||||
- VAR_IN_FSV_4024: desc edited
|
||||
- VAR_IN_FSV_4025: desc edited
|
||||
- VAR_IN_FSV_4031: desc edited
|
||||
- VAR_IN_FSV_4032: desc edited
|
||||
- VAR_IN_FSV_4033: desc edited
|
||||
- VAR_IN_FSV_4041: desc edited
|
||||
- VAR_IN_FSV_4042: desc edited
|
||||
- VAR_IN_FSV_4043: desc edited
|
||||
- VAR_IN_FSV_4044: desc edited
|
||||
- VAR_IN_FSV_4045: desc edited
|
||||
- VAR_IN_FSV_4046: desc edited
|
||||
- VAR_IN_FSV_4051: desc edited
|
||||
- VAR_IN_FSV_4052: desc edited
|
||||
- VAR_IN_FSV_4053: desc edited
|
||||
- VAR_IN_FSV_4061: desc edited
|
||||
- VAR_IN_FSV_5011: desc edited
|
||||
- VAR_IN_FSV_5012: desc edited
|
||||
- VAR_IN_FSV_5013: desc edited
|
||||
- VAR_IN_FSV_5014: desc edited
|
||||
- VAR_IN_FSV_5015: desc edited
|
||||
- VAR_IN_FSV_5016: desc edited
|
||||
- VAR_IN_FSV_5017: desc edited
|
||||
- VAR_IN_FSV_5018: desc edited
|
||||
- VAR_IN_FSV_5019: desc edited
|
||||
- VAR_IN_FSV_5021: desc edited
|
||||
- VAR_IN_FSV_5023: desc edited
|
||||
- ENUM_IN_FSV_5022: desc edited
|
||||
- ENUM_IN_FSV_5041: desc edited
|
||||
- ENUM_IN_FSV_5042: desc edited
|
||||
- ENUM_IN_FSV_5043: desc edited
|
||||
- ENUM_IN_FSV_5051: desc edited
|
||||
- VAR_IN_FSV_5083: desc edited
|
||||
- VAR_IN_FSV_5082: desc edited
|
||||
- ENUM_IN_FSV_5081: desc edited
|
||||
- ENUM_IN_FSV_5091: desc edited
|
||||
- ENUM_IN_FSV_5094: desc edited
|
||||
- VAR_IN_FSV_5092: desc edited
|
||||
- VAR_IN_FSV_5093: desc edited
|
||||
|
||||
### v0.1.0Beta - 2025-02-08
|
||||
- Initial Commit
|
||||
- Initial Commit
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
139
data/config.yml
139
data/config.yml
@@ -1,9 +1,20 @@
|
||||
general:
|
||||
nasaRepositoryFile: data/NasaRepository.yml
|
||||
silentMode: True
|
||||
serial:
|
||||
device: /dev/ttyUSB0
|
||||
baudrate: 9600
|
||||
allowControl: False
|
||||
logging:
|
||||
deviceAdded: True
|
||||
messageNotFound: False
|
||||
packetNotFromIndoorOutdoor: False
|
||||
proccessedMessage: False
|
||||
pollerMessage: False
|
||||
controlMessage: False
|
||||
invalidPacket: False
|
||||
#serial:
|
||||
# device: /dev/ttyUSB0
|
||||
# baudrate: 9600
|
||||
tcp:
|
||||
ip: 168.192.2.200
|
||||
port: 4196
|
||||
mqtt:
|
||||
broker-url: 111.111.11.1
|
||||
broker-port: 1883
|
||||
@@ -13,3 +24,123 @@ mqtt:
|
||||
homeAssistantAutoDiscoverTopic: "hass"
|
||||
useCamelCaseTopicNames: True
|
||||
topicPrefix: ehsSentinel
|
||||
polling:
|
||||
fetch_interval:
|
||||
- name: fsv10xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
- name: fsv20xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
- name: fsv30xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
- name: fsv40xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
- name: fsv50xx
|
||||
enable: false
|
||||
schedule: 30m
|
||||
groups:
|
||||
fsv10xx:
|
||||
- VAR_IN_FSV_1011
|
||||
- VAR_IN_FSV_1012
|
||||
- VAR_IN_FSV_1021
|
||||
- VAR_IN_FSV_1022
|
||||
- VAR_IN_FSV_1031
|
||||
- VAR_IN_FSV_1032
|
||||
- VAR_IN_FSV_1041
|
||||
- VAR_IN_FSV_1042
|
||||
- VAR_IN_FSV_1051
|
||||
- VAR_IN_FSV_1052
|
||||
fsv20xx:
|
||||
- VAR_IN_FSV_2011
|
||||
- VAR_IN_FSV_2012
|
||||
- VAR_IN_FSV_2021
|
||||
- VAR_IN_FSV_2022
|
||||
- VAR_IN_FSV_2031
|
||||
- VAR_IN_FSV_2032
|
||||
- ENUM_IN_FSV_2041
|
||||
- VAR_IN_FSV_2051
|
||||
- VAR_IN_FSV_2052
|
||||
- VAR_IN_FSV_2061
|
||||
- VAR_IN_FSV_2062
|
||||
- VAR_IN_FSV_2071
|
||||
- VAR_IN_FSV_2072
|
||||
- ENUM_IN_FSV_2081
|
||||
- ENUM_IN_FSV_2091
|
||||
- ENUM_IN_FSV_2092
|
||||
- ENUM_IN_FSV_2093
|
||||
- ENUM_IN_FSV_2094
|
||||
fsv30xx:
|
||||
- ENUM_IN_FSV_3011
|
||||
- VAR_IN_FSV_3021
|
||||
- VAR_IN_FSV_3022
|
||||
- VAR_IN_FSV_3023
|
||||
- VAR_IN_FSV_3024
|
||||
- VAR_IN_FSV_3025
|
||||
- VAR_IN_FSV_3026
|
||||
- ENUM_IN_FSV_3031
|
||||
- VAR_IN_FSV_3032
|
||||
- VAR_IN_FSV_3033
|
||||
- ENUM_IN_FSV_3041
|
||||
- ENUM_IN_FSV_3042
|
||||
- VAR_IN_FSV_3043
|
||||
- VAR_IN_FSV_3044
|
||||
- VAR_IN_FSV_3045
|
||||
- VAR_IN_FSV_3046
|
||||
- ENUM_IN_FSV_3051
|
||||
- VAR_IN_FSV_3052
|
||||
- ENUM_IN_FSV_3061
|
||||
- ENUM_IN_FSV_3071
|
||||
- VAR_IN_FSV_3081
|
||||
- VAR_IN_FSV_3082
|
||||
- VAR_IN_FSV_3083
|
||||
fsv40xx:
|
||||
- ENUM_IN_FSV_4011
|
||||
- VAR_IN_FSV_4012
|
||||
- VAR_IN_FSV_4013
|
||||
- ENUM_IN_FSV_4021
|
||||
- ENUM_IN_FSV_4022
|
||||
- ENUM_IN_FSV_4023
|
||||
- VAR_IN_FSV_4024
|
||||
- VAR_IN_FSV_4025
|
||||
- ENUM_IN_FSV_4031
|
||||
- ENUM_IN_FSV_4032
|
||||
- VAR_IN_FSV_4033
|
||||
- ENUM_IN_FSV_4041
|
||||
- VAR_IN_FSV_4042
|
||||
- VAR_IN_FSV_4043
|
||||
- ENUM_IN_FSV_4044
|
||||
- VAR_IN_FSV_4045
|
||||
- VAR_IN_FSV_4046
|
||||
- ENUM_IN_FSV_4051
|
||||
- VAR_IN_FSV_4052
|
||||
- ENUM_IN_FSV_4053
|
||||
- ENUM_IN_FSV_4061
|
||||
fsv50xx:
|
||||
- VAR_IN_FSV_5011
|
||||
- VAR_IN_FSV_5012
|
||||
- VAR_IN_FSV_5013
|
||||
- VAR_IN_FSV_5014
|
||||
- VAR_IN_FSV_5015
|
||||
- VAR_IN_FSV_5016
|
||||
- VAR_IN_FSV_5017
|
||||
- VAR_IN_FSV_5018
|
||||
- VAR_IN_FSV_5019
|
||||
- VAR_IN_FSV_5021
|
||||
- VAR_IN_FSV_5031
|
||||
- ENUM_IN_FSV_5022
|
||||
- VAR_IN_FSV_5023
|
||||
- ENUM_IN_FSV_5041
|
||||
- ENUM_IN_FSV_5042
|
||||
- ENUM_IN_FSV_5043
|
||||
- ENUM_IN_FSV_5051
|
||||
- ENUM_IN_FSV_5061
|
||||
- ENUM_IN_FSV_5081
|
||||
- VAR_IN_FSV_5082
|
||||
- VAR_IN_FSV_5083
|
||||
- ENUM_IN_FSV_5091
|
||||
- VAR_IN_FSV_5092
|
||||
- VAR_IN_FSV_5093
|
||||
- ENUM_IN_FSV_5094
|
||||
@@ -1,2 +1,2 @@
|
||||
[50, 0, 60, 16, 0, 0, 176, 0, 255, 192, 20, 27, 13, 2, 2, 255, 255, 4, 16, 0, 0, 0, 0, 4, 27, 0, 32, 255, 255, 128, 0, 0, 128, 5, 255, 128, 23, 0, 128, 25, 0, 128, 26, 0, 128, 33, 1, 128, 50, 1, 128, 51, 2, 128, 60, 0, 128, 69, 0, 63, 195, 52]
|
||||
[50, 0, 62, 16, 0, 0, 176, 0, 255, 192, 20, 28, 16, 128, 99, 1, 128, 102, 0, 128, 119, 0, 128, 120, 2, 128, 121, 0, 128, 122, 0, 128, 123, 0, 128, 124, 0, 128, 125, 0, 128, 126, 0, 128, 127, 255, 128, 131, 0, 128, 142, 255, 128, 169, 0, 128, 175, 0, 128, 177, 0, 145, 101, 52]
|
||||
['0x32', '0x0', '0x3e', '0x10', '0x0', '0x0', '0xb0', '0x0', '0xff', '0xc0', '0x14', '0xc7', '0xc', '0x82', '0x18', '0xff', '0xdb', '0x82', '0x1a', '0xff', '0xd5', '0x82', '0x1e', '0x0', '0x8b', '0x82', '0x24', '0x0', '0xd6', '0x82', '0x25', '0x1', '0x5c', '0x82', '0x26', '0x0', '0x1b', '0x82', '0x29', '0x0', '0xe9', '0x82', '0x2e', '0x0', '0x50', '0x82', '0x31', '0x0', '0x64', '0x82', '0x34']
|
||||
['0x32', '0x0', '0x12', '0x10', '0x0', '0x0', '0xb0', '0x0', '0xff', '0xc0', '0x14', '0x5a', '0x1', '0x82', '0x1c', '0x1', '0x38', '0x1f', '0x34']
|
||||
240
helpertils/TestPacketGenerator.py
Normal file
240
helpertils/TestPacketGenerator.py
Normal file
@@ -0,0 +1,240 @@
|
||||
import json
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
parentdir = os.path.dirname(currentdir)
|
||||
sys.path.insert(0, parentdir)
|
||||
|
||||
import NASAPacket
|
||||
import NASAMessage
|
||||
|
||||
|
||||
encode_raw = "[50, 0, 17, 16, 0, 0, 176, 0, 255, 192, 20, 143, 1, 128, 49, 0, 157, 7, 52]"
|
||||
encode_raw = "[50, 0, 60, 16, 0, 0, 176, 0, 255, 192, 20, 196, 13, 2, 2, 255, 255, 4, 16, 0, 0, 0, 0, 4, 27, 0, 32, 255, 255, 128, 0, 0, 128, 5, 255, 128, 23, 0, 128, 25, 0, 128, 26, 0, 128, 33, 1, 128, 50, 255, 128, 51, 0, 128, 60, 0, 128, 69, 0, 240, 94, 52]"
|
||||
encode_raw = "[50, 0, 56, 98, 0, 144, 178, 0, 32, 192, 17, 3, 11, 64, 147, 0, 64, 148, 0, 66, 115, 0, 0, 66, 116, 0, 0, 66, 117, 0, 0, 66, 118, 0, 0, 66, 119, 0, 0, 66, 120, 0, 0, 66, 121, 0, 0, 66, 122, 0, 0, 66, 123, 0, 0, 221, 200, 52]"
|
||||
encode_raw = "[50, 0, 56, 98, 0, 144, 178, 0, 32, 192, 17, 240, 11, 64, 147, 0, 64, 148, 0, 66, 115, 0, 0, 66, 116, 0, 0, 66, 117, 0, 0, 66, 118, 0, 0, 66, 119, 0, 0, 66, 120, 0, 0, 66, 121, 0, 0, 66, 122, 0, 0, 66, 123, 0, 0, 76, 33, 52]"
|
||||
#encode_raw ="[50, 0, 48, 98, 0, 144, 178, 0, 32, 192, 17, 240, 11, 64, 147, 0, 64, 148, 0, 66, 115, 0, 0, 66, 116, 0, 66, 117, 0, 66, 118, 0, 66, 119, 0, 66, 120, 0, 66, 121, 0, 66, 122, 0, 66, 123, 0, 7, 180, 52]"
|
||||
encode_raw = "['0x32', '0x00', '0x1A', '0x80', '0xFF', '0x00', '0x20', '0x00', '0x00', '0xC0', '0x11', '0xB0', '0x01', '0x06', '0x07', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0xBD', '0xBC', '0x34']"
|
||||
try:
|
||||
encode_bytearray = json.loads(encode_raw.strip()) # for [12, 234, 456 ,67]
|
||||
except:
|
||||
encode_tmp = encode_raw.strip().replace("'", "").replace("[", "").replace("]", "").split(", ") # for ['0x1', '0x2' ..]
|
||||
encode_bytearray = [int(value, 16) for value in encode_tmp]
|
||||
|
||||
|
||||
print(f"encode raw: {bytearray(encode_bytearray)}")
|
||||
print(f"encode bytearray: {encode_bytearray}")
|
||||
print(f"encode bytearray length: {len(encode_bytearray)}")
|
||||
|
||||
encoded_nasa = NASAPacket.NASAPacket()
|
||||
encoded_nasa.parse(encode_bytearray)
|
||||
|
||||
print(f"encode NASA Object: {encoded_nasa}")
|
||||
exit()
|
||||
# time to reverse that thing!
|
||||
decoded_nasa = NASAPacket.NASAPacket()
|
||||
decoded_nasa.set_packet_source_address_class(NASAPacket.AddressClassEnum.JIGTester)
|
||||
decoded_nasa.set_packet_source_channel(0)
|
||||
decoded_nasa.set_packet_source_address(0)
|
||||
decoded_nasa.set_packet_dest_address_class(NASAPacket.AddressClassEnum.BroadcastSelfLayer)
|
||||
decoded_nasa.set_packet_dest_channel(0)
|
||||
decoded_nasa.set_packet_dest_address(255)
|
||||
decoded_nasa.set_packet_information(True)
|
||||
decoded_nasa.set_packet_version(2)
|
||||
decoded_nasa.set_packet_retry_count(0)
|
||||
decoded_nasa.set_packet_type(NASAPacket.PacketType.Normal)
|
||||
decoded_nasa.set_packet_data_type(NASAPacket.DataType.Notification)
|
||||
decoded_nasa.set_packet_number(143)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8031)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
decoded_nasa.set_packet_messages([tmp_msg])
|
||||
|
||||
decoded_nasa = NASAPacket.NASAPacket()
|
||||
decoded_nasa.set_packet_source_address_class(NASAPacket.AddressClassEnum.Outdoor)
|
||||
decoded_nasa.set_packet_source_channel(0)
|
||||
decoded_nasa.set_packet_source_address(0)
|
||||
decoded_nasa.set_packet_dest_address_class(NASAPacket.AddressClassEnum.BroadcastSelfLayer)
|
||||
decoded_nasa.set_packet_dest_channel(0)
|
||||
decoded_nasa.set_packet_dest_address(255)
|
||||
decoded_nasa.set_packet_information(True)
|
||||
decoded_nasa.set_packet_version(2)
|
||||
decoded_nasa.set_packet_retry_count(0)
|
||||
decoded_nasa.set_packet_type(NASAPacket.PacketType.Normal)
|
||||
decoded_nasa.set_packet_data_type(NASAPacket.DataType.Notification)
|
||||
decoded_nasa.set_packet_number(196)
|
||||
lst = []
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x0202)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([255, 255])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x0410)
|
||||
tmp_msg.set_packet_message_type(2)
|
||||
tmp_msg.set_packet_payload([0, 0, 0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x41b)
|
||||
tmp_msg.set_packet_message_type(2)
|
||||
tmp_msg.set_packet_payload([0, 32, 255, 255])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8000)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8005)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([255])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8017)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8019)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x801a)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8021)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([1])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8032)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([255])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8033)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x803c)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x8045)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
decoded_nasa.set_packet_messages(lst)
|
||||
|
||||
#rasw = decoded_nasa.to_raw()
|
||||
|
||||
decoded_nasa = NASAPacket.NASAPacket()
|
||||
decoded_nasa.set_packet_source_address_class(NASAPacket.AddressClassEnum.WiFiKit)
|
||||
decoded_nasa.set_packet_source_channel(0)
|
||||
decoded_nasa.set_packet_source_address(144)
|
||||
decoded_nasa.set_packet_dest_address_class(NASAPacket.AddressClassEnum.BroadcastSetLayer)
|
||||
decoded_nasa.set_packet_dest_channel(0)
|
||||
decoded_nasa.set_packet_dest_address(32)
|
||||
decoded_nasa.set_packet_information(True)
|
||||
decoded_nasa.set_packet_version(2)
|
||||
decoded_nasa.set_packet_retry_count(0)
|
||||
decoded_nasa.set_packet_type(NASAPacket.PacketType.Normal)
|
||||
decoded_nasa.set_packet_data_type(NASAPacket.DataType.Read)
|
||||
decoded_nasa.set_packet_number(240)
|
||||
lst = []
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4093)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4094)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4273)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4274)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4275)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4276)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4277)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4278)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x4279)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x427a)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x427b)
|
||||
tmp_msg.set_packet_message_type(1)
|
||||
tmp_msg.set_packet_payload([0, 0])
|
||||
lst.append(tmp_msg)
|
||||
decoded_nasa.set_packet_messages(lst)
|
||||
|
||||
decoded_nasa = NASAPacket.NASAPacket()
|
||||
decoded_nasa.set_packet_source_address_class(NASAPacket.AddressClassEnum.WiFiKit)
|
||||
decoded_nasa.set_packet_source_channel(0)
|
||||
decoded_nasa.set_packet_source_address(144)
|
||||
decoded_nasa.set_packet_dest_address_class(NASAPacket.AddressClassEnum.BroadcastSelfLayer)
|
||||
decoded_nasa.set_packet_dest_channel(255)
|
||||
decoded_nasa.set_packet_dest_address(255)
|
||||
decoded_nasa.set_packet_information(True)
|
||||
decoded_nasa.set_packet_version(2)
|
||||
decoded_nasa.set_packet_retry_count(0)
|
||||
decoded_nasa.set_packet_type(NASAPacket.PacketType.Normal)
|
||||
decoded_nasa.set_packet_data_type(NASAPacket.DataType.Notification)
|
||||
decoded_nasa.set_packet_number(168)
|
||||
tmp_msg = NASAMessage.NASAMessage()
|
||||
tmp_msg.set_packet_message(0x0000)
|
||||
tmp_msg.set_packet_message_type(0)
|
||||
tmp_msg.set_packet_payload([2])
|
||||
decoded_nasa.set_packet_messages([tmp_msg])
|
||||
|
||||
|
||||
rasw = decoded_nasa.to_raw()
|
||||
|
||||
print(f"decoded bytearray: {rasw}")
|
||||
print(f"decoded NASA Object: {decoded_nasa}")
|
||||
print(f"decoded bytearray: {[int(value) for value in rasw]}")
|
||||
|
||||
print("Reverse Check:")
|
||||
checkback = NASAPacket.NASAPacket()
|
||||
checkback.parse(rasw)
|
||||
print(checkback)
|
||||
159
helpertils/messageFinder.py
Normal file
159
helpertils/messageFinder.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import asyncio
|
||||
import yaml
|
||||
import traceback
|
||||
|
||||
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
parentdir = os.path.dirname(currentdir)
|
||||
sys.path.insert(0, parentdir)
|
||||
|
||||
from NASAPacket import NASAPacket, AddressClassEnum, DataType, PacketType
|
||||
from NASAMessage import NASAMessage
|
||||
|
||||
# Generate a list of all possible 2-byte hex values, always padded to 4 characters
|
||||
two_byte_hex_values = [f"0x{i:04X}" for i in range(0x0000, 0xFFFF)]
|
||||
send_message_list = []
|
||||
seen_message_list = []
|
||||
|
||||
with open('data/NasaRepository.yml', mode='r') as file:
|
||||
NASA_REPO = yaml.safe_load(file)
|
||||
|
||||
async def main():
|
||||
|
||||
# load config
|
||||
with open('config.yml', mode='r') as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
# Print the total count to confirm all values are included
|
||||
print(f"Total values: {len(two_byte_hex_values)}")
|
||||
|
||||
reader, writer = await asyncio.open_connection('172.19.2.240', 4196)
|
||||
print(" serial_connection fertig")
|
||||
await asyncio.gather(
|
||||
serial_read(reader, config),
|
||||
serial_write(writer, config),
|
||||
)
|
||||
|
||||
async def serial_write(writer: asyncio.StreamWriter, config):
|
||||
_CHUNKSIZE=10
|
||||
chunks = [two_byte_hex_values[i:i + _CHUNKSIZE] for i in range(0, len(two_byte_hex_values), _CHUNKSIZE)]
|
||||
for chunk in chunks:
|
||||
nasa_msg = NASAPacket()
|
||||
nasa_msg.set_packet_source_address_class(AddressClassEnum.JIGTester)
|
||||
nasa_msg.set_packet_source_channel(240)
|
||||
nasa_msg.set_packet_source_address(0)
|
||||
nasa_msg.set_packet_dest_address_class(AddressClassEnum.BroadcastSetLayer)
|
||||
nasa_msg.set_packet_dest_channel(0)
|
||||
nasa_msg.set_packet_dest_address(32)
|
||||
nasa_msg.set_packet_information(True)
|
||||
nasa_msg.set_packet_version(2)
|
||||
nasa_msg.set_packet_retry_count(0)
|
||||
nasa_msg.set_packet_type(PacketType.Normal)
|
||||
nasa_msg.set_packet_data_type(DataType.Read)
|
||||
nasa_msg.set_packet_number(166)
|
||||
msglist=[]
|
||||
for msg in chunk:
|
||||
if msg not in send_message_list and msg not in seen_message_list:
|
||||
tmpmsg = NASAMessage()
|
||||
tmpmsg.set_packet_message(int(msg, 16))
|
||||
value = 0
|
||||
if tmpmsg.packet_message_type == 0:
|
||||
value_raw = value.to_bytes(1, byteorder='big')
|
||||
elif tmpmsg.packet_message_type == 1:
|
||||
value_raw = value.to_bytes(2, byteorder='big')
|
||||
elif tmpmsg.packet_message_type == 2:
|
||||
value_raw = value.to_bytes(4, byteorder='big')
|
||||
else:
|
||||
value_raw = value.to_bytes(1, byteorder='big')
|
||||
|
||||
tmpmsg.set_packet_payload_raw(value_raw)
|
||||
msglist.append(tmpmsg)
|
||||
nasa_msg.set_packet_messages(msglist)
|
||||
raw = nasa_msg.to_raw()
|
||||
writer.write(raw)
|
||||
await writer.drain()
|
||||
send_message_list.extend(chunk)
|
||||
if len(send_message_list) % 100 == 0:
|
||||
print(f"Sended count: {len(send_message_list)}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def serial_read(reader: asyncio.StreamReader, config):
|
||||
prev_byte = 0x00
|
||||
packet_started = False
|
||||
data = bytearray()
|
||||
packet_size = 0
|
||||
|
||||
while True:
|
||||
current_byte = await reader.read(1) # read bitewise
|
||||
|
||||
#data = await reader.read(1024)
|
||||
#data = await reader.readuntil(b'\x34fd')
|
||||
if current_byte:
|
||||
if packet_started:
|
||||
data.extend(current_byte)
|
||||
if len(data) == 3:
|
||||
packet_size = ((data[1] << 8) | data[2]) + 2
|
||||
|
||||
if packet_size <= len(data):
|
||||
asyncio.create_task(process_packet(data, config))
|
||||
data = bytearray()
|
||||
packet_started = False
|
||||
|
||||
# identify packet start
|
||||
if current_byte == b'\x00' and prev_byte == b'\x32':
|
||||
packet_started = True
|
||||
data.extend(prev_byte)
|
||||
data.extend(current_byte)
|
||||
|
||||
prev_byte = current_byte
|
||||
|
||||
def search_nasa_table(address):
|
||||
for key in NASA_REPO:
|
||||
if NASA_REPO[key]['address'].lower() == address.lower():
|
||||
return key
|
||||
|
||||
def is_valid_rawvalue(rawvalue: bytes) -> bool:
|
||||
return all(0x20 <= b <= 0x7E or b in (0x00, 0xFF) for b in rawvalue)
|
||||
|
||||
async def process_packet(buffer, config):
|
||||
try:
|
||||
nasa_packet = NASAPacket()
|
||||
nasa_packet.parse(buffer)
|
||||
for msg in nasa_packet.packet_messages:
|
||||
if msg.packet_message not in seen_message_list:
|
||||
seen_message_list.append(msg.packet_message)
|
||||
msgkey = search_nasa_table(f"0x{msg.packet_message:04X}")
|
||||
if msgkey is None:
|
||||
msgkey = ""
|
||||
msgvalue = None
|
||||
if msg.packet_message_type == 3:
|
||||
msgvalue = ""
|
||||
|
||||
if is_valid_rawvalue(msg.packet_payload[1:-1]):
|
||||
for byte in msg.packet_payload[1:-1]:
|
||||
if byte != 0x00 and byte != 0xFF:
|
||||
char = chr(byte) if 32 <= byte <= 126 else f"{byte}"
|
||||
msgvalue += char
|
||||
else:
|
||||
msgvalue += " "
|
||||
msgvalue = msgvalue.strip()
|
||||
else:
|
||||
msgvalue = "".join([f"{int(x)}" for x in msg.packet_payload])
|
||||
else:
|
||||
msgvalue = int.from_bytes(msg.packet_payload, byteorder='big', signed=True)
|
||||
|
||||
line = f"| {len(seen_message_list):<6} | {hex(msg.packet_message):<6} | {msgkey:<50} | {msg.packet_message_type} | {msgvalue:<20} | {msg.packet_payload} |"
|
||||
with open('helpertils/messagesFound.txt', "a") as dumpWriter:
|
||||
dumpWriter.write(f"{line}\n")
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
except RuntimeError as e:
|
||||
print(f"Runtime error: {e}")
|
||||
169
helpertils/messageFinderAnalyser.py
Normal file
169
helpertils/messageFinderAnalyser.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import asyncio
|
||||
import yaml
|
||||
import traceback
|
||||
import pprint
|
||||
|
||||
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
parentdir = os.path.dirname(currentdir)
|
||||
sys.path.insert(0, parentdir)
|
||||
|
||||
from NASAPacket import NASAPacket, AddressClassEnum, DataType, PacketType
|
||||
from NASAMessage import NASAMessage
|
||||
|
||||
# Generate a list of all possible 2-byte hex values, always padded to 4 characters
|
||||
found_repo = {}
|
||||
|
||||
with open('data/NasaRepository.yml', mode='r') as file:
|
||||
NASA_REPO = yaml.safe_load(file)
|
||||
|
||||
async def main():
|
||||
|
||||
# load finder file and anylse it
|
||||
with open('helpertils/messagesFound.txt', mode='r') as file:
|
||||
lines = file.read()
|
||||
for line in lines.splitlines():
|
||||
nix, nr, msgnr, msgname, type, packedval, rawval, nix2 = line.split("|")
|
||||
if type.strip() != '3':
|
||||
packedval = int(packedval.strip())
|
||||
if len(msgname.strip()) == 0 and packedval != -1:
|
||||
found_repo[msgnr.strip()] = {
|
||||
"type": type.strip(),
|
||||
"raw_value": rawval.strip(),
|
||||
"packed_value": packedval
|
||||
}
|
||||
|
||||
pprint.pprint(found_repo)
|
||||
|
||||
# load config
|
||||
with open('config.yml', mode='r') as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
# Print the total count to confirm all values are included
|
||||
print(f"Total values: {len(found_repo)}")
|
||||
|
||||
reader, writer = await asyncio.open_connection('172.19.2.240', 4196)
|
||||
print(" serial_connection fertig")
|
||||
await asyncio.gather(
|
||||
serial_read(reader, config),
|
||||
serial_write(writer, config),
|
||||
)
|
||||
|
||||
async def serial_write(writer: asyncio.StreamWriter, config):
|
||||
_CHUNKSIZE=10
|
||||
keys = list(found_repo.keys())
|
||||
chunks = [keys[i:i + _CHUNKSIZE] for i in range(0, len(keys), _CHUNKSIZE)]
|
||||
while True:
|
||||
print("Start Writing")
|
||||
for chunk in chunks:
|
||||
nasa_msg = NASAPacket()
|
||||
nasa_msg.set_packet_source_address_class(AddressClassEnum.JIGTester)
|
||||
nasa_msg.set_packet_source_channel(240)
|
||||
nasa_msg.set_packet_source_address(0)
|
||||
nasa_msg.set_packet_dest_address_class(AddressClassEnum.BroadcastSetLayer)
|
||||
nasa_msg.set_packet_dest_channel(0)
|
||||
nasa_msg.set_packet_dest_address(32)
|
||||
nasa_msg.set_packet_information(True)
|
||||
nasa_msg.set_packet_version(2)
|
||||
nasa_msg.set_packet_retry_count(0)
|
||||
nasa_msg.set_packet_type(PacketType.Normal)
|
||||
nasa_msg.set_packet_data_type(DataType.Read)
|
||||
nasa_msg.set_packet_number(166)
|
||||
msglist=[]
|
||||
for msg in chunk:
|
||||
tmpmsg = NASAMessage()
|
||||
tmpmsg.set_packet_message(int(msg, 16))
|
||||
value = 0
|
||||
if tmpmsg.packet_message_type == 0:
|
||||
value_raw = value.to_bytes(1, byteorder='big')
|
||||
elif tmpmsg.packet_message_type == 1:
|
||||
value_raw = value.to_bytes(2, byteorder='big')
|
||||
elif tmpmsg.packet_message_type == 2:
|
||||
value_raw = value.to_bytes(4, byteorder='big')
|
||||
else:
|
||||
value_raw = value.to_bytes(1, byteorder='big')
|
||||
|
||||
tmpmsg.set_packet_payload_raw(value_raw)
|
||||
msglist.append(tmpmsg)
|
||||
nasa_msg.set_packet_messages(msglist)
|
||||
raw = nasa_msg.to_raw()
|
||||
writer.write(raw)
|
||||
await writer.drain()
|
||||
await asyncio.sleep(1)
|
||||
print("End Writing")
|
||||
await asyncio.sleep(120)
|
||||
|
||||
async def serial_read(reader: asyncio.StreamReader, config):
|
||||
prev_byte = 0x00
|
||||
packet_started = False
|
||||
data = bytearray()
|
||||
packet_size = 0
|
||||
|
||||
while True:
|
||||
current_byte = await reader.read(1) # read bitewise
|
||||
|
||||
#data = await reader.read(1024)
|
||||
#data = await reader.readuntil(b'\x34fd')
|
||||
if current_byte:
|
||||
if packet_started:
|
||||
data.extend(current_byte)
|
||||
if len(data) == 3:
|
||||
packet_size = ((data[1] << 8) | data[2]) + 2
|
||||
|
||||
if packet_size <= len(data):
|
||||
asyncio.create_task(process_packet(data, config))
|
||||
data = bytearray()
|
||||
packet_started = False
|
||||
|
||||
# identify packet start
|
||||
if current_byte == b'\x00' and prev_byte == b'\x32':
|
||||
packet_started = True
|
||||
data.extend(prev_byte)
|
||||
data.extend(current_byte)
|
||||
|
||||
prev_byte = current_byte
|
||||
|
||||
def search_nasa_table(address):
|
||||
for key in NASA_REPO:
|
||||
if NASA_REPO[key]['address'].lower() == address.lower():
|
||||
return key
|
||||
|
||||
def is_valid_rawvalue(rawvalue: bytes) -> bool:
|
||||
return all(0x20 <= b <= 0x7E or b in (0x00, 0xFF) for b in rawvalue)
|
||||
|
||||
async def process_packet(buffer, config):
|
||||
try:
|
||||
nasa_packet = NASAPacket()
|
||||
nasa_packet.parse(buffer)
|
||||
for msg in nasa_packet.packet_messages:
|
||||
if f"0x{msg.packet_message:04X}" in found_repo:
|
||||
msgvalue = None
|
||||
if msg.packet_message_type == 3:
|
||||
msgvalue = ""
|
||||
|
||||
if is_valid_rawvalue(msg.packet_payload[1:-1]):
|
||||
for byte in msg.packet_payload[1:-1]:
|
||||
if byte != 0x00 and byte != 0xFF:
|
||||
char = chr(byte) if 32 <= byte <= 126 else f"{byte}"
|
||||
msgvalue += char
|
||||
else:
|
||||
msgvalue += " "
|
||||
msgvalue = msgvalue.strip()
|
||||
else:
|
||||
msgvalue = "".join([f"{int(x)}" for x in msg.packet_payload])
|
||||
else:
|
||||
msgvalue = int.from_bytes(msg.packet_payload, byteorder='big', signed=True)
|
||||
if msgvalue != found_repo[f"0x{msg.packet_message:04X}"]['packed_value']:
|
||||
line = f" {hex(msg.packet_message):<6} | {msg.packet_message_type} | {found_repo[f"0x{msg.packet_message:04X}"]['packed_value']} -> {msgvalue} |"
|
||||
print(line)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
except RuntimeError as e:
|
||||
print(f"Runtime error: {e}")
|
||||
97
helpertils/refreshNasaRepository.py
Normal file
97
helpertils/refreshNasaRepository.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import argparse
|
||||
import yaml
|
||||
|
||||
def replace_empty_with_null(d):
|
||||
|
||||
if isinstance(d, dict):
|
||||
return {k: replace_empty_with_null(v) for k, v in d.items()}
|
||||
elif isinstance(d, list):
|
||||
return [replace_empty_with_null(v) for v in d]
|
||||
elif d is None:
|
||||
return None # Ensure `None` stays as YAML `null`
|
||||
elif len(d.strip()) == 0:
|
||||
return None
|
||||
return d
|
||||
|
||||
def main():
|
||||
with open('data/NasaRepository.yml', 'r') as nasarepo:
|
||||
old_yaml = yaml.safe_load(nasarepo)
|
||||
|
||||
ele = {}
|
||||
|
||||
for key, value in old_yaml.items():
|
||||
print(key)
|
||||
ele[key] = {}
|
||||
ele[key]['hass_opts'] = {}
|
||||
ele[key]['hass_opts']['platform'] = {}
|
||||
ele[key]['address'] = replace_empty_with_null(old_yaml[key]['address'])
|
||||
if replace_empty_with_null(old_yaml[key]['arithmetic']) is not None:
|
||||
ele[key]['arithmetic'] = old_yaml[key]['arithmetic']
|
||||
if 'description' in old_yaml[key] and replace_empty_with_null(old_yaml[key]['description']) is not None:
|
||||
ele[key]['description'] = old_yaml[key]['description']
|
||||
ele[key]['hass_opts']['default_platform'] = "sensor"
|
||||
if 'writable' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['writable'] = old_yaml[key]['writable']
|
||||
else:
|
||||
ele[key]['hass_opts']['writable'] = False
|
||||
if 'enum' in old_yaml[key]:
|
||||
new_values = [x.replace("'", "") for x in old_yaml[key]['enum'].values()]
|
||||
if all([en.lower() in ['on', 'off'] for en in new_values]):
|
||||
ele[key]['enum'] = old_yaml[key]['enum']
|
||||
ele[key]['hass_opts']['default_platform'] = "binary_sensor"
|
||||
ele[key]['hass_opts']['platform']['payload_off'] = 'OFF'
|
||||
ele[key]['hass_opts']['platform']['payload_on'] = 'ON'
|
||||
ele[key]['hass_opts']['platform']['type'] = 'switch'
|
||||
else:
|
||||
ele[key]['enum'] = old_yaml[key]['enum']
|
||||
ele[key]['hass_opts']['platform']['options'] = new_values
|
||||
ele[key]['hass_opts']['platform']['type'] = 'select'
|
||||
else:
|
||||
if 'min' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['platform']['min'] = old_yaml[key]['min']
|
||||
if 'max' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['platform']['max'] = old_yaml[key]['max']
|
||||
if 'step' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['platform']['step'] = old_yaml[key]['step']
|
||||
ele[key]['hass_opts']['platform']['type'] = 'number'
|
||||
if replace_empty_with_null(old_yaml[key]['remarks']) is not None:
|
||||
ele[key]['remarks'] = old_yaml[key]['remarks']
|
||||
if replace_empty_with_null(old_yaml[key]['signed']) is not None:
|
||||
ele[key]['signed'] = old_yaml[key]['signed']
|
||||
if replace_empty_with_null(old_yaml[key]['type']) is not None:
|
||||
ele[key]['type'] = old_yaml[key]['type']
|
||||
|
||||
if 'state_class' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['state_class'] = old_yaml[key]['state_class']
|
||||
if 'device_class' in old_yaml[key]:
|
||||
ele[key]['hass_opts']['device_class'] = old_yaml[key]['device_class']
|
||||
|
||||
if 'unit' in old_yaml[key]:
|
||||
if replace_empty_with_null(old_yaml[key]['unit']) is not None:
|
||||
ele[key]['hass_opts']['unit'] = old_yaml[key]['unit']
|
||||
if ele[key]['hass_opts']['unit'] == "\u00b0C":
|
||||
ele[key]['hass_opts']['device_class'] = "temperature"
|
||||
elif ele[key]['hass_opts']['unit'] == '%':
|
||||
ele[key]['hass_opts']['state_class'] = "measurement"
|
||||
elif ele[key]['hass_opts']['unit'] == 'kW':
|
||||
ele[key]['hass_opts']['device_class'] = "power"
|
||||
elif ele[key]['hass_opts']['unit'] == 'rpm':
|
||||
ele[key]['hass_opts']['state_class'] = "measurement"
|
||||
elif ele[key]['hass_opts']['unit'] == 'bar':
|
||||
ele[key]['hass_opts']['device_class'] = "pressure"
|
||||
elif ele[key]['hass_opts']['unit'] == 'HP':
|
||||
ele[key]['hass_opts']['device_class'] = "power"
|
||||
elif ele[key]['hass_opts']['unit'] == 'hz':
|
||||
ele[key]['hass_opts']['device_class'] = "frequency"
|
||||
elif ele[key]['hass_opts']['unit'] == 'min':
|
||||
ele[key]['hass_opts']['device_class'] = "duration"
|
||||
elif ele[key]['hass_opts']['unit'] == 'h':
|
||||
ele[key]['hass_opts']['device_class'] = "duration"
|
||||
elif ele[key]['hass_opts']['unit'] == 's':
|
||||
ele[key]['hass_opts']['device_class'] = "duration"
|
||||
|
||||
with open('data/NasaRepository.yml', 'w') as newyaml:
|
||||
yaml.dump(ele, newyaml, default_flow_style=False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
16
helpertils/smartthings.http
Normal file
16
helpertils/smartthings.http
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
### list devices
|
||||
GET https://api.smartthings.com/v1/devices
|
||||
Authorization: Bearer xx
|
||||
|
||||
### "deviceId": "xx" "id": "samsungce.ehsFsvSettings", "version": 1
|
||||
GET https://api.smartthings.com/v1/capabilities/samsungce.ehsFsvSettings/1
|
||||
Authorization: Bearer xx
|
||||
|
||||
### "locationId": "xx" "name": "Mein Zuhause"
|
||||
GET https://api.smartthings.com/v1/locations
|
||||
Authorization: Bearer xx
|
||||
|
||||
###
|
||||
GET https://api.smartthings.com/v1/capabilities/samsungce.ehsFsvSettings/1/i18n/en
|
||||
Authorization: Bearer xx
|
||||
@@ -1,6 +1,7 @@
|
||||
aiofiles>=24.1.0
|
||||
future>=1.0.0
|
||||
gmqtt>=0.7.0
|
||||
iso8601>=2.1.0
|
||||
pyserial>=3.5
|
||||
pyserial-asyncio>=0.6
|
||||
PyYAML>=6.0.2
|
||||
serial>=0.0.97
|
||||
gmqtt>=0.7.0
|
||||
PyYAML>=6.0.2
|
||||
462
ressources/dashboard_controlmode_template.yaml
Normal file
462
ressources/dashboard_controlmode_template.yaml
Normal file
@@ -0,0 +1,462 @@
|
||||
views:
|
||||
- title: Overview
|
||||
type: sections
|
||||
max_columns: 4
|
||||
subview: false
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: select.samsung_ehssentinel_power
|
||||
name: Heatpump Power
|
||||
secondary_info: last-updated
|
||||
icon: mdi:power
|
||||
- entity: number.samsung_ehssentinel_intempwaterlawtargetf
|
||||
name: Adjust heating curve
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_indooropmode
|
||||
name: Operation Mode
|
||||
secondary_info: last-updated
|
||||
title: Control Heatpump
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_outdooroperationstatus
|
||||
name: Operation Status
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_dhwpower
|
||||
secondary_info: last-updated
|
||||
name: DHW Power
|
||||
- entity: sensor.samsung_ehssentinel_outdoordefroststep
|
||||
name: Defrost Step
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_controlsilence
|
||||
name: Silent Mode
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelheatoutput
|
||||
name: Heat Output
|
||||
secondary_info: last-updated
|
||||
icon: mdi:heat-wave
|
||||
- entity: sensor.samsung_ehssentinel_ingeneratedpowerlastminute
|
||||
name: Generated Power Last Minute
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inflowsensorcalc
|
||||
secondary_info: last-changed
|
||||
name: Water Flow Speed
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelcop
|
||||
name: COP
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw1temp
|
||||
name: Return Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:waves-arrow-left
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw2temp
|
||||
name: Flow Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:waves-arrow-right
|
||||
- entity: sensor.samsung_ehssentinel_indoordhwcurrenttemp
|
||||
name: DHW Tank Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:water-boiler
|
||||
- entity: sensor.samsung_ehssentinel_outdoorouttemp
|
||||
secondary_info: last-updated
|
||||
name: Outdoor Temperatur
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1targethz
|
||||
name: Compressor Target Frequence
|
||||
secondary_info: last-updated
|
||||
icon: mdi:sine-wave
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1runhz
|
||||
name: Compressor Run Frequence
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1orderhz
|
||||
name: Compressor Order Frequence
|
||||
secondary_info: last-updated
|
||||
title: Current Data
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_intotalgeneratedpower
|
||||
name: Total Generated Heat Output
|
||||
secondary_info: last-updated
|
||||
icon: mdi:heat-wave
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcontrolwattmeterallunitaccum
|
||||
name: Total Consumed Power
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_ehssentineltotalcop
|
||||
name: Total COP
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inminutessinceinstallation
|
||||
name: Total Minutes Since Installation
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inminutesactive
|
||||
name: Total Minutes Active
|
||||
secondary_info: last-updated
|
||||
title: Life Cycle Data
|
||||
- type: grid
|
||||
cards:
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1orderhz
|
||||
name: Compressor freq.
|
||||
- entity: sensor.samsung_ehssentinel_outdoorfanrpm1
|
||||
name: Outdoor FAN Speed
|
||||
logarithmic_scale: false
|
||||
title: Outdoor Unit
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 10
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw1temp
|
||||
name: Return Temperature
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw2temp
|
||||
name: Flow Temperature
|
||||
logarithmic_scale: false
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 10
|
||||
title: Water Law
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelheatoutput
|
||||
name: Heat Output
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcontrolwattmeterallunit
|
||||
name: Power Input
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelcop
|
||||
name: COP
|
||||
logarithmic_scale: false
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 16
|
||||
title: Efficiency
|
||||
column_span: 3
|
||||
- type: sections
|
||||
max_columns: 6
|
||||
title: Field Setting Value
|
||||
path: field-setting-value
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: number.samsung_ehssentinel_infsv1011
|
||||
name: Water Out Temp. for Cooling Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1012
|
||||
name: Water Out Temp. for Cooling Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1021
|
||||
name: Room Temp. for Cooling Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1022
|
||||
name: Room Temp. for Cooling Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1031
|
||||
name: Water Out Temp. for Heating Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1032
|
||||
name: Water Out Temp. for Heating Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1041
|
||||
name: Room Temp. for Heating Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1042
|
||||
name: Room Temp. for Heating Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1051
|
||||
name: DHW tank Temp. Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv1052
|
||||
name: DHW tank Temp. Min.
|
||||
secondary_info: last-updated
|
||||
title: FSV 10** - Remote Controller
|
||||
show_header_toggle: false
|
||||
state_color: false
|
||||
grid_options:
|
||||
columns: full
|
||||
column_span: 2
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: number.samsung_ehssentinel_infsv2011
|
||||
name: Heating Outdoor Temp. for WL Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2012
|
||||
name: Heating Outdoor Temp. for WL Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2021
|
||||
name: Heating Water out Temp. UFH/WL1 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2022
|
||||
name: Heating Water out Temp. UFH/WL1 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2031
|
||||
name: Heating Water out Temp. FCU/WL2 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2032
|
||||
name: Heating Water out Temp. FCU/WL2 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv2041
|
||||
name: Heating WL Selection
|
||||
icon: mdi:heating-coil
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2051
|
||||
name: Cooling Outdoor Temp. for WL Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2052
|
||||
name: Cooling Outdoor Temp. for WL Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2061
|
||||
name: Cooling Water out Temp UFH/WL1 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2062
|
||||
name: Cooling Water out Temp. UFH/WL1 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2071
|
||||
name: Cooling Water out Temp. FCU/WL2 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv2072
|
||||
name: Cooling Water out Temp. FCU/WL2 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv2081
|
||||
name: Cooling WL Selection
|
||||
secondary_info: last-updated
|
||||
icon: mdi:snowflake
|
||||
- entity: select.samsung_ehssentinel_infsv2091
|
||||
name: External Room Thermostat UFH
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv2092
|
||||
name: External Room Thermostat FCU
|
||||
secondary_info: last-updated
|
||||
title: FSV 20** - Water Law
|
||||
grid_options:
|
||||
columns: full
|
||||
column_span: 2
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: select.samsung_ehssentinel_infsv3011
|
||||
secondary_info: last-updated
|
||||
name: DHW Application
|
||||
icon: mdi:water-boiler
|
||||
- entity: number.samsung_ehssentinel_infsv3021
|
||||
secondary_info: last-updated
|
||||
name: Heat Pump Max. Temperature
|
||||
- entity: number.samsung_ehssentinel_infsv3022
|
||||
name: Heat Pump Stop
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3023
|
||||
name: Heat Pump Start
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3024
|
||||
name: Heat Pump Min. Space heating operation time
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3025
|
||||
name: Heat Pump Max. DHW operation time
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3026
|
||||
name: Heat Pump Max. Space heating operation time
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3032
|
||||
name: Booster Heat Delay Time
|
||||
secondary_info: last-updated
|
||||
- entity: switch.samsung_ehssentinel_infsv3041
|
||||
name: Disinfection Application
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv3042
|
||||
name: Disinfection Interval
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3043
|
||||
name: Disinfection Start Time
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3044
|
||||
name: Disinfection Target Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3045
|
||||
name: Disinfection Duration
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3046
|
||||
name: Disinfection Max Time
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv3051
|
||||
name: Forced DHW Operation Time OFF Function
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3052
|
||||
name: Farced DHW Operation Time Duration
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv3061
|
||||
name: Solar Panel/DHW Thermostat H/P Combination
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv3071
|
||||
name: Direction of 3Way Valve DHW Tank
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3081
|
||||
name: Energy Metering BUH 1 step capacity
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3082
|
||||
name: Energy Metering BUH 2 step capacity
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv3083
|
||||
name: Energy Metering BSH capacity
|
||||
secondary_info: last-updated
|
||||
title: FSV 30** - DHW code
|
||||
grid_options:
|
||||
columns: full
|
||||
column_span: 2
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: select.samsung_ehssentinel_infsv4011
|
||||
secondary_info: last-updated
|
||||
name: Heat Pump Heating/DHW Priority
|
||||
- entity: number.samsung_ehssentinel_infsv4012
|
||||
name: Heat Pump Outdoor Temp. for Priority
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4013
|
||||
name: Heat Pump Heat OFF
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4021
|
||||
name: Backup Heater Application
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4022
|
||||
name: Backup Heater BUH/BSH Priority
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4023
|
||||
name: Backup Heater Cold Weather Compensation
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4024
|
||||
name: Backup Heater Threshold Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4025
|
||||
name: Backup Heater Defrost Backup Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4031
|
||||
name: Backup Boiler Application
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4032
|
||||
name: Backup Boiler Boiler Priority
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4033
|
||||
name: Backup Boiler Threshold Power
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4041
|
||||
name: Mixing Valve Application
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4042
|
||||
name: Mixing Valve Target △T (Heating)
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4043
|
||||
secondary_info: last-updated
|
||||
name: Mixing Valve Target △T (Cooling)
|
||||
- entity: select.samsung_ehssentinel_infsv4044
|
||||
name: Mixing Valve Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4045
|
||||
name: Mixing Valve Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4046
|
||||
name: Mixing Valve Running Time
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4051
|
||||
name: Inverter Pump Application
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv4052
|
||||
name: Inverter Pump Target △T
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4053
|
||||
name: Inverter Pump Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv4061
|
||||
name: Zone Control Application
|
||||
secondary_info: last-updated
|
||||
title: FSV 40** - Heating code
|
||||
state_color: false
|
||||
grid_options:
|
||||
columns: full
|
||||
column_span: 2
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: number.samsung_ehssentinel_infsv5011
|
||||
secondary_info: last-updated
|
||||
name: Outing Mode Water Out Temp. for Cooling
|
||||
- entity: number.samsung_ehssentinel_infsv5012
|
||||
name: Outing Mode Room Temp. for Cooling
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5013
|
||||
name: Outing Mode Water Out Temp. for Heating
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5014
|
||||
name: Outing Mode Room Temp. for Heating
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5015
|
||||
name: Outing Mode Auto Cooling WL1 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5016
|
||||
name: Outing Mode Auto Cooling WL2 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5017
|
||||
name: Outing Mode Auto Heating WL1 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5018
|
||||
name: Outing Mode Auto Heating WL2 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5019
|
||||
name: Outing Mode Target Tank Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5021
|
||||
name: DHW Saving Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: switch.samsung_ehssentinel_infsv5022
|
||||
name: DHW Saving Mode
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5023
|
||||
name: DHW Saving Thermo on Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5041
|
||||
name: Power Peak Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5042
|
||||
name: Power Peak Control Select Forced Off Parts
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5043
|
||||
name: Power Peak Control Using Input Voltage
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5051
|
||||
name: Frequency Ratio Control
|
||||
- entity: select.samsung_ehssentinel_infsv5061
|
||||
name: Ratio of hot water supply compare to heating
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5081
|
||||
name: PV Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5082
|
||||
name: PV Control Setting Temp. Shift Value (Cool)
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5083
|
||||
name: PV Control Setting Temp. Shift Value (Heat)
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5091
|
||||
name: Smart Grid Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5092
|
||||
name: Smart Grid Control Setting Temp. Shift Value (Heat)
|
||||
secondary_info: last-updated
|
||||
- entity: number.samsung_ehssentinel_infsv5093
|
||||
name: Smart Grid Control Setting Temp. Shift Value (DHW)
|
||||
secondary_info: last-updated
|
||||
- entity: select.samsung_ehssentinel_infsv5094
|
||||
name: Smart Grid Control DHW Mode
|
||||
secondary_info: last-updated
|
||||
title: FSV 50** - Others code
|
||||
grid_options:
|
||||
columns: full
|
||||
column_span: 2
|
||||
cards: []
|
||||
dense_section_placement: true
|
||||
438
ressources/dashboard_readonly_template.yaml
Normal file
438
ressources/dashboard_readonly_template.yaml
Normal file
@@ -0,0 +1,438 @@
|
||||
views:
|
||||
- title: Overview
|
||||
type: sections
|
||||
max_columns: 4
|
||||
subview: false
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_power
|
||||
name: Heatpump Power
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdooroperationstatus
|
||||
name: Operation Mode
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_dhwpower
|
||||
name: DHW Power
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_controlsilence
|
||||
name: Silence Mode
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdoordefroststep
|
||||
name: Defrost Step
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelheatoutput
|
||||
name: Heat Output
|
||||
secondary_info: last-updated
|
||||
icon: mdi:heat-wave
|
||||
- entity: sensor.samsung_ehssentinel_ingeneratedpowerlastminute
|
||||
name: Generated Power Last Minute
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inflowsensorcalc
|
||||
secondary_info: last-changed
|
||||
name: Water Flow Speed
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelcop
|
||||
name: COP
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw1temp
|
||||
name: Return Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:waves-arrow-left
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw2temp
|
||||
name: Flow Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:waves-arrow-right
|
||||
- entity: sensor.samsung_ehssentinel_indoordhwcurrenttemp
|
||||
name: DHW Tank Temperature
|
||||
secondary_info: last-updated
|
||||
icon: mdi:water-boiler
|
||||
- entity: sensor.samsung_ehssentinel_outdoorouttemp
|
||||
secondary_info: last-updated
|
||||
name: Outdoor Temperatur
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1targethz
|
||||
name: Compressor Target Frequence
|
||||
secondary_info: last-updated
|
||||
icon: mdi:sine-wave
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1runhz
|
||||
name: Compressor Run Frequence
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1orderhz
|
||||
name: Compressor Order Frequence
|
||||
secondary_info: last-updated
|
||||
title: Current Data
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_intotalgeneratedpower
|
||||
name: Total Generated Heat Output
|
||||
secondary_info: last-updated
|
||||
icon: mdi:heat-wave
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcontrolwattmeterallunitaccum
|
||||
name: Total Consumed Power
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_ehssentineltotalcop
|
||||
name: Total COP
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inminutessinceinstallation
|
||||
name: Total Minutes Since Installation
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_inminutesactive
|
||||
name: Total Minutes Active
|
||||
secondary_info: last-updated
|
||||
title: Life Cycle Data
|
||||
- type: grid
|
||||
cards:
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcomp1orderhz
|
||||
name: Compressor freq.
|
||||
- entity: sensor.samsung_ehssentinel_outdoorfanrpm1
|
||||
name: Outdoor FAN Speed
|
||||
logarithmic_scale: false
|
||||
title: Outdoor Unit
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 10
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw1temp
|
||||
name: Return Temperature
|
||||
- entity: sensor.samsung_ehssentinel_outdoortw2temp
|
||||
name: Flow Temperature
|
||||
logarithmic_scale: false
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 10
|
||||
title: Water Law
|
||||
- type: history-graph
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelheatoutput
|
||||
name: Heat Output
|
||||
- entity: sensor.samsung_ehssentinel_outdoorcontrolwattmeterallunit
|
||||
name: Power Input
|
||||
- entity: sensor.samsung_ehssentinel_ehssentinelcop
|
||||
name: COP
|
||||
logarithmic_scale: false
|
||||
hours_to_show: 6
|
||||
grid_options:
|
||||
columns: full
|
||||
rows: 16
|
||||
title: Efficiency
|
||||
column_span: 3
|
||||
- type: sections
|
||||
max_columns: 5
|
||||
title: Field Setting Value
|
||||
path: field-setting-value
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_infsv1011
|
||||
name: Water Out Temp. for Cooling Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1012
|
||||
name: Water Out Temp. for Cooling Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1021
|
||||
name: Room Temp. for Cooling Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1022
|
||||
name: Room Temp. for Cooling Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1031
|
||||
name: Water Out Temp. for Heating Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1032
|
||||
name: Water Out Temp. for Heating Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1041
|
||||
name: Room Temp. for Heating Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1042
|
||||
name: Room Temp. for Heating Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1051
|
||||
name: DHW tank Temp. Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv1052
|
||||
name: DHW tank Temp. Min.
|
||||
secondary_info: last-updated
|
||||
title: FSV 10** - Remote Controller
|
||||
show_header_toggle: false
|
||||
state_color: false
|
||||
column_span: 1
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_infsv2011
|
||||
name: Heating Outdoor Temp. for WL Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2012
|
||||
name: Heating Outdoor Temp. for WL Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2021
|
||||
name: Heating Water out Temp. UFH/WL1 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2022
|
||||
name: Heating Water out Temp. UFH/WL1 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2031
|
||||
name: Heating Water out Temp. FCU/WL2 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2032
|
||||
name: Heating Water out Temp. FCU/WL2 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2041
|
||||
name: Heating WL Selection
|
||||
icon: mdi:heating-coil
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2051
|
||||
name: Cooling Outdoor Temp. for WL Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2052
|
||||
name: Cooling Outdoor Temp. for WL Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2061
|
||||
name: Cooling Water out Temp UFH/WL1 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2062
|
||||
name: Cooling Water out Temp. UFH/WL1 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2071
|
||||
name: Cooling Water out Temp. FCU/WL2 Max.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2072
|
||||
name: Cooling Water out Temp. FCU/WL2 Min.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2081
|
||||
name: Cooling WL Selection
|
||||
secondary_info: last-updated
|
||||
icon: mdi:snowflake
|
||||
- entity: sensor.samsung_ehssentinel_infsv2091
|
||||
name: External Room Thermostat UFH
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv2092
|
||||
name: External Room Thermostat FCU
|
||||
secondary_info: last-updated
|
||||
title: FSV 20** - Water Law
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_infsv3011
|
||||
secondary_info: last-updated
|
||||
name: DHW Application
|
||||
icon: mdi:water-boiler
|
||||
- entity: sensor.samsung_ehssentinel_infsv3021
|
||||
secondary_info: last-updated
|
||||
name: Heat Pump Max. Temperature
|
||||
- entity: sensor.samsung_ehssentinel_infsv3022
|
||||
name: Heat Pump Stop
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3023
|
||||
name: Heat Pump Start
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3024
|
||||
name: Heat Pump Min. Space heating operation time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3025
|
||||
name: Heat Pump Max. DHW operation time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3026
|
||||
name: Heat Pump Max. Space heating operation time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3032
|
||||
name: Booster Heat Delay Time
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_infsv3041
|
||||
name: Disinfection Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3042
|
||||
name: Disinfection Interval
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3043
|
||||
name: Disinfection Start Time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3044
|
||||
name: Disinfection Target Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3045
|
||||
name: Disinfection Duration
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3046
|
||||
name: Disinfection Max Time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3051
|
||||
name: Forced DHW Operation Time OFF Function
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3052
|
||||
name: Farced DHW Operation Time Duration
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3061
|
||||
name: Solar Panel/DHW Thermostat H/P Combination
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3071
|
||||
name: Direction of 3Way Valve DHW Tank
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3081
|
||||
name: Energy Metering BUH 1 step capacity
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3082
|
||||
name: Energy Metering BUH 2 step capacity
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv3083
|
||||
name: Energy Metering BSH capacity
|
||||
secondary_info: last-updated
|
||||
title: FSV 30** - DHW code
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_infsv4011
|
||||
secondary_info: last-updated
|
||||
name: Heat Pump Heating/DHW Priority
|
||||
- entity: sensor.samsung_ehssentinel_infsv4012
|
||||
name: Heat Pump Outdoor Temp. for Priority
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4013
|
||||
name: Heat Pump Heat OFF
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4021
|
||||
name: Backup Heater Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4022
|
||||
name: Backup Heater BUH/BSH Priority
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4023
|
||||
name: Backup Heater Cold Weather Compensation
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4024
|
||||
name: Backup Heater Threshold Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4025
|
||||
name: Backup Heater Defrost Backup Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4031
|
||||
name: Backup Boiler Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4032
|
||||
name: Backup Boiler Boiler Priority
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4033
|
||||
name: Backup Boiler Threshold Power
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4041
|
||||
name: Mixing Valve Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4042
|
||||
name: Mixing Valve Target △T (Heating)
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4043
|
||||
secondary_info: last-updated
|
||||
name: Mixing Valve Target △T (Cooling)
|
||||
- entity: sensor.samsung_ehssentinel_infsv4044
|
||||
name: Mixing Valve Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4045
|
||||
name: Mixing Valve Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4046
|
||||
name: Mixing Valve Running Time
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4051
|
||||
name: Inverter Pump Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4052
|
||||
name: Inverter Pump Target △T
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4053
|
||||
name: Inverter Pump Control Factor
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv4061
|
||||
name: Zone Control Application
|
||||
secondary_info: last-updated
|
||||
title: FSV 40** - Heating code
|
||||
state_color: false
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: sensor.samsung_ehssentinel_infsv5011
|
||||
secondary_info: last-updated
|
||||
name: Outing Mode Water Out Temp. for Cooling
|
||||
- entity: sensor.samsung_ehssentinel_infsv5012
|
||||
name: Outing Mode Room Temp. for Cooling
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5013
|
||||
name: Outing Mode Water Out Temp. for Heating
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5014
|
||||
name: Outing Mode Room Temp. for Heating
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5015
|
||||
name: Outing Mode Auto Cooling WL1 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5016
|
||||
name: Outing Mode Auto Cooling WL2 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5017
|
||||
name: Outing Mode Auto Heating WL1 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5018
|
||||
name: Outing Mode Auto Heating WL2 Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5019
|
||||
name: Outing Mode Target Tank Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5021
|
||||
name: DHW Saving Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: binary_sensor.samsung_ehssentinel_infsv5022
|
||||
name: DHW Saving Mode
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5023
|
||||
name: DHW Saving Thermo on Temp.
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5041
|
||||
name: Power Peak Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5042
|
||||
name: Power Peak Control Select Forced Off Parts
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5043
|
||||
name: Power Peak Control Using Input Voltage
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5051
|
||||
name: Frequency Ratio Control
|
||||
- entity: sensor.samsung_ehssentinel_infsv5061
|
||||
name: Ratio of hot water supply compare to heating
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5081
|
||||
name: PV Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5082
|
||||
name: PV Control Setting Temp. Shift Value (Cool)
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5083
|
||||
name: PV Control Setting Temp. Shift Value (Heat)
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5091
|
||||
name: Smart Grid Control Application
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5092
|
||||
name: Smart Grid Control Setting Temp. Shift Value (Heat)
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5093
|
||||
name: Smart Grid Control Setting Temp. Shift Value (DHW)
|
||||
secondary_info: last-updated
|
||||
- entity: sensor.samsung_ehssentinel_infsv5094
|
||||
name: Smart Grid Control DHW Mode
|
||||
secondary_info: last-updated
|
||||
title: FSV 50** - Others code
|
||||
cards: []
|
||||
dense_section_placement: true
|
||||
BIN
ressources/images/dashboard1.png
Normal file
BIN
ressources/images/dashboard1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
BIN
ressources/images/dashboard2.png
Normal file
BIN
ressources/images/dashboard2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
BIN
ressources/images/dashboard3.png
Normal file
BIN
ressources/images/dashboard3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 445 KiB |
BIN
ressources/images/dashboard_cm1.png
Normal file
BIN
ressources/images/dashboard_cm1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
BIN
ressources/images/dashboard_cm2.png
Normal file
BIN
ressources/images/dashboard_cm2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 293 KiB |
BIN
ressources/images/dashboard_cm3.png
Normal file
BIN
ressources/images/dashboard_cm3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 254 KiB |
@@ -3,20 +3,21 @@ import serial
|
||||
import serial_asyncio
|
||||
import traceback
|
||||
from MessageProcessor import MessageProcessor
|
||||
from MessageProducer import MessageProducer
|
||||
from EHSArguments import EHSArguments
|
||||
from EHSConfig import EHSConfig
|
||||
from EHSExceptions import MessageWarningException
|
||||
from EHSExceptions import MessageWarningException, SkipInvalidPacketException
|
||||
from MQTTClient import MQTTClient
|
||||
import aiofiles
|
||||
import json
|
||||
import struct
|
||||
import binascii
|
||||
import random
|
||||
|
||||
# Get the logger
|
||||
from CustomLogger import logger, setSilent
|
||||
from NASAPacket import NASAPacket
|
||||
from CustomLogger import logger
|
||||
from NASAPacket import NASAPacket, AddressClassEnum, PacketType, DataType
|
||||
from NASAMessage import NASAMessage
|
||||
|
||||
version = "0.1SNAPSHOT"
|
||||
version = "1.0.0 Stable"
|
||||
|
||||
async def main():
|
||||
"""
|
||||
@@ -26,9 +27,8 @@ async def main():
|
||||
2. Reads command-line arguments.
|
||||
3. Reads configuration settings.
|
||||
4. Connects to the MQTT broker.
|
||||
5. Sets silent mode if specified in the configuration.
|
||||
6. If dry run mode is enabled, reads data from a dump file and processes it.
|
||||
7. If not in dry run mode, reads data from a serial port and processes it.
|
||||
5. If dry run mode is enabled, reads data from a dump file and processes it.
|
||||
6. If not in dry run mode, reads data from a serial port and processes it.
|
||||
Args:
|
||||
None
|
||||
Returns:
|
||||
@@ -61,176 +61,137 @@ async def main():
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# if Silent is true, set Silent Mode
|
||||
if config.GENERAL['silentMode']:
|
||||
setSilent()
|
||||
|
||||
# if dryrun then we read from dumpfile
|
||||
if args.DRYRUN:
|
||||
logger.info(f"DRYRUN detected, reading from dumpfile {args.DUMPFILE}")
|
||||
async with aiofiles.open(args.DUMPFILE, mode='r') as file:
|
||||
async for line in file:
|
||||
line = json.loads(line.strip())
|
||||
await process_packet(line, args)
|
||||
try:
|
||||
line = json.loads(line.strip()) # for [12, 234, 456 ,67]
|
||||
except:
|
||||
line = line.strip().replace("'", "").replace("[", "").replace("]", "").split(", ") # for ['0x1', '0x2' ..]
|
||||
line = [int(value, 16) for value in line]
|
||||
await process_packet(line, args, config)
|
||||
else:
|
||||
# we are not in dryrun mode, so we need to read from Serial Pimort
|
||||
await serial_read(config, args)
|
||||
await serial_connection(config, args)
|
||||
|
||||
async def process_buffer(buffer, args):
|
||||
"""
|
||||
Processes a buffer of data asynchronously, identifying and handling packets based on specific criteria.
|
||||
Args:
|
||||
buffer (list): A list of bytes representing the buffer to be processed.
|
||||
args (Any): Additional arguments to be passed to the packet processing function.
|
||||
Notes:
|
||||
- The function continuously checks the buffer for data.
|
||||
- If the first byte of the buffer is 0x32, it is considered a start byte.
|
||||
- The packet size is determined by combining the second and third bytes of the buffer.
|
||||
- If the buffer contains enough data for a complete packet, the packet is processed.
|
||||
- If the buffer does not contain enough data, the function waits and checks again.
|
||||
- Non-start bytes are removed from the buffer.
|
||||
- The function sleeps for 0.03 seconds between iterations to avoid busy-waiting.
|
||||
Logging:
|
||||
- Logs the buffer size when data is present.
|
||||
- Logs when the start byte is recognized.
|
||||
- Logs the calculated packet size.
|
||||
- Logs the complete packet and the last byte read when a packet is processed.
|
||||
- Logs if the buffer is too small to read a complete packet.
|
||||
- Logs if a received byte is not a start byte.
|
||||
"""
|
||||
|
||||
while True:
|
||||
if buffer:
|
||||
logger.debug(f"Buffersize: {len(buffer)}")
|
||||
if buffer[0] == 0x32:
|
||||
logger.debug("Start Byte recognized")
|
||||
packet_size = ((buffer[1] << 8) | buffer[2]) +2
|
||||
logger.debug(f"Readed packet size: {packet_size-1}")
|
||||
if len(buffer) > packet_size-1:
|
||||
packet = []
|
||||
for i in range(0, len(buffer)):
|
||||
packet.append(buffer[i])
|
||||
if i == packet_size-1: #buffer[i] == 0x34 or
|
||||
logger.debug(f"Complete Packet: {i}/{packet_size-1}")
|
||||
logger.debug(f"Last Byte readed: {hex(buffer[i])}")
|
||||
await process_packet(packet, args)
|
||||
del buffer[0:i]
|
||||
break
|
||||
else:
|
||||
logger.debug(f"Buffer to small to read hole packet, wait... buffer size {len(buffer)} packet size {packet_size}")
|
||||
else:
|
||||
logger.debug(f"Received byte not a startbyte 0x32 {buffer[0]} / {hex(buffer[0])}")
|
||||
buffer.pop(0)
|
||||
|
||||
await asyncio.sleep(0.03)
|
||||
|
||||
async def serial_read(config, args):
|
||||
"""
|
||||
Asynchronously reads data from a serial connection and processes it.
|
||||
Args:
|
||||
config (object): Configuration object containing serial connection parameters.
|
||||
args (object): Additional arguments for buffer processing.
|
||||
This function establishes a serial connection using parameters from the config object,
|
||||
reads data from the serial port until a specified delimiter (0x34) is encountered,
|
||||
and appends the received data to a buffer. It also starts an asynchronous task to
|
||||
process the buffer.
|
||||
The serial connection is configured with the following parameters:
|
||||
- Device URL: config.SERIAL['device']
|
||||
- Baudrate: config.SERIAL['baudrate']
|
||||
- Parity: Even
|
||||
- Stopbits: One
|
||||
- Bytesize: Eight
|
||||
- RTS/CTS flow control: Enabled
|
||||
- Timeout: 0
|
||||
The function runs an infinite loop to continuously read data from the serial port.
|
||||
"""
|
||||
async def process_buffer(buffer, args, config):
|
||||
if buffer:
|
||||
if (len(buffer) > 14):
|
||||
for i in range(0, len(buffer)):
|
||||
if buffer[i] == 0x32:
|
||||
if (len(buffer[i:]) > 14):
|
||||
asyncio.create_task(process_packet(buffer[i:], args, config))
|
||||
else:
|
||||
logger.debug(f"Buffermessages to short for NASA {len(buffer)}")
|
||||
break
|
||||
else:
|
||||
logger.debug(f"Buffer to short for NASA {len(buffer)}")
|
||||
|
||||
async def serial_connection(config, args):
|
||||
buffer = []
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
reader, writer = await serial_asyncio.open_serial_connection(
|
||||
loop=loop,
|
||||
url=config.SERIAL['device'],
|
||||
baudrate=config.SERIAL['baudrate'],
|
||||
parity=serial.PARITY_EVEN,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
rtscts=True,
|
||||
timeout=0
|
||||
)
|
||||
|
||||
# start the async buffer process
|
||||
asyncio.create_task(process_buffer(buffer, args))# start the async buffer process
|
||||
|
||||
# TODO have to be tested and verified, please do not try it yet
|
||||
# start the async writer process
|
||||
#asyncio.create_task(serial_write(writer, reader))
|
||||
|
||||
# Read loop
|
||||
while True:
|
||||
data = await reader.readuntil(b'\x34') # Read up to end of next message 0x34
|
||||
if data:
|
||||
buffer.extend(data)
|
||||
logger.debug(f"Received: {[hex(x) for x in data]}")
|
||||
|
||||
async def serial_write(writer, reader):
|
||||
"""
|
||||
|
||||
TODO Not used yet, only for future use...
|
||||
|
||||
|
||||
Asynchronously writes data to the serial port.
|
||||
This function sends data through the serial port at regular intervals.
|
||||
Args:
|
||||
transport: The serial transport object.
|
||||
args: Additional arguments.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
while True:
|
||||
await asyncio.sleep(5)
|
||||
# Example data to write
|
||||
if config.TCP is not None:
|
||||
reader, writer = await asyncio.open_connection(config.TCP['ip'], config.TCP['port'])
|
||||
else:
|
||||
reader, writer = await serial_asyncio.open_serial_connection(
|
||||
loop=loop,
|
||||
url=config.SERIAL['device'],
|
||||
baudrate=config.SERIAL['baudrate'],
|
||||
parity=serial.PARITY_EVEN,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
rtscts=True,
|
||||
timeout=1
|
||||
)
|
||||
|
||||
packet = bytearray([
|
||||
#0x32, # Packet Start Byte
|
||||
#0x00, 0x12, # Packet Size
|
||||
0x80, # Source Address Class JIGTester
|
||||
0xFF, # Source Channel
|
||||
0x00, # Source Address
|
||||
0x20, # Destination Address Class Indoor
|
||||
0x00, # Destination Channel
|
||||
0x00, # Destination Address
|
||||
0xC0, # Packet Information + Protocol Version + Retry Count
|
||||
0x11, # Packet Type [Normal = 1] + Data Type [Read = 1]
|
||||
0xF0, # Packet Number
|
||||
0x01, # Capacity (Number of Messages)
|
||||
0x42, 0x56, # NASA Message Number
|
||||
0x00, 0x00 # Message Payload (placeholder for return value)
|
||||
])
|
||||
await asyncio.gather(
|
||||
serial_read(reader, args, config),
|
||||
serial_write(writer, config),
|
||||
)
|
||||
|
||||
crc=binascii.crc_hqx(packet, 0)
|
||||
# NOTE: include length of CRC(2) and length of length field(2) in the
|
||||
# total length, exclude SF/TF of total length
|
||||
final_packet = struct.pack(">BH", 0x32, len(packet)+2+2) + packet + struct.pack(">HB", crc, 0x34)
|
||||
# ['0x32', '0x0', '0x12', '0x80', '0xff', '0x0', '0x20', '0x0', '0x0', '0xc0', '0x11', '0xf0', '0x1', '0x42', '0x56', '0x0', '0x0', '0xf9', '0x65', '0x34']
|
||||
# ['0x32', '0x0', '0x12', '0x80', '0xff', '0x0', '0x20', '0x0', '0x0', '0xc0', '0x11', '0xf0', '0x1', '0x42', '0x56', '0x0', '0x0', '0x38', '0xc6', '0x34']
|
||||
writer.write(final_packet)
|
||||
await writer.drain()
|
||||
logger.debug(f"Sent data raw: {final_packet}")
|
||||
logger.debug(f"Sent data raw: {[hex(x) for x in final_packet]}")
|
||||
await asyncio.sleep(1) # Adjust the interval as needed
|
||||
async def serial_read(reader: asyncio.StreamReader, args, config):
|
||||
prev_byte = 0x00
|
||||
packet_started = False
|
||||
data = bytearray()
|
||||
packet_size = 0
|
||||
|
||||
async def process_packet(buffer, args):
|
||||
"""
|
||||
Asynchronously processes a packet buffer.
|
||||
If `dumpWriter` is `None`, it attempts to process the packet using `MessageProcessor`.
|
||||
If a `MessageWarningException` is raised, it logs a warning and skips the packet.
|
||||
If any other exception is raised, it logs an error, skips the packet, and logs the stack trace.
|
||||
If `dumpWriter` is not `None`, it writes the buffer to `dumpWriter`.
|
||||
Args:
|
||||
buffer (bytes): The packet buffer to be processed.
|
||||
"""
|
||||
while True:
|
||||
current_byte = await reader.read(1) # read bitewise
|
||||
#data = await reader.read(1024)
|
||||
#data = await reader.readuntil(b'\x34fd')
|
||||
if current_byte:
|
||||
if packet_started:
|
||||
data.extend(current_byte)
|
||||
if len(data) == 3:
|
||||
packet_size = ((data[1] << 8) | data[2]) + 2
|
||||
|
||||
if packet_size <= len(data):
|
||||
if current_byte == b'\x34':
|
||||
asyncio.create_task(process_buffer(data, args, config))
|
||||
logger.debug(f"Received int: {data}")
|
||||
logger.debug(f"Received hex: {[hex(x) for x in data]}")
|
||||
data = bytearray()
|
||||
packet_started = False
|
||||
else:
|
||||
if config.LOGGING['invalidPacket']:
|
||||
logger.warning(f"Packet does not end with an x34. Size {packet_size} length {len(data)}")
|
||||
logger.warning(f"Received hex: {[hex(x) for x in data]}")
|
||||
logger.warning(f"Received raw: {data}")
|
||||
else:
|
||||
logger.debug(f"Packet does not end with an x34. Size {packet_size} length {len(data)}")
|
||||
logger.debug(f"Received hex: {[hex(x) for x in data]}")
|
||||
logger.debug(f"Received raw: {data}")
|
||||
|
||||
data = bytearray()
|
||||
packet_started = False
|
||||
|
||||
# identify packet start
|
||||
if current_byte == b'\x00' and prev_byte == b'\x32':
|
||||
packet_started = True
|
||||
data.extend(prev_byte)
|
||||
data.extend(current_byte)
|
||||
|
||||
prev_byte = current_byte
|
||||
|
||||
#await asyncio.sleep(0.001) # Yield control to other tasks
|
||||
|
||||
|
||||
async def serial_write(writer:asyncio.StreamWriter, config):
|
||||
producer = MessageProducer(writer=writer)
|
||||
|
||||
# Wait 20s befor initial polling
|
||||
await asyncio.sleep(20)
|
||||
|
||||
if config.POLLING is not None:
|
||||
for poller in config.POLLING['fetch_interval']:
|
||||
if poller['enable']:
|
||||
await asyncio.sleep(1)
|
||||
asyncio.create_task(make_default_request_packet(producer=producer, config=config, poller=poller))
|
||||
|
||||
async def make_default_request_packet(producer: MessageProducer, config: EHSConfig, poller):
|
||||
logger.info(f"Setting up Poller {poller['name']} every {poller['schedule']} seconds")
|
||||
message_list = []
|
||||
for message in config.POLLING['groups'][poller['name']]:
|
||||
message_list.append(message)
|
||||
|
||||
while True:
|
||||
try:
|
||||
await producer.read_request(message_list)
|
||||
except MessageWarningException as e:
|
||||
logger.warning("Polling Messages was not successfull")
|
||||
logger.warning(f"Error processing message: {e}")
|
||||
logger.warning(f"Message List: {message_list}")
|
||||
except Exception as e:
|
||||
logger.error("Error Accured, Polling will be skipped")
|
||||
logger.error(f"Error processing message: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
await asyncio.sleep(poller['schedule'])
|
||||
logger.info(f"Refresh Poller {poller['name']}")
|
||||
|
||||
async def process_packet(buffer, args, config):
|
||||
if args.DUMPFILE and not args.DRYRUN:
|
||||
async with aiofiles.open(args.DUMPFILE, "a") as dumpWriter:
|
||||
await dumpWriter.write(f"{buffer}\n")
|
||||
@@ -241,18 +202,47 @@ async def process_packet(buffer, args):
|
||||
logger.debug("Packet processed: ")
|
||||
logger.debug(f"Packet raw: {[hex(x) for x in buffer]}")
|
||||
logger.debug(nasa_packet)
|
||||
messageProcessor = MessageProcessor()
|
||||
messageProcessor.process_message(nasa_packet)
|
||||
if nasa_packet.packet_source_address_class in (AddressClassEnum.Outdoor, AddressClassEnum.Indoor):
|
||||
messageProcessor = MessageProcessor()
|
||||
await messageProcessor.process_message(nasa_packet)
|
||||
elif nasa_packet.packet_source_address_class == AddressClassEnum.WiFiKit and \
|
||||
nasa_packet.packet_dest_address_class == AddressClassEnum.BroadcastSelfLayer and \
|
||||
nasa_packet.packet_data_type == DataType.Notification:
|
||||
pass
|
||||
else:
|
||||
if config.LOGGING['packetNotFromIndoorOutdoor']:
|
||||
logger.info("Message not From Indoor or Outdoor")
|
||||
logger.info(nasa_packet)
|
||||
logger.info(f"Packet int: {[x for x in buffer]}")
|
||||
logger.info(f"Packet hex: {[hex(x) for x in buffer]}")
|
||||
else:
|
||||
logger.debug("Message not From Indoor or Outdoor")
|
||||
logger.debug(nasa_packet)
|
||||
logger.debug(f"Packet int: {[x for x in buffer]}")
|
||||
logger.debug(f"Packet hex: {[hex(x) for x in buffer]}")
|
||||
except ValueError as e:
|
||||
logger.warning("Value Error on parsing Packet, Packet will be skipped")
|
||||
logger.warning(f"Error processing message: {e}")
|
||||
logger.warning(f"Complete Packet: {[hex(x) for x in buffer]}")
|
||||
logger.warning(traceback.format_exc())
|
||||
except SkipInvalidPacketException as e:
|
||||
logger.debug("Warnung accured, Packet will be skipped")
|
||||
logger.debug(f"Error processing message: {e}")
|
||||
logger.debug(f"Complete Packet: {[hex(x) for x in buffer]}")
|
||||
logger.debug(traceback.format_exc())
|
||||
except MessageWarningException as e:
|
||||
logger.warning("Warnung accured, Packet will be skipped")
|
||||
logger.warning(f"Error processing message: {e}")
|
||||
logger.warning(f"Complete Packet: {[hex(x) for x in buffer]}")
|
||||
logger.warning(traceback.format_exc())
|
||||
except Exception as e:
|
||||
logger.error("Error Accured, Packet will be skipped")
|
||||
logger.error(f"Error processing message: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
except RuntimeError as e:
|
||||
logger.error(f"Runtime error: {e}")
|
||||
Reference in New Issue
Block a user