前回、24Vシステムに同じ7.2kWhのバッテリーを並列で増設しましたが、各バッテリーの充電にばらつきがでるため、思い切って48Vシステムに変更することにしました。
⇒前回記事:「車庫屋根ソーラー発電システム54(LiFePO4バッテリー容量拡張)」
2024年3月10日(日) 48Vシステムへの変更
以下の構成変更だけで48Vシステムに変更することが可能です。
1.24Vバッテリー並列⇒直列変更、ソーラーパネルの一部並列⇒直列変更
2.充放電コントローラの設定変更(24V⇒48V設定)
3.24V⇒48Vインバータへの入替
ちょうどヤフオクでサーバ用途の48Vインバータが2台「即決」で出ていたので落札しました。新品希望小売価格の20分の1以下で7千円/台くらいだったので、かなりお得でした。
【充放電コントローラの設定変更】
【24Vバッテリー直列変更、ソーラーパネル一部直列変更、インバータ入替】
構成変更前(24Vバッテリー並列)
構成変更後(24Vバッテリー直列変更、ソーラーパネル一部直列変更、インバータ入替)
100Wソーラーパネルの電圧が足りないので直列化しました。
48VインバータにUPS並みのAC自動切替機能があり、AC自動切替器が不要になりました。
48Vシステムをしばらく運用してみると、バッテリーのアクティブバランサーが2台で独立して動作していることから2台のバッテリー間でバランスが取れないことが判明しました。2台の24Vバッテリーを48Vバッテリーとして1台に統合する構成変更の必要があるようです。
2024年3月20日(水) バッテリーの48V化変更
2台の24Vバッテリーを48Vバッテリーに構成変更するにあたって以下の部品を入手。
1.SmartBMS(LiFePO4、16セル、定格電流最大200A)
⇒前回のバッテリー増設時に8セル~21セル対応可能な、LiFePO4用BMSをAliExpressで入手済、現在はちょっと値上り
2.アクティブバランサ(LiFePO4、16セル、バランス電流最大10A)
⇒4セル~17セル対応可能な、LiFePO4/リチウム両対応のバランサをAliExpressで新たに入手
筐体はそのままですので、片方の筐体にSmartBMSとアクティブバランサを実装して、もう片方の筐体に配線します。アクティブバランサはバランス動作時に発熱するので筐体の外側に設置してます。
構成変更後(バッテリーの48V化)
SmartBMS設定の赤囲み部分をデフォルトから調整しました。
「NTC3」の無効化については、温度計は3つも要らないな・・・というのとプロセス改造に盛り込むのがちょっと面倒だったのが理由ですが、温度計を従来と同じ2つにすることでこのBMSで以前スパイク状に発生していた電圧の異常値が出なくなりました。
動作確認。
【計測システムの変更】
SmartBMSがバッテリーの48V化で変わりますので計測システムの大幅な変更が必要です。変更内容としては以下の通り
1.bms-shedプロセスを8セルから16セルに改造
2.Grafana画面の16セル表示への変更
変更前
変更後
幸い、ソースコードの赤字部分を追加するだけで済みました。
ーー
ーー
変更後のGrafana画面。16セル横に一列並ぶと壮観です。
【12Vシステムの24Vシステム化】
24Vシステムで使っていたインバータを活用するため、12Vシステムを24Vシステム化しました。以下の対応のみです。
1.鉛バッテリーを並列から直列に変更
2.充放電コントローラの設定変更(12V⇒24V変更)
変更前
変更後
⇒前回記事:「車庫屋根ソーラー発電システム54(LiFePO4バッテリー容量拡張)」
2024年3月10日(日) 48Vシステムへの変更
以下の構成変更だけで48Vシステムに変更することが可能です。
1.24Vバッテリー並列⇒直列変更、ソーラーパネルの一部並列⇒直列変更
2.充放電コントローラの設定変更(24V⇒48V設定)
3.24V⇒48Vインバータへの入替
ちょうどヤフオクでサーバ用途の48Vインバータが2台「即決」で出ていたので落札しました。新品希望小売価格の20分の1以下で7千円/台くらいだったので、かなりお得でした。
【充放電コントローラの設定変更】
【24Vバッテリー直列変更、ソーラーパネル一部直列変更、インバータ入替】
構成変更前(24Vバッテリー並列)
構成変更後(24Vバッテリー直列変更、ソーラーパネル一部直列変更、インバータ入替)
100Wソーラーパネルの電圧が足りないので直列化しました。
48VインバータにUPS並みのAC自動切替機能があり、AC自動切替器が不要になりました。
48Vシステムをしばらく運用してみると、バッテリーのアクティブバランサーが2台で独立して動作していることから2台のバッテリー間でバランスが取れないことが判明しました。2台の24Vバッテリーを48Vバッテリーとして1台に統合する構成変更の必要があるようです。
2024年3月20日(水) バッテリーの48V化変更
2台の24Vバッテリーを48Vバッテリーに構成変更するにあたって以下の部品を入手。
1.SmartBMS(LiFePO4、16セル、定格電流最大200A)
⇒前回のバッテリー増設時に8セル~21セル対応可能な、LiFePO4用BMSをAliExpressで入手済、現在はちょっと値上り
2.アクティブバランサ(LiFePO4、16セル、バランス電流最大10A)
⇒4セル~17セル対応可能な、LiFePO4/リチウム両対応のバランサをAliExpressで新たに入手
筐体はそのままですので、片方の筐体にSmartBMSとアクティブバランサを実装して、もう片方の筐体に配線します。アクティブバランサはバランス動作時に発熱するので筐体の外側に設置してます。
構成変更後(バッテリーの48V化)
SmartBMS設定の赤囲み部分をデフォルトから調整しました。
「NTC3」の無効化については、温度計は3つも要らないな・・・というのとプロセス改造に盛り込むのがちょっと面倒だったのが理由ですが、温度計を従来と同じ2つにすることでこのBMSで以前スパイク状に発生していた電圧の異常値が出なくなりました。
動作確認。
【計測システムの変更】
SmartBMSがバッテリーの48V化で変わりますので計測システムの大幅な変更が必要です。変更内容としては以下の通り
1.bms-shedプロセスを8セルから16セルに改造
2.Grafana画面の16セル表示への変更
変更前
変更後
幸い、ソースコードの赤字部分を追加するだけで済みました。
ーー
#!/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
from prometheus_client import Gauge, start_http_server
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['V09'] = self.rawdat['V09']
data['V10'] = self.rawdat['V10']
data['V11'] = self.rawdat['V11']
data['V12'] = self.rawdat['V12']
data['V13'] = self.rawdat['V13']
data['V14'] = self.rawdat['V14']
data['V15'] = self.rawdat['V15']
data['V16'] = self.rawdat['V16']
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.voltage09_gauge = Gauge('bms_voltage09', 'Metrics of voltage09')
self.voltage10_gauge = Gauge('bms_voltage10', 'Metrics of voltage10')
self.voltage11_gauge = Gauge('bms_voltage11', 'Metrics of voltage11')
self.voltage12_gauge = Gauge('bms_voltage12', 'Metrics of voltage12')
self.voltage13_gauge = Gauge('bms_voltage13', 'Metrics of voltage13')
self.voltage14_gauge = Gauge('bms_voltage14', 'Metrics of voltage14')
self.voltage15_gauge = Gauge('bms_voltage15', 'Metrics of voltage15')
self.voltage16_gauge = Gauge('bms_voltage16', 'Metrics of voltage16')
#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'])
self.voltage09_gauge.set(data['V09'])
self.voltage10_gauge.set(data['V10'])
self.voltage11_gauge.set(data['V11'])
self.voltage12_gauge.set(data['V12'])
self.voltage13_gauge.set(data['V13'])
self.voltage14_gauge.set(data['V14'])
self.voltage15_gauge.set(data['V15'])
self.voltage16_gauge.set(data['V16'])
celbals = [0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0,0b0]
for num in range(0,16):
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])
self.bal_status.labels(cell='celbals[9]').set(celbals[8])
self.bal_status.labels(cell='celbals[10]').set(celbals[9])
self.bal_status.labels(cell='celbals[11]').set(celbals[10])
self.bal_status.labels(cell='celbals[12]').set(celbals[11])
self.bal_status.labels(cell='celbals[13]').set(celbals[12])
self.bal_status.labels(cell='celbals[14]').set(celbals[13])
self.bal_status.labels(cell='celbals[15]').set(celbals[14])
self.bal_status.labels(cell='celbals[16]').set(celbals[15])
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)
変更後のGrafana画面。16セル横に一列並ぶと壮観です。
【12Vシステムの24Vシステム化】
24Vシステムで使っていたインバータを活用するため、12Vシステムを24Vシステム化しました。以下の対応のみです。
1.鉛バッテリーを並列から直列に変更
2.充放電コントローラの設定変更(12V⇒24V変更)
変更前
変更後
コメント