diff --git a/.idea/misc.xml b/.idea/misc.xml
index bef9027..cf67d7b 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/.idea/wxGladeTest.iml b/.idea/wxGladeTest.iml
index 74d515a..a7875f7 100644
--- a/.idea/wxGladeTest.iml
+++ b/.idea/wxGladeTest.iml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/BmsApp.py b/BmsApp.py
index 0fa29fe..8c9bd32 100644
--- a/BmsApp.py
+++ b/BmsApp.py
@@ -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)
diff --git a/VoltageView.py b/VoltageView.py
new file mode 100644
index 0000000..c720314
--- /dev/null
+++ b/VoltageView.py
@@ -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.
+ 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()
diff --git a/VoltageView.wxg b/VoltageView.wxg
new file mode 100644
index 0000000..af3292c
--- /dev/null
+++ b/VoltageView.wxg
@@ -0,0 +1,155 @@
+
+
+
+
+
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..24f86bd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+wxPython~=4.1.0
+python-can~=3.3.3
+cantools~=35.3.0
+pywin32~=300
+Pypubsub~=4.0.3
\ No newline at end of file