Feature/0.1.0 releasebranch (#6)

* device_class: measurement for NASA_EHSSENTINEL_COP and NASA_EHSSENTINEL_HEAT_OUTPUT

* state_class: measurement for NASA_EHSSENTINEL_COP and NASA_EHSSENTINEL_HEAT_OUTPUT

* ENUM_IN_WATERPUMP_PWM_VALUE as var not enum
Unit % as  = measurement

* NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM state_class: total_increasing

* NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM device_class and unit

* NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT_ACCUM device_class energy

* fixing ValueError and better logging for determine_value try catch

* update reqierments and rreadme for venv

* ENUM_IN_FSV_2041 enum value fixed

* large buffer log

* prevent buffer overloading

* remove await

* a

* Feature/test without buffer (#5)

* test

* test

* test

* devision by 0 error fixed
remove task print

* logger

* topic clear only on online message

* expand logging

* reduce logging
This commit is contained in:
echoDaveD
2025-02-14 18:27:15 +01:00
committed by GitHub
parent ef1e0a0f79
commit cce625dabb
7 changed files with 52 additions and 46 deletions

View File

@@ -99,7 +99,7 @@ def setSilent():
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.info("Silent Mode is turning on, only Messages at Level WARNING or higher are displayed")
#logger.setLevel(logging.ERROR)
# Add the filter to suppress INFO level messages from MessageProcessor.py
logger.addFilter(MessageProcessorFilter())

View File

@@ -47,7 +47,7 @@ class ArgumentException(EHSException):
def __str__(self):
return f'{self.argument} -> {self.message}'
class InvalidMessageTypeException(EHSException):
class MessageCapacityStructureWarning(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}'

View File

@@ -90,7 +90,7 @@ class MQTTClient:
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
@@ -182,8 +182,9 @@ class MQTTClient:
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")
if payload.decode() == "online":
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):
"""

View File

@@ -76,7 +76,7 @@ class MessageProcessor:
try:
msgvalue = self.determine_value(msg.packet_payload, msgname)
except Exception as e:
raise MessageWarningException(argument=msg.packet_payload, message=f"Value of {hexmsg} couldn't be determinate, skip Message {e}")
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}")
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}")
@@ -111,8 +111,8 @@ class MessageProcessor:
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(
"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)
@@ -121,9 +121,10 @@ class MessageProcessor:
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 (self.NASA_VAL_STORE['NASA_OUTDOOR_CONTROL_WATTMETER_ALL_UNIT'] > 0):
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))
def search_nasa_table(self, address):
"""

View File

@@ -1,5 +1,6 @@
from enum import Enum
from NASAMessage import NASAMessage
from EHSExceptions import MessageCapacityStructureWarning
class AddressClassEnum(Enum):
"""
@@ -259,7 +260,7 @@ class NASAPacket:
elif message_type == 3:
payload_size = len(msg_rest)
if capacity != 1:
raise ValueError("Message with structure type must have capacity of 1.")
raise MessageCapacityStructureWarning("Message with structure type must have capacity of 1.")
else:
raise ValueError(f"Mssage type unknown: {message_type}")

View File

@@ -2991,6 +2991,8 @@ NASA_OUTDOOR_DEFROST_STEP:
0x05: 'Defrost stage 5'
0x06: 'Defrost stage 6'
0x07: 'Defrost operation end stage'
0x08: 'Defrost stage 8'
0x09: 'Defrost stage 9'
0xFF: 'No defrost operation'
remarks: 1 Defrost stage 1, 2 Defrost stage 2, 3 Defrost stage 3, 4 Defrost stage
4, 7 Defrost operation end stage, 255 No defrost operation
@@ -5808,4 +5810,4 @@ NASA_EHSSENTINEL_HEAT_OUTPUT:
state_class: measurement
signed: 'true'
type: VAR
unit: "kW"
unit: "W"

View File

@@ -5,7 +5,7 @@ import traceback
from MessageProcessor import MessageProcessor
from EHSArguments import EHSArguments
from EHSConfig import EHSConfig
from EHSExceptions import MessageWarningException
from EHSExceptions import MessageWarningException, MessageCapacityStructureWarning
from MQTTClient import MQTTClient
import aiofiles
import json
@@ -16,7 +16,7 @@ import binascii
from CustomLogger import logger, setSilent
from NASAPacket import NASAPacket
version = "0.0.1Beta"
version = "0.1.0 Stable"
async def main():
"""
@@ -70,7 +70,11 @@ async def main():
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())
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)
else:
# we are not in dryrun mode, so we need to read from Serial Pimort
@@ -99,30 +103,17 @@ async def process_buffer(buffer, args):
- 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)
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))
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_read(config, args):
"""
@@ -160,7 +151,7 @@ async def serial_read(config, args):
)
# start the async buffer process
asyncio.create_task(process_buffer(buffer, args))# 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
@@ -170,7 +161,8 @@ async def serial_read(config, args):
while True:
data = await reader.readuntil(b'\x34') # Read up to end of next message 0x34
if data:
buffer.extend(data)
asyncio.create_task(process_buffer(data, args))
#buffer.extend(data)
logger.debug(f"Received: {[hex(x) for x in data]}")
async def serial_write(writer, reader):
@@ -246,13 +238,23 @@ async def process_packet(buffer, args):
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]}")
except MessageCapacityStructureWarning 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]}")
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]}")
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}")