36-cell (3-slave) voltage monitoring/logging version

This commit is contained in:
Yeongdo Park 2020-11-29 10:07:37 -08:00
parent 915ca498fb
commit b155a58374
6 changed files with 354 additions and 52 deletions

2
.idea/misc.xml generated
View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (wxGladeTest)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (bms-can-ui)" project-jdk-type="Python SDK" />
</project>

2
.idea/wxGladeTest.iml generated
View file

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="Python 3.7 (bms-can-ui)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

127
BmsApp.py
View file

@ -1,6 +1,10 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import datetime
import csv
import win32api
import wx
import wx.propgrid
@ -13,13 +17,22 @@ from enum import Enum
import can
import cantools
from BmsView import BmsMonitorFrame
from VoltageView import BmsMonitorFrame
startTime = datetime.datetime.now()
winUptime_ms = win32api.GetTickCount()
winUptime_dt = datetime.timedelta(milliseconds=winUptime_ms)
bootTime = startTime-winUptime_dt
db = cantools.database.load_file('res/EBUS_Host.dbc')
nModule = 22
nModule = 3
nCell = 12
completeKey = "isModuleDataComplete"
timestampKey = "MessageTimestamp"
lastUpdateKey = timestampKey + ".old"
lastMsgKey = "LastMessageId"
def parse_can_id(arbitration_id):
mprefix = (arbitration_id & 0xfffff000) >> 12
@ -48,7 +61,7 @@ def find_category(imsg, isignal):
if 0x00 <= msg_type <= 0x06:
return BMS_MSG_CAT.BMS_PROFILE
elif 0x07 <= msg_type <= 0x0B:
elif 0x07 <= msg_type <= 0x0C:
return BMS_MSG_CAT.MODULE_DATA
elif msg_type == 0x10:
if isignal < 4:
@ -84,7 +97,7 @@ class BmsListener (can.Listener):
self.parent = parent
def on_message_received(self, msg):
print("{:#X}".format(msg.arbitration_id))
# print("{:#X}".format(msg.arbitration_id))
self.parent.process_message(msg)
@ -98,8 +111,31 @@ class Controller:
pub.subscribe (self.view.update_battery_module_grid, "bms.module.update")
pub.subscribe(self.start_write, "view.start")
pub.subscribe(self.stop_write, "view.stop")
pub.subscribe (self.write_battery_module, "bms.module.update")
pub.subscribe (self.model.setter, "view.id_selector")
def start_write(self):
filename = self.view.text_ctrl_1.GetValue()
self.output_files = [open(filename+"-M{}.csv".format(i+1), 'w') for i in range(nModule)]
self.csv_writers = [csv.writer(output_file, delimiter=',') for output_file in self.output_files]
for csv_writer in self.csv_writers:
csv_writer.writerow(["time"] + ["CV{:02d}".format(i) for i in range(nCell)])
def stop_write(self):
for output_file in self.output_files:
output_file.close()
def write_battery_module(self, state, i_module):
module_dict = state[i_module]
if module_dict[lastMsgKey] & 0xff == 0x0c:
keyString = 'BatS_Cell{}_V'
voltages = [module_dict.get(keyString.format(x), -1) for x in range(nCell)]
dt = str(bootTime+datetime.timedelta(seconds=module_dict.get(timestampKey, 0.0)))
self.csv_writers[i_module].writerow([dt]+voltages)
class PmgrowBms:
def __init__(self, add_printer=True):
@ -108,7 +144,7 @@ class PmgrowBms:
self.notifier = None
self.listeners = [can.Printer()] if add_printer else []
self.listeners.append(BmsListener(self))
self.master_id = 0x02
self.master_id = 0x09
def setter (self, id):
print ("updating master id")
@ -128,17 +164,28 @@ class PmgrowBms:
self.notifier = None
self.bus = None
def __update_bm_state(self, dmsg):
imodule = dmsg['BatS_Cell_ModuleNo']
def __update_bm_state(self, dmsg, msg):
i_module = dmsg['BatS_Cell_ModuleNo']
self.battery_module_state[i_module][lastUpdateKey] = self.battery_module_state[i_module].get(timestampKey,0.0)
self.battery_module_state[i_module][timestampKey] = msg.timestamp
self.battery_module_state[i_module][lastMsgKey] = msg.arbitration_id
if self.battery_module_state[i_module][timestampKey] - self.battery_module_state[i_module][lastUpdateKey] > 0.1:
self.battery_module_state[i_module][completeKey] = 4
else:
self.battery_module_state[i_module][completeKey] = self.battery_module_state[i_module][completeKey] - 1
for k in dmsg:
self.battery_module_state[imodule][k] = dmsg[k]
self.battery_module_state[i_module][k] = dmsg[k]
return i_module
def process_message(self, msg):
msg_prefix, master_id, msg_type = parse_can_id(msg.arbitration_id)
full_id = (msg_prefix, master_id, msg_type)
db_msg = db.get_message_by_frame_id(msg.arbitration_id)
print(master_id, self.master_id)
# print(master_id, self.master_id)
if master_id == self.master_id:
if find_category(full_id, 0) == BMS_MSG_CAT.MODULE_DATA:
try:
@ -146,8 +193,8 @@ class PmgrowBms:
except KeyError as key:
print(key, " does not exist")
else:
self.__update_bm_state(decoded)
pub.sendMessage("bms.module.update", state=self.battery_module_state)
i_module = self.__update_bm_state(decoded, msg)
pub.sendMessage("bms.module.update", state=self.battery_module_state, i_module=i_module)
elif find_category(full_id, 0) == BMS_MSG_CAT.BMS_PROFILE:
pass
@ -184,36 +231,6 @@ class BmsMonitorView(BmsMonitorFrame):
def __init__(self, *args, **kwds):
BmsMonitorFrame.__init__(self, *args, **kwds)
self.property_grid_page_info = self.property_grid_1.AddPage("Info")
for c in pgrid_category:
cat_name = c.name
cat = wx.propgrid.PropertyCategory(label=cat_name)
self.property_grid_page_info.Append(cat)
# bms_model = wx.propgrid.StringProperty(label="Model", name="bms_model", value="B3DWER32EW")
# self.property_grid_page_info.AppendIn("packdata", bms_model)
for msg in db.messages:
prefix, id, type = parse_can_id(msg.frame_id)
id_tuple = prefix, id, type
if id == 0x02:
if find_category(id_tuple, 0) == BMS_MSG_CAT.BMS_PROFILE:
prop = wx.propgrid.StringProperty(label=msg.name)
self.property_grid_page_info.AppendIn(BMS_MSG_CAT.BMS_PROFILE.name, prop)
for isig, signal in enumerate(msg.signals):
plabel = str_hex(type) + signal.name
prop = wx.propgrid.StringProperty(label=plabel)
prop.SetHelpString(signal.comment)
cat = find_category(id_tuple, isig)
if cat in pgrid_category:
self.property_grid_page_info.AppendIn(cat.name, prop)
def GetModuleVoltage(self, module_dict):
keyString = 'BatS_Cell{}_V'
return [module_dict.get(keyString.format(x), -1) for x in range(nCell)]
@ -222,18 +239,28 @@ class BmsMonitorView(BmsMonitorFrame):
keyString = 'BatS_Cell{}_BalState'
return [module_dict.get(keyString.format(x), -1) for x in range(nCell)]
def update_battery_module_grid(self, state):
i_module = self.spin_ctrl_1.GetValue()
def update_battery_module_grid(self, state, i_module):
# grids = [self.grid_1, self.grid_2, self.grid_3]
ts_ctrls = [self.timestamp_1, self.timestamp_2, self.timestamp_3, ]
grid = self.grid_1
ts_ctrl = ts_ctrls[i_module]
module_dict = state[i_module]
voltages = self.GetModuleVoltage(module_dict)
bal_states = self.GetModuleBalState(module_dict)
for i in range(nCell):
self.grid_1.SetCellValue(i, 0, str(voltages[i]))
self.grid_1.SetCellValue(i, 2, str(bal_states[i]))
self.grid_1.ForceRefresh()
if module_dict[lastMsgKey] & 0xff == 0x0c:
voltages = self.GetModuleVoltage(module_dict)
bal_states = self.GetModuleBalState(module_dict)
dt = datetime.datetime.fromtimestamp(module_dict.get(timestampKey, 0.0))
msg_id = module_dict.get(lastMsgKey, 0)
ts_ctrl.SetValue(str(bootTime+datetime.timedelta(seconds=dt.timestamp())))
for i in range(nCell):
grid.SetCellValue(i, i_module, str(voltages[i]))
print("refreshing module ", i_module, "latest update: ", bootTime+datetime.timedelta(seconds=dt.timestamp()),
dt.timestamp(), "{:x}".format(msg_id))
grid.ForceRefresh()
def OnIdChoice(self, event):
print ("sending update request")
# event.Get
# wx.Event.GetEventObject()
pub.sendMessage("view.id_selector", id=int(self.choice_1.GetStringSelection(), 16))
event.Skip()
@ -253,7 +280,7 @@ class BmsMonitorView(BmsMonitorFrame):
class BmsApp(wx.App):
def OnInit(self):
self.bms = PmgrowBms()
self.bms = PmgrowBms(add_printer=True)
self.frame = BmsMonitorView(None, wx.ID_ANY, "")
self.bms_controller = Controller(self.bms, self.frame)
self.SetTopWindow(self.frame)

115
VoltageView.py Normal file
View file

@ -0,0 +1,115 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 0.9.6 on Sun Nov 29 07:00:24 2020
#
import wx
import wx.grid
# begin wxGlade: dependencies
import gettext
# end wxGlade
# begin wxGlade: extracode
# end wxGlade
class BmsMonitorFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: BmsMonitorFrame.__init__
kwds["style"] = kwds.get("style", 0) | wx.CAPTION | wx.CLIP_CHILDREN | wx.CLOSE_BOX | wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.SYSTEM_MENU
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((338, 393))
self.button_1 = wx.ToggleButton(self, wx.ID_ANY, _("Start"))
self.text_ctrl_1 = wx.TextCtrl(self, wx.ID_ANY, _("prefix"))
self.timestamp_1 = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.timestamp_2 = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.timestamp_3 = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.grid_1 = wx.grid.Grid(self, wx.ID_ANY, size=(1, 1))
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggle, self.button_1)
# end wxGlade
def __set_properties(self):
# begin wxGlade: BmsMonitorFrame.__set_properties
self.SetTitle(_("BMS Monitor"))
_icon = wx.NullIcon
_icon.CopyFromBitmap(wx.Bitmap("res\\repurpose-energy-ci-icon.ico", wx.BITMAP_TYPE_ANY))
self.SetIcon(_icon)
self.grid_1.CreateGrid(12, 3)
self.grid_1.EnableEditing(0)
self.grid_1.EnableDragColSize(0)
self.grid_1.EnableDragRowSize(0)
self.grid_1.EnableDragGridSize(0)
self.grid_1.SetColLabelValue(0, _("Slave 1 (V)"))
self.grid_1.SetColLabelValue(1, _("Slave 2 (V)"))
self.grid_1.SetColLabelValue(2, _("Slave 3 (V)"))
self.grid_1.SetRowLabelValue(0, _("Cell 1"))
self.grid_1.SetRowLabelValue(1, _("Cell 2"))
self.grid_1.SetRowLabelValue(2, _("Cell 3"))
self.grid_1.SetRowLabelValue(3, _("Cell 4"))
self.grid_1.SetRowLabelValue(4, _("Cell 5"))
self.grid_1.SetRowLabelValue(5, _("Cell 6"))
self.grid_1.SetRowSize(5, 18)
self.grid_1.SetRowLabelValue(6, _("Cell 7"))
self.grid_1.SetRowLabelValue(7, _("Cell 8"))
self.grid_1.SetRowLabelValue(8, _("Cell 9"))
self.grid_1.SetRowLabelValue(9, _("Cell 10"))
self.grid_1.SetRowLabelValue(10, _("Cell 11"))
self.grid_1.SetRowLabelValue(11, _("Cell 12"))
# end wxGlade
def __do_layout(self):
# begin wxGlade: BmsMonitorFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_6 = wx.BoxSizer(wx.HORIZONTAL)
sizer_6.Add(self.button_1, 0, 0, 0)
sizer_6.Add((20, 20), 0, 0, 0)
label_4 = wx.StaticText(self, wx.ID_ANY, _("Output: "))
sizer_6.Add(label_4, 0, 0, 0)
sizer_6.Add(self.text_ctrl_1, 0, 0, 0)
sizer_1.Add(sizer_6, 0, wx.EXPAND, 0)
label_1 = wx.StaticText(self, wx.ID_ANY, _("Slave 1 Update "))
sizer_2.Add(label_1, 0, 0, 0)
sizer_2.Add(self.timestamp_1, 1, 0, 0)
sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
label_2 = wx.StaticText(self, wx.ID_ANY, _("Slave 2 Update "))
sizer_3.Add(label_2, 0, 0, 0)
sizer_3.Add(self.timestamp_2, 1, wx.EXPAND, 0)
sizer_1.Add(sizer_3, 0, wx.EXPAND, 0)
label_3 = wx.StaticText(self, wx.ID_ANY, _("Slave 3 Update "))
sizer_4.Add(label_3, 0, 0, 0)
sizer_4.Add(self.timestamp_3, 2, wx.EXPAND, 0)
sizer_1.Add(sizer_4, 0, wx.EXPAND, 0)
sizer_1.Add(self.grid_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
# end wxGlade
def OnToggle(self, event): # wxGlade: BmsMonitorFrame.<event_handler>
print("Event handler 'OnToggle' not implemented!")
event.Skip()
# end of class BmsMonitorFrame
class BmsApp(wx.App):
def OnInit(self):
self.frame = BmsMonitorFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
# end of class BmsApp
if __name__ == "__main__":
gettext.install("app") # replace with the appropriate catalog name
app = BmsApp(0)
app.MainLoop()

155
VoltageView.wxg Normal file
View file

@ -0,0 +1,155 @@
<?xml version="1.0"?>
<!-- generated by wxGlade 0.9.6 on Sun Nov 29 07:00:21 2020 -->
<application class="BmsApp" encoding="UTF-8" for_version="3.0" header_extension=".h" indent_amount="4" indent_symbol="space" is_template="0" language="python" mark_blocks="1" name="app" option="0" overwrite="1" path="./VoltageView.py" source_extension=".cpp" top_window="frame" use_gettext="1" use_new_namespace="1">
<object class="BmsMonitorFrame" name="frame" base="EditFrame">
<size>338, 393</size>
<title>BMS Monitor</title>
<style>wxCAPTION|wxMINIMIZE_BOX|wxCLOSE_BOX|wxMAXIMIZE_BOX|wxSYSTEM_MENU|wxCLIP_CHILDREN</style>
<icon>res\repurpose-energy-ci-icon.ico</icon>
<object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxBoxSizer" name="sizer_6" base="EditBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxToggleButton" name="button_1" base="EditToggleButton">
<events>
<handler event="EVT_TOGGLEBUTTON">OnToggle</handler>
</events>
<label>Start</label>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="spacer" name="spacer" base="EditSpacer">
<width>20</width>
<height>20</height>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxStaticText" name="label_4" base="EditStaticText">
<label>Output: </label>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxTextCtrl" name="text_ctrl_1" base="EditTextCtrl">
<value>prefix</value>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxStaticText" name="label_1" base="EditStaticText">
<label>Slave 1 Update </label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<border>0</border>
<object class="wxTextCtrl" name="timestamp_1" base="EditTextCtrl">
<style>wxTE_READONLY</style>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxStaticText" name="label_2" base="EditStaticText">
<label>Slave 2 Update </label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxTextCtrl" name="timestamp_2" base="EditTextCtrl">
<style>wxTE_READONLY</style>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxBoxSizer" name="sizer_4" base="EditBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>0</option>
<border>0</border>
<object class="wxStaticText" name="label_3" base="EditStaticText">
<label>Slave 3 Update </label>
</object>
</object>
<object class="sizeritem">
<option>2</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxTextCtrl" name="timestamp_3" base="EditTextCtrl">
<style>wxTE_READONLY</style>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<border>0</border>
<flag>wxEXPAND</flag>
<object class="wxGrid" name="grid_1" base="EditGrid">
<create_grid>1</create_grid>
<columns>
<column size="-1">Slave 1 (V)</column>
<column size="-1">Slave 2 (V)</column>
<column size="-1">Slave 3 (V)</column>
</columns>
<rows>
<row size="-1">Cell 1</row>
<row size="-1">Cell 2</row>
<row size="-1">Cell 3</row>
<row size="-1">Cell 4</row>
<row size="-1">Cell 5</row>
<row size="18">Cell 6</row>
<row size="-1">Cell 7</row>
<row size="-1">Cell 8</row>
<row size="-1">Cell 9</row>
<row size="-1">Cell 10</row>
<row size="-1">Cell 11</row>
<row size="-1">Cell 12</row>
</rows>
<enable_editing>0</enable_editing>
<enable_grid_lines>1</enable_grid_lines>
<enable_col_resize>0</enable_col_resize>
<enable_row_resize>0</enable_row_resize>
<enable_grid_resize>0</enable_grid_resize>
<selection_mode>wxGrid.wxGridSelectCells</selection_mode>
</object>
</object>
</object>
</object>
</application>

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
wxPython~=4.1.0
python-can~=3.3.3
cantools~=35.3.0
pywin32~=300
Pypubsub~=4.0.3