今回、計測システム安定化のための以下ソフトウェア改善になります。
・Docker上で動作させていたものをRaspberryPI OS上で動作させる。
・sqlite3 DBにデータを書き込んで、Prometheus Agentで読みだす2プロセス構成を統合してDB廃止。
・Bluetooth通信上のバグ回避のため、プロセスの再起動を定期的に行う。
また、以下機能追加。
・放電/充電のFETステータス追加
⇒前回記事:「車庫屋根ソーラー発電システム28(パネル接続変更)」
⇒関連記事:「車庫屋根ソーラー発電システム25(計測システム改造)」

1.【bms-shed:プログラム統合・DB廃止とFETステータス追加】
以下プログラム1本に統合しました。また、FETステータスを追加しています。
以下起動シェルを別途作成。起動済プロセスを終了してから再起動する仕組みです。
2021年 7月22日(木)
solarshedのデータ取得間隔はデフォルトで600秒になっているのですが、bms-shed機能は10秒としています。solarshedの方も10秒で稼働させることにしました。
1.【solarshed:プログラム改造】
作者のGitt-Hubから、solarshed-0.1.tar.gz をダウンロード・展開し、server.py に記載されている600を10に変更しました。これまではpipにてインストールしたモジュールを使用していましたが、bms-shed機能と同様、ソースをpythonで直接起動する方法にします。
2.【solarshed:起動環境の設定】
以下起動シェルを別途作成。起動済プロセスを終了してから再起動する仕組みです。起動の仕組みは、bms-shed機能に合わせます。ただし、こちらは定期的な再起動はしません。クーロンにも同様に追加します。
・Docker上で動作させていたものをRaspberryPI OS上で動作させる。
・sqlite3 DBにデータを書き込んで、Prometheus Agentで読みだす2プロセス構成を統合してDB廃止。
・Bluetooth通信上のバグ回避のため、プロセスの再起動を定期的に行う。
また、以下機能追加。
・放電/充電のFETステータス追加
⇒前回記事:「車庫屋根ソーラー発電システム28(パネル接続変更)」
⇒関連記事:「車庫屋根ソーラー発電システム25(計測システム改造)」

1.【bms-shed:プログラム統合・DB廃止とFETステータス追加】
以下プログラム1本に統合しました。また、FETステータスを追加しています。
# cat bms-shed_dbg.py
#!/usr/bin/python3
import gatt
import json
import datetime
import sys
import time
import click
from time import gmtime, strftime
from prometheus_client import Gauge, start_http_server, Enum
global data
#data = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
data = {}
manager = gatt.DeviceManager(adapter_name='hci0')
class AnyDevice(gatt.Device):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def connect_succeeded(self):
super().connect_succeeded()
def connect_failed(self, error):
super().connect_failed(error)
print("[%s] Connection failed: %s" % (self.mac_address, str(error)))
exit(1)
def disconnect_succeeded(self):
super().disconnect_succeeded()
def services_resolved(self):
super().services_resolved()
device_information_service = next(
s for s in self.services
if s.uuid == '0000ff00-0000-1000-8000-00805f9b34fb')
self.bms_read_characteristic = next(
c for c in device_information_service.characteristics
if c.uuid == '0000ff01-0000-1000-8000-00805f9b34fb')
self.bms_write_characteristic = next(
c for c in device_information_service.characteristics
if c.uuid == '0000ff02-0000-1000-8000-00805f9b34fb')
print("BMS found")
self.bms_read_characteristic.enable_notifications()
def characteristic_enable_notifications_succeeded(self, characteristic):
super().characteristic_enable_notifications_succeeded(characteristic)
print("BMS request generic data")
self.response=bytearray()
self.rawdat={}
self.get_voltages=False
self.bms_write_characteristic.write_value(bytes([0xDD,0xA5,0x03,0x00,0xFF,0xFD,0x77]));
def characteristic_enable_notifications_failed(self, characteristic, error):
super.characteristic_enable_notifications_failed(characteristic, error)
print("BMS notification failed:",error)
def characteristic_value_updated(self, characteristic, value):
print("BMS answering")
self.response+=value
if (self.response.endswith(b'w')):
print("BMS answer:", self.response.hex())
self.response=self.response[4:]
if (self.get_voltages):
packVolts=0
for i in range(int(len(self.response)/2)-1):
cell=int.from_bytes(self.response[i*2:i*2+2], byteorder = 'big')/1000
self.rawdat['V{0:0=2}'.format(i+1)]=cell
packVolts+=cell
# + self.rawdat['V{0:0=2}'.format(i)]
self.rawdat['Vbat']=packVolts
self.rawdat['P']=round(self.rawdat['Vbat']*self.rawdat['Ibat'], 1)
data['Ah_percent'] = self.rawdat['Ah_percent']
data['Ah_remaining'] = self.rawdat['Ah_remaining']
data['Ah_full'] = self.rawdat['Ah_full']
data['P'] = self.rawdat['P']
data['Vbat'] = self.rawdat['Vbat']
data['Ibat'] = self.rawdat['Ibat']
data['T1'] = self.rawdat['T1']
data['Cycles'] = self.rawdat['Cycles']
data['T2'] = self.rawdat['T2']
data['V01'] = self.rawdat['V01']
data['V02'] = self.rawdat['V02']
data['V03'] = self.rawdat['V03']
data['V04'] = self.rawdat['V04']
data['V05'] = self.rawdat['V05']
data['V06'] = self.rawdat['V06']
data['V07'] = self.rawdat['V07']
data['V08'] = self.rawdat['V08']
data['Bal'] = self.rawdat['Bal']
data['State'] = self.rawdat['State']
data['FET_St'] = self.rawdat['FET_St']
self.manager.stop()
else:
self.rawdat['packV']=int.from_bytes(self.response[0:2], byteorder = 'big',signed=True)/100.0
self.rawdat['Ibat']=int.from_bytes(self.response[2:4], byteorder = 'big',signed=True)/100.0
self.rawdat['Ah_remaining']=int.from_bytes(self.response[4:6], byteorder='big', signed=True)/100
self.rawdat['Ah_full']=int.from_bytes(self.response[6:8], byteorder='big', signed=True)/100
self.rawdat['Cycles']=int.from_bytes(self.response[8:10], byteorder='big', signed=True)
self.rawdat['Bal']=int.from_bytes(self.response[12:14],byteorder = 'big',signed=False)
self.rawdat['State']=int.from_bytes(self.response[16:18], byteorder = 'big',signed=False)
self.rawdat['FET_St']=int.from_bytes(self.response[20:21], byteorder = 'big',signed=False)
self.rawdat['Ah_percent']=round(self.rawdat['Ah_remaining'] / self.rawdat['Ah_full'] * 100, 2)
for i in range(int.from_bytes(self.response[22:23],'big')): # read temperatures
self.rawdat['T{0:0=1}'.format(i+1)]=(int.from_bytes(self.response[23+i*2:i*2+25],'big')-2731)/10
print("BMS request voltages")
self.get_voltages=True
self.response=bytearray()
self.bms_write_characteristic.write_value(bytes([0xDD,0xA5,0x04,0x00,0xFF,0xFC,0x77]));
def characteristic_write_value_failed(self, characteristic, error):
pass
class Metrics():
def __init__(self):
self.last_update_gauge = Gauge('bms_last_update', 'Metrics of last update')
self.ah_percent_gauge = Gauge('bms_ah_percent', 'Metrics of percent of capacity')
self.ah_remaining_gauge = Gauge('bms_ah_remaining', 'Metrics of remaining capcity')
self.ah_full_gauge = Gauge('bms_ah_full', 'Metrics of full capacity')
self.power_gauge = Gauge('bms_power', 'Metrics of power')
self.voltage_gauge = Gauge('bms_voltage', 'Metrics of voltage')
self.current_gauge = Gauge('bms_current', 'Metrics of current')
self.temperature_gauge = Gauge('bms_temperature', 'Metrics of temperature')
self.cycles_gauge = Gauge('bms_cycles', 'Metrics of cycles')
self.mode_enum = Enum('bms_mode', 'Metrics of mode', states=['discharging', 'charging'])
self.time_left = Gauge('bms_time_left', 'Metrics of time to charge/discharge', ['mode'])
self.temperature2_gauge = Gauge('bms_temperature2', 'Metrics of temperature2')
self.voltage01_gauge = Gauge('bms_voltage01', 'Metrics of voltage01')
self.voltage02_gauge = Gauge('bms_voltage02', 'Metrics of voltage02')
self.voltage03_gauge = Gauge('bms_voltage03', 'Metrics of voltage03')
self.voltage04_gauge = Gauge('bms_voltage04', 'Metrics of voltage04')
self.voltage05_gauge = Gauge('bms_voltage05', 'Metrics of voltage05')
self.voltage06_gauge = Gauge('bms_voltage06', 'Metrics of voltage06')
self.voltage07_gauge = Gauge('bms_voltage07', 'Metrics of voltage07')
self.voltage08_gauge = Gauge('bms_voltage08', 'Metrics of voltage08')
#self.bal_gauge = Gauge('bms_bal', 'Metrics of bal')
self.bal_status = Gauge('bms_balance_status', 'Metrics of balance status', ['cell'])
self.bms_state = Gauge('bms_state', 'Metrics of bms state', ['state'])
self.bms_fetst = Gauge('bms_fetst', 'Metrics of bms fetst', ['fetst'])
def build_metrics(self):
last_update = datetime.datetime.now()
# last_update = datetime.datetime.strptime(last_update, '%Y-%m-%d %H:%M:%S')
last_update = last_update.timestamp()
self.last_update_gauge.set(last_update)
self.ah_percent_gauge.set(data['Ah_percent'])
ah_remaining_gauge = data['Ah_remaining']
self.ah_remaining_gauge.set(ah_remaining_gauge)
ah_full_gauge = data['Ah_full']
self.ah_full_gauge.set(ah_full_gauge)
self.power_gauge.set(data['P'])
self.voltage_gauge.set(data['Vbat'])
self.current_gauge.set(data['Ibat'])
self.temperature_gauge.set(data['T1'])
self.cycles_gauge.set(data['Cycles'])
self.temperature2_gauge.set(data['T2'])
self.voltage01_gauge.set(data['V01'])
self.voltage02_gauge.set(data['V02'])
self.voltage03_gauge.set(data['V03'])
self.voltage04_gauge.set(data['V04'])
self.voltage05_gauge.set(data['V05'])
self.voltage06_gauge.set(data['V06'])
self.voltage07_gauge.set(data['V07'])
self.voltage08_gauge.set(data['V08'])
celbals = [0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0]
for num in range(0,8):
celbals[num] = ( data['Bal']>>num ) & 0b1
self.bal_status.labels(cell='celbals[1]').set(celbals[0])
self.bal_status.labels(cell='celbals[2]').set(celbals[1])
self.bal_status.labels(cell='celbals[3]').set(celbals[2])
self.bal_status.labels(cell='celbals[4]').set(celbals[3])
self.bal_status.labels(cell='celbals[5]').set(celbals[4])
self.bal_status.labels(cell='celbals[6]').set(celbals[5])
self.bal_status.labels(cell='celbals[7]').set(celbals[6])
self.bal_status.labels(cell='celbals[8]').set(celbals[7])
bms_state = [0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0]
for num in range(0,13):
bms_state[num] = ( data['State']>>num ) & 0b1
self.bms_state.labels(state='Cell_Block_Over_Vol').set(bms_state[0])
self.bms_state.labels(state='Cell_Block_Under_Vol').set(bms_state[1])
self.bms_state.labels(state='Battery_Over_Vol').set(bms_state[2])
self.bms_state.labels(state='Battery_Under_Vol').set(bms_state[3])
self.bms_state.labels(state='Charging_Over_Temp').set(bms_state[4])
self.bms_state.labels(state='Charging_Under_Temp').set(bms_state[5])
self.bms_state.labels(state='Discharging_Over_Temp').set(bms_state[6])
self.bms_state.labels(state='Discharging_Under_Temp').set(bms_state[7])
self.bms_state.labels(state='Charging_Over_Current').set(bms_state[8])
self.bms_state.labels(state='Discharging_Over_Current').set(bms_state[9])
self.bms_state.labels(state='Short_Circuit').set(bms_state[10])
self.bms_state.labels(state='Fore-end_IC_Error').set(bms_state[11])
self.bms_state.labels(state='MOS_Software_Lock-in').set(bms_state[12])
fet_state = [0b0,0b0]
for num in range(0,2):
fet_state[num] = ( data['FET_St']>>num ) & 0b1
self.bms_fetst.labels(fetst='FET_charging').set(fet_state[0])
self.bms_fetst.labels(fetst='FET_discharging').set(fet_state[1])
if float(data['P']) >= 0:
mode = 'charging'
else:
mode = 'discharging'
self.mode_enum.state(mode)
if (len(sys.argv)<2):
print("Usage: bms-shed.py <device_uuid>")
else:
start_http_server(8000)
metrics = Metrics()
while True:
device = AnyDevice(mac_address=sys.argv[1], manager=manager)
print("main AnyDevice")
device.connect()
print("main device.connect")
manager.run()
print("main manager.run")
metrics.build_metrics()
print("main metrics.build_metrics")
time.sleep(10)
2.【bms-shed:起動環境の設定】
2.【bms-shed:起動環境の設定】
以下起動シェルを別途作成。起動済プロセスを終了してから再起動する仕組みです。
# cat /root/bms-shed.sh
#!/bin/bash
pyspid=`ps -ef | grep bms-shed | awk '{if($8=="python")print $2}'`
if [ ! "$pyspid" = "" ]
then
kill -9 $pyspid
sleep 1
fi
python /root/bms-shed_dbg.py XX:XX:XX:XX:XX:XX >> /root/bms-shed_dbg.log &
クーロンに以下追加。10分毎に再起動します。連続稼働していると通信ができなくなるBluetoothの不具合回避のための対応になります。オリジナルのGitHUB Q&A によるとRaspberry PIの内蔵Bluetoothに問題があり、別途Bluetoothドングルで改善するかもしれないとのことですが、運用対処として内蔵Bluetoothを使用して定期的なプロセス再起動をしています。
クーロンに以下追加。10分毎に再起動します。連続稼働していると通信ができなくなるBluetoothの不具合回避のための対応になります。オリジナルのGitHUB Q&A によるとRaspberry PIの内蔵Bluetoothに問題があり、別途Bluetoothドングルで改善するかもしれないとのことですが、運用対処として内蔵Bluetoothを使用して定期的なプロセス再起動をしています。
# crontab -l
・・・
・・・
09,19,29,39,49,59 * * * * /root/bms-shed.sh
@reboot /root/bms-shed.sh
・・・2021年 7月22日(木)
solarshedのデータ取得間隔はデフォルトで600秒になっているのですが、bms-shed機能は10秒としています。solarshedの方も10秒で稼働させることにしました。
1.【solarshed:プログラム改造】
作者のGitt-Hubから、solarshed-0.1.tar.gz をダウンロード・展開し、server.py に記載されている600を10に変更しました。これまではpipにてインストールしたモジュールを使用していましたが、bms-shed機能と同様、ソースをpythonで直接起動する方法にします。
2.【solarshed:起動環境の設定】
以下起動シェルを別途作成。起動済プロセスを終了してから再起動する仕組みです。起動の仕組みは、bms-shed機能に合わせます。ただし、こちらは定期的な再起動はしません。クーロンにも同様に追加します。
# cat /root/solarshed.sh
#!/bin/bash
pyspid=`ps -ef | grep solarshed | awk '{if($8=="python")print $2}'`
if [ ! "$pyspid" = "" ]
then
kill -9 $pyspid
# echo $pyspid
sleep 1
fi
# python /root/solarshed-0.1/solarshed/server.py &
# crontab -l
・・・
・・・
@reboot /root/solarshed.sh
・・・
コメント