Feature/v0.2.0 (#7)

* heateroutput limit 0 - 15000 w

* heatoutput always positive

* ENUM_IN_FSV_2041 value 0c00 unknwon added

* recalculate crc6 checksum and check it

* skip exception to reduce logging

* NASAPacket and NASAMessage prepared for write mode
MQTT Auto Discovery changed single entitity to all entities
NASA Packet crc16 Checksum verificated

* - Changed MQTT Auto Discovery Config Message from single Entitiy to all Entities at ones, known devices are fully configured, not known empty (markt to delete)
- NASAPacket and NASAMessage are now bidirectional, can decode and encode Packets
- Added crc16 Checksum check for any Packet to reduce incorrect value changes
- Folling warnings moved to SkipInvalidPacketException and from warning to debug log level to reduce Logentries
  - 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.

* NASA_OUTDOOR_HP as kw unit

* NASA Repository, measurements enums completed

* filter wifikit heartbeat

* process only packets from indoor or outdoor

* correct readme

* remove expire

* device discovery status

* new mqtt hass configuration approach

* added new measurements

* added new logging features from config


* NASA_EHSSENTINEL_TOTAL_COP added

* removed silentMode, added logging proccessedMessage

* loaded devices

* loaded devices counter fix

* only if retain true

* final 0.2.0 commit
This commit is contained in:
echoDaveD
2025-02-22 22:45:18 +01:00
committed by GitHub
parent cce625dabb
commit 48ef003f22
13 changed files with 1069 additions and 386 deletions

View File

@@ -5,7 +5,7 @@ import traceback
from MessageProcessor import MessageProcessor
from EHSArguments import EHSArguments
from EHSConfig import EHSConfig
from EHSExceptions import MessageWarningException, MessageCapacityStructureWarning
from EHSExceptions import MessageWarningException, SkipInvalidPacketException
from MQTTClient import MQTTClient
import aiofiles
import json
@@ -13,10 +13,11 @@ import struct
import binascii
# 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.1.0 Stable"
version = "0.2.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,10 +61,6 @@ 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}")
@@ -75,12 +71,12 @@ async def main():
except:
line = line.strip().replace("'", "").replace("[", "").replace("]", "").split(", ") # for ['0x1', '0x2' ..]
line = [int(value, 16) for value in line]
await process_packet(line, args)
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):
async def process_buffer(buffer, args, config):
"""
Processes a buffer of data asynchronously, identifying and handling packets based on specific criteria.
Args:
@@ -108,14 +104,14 @@ async def process_buffer(buffer, args):
for i in range(0, len(buffer)):
if buffer[i] == 0x32:
if (len(buffer[i:]) > 14):
asyncio.create_task(process_packet(buffer[i:], args))
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_read(config, args):
async def serial_connection(config, args):
"""
Asynchronously reads data from a serial connection and processes it.
Args:
@@ -147,25 +143,28 @@ async def serial_read(config, args):
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
rtscts=True,
timeout=0
timeout=1
)
# 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))
await asyncio.gather(
serial_read(reader, args, config),
#serial_write(writer, reader, args),
)
# Read loop
async def serial_read(reader, args, config):
while True:
data = await reader.readuntil(b'\x34') # Read up to end of next message 0x34
if data:
asyncio.create_task(process_buffer(data, args))
asyncio.create_task(process_buffer(data, args, config))
#buffer.extend(data)
logger.debug(f"Received: {data}")
logger.debug(f"Received: {data!r}")
logger.debug(f"Received: {[hex(x) for x in data]}")
async def serial_write(writer, reader):
await asyncio.sleep(0.1) # Yield control to other tasks
async def serial_write(writer:asyncio.StreamWriter, reader: asyncio.StreamReader, args):
"""
TODO Not used yet, only for future use...
@@ -183,36 +182,85 @@ async def serial_write(writer, reader):
await asyncio.sleep(5)
# Example data to write
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)
])
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']
decoded_nasa = NASAPacket()
decoded_nasa.set_packet_source_address_class(AddressClassEnum.WiFiKit)
decoded_nasa.set_packet_source_channel(0)
decoded_nasa.set_packet_source_address(144)
decoded_nasa.set_packet_dest_address_class(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(PacketType.Normal)
decoded_nasa.set_packet_data_type(DataType.Read)
decoded_nasa.set_packet_number(3)
lst = []
tmp_msg = 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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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)
final_packet = decoded_nasa.to_raw()
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
logger.info(f"Sent data raw: {final_packet}")
logger.info(f"Sent data raw: {decoded_nasa}")
logger.info(f"Sent data raw: {[hex(x) for x in final_packet]}")
logger.info(f"Sent data raw: {[x for x in final_packet]}")
async def process_packet(buffer, args):
async def process_packet(buffer, args, config):
"""
Asynchronously processes a packet buffer.
If `dumpWriter` is `None`, it attempts to process the packet using `MessageProcessor`.
@@ -233,13 +281,29 @@ 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()
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]}")
except MessageCapacityStructureWarning as e:
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]}")