2023-05-30 13:42:36 +03:00
#!/usr/bin/env python3
import subprocess
import re
import os
import json
2023-05-30 14:20:12 +03:00
import argparse
2023-05-30 13:42:36 +03:00
2023-05-30 17:42:05 +03:00
def restart_service ( skip_restart = False , history = False ) :
if not skip_restart and not history :
2023-05-30 14:20:12 +03:00
subprocess . Popen ( [ " systemctl " , " restart " , " wb-mqtt-serial " ] , stdout = subprocess . PIPE )
2023-05-30 13:42:36 +03:00
def parse_config_file ( filename ) :
with open ( filename , " r " ) as file :
config_data = json . load ( file )
device_to_port = { }
2023-05-30 14:07:49 +03:00
device_stats = { }
2023-05-30 13:42:36 +03:00
for port in config_data [ " ports " ] :
for device in port [ " devices " ] :
device_to_port [ device [ " slave_id " ] ] = port [ " path " ]
2023-07-27 18:55:05 +03:00
device_stats [ device [ " slave_id " ] ] = { " type " : device . get ( " device_type " , " Unknown type " ) , " errors " : 0 , " disconnects " : 0 , " write_failures " : 0 , " invalid_crc_errors " : 0 } # New line
2023-05-30 13:42:36 +03:00
2023-05-30 14:07:49 +03:00
return device_to_port , device_stats
2023-05-30 13:42:36 +03:00
2023-07-27 18:55:05 +03:00
2023-05-30 17:42:05 +03:00
def parse_journal ( device_to_port , device_stats , skip_lines = 10 , history = False ) :
2023-05-30 14:20:12 +03:00
cmd = [ " journalctl " , " -f " , " -u " , " wb-mqtt-serial " ] if not history else [ " journalctl " , " -u " , " wb-mqtt-serial " ]
p = subprocess . Popen ( cmd , stdout = subprocess . PIPE )
2023-05-30 13:42:36 +03:00
last_log_line = None
2023-05-30 17:42:05 +03:00
line_counter = 0
2023-05-30 13:42:36 +03:00
for line in iter ( p . stdout . readline , b ' ' ) :
2023-05-30 17:42:05 +03:00
line_counter + = 1
if line_counter < = skip_lines :
continue
2023-05-30 13:42:36 +03:00
line = line . decode ( ' utf-8 ' ) # convert bytes to string
2023-05-30 14:07:49 +03:00
match_error = re . search ( r ' modbus:( \ d+): Serial protocol error: request timed out ' , line )
match_disconnect = re . search ( r ' INFO: \ [serial device \ ] device modbus:( \ d+) is disconnected ' , line )
match_write_failure = re . search ( r ' WARNING: \ [modbus \ ] failed to write: <modbus:( \ d+): ' , line )
2023-07-27 18:55:05 +03:00
match_invalid_crc = re . search ( r ' modbus:( \ d+): Serial protocol error: malformed response: invalid crc ' , line ) # New line
2023-05-30 14:07:49 +03:00
if match_error :
device_number = match_error . group ( 1 )
device_stats [ device_number ] [ " errors " ] + = 1
elif match_disconnect :
device_number = match_disconnect . group ( 1 )
device_stats [ device_number ] [ " disconnects " ] + = 1
elif match_write_failure :
device_number = match_write_failure . group ( 1 )
device_stats [ device_number ] [ " write_failures " ] + = 1
2023-07-27 18:55:05 +03:00
elif match_invalid_crc : # New line
device_number = match_invalid_crc . group ( 1 )
device_stats [ device_number ] [ " invalid_crc_errors " ] + = 1 # New line
2023-05-30 14:07:49 +03:00
2023-05-30 13:42:36 +03:00
last_log_line = line
2023-05-30 14:07:49 +03:00
os . system ( ' clear ' )
2023-05-30 13:42:36 +03:00
print ( f " Last log line: { last_log_line } " )
2023-05-30 14:07:49 +03:00
print_error_statistics ( device_stats , device_to_port )
2023-05-30 13:42:36 +03:00
2023-07-27 18:55:05 +03:00
2023-05-30 17:42:05 +03:00
def print_table ( headers , data ) :
col_widths = [
max ( len ( str ( x ) ) for x in col )
for col in zip ( * data , headers )
]
row_format = " | " + " | " . join ( " { :< " + str ( width ) + " } " for width in col_widths ) + " | "
print ( " +- " + " -+- " . join ( " - " * width for width in col_widths ) + " -+ " )
print ( row_format . format ( * headers ) )
print ( " +- " + " -+- " . join ( " - " * width for width in col_widths ) + " -+ " )
for row in data :
print ( row_format . format ( * row ) )
print ( " +- " + " -+- " . join ( " - " * width for width in col_widths ) + " -+ " )
2023-05-30 14:07:49 +03:00
def print_error_statistics ( device_stats , device_to_port ) :
2023-07-27 18:55:05 +03:00
print ( " \n --- Error Statistics --- " )
sorted_device_stats = sorted ( device_stats . items ( ) , key = lambda x : ( x [ 1 ] [ " errors " ] , x [ 1 ] [ " disconnects " ] , x [ 1 ] [ " write_failures " ] , x [ 1 ] [ " invalid_crc_errors " ] ) , reverse = True )
headers = [ " Type " , " Port " , " ID " , " Timeouts " , " Disconnects " , " Write Failures " , " CRC Errors " ]
2023-05-30 17:42:05 +03:00
data = [ ]
2023-05-30 14:07:49 +03:00
for device , stats in sorted_device_stats :
2023-05-30 17:42:05 +03:00
device_port = device_to_port . get ( device , " Unknown port " ) . replace ( " /dev/tty " , " " )
type_field = stats [ ' type ' ]
error_field = str ( stats [ ' errors ' ] )
disconnect_field = str ( stats [ ' disconnects ' ] )
write_failure_field = str ( stats [ ' write_failures ' ] )
2023-07-27 18:55:05 +03:00
invalid_crc_errors_field = str ( stats [ ' invalid_crc_errors ' ] )
data . append ( [ type_field , device_port , device , error_field , disconnect_field , write_failure_field , invalid_crc_errors_field ] )
2023-05-30 17:42:05 +03:00
print_table ( headers , data )
2023-05-30 14:07:49 +03:00
2023-07-27 18:55:05 +03:00
2023-05-30 14:20:12 +03:00
def parse_args ( ) :
parser = argparse . ArgumentParser ( )
parser . add_argument ( " -H " , " --history " , help = " parse historical data from journal " , action = " store_true " )
2023-05-30 17:42:05 +03:00
parser . add_argument ( " -S " , " --skip-service-restart " , help = " skip service restart " , action = " store_true " )
2023-05-30 14:20:12 +03:00
args = parser . parse_args ( )
return args
2023-05-30 13:42:36 +03:00
if __name__ == " __main__ " :
2023-05-30 14:20:12 +03:00
args = parse_args ( )
2023-05-30 17:42:05 +03:00
restart_service ( skip_restart = args . skip_service_restart , history = args . history )
2023-05-30 14:07:49 +03:00
device_to_port , device_stats = parse_config_file ( " /mnt/data/etc/wb-mqtt-serial.conf " )
2023-05-30 14:20:12 +03:00
parse_journal ( device_to_port , device_stats , history = args . history )