Add debugger
This commit is contained in:
+20
-10
@@ -4,7 +4,8 @@
|
|||||||
import atexit
|
import atexit
|
||||||
import argparse
|
import argparse
|
||||||
import pprint
|
import pprint
|
||||||
import time
|
|
||||||
|
from bmspy.utilities import debugger
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@@ -73,28 +74,37 @@ def main():
|
|||||||
|
|
||||||
elif args.report_textfile:
|
elif args.report_textfile:
|
||||||
from bmspy import prometheus
|
from bmspy import prometheus
|
||||||
prometheus.prometheus_export(daemonize=False, filename=args.report_textfile, debug=debug)
|
prometheus.prometheus_export(
|
||||||
|
daemonize=False, filename=args.report_textfile, debug=debug
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
elif args.report_print:
|
||||||
from bmspy import client
|
from bmspy import client
|
||||||
client.handle_registration(args.socket, 'bmspy', debug)
|
|
||||||
atexit.register(client.handle_registration, args.socket, 'bmspy', debug)
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
|
||||||
|
client.handle_registration(args.socket, "bmspy", debug)
|
||||||
|
atexit.register(client.handle_registration, args.socket, "bmspy", debug)
|
||||||
|
|
||||||
# {ups_name: JBDUPS}
|
# {ups_name: JBDUPS}
|
||||||
data = client.read_data(args.socket, 'bmspy', ups=args.ups, debug=debug)
|
data = client.read_data(args.socket, 'bmspy', ups=args.ups, debug=debug)
|
||||||
|
|
||||||
if args.report_json:
|
if args.report_json:
|
||||||
import json
|
import json
|
||||||
print(json.dumps({name: dict(ups.items()) for name, ups in data.items()}, default=str))
|
|
||||||
|
|
||||||
elif args.report_print:
|
pp.pprint(
|
||||||
pp = pprint.PrettyPrinter(indent=4)
|
json.dumps(
|
||||||
|
{name: dict(ups.items()) for name, ups in data.items()},
|
||||||
|
default=str,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
for ups_name, ups_data in data.items():
|
for ups_name, ups_data in data.items():
|
||||||
print("=== {} ===".format(ups_name))
|
print("=== {} ===".format(ups_name))
|
||||||
pp.pprint(ups_data)
|
pp.pprint(dict(ups_data.items()))
|
||||||
|
|
||||||
except KeyboardInterrupt as e:
|
except KeyboardInterrupt as e:
|
||||||
print(e)
|
debugger(e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+14
-13
@@ -5,6 +5,7 @@ import sys
|
|||||||
import struct
|
import struct
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
|
from bmspy.utilities import debugger
|
||||||
|
|
||||||
|
|
||||||
is_registered = False
|
is_registered = False
|
||||||
@@ -33,9 +34,9 @@ def handle_registration(socket_path, client_name, debug=0):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if is_registered:
|
if is_registered:
|
||||||
print("{}: failed to register with daemon: {}".format(client_name, e))
|
debugger("{}: failed to register with daemon: {}".format(client_name, e))
|
||||||
else:
|
else:
|
||||||
print("{}: failed to deregister with daemon: {}".format(client_name, e))
|
debugger("{}: failed to deregister with daemon: {}".format(client_name, e))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -48,34 +49,34 @@ def socket_comms(socket_path, request_data, debug=0):
|
|||||||
|
|
||||||
# Connect the socket to the port where the server is listening
|
# Connect the socket to the port where the server is listening
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket client: connecting to {}".format(socket_path))
|
debugger("socket client: connecting to {}".format(socket_path))
|
||||||
try:
|
try:
|
||||||
sock.connect(socket_path)
|
sock.connect(socket_path)
|
||||||
except socket.error as msg:
|
except socket.error as msg:
|
||||||
if msg.errno == 2:
|
if msg.errno == 2:
|
||||||
print("Failed to connect to bmspy daemon")
|
debugger("Failed to connect to bmspy daemon")
|
||||||
else:
|
else:
|
||||||
print("socket client: {}".format(msg))
|
debugger("socket client: {}".format(msg))
|
||||||
|
|
||||||
# Send request
|
# Send request
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket client: sending {!r}".format(request_data))
|
debugger("socket client: sending {!r}".format(request_data))
|
||||||
request = bytes()
|
request = bytes()
|
||||||
try:
|
try:
|
||||||
request = json.dumps(request_data).encode()
|
request = json.dumps(request_data).encode()
|
||||||
# add length to the start of the json string, so we know how much to read on the other end
|
# add length to the start of the json string, so we know how much to read on the other end
|
||||||
length = struct.pack("!I", len(request))
|
length = struct.pack("!I", len(request))
|
||||||
if debug > 3:
|
if debug > 3:
|
||||||
print(
|
debugger(
|
||||||
"socket client: outgoing request length: {}, encoded as {}".format(
|
"socket client: outgoing request length: {}, encoded as {}".format(
|
||||||
len(request), length
|
len(request), length
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
request = length + request
|
request = length + request
|
||||||
if debug > 4:
|
if debug > 4:
|
||||||
print("socket client: outgoing request: {}".format(request))
|
debugger("socket client: outgoing request: {}".format(request))
|
||||||
except Exception:
|
except Exception:
|
||||||
print("socket client ERROR: unable to encode request")
|
debugger("socket client ERROR: unable to encode request")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
sock.sendall(request)
|
sock.sendall(request)
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ def socket_comms(socket_path, request_data, debug=0):
|
|||||||
try:
|
try:
|
||||||
length = struct.unpack("!I", response)[0]
|
length = struct.unpack("!I", response)[0]
|
||||||
if debug > 4:
|
if debug > 4:
|
||||||
print(
|
debugger(
|
||||||
"socket client: incoming length: {}, encoded as {}".format(
|
"socket client: incoming length: {}, encoded as {}".format(
|
||||||
length, response
|
length, response
|
||||||
)
|
)
|
||||||
@@ -92,13 +93,13 @@ def socket_comms(socket_path, request_data, debug=0):
|
|||||||
# read length bytes
|
# read length bytes
|
||||||
response = sock.recv(length)
|
response = sock.recv(length)
|
||||||
if debug > 3:
|
if debug > 3:
|
||||||
print("socket client: incoming response: {}".format(response))
|
debugger("socket client: incoming response: {}".format(response))
|
||||||
response_data = json.loads(response)
|
response_data = json.loads(response)
|
||||||
except Exception:
|
except Exception:
|
||||||
print("socket client ERROR: unable to decode response")
|
debugger("socket client ERROR: unable to decode response")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket client: received {!r}".format(response_data))
|
debugger("socket client: received {!r}".format(response_data))
|
||||||
|
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,7 @@
|
|||||||
import atexit, datetime, os, sys, time
|
import atexit, datetime, os, sys, time
|
||||||
from influxdb_client_3 import InfluxDBClient3, Point
|
from influxdb_client_3 import InfluxDBClient3, Point
|
||||||
from bmspy import client
|
from bmspy import client
|
||||||
|
from bmspy.utilities import debugger
|
||||||
|
|
||||||
DAEMON_UPDATE_PERIOD = 30
|
DAEMON_UPDATE_PERIOD = 30
|
||||||
|
|
||||||
@@ -34,14 +35,14 @@ def influxdb_export(bucket, url=None, org=None, token=None, socket_path=None, up
|
|||||||
|
|
||||||
def influxdb_write_snapshot(influxclient, bucket, ups_data, debug=0):
|
def influxdb_write_snapshot(influxclient, bucket, ups_data, debug=0):
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print("influxdb: creating snapshot")
|
debugger("influxdb: creating snapshot")
|
||||||
points = influxdb_create_snapshot(ups_data, debug)
|
points = influxdb_create_snapshot(ups_data, debug)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print("influxdb: writing snapshot")
|
debugger("influxdb: writing snapshot")
|
||||||
try:
|
try:
|
||||||
influxclient.write(record=points, database=bucket)
|
influxclient.write(record=points, database=bucket)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
debugger(e)
|
||||||
|
|
||||||
|
|
||||||
def influxdb_create_snapshot(ups_data, debug=0):
|
def influxdb_create_snapshot(ups_data, debug=0):
|
||||||
@@ -57,7 +58,7 @@ def influxdb_create_snapshot(ups_data, debug=0):
|
|||||||
if contains.get('raw_value') is not None:
|
if contains.get('raw_value') is not None:
|
||||||
value = contains.get('raw_value')
|
value = contains.get('raw_value')
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("value: {} [{}] : {}".format(kind, ups_name, value))
|
debugger("value: {} [{}] : {}".format(kind, ups_name, value))
|
||||||
points.append(
|
points.append(
|
||||||
Point(kind)
|
Point(kind)
|
||||||
.tag("ups", ups_name)
|
.tag("ups", ups_name)
|
||||||
@@ -71,7 +72,7 @@ def influxdb_create_snapshot(ups_data, debug=0):
|
|||||||
label = contains.get('label')
|
label = contains.get('label')
|
||||||
for idx, label_value in contains.get('raw_values').items():
|
for idx, label_value in contains.get('raw_values').items():
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("labels: {} [{}][{}] : {}".format(kind, ups_name, idx, label_value))
|
debugger("labels: {} [{}][{}] : {}".format(kind, ups_name, idx, label_value))
|
||||||
points.append(
|
points.append(
|
||||||
Point(kind)
|
Point(kind)
|
||||||
.tag("ups", ups_name)
|
.tag("ups", ups_name)
|
||||||
@@ -85,7 +86,7 @@ def influxdb_create_snapshot(ups_data, debug=0):
|
|||||||
if contains.get('info') is not None:
|
if contains.get('info') is not None:
|
||||||
value = contains.get('info')
|
value = contains.get('info')
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("info: {} [{}] : {}".format(kind, ups_name, value))
|
debugger("info: {} [{}] : {}".format(kind, ups_name, value))
|
||||||
points.append(
|
points.append(
|
||||||
Point(kind)
|
Point(kind)
|
||||||
.tag("ups", ups_name)
|
.tag("ups", ups_name)
|
||||||
@@ -131,10 +132,10 @@ def main():
|
|||||||
if not os.getenv('INFLUXDB_V2_TOKEN') and not args.influx_token:
|
if not os.getenv('INFLUXDB_V2_TOKEN') and not args.influx_token:
|
||||||
raise argparse.ArgumentTypeError('Missing value for --token')
|
raise argparse.ArgumentTypeError('Missing value for --token')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("bmspy-influxdb: {}".format(e))
|
debugger("bmspy-influxdb: {}".format(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("Running BMS influxdb daemon on socket {}".format(args.socket))
|
debugger("Running BMS influxdb daemon on socket {}".format(args.socket))
|
||||||
|
|
||||||
client.handle_registration(args.socket, 'influxdb', debug)
|
client.handle_registration(args.socket, 'influxdb', debug)
|
||||||
atexit.register(client.handle_registration, args.socket, 'influxdb', debug)
|
atexit.register(client.handle_registration, args.socket, 'influxdb', debug)
|
||||||
|
|||||||
+40
-39
@@ -8,6 +8,7 @@ import serial.rs485
|
|||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, fields as dataclass_fields
|
from dataclasses import dataclass, fields as dataclass_fields
|
||||||
|
|
||||||
|
from bmspy.utilities import debugger
|
||||||
from bmspy.classes import BMSScalarField, BMSMultiField, BMSInfoField
|
from bmspy.classes import BMSScalarField, BMSMultiField, BMSInfoField
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ class JBDUPS:
|
|||||||
|
|
||||||
def serial_cleanup(ser, debug=0):
|
def serial_cleanup(ser, debug=0):
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("serial: cleaning up...")
|
debugger("serial: cleaning up...")
|
||||||
if ser.is_open:
|
if ser.is_open:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
ser.reset_output_buffer()
|
ser.reset_output_buffer()
|
||||||
@@ -114,14 +115,14 @@ def bytes_to_date(high, low):
|
|||||||
|
|
||||||
def requestMessage(ser, reqmsg, debug=0):
|
def requestMessage(ser, reqmsg, debug=0):
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("serial: starting up monitor")
|
debugger("serial: starting up monitor")
|
||||||
if ser.is_open:
|
if ser.is_open:
|
||||||
ser.close()
|
ser.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ser.open()
|
ser.open()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("serial: error open port: {0}".format(str(e)))
|
debugger("serial: error open port: {0}".format(str(e)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if ser.is_open:
|
if ser.is_open:
|
||||||
@@ -132,16 +133,16 @@ def requestMessage(ser, reqmsg, debug=0):
|
|||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
ser.reset_output_buffer()
|
ser.reset_output_buffer()
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print(
|
debugger(
|
||||||
"serial: write data: {0}".format(
|
"serial: write data: {0}".format(
|
||||||
"".join("0x{:02x} ".format(x) for x in reqmsg)
|
"".join("0x{:02x} ".format(x) for x in reqmsg)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
w = ser.write(reqmsg)
|
w = ser.write(reqmsg)
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("serial: bytes written: {0}".format(w))
|
debugger("serial: bytes written: {0}".format(w))
|
||||||
if w != len(reqmsg):
|
if w != len(reqmsg):
|
||||||
print(
|
debugger(
|
||||||
"serial ERROR: {0} bytes written, {1} expected.".format(
|
"serial ERROR: {0} bytes written, {1} expected.".format(
|
||||||
w, len(reqmsg)
|
w, len(reqmsg)
|
||||||
)
|
)
|
||||||
@@ -153,22 +154,22 @@ def requestMessage(ser, reqmsg, debug=0):
|
|||||||
serial_cleanup(ser, debug)
|
serial_cleanup(ser, debug)
|
||||||
return ""
|
return ""
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("serial: waiting for data...")
|
debugger("serial: waiting for data...")
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
wait_time += 1
|
wait_time += 1
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print("serial: waiting reading: {0}".format(ser.in_waiting))
|
debugger("serial: waiting reading: {0}".format(ser.in_waiting))
|
||||||
response = ser.read_until(b"\x77")
|
response = ser.read_until(b"\x77")
|
||||||
if len(response) == 0:
|
if len(response) == 0:
|
||||||
return ""
|
return ""
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print("serial: read data: {0}".format(response.hex()))
|
debugger("serial: read data: {0}".format(response.hex()))
|
||||||
serial_cleanup(ser, debug)
|
serial_cleanup(ser, debug)
|
||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("serial: error communicating: {0}".format(str(e)))
|
debugger("serial: error communicating: {0}".format(str(e)))
|
||||||
else:
|
else:
|
||||||
print("serial: cannot open port")
|
debugger("serial: cannot open port")
|
||||||
|
|
||||||
|
|
||||||
def parse_03_response(response, debug=0):
|
def parse_03_response(response, debug=0):
|
||||||
@@ -183,11 +184,11 @@ def parse_03_response(response, debug=0):
|
|||||||
# length+5 checksum
|
# length+5 checksum
|
||||||
# length+6 end \x77
|
# length+6 end \x77
|
||||||
if bytes([response[0]]) != b"\xdd":
|
if bytes([response[0]]) != b"\xdd":
|
||||||
print("parse_03_response ERROR: first byte not found: {0}".format(response[0]))
|
debugger("parse_03_response ERROR: first byte not found: {0}".format(response[0]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if bytes([response[2]]) == b"\x80":
|
if bytes([response[2]]) == b"\x80":
|
||||||
print(
|
debugger(
|
||||||
"parse_03_response ERROR: error byte returned from BMS: {0}".format(
|
"parse_03_response ERROR: error byte returned from BMS: {0}".format(
|
||||||
response[2]
|
response[2]
|
||||||
)
|
)
|
||||||
@@ -196,7 +197,7 @@ def parse_03_response(response, debug=0):
|
|||||||
|
|
||||||
data_len = response[3]
|
data_len = response[3]
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("parse_03_response: data length (trimming 4 bytes): {0}".format(data_len))
|
debugger("parse_03_response: data length (trimming 4 bytes): {0}".format(data_len))
|
||||||
|
|
||||||
# The checksum is two bytes, offset by data_len + 4
|
# The checksum is two bytes, offset by data_len + 4
|
||||||
# Five bytes at the front of data: begin; rw; status, command; length
|
# Five bytes at the front of data: begin; rw; status, command; length
|
||||||
@@ -204,11 +205,11 @@ def parse_03_response(response, debug=0):
|
|||||||
first = data_len + 4
|
first = data_len + 4
|
||||||
second = data_len + 5
|
second = data_len + 5
|
||||||
if second > len(response):
|
if second > len(response):
|
||||||
print("parse_03_response ERROR: primary response checksum not found")
|
debugger("parse_03_response ERROR: primary response checksum not found")
|
||||||
return False
|
return False
|
||||||
checksum = bytes([response[first], response[second]])
|
checksum = bytes([response[first], response[second]])
|
||||||
if not verify_checksum(response[3:first], checksum):
|
if not verify_checksum(response[3:first], checksum):
|
||||||
print("parse_03_response ERROR: failed to validate received checksum")
|
debugger("parse_03_response ERROR: failed to validate received checksum")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if data_len == 0:
|
if data_len == 0:
|
||||||
@@ -221,14 +222,14 @@ def parse_03_response(response, debug=0):
|
|||||||
help="Total Voltage", raw_value=vtot, value="{:.2f}".format(vtot), units="V"
|
help="Total Voltage", raw_value=vtot, value="{:.2f}".format(vtot), units="V"
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Total voltage: {:.2f}V".format(vtot))
|
debugger(" Total voltage: {:.2f}V".format(vtot))
|
||||||
|
|
||||||
current = convert_to_signed(bytes_to_digits(response[6], response[7])) * 0.01
|
current = convert_to_signed(bytes_to_digits(response[6], response[7])) * 0.01
|
||||||
result.bms_current_amps = BMSScalarField(
|
result.bms_current_amps = BMSScalarField(
|
||||||
help="Current", raw_value=current, value="{:.2f}".format(current), units="A"
|
help="Current", raw_value=current, value="{:.2f}".format(current), units="A"
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Current: {:.2f}A".format(current))
|
debugger(" Current: {:.2f}A".format(current))
|
||||||
|
|
||||||
res_cap = bytes_to_digits(response[8], response[9]) * 0.01
|
res_cap = bytes_to_digits(response[8], response[9]) * 0.01
|
||||||
nom_cap = bytes_to_digits(response[10], response[11]) * 0.01
|
nom_cap = bytes_to_digits(response[10], response[11]) * 0.01
|
||||||
@@ -245,29 +246,29 @@ def parse_03_response(response, debug=0):
|
|||||||
units="Ah",
|
units="Ah",
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Remaining capacity: {:.2f}Ah".format(res_cap))
|
debugger(" Remaining capacity: {:.2f}Ah".format(res_cap))
|
||||||
print(" Nominal capacity: {:.2f}Ah".format(nom_cap))
|
debugger(" Nominal capacity: {:.2f}Ah".format(nom_cap))
|
||||||
|
|
||||||
cycle_times = bytes_to_digits(response[12], response[13])
|
cycle_times = bytes_to_digits(response[12], response[13])
|
||||||
result.bms_charge_cycles = BMSScalarField(
|
result.bms_charge_cycles = BMSScalarField(
|
||||||
help="Charge Cycles", raw_value=cycle_times, value="{0}".format(cycle_times)
|
help="Charge Cycles", raw_value=cycle_times, value="{0}".format(cycle_times)
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Cycle times: {0}".format(cycle_times))
|
debugger(" Cycle times: {0}".format(cycle_times))
|
||||||
|
|
||||||
man_date = bytes_to_date(response[14], response[15])
|
man_date = bytes_to_date(response[14], response[15])
|
||||||
result.bms_manufacture_date = BMSInfoField(
|
result.bms_manufacture_date = BMSInfoField(
|
||||||
help="Date of Manufacture", info="{0}".format(man_date)
|
help="Date of Manufacture", info="{0}".format(man_date)
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Manufacturing date: {0}".format(man_date))
|
debugger(" Manufacturing date: {0}".format(man_date))
|
||||||
|
|
||||||
cells = response[25]
|
cells = response[25]
|
||||||
result.bms_cell_number = BMSScalarField(
|
result.bms_cell_number = BMSScalarField(
|
||||||
help="Cells", raw_value=cells, value="{0}".format(cells)
|
help="Cells", raw_value=cells, value="{0}".format(cells)
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Cells: {0}S".format(cells))
|
debugger(" Cells: {0}S".format(cells))
|
||||||
|
|
||||||
balance_state_high = bytes_to_digits(response[16], response[17]) # 1S to 16S
|
balance_state_high = bytes_to_digits(response[16], response[17]) # 1S to 16S
|
||||||
balance_state_low = bytes_to_digits(response[18], response[19]) # 17S to 32S
|
balance_state_low = bytes_to_digits(response[18], response[19]) # 17S to 32S
|
||||||
@@ -306,7 +307,7 @@ def parse_03_response(response, debug=0):
|
|||||||
raw_balancing[cell + 1] = balancing
|
raw_balancing[cell + 1] = balancing
|
||||||
str_balancing[cell + 1] = "{0}".format(int(balancing))
|
str_balancing[cell + 1] = "{0}".format(int(balancing))
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Balancing cell {0}: {1}".format(cell, balancing))
|
debugger(" Balancing cell {0}: {1}".format(cell, balancing))
|
||||||
result.bms_cells_balancing = BMSMultiField(
|
result.bms_cells_balancing = BMSMultiField(
|
||||||
help="Cells balancing",
|
help="Cells balancing",
|
||||||
label="cell",
|
label="cell",
|
||||||
@@ -355,7 +356,7 @@ def parse_03_response(response, debug=0):
|
|||||||
result.bms_protection_slmos_bool = _prot("Software lock MOS", slm)
|
result.bms_protection_slmos_bool = _prot("Software lock MOS", slm)
|
||||||
|
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print(" Protection state: {0}".format(protection_state))
|
debugger(" Protection state: {0}".format(protection_state))
|
||||||
for attr in (
|
for attr in (
|
||||||
"sop",
|
"sop",
|
||||||
"sup",
|
"sup",
|
||||||
@@ -372,14 +373,14 @@ def parse_03_response(response, debug=0):
|
|||||||
"slm",
|
"slm",
|
||||||
):
|
):
|
||||||
val = locals()[attr]
|
val = locals()[attr]
|
||||||
print(" {}: {}".format(attr, bool(val)))
|
debugger(" {}: {}".format(attr, bool(val)))
|
||||||
|
|
||||||
rsoc = response[23] * 0.01
|
rsoc = response[23] * 0.01
|
||||||
result.bms_capacity_charge_ratio = BMSScalarField(
|
result.bms_capacity_charge_ratio = BMSScalarField(
|
||||||
help="Percent Charge", raw_value=rsoc, value="{0}".format(rsoc), units="‰"
|
help="Percent Charge", raw_value=rsoc, value="{0}".format(rsoc), units="‰"
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Capacity remaining: {0}%".format(rsoc * 100))
|
debugger(" Capacity remaining: {0}%".format(rsoc * 100))
|
||||||
|
|
||||||
# bit0 = charging; bit1 = discharging; 0 = MOS closing; 1 = MOS opening
|
# bit0 = charging; bit1 = discharging; 0 = MOS closing; 1 = MOS opening
|
||||||
control_status = response[24]
|
control_status = response[24]
|
||||||
@@ -394,8 +395,8 @@ def parse_03_response(response, debug=0):
|
|||||||
value="{0}".format(int(bool((control_status >> 1) & 1))),
|
value="{0}".format(int(bool((control_status >> 1) & 1))),
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" MOSFET charging: {0}".format("yes" if (control_status & 1) else "no"))
|
debugger(" MOSFET charging: {0}".format("yes" if (control_status & 1) else "no"))
|
||||||
print(
|
debugger(
|
||||||
" MOSFET discharging: {0}".format(
|
" MOSFET discharging: {0}".format(
|
||||||
"yes" if ((control_status >> 1) & 1) else "no"
|
"yes" if ((control_status >> 1) & 1) else "no"
|
||||||
)
|
)
|
||||||
@@ -417,9 +418,9 @@ def parse_03_response(response, debug=0):
|
|||||||
units="°C",
|
units="°C",
|
||||||
)
|
)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Number of temperature sensors: {0}".format(ntc_num))
|
debugger(" Number of temperature sensors: {0}".format(ntc_num))
|
||||||
for i, temp in enumerate(temperatures):
|
for i, temp in enumerate(temperatures):
|
||||||
print(" Temperature sensor {:d}: {:.2f}°C".format(i + 1, temp))
|
debugger(" Temperature sensor {:d}: {:.2f}°C".format(i + 1, temp))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -436,11 +437,11 @@ def parse_04_response(response, debug=0):
|
|||||||
# length+5 checksum
|
# length+5 checksum
|
||||||
# length+6 end \x77
|
# length+6 end \x77
|
||||||
if bytes([response[0]]) != b"\xdd":
|
if bytes([response[0]]) != b"\xdd":
|
||||||
print("parse_04_response ERROR: first byte not found: {0}".format(response[0]))
|
debugger("parse_04_response ERROR: first byte not found: {0}".format(response[0]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if bytes([response[2]]) == b"\x80":
|
if bytes([response[2]]) == b"\x80":
|
||||||
print(
|
debugger(
|
||||||
"parse_04_response ERROR: error byte returned from BMS: {0}".format(
|
"parse_04_response ERROR: error byte returned from BMS: {0}".format(
|
||||||
response[2]
|
response[2]
|
||||||
)
|
)
|
||||||
@@ -449,7 +450,7 @@ def parse_04_response(response, debug=0):
|
|||||||
|
|
||||||
data_len = response[3]
|
data_len = response[3]
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print(" Data length (trimming 4 bytes): {0}".format(data_len))
|
debugger(" Data length (trimming 4 bytes): {0}".format(data_len))
|
||||||
|
|
||||||
# The checksum is two bytes, offset by data_len + 4
|
# The checksum is two bytes, offset by data_len + 4
|
||||||
# Five bytes at the front of data: begin; rw; status, command; length
|
# Five bytes at the front of data: begin; rw; status, command; length
|
||||||
@@ -457,11 +458,11 @@ def parse_04_response(response, debug=0):
|
|||||||
first = data_len + 4
|
first = data_len + 4
|
||||||
second = data_len + 5
|
second = data_len + 5
|
||||||
if second > len(response):
|
if second > len(response):
|
||||||
print("parse_04_response ERROR: cell voltage checksum not found")
|
debugger("parse_04_response ERROR: cell voltage checksum not found")
|
||||||
return False
|
return False
|
||||||
checksum = bytes([response[first], response[second]])
|
checksum = bytes([response[first], response[second]])
|
||||||
if not verify_checksum(response[3:first], checksum):
|
if not verify_checksum(response[3:first], checksum):
|
||||||
print("parse_04_response ERROR: failed to validate received checksum")
|
debugger("parse_04_response ERROR: failed to validate received checksum")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if data_len == 0:
|
if data_len == 0:
|
||||||
@@ -476,7 +477,7 @@ def parse_04_response(response, debug=0):
|
|||||||
raw_values[cell + 1] = cellv
|
raw_values[cell + 1] = cellv
|
||||||
str_values[cell + 1] = "{:.3f}".format(cellv)
|
str_values[cell + 1] = "{:.3f}".format(cellv)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(" Cell {:.0f}: {:.3f}V".format(cell + 1, cellv))
|
debugger(" Cell {:.0f}: {:.3f}V".format(cell + 1, cellv))
|
||||||
|
|
||||||
return BMSMultiField(
|
return BMSMultiField(
|
||||||
help="Cell Voltages",
|
help="Cell Voltages",
|
||||||
@@ -498,7 +499,7 @@ def collect_data(ser, debug=0):
|
|||||||
|
|
||||||
if len(response_03) == 0:
|
if len(response_03) == 0:
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print("collect_data: Error retrieving BMS info. Trying again...")
|
debugger("collect_data: Error retrieving BMS info. Trying again...")
|
||||||
return False
|
return False
|
||||||
response_03 = bytearray(response_03)
|
response_03 = bytearray(response_03)
|
||||||
|
|
||||||
@@ -507,7 +508,7 @@ def collect_data(ser, debug=0):
|
|||||||
|
|
||||||
if len(response_04) == 0:
|
if len(response_04) == 0:
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print("collect_data: Error retrieving BMS info. Trying again...")
|
debugger("collect_data: Error retrieving BMS info. Trying again...")
|
||||||
return False
|
return False
|
||||||
response_04 = bytearray(response_04)
|
response_04 = bytearray(response_04)
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,6 @@
|
|||||||
import prometheus_client
|
import prometheus_client
|
||||||
|
from bmspy.utilities import debugger
|
||||||
|
|
||||||
|
|
||||||
def prometheus_export(daemonize=True, filename=None):
|
def prometheus_export(daemonize=True, filename=None):
|
||||||
global debug
|
global debug
|
||||||
@@ -32,7 +34,7 @@ def prometheus_export(daemonize=True, filename=None):
|
|||||||
prometheus_client.generate_latest(registry)
|
prometheus_client.generate_latest(registry)
|
||||||
else:
|
else:
|
||||||
if filename is None:
|
if filename is None:
|
||||||
print("Invalid filename supplied");
|
debugger("Invalid filename supplied");
|
||||||
return False
|
return False
|
||||||
prometheus_client.write_to_textfile(filename, registry=registry)
|
prometheus_client.write_to_textfile(filename, registry=registry)
|
||||||
return True
|
return True
|
||||||
@@ -50,7 +52,7 @@ def prometheus_create_metric(registry, data):
|
|||||||
# Has multiple values, each a different label
|
# Has multiple values, each a different label
|
||||||
elif contains.get('values') is not None:
|
elif contains.get('values') is not None:
|
||||||
if contains.get('label') is None:
|
if contains.get('label') is None:
|
||||||
print("ERROR: no label for {0} specified".format(name))
|
debugger("ERROR: no label for {0} specified".format(name))
|
||||||
label = contains.get('label')
|
label = contains.get('label')
|
||||||
metric[name] = prometheus_client.Gauge(name, helpmsg, [label], registry=registry)
|
metric[name] = prometheus_client.Gauge(name, helpmsg, [label], registry=registry)
|
||||||
elif contains.get('info') is not None:
|
elif contains.get('info') is not None:
|
||||||
@@ -78,7 +80,7 @@ def prometheus_populate_metric(metric, data):
|
|||||||
# TODO fork bms daemon if need be?
|
# TODO fork bms daemon if need be?
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("TODO. At present, run from bmspy directly.")
|
debugger("TODO. At present, run from bmspy directly.")
|
||||||
|
|
||||||
# influxdb_export(bucket=args.influx_bucket, \
|
# influxdb_export(bucket=args.influx_bucket, \
|
||||||
# url=args.influx_url, \
|
# url=args.influx_url, \
|
||||||
|
|||||||
+15
-15
@@ -12,6 +12,7 @@ import json
|
|||||||
import struct
|
import struct
|
||||||
from dataclasses import asdict as dataclass_asdict
|
from dataclasses import asdict as dataclass_asdict
|
||||||
|
|
||||||
|
from bmspy.utilities import debugger
|
||||||
from bmspy.jbd_ups import collect_data, initialise_serial
|
from bmspy.jbd_ups import collect_data, initialise_serial
|
||||||
|
|
||||||
# Expected kernel log output when the USB-serial adapter is plugged in:
|
# Expected kernel log output when the USB-serial adapter is plugged in:
|
||||||
@@ -52,7 +53,7 @@ def read_request(connection, debug=0):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception("unable to determine request length: {}".format(e))
|
raise Exception("unable to determine request length: {}".format(e))
|
||||||
if debug > 4:
|
if debug > 4:
|
||||||
print("socket: incoming length: {}, encoded as {}".format(length, request))
|
debugger("socket: incoming length: {}, encoded as {}".format(length, request))
|
||||||
|
|
||||||
# read length bytes
|
# read length bytes
|
||||||
try:
|
try:
|
||||||
@@ -60,22 +61,21 @@ def read_request(connection, debug=0):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OSError("unable to read socket: {}".format(e))
|
raise OSError("unable to read socket: {}".format(e))
|
||||||
if debug > 3:
|
if debug > 3:
|
||||||
print("socket: incoming request: {}".format(request))
|
debugger("socket: incoming request: {}".format(request))
|
||||||
try:
|
try:
|
||||||
request_data = json.loads(request)
|
request_data = json.loads(request)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception("unable to read incoming request: {}".format(e))
|
raise Exception("unable to read incoming request: {}".format(e))
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket: received {!r}".format(request_data))
|
debugger("socket: received {!r}".format(request_data))
|
||||||
|
|
||||||
return request_data
|
return request_data
|
||||||
|
|
||||||
|
|
||||||
def send_response(connection, response_data, client, debug=0):
|
def send_response(connection, response_data, client, debug=0):
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket: sending {!r}".format(response_data))
|
debugger("socket: sending {!r}".format(response_data))
|
||||||
try:
|
try:
|
||||||
response = json.dumps(response_data).encode()
|
|
||||||
response = json.dumps(
|
response = json.dumps(
|
||||||
response_data,
|
response_data,
|
||||||
default=lambda o: {k: dataclass_asdict(v) for k, v in o.items()}
|
default=lambda o: {k: dataclass_asdict(v) for k, v in o.items()}
|
||||||
@@ -164,7 +164,7 @@ def main():
|
|||||||
for device_str in device_list:
|
for device_str in device_list:
|
||||||
name, path = parse_device(device_str)
|
name, path = parse_device(device_str)
|
||||||
if name in ups_devices:
|
if name in ups_devices:
|
||||||
print("server: duplicate UPS name '{}', skipping {}".format(name, path))
|
debugger("server: duplicate UPS name '{}', skipping {}".format(name, path))
|
||||||
continue
|
continue
|
||||||
ups_devices[name] = {
|
ups_devices[name] = {
|
||||||
"ser": initialise_serial(path, debug),
|
"ser": initialise_serial(path, debug),
|
||||||
@@ -175,7 +175,7 @@ def main():
|
|||||||
print("server: registered UPS '{}' on {}".format(name, path))
|
print("server: registered UPS '{}' on {}".format(name, path))
|
||||||
|
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print("Running BMS query daemon on socket {}".format(args.socket))
|
debugger("Running BMS query daemon on socket {}".format(args.socket))
|
||||||
|
|
||||||
socket_dir = os.path.dirname(args.socket)
|
socket_dir = os.path.dirname(args.socket)
|
||||||
socket_dir_created = False
|
socket_dir_created = False
|
||||||
@@ -207,7 +207,7 @@ def main():
|
|||||||
new_umask = 0o003
|
new_umask = 0o003
|
||||||
old_umask = os.umask(new_umask)
|
old_umask = os.umask(new_umask)
|
||||||
if debug > 1:
|
if debug > 1:
|
||||||
print(
|
debugger(
|
||||||
"socket: old umask: %s, new umask: %s"
|
"socket: old umask: %s, new umask: %s"
|
||||||
% (oct(old_umask), oct(new_umask))
|
% (oct(old_umask), oct(new_umask))
|
||||||
)
|
)
|
||||||
@@ -215,17 +215,17 @@ def main():
|
|||||||
try:
|
try:
|
||||||
os.setgid(running_gid)
|
os.setgid(running_gid)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print("could not set effective group id: {}".format(e))
|
debugger("could not set effective group id: {}".format(e))
|
||||||
try:
|
try:
|
||||||
os.setuid(running_uid)
|
os.setuid(running_uid)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print("could not set effective user id: {}".format(e))
|
debugger("could not set effective user id: {}".format(e))
|
||||||
|
|
||||||
final_uid = os.getuid()
|
final_uid = os.getuid()
|
||||||
final_gid = os.getgid()
|
final_gid = os.getgid()
|
||||||
|
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print(
|
debugger(
|
||||||
"socket: running as {}:{}".format(
|
"socket: running as {}:{}".format(
|
||||||
pwd.getpwuid(final_uid)[0], grp.getgrgid(final_gid)[0]
|
pwd.getpwuid(final_uid)[0], grp.getgrgid(final_gid)[0]
|
||||||
)
|
)
|
||||||
@@ -237,7 +237,7 @@ def main():
|
|||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("starting up on {}".format(args.socket))
|
debugger("starting up on {}".format(args.socket))
|
||||||
sock.bind(args.socket)
|
sock.bind(args.socket)
|
||||||
atexit.register(socket_cleanup, args.socket, debug)
|
atexit.register(socket_cleanup, args.socket, debug)
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if debug > 2:
|
if debug > 2:
|
||||||
print("socket: waiting for a connection")
|
debugger("socket: waiting for a connection")
|
||||||
connection, client_address = sock.accept()
|
connection, client_address = sock.accept()
|
||||||
|
|
||||||
request_data = dict()
|
request_data = dict()
|
||||||
@@ -294,7 +294,7 @@ def main():
|
|||||||
result = {}
|
result = {}
|
||||||
for name, device in targets.items():
|
for name, device in targets.items():
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print(
|
debugger(
|
||||||
"reading data for '{}', timestamp={}, time={}".format(
|
"reading data for '{}', timestamp={}, time={}".format(
|
||||||
name, device["timestamp"], time.time()
|
name, device["timestamp"], time.time()
|
||||||
)
|
)
|
||||||
@@ -311,7 +311,7 @@ def main():
|
|||||||
send_response(connection, result, client, debug)
|
send_response(connection, result, client, debug)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
print(
|
debugger(
|
||||||
"socket: invalid request from {}".format(request_data["client"])
|
"socket: invalid request from {}".format(request_data["client"])
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Daemon: listens on a Unix socket and serves JBD BMS data to clients
|
||||||
|
#
|
||||||
|
import datetime
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
|
||||||
|
def debugger(data, pretty: bool = False):
|
||||||
|
if pretty:
|
||||||
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
pp.pprint(
|
||||||
|
{
|
||||||
|
"time": datetime.datetime.now(),
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = f"{str(datetime.datetime.now())} {data}"
|
||||||
|
print(msg)
|
||||||
Reference in New Issue
Block a user