Browse Source

1、故障记录功能

Franike 11 tháng trước cách đây
mục cha
commit
4934013f15
7 tập tin đã thay đổi với 176 bổ sung48 xóa
  1. 74 39
      controller/bms_home_ctl.py
  2. 31 3
      controller/bms_record_ctl.py
  3. 8 1
      model/record_model.py
  4. 1 0
      ui/record.py
  5. 41 3
      utils/can.py
  6. 5 0
      utils/globalvar.py
  7. 16 2
      widget/bms_record.py

+ 74 - 39
controller/bms_home_ctl.py

@@ -25,6 +25,8 @@ from utils.qt import QTimer, QThread, QColor, Qt, QDateTime, QtGui, QtWidgets, Q
 from utils.hex_bit import a_bit
 from worker.bms_work import BmsSend, BmsReceived
 from ui.own.palette_theme import set_edt_bg
+import csv
+
 
 class BmsHomeControll:
     def __init__(self):
@@ -334,31 +336,31 @@ class BmsHomeControll:
             set_edt_bg(self.bms_main_ctl._view.center_widget.over_cell_warn, QColor(Qt.yellow))
 
         # 总压过压告警
-        if (data[0] & 0x0C >> 2) == 0:
+        if ((data[0] & 0x0C) >> 2) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.hv_warn, QColor(Qt.gray))
-        elif (data[0] & 0x0C >> 2) == 1:
+        elif ((data[0] & 0x0C) >> 2) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.hv_warn, QColor(Qt.red))
-        elif (data[0] & 0x0C >> 2) == 2:
+        elif ((data[0] & 0x0C) >> 2) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.hv_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.hv_warn, QColor(Qt.yellow))
 
         # 单体欠压告警
-        if (data[0] & 0x30 >> 4) == 0:
+        if ((data[0] & 0x30) >> 4) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.under_cell_warn, QColor(Qt.gray))
-        elif (data[0] & 0x30 >> 4) == 1:
+        elif ((data[0] & 0x30) >> 4) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.under_cell_warn, QColor(Qt.red))
-        elif (data[0] & 0x30 >> 4) == 2:
+        elif ((data[0] & 0x30) >> 4) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.under_cell_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.under_cell_warn, QColor(Qt.yellow))
 
         # 总压欠压告警
-        if (data[0] & 0xC0 >> 6) == 0:
+        if ((data[0] & 0xC0) >> 6) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.lv_warn, QColor(Qt.gray))
-        elif (data[0] & 0xC0 >> 6) == 1:
+        elif ((data[0] & 0xC0) >> 6) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.lv_warn, QColor(Qt.red))
-        elif (data[0] & 0xC0 >> 6) == 2:
+        elif ((data[0] & 0xC0) >> 6) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.lv_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.lv_warn, QColor(Qt.yellow))
@@ -375,31 +377,31 @@ class BmsHomeControll:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_lt_warn, QColor(Qt.yellow))
 
         # 充电高温告警
-        if (data[1] & 0x0C >> 2) == 0:
+        if ((data[1] & 0x0C) >> 2) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_ht_warn, QColor(Qt.gray))
-        elif (data[1] & 0x0C >> 2) == 1:
+        elif ((data[1] & 0x0C) >> 2) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_ht_warn, QColor(Qt.red))
-        elif (data[1] & 0x0C >> 2) == 2:
+        elif ((data[1] & 0x0C) >> 2) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_ht_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_ht_warn, QColor(Qt.yellow))
 
         # 放电低温告警
-        if (data[1] & 0x30 >> 4) == 0:
+        if ((data[1] & 0x30) >> 4) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_lt_warn, QColor(Qt.gray))
-        elif (data[1] & 0x30 >> 4) == 1:
+        elif ((data[1] & 0x30) >> 4) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_lt_warn, QColor(Qt.red))
-        elif (data[1] & 0x30 >> 4) == 2:
+        elif ((data[1] & 0x30) >> 4) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_lt_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_lt_warn, QColor(Qt.yellow))
 
         # 放电高温告警
-        if (data[1] & 0xC0 >> 6) == 0:
+        if ((data[1] & 0xC0) >> 6) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_ht_warn, QColor(Qt.gray))
-        elif (data[1] & 0xC0 >> 6) == 1:
+        elif ((data[1] & 0xC0) >> 6) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_ht_warn, QColor(Qt.red))
-        elif (data[1] & 0xC0 >> 6) == 2:
+        elif ((data[1] & 0xC0) >> 6) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_ht_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_ht_warn, QColor(Qt.yellow))
@@ -416,31 +418,31 @@ class BmsHomeControll:
             set_edt_bg(self.bms_main_ctl._view.center_widget.chg_hcur_warn, QColor(Qt.yellow))
 
         # 放电过流告警
-        if (data[2] & 0x0C >> 2) == 0:
+        if ((data[2] & 0x0C) >> 2) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_hcur_warn, QColor(Qt.gray))
-        elif (data[2] & 0x0C >> 2) == 1:
+        elif ((data[2] & 0x0C) >> 2) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_hcur_warn, QColor(Qt.red))
-        elif (data[2] & 0x0C >> 2) == 2:
+        elif ((data[2] & 0x0C) >> 2) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_hcur_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.dchg_hcur_warn, QColor(Qt.yellow))
 
         # 单体压差告警
-        if (data[2] & 0x30 >> 4) == 0:
+        if ((data[2] & 0x30) >> 4) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_vol_warn, QColor(Qt.gray))
-        elif (data[2] & 0x30 >> 4) == 1:
+        elif ((data[2] & 0x30) >> 4) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_vol_warn, QColor(Qt.red))
-        elif (data[2] & 0x30 >> 4) == 2:
+        elif ((data[2] & 0x30) >> 4) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_vol_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_vol_warn, QColor(Qt.yellow))
 
         # 单体温差告警
-        if (data[2] & 0xC0 >> 6) == 0:
+        if ((data[2] & 0xC0) >> 6) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_temp_warn, QColor(Qt.gray))
-        elif (data[2] & 0xC0 >> 6) == 1:
+        elif ((data[2] & 0xC0) >> 6) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_temp_warn, QColor(Qt.red))
-        elif (data[2] & 0xC0 >> 6) == 2:
+        elif ((data[2] & 0xC0) >> 6) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_temp_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.diff_cell_temp_warn, QColor(Qt.yellow))
@@ -457,31 +459,31 @@ class BmsHomeControll:
             set_edt_bg(self.bms_main_ctl._view.center_widget.against_warn, QColor(Qt.yellow))
 
         # 正极绝缘过低报警
-        if (data[3] & 0x0C >> 2) == 0:
+        if ((data[3] & 0x0C) >> 2) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.gray))
-        elif (data[3] & 0x0C >> 2) == 1:
+        elif ((data[3] & 0x0C) >> 2) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.red))
-        elif (data[3] & 0x0C >> 2) == 2:
+        elif ((data[3] & 0x0C) >> 2) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.yellow))
 
         # 负极绝缘过低报警
-        if (data[3] & 0x30 >> 4) == 0:
+        if ((data[3] & 0x30) >> 4) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.gray))
-        elif (data[3] & 0x30 >> 4) == 1:
+        elif ((data[3] & 0x30) >> 4) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.red))
-        elif (data[3] & 0x30 >> 4) == 2:
+        elif ((data[3] & 0x30) >> 4) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.p_against_warn, QColor(Qt.yellow))
 
         # 极柱温度过高
-        if (data[3] & 0xC0 >> 6) == 0:
+        if ((data[3] & 0xC0) >> 6) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.jz_ht_warn, QColor(Qt.gray))
-        elif (data[3] & 0xC0 >> 6) == 1:
+        elif ((data[3] & 0xC0) >> 6) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.jz_ht_warn, QColor(Qt.red))
-        elif (data[3] & 0xC0 >> 6) == 2:
+        elif ((data[3] & 0xC0) >> 6) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.jz_ht_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.jz_ht_warn, QColor(Qt.yellow))
@@ -653,11 +655,11 @@ class BmsHomeControll:
             set_edt_bg(self.bms_main_ctl._view.center_widget.ccu_ht_warn, QColor(Qt.red))
 
         # MOS温度过高故障
-        if (data[7] & 0x18 >> 3) == 0:
+        if ((data[7] & 0x18) >> 3) == 0:
             set_edt_bg(self.bms_main_ctl._view.center_widget.mos_ht_warn, QColor(Qt.gray))
-        elif (data[7] & 0x18 >> 3) == 1:
+        elif ((data[7] & 0x18) >> 3) == 1:
             set_edt_bg(self.bms_main_ctl._view.center_widget.mos_ht_warn, QColor(Qt.red))
-        elif (data[2] & 0x18 >> 3) == 2:
+        elif ((data[2] & 0x18) >> 3) == 2:
             set_edt_bg(self.bms_main_ctl._view.center_widget.mos_ht_warn, QColor(Qt.magenta))
         else:
             set_edt_bg(self.bms_main_ctl._view.center_widget.mos_ht_warn, QColor(Qt.yellow))
@@ -1262,6 +1264,39 @@ class BmsHomeControll:
         text = dataTime.toString("yyyy-MM-dd HH:mm:ss")
         self._model.now_time = text
 
+        if SD.RECORD_SAVE:
+            record_data = []
+            record_data.append(self._model.now_time)
+            record_data.append(self._model.ccu_id)
+            record_data.append(self._model.fault_level)
+            record_data.append(self._model.volt)
+            record_data.append(self._model.cur)
+            record_data.append(self._model.soc)
+            record_data.append(self._model.soh)
+            record_data.append(self._model.avg_cell_volt)
+            record_data.append(self._model.max_cell_volt_id)
+            record_data.append(self._model.max_cell_volt)
+            record_data.append(self._model.min_cell_volt_id)
+            record_data.append(self._model.min_cell_volt)
+            record_data.append(self._model.avg_cell_temp)
+            record_data.append(self._model.max_cell_temp_id)
+            record_data.append(self._model.max_cell_temp)
+            record_data.append(self._model.min_cell_temp_id)
+            record_data.append(self._model.min_cell_temp)
+            record_data.append(self._model.p)
+            record_data.append(self._model.n)
+            record_data.append(self._model.pre)
+            record_data.append(self._model.dlq)
+            record_data.append(self._model.gjd_first)
+            record_data.append(self._model.gjd_second)
+            record_data.append(self._model.max_soc)
+            record_data.append(self._model.min_soc)
+            record_data.append(self._model.avg_soc)  
+            record_data.extend(self._model.cell_volt)
+            record_data.extend(self._model.cell_temp)
+            with open(SD.RECORD_NAME, 'a') as csvfile:
+                writer = csv.writer(csvfile, dialect='excel', lineterminator='\n')
+                writer.writerow(record_data)
     
     def run(self):
         self._view.show()

+ 31 - 3
controller/bms_record_ctl.py

@@ -2,15 +2,43 @@
 # -*- encoding: utf-8 -*-
 
 
+import csv
 from utils.delay import m_delay
 from utils.globalvar import SD
 from widget.bms_record import Win_Record
+from model.record_model import RecordModel
 
 
 class BmsRecordControll:
     def __init__(self):
         self._view = Win_Record()
-    #     self.init()
+        self._model = RecordModel()
+        self.init()
 
-    # def init(self):
-        
+    def init(self):
+        self._view.can_record_start_signal.connect(self._save)
+        self._view.can_record_stop_signal.connect(self._stop)
+        
+    def _save(self):
+        SD.RECORD_NAME, ok = self._view.save_csv()
+        if SD.RECORD_NAME == "":
+            return
+        self._view.save_record.setDisabled(True)
+        self._view.stop_record.setDisabled(False)
+        header =  ["时间戳", "簇号", "故障等级", "簇电压", "簇电流", "簇SOC", "簇SOH", "簇单体V平均", "簇单体Vmax单体号", "簇单体Vmax", "簇单体Vmin单体号", "簇单体Vmin", "簇单体T平均", "簇单体Tmax单体号", "簇单体Tmax", "簇单体Tmin单体号", "簇单体Tmin", "主正接触器", "主负接触器", "预充接触器", "断路器", "干接点1", "干接点2", "最高单体SOC", "最低单体SOC", "平均单体SOC"]
+        header_1=  ["V" + str(i + 1) for i in range(396)]
+        header_2 =  ["T" + str(i + 1) for i in range(360)]
+        header.extend(header_1)
+        header.extend(header_2)
+        if ok:
+            with open(SD.RECORD_NAME, 'w') as csvfile:
+                writer = csv.writer(csvfile, dialect='excel', lineterminator='\n')
+                writer.writerow(header)
+                SD.RECORD_SAVE = 1
+        else:
+            return
+
+    def _stop(self):
+        self._view.save_record.setDisabled(False)
+        self._view.stop_record.setDisabled(True)
+        SD.RECORD_SAVE = 0

+ 8 - 1
model/record_model.py

@@ -1,7 +1,14 @@
 #!/usr/bin/env python
 # -*- encoding: utf-8 -*-
 
-class RecordModel:
+class Singleton(object):
+    def __new__(cls, *args, **kw):
+        if not hasattr(cls, '_instance'):
+            orig = super(Singleton, cls)
+            cls._instance = orig.__new__(cls, *args, **kw)
+        return cls._instance
+
+class RecordModel(Singleton):
     def __init__(self):
         # 时间戳	簇号	故障等级	簇电压	簇电流	簇SOC	簇SOH	
         self.__now_time = ""

+ 1 - 0
ui/record.py

@@ -38,6 +38,7 @@ class Record(MyFrame):
         self.save_record = QtWidgets.QPushButton("运行数据保存")
         self.record_table_layout.addWidget(self.save_record)
         self.stop_record = QtWidgets.QPushButton("停止保存运行数据")
+        self.stop_record.setDisabled(True)
         self.record_table_layout.addWidget(self.stop_record)
 
         spacerItem1 = QtWidgets.QSpacerItem(0, 40, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)

+ 41 - 3
utils/can.py

@@ -9,11 +9,13 @@
 '''
 
 
+import csv
+import os
 from ctypes.util import find_library
 from sys import platform
 import ctypes
 from ctypes import c_ubyte, c_uint, c_ushort, c_char
-from utils.log_signal import LogSignal
+from utils.qt import QDateTime
 from utils.resource import resource_path, resource_path_extend
 
 if platform == "win64":
@@ -427,6 +429,8 @@ class TPCANTimestamp (ctypes.Structure):
 class MessageDeal:
     def __init__(self):
         self.can_connect = 0
+        self.index = 0
+        self.can_file  = ""
 
     def set_can_board(self, can_type, canIndex, canChannel, canBaudrate):
         if can_type == 0:
@@ -450,6 +454,9 @@ class MessageDeal:
                 ret = PCAN.CAN_Initialize(self.canType, PCAN_BAUD_500K ,0,0,0)
             if TPCANStatus(ret) != PCAN_ERROR_OK:
                 return False
+            dataTime = QDateTime.currentDateTime()
+            time_str = dataTime.toString("yyMMddHHmmss") + '.csv'
+            self.can_file  = resource_path(time_str)
             return True
         else:
             if (STATUS_OK != ZLGCAN.VCI_OpenDevice(self.canType, self.canChannel, self.canChannel)):
@@ -463,7 +470,9 @@ class MessageDeal:
 
             if (STATUS_OK != ZLGCAN.VCI_StartCAN(self.canType, self.canIndex, self.canChannel)):
                 return False
-            
+            dataTime = QDateTime.currentDateTime()
+            time_str = dataTime.toString("yyMMddHHmmss") + '.csv'
+            self.can_file  = resource_path(time_str)  
             return True
 
     def get_undeal_number(self):
@@ -478,6 +487,8 @@ class MessageDeal:
             timestamp = TPCANTimestamp()
             res = PCAN.CAN_Read(self.canType,ctypes.byref(msg),ctypes.byref(timestamp))
             if (res != PCAN_ERROR_QRCVEMPTY):
+                self.format_msg_data(self.index, msg.ID, msg.LEN, msg.DATA, 1)
+                self.index += 1
                 return 1, msg.ID, msg.DATA
             else:
                 return 0, 0, 0
@@ -486,6 +497,8 @@ class MessageDeal:
             ret = ZLGCAN.VCI_Receive(self.canType, self.canIndex, self.canChannel, ctypes.byref(objs), number, 10)
             if (ret > 0):
                 for i in range(0, ret):
+                    self.format_msg_data(self.index, objs[i].ID, objs[i].DataLen, objs[i].Data, 1)
+                    self.index += 1
                     return 1, objs[i].ID, objs[i].Data
             else:
                 return 0, 0, 0      
@@ -508,6 +521,8 @@ class MessageDeal:
             if (TPCANStatus(send_status) != PCAN_ERROR_OK):
                 return False
             else:
+                self.format_msg_data(self.index, msg.ID, msg.LEN, msg.DATA, 0)
+                self.index += 1
                 return True
         else:
             vci_can_obj = VCI_CAN_OBJ()
@@ -528,12 +543,13 @@ class MessageDeal:
             if ret != STATUS_OK:
                 return False
             else:
+                self.format_msg_data(self.index, vci_can_obj.ID, vci_can_obj.DataLen, vci_can_obj.Data, 0)
+                self.index += 1
                 return True
 
     def read_err_info(self):
         errInfo = PVCI_ERR_INFO(0, ubyte_3array(0, 0, 0), 0)
         ZLGCAN.VCI_ReadErrInfo(self.canType, self.canIndex, self.canChannel, ctypes.byref(errInfo))
-        LogSignal.print_log_signal().log_emit(errInfo.ErrorCode, errInfo.PassiveErrData[0], errInfo.PassiveErrData[1], errInfo.PassiveErrData[2], errInfo.ArLostErrData)
 
     def close_can(self):
         if self.can_connect:
@@ -541,3 +557,25 @@ class MessageDeal:
         else:
             ZLGCAN.VCI_ClearBuffer(self.canType, self.canIndex, self.canChannel)
             ZLGCAN.VCI_CloseDevice(self.canType, self.canIndex)
+
+    def format_msg_data(self, index, id, len, can_data, received):
+        data = []
+        data.append('{0:0>7}'.format(index))
+        data.append('接收' if received else '发送')
+        data.append(str.upper('0x' + '{:0>8x}'.format(id)).replace('X', 'x'))
+        data.append(len)
+        data.append(' '.join(['{:0>2x}'.format(a) for a in list(can_data)]))
+        if os.path.exists(self.can_file):    
+            with open(self.can_file, 'a') as csvfile:
+                writer = csv.writer(csvfile, dialect='excel', lineterminator='\n')
+                writer.writerow(data)
+        else:
+            dataTime = QDateTime.currentDateTime()
+            time_str = dataTime.toString("yyMMddHHmmss") + '.csv'
+            self.can_file  = resource_path(time_str)
+            with open(self.can_file, 'w') as csvfile:
+                writer = csv.writer(csvfile, dialect='excel', lineterminator='\n')
+                header = ["序号", "收/发", "CAN ID", "CAN LEN", "CAN DATA"]
+                writer.writerow(header)
+                writer.writerow(data)
+            

+ 5 - 0
utils/globalvar.py

@@ -40,3 +40,8 @@ class SD:
 
     START_RUN = False
 
+    FILE_PATH = "./"
+
+    RECORD_SAVE = 0
+
+    RECORD_NAME = ""

+ 16 - 2
widget/bms_record.py

@@ -6,11 +6,25 @@ from ui.record import Record
 from ui.own.frame_theme import MyFrame
 from ui.own.my_chart_view import MyChartView
 from utils.globalvar import SD
-from utils.qt import QLineSeries, QChart, QValueAxis, QHBoxLayout, QPainter, Qt, Signal, Slot, QLegendMarker
+from utils.qt import QFileDialog, QChart, QValueAxis, QHBoxLayout, QPainter, Qt, Signal, Slot, QLegendMarker
 
 
 class Win_Record(Record, MyFrame):
+    can_record_start_signal = Signal()
+    can_record_stop_signal = Signal()
 
     def __init__(self, parent=None):
         super(Win_Record, self).__init__(parent)
-        self.setupUi(self)
+        self.setupUi(self)
+
+        self.save_record.clicked.connect(self._start)
+        self.stop_record.clicked.connect(self._stop)
+
+    def _start(self):
+        self.can_record_start_signal.emit()
+
+    def _stop(self):
+        self.can_record_stop_signal.emit()
+
+    def save_csv(self):
+        return QFileDialog.getSaveFileName(self, "保存文件" if SD.SYSTEM_LANGUAGE == 0 else "Save File", SD.FILE_PATH, 'CSV(*.csv)')