diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..1519f10b --- /dev/null +++ b/.flake8 @@ -0,0 +1,23 @@ +[flake8] +max-line-length = 120 +exclude = + ./etc/dbus-serialbattery/ant.py, + ./etc/dbus-serialbattery/battery.py, + ./etc/dbus-serialbattery/battery_template.py, + ./etc/dbus-serialbattery/daly.py, + ./etc/dbus-serialbattery/dbus-serialbattery.py, + ./etc/dbus-serialbattery/dbushelper.py, + ./etc/dbus-serialbattery/ecs.py, + ./etc/dbus-serialbattery/lifepower.py, + ./etc/dbus-serialbattery/lltjbd.py, + ./etc/dbus-serialbattery/minimalmodbus.py, + ./etc/dbus-serialbattery/mnb.py, + ./etc/dbus-serialbattery/renogy.py, + ./etc/dbus-serialbattery/revov.py, + ./etc/dbus-serialbattery/sinowealth.py, + ./etc/dbus-serialbattery/test_max17853.py, + ./etc/dbus-serialbattery/util_max17853.py, + venv +extend-ignore: + # E203 whitespace fefore ':' conflicts with black code formatting. Will be ignored in flake8 + E203 \ No newline at end of file diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml new file mode 100644 index 00000000..1e308870 --- /dev/null +++ b/.github/workflows/analyse.yml @@ -0,0 +1,39 @@ +name: "CodeChecks" + +on: + push: + branches: + - '**' + +jobs: + CodeQL: + name: Analyze Using GitHub CodeQL + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: python + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + lint: + name: Check Code Formatting + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Execute black lint check + uses: psf/black@stable + + - name: Set up Python environment + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: flake8 Lint + uses: py-actions/flake8@v2 \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index ff5ba6e0..00000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,67 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '29 1 * * 6' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/conf/serial-starter.d b/conf/serial-starter.d index 37e4c883..e25054dc 100644 --- a/conf/serial-starter.d +++ b/conf/serial-starter.d @@ -1,3 +1,3 @@ -service sbattery dbus-serialbattery +service sbattery dbus-serialbattery alias default gps:vedirect:sbattery alias rs485 cgwacs:fzsonick:imt:modbus:sbattery diff --git a/etc/dbus-serialbattery/ant.py b/etc/dbus-serialbattery/ant.py index 02d0d30d..215a692f 100644 --- a/etc/dbus-serialbattery/ant.py +++ b/etc/dbus-serialbattery/ant.py @@ -5,7 +5,6 @@ class Ant(Battery): - def __init__(self, port, baud): super(Ant, self).__init__(port, baud) self.type = self.BATTERYTYPE @@ -54,55 +53,77 @@ def read_status_data(self): if status_data is False: return False - voltage = unpack_from('>H', status_data, 4) - self.voltage = voltage[0]*0.1 - current, self.soc = unpack_from('>lB', status_data, 70) + voltage = unpack_from(">H", status_data, 4) + self.voltage = voltage[0] * 0.1 + current, self.soc = unpack_from(">lB", status_data, 70) self.current = 0.0 if current == 0 else current / -10 - self.cell_count = unpack_from('>b', status_data, 123)[0] + self.cell_count = unpack_from(">b", status_data, 123)[0] self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count - cell_max_no, cell_max_voltage, cell_min_no, cell_min_voltage = unpack_from('>bhbh', status_data, 115) + cell_max_no, cell_max_voltage, cell_min_no, cell_min_voltage = unpack_from( + ">bhbh", status_data, 115 + ) self.cell_max_no = cell_max_no - 1 self.cell_min_no = cell_min_no - 1 self.cell_max_voltage = cell_max_voltage / 1000 self.cell_min_voltage = cell_min_voltage / 1000 - - capacity = unpack_from('>L', status_data, 75) + + capacity = unpack_from(">L", status_data, 75) self.capacity = capacity[0] / 1000000 - capacity_remain = unpack_from('>L', status_data, 79) + capacity_remain = unpack_from(">L", status_data, 79) self.capacity_remain = capacity_remain[0] / 1000000 - - total_ah_drawn = unpack_from('>L', status_data, 83) - self.total_ah_drawn = total_ah_drawn[0] / 1000 + + total_ah_drawn = unpack_from(">L", status_data, 83) + self.total_ah_drawn = total_ah_drawn[0] / 1000 self.cycles = self.total_ah_drawn / self.capacity - - self.charge_fet, self.discharge_fet, self.balancing = unpack_from('>bbb',status_data, 103) - self.temp1, self.temp2 = unpack_from('>bxb',status_data, 96) + self.charge_fet, self.discharge_fet, self.balancing = unpack_from( + ">bbb", status_data, 103 + ) + + self.temp1, self.temp2 = unpack_from(">bxb", status_data, 96) self.hardware_version = "ANT BMS " + str(self.cell_count) + " cells" - + # Alarms - self.protection.voltage_high = 2 if self.charge_fet==2 else 0 - self.protection.voltage_low = 2 if self.discharge_fet==2 or self.discharge_fet==5 else 0 - self.protection.voltage_cell_low = 2 if self.cell_min_voltage < MIN_CELL_VOLTAGE - 0.1 else 1 if self.cell_min_voltage < MIN_CELL_VOLTAGE else 0 - self.protection.temp_high_charge = 1 if self.charge_fet==3 or self.charge_fet==6 else 0 - self.protection.temp_high_discharge = 1 if self.discharge_fet==7 or self.discharge_fet==6 else 0 - self.protection.current_over = 2 if self.charge_fet==3 else 0 - self.protection.current_under = 2 if self.discharge_fet==3 else 0 - + self.protection.voltage_high = 2 if self.charge_fet == 2 else 0 + self.protection.voltage_low = ( + 2 if self.discharge_fet == 2 or self.discharge_fet == 5 else 0 + ) + self.protection.voltage_cell_low = ( + 2 + if self.cell_min_voltage < MIN_CELL_VOLTAGE - 0.1 + else 1 + if self.cell_min_voltage < MIN_CELL_VOLTAGE + else 0 + ) + self.protection.temp_high_charge = ( + 1 if self.charge_fet == 3 or self.charge_fet == 6 else 0 + ) + self.protection.temp_high_discharge = ( + 1 if self.discharge_fet == 7 or self.discharge_fet == 6 else 0 + ) + self.protection.current_over = 2 if self.charge_fet == 3 else 0 + self.protection.current_under = 2 if self.discharge_fet == 3 else 0 + return True - - def get_balancing(self): + + def get_balancing(self): return 1 if self.balancing or self.balancing == 2 else 0 def read_serial_data_ant(self, command): # use the read_serial_data() function to read the data and then do BMS spesific checks (crc, start bytes, etc) - data = read_serial_data(command, self.port, self.baud_rate, - self.LENGTH_POS, self.LENGTH_CHECK, self.LENGTH_FIXED) + data = read_serial_data( + command, + self.port, + self.baud_rate, + self.LENGTH_POS, + self.LENGTH_CHECK, + self.LENGTH_FIXED, + ) if data is False: logger.error(">>> ERROR: Incorrect Data") return False diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d7b035c5..cdd700ec 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -4,6 +4,7 @@ from datetime import timedelta from time import time + class Protection(object): # 2 = Alarm, 1 = Warning, 0 = Normal def __init__(self): @@ -31,12 +32,11 @@ def __init__(self, balance): class Battery(object): - def __init__(self, port, baud): self.port = port self.baud_rate = baud - self.role = 'battery' - self.type = 'Generic' + self.role = "battery" + self.type = "Generic" self.poll_interval = 1000 self.online = True @@ -120,28 +120,38 @@ def manage_charge_voltage_linear(self): if cv >= PENALTY_AT_CELL_VOLTAGE[0]: foundHighCellVoltage = True - penaltySum += calcLinearRelationship(cv, PENALTY_AT_CELL_VOLTAGE, PENALTY_BATTERY_VOLTAGE) - self.voltage = currentBatteryVoltage # for testing - + penaltySum += calcLinearRelationship( + cv, PENALTY_AT_CELL_VOLTAGE, PENALTY_BATTERY_VOLTAGE + ) + self.voltage = currentBatteryVoltage # for testing + if foundHighCellVoltage: # Keep penalty above min battery voltage - self.control_voltage = max(currentBatteryVoltage - penaltySum, MIN_CELL_VOLTAGE * self.cell_count) + self.control_voltage = max( + currentBatteryVoltage - penaltySum, MIN_CELL_VOLTAGE * self.cell_count + ) else: self.control_voltage = FLOAT_CELL_VOLTAGE * self.cell_count def manage_charge_voltage_step(self): voltageSum = 0 - if (CVCM_ENABLE): + if CVCM_ENABLE: for i in range(self.cell_count): voltage = self.get_cell_voltage(i) if voltage: - voltageSum+=voltage + voltageSum += voltage if None == self.max_voltage_start_time: - if MAX_CELL_VOLTAGE * self.cell_count <= voltageSum and True == self.allow_max_voltage: + if ( + MAX_CELL_VOLTAGE * self.cell_count <= voltageSum + and True == self.allow_max_voltage + ): self.max_voltage_start_time = time() else: - if SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc and not self.allow_max_voltage: + if ( + SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc + and not self.allow_max_voltage + ): self.allow_max_voltage = True else: tDiff = time() - self.max_voltage_start_time @@ -151,7 +161,9 @@ def manage_charge_voltage_step(self): if self.allow_max_voltage: # Keep penalty above min battery voltage - self.control_voltage = max(MAX_CELL_VOLTAGE * self.cell_count, MIN_CELL_VOLTAGE * self.cell_count) + self.control_voltage = max( + MAX_CELL_VOLTAGE * self.cell_count, MIN_CELL_VOLTAGE * self.cell_count + ) else: self.control_voltage = FLOAT_CELL_VOLTAGE * self.cell_count @@ -164,7 +176,7 @@ def manage_charge_current(self): charge_limits.append(self.calcMaxChargeCurrentReferringToCellVoltage()) if CCCM_T_ENABLE: charge_limits.append(self.calcMaxChargeCurrentReferringToTemperature()) - + self.control_charge_current = min(charge_limits) if self.control_charge_current == 0: @@ -177,10 +189,14 @@ def manage_charge_current(self): if DCCM_SOC_ENABLE: discharge_limits.append(self.calcMaxDischargeCurrentReferringToSoc()) if DCCM_CV_ENABLE: - discharge_limits.append(self.calcMaxDischargeCurrentReferringToCellVoltage()) + discharge_limits.append( + self.calcMaxDischargeCurrentReferringToCellVoltage() + ) if DCCM_T_ENABLE: - discharge_limits.append(self.calcMaxDischargeCurrentReferringToTemperature()) - + discharge_limits.append( + self.calcMaxDischargeCurrentReferringToTemperature() + ) + self.control_discharge_current = min(discharge_limits) if self.control_discharge_current == 0: @@ -191,20 +207,34 @@ def manage_charge_current(self): def calcMaxChargeCurrentReferringToCellVoltage(self): try: if LINEAR_LIMITATION_ENABLE: - return calcLinearRelationship(self.get_max_cell_voltage(), - CELL_VOLTAGES_WHILE_CHARGING, MAX_CHARGE_CURRENT_CV) - return calcStepRelationship(self.get_max_cell_voltage(), - CELL_VOLTAGES_WHILE_CHARGING, MAX_CHARGE_CURRENT_CV, False) + return calcLinearRelationship( + self.get_max_cell_voltage(), + CELL_VOLTAGES_WHILE_CHARGING, + MAX_CHARGE_CURRENT_CV, + ) + return calcStepRelationship( + self.get_max_cell_voltage(), + CELL_VOLTAGES_WHILE_CHARGING, + MAX_CHARGE_CURRENT_CV, + False, + ) except: return self.max_battery_charge_current def calcMaxDischargeCurrentReferringToCellVoltage(self): try: if LINEAR_LIMITATION_ENABLE: - return calcLinearRelationship(self.get_min_cell_voltage(), - CELL_VOLTAGES_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_CV) - return calcStepRelationship(self.get_min_cell_voltage(), - CELL_VOLTAGES_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_CV, True) + return calcLinearRelationship( + self.get_min_cell_voltage(), + CELL_VOLTAGES_WHILE_DISCHARGING, + MAX_DISCHARGE_CURRENT_CV, + ) + return calcStepRelationship( + self.get_min_cell_voltage(), + CELL_VOLTAGES_WHILE_DISCHARGING, + MAX_DISCHARGE_CURRENT_CV, + True, + ) except: return self.max_battery_charge_current @@ -216,11 +246,18 @@ def calcMaxChargeCurrentReferringToTemperature(self): for key, currentMaxTemperature in temps.items(): if LINEAR_LIMITATION_ENABLE: - temps[key] = calcLinearRelationship(currentMaxTemperature, - TEMPERATURE_LIMITS_WHILE_CHARGING, MAX_CHARGE_CURRENT_T) - else: - temps[key] = calcStepRelationship(currentMaxTemperature, - TEMPERATURE_LIMITS_WHILE_CHARGING, MAX_CHARGE_CURRENT_T, False) + temps[key] = calcLinearRelationship( + currentMaxTemperature, + TEMPERATURE_LIMITS_WHILE_CHARGING, + MAX_CHARGE_CURRENT_T, + ) + else: + temps[key] = calcStepRelationship( + currentMaxTemperature, + TEMPERATURE_LIMITS_WHILE_CHARGING, + MAX_CHARGE_CURRENT_T, + False, + ) return min(temps[0], temps[1]) @@ -232,11 +269,18 @@ def calcMaxDischargeCurrentReferringToTemperature(self): for key, currentMaxTemperature in temps.items(): if LINEAR_LIMITATION_ENABLE: - temps[key] = calcLinearRelationship(currentMaxTemperature, - TEMPERATURE_LIMITS_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_T) - else: - temps[key] = calcStepRelationship(currentMaxTemperature, - TEMPERATURE_LIMITS_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_T, True) + temps[key] = calcLinearRelationship( + currentMaxTemperature, + TEMPERATURE_LIMITS_WHILE_DISCHARGING, + MAX_DISCHARGE_CURRENT_T, + ) + else: + temps[key] = calcStepRelationship( + currentMaxTemperature, + TEMPERATURE_LIMITS_WHILE_DISCHARGING, + MAX_DISCHARGE_CURRENT_T, + True, + ) return min(temps[0], temps[1]) @@ -244,10 +288,19 @@ def calcMaxChargeCurrentReferringToSoc(self): try: # Create value list. Will more this to the settings object SOC_WHILE_CHARGING = [100, CC_SOC_LIMIT1, CC_SOC_LIMIT2, CC_SOC_LIMIT3] - MAX_CHARGE_CURRENT_SOC = [CC_CURRENT_LIMIT1,CC_CURRENT_LIMIT2,CC_CURRENT_LIMIT3,MAX_BATTERY_CHARGE_CURRENT] + MAX_CHARGE_CURRENT_SOC = [ + CC_CURRENT_LIMIT1, + CC_CURRENT_LIMIT2, + CC_CURRENT_LIMIT3, + MAX_BATTERY_CHARGE_CURRENT, + ] if LINEAR_LIMITATION_ENABLE: - return calcLinearRelationship(self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC) - return calcStepRelationship(self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC, True) + return calcLinearRelationship( + self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC + ) + return calcStepRelationship( + self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC, True + ) except: return self.max_battery_charge_current @@ -255,21 +308,33 @@ def calcMaxDischargeCurrentReferringToSoc(self): try: # Create value list. Will more this to the settings object SOC_WHILE_DISCHARGING = [DC_SOC_LIMIT3, DC_SOC_LIMIT2, DC_SOC_LIMIT1] - MAX_DISCHARGE_CURRENT_SOC = [MAX_BATTERY_DISCHARGE_CURRENT,DC_CURRENT_LIMIT3,DC_CURRENT_LIMIT2,DC_CURRENT_LIMIT1] + MAX_DISCHARGE_CURRENT_SOC = [ + MAX_BATTERY_DISCHARGE_CURRENT, + DC_CURRENT_LIMIT3, + DC_CURRENT_LIMIT2, + DC_CURRENT_LIMIT1, + ] if LINEAR_LIMITATION_ENABLE: - return calcLinearRelationship(self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC) - return calcStepRelationship(self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC, True) + return calcLinearRelationship( + self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC + ) + return calcStepRelationship( + self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC, True + ) except: return self.max_battery_charge_current def get_min_cell(self): min_voltage = 9999 min_cell = None - if len(self.cells) == 0 and hasattr(self, 'cell_min_no'): + if len(self.cells) == 0 and hasattr(self, "cell_min_no"): return self.cell_min_no for c in range(min(len(self.cells), self.cell_count)): - if self.cells[c].voltage is not None and min_voltage > self.cells[c].voltage: + if ( + self.cells[c].voltage is not None + and min_voltage > self.cells[c].voltage + ): min_voltage = self.cells[c].voltage min_cell = c return min_cell @@ -277,33 +342,36 @@ def get_min_cell(self): def get_max_cell(self): max_voltage = 0 max_cell = None - if len(self.cells) == 0 and hasattr(self, 'cell_max_no'): + if len(self.cells) == 0 and hasattr(self, "cell_max_no"): return self.cell_max_no for c in range(min(len(self.cells), self.cell_count)): - if self.cells[c].voltage is not None and max_voltage < self.cells[c].voltage: + if ( + self.cells[c].voltage is not None + and max_voltage < self.cells[c].voltage + ): max_voltage = self.cells[c].voltage max_cell = c return max_cell def get_min_cell_desc(self): cell_no = self.get_min_cell() - return cell_no if cell_no is None else 'C' + str(cell_no + 1) + return cell_no if cell_no is None else "C" + str(cell_no + 1) def get_max_cell_desc(self): cell_no = self.get_max_cell() - return cell_no if cell_no is None else 'C' + str(cell_no + 1) + return cell_no if cell_no is None else "C" + str(cell_no + 1) def get_cell_voltage(self, idx): - if idx>=min(len(self.cells), self.cell_count): - return None + if idx >= min(len(self.cells), self.cell_count): + return None return self.cells[idx].voltage def get_cell_balancing(self, idx): - if idx>=min(len(self.cells), self.cell_count): - return None + if idx >= min(len(self.cells), self.cell_count): + return None if self.cells[idx].balance is not None and self.cells[idx].balance: - return 1 + return 1 return 0 def get_capacity_remain(self): @@ -315,71 +383,93 @@ def get_capacity_remain(self): def get_timetosoc(self, socnum, crntPrctPerSec): if self.current > 0: - diffSoc = (socnum - self.soc) + diffSoc = socnum - self.soc else: - diffSoc = (self.soc - socnum) + diffSoc = self.soc - socnum ttgStr = None if self.soc != socnum and (diffSoc > 0 or TIME_TO_SOC_INC_FROM is True): secondstogo = int(diffSoc / crntPrctPerSec) ttgStr = "" - if (TIME_TO_SOC_VALUE_TYPE & 1): + if TIME_TO_SOC_VALUE_TYPE & 1: ttgStr += str(secondstogo) - if (TIME_TO_SOC_VALUE_TYPE & 2): + if TIME_TO_SOC_VALUE_TYPE & 2: ttgStr += " [" - if (TIME_TO_SOC_VALUE_TYPE & 2): + if TIME_TO_SOC_VALUE_TYPE & 2: ttgStr += str(timedelta(seconds=secondstogo)) - if (TIME_TO_SOC_VALUE_TYPE & 1): + if TIME_TO_SOC_VALUE_TYPE & 1: ttgStr += "]" return ttgStr - def get_min_cell_voltage(self): min_voltage = None - if hasattr(self, 'cell_min_voltage'): + if hasattr(self, "cell_min_voltage"): min_voltage = self.cell_min_voltage if min_voltage is None: try: - min_voltage = min(c.voltage for c in self.cells if c.voltage is not None) + min_voltage = min( + c.voltage for c in self.cells if c.voltage is not None + ) except ValueError: pass return min_voltage def get_max_cell_voltage(self): max_voltage = None - if hasattr(self, 'cell_max_voltage'): + if hasattr(self, "cell_max_voltage"): max_voltage = self.cell_max_voltage if max_voltage is None: try: - max_voltage = max(c.voltage for c in self.cells if c.voltage is not None) + max_voltage = max( + c.voltage for c in self.cells if c.voltage is not None + ) except ValueError: pass return max_voltage def get_midvoltage(self): - if not MIDPOINT_ENABLE or self.cell_count is None or self.cell_count == 0 or self.cell_count < 4 or len(self.cells) != self.cell_count: + if ( + not MIDPOINT_ENABLE + or self.cell_count is None + or self.cell_count == 0 + or self.cell_count < 4 + or len(self.cells) != self.cell_count + ): return None, None - halfcount = int(math.floor(self.cell_count/2)) + halfcount = int(math.floor(self.cell_count / 2)) half1voltage = 0 half2voltage = 0 try: - half1voltage = sum(c.voltage for c in self.cells[:halfcount] if c.voltage is not None) - half2voltage = sum(c.voltage for c in self.cells[halfcount:halfcount*2] if c.voltage is not None) + half1voltage = sum( + c.voltage for c in self.cells[:halfcount] if c.voltage is not None + ) + half2voltage = sum( + c.voltage + for c in self.cells[halfcount : halfcount * 2] + if c.voltage is not None + ) except ValueError: pass try: # handle uneven cells by giving half the voltage of the last cell to half1 and half2 - extra = 0 if (2*halfcount == self.cell_count) else self.cells[self.cell_count-1].voltage/2 + extra = ( + 0 + if (2 * halfcount == self.cell_count) + else self.cells[self.cell_count - 1].voltage / 2 + ) # get the midpoint of the battery - midpoint = (half1voltage + half2voltage)/2 + extra - return midpoint, (half2voltage-half1voltage)/(half2voltage+half1voltage)*100 + midpoint = (half1voltage + half2voltage) / 2 + extra + return ( + midpoint, + (half2voltage - half1voltage) / (half2voltage + half1voltage) * 100, + ) except ValueError: return None, None @@ -393,9 +483,9 @@ def get_temp(self): if self.temp1 is not None and self.temp2 is not None: return round((float(self.temp1) + float(self.temp2)) / 2, 2) if self.temp1 is not None and self.temp2 is None: - return round(float(self.temp1) , 2) + return round(float(self.temp1), 2) if self.temp1 is None and self.temp2 is not None: - return round(float(self.temp2) , 2) + return round(float(self.temp2), 2) else: return None @@ -433,14 +523,18 @@ def log_cell_data(self): def log_settings(self): - logger.info(f'Battery {self.type} connected to dbus from {self.port}') - logger.info(f'=== Settings ===') + logger.info(f"Battery {self.type} connected to dbus from {self.port}") + logger.info(f"=== Settings ===") cell_counter = len(self.cells) - logger.info(f'> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%') - logger.info(f'> Cell count {self.cell_count} | cells populated {cell_counter}') - logger.info(f'> CCCM SOC {CCCM_SOC_ENABLE} | DCCM SOC {DCCM_SOC_ENABLE}') - logger.info(f'> CCCM CV {CCCM_CV_ENABLE} | DCCM CV {DCCM_CV_ENABLE}') - logger.info(f'> CCCM T {CCCM_T_ENABLE} | DCCM T {DCCM_T_ENABLE}') - logger.info(f'> MIN_CELL_VOLTAGE {MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {MAX_CELL_VOLTAGE}V') - - return \ No newline at end of file + logger.info( + f"> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%" + ) + logger.info(f"> Cell count {self.cell_count} | cells populated {cell_counter}") + logger.info(f"> CCCM SOC {CCCM_SOC_ENABLE} | DCCM SOC {DCCM_SOC_ENABLE}") + logger.info(f"> CCCM CV {CCCM_CV_ENABLE} | DCCM CV {DCCM_CV_ENABLE}") + logger.info(f"> CCCM T {CCCM_T_ENABLE} | DCCM T {DCCM_T_ENABLE}") + logger.info( + f"> MIN_CELL_VOLTAGE {MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {MAX_CELL_VOLTAGE}V" + ) + + return diff --git a/etc/dbus-serialbattery/battery_template.py b/etc/dbus-serialbattery/battery_template.py index 1b5a5e80..b61546d6 100644 --- a/etc/dbus-serialbattery/battery_template.py +++ b/etc/dbus-serialbattery/battery_template.py @@ -3,17 +3,16 @@ from utils import * from struct import * -class BatteryTemplate(Battery): - def __init__(self, port,baud): - super(BatteryTemplate, self).__init__(port,baud) +class BatteryTemplate(Battery): + def __init__(self, port, baud): + super(BatteryTemplate, self).__init__(port, baud) self.type = self.BATTERYTYPE BATTERYTYPE = "Template" LENGTH_CHECK = 4 LENGTH_POS = 3 - def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. # The result or call should be unique to this BMS. Battery name or version, etc. @@ -30,7 +29,7 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - + # Uncomment if BMS does not supply capacity # self.capacity = BATTERY_CAPACITY self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT @@ -53,8 +52,14 @@ def read_status_data(self): if status_data is False: return False - self.cell_count, self.temp_sensors, self.charger_connected, self.load_connected, \ - state, self.cycles = unpack_from('>bb??bhx', status_data) + ( + self.cell_count, + self.temp_sensors, + self.charger_connected, + self.load_connected, + state, + self.cycles, + ) = unpack_from(">bb??bhx", status_data) self.hardware_version = "TemplateBMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) @@ -66,7 +71,7 @@ def read_soc_data(self): if soc_data is False: return False - voltage, current, soc = unpack_from('>hxxhh', soc_data) + voltage, current, soc = unpack_from(">hxxhh", soc_data) self.voltage = voltage / 10 self.current = current / -10 self.soc = soc / 10 @@ -74,15 +79,17 @@ def read_soc_data(self): def read_serial_data_template(self, command): # use the read_serial_data() function to read the data and then do BMS spesific checks (crc, start bytes, etc) - data = read_serial_data(command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK) + data = read_serial_data( + command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK + ) if data is False: return False - start, flag, command_ret, length = unpack_from('BBBB', data) + start, flag, command_ret, length = unpack_from("BBBB", data) checksum = sum(data[:-1]) & 0xFF if start == 165 and length == 8 and checksum == data[12]: - return data[4:length+4] + return data[4 : length + 4] else: logger.error(">>> ERROR: Incorrect Reply") return False diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py index 74b3697b..10281cbd 100644 --- a/etc/dbus-serialbattery/daly.py +++ b/etc/dbus-serialbattery/daly.py @@ -3,10 +3,10 @@ from utils import * from struct import * -class Daly(Battery): - def __init__(self, port,baud,address): - super(Daly, self).__init__(port,baud) +class Daly(Battery): + def __init__(self, port, baud, address): + super(Daly, self).__init__(port, baud) self.charger_connected = None self.load_connected = None self.command_address = address @@ -17,6 +17,7 @@ def __init__(self, port,baud,address): self.poll_interval = 1000 self.poll_step = 0 self.type = self.BATTERYTYPE + # command bytes [StartFlag=A5][Address=40][Command=94][DataLength=8][8x zero bytes][checksum] command_base = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x81" cellvolt_buffer = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" @@ -55,7 +56,7 @@ def get_settings(self): def refresh_data(self): result = False - # Open serial port to be used for all data reads instead of openning multiple times + # Open serial port to be used for all data reads instead of openning multiple times ser = open_serial_port(self.port, self.baud_rate) if ser is not None: result = self.read_soc_data(ser) @@ -67,13 +68,13 @@ def refresh_data(self): result = result and self.read_temperature_range_data(ser) elif self.poll_step == 1: result = result and self.read_cells_volts(ser) - - #else: # A placeholder to remind this is the last step. Add any additional steps before here + + # else: # A placeholder to remind this is the last step. Add any additional steps before here # This is last step so reset poll_step self.poll_step = -1 self.poll_step += 1 - + ser.close() return result @@ -85,8 +86,14 @@ def read_status_data(self, ser): logger.debug("read_status_data") return False - self.cell_count, self.temp_sensors, self.charger_connected, self.load_connected, \ - state, self.cycles = unpack_from('>bb??bhx', status_data) + ( + self.cell_count, + self.temp_sensors, + self.charger_connected, + self.load_connected, + state, + self.cycles, + ) = unpack_from(">bb??bhx", status_data) self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count @@ -98,7 +105,7 @@ def read_status_data(self, ser): def read_soc_data(self, ser): # Ensure data received is valid crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1) - crntMaxValid = (MAX_BATTERY_CHARGE_CURRENT * 1.3) + crntMaxValid = MAX_BATTERY_CHARGE_CURRENT * 1.3 triesValid = 2 while triesValid > 0: soc_data = self.read_serial_data_daly(ser, self.command_soc) @@ -106,14 +113,18 @@ def read_soc_data(self, ser): if soc_data is False: return False - voltage, tmp, current, soc = unpack_from('>hhhh', soc_data) - current = ((current - self.CURRENT_ZERO_CONSTANT) / -10 * INVERT_CURRENT_MEASUREMENT) + voltage, tmp, current, soc = unpack_from(">hhhh", soc_data) + current = ( + (current - self.CURRENT_ZERO_CONSTANT) + / -10 + * INVERT_CURRENT_MEASUREMENT + ) if crntMinValid < current < crntMaxValid: - self.voltage = (voltage / 10) + self.voltage = voltage / 10 self.current = current - self.soc = (soc / 10) + self.soc = soc / 10 return True - + logger.warning("read_soc_data - triesValid " + str(triesValid)) triesValid -= 1 @@ -126,12 +137,20 @@ def read_alarm_data(self, ser): logger.warning("read_alarm_data") return False - al_volt, al_temp, al_crnt_soc, al_diff, \ - al_mos, al_misc1, al_misc2, al_fault = unpack_from('>bbbbbbbb', alarm_data) + ( + al_volt, + al_temp, + al_crnt_soc, + al_diff, + al_mos, + al_misc1, + al_misc2, + al_fault, + ) = unpack_from(">bbbbbbbb", alarm_data) if al_volt & 48: # High voltage levels - Alarm - self.voltage_high = 2 + self.voltage_high = 2 elif al_volt & 15: # High voltage Warning levels - Pre-alarm self.voltage_high = 1 @@ -149,7 +168,7 @@ def read_alarm_data(self, ser): if al_temp & 2: # High charge temp - Alarm - self.temp_high_charge = 2 + self.temp_high_charge = 2 elif al_temp & 1: # High charge temp - Pre-alarm self.temp_high_charge = 1 @@ -158,17 +177,16 @@ def read_alarm_data(self, ser): if al_temp & 8: # Low charge temp - Alarm - self.temp_low_charge = 2 + self.temp_low_charge = 2 elif al_temp & 4: # Low charge temp - Pre-alarm self.temp_low_charge = 1 else: self.temp_low_charge = 0 - if al_temp & 32: # High discharge temp - Alarm - self.temp_high_discharge = 2 + self.temp_high_discharge = 2 elif al_temp & 16: # High discharge temp - Pre-alarm self.temp_high_discharge = 1 @@ -177,34 +195,34 @@ def read_alarm_data(self, ser): if al_temp & 128: # Low discharge temp - Alarm - self.temp_low_discharge = 2 + self.temp_low_discharge = 2 elif al_temp & 64: # Low discharge temp - Pre-alarm self.temp_low_discharge = 1 else: self.temp_low_discharge = 0 - #if al_crnt_soc & 2: + # if al_crnt_soc & 2: # # High charge current - Alarm - # self.current_over = 2 - #elif al_crnt_soc & 1: + # self.current_over = 2 + # elif al_crnt_soc & 1: # # High charge current - Pre-alarm # self.current_over = 1 - #else: + # else: # self.current_over = 0 - #if al_crnt_soc & 8: + # if al_crnt_soc & 8: # # High discharge current - Alarm - # self.current_over = 2 - #elif al_crnt_soc & 4: + # self.current_over = 2 + # elif al_crnt_soc & 4: # # High discharge current - Pre-alarm # self.current_over = 1 - #else: + # else: # self.current_over = 0 if al_crnt_soc & 2 or al_crnt_soc & 8: # High charge/discharge current - Alarm - self.current_over = 2 + self.current_over = 2 elif al_crnt_soc & 1 or al_crnt_soc & 4: # High charge/discharge current - Pre-alarm self.current_over = 1 @@ -219,25 +237,29 @@ def read_alarm_data(self, ser): self.soc_low = 1 else: self.soc_low = 0 - + return True def read_cells_volts(self, ser): if self.cell_count is not None: buffer = bytearray(self.cellvolt_buffer) - buffer[1] = self.command_address[0] # Always serial 40 or 80 + buffer[1] = self.command_address[0] # Always serial 40 or 80 buffer[2] = self.command_cell_volts[0] - maxFrame = (int(self.cell_count / 3) + 1) - lenFixed = (maxFrame * 12) # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + maxFrame = int(self.cell_count / 3) + 1 + lenFixed = ( + maxFrame * 12 + ) # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved - cells_volts_data = read_serialport_data(ser, buffer, self.LENGTH_POS, self.LENGTH_CHECK, lenFixed) + cells_volts_data = read_serialport_data( + ser, buffer, self.LENGTH_POS, self.LENGTH_CHECK, lenFixed + ) if cells_volts_data is False: logger.warning("read_cells_volts") return False frameCell = [0, 0, 0] - lowMin = (MIN_CELL_VOLTAGE / 2) + lowMin = MIN_CELL_VOLTAGE / 2 frame = 0 bufIdx = 0 @@ -247,17 +269,25 @@ def read_cells_volts(self, ser): for idx in range(self.cell_count): self.cells.append(Cell(True)) - while bufIdx < len(cells_volts_data) - 4: # we at least need 4 bytes to extract the identifiers - b1, b2, b3, b4 = unpack_from('>BBBB', cells_volts_data, bufIdx) + while ( + bufIdx < len(cells_volts_data) - 4 + ): # we at least need 4 bytes to extract the identifiers + b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx) if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08: - frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from('>Bhhh', cells_volts_data, bufIdx + 4) - for idx in range(3): - cellnum = ((frame - 1) * 3) + idx # daly is 1 based, driver 0 based - if cellnum >= self.cell_count: - break - cellVoltage = frameCell[idx] / 1000 - self.cells[cellnum].voltage = None if cellVoltage < lowMin else cellVoltage - bufIdx += 10 # BBBBBhhh -> 11 byte + frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from( + ">Bhhh", cells_volts_data, bufIdx + 4 + ) + for idx in range(3): + cellnum = ( + (frame - 1) * 3 + ) + idx # daly is 1 based, driver 0 based + if cellnum >= self.cell_count: + break + cellVoltage = frameCell[idx] / 1000 + self.cells[cellnum].voltage = ( + None if cellVoltage < lowMin else cellVoltage + ) + bufIdx += 10 # BBBBBhhh -> 11 byte bufIdx += 1 return True @@ -269,7 +299,12 @@ def read_cell_voltage_range_data(self, ser): logger.warning("read_cell_voltage_range_data") return False - cell_max_voltage,self.cell_max_no,cell_min_voltage, self.cell_min_no = unpack_from('>hbhb', minmax_data) + ( + cell_max_voltage, + self.cell_max_no, + cell_min_voltage, + self.cell_min_no, + ) = unpack_from(">hbhb", minmax_data) # Daly cells numbers are 1 based and not 0 based self.cell_min_no -= 1 self.cell_max_no -= 1 @@ -285,7 +320,7 @@ def read_temperature_range_data(self, ser): logger.debug("read_temperature_range_data") return False - max_temp,max_no,min_temp, min_no = unpack_from('>bbbb', minmax_data) + max_temp, max_no, min_temp, min_no = unpack_from(">bbbb", minmax_data) self.temp1 = min_temp - self.TEMP_ZERO_CONSTANT self.temp2 = max_temp - self.TEMP_ZERO_CONSTANT return True @@ -297,27 +332,35 @@ def read_fed_data(self, ser): logger.debug("read_fed_data") return False - status, self.charge_fet, self.discharge_fet, bms_cycles, capacity_remain = unpack_from('>b??BL', fed_data) + ( + status, + self.charge_fet, + self.discharge_fet, + bms_cycles, + capacity_remain, + ) = unpack_from(">b??BL", fed_data) self.capacity_remain = capacity_remain / 1000 return True def generate_command(self, command): buffer = bytearray(self.command_base) - buffer[1] = self.command_address[0] # Always serial 40 or 80 + buffer[1] = self.command_address[0] # Always serial 40 or 80 buffer[2] = command[0] - buffer[12] = sum(buffer[:12]) & 0xFF #checksum calc + buffer[12] = sum(buffer[:12]) & 0xFF # checksum calc return buffer def read_serial_data_daly(self, ser, command): - data = read_serialport_data(ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK) + data = read_serialport_data( + ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK + ) if data is False: return False - start, flag, command_ret, length = unpack_from('BBBB', data) + start, flag, command_ret, length = unpack_from("BBBB", data) checksum = sum(data[:-1]) & 0xFF if start == 165 and length == 8 and checksum == data[12]: - return data[4:length+4] + return data[4 : length + 4] else: logger.error(">>> ERROR: Incorrect Reply") return False diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 25d3533b..932236ad 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -5,6 +5,7 @@ from threading import Thread import dbus import sys + if sys.version_info.major == 2: import gobject else: @@ -24,13 +25,14 @@ from renogy import Renogy from ecs import Ecs from lifepower import Lifepower -#from mnb import MNB +# from mnb import MNB -logger.info('Starting dbus-serialbattery') -def main(): +logger.info("Starting dbus-serialbattery") + +def main(): def poll_battery(loop): # Run in separate thread. Pass in the mainloop so the thread can kill us if there is an exception. poller = Thread(target=lambda: helper.publish_battery(loop)) @@ -46,7 +48,7 @@ def get_battery_type(_port): while count > 0: # create a new battery object that can read the battery and run connection test for test in battery_types: - logger.info('Testing ' + test["bms"]) + logger.info("Testing " + test["bms"]) class_ = eval(test["bms"]) if "baud" in test.keys(): baud = test["baud"] @@ -57,7 +59,9 @@ def get_battery_type(_port): else: testbms = class_(_port, baud) if testbms.test_connection() is True: - logger.info('Connection established to ' + testbms.__class__.__name__) + logger.info( + "Connection established to " + testbms.__class__.__name__ + ) return testbms count -= 1 sleep(0.5) @@ -70,10 +74,10 @@ def get_port(): return sys.argv[1] else: # just for MNB-SPI - logger.info('No Port needed') - return '/dev/tty/USB9' + logger.info("No Port needed") + return "/dev/tty/USB9" - logger.info('dbus-serialbattery v' + str(DRIVER_VERSION) + DRIVER_SUBVERSION) + logger.info("dbus-serialbattery v" + str(DRIVER_VERSION) + DRIVER_SUBVERSION) port = get_port() battery = get_battery_type(port) @@ -82,9 +86,9 @@ def get_port(): if battery is None: logger.error("ERROR >>> No battery connection at " + port) sys.exit(1) - + battery.log_settings() - + # Have a mainloop, so we can send/receive asynchronous calls to and from dbus DBusGMainLoop(set_as_default=True) if sys.version_info.major == 2: @@ -93,7 +97,7 @@ def get_port(): # Get the initial values for the battery used by setup_vedbus helper = DbusHelper(battery) - + if not helper.setup_vedbus(): logger.error("ERROR >>> Problem with battery set up at " + port) sys.exit(1) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index a2287e93..d29b04d2 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -4,85 +4,105 @@ import platform import dbus import traceback + # Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/dbus-systemcalc-py/ext/velib_python')) +sys.path.insert( + 1, + os.path.join( + os.path.dirname(__file__), + "/opt/victronenergy/dbus-systemcalc-py/ext/velib_python", + ), +) from vedbus import VeDbusService from settingsdevice import SettingsDevice import battery from utils import * + def get_bus(): - return dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() + return ( + dbus.SessionBus() + if "DBUS_SESSION_BUS_ADDRESS" in os.environ + else dbus.SystemBus() + ) -class DbusHelper: +class DbusHelper: def __init__(self, battery): self.battery = battery self.instance = 1 self.settings = None - self._dbusservice = VeDbusService("com.victronenergy.battery." + - self.battery.port[self.battery.port.rfind('/') + 1:], - get_bus()) + self._dbusservice = VeDbusService( + "com.victronenergy.battery." + + self.battery.port[self.battery.port.rfind("/") + 1 :], + get_bus(), + ) def setup_instance(self): # bms_id = self.battery.production if self.battery.production is not None else \ # self.battery.port[self.battery.port.rfind('/') + 1:] - bms_id = self.battery.port[self.battery.port.rfind('/') + 1:] - path = '/Settings/Devices/serialbattery' - default_instance = 'battery:1' + bms_id = self.battery.port[self.battery.port.rfind("/") + 1 :] + path = "/Settings/Devices/serialbattery" + default_instance = "battery:1" settings = { - 'instance': [path + '_' + str(bms_id).replace(" ", "_")+ '/ClassAndVrmInstance', default_instance, 0, 0], - # 'CellVoltageMin': [path + '/CellVoltageMin', 2.8, 0.0, 5.0], - # 'CellVoltageMax': [path + '/CellVoltageMax', 3.45, 0.0, 5.0], - # 'CellVoltageFloat': [path + '/CellVoltageFloat', 3.35, 0.0, 5.0], - # 'VoltageMaxTime': [path + '/VoltageMaxTime', 900, 0, 0], - # 'VoltageResetSocLimit': [path + '/VoltageResetSocLimit', 90, 0, 100], - # 'MaxChargeCurrent': [path + '/MaxCurrentCharge', 5, 0.0, 500], - # 'MaxDischargeCurrent': [path + '/MaxCurrentDischarge', 7, 0.0, 500], - # 'AllowDynamicChargeCurrent': [path + '/AllowDynamicChargeCurrent', 1, 0, 1], - # 'AllowDynamicDischargeCurrent': [path + '/AllowDynamicDischargeCurrent', 1, 0, 1], - # 'AllowDynamicChargeVoltage': [path + '/AllowDynamicChargeVoltage', 0, 0, 1], - # 'SocLowWarning': [path + '/SocLowWarning', 20, 0, 100], - # 'SocLowAlarm': [path + '/SocLowAlarm', 10, 0, 100], - # 'Capacity': [path + '/Capacity', '', 0, 500], - # 'EnableInvertedCurrent': [path + '/EnableInvertedCurrent', 0, 0, 1], - + "instance": [ + path + "_" + str(bms_id).replace(" ", "_") + "/ClassAndVrmInstance", + default_instance, + 0, + 0, + ], + # 'CellVoltageMin': [path + '/CellVoltageMin', 2.8, 0.0, 5.0], + # 'CellVoltageMax': [path + '/CellVoltageMax', 3.45, 0.0, 5.0], + # 'CellVoltageFloat': [path + '/CellVoltageFloat', 3.35, 0.0, 5.0], + # 'VoltageMaxTime': [path + '/VoltageMaxTime', 900, 0, 0], + # 'VoltageResetSocLimit': [path + '/VoltageResetSocLimit', 90, 0, 100], + # 'MaxChargeCurrent': [path + '/MaxCurrentCharge', 5, 0.0, 500], + # 'MaxDischargeCurrent': [path + '/MaxCurrentDischarge', 7, 0.0, 500], + # 'AllowDynamicChargeCurrent': [path + '/AllowDynamicChargeCurrent', 1, 0, 1], + # 'AllowDynamicDischargeCurrent': [path + '/AllowDynamicDischargeCurrent', 1, 0, 1], + # 'AllowDynamicChargeVoltage': [path + '/AllowDynamicChargeVoltage', 0, 0, 1], + # 'SocLowWarning': [path + '/SocLowWarning', 20, 0, 100], + # 'SocLowAlarm': [path + '/SocLowAlarm', 10, 0, 100], + # 'Capacity': [path + '/Capacity', '', 0, 500], + # 'EnableInvertedCurrent': [path + '/EnableInvertedCurrent', 0, 0, 1], # 'CCMSocLimitCharge1': [path + '/CCMSocLimitCharge1', 98, 0, 100], # 'CCMSocLimitCharge2': [path + '/CCMSocLimitCharge2', 95, 0, 100], - # 'CCMSocLimitCharge3': [path + '/CCMSocLimitCharge3', 91, 0, 100], + # 'CCMSocLimitCharge3': [path + '/CCMSocLimitCharge3', 91, 0, 100], # 'CCMSocLimitDischarge1': [path + '/CCMSocLimitDischarge1', 10, 0, 100], # 'CCMSocLimitDischarge2': [path + '/CCMSocLimitDischarge2', 20, 0, 100], # 'CCMSocLimitDischarge3': [path + '/CCMSocLimitDischarge3', 30, 0, 100], # 'CCMCurrentLimitCharge1': [path + '/CCMCurrentLimitCharge1', 5, 0, 100], # 'CCMCurrentLimitCharge2': [path + '/CCMCurrentLimitCharge2', '', 0, 100], - # 'CCMCurrentLimitCharge3': [path + '/CCMCurrentLimitCharge3', '', 0, 100], + # 'CCMCurrentLimitCharge3': [path + '/CCMCurrentLimitCharge3', '', 0, 100], # 'CCMCurrentLimitDischarge1': [path + '/CCMCurrentLimitDischarge1', 5, 0, 100], # 'CCMCurrentLimitDischarge2': [path + '/CCMCurrentLimitDischarge2', '', 0, 100], - # 'CCMCurrentLimitDischarge3': [path + '/CCMCurrentLimitDischarge3', '', 0, 100], + # 'CCMCurrentLimitDischarge3': [path + '/CCMCurrentLimitDischarge3', '', 0, 100], } self.settings = SettingsDevice(get_bus(), settings, self.handle_changed_setting) self.battery.role, self.instance = self.get_role_instance() def get_role_instance(self): - val = self.settings['instance'].split(':') + val = self.settings["instance"].split(":") logger.info("DeviceInstance = %d", int(val[1])) return val[0], int(val[1]) def handle_changed_setting(self, setting, oldvalue, newvalue): - if setting == 'instance': + if setting == "instance": self.battery.role, self.instance = self.get_role_instance() logger.info("Changed DeviceInstance = %d", self.instance) return - logger.info("Changed DeviceInstance = %d", float(self.settings['CellVoltageMin'])) - #self._dbusservice['/History/ChargeCycles'] + logger.info( + "Changed DeviceInstance = %d", float(self.settings["CellVoltageMin"]) + ) + # self._dbusservice['/History/ChargeCycles'] def setup_vedbus(self): # Set up dbus service and device instance # and notify of all the attributes we intend to update # This is only called once when a battery is initiated self.setup_instance() - short_port = self.battery.port[self.battery.port.rfind('/') + 1:] + short_port = self.battery.port[self.battery.port.rfind("/") + 1 :] logger.info("%s" % ("com.victronenergy.battery." + short_port)) # Get the settings for the battery @@ -90,98 +110,192 @@ def setup_vedbus(self): return False # Create the management objects, as specified in the ccgx dbus-api document - self._dbusservice.add_path('/Mgmt/ProcessName', __file__) - self._dbusservice.add_path('/Mgmt/ProcessVersion', 'Python ' + platform.python_version()) - self._dbusservice.add_path('/Mgmt/Connection', 'Serial ' + self.battery.port) + self._dbusservice.add_path("/Mgmt/ProcessName", __file__) + self._dbusservice.add_path( + "/Mgmt/ProcessVersion", "Python " + platform.python_version() + ) + self._dbusservice.add_path("/Mgmt/Connection", "Serial " + self.battery.port) # Create the mandatory objects - self._dbusservice.add_path('/DeviceInstance', self.instance) - self._dbusservice.add_path('/ProductId', 0x0) - self._dbusservice.add_path('/ProductName', 'SerialBattery(' + self.battery.type + ')') - self._dbusservice.add_path('/FirmwareVersion', str(DRIVER_VERSION) + DRIVER_SUBVERSION) - self._dbusservice.add_path('/HardwareVersion', self.battery.hardware_version) - self._dbusservice.add_path('/Connected', 1) - self._dbusservice.add_path('/CustomName', 'SerialBattery(' + self.battery.type + ')', writeable=True) + self._dbusservice.add_path("/DeviceInstance", self.instance) + self._dbusservice.add_path("/ProductId", 0x0) + self._dbusservice.add_path( + "/ProductName", "SerialBattery(" + self.battery.type + ")" + ) + self._dbusservice.add_path( + "/FirmwareVersion", str(DRIVER_VERSION) + DRIVER_SUBVERSION + ) + self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version) + self._dbusservice.add_path("/Connected", 1) + self._dbusservice.add_path( + "/CustomName", "SerialBattery(" + self.battery.type + ")", writeable=True + ) # Create static battery info - self._dbusservice.add_path('/Info/BatteryLowVoltage', self.battery.min_battery_voltage, writeable=True) - self._dbusservice.add_path('/Info/MaxChargeVoltage', self.battery.max_battery_voltage, writeable=True, - gettextcallback=lambda p, v: "{:0.2f}V".format(v)) - self._dbusservice.add_path('/Info/MaxChargeCurrent', self.battery.max_battery_charge_current, writeable=True, - gettextcallback=lambda p, v: "{:0.2f}A".format(v)) - self._dbusservice.add_path('/Info/MaxDischargeCurrent', self.battery.max_battery_discharge_current, - writeable=True, gettextcallback=lambda p, v: "{:0.2f}A".format(v)) - self._dbusservice.add_path('/System/NrOfCellsPerBattery', self.battery.cell_count, writeable=True) - self._dbusservice.add_path('/System/NrOfModulesOnline', 1, writeable=True) - self._dbusservice.add_path('/System/NrOfModulesOffline', 0, writeable=True) - self._dbusservice.add_path('/System/NrOfModulesBlockingCharge', None, writeable=True) - self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge', None, writeable=True) - self._dbusservice.add_path('/Capacity', self.battery.get_capacity_remain(), writeable=True, - gettextcallback=lambda p, v: "{:0.2f}Ah".format(v)) - self._dbusservice.add_path('/InstalledCapacity', self.battery.capacity, writeable=True, - gettextcallback=lambda p, v: "{:0.0f}Ah".format(v)) - self._dbusservice.add_path('/ConsumedAmphours', None, writeable=True, - gettextcallback=lambda p, v: "{:0.0f}Ah".format(v)) + self._dbusservice.add_path( + "/Info/BatteryLowVoltage", self.battery.min_battery_voltage, writeable=True + ) + self._dbusservice.add_path( + "/Info/MaxChargeVoltage", + self.battery.max_battery_voltage, + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}V".format(v), + ) + self._dbusservice.add_path( + "/Info/MaxChargeCurrent", + self.battery.max_battery_charge_current, + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}A".format(v), + ) + self._dbusservice.add_path( + "/Info/MaxDischargeCurrent", + self.battery.max_battery_discharge_current, + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}A".format(v), + ) + self._dbusservice.add_path( + "/System/NrOfCellsPerBattery", self.battery.cell_count, writeable=True + ) + self._dbusservice.add_path("/System/NrOfModulesOnline", 1, writeable=True) + self._dbusservice.add_path("/System/NrOfModulesOffline", 0, writeable=True) + self._dbusservice.add_path( + "/System/NrOfModulesBlockingCharge", None, writeable=True + ) + self._dbusservice.add_path( + "/System/NrOfModulesBlockingDischarge", None, writeable=True + ) + self._dbusservice.add_path( + "/Capacity", + self.battery.get_capacity_remain(), + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}Ah".format(v), + ) + self._dbusservice.add_path( + "/InstalledCapacity", + self.battery.capacity, + writeable=True, + gettextcallback=lambda p, v: "{:0.0f}Ah".format(v), + ) + self._dbusservice.add_path( + "/ConsumedAmphours", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.0f}Ah".format(v), + ) # Not used at this stage # self._dbusservice.add_path('/System/MinTemperatureCellId', None, writeable=True) # self._dbusservice.add_path('/System/MaxTemperatureCellId', None, writeable=True) # Create SOC, DC and System items - self._dbusservice.add_path('/Soc', None, writeable=True) - self._dbusservice.add_path('/Dc/0/Voltage', None, writeable=True, gettextcallback=lambda p, v: "{:2.2f}V".format(v)) - self._dbusservice.add_path('/Dc/0/Current', None, writeable=True, gettextcallback=lambda p, v: "{:2.2f}A".format(v)) - self._dbusservice.add_path('/Dc/0/Power', None, writeable=True, gettextcallback=lambda p, v: "{:0.0f}W".format(v)) - self._dbusservice.add_path('/Dc/0/Temperature', None, writeable=True) - self._dbusservice.add_path('/Dc/0/MidVoltage', None, writeable=True, - gettextcallback=lambda p, v: "{:0.2f}V".format(v)) - self._dbusservice.add_path('/Dc/0/MidVoltageDeviation', None, writeable=True, - gettextcallback=lambda p, v: "{:0.1f}%".format(v)) + self._dbusservice.add_path("/Soc", None, writeable=True) + self._dbusservice.add_path( + "/Dc/0/Voltage", + None, + writeable=True, + gettextcallback=lambda p, v: "{:2.2f}V".format(v), + ) + self._dbusservice.add_path( + "/Dc/0/Current", + None, + writeable=True, + gettextcallback=lambda p, v: "{:2.2f}A".format(v), + ) + self._dbusservice.add_path( + "/Dc/0/Power", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.0f}W".format(v), + ) + self._dbusservice.add_path("/Dc/0/Temperature", None, writeable=True) + self._dbusservice.add_path( + "/Dc/0/MidVoltage", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}V".format(v), + ) + self._dbusservice.add_path( + "/Dc/0/MidVoltageDeviation", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.1f}%".format(v), + ) # Create battery extras - self._dbusservice.add_path('/System/MinCellTemperature', None, writeable=True) - self._dbusservice.add_path('/System/MaxCellTemperature', None, writeable=True) - self._dbusservice.add_path('/System/MaxCellVoltage', None, writeable=True, - gettextcallback=lambda p, v: "{:0.3f}V".format(v)) - self._dbusservice.add_path('/System/MaxVoltageCellId', None, writeable=True) - self._dbusservice.add_path('/System/MinCellVoltage', None, writeable=True, - gettextcallback=lambda p, v: "{:0.3f}V".format(v)) - self._dbusservice.add_path('/System/MinVoltageCellId', None, writeable=True) - self._dbusservice.add_path('/History/ChargeCycles', None, writeable=True) - self._dbusservice.add_path('/History/TotalAhDrawn', None, writeable=True) - self._dbusservice.add_path('/Balancing', None, writeable=True) - self._dbusservice.add_path('/Io/AllowToCharge', 0, writeable=True) - self._dbusservice.add_path('/Io/AllowToDischarge', 0, writeable=True) + self._dbusservice.add_path("/System/MinCellTemperature", None, writeable=True) + self._dbusservice.add_path("/System/MaxCellTemperature", None, writeable=True) + self._dbusservice.add_path( + "/System/MaxCellVoltage", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.3f}V".format(v), + ) + self._dbusservice.add_path("/System/MaxVoltageCellId", None, writeable=True) + self._dbusservice.add_path( + "/System/MinCellVoltage", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.3f}V".format(v), + ) + self._dbusservice.add_path("/System/MinVoltageCellId", None, writeable=True) + self._dbusservice.add_path("/History/ChargeCycles", None, writeable=True) + self._dbusservice.add_path("/History/TotalAhDrawn", None, writeable=True) + self._dbusservice.add_path("/Balancing", None, writeable=True) + self._dbusservice.add_path("/Io/AllowToCharge", 0, writeable=True) + self._dbusservice.add_path("/Io/AllowToDischarge", 0, writeable=True) # self._dbusservice.add_path('/SystemSwitch',1,writeable=True) # Create the alarms - self._dbusservice.add_path('/Alarms/LowVoltage', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighVoltage', None, writeable=True) - self._dbusservice.add_path('/Alarms/LowCellVoltage', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighCellVoltage', None, writeable=True) - self._dbusservice.add_path('/Alarms/LowSoc', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighChargeCurrent', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighDischargeCurrent', None, writeable=True) - self._dbusservice.add_path('/Alarms/CellImbalance', None, writeable=True) - self._dbusservice.add_path('/Alarms/InternalFailure', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighChargeTemperature', None, writeable=True) - self._dbusservice.add_path('/Alarms/LowChargeTemperature', None, writeable=True) - self._dbusservice.add_path('/Alarms/HighTemperature', None, writeable=True) - self._dbusservice.add_path('/Alarms/LowTemperature', None, writeable=True) - - #cell voltages - if (BATTERY_CELL_DATA_FORMAT>0): - for i in range(1, self.battery.cell_count+1): - cellpath = '/Cell/%s/Volts' if (BATTERY_CELL_DATA_FORMAT & 2) else '/Voltages/Cell%s' - self._dbusservice.add_path(cellpath%(str(i)), None, writeable=True, gettextcallback=lambda p, v: "{:0.3f}V".format(v)) - if (BATTERY_CELL_DATA_FORMAT & 1): - self._dbusservice.add_path('/Balances/Cell%s'%(str(i)), None, writeable=True) - pathbase = 'Cell' if (BATTERY_CELL_DATA_FORMAT & 2) else 'Voltages' - self._dbusservice.add_path('/%s/Sum'%pathbase, None, writeable=True, gettextcallback=lambda p, v: "{:2.2f}V".format(v)) - self._dbusservice.add_path('/%s/Diff'%pathbase, None, writeable=True, gettextcallback=lambda p, v: "{:0.3f}V".format(v)) + self._dbusservice.add_path("/Alarms/LowVoltage", None, writeable=True) + self._dbusservice.add_path("/Alarms/HighVoltage", None, writeable=True) + self._dbusservice.add_path("/Alarms/LowCellVoltage", None, writeable=True) + self._dbusservice.add_path("/Alarms/HighCellVoltage", None, writeable=True) + self._dbusservice.add_path("/Alarms/LowSoc", None, writeable=True) + self._dbusservice.add_path("/Alarms/HighChargeCurrent", None, writeable=True) + self._dbusservice.add_path("/Alarms/HighDischargeCurrent", None, writeable=True) + self._dbusservice.add_path("/Alarms/CellImbalance", None, writeable=True) + self._dbusservice.add_path("/Alarms/InternalFailure", None, writeable=True) + self._dbusservice.add_path( + "/Alarms/HighChargeTemperature", None, writeable=True + ) + self._dbusservice.add_path("/Alarms/LowChargeTemperature", None, writeable=True) + self._dbusservice.add_path("/Alarms/HighTemperature", None, writeable=True) + self._dbusservice.add_path("/Alarms/LowTemperature", None, writeable=True) + + # cell voltages + if BATTERY_CELL_DATA_FORMAT > 0: + for i in range(1, self.battery.cell_count + 1): + cellpath = ( + "/Cell/%s/Volts" + if (BATTERY_CELL_DATA_FORMAT & 2) + else "/Voltages/Cell%s" + ) + self._dbusservice.add_path( + cellpath % (str(i)), + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.3f}V".format(v), + ) + if BATTERY_CELL_DATA_FORMAT & 1: + self._dbusservice.add_path( + "/Balances/Cell%s" % (str(i)), None, writeable=True + ) + pathbase = "Cell" if (BATTERY_CELL_DATA_FORMAT & 2) else "Voltages" + self._dbusservice.add_path( + "/%s/Sum" % pathbase, + None, + writeable=True, + gettextcallback=lambda p, v: "{:2.2f}V".format(v), + ) + self._dbusservice.add_path( + "/%s/Diff" % pathbase, + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.3f}V".format(v), + ) # Create TimeToSoC items for num in TIME_TO_SOC_POINTS: - self._dbusservice.add_path('/TimeToSoC/' + str(num), None, writeable=True) + self._dbusservice.add_path("/TimeToSoC/" + str(num), None, writeable=True) return True @@ -197,18 +311,18 @@ def publish_battery(self, loop): else: error_count += 1 # If the battery is offline for more than 10 polls (polled every second for most batteries) - if error_count >= 10: + if error_count >= 10: self.battery.online = False # Has it completely failed - if error_count >= 60: + if error_count >= 60: loop.quit() # This is to mannage CCL\DCL self.battery.manage_charge_current() - + # This is to mannage CVCL - self.battery.manage_charge_voltage() - + self.battery.manage_charge_voltage() + # publish all the data fro the battery object to dbus self.publish_dbus() @@ -219,96 +333,156 @@ def publish_battery(self, loop): def publish_dbus(self): # Update SOC, DC and System items - self._dbusservice['/System/NrOfCellsPerBattery'] = self.battery.cell_count - self._dbusservice['/Soc'] = round(self.battery.soc, 2) - self._dbusservice['/Dc/0/Voltage'] = round(self.battery.voltage, 2) - self._dbusservice['/Dc/0/Current'] = round(self.battery.current, 2) - self._dbusservice['/Dc/0/Power'] = round(self.battery.voltage * self.battery.current, 2) - self._dbusservice['/Dc/0/Temperature'] = self.battery.get_temp() - self._dbusservice['/Capacity'] = self.battery.get_capacity_remain() - self._dbusservice['/ConsumedAmphours'] = 0 if self.battery.capacity is None or \ - self.battery.get_capacity_remain() is None else \ - self.battery.capacity - self.battery.get_capacity_remain() - + self._dbusservice["/System/NrOfCellsPerBattery"] = self.battery.cell_count + self._dbusservice["/Soc"] = round(self.battery.soc, 2) + self._dbusservice["/Dc/0/Voltage"] = round(self.battery.voltage, 2) + self._dbusservice["/Dc/0/Current"] = round(self.battery.current, 2) + self._dbusservice["/Dc/0/Power"] = round( + self.battery.voltage * self.battery.current, 2 + ) + self._dbusservice["/Dc/0/Temperature"] = self.battery.get_temp() + self._dbusservice["/Capacity"] = self.battery.get_capacity_remain() + self._dbusservice["/ConsumedAmphours"] = ( + 0 + if self.battery.capacity is None + or self.battery.get_capacity_remain() is None + else self.battery.capacity - self.battery.get_capacity_remain() + ) + midpoint, deviation = self.battery.get_midvoltage() - if (midpoint is not None): - self._dbusservice['/Dc/0/MidVoltage'] = midpoint - self._dbusservice['/Dc/0/MidVoltageDeviation'] = deviation + if midpoint is not None: + self._dbusservice["/Dc/0/MidVoltage"] = midpoint + self._dbusservice["/Dc/0/MidVoltageDeviation"] = deviation # Update battery extras - self._dbusservice['/History/ChargeCycles'] = self.battery.cycles - self._dbusservice['/History/TotalAhDrawn'] = self.battery.total_ah_drawn - self._dbusservice['/Io/AllowToCharge'] = 1 if self.battery.charge_fet \ - and self.battery.control_allow_charge else 0 - self._dbusservice['/Io/AllowToDischarge'] = 1 if self.battery.discharge_fet else 0 - self._dbusservice['/System/NrOfModulesBlockingCharge'] = 0 if self.battery.charge_fet is None or \ - (self.battery.charge_fet and self.battery.control_allow_charge) else 1 - self._dbusservice['/System/NrOfModulesBlockingDischarge'] = 0 if self.battery.discharge_fet is None \ - or self.battery.discharge_fet else 1 - self._dbusservice['/System/NrOfModulesOnline'] = 1 if self.battery.online else 0 - self._dbusservice['/System/NrOfModulesOffline'] = 0 if self.battery.online else 1 - self._dbusservice['/System/MinCellTemperature'] = self.battery.get_min_temp() - self._dbusservice['/System/MaxCellTemperature'] = self.battery.get_max_temp() + self._dbusservice["/History/ChargeCycles"] = self.battery.cycles + self._dbusservice["/History/TotalAhDrawn"] = self.battery.total_ah_drawn + self._dbusservice["/Io/AllowToCharge"] = ( + 1 if self.battery.charge_fet and self.battery.control_allow_charge else 0 + ) + self._dbusservice["/Io/AllowToDischarge"] = ( + 1 if self.battery.discharge_fet else 0 + ) + self._dbusservice["/System/NrOfModulesBlockingCharge"] = ( + 0 + if self.battery.charge_fet is None + or (self.battery.charge_fet and self.battery.control_allow_charge) + else 1 + ) + self._dbusservice["/System/NrOfModulesBlockingDischarge"] = ( + 0 if self.battery.discharge_fet is None or self.battery.discharge_fet else 1 + ) + self._dbusservice["/System/NrOfModulesOnline"] = 1 if self.battery.online else 0 + self._dbusservice["/System/NrOfModulesOffline"] = ( + 0 if self.battery.online else 1 + ) + self._dbusservice["/System/MinCellTemperature"] = self.battery.get_min_temp() + self._dbusservice["/System/MaxCellTemperature"] = self.battery.get_max_temp() # Charge control - self._dbusservice['/Info/MaxChargeCurrent'] = self.battery.control_charge_current - self._dbusservice['/Info/MaxDischargeCurrent'] = self.battery.control_discharge_current + self._dbusservice[ + "/Info/MaxChargeCurrent" + ] = self.battery.control_charge_current + self._dbusservice[ + "/Info/MaxDischargeCurrent" + ] = self.battery.control_discharge_current # Voltage control - self._dbusservice['/Info/MaxChargeVoltage'] = self.battery.control_voltage - + self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage + # Updates from cells - self._dbusservice['/System/MinVoltageCellId'] = self.battery.get_min_cell_desc() - self._dbusservice['/System/MaxVoltageCellId'] = self.battery.get_max_cell_desc() - self._dbusservice['/System/MinCellVoltage'] = self.battery.get_min_cell_voltage() - self._dbusservice['/System/MaxCellVoltage'] = self.battery.get_max_cell_voltage() - self._dbusservice['/Balancing'] = self.battery.get_balancing() + self._dbusservice["/System/MinVoltageCellId"] = self.battery.get_min_cell_desc() + self._dbusservice["/System/MaxVoltageCellId"] = self.battery.get_max_cell_desc() + self._dbusservice[ + "/System/MinCellVoltage" + ] = self.battery.get_min_cell_voltage() + self._dbusservice[ + "/System/MaxCellVoltage" + ] = self.battery.get_max_cell_voltage() + self._dbusservice["/Balancing"] = self.battery.get_balancing() # Update the alarms - self._dbusservice['/Alarms/LowVoltage'] = self.battery.protection.voltage_low - self._dbusservice['/Alarms/LowCellVoltage'] = self.battery.protection.voltage_cell_low - self._dbusservice['/Alarms/HighVoltage'] = self.battery.protection.voltage_high - self._dbusservice['/Alarms/LowSoc'] = self.battery.protection.soc_low - self._dbusservice['/Alarms/HighChargeCurrent'] = self.battery.protection.current_over - self._dbusservice['/Alarms/HighDischargeCurrent'] = self.battery.protection.current_under - self._dbusservice['/Alarms/CellImbalance'] = self.battery.protection.cell_imbalance - self._dbusservice['/Alarms/InternalFailure'] = self.battery.protection.internal_failure - self._dbusservice['/Alarms/HighChargeTemperature'] = self.battery.protection.temp_high_charge - self._dbusservice['/Alarms/LowChargeTemperature'] = self.battery.protection.temp_low_charge - self._dbusservice['/Alarms/HighTemperature'] = self.battery.protection.temp_high_discharge - self._dbusservice['/Alarms/LowTemperature'] = self.battery.protection.temp_low_discharge - - #cell voltages - if (BATTERY_CELL_DATA_FORMAT>0): + self._dbusservice["/Alarms/LowVoltage"] = self.battery.protection.voltage_low + self._dbusservice[ + "/Alarms/LowCellVoltage" + ] = self.battery.protection.voltage_cell_low + self._dbusservice["/Alarms/HighVoltage"] = self.battery.protection.voltage_high + self._dbusservice["/Alarms/LowSoc"] = self.battery.protection.soc_low + self._dbusservice[ + "/Alarms/HighChargeCurrent" + ] = self.battery.protection.current_over + self._dbusservice[ + "/Alarms/HighDischargeCurrent" + ] = self.battery.protection.current_under + self._dbusservice[ + "/Alarms/CellImbalance" + ] = self.battery.protection.cell_imbalance + self._dbusservice[ + "/Alarms/InternalFailure" + ] = self.battery.protection.internal_failure + self._dbusservice[ + "/Alarms/HighChargeTemperature" + ] = self.battery.protection.temp_high_charge + self._dbusservice[ + "/Alarms/LowChargeTemperature" + ] = self.battery.protection.temp_low_charge + self._dbusservice[ + "/Alarms/HighTemperature" + ] = self.battery.protection.temp_high_discharge + self._dbusservice[ + "/Alarms/LowTemperature" + ] = self.battery.protection.temp_low_discharge + + # cell voltages + if BATTERY_CELL_DATA_FORMAT > 0: try: voltageSum = 0 for i in range(self.battery.cell_count): voltage = self.battery.get_cell_voltage(i) - cellpath = '/Cell/%s/Volts' if (BATTERY_CELL_DATA_FORMAT & 2) else '/Voltages/Cell%s' - self._dbusservice[cellpath%(str(i+1))] = voltage - if (BATTERY_CELL_DATA_FORMAT & 1): - self._dbusservice['/Balances/Cell%s'%(str(i+1))] = self.battery.get_cell_balancing(i) + cellpath = ( + "/Cell/%s/Volts" + if (BATTERY_CELL_DATA_FORMAT & 2) + else "/Voltages/Cell%s" + ) + self._dbusservice[cellpath % (str(i + 1))] = voltage + if BATTERY_CELL_DATA_FORMAT & 1: + self._dbusservice[ + "/Balances/Cell%s" % (str(i + 1)) + ] = self.battery.get_cell_balancing(i) if voltage: - voltageSum+=voltage - pathbase = 'Cell' if (BATTERY_CELL_DATA_FORMAT & 2) else 'Voltages' - self._dbusservice['/%s/Sum'%pathbase] = voltageSum - self._dbusservice['/%s/Diff'%pathbase] = self.battery.get_max_cell_voltage() - self.battery.get_min_cell_voltage() + voltageSum += voltage + pathbase = "Cell" if (BATTERY_CELL_DATA_FORMAT & 2) else "Voltages" + self._dbusservice["/%s/Sum" % pathbase] = voltageSum + self._dbusservice["/%s/Diff" % pathbase] = ( + self.battery.get_max_cell_voltage() + - self.battery.get_min_cell_voltage() + ) except: pass # Update TimeToSoC try: - if self.battery.capacity is not None and len(TIME_TO_SOC_POINTS) > 0 and self.battery.time_to_soc_update == 0: + if ( + self.battery.capacity is not None + and len(TIME_TO_SOC_POINTS) > 0 + and self.battery.time_to_soc_update == 0 + ): self.battery.time_to_soc_update = TIME_TO_SOC_LOOP_CYCLES - crntPrctPerSec = (abs(self.battery.current / (self.battery.capacity / 100)) / 3600) + crntPrctPerSec = ( + abs(self.battery.current / (self.battery.capacity / 100)) / 3600 + ) for num in TIME_TO_SOC_POINTS: - self._dbusservice['/TimeToSoC/' + str(num)] = self.battery.get_timetosoc(num, crntPrctPerSec) if self.battery.current else None - + self._dbusservice["/TimeToSoC/" + str(num)] = ( + self.battery.get_timetosoc(num, crntPrctPerSec) + if self.battery.current + else None + ) + else: self.battery.time_to_soc_update -= 1 except: pass - logger.debug("logged to dbus [%s]"%str(round(self.battery.soc, 2))) + logger.debug("logged to dbus [%s]" % str(round(self.battery.soc, 2))) self.battery.log_cell_data() diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/ecs.py index ba9737a3..598cc58c 100644 --- a/etc/dbus-serialbattery/ecs.py +++ b/etc/dbus-serialbattery/ecs.py @@ -5,10 +5,10 @@ from struct import * import minimalmodbus -class Ecs(Battery): - def __init__(self, port,baud): - super(Ecs, self).__init__(port,baud) +class Ecs(Battery): + def __init__(self, port, baud): + super(Ecs, self).__init__(port, baud) self.type = self.BATTERYTYPE BATTERYTYPE = "ECS_LiPro" @@ -16,8 +16,8 @@ def __init__(self, port,baud): GREENMETER_ID_250A = 501 GREENMETER_ID_125A = 502 METER_SIZE = "" - # LiPro 100, 101 is untested but should work if they have updated firmware - # that match the registers of the newer models + # LiPro 100, 101 is untested but should work if they have updated firmware + # that match the registers of the newer models LIPRO1X_ID_V1 = 100 LIPRO1X_ID_ACTIVE = 101 # --- @@ -25,25 +25,25 @@ def __init__(self, port,baud): LIPRO1X_ID_ACTIVE_V2 = 103 LIPRO1X_ID_V3 = 104 LiProCells = [] - + def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. # The result or call should be unique to this BMS. Battery name or version, etc. # Return True if success, False for failure - + # Trying to find Green Meter ID try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN tmpId = mbdev.read_register(0, 0) - if tmpId in range(self.GREENMETER_ID_500A,self.GREENMETER_ID_125A+1): + if tmpId in range(self.GREENMETER_ID_500A, self.GREENMETER_ID_125A + 1): if tmpId == self.GREENMETER_ID_500A: self.METER_SIZE = "500A" if tmpId == self.GREENMETER_ID_250A: self.METER_SIZE = "250A" if tmpId == self.GREENMETER_ID_125A: self.METER_SIZE = "125A" - + self.find_LiPro_cells() return self.get_settings() @@ -51,28 +51,28 @@ def test_connection(self): return False def find_LiPro_cells(self): - #test for LiPro cell devices - for cell_address in range(LIPRO_START_ADDRESS, LIPRO_END_ADDRESS+1): + # test for LiPro cell devices + for cell_address in range(LIPRO_START_ADDRESS, LIPRO_END_ADDRESS + 1): try: - mbdev = minimalmodbus.Instrument(self.port, cell_address) + mbdev = minimalmodbus.Instrument(self.port, cell_address) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN tmpId = mbdev.read_register(0, 0) - if tmpId in range(self.LIPRO1X_ID_V1,self.LIPRO1X_ID_V3+1): + if tmpId in range(self.LIPRO1X_ID_V1, self.LIPRO1X_ID_V3 + 1): self.LiProCells.append(cell_address) logger.info("Found LiPro at " + str(cell_address)) self.cells.append(Cell(False)) except IOError: pass - + return True if len(self.LiProCells) > 0 else False def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - + # Uncomment if BMS does not supply capacity self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT @@ -94,15 +94,24 @@ def refresh_data(self): def read_status_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN - - self.max_battery_discharge_current = abs(mbdev.read_register(30, 0, 3, True)) - self.max_battery_charge_current = mbdev.read_register(31, 0, 3, True) - self.capacity = mbdev.read_long(46, 3, False, minimalmodbus.BYTEORDER_LITTLE_SWAP)/1000 - self.production = mbdev.read_long(2, 3, False, minimalmodbus.BYTEORDER_LITTLE_SWAP) - self.hardware_version = "Greenmeter-" + self.METER_SIZE + " " + str(self.cell_count) + " cells" + self.max_battery_discharge_current = abs( + mbdev.read_register(30, 0, 3, True) + ) + self.max_battery_charge_current = mbdev.read_register(31, 0, 3, True) + self.capacity = ( + mbdev.read_long(46, 3, False, minimalmodbus.BYTEORDER_LITTLE_SWAP) + / 1000 + ) + self.production = mbdev.read_long( + 2, 3, False, minimalmodbus.BYTEORDER_LITTLE_SWAP + ) + + self.hardware_version = ( + "Greenmeter-" + self.METER_SIZE + " " + str(self.cell_count) + " cells" + ) logger.info(self.hardware_version) return True @@ -111,39 +120,52 @@ def read_status_data(self): def read_soc_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN - self.voltage = mbdev.read_long(108, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP) / 1000 - self.current = mbdev.read_long(114, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP) / 1000 + self.voltage = ( + mbdev.read_long(108, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP) + / 1000 + ) + self.current = ( + mbdev.read_long(114, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP) + / 1000 + ) # if (mbdev.read_register(129, 0, 3, False) != 65535): - temp_soc = mbdev.read_long(128, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP) - # Fix for Greenmeter that seems to not correctly define/set the high bytes + temp_soc = mbdev.read_long( + 128, 3, True, minimalmodbus.BYTEORDER_LITTLE_SWAP + ) + # Fix for Greenmeter that seems to not correctly define/set the high bytes # if the SOC value is less than 65535 (65.535%). So 50% comes through as #C350 FFFF instead of #C350 0000 - self.soc = (temp_soc if temp_soc < 4294901760 else temp_soc-4294901760) / 1000 - + self.soc = ( + temp_soc if temp_soc < 4294901760 else temp_soc - 4294901760 + ) / 1000 + # self.cycles = None self.total_ah_drawn = None - + self.protection = Protection() - + over_voltage = mbdev.read_register(130, 0, 3, True) under_voltage = mbdev.read_register(131, 0, 3, True) self.charge_fet = True if over_voltage == 0 else False self.discharge_fet = True if under_voltage == 0 else False self.protection.voltage_high = 2 if over_voltage == 1 else 0 self.protection.voltage_low = 2 if under_voltage == 1 else 0 - self.protection.temp_high_charge = 1 if over_voltage in range(3,5) else 0 - self.protection.temp_low_charge = 1 if over_voltage in range(5,7) else 0 - self.protection.temp_high_discharge = 1 if under_voltage in range(3,5) else 0 - self.protection.temp_low_discharge = 1 if under_voltage in range(5,7) else 0 + self.protection.temp_high_charge = 1 if over_voltage in range(3, 5) else 0 + self.protection.temp_low_charge = 1 if over_voltage in range(5, 7) else 0 + self.protection.temp_high_discharge = ( + 1 if under_voltage in range(3, 5) else 0 + ) + self.protection.temp_low_discharge = ( + 1 if under_voltage in range(5, 7) else 0 + ) self.protection.current_over = 1 if over_voltage == 2 else 0 self.protection.current_under = 1 if under_voltage == 2 else 0 - self.temp1 = mbdev.read_register(102, 0, 3, True) / 100 self.temp2 = mbdev.read_register(103, 0, 3, True) / 100 - + return True except IOError: return False @@ -151,11 +173,13 @@ def read_soc_data(self): def read_cell_data(self): for cell in range(len(self.LiProCells)): try: - mbdev = minimalmodbus.Instrument(self.port, self.LiProCells[cell]) + mbdev = minimalmodbus.Instrument(self.port, self.LiProCells[cell]) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN self.cells[cell].voltage = mbdev.read_register(100, 0, 3, False) / 1000 - self.cells[cell].balance = True if mbdev.read_register(102, 0, 3, False) > 50 else False + self.cells[cell].balance = ( + True if mbdev.read_register(102, 0, 3, False) > 50 else False + ) self.cells[cell].temp = mbdev.read_register(101, 0, 3, True) / 100 return True diff --git a/etc/dbus-serialbattery/jkbms.py b/etc/dbus-serialbattery/jkbms.py index efd80fdb..f097cce1 100644 --- a/etc/dbus-serialbattery/jkbms.py +++ b/etc/dbus-serialbattery/jkbms.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import is_bit_set, read_serial_data, logger +import utils +from struct import unpack_from class Jkbms(Battery): - - def __init__(self, port,baud): - super(Jkbms, self).__init__(port,baud) + def __init__(self, port, baud): + super(Jkbms, self).__init__(port, baud) self.type = self.BATTERYTYPE BATTERYTYPE = "Jkbms" LENGTH_CHECK = 1 LENGTH_POS = 2 - LENGTH_SIZE = 'H' + LENGTH_SIZE = "H" CURRENT_ZERO_CONSTANT = 32768 command_status = b"\x4E\x57\x00\x13\x00\x00\x00\x00\x06\x03\x00\x00\x00\x00\x00\x00\x68\x00\x00\x01\x29" @@ -24,7 +24,8 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") pass return result @@ -33,14 +34,14 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count # init the cell array - for c in range(self.cell_count): - self.cells.append(Cell(False)) + for _ in range(self.cell_count): + self.cells.append(Cell(False)) self.hardware_version = "JKBMS " + str(self.cell_count) + " cells" return True @@ -57,9 +58,9 @@ def get_data(self, bytes, idcode, start, length): # logger.debug("start "+str(start) + " length " + str(length)) # logger.debug(binascii.hexlify(bytearray(bytes[start:start + 1 + length])).decode('ascii')) start = bytes.find(idcode, start, start + 1 + length) - if start < 0: return False - return bytes[start+1:start+length+1] - + if start < 0: + return False + return bytes[start + 1 : start + length + 1] def read_status_data(self): status_data = self.read_serial_data_jkbms(self.command_status) @@ -67,106 +68,141 @@ def read_status_data(self): if status_data is False: return False - # cell voltages offset = 1 - cellbyte_count = unpack_from('>B', self.get_data(status_data, b'\x79', offset, 1))[0] + cellbyte_count = unpack_from( + ">B", self.get_data(status_data, b"\x79", offset, 1) + )[0] offset = cellbyte_count + 30 - self.cell_count = unpack_from('>H', self.get_data(status_data, b'\x8A', offset, 2))[0] + self.cell_count = unpack_from( + ">H", self.get_data(status_data, b"\x8A", offset, 2) + )[0] - if cellbyte_count == 3*self.cell_count and self.cell_count == len(self.cells): + if cellbyte_count == 3 * self.cell_count and self.cell_count == len(self.cells): offset = 1 - celldata = self.get_data(status_data, b'\x79', offset, 1 + cellbyte_count) + celldata = self.get_data(status_data, b"\x79", offset, 1 + cellbyte_count) for c in range(self.cell_count): - self.cells[c].voltage = unpack_from('>xH', celldata, c * 3 + 1)[0]/1000 - + self.cells[c].voltage = ( + unpack_from(">xH", celldata, c * 3 + 1)[0] / 1000 + ) + offset = cellbyte_count + 6 - temp1 = unpack_from('>H', self.get_data(status_data, b'\x81', offset, 2))[0] + temp1 = unpack_from(">H", self.get_data(status_data, b"\x81", offset, 2))[0] offset = cellbyte_count + 9 - temp2 = unpack_from('>H', self.get_data(status_data, b'\x82', offset, 2))[0] + temp2 = unpack_from(">H", self.get_data(status_data, b"\x82", offset, 2))[0] self.to_temp(1, temp1 if temp1 < 99 else (100 - temp1)) self.to_temp(2, temp2 if temp2 < 99 else (100 - temp2)) - + offset = cellbyte_count + 12 - voltage = unpack_from('>H', self.get_data(status_data, b'\x83', offset, 2))[0] + voltage = unpack_from(">H", self.get_data(status_data, b"\x83", offset, 2))[0] self.voltage = voltage / 100 offset = cellbyte_count + 15 - current = unpack_from('>H', self.get_data(status_data, b'\x84', offset, 2))[0] - self.current = current / -100 if current < self.CURRENT_ZERO_CONSTANT else (current - self.CURRENT_ZERO_CONSTANT) / 100 + current = unpack_from(">H", self.get_data(status_data, b"\x84", offset, 2))[0] + self.current = ( + current / -100 + if current < self.CURRENT_ZERO_CONSTANT + else (current - self.CURRENT_ZERO_CONSTANT) / 100 + ) offset = cellbyte_count + 18 - self.soc = unpack_from('>B', self.get_data(status_data, b'\x85', offset, 1))[0] + self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0] offset = cellbyte_count + 22 - self.cycles = unpack_from('>H', self.get_data(status_data, b'\x87', offset, 2))[0] + self.cycles = unpack_from(">H", self.get_data(status_data, b"\x87", offset, 2))[ + 0 + ] # offset = cellbyte_count + 25 # self.capacity_remain = unpack_from('>L', self.get_data(status_data, b'\x89', offset, 4))[0] offset = cellbyte_count + 121 - self.capacity = unpack_from('>L', self.get_data(status_data, b'\xAA', offset, 4))[0] - + self.capacity = unpack_from( + ">L", self.get_data(status_data, b"\xAA", offset, 4) + )[0] + offset = cellbyte_count + 33 - self.to_protection_bits(unpack_from('>H', self.get_data(status_data, b'\x8B', offset, 2))[0] ) + self.to_protection_bits( + unpack_from(">H", self.get_data(status_data, b"\x8B", offset, 2))[0] + ) offset = cellbyte_count + 36 - self.to_fet_bits(unpack_from('>H', self.get_data(status_data, b'\x8C', offset, 2))[0] ) + self.to_fet_bits( + unpack_from(">H", self.get_data(status_data, b"\x8C", offset, 2))[0] + ) offset = cellbyte_count + 155 - self.production = unpack_from('>8s', self.get_data(status_data, b'\xB4', offset, 8))[0].decode() + self.production = unpack_from( + ">8s", self.get_data(status_data, b"\xB4", offset, 8) + )[0].decode() offset = cellbyte_count + 174 - self.version = unpack_from('>15s', self.get_data(status_data, b'\xB7', offset, 15))[0].decode() + self.version = unpack_from( + ">15s", self.get_data(status_data, b"\xB7", offset, 15) + )[0].decode() # logger.info(self.hardware_version) return True - + def to_fet_bits(self, byte_data): - tmp = bin(byte_data)[2:].rjust(2, zero_char) + tmp = bin(byte_data)[2:].rjust(2, utils.zero_char) self.charge_fet = is_bit_set(tmp[1]) self.discharge_fet = is_bit_set(tmp[0]) def to_protection_bits(self, byte_data): - pos=13 - tmp = bin(byte_data)[15-pos:].rjust(pos + 1, zero_char) + pos = 13 + tmp = bin(byte_data)[15 - pos :].rjust(pos + 1, utils.zero_char) # logger.debug(tmp) - self.protection.soc_low = 2 if is_bit_set(tmp[pos-0]) else 0 - self.protection.set_IC_inspection = 2 if is_bit_set(tmp[pos-1]) else 0 # BMS over temp - self.protection.voltage_high = 2 if is_bit_set(tmp[pos-2]) else 0 - self.protection.voltage_low = 2 if is_bit_set(tmp[pos-3]) else 0 - self.protection.current_over = 1 if is_bit_set(tmp[pos-5]) else 0 - self.protection.current_under = 1 if is_bit_set(tmp[pos-6]) else 0 - self.protection.cell_imbalance = 2 if is_bit_set(tmp[pos-7]) else 1 if is_bit_set(tmp[pos-10]) else 0 - self.protection.voltage_cell_low = 2 if is_bit_set(tmp[pos-11]) else 0 + self.protection.soc_low = 2 if is_bit_set(tmp[pos - 0]) else 0 + self.protection.set_IC_inspection = ( + 2 if is_bit_set(tmp[pos - 1]) else 0 + ) # BMS over temp + self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0 + self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0 + self.protection.current_over = 1 if is_bit_set(tmp[pos - 5]) else 0 + self.protection.current_under = 1 if is_bit_set(tmp[pos - 6]) else 0 + self.protection.cell_imbalance = ( + 2 if is_bit_set(tmp[pos - 7]) else 1 if is_bit_set(tmp[pos - 10]) else 0 + ) + self.protection.voltage_cell_low = 2 if is_bit_set(tmp[pos - 11]) else 0 # there is just a BMS and Battery temp alarm (not high/low) - self.protection.temp_high_charge = 1 if is_bit_set(tmp[pos-4]) or is_bit_set(tmp[pos-8]) else 0 - self.protection.temp_low_charge = 1 if is_bit_set(tmp[pos-4]) or is_bit_set(tmp[pos-8]) else 0 - self.protection.temp_high_discharge = 1 if is_bit_set(tmp[pos-4]) or is_bit_set(tmp[pos-8]) else 0 - self.protection.temp_low_discharge = 1 if is_bit_set(tmp[pos-4]) or is_bit_set(tmp[pos-8]) else 0 + self.protection.temp_high_charge = ( + 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + ) + self.protection.temp_low_charge = ( + 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + ) + self.protection.temp_high_discharge = ( + 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + ) + self.protection.temp_low_discharge = ( + 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + ) - def read_serial_data_jkbms(self, command): # use the read_serial_data() function to read the data and then do BMS spesific checks (crc, start bytes, etc) - data = read_serial_data(command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK,None, self.LENGTH_SIZE) + data = read_serial_data( + command, + self.port, + self.baud_rate, + self.LENGTH_POS, + self.LENGTH_CHECK, + None, + self.LENGTH_SIZE, + ) if data is False: return False - start, length = unpack_from('>HH', data) - terminal = unpack_from('>L', data[4:])[0] - cmd, src, tt = unpack_from('>bbb', data[8:]) - frame = unpack_from('>H', data[-9:])[0] - end, crc_hi, crc_lo = unpack_from('>BHH', data[-5:]) + start, length = unpack_from(">HH", data) + end, crc_hi, crc_lo = unpack_from(">BHH", data[-5:]) s = sum(data[0:-4]) - # logger.debug('S%d C%d C%d' % (s, crc_lo, crc_hi)) - # logger.debug('T%d C%d S%d F%d TT%d' % (terminal, cmd, src, frame, tt)) if start == 0x4E57 and end == 0x68 and s == crc_lo: - return data[10:length-19] + return data[10 : length - 19] elif s != crc_lo: - logger.error('CRC checksum mismatch: Expected 0x%04x, Got 0x%04x' % (crc_lo, s)) + logger.error( + "CRC checksum mismatch: Expected 0x%04x, Got 0x%04x" % (crc_lo, s) + ) return False else: logger.error(">>> ERROR: Incorrect Reply ") return False - - \ No newline at end of file diff --git a/etc/dbus-serialbattery/lifepower.py b/etc/dbus-serialbattery/lifepower.py index 86f83a66..90e31ce5 100644 --- a/etc/dbus-serialbattery/lifepower.py +++ b/etc/dbus-serialbattery/lifepower.py @@ -6,7 +6,6 @@ class Lifepower(Battery): - def __init__(self, port, baud): super(Lifepower, self).__init__(port, baud) self.type = self.BATTERYTYPE @@ -57,8 +56,13 @@ def read_status_data(self): # 01 02 0a 0b 0c 0d group_len = status_data[i + 1] end = i + 2 + (group_len * 2) - group_payload = status_data[i + 2:end] - groups.append([unpack_from('>H', group_payload, i)[0] for i in range(0, len(group_payload), 2)]) + group_payload = status_data[i + 2 : end] + groups.append( + [ + unpack_from(">H", group_payload, i)[0] + for i in range(0, len(group_payload), 2) + ] + ) i = end @@ -115,8 +119,14 @@ def get_balancing(self): def read_serial_data_eg4(self, command): # use the read_serial_data() function to read the data and then do BMS # specific checks (crc, start bytes, etc) - data = read_serial_data(command, self.port, self.baud_rate, - self.LENGTH_POS, self.LENGTH_CHECK, self.LENGTH_FIXED) + data = read_serial_data( + command, + self.port, + self.baud_rate, + self.LENGTH_POS, + self.LENGTH_CHECK, + self.LENGTH_FIXED, + ) if data is False: logger.error(">>> ERROR: Incorrect Data") return False diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py index e6161716..e8beb480 100644 --- a/etc/dbus-serialbattery/lltjbd.py +++ b/etc/dbus-serialbattery/lltjbd.py @@ -3,8 +3,8 @@ from utils import * from struct import * -class LltJbdProtection(Protection): +class LltJbdProtection(Protection): def __init__(self): super(LltJbdProtection, self).__init__() self.voltage_high_cell = False @@ -15,41 +15,42 @@ def __init__(self): def set_voltage_high_cell(self, value): self.voltage_high_cell = value - self.cell_imbalance = 2 if self.voltage_low_cell \ - or self.voltage_high_cell else 0 + self.cell_imbalance = ( + 2 if self.voltage_low_cell or self.voltage_high_cell else 0 + ) def set_voltage_low_cell(self, value): self.voltage_low_cell = value - self.cell_imbalance = 2 if self.voltage_low_cell \ - or self.voltage_high_cell else 0 + self.cell_imbalance = ( + 2 if self.voltage_low_cell or self.voltage_high_cell else 0 + ) def set_short(self, value): self.short = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) def set_ic_inspection(self, value): self.IC_inspection = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) def set_software_lock(self, value): self.software_lock = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) class LltJbd(Battery): - - def __init__(self, port,baud): - super(LltJbd, self).__init__(port,baud) + def __init__(self, port, baud): + super(LltJbd, self).__init__(port, baud) self.protection = LltJbdProtection() self.type = self.BATTERYTYPE -# degree_sign = u'\N{DEGREE SIGN}' + # degree_sign = u'\N{DEGREE SIGN}' command_general = b"\xDD\xA5\x03\x00\xFF\xFD\x77" command_cell = b"\xDD\xA5\x04\x00\xFF\xFC\x77" command_hardware = b"\xDD\xA5\x05\x00\xFF\xFB\x77" @@ -65,7 +66,7 @@ def test_connection(self): pass return result - + def get_settings(self): self.read_gen_data() self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT @@ -90,7 +91,9 @@ def to_protection_bits(self, byte_data): self.protection.current_under = 1 if is_bit_set(tmp[3]) else 0 # Software implementations for low soc - self.protection.soc_low = 2 if self.soc < SOC_LOW_ALARM else 1 if self.soc < SOC_LOW_WARNING else 0 + self.protection.soc_low = ( + 2 if self.soc < SOC_LOW_ALARM else 1 if self.soc < SOC_LOW_WARNING else 0 + ) # extra protection flags for LltJbd self.protection.set_voltage_low_cell = is_bit_set(tmp[11]) @@ -109,7 +112,7 @@ def to_cell_bits(self, byte_data, byte_data_high): self.cells.append(Cell(is_bit_set(bit))) # get any cells above 16 if self.cell_count > 16: - tmp = bin(byte_data_high)[2:].rjust(self.cell_count-16, zero_char) + tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16, zero_char) for bit in reversed(tmp): self.cells.append(Cell(is_bit_set(bit))) @@ -124,9 +127,22 @@ def read_gen_data(self): if gen_data is False or len(gen_data) < 27: return False - voltage, current, capacity_remain, capacity, self.cycles, self.production, balance, \ - balance2, protection, version, self.soc, fet, self.cell_count, self.temp_sensors \ - = unpack_from('>HhHHHHhHHBBBBB', gen_data) + ( + voltage, + current, + capacity_remain, + capacity, + self.cycles, + self.production, + balance, + balance2, + protection, + version, + self.soc, + fet, + self.cell_count, + self.temp_sensors, + ) = unpack_from(">HhHHHHhHHBBBBB", gen_data) self.voltage = voltage / 100 self.current = current / 100 self.capacity_remain = capacity_remain / 100 @@ -139,20 +155,20 @@ def read_gen_data(self): self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count for t in range(self.temp_sensors): - temp1 = unpack_from('>H', gen_data, 23 + (2*t))[0] + temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0] self.to_temp(t + 1, kelvin_to_celsius(temp1 / 10)) - + return True def read_cell_data(self): cell_data = self.read_serial_data_llt(self.command_cell) # check if connect success - if cell_data is False or len(cell_data) < self.cell_count*2: + if cell_data is False or len(cell_data) < self.cell_count * 2: return False for c in range(self.cell_count): try: - cell_volts = unpack_from('>H', cell_data, c * 2) + cell_volts = unpack_from(">H", cell_data, c * 2) if len(cell_volts) != 0: self.cells[c].voltage = cell_volts[0] / 1000 except struct.error: @@ -165,20 +181,24 @@ def read_hardware_data(self): if hardware_data is False: return False - self.hardware_version = unpack_from('>' + str(len(hardware_data)) + 's', hardware_data)[0].decode() + self.hardware_version = unpack_from( + ">" + str(len(hardware_data)) + "s", hardware_data + )[0].decode() logger.debug(self.hardware_version) return True def read_serial_data_llt(self, command): - data = read_serial_data(command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK) + data = read_serial_data( + command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK + ) if data is False: return False - start, flag, command_ret, length = unpack_from('BBBB', data) - checksum, end = unpack_from('HB', data, length + 4) + start, flag, command_ret, length = unpack_from("BBBB", data) + checksum, end = unpack_from("HB", data, length + 4) if end == 119: - return data[4:length + 4] + return data[4 : length + 4] else: logger.error(">>> ERROR: Incorrect Reply") - return False \ No newline at end of file + return False diff --git a/etc/dbus-serialbattery/minimalmodbus.py b/etc/dbus-serialbattery/minimalmodbus.py index 0d132248..dc00434b 100644 --- a/etc/dbus-serialbattery/minimalmodbus.py +++ b/etc/dbus-serialbattery/minimalmodbus.py @@ -2093,7 +2093,7 @@ def _num_to_twobyte_string( _check_bool(lsb_first, description="lsb_first") _check_bool(signed, description="signed parameter") - multiplier = 10 ** number_of_decimals + multiplier = 10**number_of_decimals integer = int(float(value) * multiplier) if lsb_first: @@ -2161,7 +2161,7 @@ def _twobyte_string_to_num( if number_of_decimals == 0: return fullregister - divisor = 10 ** number_of_decimals + divisor = 10**number_of_decimals return fullregister / float(divisor) @@ -2886,7 +2886,7 @@ def _twos_complement(x: int, bits: int = 16) -> int: # Calculate two'2 complement if x >= 0: return x - return int(x + 2 ** bits) + return int(x + 2**bits) def _from_twos_complement(x: int, bits: int = 16) -> int: @@ -2930,7 +2930,7 @@ def _from_twos_complement(x: int, bits: int = 16) -> int: limit = 2 ** (bits - 1) - 1 if x <= limit: return x - return int(x - 2 ** bits) + return int(x - 2**bits) # ################ # diff --git a/etc/dbus-serialbattery/mnb.py b/etc/dbus-serialbattery/mnb.py index c9cfa7ce..233d624a 100644 --- a/etc/dbus-serialbattery/mnb.py +++ b/etc/dbus-serialbattery/mnb.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from battery import Protection, Battery, Cell from struct import * -#from test_max17853 import *#{these two lines are mutually} -from util_max17853 import * #{exclusive. use test for testing} -class MNBProtection(Protection): +# from test_max17853 import *#{these two lines are mutually} +from util_max17853 import * # {exclusive. use test for testing} + +class MNBProtection(Protection): def __init__(self): super(MNBProtection, self).__init__() self.voltage_high_cell = False @@ -16,37 +17,38 @@ def __init__(self): def set_voltage_high_cell(self, value): self.voltage_high_cell = value - self.set_cell_imbalance(2 if self.voltage_low_cell \ - or self.voltage_high_cell else 0) + self.set_cell_imbalance( + 2 if self.voltage_low_cell or self.voltage_high_cell else 0 + ) def set_voltage_low_cell(self, value): self.voltage_low_cell = value - self.set_cell_imbalance(2 if self.voltage_low_cell \ - or self.voltage_high_cell else 0) + self.set_cell_imbalance( + 2 if self.voltage_low_cell or self.voltage_high_cell else 0 + ) def set_short(self, value): self.short = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) def set_ic_inspection(self, value): self.IC_inspection = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) def set_software_lock(self, value): self.software_lock = value - self.set_cell_imbalance(2 if self.short \ - or self.IC_inspection \ - or self.software_lock else 0) + self.set_cell_imbalance( + 2 if self.short or self.IC_inspection or self.software_lock else 0 + ) class MNB(Battery): - - def __init__(self, port,baud,address=0): - super(MNB, self).__init__(port,baud) + def __init__(self, port, baud, address=0): + super(MNB, self).__init__(port, baud) self.protection = MNBProtection() self.hardware_version = 1.02 self.voltage = 26 @@ -77,10 +79,10 @@ def __init__(self, port,baud,address=0): self.current = None self.temp3 = None self.temp4 = None - self.cells= [] - - BATTERYTYPE = "MNB-Li SPI" - + self.cells = [] + + BATTERYTYPE = "MNB-Li SPI" + def test_connection(self): self.get_settings() init_max(self) @@ -95,25 +97,29 @@ def test_connection(self): def get_settings(self): # imutable constants for the battery # Need to include this in BMS initialisation - # Thresholds need to be set also, or derived from + # Thresholds need to be set also, or derived from # cell voltage values. Other BMS user parameters # also need to be defined here so all settings are in one place. - #***************************************************************** - self.inst_capacity = 36*3.6 # Equivalent cell capacity Ah - self.C_rating = 1 # Max current/Ah eg 1, 0.5 or 0.25 - self.max_battery_charge_current = self.inst_capacity*self.C_rating #MAX_BATTERY_CHARGE_CURRENT = Crating * Capacity - self.max_battery_discharge_current = self.inst_capacity*self.C_rating #MAX_BATTERY_DISCHARGE_CURRENT - self.V_C_min = 2.55 # Min cell voltage permitted - self.V_C_max = 3.65 # Max cell voltage permitted - self.cell_count = 8 # Number of cells in series (max) 8 for 24V + # ***************************************************************** + self.inst_capacity = 36 * 3.6 # Equivalent cell capacity Ah + self.C_rating = 1 # Max current/Ah eg 1, 0.5 or 0.25 + self.max_battery_charge_current = ( + self.inst_capacity * self.C_rating + ) # MAX_BATTERY_CHARGE_CURRENT = Crating * Capacity + self.max_battery_discharge_current = ( + self.inst_capacity * self.C_rating + ) # MAX_BATTERY_DISCHARGE_CURRENT + self.V_C_min = 2.55 # Min cell voltage permitted + self.V_C_max = 3.65 # Max cell voltage permitted + self.cell_count = 8 # Number of cells in series (max) 8 for 24V self.version = "V2.01" - self.temp_sensors =6 + self.temp_sensors = 6 self.T_C_max = 40 self.T_C_min = 15 self.max_battery_voltage = self.V_C_max * self.cell_count self.min_battery_voltage = self.V_C_min * self.cell_count self.hardware_version = "MNB_BMS " + str(self.cell_count) + " cells" - self.poll_interval = 1000 #scan repeat time, ms + self.poll_interval = 1000 # scan repeat time, ms for c in range(self.cell_count): self.cells.append(Cell(False)) return True @@ -127,45 +133,44 @@ def read_status_data(self): # used once in init... self.charger_connected = True self.load_connected = True - self.state = True + self.state = True self.cycles = None - - return True + return True def manage_charge_current(self): # Start with the current values # Change depending on the cell_max_voltage values - if self.cell_max_voltage > self.V_C_max-.05: + if self.cell_max_voltage > self.V_C_max - 0.05: self.control_allow_charge = False else: self.control_allow_charge = True # Change depending on the cell_max_voltage values - if self.cell_max_voltage > self.V_C_max-0.15: - b= 10*(self.V_C_max - self.cell_max_voltage-.05) - if b >1: - b=1 - if b <0: + if self.cell_max_voltage > self.V_C_max - 0.15: + b = 10 * (self.V_C_max - self.cell_max_voltage - 0.05) + if b > 1: + b = 1 + if b < 0: b = 0 - self.control_charge_current = self.max_battery_charge_current *b - + self.control_charge_current = self.max_battery_charge_current * b + else: self.control_charge_current = self.max_battery_charge_current # Change depending on the cell_min_voltage values - if self.cell_min_voltage < self.V_C_min+0.05: - self.control_allow_dicharge = False + if self.cell_min_voltage < self.V_C_min + 0.05: + self.control_allow_dicharge = False else: - self.control_allow_dicharge = True - - if self.cell_min_voltage < self.V_C_min+0.15: - b = 10*(self.cell_min_voltage - self.V_C_min-.05) + self.control_allow_dicharge = True + + if self.cell_min_voltage < self.V_C_min + 0.15: + b = 10 * (self.cell_min_voltage - self.V_C_min - 0.05) if b > 1: - b=1 + b = 1 if b < 0: b = 0 - self.control_discharge_current = self.max_battery_discharge_current*b + self.control_discharge_current = self.max_battery_discharge_current * b else: - self.control_discharge_current = self.max_battery_discharge_current + self.control_discharge_current = self.max_battery_discharge_current diff --git a/etc/dbus-serialbattery/qml/PageBattery.qml b/etc/dbus-serialbattery/qml/PageBattery.qml index 392b3e86..ad8f6614 100644 --- a/etc/dbus-serialbattery/qml/PageBattery.qml +++ b/etc/dbus-serialbattery/qml/PageBattery.qml @@ -2,370 +2,370 @@ import QtQuick 1.1 import com.victron.velib 1.0 MbPage { - id: root - - property variant service - property string bindPrefix - - property VBusItem hasSettings: VBusItem { bind: service.path("/Settings/HasSettings") } - property VBusItem dcVoltage: VBusItem { bind: service.path("/Dc/0/Voltage") } - property VBusItem dcCurrent: VBusItem { bind: service.path("/Dc/0/Current") } - property VBusItem midVoltage: VBusItem { bind: service.path("/Dc/0/MidVoltage") } - property VBusItem productId: VBusItem { bind: service.path("/ProductId") } - property VBusItem cell1: VBusItem { bind: service.path("/Voltages/Cell1") } - property VBusItem nrOfDistributors: VBusItem { bind: service.path("/NrOfDistributors") } - - property PageLynxDistributorList distributorListPage - - property bool isFiamm48TL: productId.value === 0xB012 - property int numberOfDistributors: nrOfDistributors.valid ? nrOfDistributors.value : 0 - - title: service.description - summary: [soc.item.format(0), dcVoltage.text, dcCurrent.text] - - /* PageLynxDistributorList cannot use Component for its subpages, because of the summary. - * Therefor create it upon reception of /NrOfDistributors instead of when accessing the page - * to prevent a ~3s loading time. */ - onNumberOfDistributorsChanged: { - if (distributorListPage == undefined && numberOfDistributors > 0) { - distributorListPage = distributorPageComponent.createObject(root) - } - } - - Component { - id: distributorPageComponent - PageLynxDistributorList { - bindPrefix: service.path("") - } - } - - model: VisualItemModel { - MbItemOptions { - description: qsTr("Switch") - bind: service.path("/Mode") - show: item.valid - - possibleValues: [ - MbOption { description: qsTr("Off"); value: 4; readonly: true }, - MbOption { description: qsTr("Standby"); value: 0xfc }, - MbOption { description: qsTr("On"); value: 3 } - ] - } - - MbItemOptions { - description: qsTr("State") - bind: service.path("/State") - readonly: true - show: item.valid - possibleValues:[ - MbOption { description: qsTr("Initializing"); value: 0 }, - MbOption { description: qsTr("Initializing"); value: 1 }, - MbOption { description: qsTr("Initializing"); value: 2 }, - MbOption { description: qsTr("Initializing"); value: 3 }, - MbOption { description: qsTr("Initializing"); value: 4 }, - MbOption { description: qsTr("Initializing"); value: 5 }, - MbOption { description: qsTr("Initializing"); value: 6 }, - MbOption { description: qsTr("Initializing"); value: 7 }, - MbOption { description: qsTr("Initializing"); value: 8 }, - MbOption { description: qsTr("Running"); value: 9 }, - MbOption { description: qsTr("Error"); value: 10 }, -// MbOption { description: qsTr("Unknown"); value: 11 }, - MbOption { description: qsTr("Shutdown"); value: 12 }, - MbOption { description: qsTr("Updating"); value: 13 }, - MbOption { description: qsTr("Standby"); value: 14 }, - MbOption { description: qsTr("Going to run"); value: 15 }, - MbOption { description: qsTr("Pre-Charging"); value: 16 }, - MbOption { description: qsTr("Contactor check"); value: 17 } - ] - } - - MbItemBmsError { - description: qsTr("Error") - item.bind: service.path("/ErrorCode") - show: item.valid - } - - MbItemRow { - description: qsTr("Battery") - values: [ - MbTextBlock { item: dcVoltage; width: 90; height: 25 }, - MbTextBlock { item: dcCurrent; width: 90; height: 25 }, - MbTextBlock { item.bind: service.path("/Dc/0/Power"); width: 90; height: 25 } - ] - } - - MbItemValue { - id: soc - - description: qsTr("State of charge") - item { - bind: service.path("/Soc") - unit: "%" - } - } - - MbItemValue { - description: qsTr("State of health") - item.bind: service.path("/Soh") - show: item.valid - } - - MbItemValue { - description: qsTr("Battery temperature") - show: item.valid - item { - bind: service.path("/Dc/0/Temperature") - displayUnit: user.temperatureUnit - } - } - - MbItemValue { - description: qsTr("Air temperature") - item { - bind: service.path("/AirTemperature") - displayUnit: user.temperatureUnit - } - show: item.valid - } - - MbItemValue { - description: qsTr("Starter voltage") - item.bind: service.path("/Dc/1/Voltage") - show: item.valid - } - - MbItemValue { - description: qsTr("Bus voltage") - item.bind: service.path("/BusVoltage") - show: item.valid - } - - MbItemValue { - description: qsTr("Top section voltage") - item { - value: midVoltage.valid && dcVoltage.valid ? dcVoltage.value - midVoltage.value : undefined - unit: "V" - decimals: 2 - } - show: midVoltage.valid - } - - MbItemValue { - description: qsTr("Bottom section voltage") - item: midVoltage - show: item.valid - } - - MbItemValue { - description: qsTr("Mid-point deviation") - item.bind: service.path("/Dc/0/MidVoltageDeviation") - show: item.valid - } - - MbItemValue { - description: qsTr("Consumed AmpHours") - item.bind: service.path("/ConsumedAmphours") - show: item.valid - } - - MbItemValue { - description: qsTr("Bus voltage") - item.bind: service.path("/BussVoltage") - show: item.valid - } - - /* Time to go also needs to display infinite value */ - MbItemTimeSpan { - description: qsTr("Time-to-go") - item.bind: service.path("/TimeToGo") - show: item.seen - } - - MbItemOptions { - description: qsTr("Relay state") - bind: service.path("/Relay/0/State") - readonly: true - possibleValues:[ - MbOption { description: qsTr("Off"); value: 0 }, - MbOption { description: qsTr("On"); value: 1 } - ] - show: valid - } - - MbItemOptions { - description: qsTr("Alarm state") - bind: service.path("/Alarms/Alarm") - readonly: true - possibleValues:[ - MbOption { description: qsTr("Ok"); value: 0 }, - MbOption { description: qsTr("Alarm"); value: 1 } - ] - show: valid - } - - MbSubMenu { - description: qsTr("Details") - show: details.anyItemValid - - property BatteryDetails details: BatteryDetails { id: details; bindPrefix: service.path("") } - - subpage: Component { - PageBatteryDetails { - bindPrefix: service.path("") - details: details - } - } - } - - MbSubMenu { - description: qsTr("Cell Voltages") - show: cell1.valid - subpage: Component { - PageBatteryCellVoltages { - bindPrefix: service.path("") - } - } - } - - /*MbSubMenu { - description: qsTr("Setup") - subpage: Component { - PageBatterySetup { - bindPrefix: service.path("") - } - } - }*/ - - MbSubMenu { - description: qsTr("Alarms") - subpage: Component { - PageBatteryAlarms { - title: qsTr("Alarms") - bindPrefix: service.path("") - } - } - } - - MbSubMenu { - description: qsTr("History") - subpage: Component { - PageBatteryHistory { - title: qsTr("History") - bindPrefix: service.path("") - } - } - show: !isFiamm48TL - } - - MbSubMenu { - id: settings - description: qsTr("Settings") - show: hasSettings.value === 1 - subpage: Component { - PageBatterySettings { - title: settings.description - bindPrefix: service.path("") - } - } - } - - MbSubMenu { - property VBusItem lastError: VBusItem { bind: service.path("/Diagnostics/LastErrors/1/Error") } - - description: qsTr("Diagnostics") - subpage: Component { - PageLynxIonDiagnostics { - title: qsTr("Diagnostics") - bindPrefix: service.path("") - } - } - show: lastError.valid - } - - MbSubMenu { - description: qsTr("Diagnostics") - subpage: Component { - Page48TlDiagnostics { - title: qsTr("Diagnostics") - bindPrefix: service.path("") - } - } - show: isFiamm48TL - } - - MbSubMenu { - description: qsTr("Fuses") - subpage: distributorListPage - show: numberOfDistributors > 0 - } - - MbSubMenu { - property VBusItem allowToCharge: VBusItem { bind: service.path("/Io/AllowToCharge") } - - description: qsTr("IO") - subpage: Component { - PageLynxIonIo { - title: qsTr("IO") - bindPrefix: service.path("") - } - } - show: allowToCharge.valid - } - - MbSubMenu { - property VBusItem nrOfBatteries: VBusItem { bind: service.path("/System/NrOfBatteries") } - - description: qsTr("System") - subpage: Component { - PageLynxIonSystem { - title: qsTr("System") - bindPrefix: service.path("") - } - } - show: nrOfBatteries.valid - } - - MbSubMenu { - description: qsTr("Device") - subpage: Component { - PageDeviceInfo { - title: qsTr("Device") - bindPrefix: service.path("") - } - } - } - - MbSubMenu { - property VBusItem cvl: VBusItem { bind: service.path("/Info/MaxChargeVoltage") } - property VBusItem ccl: VBusItem { bind: service.path("/Info/MaxChargeCurrent") } - property VBusItem dcl: VBusItem { bind: service.path("/Info/MaxDischargeCurrent") } - - description: qsTr("Parameters") - show: cvl.valid || ccl.valid || dcl.valid - subpage: Component { - PageBatteryParameters { - title: qsTr("Parameters") - service: root.service - } - } - } - - MbOK { - VBusItem { - id: redetect - bind: service.path("/Redetect") - } - - description: qsTr("Redetect Battery") - value: qsTr("Press to redetect") - editable: redetect.value === 0 - show: redetect.valid - cornerMark: false - writeAccessLevel: User.AccessUser - onClicked: { - redetect.setValue(1) - toast.createToast(qsTr("Redetecting the battery may take up time 60 seconds. Meanwhile the name of the battery may be incorrect."), 10000); - } - } - } + id: root + + property variant service + property string bindPrefix + + property VBusItem hasSettings: VBusItem { bind: service.path("/Settings/HasSettings") } + property VBusItem dcVoltage: VBusItem { bind: service.path("/Dc/0/Voltage") } + property VBusItem dcCurrent: VBusItem { bind: service.path("/Dc/0/Current") } + property VBusItem midVoltage: VBusItem { bind: service.path("/Dc/0/MidVoltage") } + property VBusItem productId: VBusItem { bind: service.path("/ProductId") } + property VBusItem cell1: VBusItem { bind: service.path("/Voltages/Cell1") } + property VBusItem nrOfDistributors: VBusItem { bind: service.path("/NrOfDistributors") } + + property PageLynxDistributorList distributorListPage + + property bool isFiamm48TL: productId.value === 0xB012 + property int numberOfDistributors: nrOfDistributors.valid ? nrOfDistributors.value : 0 + + title: service.description + summary: [soc.item.format(0), dcVoltage.text, dcCurrent.text] + + /* PageLynxDistributorList cannot use Component for its subpages, because of the summary. + * Therefor create it upon reception of /NrOfDistributors instead of when accessing the page + * to prevent a ~3s loading time. */ + onNumberOfDistributorsChanged: { + if (distributorListPage == undefined && numberOfDistributors > 0) { + distributorListPage = distributorPageComponent.createObject(root) + } + } + + Component { + id: distributorPageComponent + PageLynxDistributorList { + bindPrefix: service.path("") + } + } + + model: VisualItemModel { + MbItemOptions { + description: qsTr("Switch") + bind: service.path("/Mode") + show: item.valid + + possibleValues: [ + MbOption { description: qsTr("Off"); value: 4; readonly: true }, + MbOption { description: qsTr("Standby"); value: 0xfc }, + MbOption { description: qsTr("On"); value: 3 } + ] + } + + MbItemOptions { + description: qsTr("State") + bind: service.path("/State") + readonly: true + show: item.valid + possibleValues:[ + MbOption { description: qsTr("Initializing"); value: 0 }, + MbOption { description: qsTr("Initializing"); value: 1 }, + MbOption { description: qsTr("Initializing"); value: 2 }, + MbOption { description: qsTr("Initializing"); value: 3 }, + MbOption { description: qsTr("Initializing"); value: 4 }, + MbOption { description: qsTr("Initializing"); value: 5 }, + MbOption { description: qsTr("Initializing"); value: 6 }, + MbOption { description: qsTr("Initializing"); value: 7 }, + MbOption { description: qsTr("Initializing"); value: 8 }, + MbOption { description: qsTr("Running"); value: 9 }, + MbOption { description: qsTr("Error"); value: 10 }, +// MbOption { description: qsTr("Unknown"); value: 11 }, + MbOption { description: qsTr("Shutdown"); value: 12 }, + MbOption { description: qsTr("Updating"); value: 13 }, + MbOption { description: qsTr("Standby"); value: 14 }, + MbOption { description: qsTr("Going to run"); value: 15 }, + MbOption { description: qsTr("Pre-Charging"); value: 16 }, + MbOption { description: qsTr("Contactor check"); value: 17 } + ] + } + + MbItemBmsError { + description: qsTr("Error") + item.bind: service.path("/ErrorCode") + show: item.valid + } + + MbItemRow { + description: qsTr("Battery") + values: [ + MbTextBlock { item: dcVoltage; width: 90; height: 25 }, + MbTextBlock { item: dcCurrent; width: 90; height: 25 }, + MbTextBlock { item.bind: service.path("/Dc/0/Power"); width: 90; height: 25 } + ] + } + + MbItemValue { + id: soc + + description: qsTr("State of charge") + item { + bind: service.path("/Soc") + unit: "%" + } + } + + MbItemValue { + description: qsTr("State of health") + item.bind: service.path("/Soh") + show: item.valid + } + + MbItemValue { + description: qsTr("Battery temperature") + show: item.valid + item { + bind: service.path("/Dc/0/Temperature") + displayUnit: user.temperatureUnit + } + } + + MbItemValue { + description: qsTr("Air temperature") + item { + bind: service.path("/AirTemperature") + displayUnit: user.temperatureUnit + } + show: item.valid + } + + MbItemValue { + description: qsTr("Starter voltage") + item.bind: service.path("/Dc/1/Voltage") + show: item.valid + } + + MbItemValue { + description: qsTr("Bus voltage") + item.bind: service.path("/BusVoltage") + show: item.valid + } + + MbItemValue { + description: qsTr("Top section voltage") + item { + value: midVoltage.valid && dcVoltage.valid ? dcVoltage.value - midVoltage.value : undefined + unit: "V" + decimals: 2 + } + show: midVoltage.valid + } + + MbItemValue { + description: qsTr("Bottom section voltage") + item: midVoltage + show: item.valid + } + + MbItemValue { + description: qsTr("Mid-point deviation") + item.bind: service.path("/Dc/0/MidVoltageDeviation") + show: item.valid + } + + MbItemValue { + description: qsTr("Consumed AmpHours") + item.bind: service.path("/ConsumedAmphours") + show: item.valid + } + + MbItemValue { + description: qsTr("Bus voltage") + item.bind: service.path("/BussVoltage") + show: item.valid + } + + /* Time to go also needs to display infinite value */ + MbItemTimeSpan { + description: qsTr("Time-to-go") + item.bind: service.path("/TimeToGo") + show: item.seen + } + + MbItemOptions { + description: qsTr("Relay state") + bind: service.path("/Relay/0/State") + readonly: true + possibleValues:[ + MbOption { description: qsTr("Off"); value: 0 }, + MbOption { description: qsTr("On"); value: 1 } + ] + show: valid + } + + MbItemOptions { + description: qsTr("Alarm state") + bind: service.path("/Alarms/Alarm") + readonly: true + possibleValues:[ + MbOption { description: qsTr("Ok"); value: 0 }, + MbOption { description: qsTr("Alarm"); value: 1 } + ] + show: valid + } + + MbSubMenu { + description: qsTr("Details") + show: details.anyItemValid + + property BatteryDetails details: BatteryDetails { id: details; bindPrefix: service.path("") } + + subpage: Component { + PageBatteryDetails { + bindPrefix: service.path("") + details: details + } + } + } + + MbSubMenu { + description: qsTr("Cell Voltages") + show: cell1.valid + subpage: Component { + PageBatteryCellVoltages { + bindPrefix: service.path("") + } + } + } + + /*MbSubMenu { + description: qsTr("Setup") + subpage: Component { + PageBatterySetup { + bindPrefix: service.path("") + } + } + }*/ + + MbSubMenu { + description: qsTr("Alarms") + subpage: Component { + PageBatteryAlarms { + title: qsTr("Alarms") + bindPrefix: service.path("") + } + } + } + + MbSubMenu { + description: qsTr("History") + subpage: Component { + PageBatteryHistory { + title: qsTr("History") + bindPrefix: service.path("") + } + } + show: !isFiamm48TL + } + + MbSubMenu { + id: settings + description: qsTr("Settings") + show: hasSettings.value === 1 + subpage: Component { + PageBatterySettings { + title: settings.description + bindPrefix: service.path("") + } + } + } + + MbSubMenu { + property VBusItem lastError: VBusItem { bind: service.path("/Diagnostics/LastErrors/1/Error") } + + description: qsTr("Diagnostics") + subpage: Component { + PageLynxIonDiagnostics { + title: qsTr("Diagnostics") + bindPrefix: service.path("") + } + } + show: lastError.valid + } + + MbSubMenu { + description: qsTr("Diagnostics") + subpage: Component { + Page48TlDiagnostics { + title: qsTr("Diagnostics") + bindPrefix: service.path("") + } + } + show: isFiamm48TL + } + + MbSubMenu { + description: qsTr("Fuses") + subpage: distributorListPage + show: numberOfDistributors > 0 + } + + MbSubMenu { + property VBusItem allowToCharge: VBusItem { bind: service.path("/Io/AllowToCharge") } + + description: qsTr("IO") + subpage: Component { + PageLynxIonIo { + title: qsTr("IO") + bindPrefix: service.path("") + } + } + show: allowToCharge.valid + } + + MbSubMenu { + property VBusItem nrOfBatteries: VBusItem { bind: service.path("/System/NrOfBatteries") } + + description: qsTr("System") + subpage: Component { + PageLynxIonSystem { + title: qsTr("System") + bindPrefix: service.path("") + } + } + show: nrOfBatteries.valid + } + + MbSubMenu { + description: qsTr("Device") + subpage: Component { + PageDeviceInfo { + title: qsTr("Device") + bindPrefix: service.path("") + } + } + } + + MbSubMenu { + property VBusItem cvl: VBusItem { bind: service.path("/Info/MaxChargeVoltage") } + property VBusItem ccl: VBusItem { bind: service.path("/Info/MaxChargeCurrent") } + property VBusItem dcl: VBusItem { bind: service.path("/Info/MaxDischargeCurrent") } + + description: qsTr("Parameters") + show: cvl.valid || ccl.valid || dcl.valid + subpage: Component { + PageBatteryParameters { + title: qsTr("Parameters") + service: root.service + } + } + } + + MbOK { + VBusItem { + id: redetect + bind: service.path("/Redetect") + } + + description: qsTr("Redetect Battery") + value: qsTr("Press to redetect") + editable: redetect.value === 0 + show: redetect.valid + cornerMark: false + writeAccessLevel: User.AccessUser + onClicked: { + redetect.setValue(1) + toast.createToast(qsTr("Redetecting the battery may take up time 60 seconds. Meanwhile the name of the battery may be incorrect."), 10000); + } + } + } } diff --git a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml index 07f693db..cbe1b49d 100644 --- a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml +++ b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml @@ -2,162 +2,162 @@ import QtQuick 1.1 import com.victron.velib 1.0 MbPage { - id: root - property string bindPrefix - property VBusItem _b1: VBusItem { bind: service.path("/Balances/Cell1") } - property VBusItem _b2: VBusItem { bind: service.path("/Balances/Cell2") } - property VBusItem _b3: VBusItem { bind: service.path("/Balances/Cell3") } - property VBusItem _b4: VBusItem { bind: service.path("/Balances/Cell4") } - property VBusItem _b5: VBusItem { bind: service.path("/Balances/Cell5") } - property VBusItem _b6: VBusItem { bind: service.path("/Balances/Cell6") } - property VBusItem _b7: VBusItem { bind: service.path("/Balances/Cell7") } - property VBusItem _b8: VBusItem { bind: service.path("/Balances/Cell8") } - property VBusItem _b9: VBusItem { bind: service.path("/Balances/Cell9") } - property VBusItem _b10: VBusItem { bind: service.path("/Balances/Cell10") } - property VBusItem _b11: VBusItem { bind: service.path("/Balances/Cell11") } - property VBusItem _b12: VBusItem { bind: service.path("/Balances/Cell12") } - property VBusItem _b13: VBusItem { bind: service.path("/Balances/Cell13") } - property VBusItem _b14: VBusItem { bind: service.path("/Balances/Cell14") } - property VBusItem _b15: VBusItem { bind: service.path("/Balances/Cell15") } - property VBusItem _b16: VBusItem { bind: service.path("/Balances/Cell16") } - property VBusItem _b17: VBusItem { bind: service.path("/Balances/Cell17") } - property VBusItem _b18: VBusItem { bind: service.path("/Balances/Cell18") } - property VBusItem _b19: VBusItem { bind: service.path("/Balances/Cell19") } - property VBusItem _b20: VBusItem { bind: service.path("/Balances/Cell20") } - property VBusItem _b21: VBusItem { bind: service.path("/Balances/Cell21") } - property VBusItem _b22: VBusItem { bind: service.path("/Balances/Cell22") } - property VBusItem _b23: VBusItem { bind: service.path("/Balances/Cell23") } - property VBusItem _b24: VBusItem { bind: service.path("/Balances/Cell24") } - property VBusItem volt1: VBusItem { bind: service.path("/Voltages/Cell1") } - property VBusItem volt2: VBusItem { bind: service.path("/Voltages/Cell2") } - property VBusItem volt3: VBusItem { bind: service.path("/Voltages/Cell3") } - property VBusItem volt4: VBusItem { bind: service.path("/Voltages/Cell4") } - property VBusItem volt5: VBusItem { bind: service.path("/Voltages/Cell5") } - property VBusItem volt6: VBusItem { bind: service.path("/Voltages/Cell6") } - property VBusItem volt7: VBusItem { bind: service.path("/Voltages/Cell7") } - property VBusItem volt8: VBusItem { bind: service.path("/Voltages/Cell8") } - property VBusItem volt9: VBusItem { bind: service.path("/Voltages/Cell9") } - property VBusItem volt10: VBusItem { bind: service.path("/Voltages/Cell10") } - property VBusItem volt11: VBusItem { bind: service.path("/Voltages/Cell11") } - property VBusItem volt12: VBusItem { bind: service.path("/Voltages/Cell12") } - property VBusItem volt13: VBusItem { bind: service.path("/Voltages/Cell13") } - property VBusItem volt14: VBusItem { bind: service.path("/Voltages/Cell14") } - property VBusItem volt15: VBusItem { bind: service.path("/Voltages/Cell15") } - property VBusItem volt16: VBusItem { bind: service.path("/Voltages/Cell16") } - property VBusItem volt17: VBusItem { bind: service.path("/Voltages/Cell17") } - property VBusItem volt18: VBusItem { bind: service.path("/Voltages/Cell18") } - property VBusItem volt19: VBusItem { bind: service.path("/Voltages/Cell19") } - property VBusItem volt20: VBusItem { bind: service.path("/Voltages/Cell20") } - property VBusItem volt21: VBusItem { bind: service.path("/Voltages/Cell21") } - property VBusItem volt22: VBusItem { bind: service.path("/Voltages/Cell22") } - property VBusItem volt23: VBusItem { bind: service.path("/Voltages/Cell23") } - property VBusItem volt24: VBusItem { bind: service.path("/Voltages/Cell24") } - property string c1: _b1.valid && _b1.text == "1" ? "#ff0000" : "#ddd" - property string c2: _b2.valid && _b2.text == "1" ? "#ff0000" : "#ddd" - property string c3: _b3.valid && _b3.text == "1" ? "#ff0000" : "#ddd" - property string c4: _b4.valid && _b4.text == "1" ? "#ff0000" : "#ddd" - property string c5: _b5.valid && _b5.text == "1" ? "#ff0000" : "#ddd" - property string c6: _b6.valid && _b6.text == "1" ? "#ff0000" : "#ddd" - property string c7: _b7.valid && _b7.text == "1" ? "#ff0000" : "#ddd" - property string c8: _b8.valid && _b8.text == "1" ? "#ff0000" : "#ddd" - property string c9: _b9.valid && _b9.text == "1" ? "#ff0000" : "#ddd" - property string c10: _b10.valid && _b10.text == "1" ? "#ff0000" : "#ddd" - property string c11: _b11.valid && _b11.text == "1" ? "#ff0000" : "#ddd" - property string c12: _b12.valid && _b12.text == "1" ? "#ff0000" : "#ddd" - property string c13: _b13.valid && _b13.text == "1" ? "#ff0000" : "#ddd" - property string c14: _b14.valid && _b14.text == "1" ? "#ff0000" : "#ddd" - property string c15: _b15.valid && _b15.text == "1" ? "#ff0000" : "#ddd" - property string c16: _b16.valid && _b16.text == "1" ? "#ff0000" : "#ddd" - property string c17: _b17.valid && _b17.text == "1" ? "#ff0000" : "#ddd" - property string c18: _b18.valid && _b18.text == "1" ? "#ff0000" : "#ddd" - property string c19: _b19.valid && _b19.text == "1" ? "#ff0000" : "#ddd" - property string c20: _b20.valid && _b20.text == "1" ? "#ff0000" : "#ddd" - property string c21: _b21.valid && _b21.text == "1" ? "#ff0000" : "#ddd" - property string c22: _b22.valid && _b22.text == "1" ? "#ff0000" : "#ddd" - property string c23: _b23.valid && _b23.text == "1" ? "#ff0000" : "#ddd" - property string c24: _b24.valid && _b24.text == "1" ? "#ff0000" : "#ddd" - title: service.description + " | Cell Voltages" + id: root + property string bindPrefix + property VBusItem _b1: VBusItem { bind: service.path("/Balances/Cell1") } + property VBusItem _b2: VBusItem { bind: service.path("/Balances/Cell2") } + property VBusItem _b3: VBusItem { bind: service.path("/Balances/Cell3") } + property VBusItem _b4: VBusItem { bind: service.path("/Balances/Cell4") } + property VBusItem _b5: VBusItem { bind: service.path("/Balances/Cell5") } + property VBusItem _b6: VBusItem { bind: service.path("/Balances/Cell6") } + property VBusItem _b7: VBusItem { bind: service.path("/Balances/Cell7") } + property VBusItem _b8: VBusItem { bind: service.path("/Balances/Cell8") } + property VBusItem _b9: VBusItem { bind: service.path("/Balances/Cell9") } + property VBusItem _b10: VBusItem { bind: service.path("/Balances/Cell10") } + property VBusItem _b11: VBusItem { bind: service.path("/Balances/Cell11") } + property VBusItem _b12: VBusItem { bind: service.path("/Balances/Cell12") } + property VBusItem _b13: VBusItem { bind: service.path("/Balances/Cell13") } + property VBusItem _b14: VBusItem { bind: service.path("/Balances/Cell14") } + property VBusItem _b15: VBusItem { bind: service.path("/Balances/Cell15") } + property VBusItem _b16: VBusItem { bind: service.path("/Balances/Cell16") } + property VBusItem _b17: VBusItem { bind: service.path("/Balances/Cell17") } + property VBusItem _b18: VBusItem { bind: service.path("/Balances/Cell18") } + property VBusItem _b19: VBusItem { bind: service.path("/Balances/Cell19") } + property VBusItem _b20: VBusItem { bind: service.path("/Balances/Cell20") } + property VBusItem _b21: VBusItem { bind: service.path("/Balances/Cell21") } + property VBusItem _b22: VBusItem { bind: service.path("/Balances/Cell22") } + property VBusItem _b23: VBusItem { bind: service.path("/Balances/Cell23") } + property VBusItem _b24: VBusItem { bind: service.path("/Balances/Cell24") } + property VBusItem volt1: VBusItem { bind: service.path("/Voltages/Cell1") } + property VBusItem volt2: VBusItem { bind: service.path("/Voltages/Cell2") } + property VBusItem volt3: VBusItem { bind: service.path("/Voltages/Cell3") } + property VBusItem volt4: VBusItem { bind: service.path("/Voltages/Cell4") } + property VBusItem volt5: VBusItem { bind: service.path("/Voltages/Cell5") } + property VBusItem volt6: VBusItem { bind: service.path("/Voltages/Cell6") } + property VBusItem volt7: VBusItem { bind: service.path("/Voltages/Cell7") } + property VBusItem volt8: VBusItem { bind: service.path("/Voltages/Cell8") } + property VBusItem volt9: VBusItem { bind: service.path("/Voltages/Cell9") } + property VBusItem volt10: VBusItem { bind: service.path("/Voltages/Cell10") } + property VBusItem volt11: VBusItem { bind: service.path("/Voltages/Cell11") } + property VBusItem volt12: VBusItem { bind: service.path("/Voltages/Cell12") } + property VBusItem volt13: VBusItem { bind: service.path("/Voltages/Cell13") } + property VBusItem volt14: VBusItem { bind: service.path("/Voltages/Cell14") } + property VBusItem volt15: VBusItem { bind: service.path("/Voltages/Cell15") } + property VBusItem volt16: VBusItem { bind: service.path("/Voltages/Cell16") } + property VBusItem volt17: VBusItem { bind: service.path("/Voltages/Cell17") } + property VBusItem volt18: VBusItem { bind: service.path("/Voltages/Cell18") } + property VBusItem volt19: VBusItem { bind: service.path("/Voltages/Cell19") } + property VBusItem volt20: VBusItem { bind: service.path("/Voltages/Cell20") } + property VBusItem volt21: VBusItem { bind: service.path("/Voltages/Cell21") } + property VBusItem volt22: VBusItem { bind: service.path("/Voltages/Cell22") } + property VBusItem volt23: VBusItem { bind: service.path("/Voltages/Cell23") } + property VBusItem volt24: VBusItem { bind: service.path("/Voltages/Cell24") } + property string c1: _b1.valid && _b1.text == "1" ? "#ff0000" : "#ddd" + property string c2: _b2.valid && _b2.text == "1" ? "#ff0000" : "#ddd" + property string c3: _b3.valid && _b3.text == "1" ? "#ff0000" : "#ddd" + property string c4: _b4.valid && _b4.text == "1" ? "#ff0000" : "#ddd" + property string c5: _b5.valid && _b5.text == "1" ? "#ff0000" : "#ddd" + property string c6: _b6.valid && _b6.text == "1" ? "#ff0000" : "#ddd" + property string c7: _b7.valid && _b7.text == "1" ? "#ff0000" : "#ddd" + property string c8: _b8.valid && _b8.text == "1" ? "#ff0000" : "#ddd" + property string c9: _b9.valid && _b9.text == "1" ? "#ff0000" : "#ddd" + property string c10: _b10.valid && _b10.text == "1" ? "#ff0000" : "#ddd" + property string c11: _b11.valid && _b11.text == "1" ? "#ff0000" : "#ddd" + property string c12: _b12.valid && _b12.text == "1" ? "#ff0000" : "#ddd" + property string c13: _b13.valid && _b13.text == "1" ? "#ff0000" : "#ddd" + property string c14: _b14.valid && _b14.text == "1" ? "#ff0000" : "#ddd" + property string c15: _b15.valid && _b15.text == "1" ? "#ff0000" : "#ddd" + property string c16: _b16.valid && _b16.text == "1" ? "#ff0000" : "#ddd" + property string c17: _b17.valid && _b17.text == "1" ? "#ff0000" : "#ddd" + property string c18: _b18.valid && _b18.text == "1" ? "#ff0000" : "#ddd" + property string c19: _b19.valid && _b19.text == "1" ? "#ff0000" : "#ddd" + property string c20: _b20.valid && _b20.text == "1" ? "#ff0000" : "#ddd" + property string c21: _b21.valid && _b21.text == "1" ? "#ff0000" : "#ddd" + property string c22: _b22.valid && _b22.text == "1" ? "#ff0000" : "#ddd" + property string c23: _b23.valid && _b23.text == "1" ? "#ff0000" : "#ddd" + property string c24: _b24.valid && _b24.text == "1" ? "#ff0000" : "#ddd" + title: service.description + " | Cell Voltages" - model: VisualItemModel { + model: VisualItemModel { - MbItemRow { - description: qsTr("Cells Sum") - values: [ - MbTextBlock { item { bind: service.path("/Voltages/Sum") } width: 70; height: 25 } - ] - } - MbItemRow { - description: qsTr("Cells (Min/Max/Diff)") - values: [ - MbTextBlock { item { bind: service.path("/System/MinCellVoltage") } width: 70; height: 25 }, - MbTextBlock { item { bind: service.path("/System/MaxCellVoltage") } width: 70; height: 25 }, - MbTextBlock { item { bind: service.path("/Voltages/Diff") } width: 70; height: 25 } - ] - } - MbItemRow { - description: qsTr("Cells (1/2/3/4)") - height: 22 - values: [ - MbTextBlock { item: volt1; width: 70; height: 20; color: c1 }, - MbTextBlock { item: volt2; width: 70; height: 20; color: c2 }, - MbTextBlock { item: volt3; width: 70; height: 20; color: c3 }, - MbTextBlock { item: volt4; width: 70; height: 20; color: c4 } - ] - } - MbItemRow { - description: qsTr("Cells (5/6/7/8)") - height: 22 - show: volt5.valid - values: [ - MbTextBlock { item: volt5; width: 70; height: 20; color: c5 }, - MbTextBlock { item: volt6; width: 70; height: 20; color: c6 }, - MbTextBlock { item: volt7; width: 70; height: 20; color: c7 }, - MbTextBlock { item: volt8; width: 70; height: 20; color: c8 } - ] - } - MbItemRow { - description: qsTr("Cells (9/10/11/12)") - height: 22 - show: volt9.valid - values: [ - MbTextBlock { item: volt9; width: 70; height: 20; color: c9 }, - MbTextBlock { item: volt10; width: 70; height: 20; color: c10 }, - MbTextBlock { item: volt11; width: 70; height: 20; color: c11 }, - MbTextBlock { item: volt12; width: 70; height: 20; color: c12 } - ] - } - MbItemRow { - description: qsTr("Cells (13/14/15/16)") - height: 22 - show: volt13.valid - values: [ - MbTextBlock { item: volt13; width: 70; height: 20; color: c13 }, - MbTextBlock { item: volt14; width: 70; height: 20; color: c14 }, - MbTextBlock { item: volt15; width: 70; height: 20; color: c15 }, - MbTextBlock { item: volt16; width: 70; height: 20; color: c16 } - ] - } - MbItemRow { - description: qsTr("Cells (17/18/19/20)") - height: 22 - show: volt17.valid - values: [ - MbTextBlock { item: volt17; width: 70; height: 20; color: c13 }, - MbTextBlock { item: volt18; width: 70; height: 20; color: c14 }, - MbTextBlock { item: volt19; width: 70; height: 20; color: c15 }, - MbTextBlock { item: volt20; width: 70; height: 20; color: c16 } - ] - } - MbItemRow { - description: qsTr("Cells (21/22/23/24)") - height: 22 - show: volt21.valid - values: [ - MbTextBlock { item: volt21; width: 70; height: 20; color: c13 }, - MbTextBlock { item: volt22; width: 70; height: 20; color: c14 }, - MbTextBlock { item: volt23; width: 70; height: 20; color: c15 }, - MbTextBlock { item: volt24; width: 70; height: 20; color: c16 } - ] - } - } + MbItemRow { + description: qsTr("Cells Sum") + values: [ + MbTextBlock { item { bind: service.path("/Voltages/Sum") } width: 70; height: 25 } + ] + } + MbItemRow { + description: qsTr("Cells (Min/Max/Diff)") + values: [ + MbTextBlock { item { bind: service.path("/System/MinCellVoltage") } width: 70; height: 25 }, + MbTextBlock { item { bind: service.path("/System/MaxCellVoltage") } width: 70; height: 25 }, + MbTextBlock { item { bind: service.path("/Voltages/Diff") } width: 70; height: 25 } + ] + } + MbItemRow { + description: qsTr("Cells (1/2/3/4)") + height: 22 + values: [ + MbTextBlock { item: volt1; width: 70; height: 20; color: c1 }, + MbTextBlock { item: volt2; width: 70; height: 20; color: c2 }, + MbTextBlock { item: volt3; width: 70; height: 20; color: c3 }, + MbTextBlock { item: volt4; width: 70; height: 20; color: c4 } + ] + } + MbItemRow { + description: qsTr("Cells (5/6/7/8)") + height: 22 + show: volt5.valid + values: [ + MbTextBlock { item: volt5; width: 70; height: 20; color: c5 }, + MbTextBlock { item: volt6; width: 70; height: 20; color: c6 }, + MbTextBlock { item: volt7; width: 70; height: 20; color: c7 }, + MbTextBlock { item: volt8; width: 70; height: 20; color: c8 } + ] + } + MbItemRow { + description: qsTr("Cells (9/10/11/12)") + height: 22 + show: volt9.valid + values: [ + MbTextBlock { item: volt9; width: 70; height: 20; color: c9 }, + MbTextBlock { item: volt10; width: 70; height: 20; color: c10 }, + MbTextBlock { item: volt11; width: 70; height: 20; color: c11 }, + MbTextBlock { item: volt12; width: 70; height: 20; color: c12 } + ] + } + MbItemRow { + description: qsTr("Cells (13/14/15/16)") + height: 22 + show: volt13.valid + values: [ + MbTextBlock { item: volt13; width: 70; height: 20; color: c13 }, + MbTextBlock { item: volt14; width: 70; height: 20; color: c14 }, + MbTextBlock { item: volt15; width: 70; height: 20; color: c15 }, + MbTextBlock { item: volt16; width: 70; height: 20; color: c16 } + ] + } + MbItemRow { + description: qsTr("Cells (17/18/19/20)") + height: 22 + show: volt17.valid + values: [ + MbTextBlock { item: volt17; width: 70; height: 20; color: c13 }, + MbTextBlock { item: volt18; width: 70; height: 20; color: c14 }, + MbTextBlock { item: volt19; width: 70; height: 20; color: c15 }, + MbTextBlock { item: volt20; width: 70; height: 20; color: c16 } + ] + } + MbItemRow { + description: qsTr("Cells (21/22/23/24)") + height: 22 + show: volt21.valid + values: [ + MbTextBlock { item: volt21; width: 70; height: 20; color: c13 }, + MbTextBlock { item: volt22; width: 70; height: 20; color: c14 }, + MbTextBlock { item: volt23; width: 70; height: 20; color: c15 }, + MbTextBlock { item: volt24; width: 70; height: 20; color: c16 } + ] + } + } } diff --git a/etc/dbus-serialbattery/qml/PageBatterySetup.qml b/etc/dbus-serialbattery/qml/PageBatterySetup.qml index bc0cf09d..5f732dbb 100644 --- a/etc/dbus-serialbattery/qml/PageBatterySetup.qml +++ b/etc/dbus-serialbattery/qml/PageBatterySetup.qml @@ -3,131 +3,131 @@ import com.victron.velib 1.0 import "utils.js" as Utils MbPage { - id: root - property string bindPrefix: "com.victronenergy.settings//Settings/Devices/serialbattery/" - - property VBusItem cellVoltageMin: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageMin")} - property VBusItem cellVoltageMax: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageMax")} - property VBusItem cellVoltageFloat: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageFloat")} - property VBusItem voltageMaxTime: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/VoltageMaxTime")} - property VBusItem voltageResetSocLimit: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/VoltageResetSocLimit")} - property VBusItem maxCurrentCharge: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/MaxCurrentCharge")} - property VBusItem maxCurrentDischarge: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/MaxCurrentDischarge")} - property VBusItem allowDynamicChargeCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicChargeCurrent")} - property VBusItem allowDynamicDischargeCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicDischargeCurrent")} - property VBusItem allowDynamicChargeVoltage: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicChargeVoltage")} - property VBusItem socLowWarning: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/SocLowWarning")} - property VBusItem socLowAlarm: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/SocLowAlarm")} - property VBusItem capacity: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/Capacity")} - property VBusItem enableInvertedCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/EnableInvertedCurrent")} - - property VBusItem ccmSocLimitCharge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge1")} - property VBusItem ccmSocLimitCharge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge2")} - property VBusItem ccmSocLimitCharge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge3")} - property VBusItem ccmSocLimitDischarge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge1")} - property VBusItem ccmSocLimitDischarge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge2")} - property VBusItem ccmSocLimitDischarge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge3")} - property VBusItem ccmCurrentLimitCharge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge1")} - property VBusItem ccmCurrentLimitCharge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge2")} - property VBusItem ccmCurrentLimitCharge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge3")} - property VBusItem ccmCurrentLimitDischarge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge1")} - property VBusItem ccmCurrentLimitDischarge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge2")} - property VBusItem ccmCurrentLimitDischarge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge3")} - - title: service.description + " | Cell Voltages" - - model: VisualItemModel { - - MbSpinBox { - description: qsTr("Maximum charge current") - item { - bind: maxCurrentCharge.bind - unit: "A" - decimals: 0 - step: 1 - min: 0 - } - } - MbSpinBox { - description: qsTr("Maximum discharge current") - item { - bind: maxCurrentDischarge.bind - unit: "A" - decimals: 0 - step: 1 - min: 0 - } - } - - MbSpinBox { - description: qsTr("Maximum cell voltage") - item { - bind: cellVoltageMax.bind - unit: "V" - decimals: 2 - step: 0.05 - } - } - MbSpinBox { - description: qsTr("Minimum cell voltage") - item { - bind: cellVoltageMin.bind - unit: "V" - decimals: 2 - step: 0.05 - } - } - - MbSwitch { - id: allowDynamicChargeCurrentSwitch - name: qsTr("Dynamic charge current") - bind: allowDynamicChargeCurrent.bind - } - MbSwitch { - id: allowDynamicDischargeCurrentSwitch - name: qsTr("Dynamic discharge current") - bind: allowDynamicDischargeCurrent.bind - } - - MbSwitch { - id: allowDynamicChargeVoltageSwitch - name: qsTr("Dynamic charge voltage") - bind : allowDynamicChargeVoltage.bind - } - - MbSpinBox { - description: qsTr("Float cell voltage") - item { - bind: cellVoltageFloat.bind - unit: "V" - decimals: 2 - step: 0.05 - } - show: allowDynamicChargeVoltageSwitch.checked - } - - MbSpinBox { - description: qsTr("Low SOC Warning") - item { - bind: socLowWarning.bind - unit: "%" - decimals: 0 - step: 1 - } - } - MbSpinBox { - description: qsTr("Low SOC Alarm") - item { - bind: socLowAlarm.bind - unit: "%" - decimals: 0 - step: 1 - } - } - - - - - - } + id: root + property string bindPrefix: "com.victronenergy.settings//Settings/Devices/serialbattery/" + + property VBusItem cellVoltageMin: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageMin")} + property VBusItem cellVoltageMax: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageMax")} + property VBusItem cellVoltageFloat: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CellVoltageFloat")} + property VBusItem voltageMaxTime: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/VoltageMaxTime")} + property VBusItem voltageResetSocLimit: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/VoltageResetSocLimit")} + property VBusItem maxCurrentCharge: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/MaxCurrentCharge")} + property VBusItem maxCurrentDischarge: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/MaxCurrentDischarge")} + property VBusItem allowDynamicChargeCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicChargeCurrent")} + property VBusItem allowDynamicDischargeCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicDischargeCurrent")} + property VBusItem allowDynamicChargeVoltage: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/AllowDynamicChargeVoltage")} + property VBusItem socLowWarning: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/SocLowWarning")} + property VBusItem socLowAlarm: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/SocLowAlarm")} + property VBusItem capacity: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/Capacity")} + property VBusItem enableInvertedCurrent: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/EnableInvertedCurrent")} + + property VBusItem ccmSocLimitCharge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge1")} + property VBusItem ccmSocLimitCharge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge2")} + property VBusItem ccmSocLimitCharge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitCharge3")} + property VBusItem ccmSocLimitDischarge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge1")} + property VBusItem ccmSocLimitDischarge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge2")} + property VBusItem ccmSocLimitDischarge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMSocLimitDischarge3")} + property VBusItem ccmCurrentLimitCharge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge1")} + property VBusItem ccmCurrentLimitCharge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge2")} + property VBusItem ccmCurrentLimitCharge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitCharge3")} + property VBusItem ccmCurrentLimitDischarge1: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge1")} + property VBusItem ccmCurrentLimitDischarge2: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge2")} + property VBusItem ccmCurrentLimitDischarge3: VBusItem {bind: Utils.path("com.victronenergy.settings", "/Settings/Devices/serialbattery/CCMCurrentLimitDischarge3")} + + title: service.description + " | Cell Voltages" + + model: VisualItemModel { + + MbSpinBox { + description: qsTr("Maximum charge current") + item { + bind: maxCurrentCharge.bind + unit: "A" + decimals: 0 + step: 1 + min: 0 + } + } + MbSpinBox { + description: qsTr("Maximum discharge current") + item { + bind: maxCurrentDischarge.bind + unit: "A" + decimals: 0 + step: 1 + min: 0 + } + } + + MbSpinBox { + description: qsTr("Maximum cell voltage") + item { + bind: cellVoltageMax.bind + unit: "V" + decimals: 2 + step: 0.05 + } + } + MbSpinBox { + description: qsTr("Minimum cell voltage") + item { + bind: cellVoltageMin.bind + unit: "V" + decimals: 2 + step: 0.05 + } + } + + MbSwitch { + id: allowDynamicChargeCurrentSwitch + name: qsTr("Dynamic charge current") + bind: allowDynamicChargeCurrent.bind + } + MbSwitch { + id: allowDynamicDischargeCurrentSwitch + name: qsTr("Dynamic discharge current") + bind: allowDynamicDischargeCurrent.bind + } + + MbSwitch { + id: allowDynamicChargeVoltageSwitch + name: qsTr("Dynamic charge voltage") + bind : allowDynamicChargeVoltage.bind + } + + MbSpinBox { + description: qsTr("Float cell voltage") + item { + bind: cellVoltageFloat.bind + unit: "V" + decimals: 2 + step: 0.05 + } + show: allowDynamicChargeVoltageSwitch.checked + } + + MbSpinBox { + description: qsTr("Low SOC Warning") + item { + bind: socLowWarning.bind + unit: "%" + decimals: 0 + step: 1 + } + } + MbSpinBox { + description: qsTr("Low SOC Alarm") + item { + bind: socLowAlarm.bind + unit: "%" + decimals: 0 + step: 1 + } + } + + + + + + } } diff --git a/etc/dbus-serialbattery/renogy.py b/etc/dbus-serialbattery/renogy.py index fd50af84..66ad7aa7 100644 --- a/etc/dbus-serialbattery/renogy.py +++ b/etc/dbus-serialbattery/renogy.py @@ -3,10 +3,10 @@ from utils import * import struct -class Renogy(Battery): +class Renogy(Battery): def __init__(self, port, baud, address): - super(Renogy, self).__init__(port,baud) + super(Renogy, self).__init__(port, baud) self.type = self.BATTERYTYPE # The RBT100LFP12SH-G1 uses 0xF7, another battery uses 0x30 @@ -19,20 +19,22 @@ def __init__(self, port, baud, address): # command bytes [Address field][Function code (03 = Read register)][Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)] command_read = b"\x03" # Core data = voltage, temp, current, soc - command_cell_count = b"\x13\x88\x00\x01" #Register 5000 - command_cell_voltages = b"\x13\x89\x00\x04" #Registers 5001-5004 - command_cell_temps = b"\x13\x9A\x00\x04" #Registers 5018-5021 - command_total_voltage = b"\x13\xB3\x00\x01" #Register 5043 - command_bms_temp1 = b"\x13\xAD\x00\x01" #Register 5037 - command_bms_temp2 = b"\x13\xB0\x00\x01" #Register 5040 - command_current = b"\x13\xB2\x00\x01" #Register 5042 (signed int) - command_capacity = b"\x13\xB6\x00\x02" #Registers 5046-5047 (long) - command_soc = b"\x13\xB2\x00\x04" #Registers 5042-5045 (amps, volts, soc as long) + command_cell_count = b"\x13\x88\x00\x01" # Register 5000 + command_cell_voltages = b"\x13\x89\x00\x04" # Registers 5001-5004 + command_cell_temps = b"\x13\x9A\x00\x04" # Registers 5018-5021 + command_total_voltage = b"\x13\xB3\x00\x01" # Register 5043 + command_bms_temp1 = b"\x13\xAD\x00\x01" # Register 5037 + command_bms_temp2 = b"\x13\xB0\x00\x01" # Register 5040 + command_current = b"\x13\xB2\x00\x01" # Register 5042 (signed int) + command_capacity = b"\x13\xB6\x00\x02" # Registers 5046-5047 (long) + command_soc = b"\x13\xB2\x00\x04" # Registers 5042-5045 (amps, volts, soc as long) # Battery info - command_manufacturer = b"\x14\x0C\x00\x08" #Registers 5132-5139 (8 byte string) - command_model = b"\x14\x02\x00\x08" #Registers 5122-5129 (8 byte string) - command_serial_number = b"\x13\xF6\x00\x08" #Registers 5110-5117 (8 byte string) - command_firmware_version = b"\x14\x0A\x00\x02" #Registers 5130-5131 (2 byte string) + command_manufacturer = b"\x14\x0C\x00\x08" # Registers 5132-5139 (8 byte string) + command_model = b"\x14\x02\x00\x08" # Registers 5122-5129 (8 byte string) + command_serial_number = b"\x13\xF6\x00\x08" # Registers 5110-5117 (8 byte string) + command_firmware_version = ( + b"\x14\x0A\x00\x02" # Registers 5130-5131 (2 byte string) + ) # BMS warning and protection config def test_connection(self): @@ -75,15 +77,17 @@ def read_gen_data(self): if model is False: return False # may contain null bytes that we don't want - model_num, _, _ = unpack('16s', model)[0].decode('utf-8').partition('\0') + model_num, _, _ = unpack("16s", model)[0].decode("utf-8").partition("\0") manufacturer = self.read_serial_data_renogy(self.command_manufacturer) if manufacturer is False: self.hardware_version = model_num else: # may contain null bytes that we don't want - manufacturer, _, _ = unpack('16s', manufacturer)[0].decode('utf-8').partition('\0') - self.hardware_version = f'{manufacturer} {model_num}' + manufacturer, _, _ = ( + unpack("16s", manufacturer)[0].decode("utf-8").partition("\0") + ) + self.hardware_version = f"{manufacturer} {model_num}" logger.info(self.hardware_version) @@ -92,19 +96,19 @@ def read_gen_data(self): if self.cell_count is None: cc = self.read_serial_data_renogy(self.command_cell_count) - self.cell_count = struct.unpack('>H',cc)[0] + self.cell_count = struct.unpack(">H", cc)[0] for c in range(self.cell_count): self.cells.append(Cell(False)) firmware = self.read_serial_data_renogy(self.command_firmware_version) - firmware_major, firmware_minor = unpack_from('2s2s', firmware) - firmware_major = firmware_major.decode('utf-8') - firmware_minor = firmware_minor.decode('utf-8') + firmware_major, firmware_minor = unpack_from("2s2s", firmware) + firmware_major = firmware_major.decode("utf-8") + firmware_minor = firmware_minor.decode("utf-8") self.version = float(f"{firmware_major}.{firmware_minor}") capacity = self.read_serial_data_renogy(self.command_capacity) - self.capacity = unpack('>L',capacity)[0] / 1000.0 + self.capacity = unpack(">L", capacity)[0] / 1000.0 return True @@ -114,7 +118,7 @@ def read_soc_data(self): if soc_data is False: return False - current, voltage, capacity_remain = unpack_from('>hhL', soc_data) + current, voltage, capacity_remain = unpack_from(">hhL", soc_data) self.capacity_remain = capacity_remain / 1000.0 self.current = current / 100.0 self.voltage = voltage / 10.0 @@ -126,8 +130,8 @@ def read_cell_data(self): cell_temp_data = self.read_serial_data_renogy(self.command_cell_temps) for c in range(self.cell_count): try: - cell_volts = unpack_from('>H', cell_volt_data, c*2) - cell_temp = unpack_from('>H', cell_temp_data, c*2) + cell_volts = unpack_from(">H", cell_volt_data, c * 2) + cell_temp = unpack_from(">H", cell_temp_data, c * 2) if len(cell_volts) != 0: self.cells[c].voltage = cell_volts[0] / 10 self.cells[c].temp = cell_temp[0] / 10 @@ -140,8 +144,8 @@ def read_temp_data(self): temp2 = self.read_serial_data_renogy(self.command_bms_temp2) if temp1 is False: return False - self.temp1 = unpack('>H',temp1)[0] / 10 - self.temp2 = unpack('>H',temp2)[0] / 10 + self.temp1 = unpack(">H", temp1)[0] / 10 + self.temp2 = unpack(">H", temp2)[0] / 10 return True @@ -153,12 +157,12 @@ def calc_crc(self, data): for pos in data: crc ^= pos for i in range(8): - if ((crc & 1) != 0): + if (crc & 1) != 0: crc >>= 1 crc ^= 0xA001 else: crc >>= 1 - return struct.pack('H', data, length + 3) + start, flag, length = unpack_from("BBB", data) + checksum = unpack_from(">H", data, length + 3) if flag == 3: - return data[3:length+3] + return data[3 : length + 3] else: logger.error(">>> ERROR: Incorrect Reply") - return False \ No newline at end of file + return False diff --git a/etc/dbus-serialbattery/revov.py b/etc/dbus-serialbattery/revov.py index 6355e772..40b245a1 100755 --- a/etc/dbus-serialbattery/revov.py +++ b/etc/dbus-serialbattery/revov.py @@ -5,12 +5,12 @@ import struct -# Author: L Sheed +# Author: L Sheed # Date: 3 May 2022 # Version 0.1.3 -# Cell Voltage Implemented +# Cell Voltage Implemented # Hardware Name Implemented -# Hardware Revision Implemented +# Hardware Revision Implemented # Battery Voltage added (but not correct!) # Added additional binary logging so I can try spot what bits are used for RED errors # To do: @@ -18,9 +18,8 @@ class Revov(Battery): - - def __init__(self, port,baud): - super(Revov, self).__init__(port,baud) + def __init__(self, port, baud): + super(Revov, self).__init__(port, baud) self.type = self.BATTERYTYPE self.soc = 100 self.voltage = None @@ -30,21 +29,20 @@ def __init__(self, port,baud): self.cell_min_no = None self.cell_max_no = None self.cell_count = 16 - self.cells= [] - self.cycles = None + self.cells = [] + self.cycles = None BATTERYTYPE = "Revov" - LENGTH_CHECK = 0 - LENGTH_POS = 3 #offset starting from 0 + LENGTH_CHECK = 0 + LENGTH_POS = 3 # offset starting from 0 LENGTH_FIXED = -1 - #setup the variables being looked for - - command_get_version = b"\x7C\x01\x42\x00\x80\x0D" #Get version number - command_get_model = b"\x7C\x01\x33\x00\xFE\x0D" #Get model number - command_one = b"\x7C\x01\x06\x00\xF8\x0D" #returns 4 bytes - command_two = b"\x7C\x01\x01\x00\x02\x0D" #returns a ton of data + # setup the variables being looked for + command_get_version = b"\x7C\x01\x42\x00\x80\x0D" # Get version number + command_get_model = b"\x7C\x01\x33\x00\xFE\x0D" # Get model number + command_one = b"\x7C\x01\x06\x00\xF8\x0D" # returns 4 bytes + command_two = b"\x7C\x01\x01\x00\x02\x0D" # returns a ton of data def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. @@ -62,24 +60,23 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - + self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count - #Need to fix to use correct value will do later. hard coded for now + # Need to fix to use correct value will do later. hard coded for now for c in range(self.cell_count): self.cells.append(Cell(False)) return True - def refresh_data(self): # call all functions that will refresh the battery data. # This will be called for every iteration (1 second) # Return True if success, False for failure result = self.read_soc_data() result = result and self.read_cell_data() - #result = result and self.read_temp_data() + # result = result and self.read_temp_data() return result def read_gen_data(self): @@ -88,136 +85,151 @@ def read_gen_data(self): # check if connection success if model is False: return False - - self.version = self.BATTERYTYPE + " " + str(model, 'utf-8') + + self.version = self.BATTERYTYPE + " " + str(model, "utf-8") logger.error(self.version) version = self.read_serial_data_revov(self.command_get_version) if version is False: return False - self.hardware_version = self.BATTERYTYPE + " ver ( " + str(version, 'utf-8') + ")" + self.hardware_version = ( + self.BATTERYTYPE + " ver ( " + str(version, "utf-8") + ")" + ) logger.error(self.hardware_version) - #At moment run solely for logging purposes so i can compare - one = self.read_serial_data_revov(self.command_one) - two = self.read_serial_data_revov(self.command_two) - + # At moment run solely for logging purposes so i can compare + one = self.read_serial_data_revov(self.command_one) + two = self.read_serial_data_revov(self.command_two) return True def read_soc_data(self): - #self.soc=70 + # self.soc=70 self.voltage = 55 self.current = 5 return True - #soc_data = self.read_serial_data_revov(self.command_soc) - # check if connection success - #if soc_data is False: - # return False + # soc_data = self.read_serial_data_revov(self.command_soc) + # check if connection success + # if soc_data is False: + # return False - #current, voltage, self.capacity_remain = unpack_from('>hhL', soc_data) - #self.current = current / 100 - #self.voltage = voltage / 10 - #self.soc = self.capacity_remain / self.capacity * 100 - #return True + # current, voltage, self.capacity_remain = unpack_from('>hhL', soc_data) + # self.current = current / 100 + # self.voltage = voltage / 10 + # self.soc = self.capacity_remain / self.capacity * 100 + # return True def read_cell_data(self): - packet = self.read_serial_data_revov(self.command_two) + packet = self.read_serial_data_revov(self.command_two) if packet is False: return False - #cell_volt_data = self.read_serial_data_revov(self.command_cell_voltages) - #cell_temp_data = self.read_serial_data_revov(self.command_cell_temps) + # cell_volt_data = self.read_serial_data_revov(self.command_cell_voltages) + # cell_temp_data = self.read_serial_data_revov(self.command_cell_temps) - self.voltage = unpack_from ('>H', packet,72 )[0] + self.voltage = unpack_from(">H", packet, 72)[0] if self.voltage > 9999: self.voltage = self.voltage / 1000 elif self.voltage > 999: self.voltage = self.voltage / 100 - logger.warn ( "Voltage Data: [" + str(self.voltage) +"v]" ) + logger.warn("Voltage Data: [" + str(self.voltage) + "v]") - self.cycles = unpack_from ('>H', packet,68 )[0] - logger.warn ( "Battery Cycles: [" + str(self.cycles) +"]" ) + self.cycles = unpack_from(">H", packet, 68)[0] + logger.warn("Battery Cycles: [" + str(self.cycles) + "]") - self.capacity = unpack_from ('>H', packet, 44)[0] + self.capacity = unpack_from(">H", packet, 44)[0] self.capacity = self.capacity / 100 - logger.warn ( "Battery Capacity: [" + str(self.capacity) +"Ah]" ) + logger.warn("Battery Capacity: [" + str(self.capacity) + "Ah]") - #serial returns from offset 4 onwards, so our packet data will start with module #, then cell count. - cell_count = unpack_from('>B', packet,1)[0] + # serial returns from offset 4 onwards, so our packet data will start with module #, then cell count. + cell_count = unpack_from(">B", packet, 1)[0] self.cell_count = cell_count - - logger.warn ("Cell count: [" + str(self.cell_count)+"]") - cell_volt_data = packet[2:(self.cell_count*2)+2] #16 2 byte values from pos 3 (index 2) - logger.warn ( "Raw Cell Data: [" + str(cell_volt_data.hex(':')).upper() +"]" ) + logger.warn("Cell count: [" + str(self.cell_count) + "]") + + cell_volt_data = packet[ + 2 : (self.cell_count * 2) + 2 + ] # 16 2 byte values from pos 3 (index 2) + logger.warn("Raw Cell Data: [" + str(cell_volt_data.hex(":")).upper() + "]") - #first, second = unpack_from ('>HH',cell_volt_data) - #logger.warn ("First Cell: " + str(first) + " Second Cell: " + str(second)) + # first, second = unpack_from ('>HH',cell_volt_data) + # logger.warn ("First Cell: " + str(first) + " Second Cell: " + str(second)) cell_total = 0 for c in range(self.cell_count): try: - cell_volts = unpack_from('>H', cell_volt_data, c*2)[0] - #raw_data = cell_volt_data[c*2:2] - #logger.warn ("Cell [" + str(c+1) + "] " + str(cell_volts) + " " + str(raw_data.hex(':')).upper() ) - #hacky divisor code + cell_volts = unpack_from(">H", cell_volt_data, c * 2)[0] + # raw_data = cell_volt_data[c*2:2] + # logger.warn ("Cell [" + str(c+1) + "] " + str(cell_volts) + " " + str(raw_data.hex(':')).upper() ) + # hacky divisor code if cell_volts > 9999: - self.cells[c].voltage = cell_volts /10000 + self.cells[c].voltage = cell_volts / 10000 elif cell_volts > 999: self.cells[c].voltage = cell_volts / 1000 - #Show Cell #, Voltage to 4DP, Hex and Binary value - logger.warn ( "Cell [" + "%02d" % (c+1) +"] " + "%.3f" % self.cells[c].voltage + "v " + "0x%04X" % cell_volts +" " +str(bin(cell_volts))) + # Show Cell #, Voltage to 4DP, Hex and Binary value + logger.warn( + "Cell [" + + "%02d" % (c + 1) + + "] " + + "%.3f" % self.cells[c].voltage + + "v " + + "0x%04X" % cell_volts + + " " + + str(bin(cell_volts)) + ) cell_total = cell_total + self.cells[c].voltage except struct.error: self.cells[c].voltage = 0 - logger.warn ("Cell Total: " + "%.2fv" % cell_total ) + logger.warn("Cell Total: " + "%.2fv" % cell_total) return True def read_temp_data(self): return True - #disabled for now. I need to find what bytes map to the 2 temp sensors + # disabled for now. I need to find what bytes map to the 2 temp sensors temp1 = self.read_serial_data_revov(self.command_bms_temp1) temp2 = self.read_serial_data_revov(self.command_bms_temp2) if temp1 is False: return False - self.temp1 = unpack('>H',temp1)[0] / 10 - self.temp2 = unpack('>H',temp2)[0] / 10 - + self.temp1 = unpack(">H", temp1)[0] / 10 + self.temp2 = unpack(">H", temp2)[0] / 10 + return True def read_bms_config(self): return True - def read_serial_data_revov(self, command): # use the read_serial_data() function to read the data and then do BMS spesific checks (crc, start bytes, etc) - data = read_serial_data(command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK) + data = read_serial_data( + command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK + ) if data is False: - logger.error ("read_serial_data_revov::Serial Data is Bad") + logger.error("read_serial_data_revov::Serial Data is Bad") return False - #Its not quite modbus, but psuedo modbus'ish' - modbus_address, modbus_type, modbus_cmd, modbus_packet_length = unpack_from('BBBB', data) + # Its not quite modbus, but psuedo modbus'ish' + modbus_address, modbus_type, modbus_cmd, modbus_packet_length = unpack_from( + "BBBB", data + ) + + logger.warn("Modbus Address: " + str(modbus_address)) + logger.warn("Modbus Type : " + str(modbus_type)) + logger.warn("Modbus Command: " + str(modbus_cmd)) + logger.warn("Modbus PackLen: " + str(modbus_packet_length)) + logger.warn("Modbus Packet : [" + str(data.hex(":")).upper() + "]") - logger.warn ("Modbus Address: " + str(modbus_address)) - logger.warn ("Modbus Type : " + str(modbus_type)) - logger.warn ("Modbus Command: " + str(modbus_cmd)) - logger.warn ("Modbus PackLen: " + str(modbus_packet_length)) - logger.warn ("Modbus Packet : [" + str(data.hex(':')).upper() +"]" ) - - #If address is correct and the command is correct then its a good packet + # If address is correct and the command is correct then its a good packet if modbus_type == 1 and modbus_address == 124: - logger.warn ("== Modbus packet good ==") - return data[4:modbus_packet_length+4] + logger.warn("== Modbus packet good ==") + return data[4 : modbus_packet_length + 4] else: logger.error(">>> ERROR: Incorrect Reply") return False diff --git a/etc/dbus-serialbattery/sinowealth.py b/etc/dbus-serialbattery/sinowealth.py index ada97b8d..8788c60e 100755 --- a/etc/dbus-serialbattery/sinowealth.py +++ b/etc/dbus-serialbattery/sinowealth.py @@ -3,12 +3,13 @@ from utils import * from struct import * -class Sinowealth(Battery): - def __init__(self, port,baud): - super(Sinowealth, self).__init__(port,baud) +class Sinowealth(Battery): + def __init__(self, port, baud): + super(Sinowealth, self).__init__(port, baud) self.poll_interval = 2000 self.type = self.BATTERYTYPE + # command bytes [StartFlag=0A][Command byte][response dataLength=2 to 20 bytes][checksum] command_base = b"\x0A\x00\x04" command_cell_base = b"\x01" @@ -25,7 +26,7 @@ def __init__(self, port,baud): command_status = b"\x15" command_battery_status = b"\x16" command_pack_config = b"\x17" - + command_cell_base = b"\x01" BATTERYTYPE = "Sinowealth" LENGTH_CHECK = 0 @@ -45,20 +46,20 @@ def get_settings(self): # hardcoded parameters, to be requested from the BMS in the future self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - + if self.cell_count is None: - self.read_pack_config_data() - + self.read_pack_config_data() + self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count - + self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) - + self.read_capacity() - + for c in range(self.cell_count): - self.cells.append(Cell(False)) + self.cells.append(Cell(False)) return True def refresh_data(self): @@ -78,34 +79,55 @@ def read_status_data(self): # check if connection success if status_data is False: return False - + # BMS status command layout (from screenshot) # [0] - - - - - VDQ FD FC # [1] - FAST_DSG MID_DSG SLOW_DSG DSGING CHGING DSGMOS CHGMOS - self.discharge_fet = bool(status_data[1]>>1 & int(1)) # DSGMOS - self.charge_fet = bool(status_data[1] & int(1)) # CHGMOS - logger.info(">>> INFO: Discharge fet: %s, charge fet: %s", self.discharge_fet, self.charge_fet) - + self.discharge_fet = bool(status_data[1] >> 1 & int(1)) # DSGMOS + self.charge_fet = bool(status_data[1] & int(1)) # CHGMOS + logger.info( + ">>> INFO: Discharge fet: %s, charge fet: %s", + self.discharge_fet, + self.charge_fet, + ) + if self.cell_count is None: - self.read_pack_config_data() + self.read_pack_config_data() return True - + def read_battery_status(self): battery_status = self.read_serial_data_sinowealth(self.command_battery_status) # check if connection success if battery_status is False: return False - + # Battery status command layout (from screenshot) # [0] - CTO AFE_SC AFE_OV UTD UTC OTD OTC # [1] - - - - OCD OC UV OV - self.protection.voltage_high = 2 if bool(battery_status[1] & int(1)) else 0 #OV - self.protection.voltage_low = 2 if bool(battery_status[1]>>1 & int(1)) else 0 #UV - self.protection.current_over = 2 if bool(battery_status[1]>>2 & int(1)) or bool(battery_status[1]>>3 & int(1)) else 0 # OC (OCC?)| OCD - self.protection.temp_high_charge = 2 if bool(battery_status[0] & int(1)) else 0 # OTC - self.protection.temp_high_discharge = 2 if bool(battery_status[0]>>1 & int(1)) else 0 # OTD - self.protection.temp_low_charge = 2 if bool(battery_status[0]>>2 & int(1)) else 0 # UTC - self.protection.temp_low_discharge = 2 if bool(battery_status[0]>>3 & int(1)) else 0 # UTD + self.protection.voltage_high = ( + 2 if bool(battery_status[1] & int(1)) else 0 + ) # OV + self.protection.voltage_low = ( + 2 if bool(battery_status[1] >> 1 & int(1)) else 0 + ) # UV + self.protection.current_over = ( + 2 + if bool(battery_status[1] >> 2 & int(1)) + or bool(battery_status[1] >> 3 & int(1)) + else 0 + ) # OC (OCC?)| OCD + self.protection.temp_high_charge = ( + 2 if bool(battery_status[0] & int(1)) else 0 + ) # OTC + self.protection.temp_high_discharge = ( + 2 if bool(battery_status[0] >> 1 & int(1)) else 0 + ) # OTD + self.protection.temp_low_charge = ( + 2 if bool(battery_status[0] >> 2 & int(1)) else 0 + ) # UTC + self.protection.temp_low_discharge = ( + 2 if bool(battery_status[0] >> 3 & int(1)) else 0 + ) # UTD return True def read_soc(self): @@ -123,16 +145,16 @@ def read_cycle_count(self): # check if connection success if cycle_count is False: return False - self.cycles = int(unpack_from('>H', cycle_count[:2])[0]) + self.cycles = int(unpack_from(">H", cycle_count[:2])[0]) logger.info(">>> INFO: current cycle count: %u", self.cycles) - return True - + return True + def read_pack_voltage(self): pack_voltage_data = self.read_serial_data_sinowealth(self.command_total_voltage) if pack_voltage_data is False: return False - pack_voltage = unpack_from('>H', pack_voltage_data[:-1]) - pack_voltage = pack_voltage[0]/1000 + pack_voltage = unpack_from(">H", pack_voltage_data[:-1]) + pack_voltage = pack_voltage[0] / 1000 logger.info(">>> INFO: current pack voltage: %f", pack_voltage) self.voltage = pack_voltage return True @@ -141,30 +163,32 @@ def read_pack_current(self): current_data = self.read_serial_data_sinowealth(self.command_current) if current_data is False: return False - current = unpack_from('>i', current_data[:-1]) - current = current[0]/1000 + current = unpack_from(">i", current_data[:-1]) + current = current[0] / 1000 logger.info(">>> INFO: current pack current: %f", current) self.current = current return True - + def read_remaining_capacity(self): - remaining_capacity_data = self.read_serial_data_sinowealth(self.command_remaining_capacity) + remaining_capacity_data = self.read_serial_data_sinowealth( + self.command_remaining_capacity + ) if remaining_capacity_data is False: return False - remaining_capacity = unpack_from('>i', remaining_capacity_data[:-1]) - self.capacity_remain = remaining_capacity[0]/1000 + remaining_capacity = unpack_from(">i", remaining_capacity_data[:-1]) + self.capacity_remain = remaining_capacity[0] / 1000 logger.info(">>> INFO: remaining battery capacity: %f Ah", self.capacity_remain) return True - + def read_capacity(self): capacity_data = self.read_serial_data_sinowealth(self.command_capacity) if capacity_data is False: return False - capacity = unpack_from('>i', capacity_data[:-1]) - logger.info(">>> INFO: Battery capacity: %f Ah", capacity[0]/1000) - self.capacity = capacity[0]/1000 - return True - + capacity = unpack_from(">i", capacity_data[:-1]) + logger.info(">>> INFO: Battery capacity: %f Ah", capacity[0] / 1000) + self.capacity = capacity[0] / 1000 + return True + def read_pack_config_data(self): # TODO: detect correct chipset, currently the pack_config_map register is parsed as, # SH367303 / 367305 / 367306 / 39F003 / 39F004 / BMS_10. So these are the currently supported chips @@ -174,30 +198,34 @@ def read_pack_config_data(self): cell_cnt_mask = int(7) self.cell_count = (pack_config_data[1] & cell_cnt_mask) + 3 if self.cell_count < 1 or self.cell_count > 32: - logger.error(">>> ERROR: No valid cell count returnd: %u", self.cell_count) + logger.error(">>> ERROR: No valid cell count returnd: %u", self.cell_count) return False logger.info(">>> INFO: Number of cells: %u", self.cell_count) temp_sens_mask = int(~(1 << 6)) - self.temp_sensors = 1 if (pack_config_data[1] & temp_sens_mask) else 2 # one means two + self.temp_sensors = ( + 1 if (pack_config_data[1] & temp_sens_mask) else 2 + ) # one means two logger.info(">>> INFO: Number of temperatur sensors: %u", self.temp_sensors) return True - + def read_cell_data(self): if self.cell_count is None: - self.read_pack_config_data() + self.read_pack_config_data() for c in range(self.cell_count): self.cells[c].voltage = self.read_cell_voltage(c + 1) return True - + def read_cell_voltage(self, cell_index): - cell_data = self.read_serial_data_sinowealth(cell_index.to_bytes(1,byteorder='little')) + cell_data = self.read_serial_data_sinowealth( + cell_index.to_bytes(1, byteorder="little") + ) if cell_data is False: return None - cell_voltage = unpack_from('>H', cell_data[:-1]) - cell_voltage = cell_voltage[0]/1000 - - logger.info(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage ) + cell_voltage = unpack_from(">H", cell_data[:-1]) + cell_voltage = cell_voltage[0] / 1000 + + logger.info(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage) return cell_voltage def read_temperature_data(self): @@ -207,35 +235,41 @@ def read_temperature_data(self): temp_ext1_data = self.read_serial_data_sinowealth(self.command_temp_ext1) if temp_ext1_data is False: return False - - temp_ext1 = unpack_from('>H', temp_ext1_data[:-1]) - self.to_temp(1, kelvin_to_celsius(temp_ext1[0]/10)) - logger.info(">>> INFO: BMS external temperature 1: %f C", self.temp1 ) + + temp_ext1 = unpack_from(">H", temp_ext1_data[:-1]) + self.to_temp(1, kelvin_to_celsius(temp_ext1[0] / 10)) + logger.info(">>> INFO: BMS external temperature 1: %f C", self.temp1) if self.temp_sensors == 2: temp_ext2_data = self.read_serial_data_sinowealth(self.command_temp_ext2) if temp_ext2_data is False: return False - - temp_ext2 = unpack_from('>H', temp_ext2_data[:-1]) - self.to_temp(2, kelvin_to_celsius(temp_ext2[0]/10)) - logger.info(">>> INFO: BMS external temperature 2: %f C", self.temp2 ) - - # Internal temperature 1 seems to give a logical value + + temp_ext2 = unpack_from(">H", temp_ext2_data[:-1]) + self.to_temp(2, kelvin_to_celsius(temp_ext2[0] / 10)) + logger.info(">>> INFO: BMS external temperature 2: %f C", self.temp2) + + # Internal temperature 1 seems to give a logical value temp_int1_data = self.read_serial_data_sinowealth(self.command_temp_int1) if temp_int1_data is False: return False - - temp_int1 = unpack_from('>H', temp_int1_data[:-1]) - logger.info(">>> INFO: BMS internal temperature 1: %f C", kelvin_to_celsius(temp_int1[0]/10) ) - - # Internal temperature 2 seems to give a useless value + + temp_int1 = unpack_from(">H", temp_int1_data[:-1]) + logger.info( + ">>> INFO: BMS internal temperature 1: %f C", + kelvin_to_celsius(temp_int1[0] / 10), + ) + + # Internal temperature 2 seems to give a useless value temp_int2_data = self.read_serial_data_sinowealth(self.command_temp_int2) if temp_int2_data is False: return False - - temp_int2 = unpack_from('>H', temp_int2_data[:-1]) - logger.info(">>> INFO: BMS internal temperature 2: %f C", kelvin_to_celsius(temp_int2[0]/10) ) + + temp_int2 = unpack_from(">H", temp_int2_data[:-1]) + logger.info( + ">>> INFO: BMS internal temperature 2: %f C", + kelvin_to_celsius(temp_int2[0] / 10), + ) return True def generate_command(self, command): @@ -244,7 +278,14 @@ def generate_command(self, command): return buffer def read_serial_data_sinowealth(self, command): - data = read_serial_data(self.generate_command(command), self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK, int(self.generate_command(command)[2])) + data = read_serial_data( + self.generate_command(command), + self.port, + self.baud_rate, + self.LENGTH_POS, + self.LENGTH_CHECK, + int(self.generate_command(command)[2]), + ) if data is False: return False diff --git a/etc/dbus-serialbattery/test_max17853.py b/etc/dbus-serialbattery/test_max17853.py index 22850b56..fe267465 100644 --- a/etc/dbus-serialbattery/test_max17853.py +++ b/etc/dbus-serialbattery/test_max17853.py @@ -3,217 +3,224 @@ import math from gpiozero import LED -def init_spi(): - global spi, Q_time ,Q_Batt,Q_B_chg,kWh_dis,kWh_chg,cum_bp_kwh_in,\ - cum_bp_kwh_out,Q_B_dis,Q_nom,SOH ,R_shunt,Vt_ref,V_bat_Sum,Ai,Ai_offs,\ - Tj,Tbat,bal_stat,bal_stat2,p_genrun,p_charging ,p_loadshed,Fan_run_b, V_Cells,\ - Ah_b_max, Ah_b_min,T_Cells,err_no, Q_Cycles,bal_count,chg_out,load_out,Genrun,Fan_run +def init_spi(): + global spi, Q_time, Q_Batt, Q_B_chg, kWh_dis, kWh_chg, cum_bp_kwh_in, cum_bp_kwh_out, Q_B_dis, Q_nom, SOH, R_shunt, Vt_ref, V_bat_Sum, Ai, Ai_offs, Tj, Tbat, bal_stat, bal_stat2, p_genrun, p_charging, p_loadshed, Fan_run_b, V_Cells, Ah_b_max, Ah_b_min, T_Cells, err_no, Q_Cycles, bal_count, chg_out, load_out, Genrun, Fan_run - # temp home for BMS constants + # temp home for BMS constants err_no = 0 Q_time = 0 - Q_Batt = 1.8*60 + Q_Batt = 1.8 * 60 Q_B_chg = 0 Q_Cycles = 0 kWh_dis = 0 - kWh_chg = 0 - cum_bp_kwh_in=0 - cum_bp_kwh_out=0 - Q_B_dis = 0 - Q_nom = 3.6*36 - SOH = 1 - R_shunt = 0.025 - V_Cells = [0]*8 - T_Cells = [11]*8 - Vt_ref = 3.299 - V_bat_Sum = 25 - Ai = 0 - Ai_offs = 0.6 - Ah_b_max = 0 + kWh_chg = 0 + cum_bp_kwh_in = 0 + cum_bp_kwh_out = 0 + Q_B_dis = 0 + Q_nom = 3.6 * 36 + SOH = 1 + R_shunt = 0.025 + V_Cells = [0] * 8 + T_Cells = [11] * 8 + Vt_ref = 3.299 + V_bat_Sum = 25 + Ai = 0 + Ai_offs = 0.6 + Ah_b_max = 0 Ah_b_min = 300 Tj = 25 - Tbat = 25 - bal_stat = 0 - bal_stat2 = 0 - bal_count = [0]*8 - p_genrun = False - p_charging = True - p_loadshed = False - Fan_run_b = False - - chg_out = LED(2) - load_out =LED(3) - Genrun = LED(4) - Fan_run = LED(17) - - #spi = spidev.SpiDev() - #spi.open(0,0) - #spi.max_speed_hz = 500000 - #spi.mode = 0 - spi = None - return (spi) - -def CrcA_MAX17( InputWord,WORD_LEN): - CRC_LEN =3 - CRC_POLY =0x0B - CRC_SEED =0x000 - CRCMask =(CRC_POLY<<(WORD_LEN-1)) - LeftAlignedWord = InputWord<>= 1 TestBitMask >>= 1 - - return (LeftAlignedWord) #returns word with CRC apended; crc test. -def spi_xfer_MAX17(RW,Adr,xdata): + return LeftAlignedWord # returns word with CRC apended; crc test. + + +def spi_xfer_MAX17(RW, Adr, xdata): global spi - #********************* + # ********************* # Python 2.7 can't cope with 32 bit numbers - #**************************** - print("SPI:",RW,"{:02x}".format(Adr),"{:04x}".format(xdata)) - return(0,0,Adr,xdata,0,0) - - txdata = [0,0,0,0] - rxdata = [0,0,0,0] - tdwd = RW<<8^Adr - crca = CrcA_MAX17(tdwd,9) - crcb = CrcA_MAX17(xdata,16) - txword1 = 0^RW<<15 - txword2 = 0^RW<<3 - txword1 ^= Adr<<7 - txword1 ^= crca<<4 - txword1 ^= xdata&0xf000>>12 - txword2 ^= xdata&0xfff<<4 + # **************************** + print("SPI:", RW, "{:02x}".format(Adr), "{:04x}".format(xdata)) + return (0, 0, Adr, xdata, 0, 0) + + txdata = [0, 0, 0, 0] + rxdata = [0, 0, 0, 0] + tdwd = RW << 8 ^ Adr + crca = CrcA_MAX17(tdwd, 9) + crcb = CrcA_MAX17(xdata, 16) + txword1 = 0 ^ RW << 15 + txword2 = 0 ^ RW << 3 + txword1 ^= Adr << 7 + txword1 ^= crca << 4 + txword1 ^= xdata & 0xF000 >> 12 + txword2 ^= xdata & 0xFFF << 4 txword2 ^= crcb - txdata[0] =0^RW<<7^Adr>>1 #(txword1)>>8 - fadr = Adr&1 - gadr = fadr<<7 - txdata[1] =gadr^crca<<4^xdata>>12 #(txword1&0x00ff) - txdata[2] =(xdata>>4)&0xff #(txword2)>>8 - txdata[3] =0^(xdata<<4)&0xff^RW<<3^crcb&0x7 #(txword2&0x00ff) - + txdata[0] = 0 ^ RW << 7 ^ Adr >> 1 # (txword1)>>8 + fadr = Adr & 1 + gadr = fadr << 7 + txdata[1] = gadr ^ crca << 4 ^ xdata >> 12 # (txword1&0x00ff) + txdata[2] = (xdata >> 4) & 0xFF # (txword2)>>8 + txdata[3] = 0 ^ (xdata << 4) & 0xFF ^ RW << 3 ^ crcb & 0x7 # (txword2&0x00ff) + rxdata = spi.xfer(txdata) # - + flags = rxdata[0] - crcs = flags&0x07 - flags = flags>>3 + crcs = flags & 0x07 + flags = flags >> 3 if RW == 0: radr = rxdata[1] - rdat = rxdata[2]<<8^rxdata[3] + rdat = rxdata[2] << 8 ^ rxdata[3] rcrc = 0 - rxok = rxdata[0]>>3&1 #crc check n-1 + rxok = rxdata[0] >> 3 & 1 # crc check n-1 else: radr = Adr - rdat = 0^((rxdata[1]&0x0f)<<16^rxdata[2]<<8^rxdata[3])>>4 - rcrc = rxdata[3]&0x07 - rxok = (rxdata[3]>>3)&0x01 - - - time.sleep(.01) - return(flags,crcs,radr,rdat,rcrc,rxok) + rdat = 0 ^ ((rxdata[1] & 0x0F) << 16 ^ rxdata[2] << 8 ^ rxdata[3]) >> 4 + rcrc = rxdata[3] & 0x07 + rxok = (rxdata[3] >> 3) & 0x01 + + time.sleep(0.01) + return (flags, crcs, radr, rdat, rcrc, rxok) + def init_max(self): - #*************************************8 - # need to pick up cell min and max to set cell voltage + # *************************************8 + # need to pick up cell min and max to set cell voltage # thresholds et al. - #********************************************8 + # ********************************************8 init_spi() time.sleep(0.1) - for i in range(1,7): - spi_xfer_MAX17(0,i,0x00) # clear por - spi_xfer_MAX17(0,0x14,0x02) # set spi int on AL out - spi_xfer_MAX17(0,0x15,0x04) #disable spi to - spi_xfer_MAX17(0,0x16,0x00) #enable gpio anlg in - t_cell = self.cell_count+1 + for i in range(1, 7): + spi_xfer_MAX17(0, i, 0x00) # clear por + spi_xfer_MAX17(0, 0x14, 0x02) # set spi int on AL out + spi_xfer_MAX17(0, 0x15, 0x04) # disable spi to + spi_xfer_MAX17(0, 0x16, 0x00) # enable gpio anlg in + t_cell = self.cell_count + 1 tc = 0x2000 - tc = tc | t_cell<<8 |t_cell<<4 | t_cell - spi_xfer_MAX17(0,0x18,tc) # top cell selection - spi_xfer_MAX17(0,0x19,0x3faf) #IRQ enable + tc = tc | t_cell << 8 | t_cell << 4 | t_cell + spi_xfer_MAX17(0, 0x18, tc) # top cell selection + spi_xfer_MAX17(0, 0x19, 0x3FAF) # IRQ enable ov = 0x4000 - for i in range(1,t_cell): - ov |= 1< 0: err_no = 11 @@ -221,7 +228,7 @@ def err_dec(st_wd1,st_wd2,fema1,self): if st_wd1 & 0x8 > 0: err_no = 10 err_msg = "Cal Error" - if st_wd1 & 0x10 > 0 and st_wd2 & 0xd0 > 0: + if st_wd1 & 0x10 > 0 and st_wd2 & 0xD0 > 0: err_no = 9 err_msg = "SPI Error" if st_wd1 & 0x80 > 0: @@ -255,19 +262,19 @@ def err_dec(st_wd1,st_wd2,fema1,self): else: self.protection.voltage_low = False if st_wd1 & 0x1000 > 0: - err_no = 3 #overvoltage + err_no = 3 # overvoltage err_msg = "Cell Overvoltage" self.protection.voltage_high = True else: self.protection.voltage_high = False if st_wd1 & 0x2000 > 0: - err_no = 2 #cell mismatch Dv too high + err_no = 2 # cell mismatch Dv too high err_msg = "Cell voltage mismatch" self.protection.cell_imbalance = True else: self.protection.cell_imbalance = False if st_wd1 & 0x4000 > 0: - err_no = 1 #POR + err_no = 1 # POR err_msg = "POR" if st_wd2 & 0x40 > 0: err_no = 13 @@ -278,81 +285,82 @@ def err_dec(st_wd1,st_wd2,fema1,self): if st_wd2 & 0x10 > 0: err_no = 15 err_msg += " SPI INT BUS FLT" - if fema1 &0x08 >0: + if fema1 & 0x08 > 0: err_no = 16 - #print(315) + # print(315) err_msg += " HV_UV" - if fema1 &0x04 >0: + if fema1 & 0x04 > 0: err_no = 17 - #print(319) + # print(319) err_msg += " HV_DR" - if fema1 &0x70 >0: + if fema1 & 0x70 > 0: err_no = 18 err_msg += " gnd flt" - if st_wd1 ==0 and st_wd2==0 and fema1 ==0: + if st_wd1 == 0 and st_wd2 == 0 and fema1 == 0: err_no = 0 err_msg = "No Error" - #store_reg([err_no],0) - #print(328,err_no) - return(err_no) + # store_reg([err_no],0) + # print(328,err_no) + return err_no + def v_cell_d(self): - global vc_del,vc_min,vc_max,Q_Batt, V_Cells,p_genrun,p_charging,p_loadshed + global vc_del, vc_min, vc_max, Q_Batt, V_Cells, p_genrun, p_charging, p_loadshed vc_del = 0 vc_max = 0 vc_min = 4 - i_min =0 + i_min = 0 i_max = 0 b_lim = False - for index,v in enumerate(V_Cells): + for index, v in enumerate(V_Cells): if v > 3.55: b_lim = True - if v> vc_max: + if v > vc_max: vc_max = v i_max = index if v < vc_min: vc_min = v i_min = index - + self.cell_min_voltage = vc_min - self.cell_max_voltage = vc_max + self.cell_max_voltage = vc_max self.cell_min_no = i_min - self.cell_max_no = i_max + self.cell_max_no = i_max vc_del = vc_max - vc_min # current control done elsewhere. - if vc_min<(self.V_C_min+0.05): + if vc_min < (self.V_C_min + 0.05): p_genrun = True p_loadshed = True Q_Batt = 0 - elif vc_min > self.V_C_min+0.15: + elif vc_min > self.V_C_min + 0.15: p_loadshed = False - if vc_max > self.V_C_max-0.05: + if vc_max > self.V_C_max - 0.05: p_charging = False Q_Batt = Q_nom - elif vc_max< self.V_C_max-0.15: + elif vc_max < self.V_C_max - 0.15: p_charging = True - inpins(self) - return(b_lim) + inpins(self) + return b_lim -def CSA(xdata,self): - global R_shunt,Ai,Ai_offs - Ai = (xdata*0.000305-2.5)/R_shunt +Ai_offs + +def CSA(xdata, self): + global R_shunt, Ai, Ai_offs + Ai = (xdata * 0.000305 - 2.5) / R_shunt + Ai_offs self.current = Ai - calc_Ah(Ai,self) - return(Ai) + calc_Ah(Ai, self) + return Ai + -def calc_Ah(Ai,self): - global Q_Batt, Q_time,Q_B_chg,Q_B_dis,Ah_b_max,Ah_b_min,\ - x_soc_min,x_soc_max,x_Soc,Q_nom,SOH,kWh_chg,kWh_dis,V_bat_Sum,\ - cum_bp_kwh_in,cum_bp_kwh_out,p_genrun,Q_Cycles +def calc_Ah(Ai, self): + global Q_Batt, Q_time, Q_B_chg, Q_B_dis, Ah_b_max, Ah_b_min, x_soc_min, x_soc_max, x_Soc, Q_nom, SOH, kWh_chg, kWh_dis, V_bat_Sum, cum_bp_kwh_in, cum_bp_kwh_out, p_genrun, Q_Cycles if Q_time == 0: Q_time = time.time() t_Q = time.time() - d_Qt = t_Q-Q_time + d_Qt = t_Q - Q_time Q_time = t_Q - dQ_Batt = Ai*d_Qt/3600 - Q_Batt +=dQ_Batt + dQ_Batt = Ai * d_Qt / 3600 + Q_Batt += dQ_Batt if Q_Batt > Q_nom: Q_Batt = Q_nom if Q_Batt < 0: @@ -362,108 +370,112 @@ def calc_Ah(Ai,self): if Q_Batt < Ah_b_min: Ah_b_min = Q_Batt - x_Soc = Q_Batt/Q_nom*100 + x_Soc = Q_Batt / Q_nom * 100 self.soc = x_Soc - self.capacity_remain = x_Soc*Q_nom/100 - if x_Soc<20: + self.capacity_remain = x_Soc * Q_nom / 100 + if x_Soc < 20: p_genrun = True elif x_Soc > 35: p_genrun = False - - SOH = (1-cum_bp_kwh_out/Q_nom*0.00005)*100 - Q_act = Q_nom*SOH/100 - Q_Cycles = cum_bp_kwh_out/Q_nom*.00005 + + SOH = (1 - cum_bp_kwh_out / Q_nom * 0.00005) * 100 + Q_act = Q_nom * SOH / 100 + Q_Cycles = cum_bp_kwh_out / Q_nom * 0.00005 self.cycles = Q_Cycles - - if Ai>0: + + if Ai > 0: Q_B_chg += dQ_Batt - kWh_chg += dQ_Batt*V_bat_Sum/1000 - cum_bp_kwh_in +=dQ_Batt*V_bat_Sum/1000 + kWh_chg += dQ_Batt * V_bat_Sum / 1000 + cum_bp_kwh_in += dQ_Batt * V_bat_Sum / 1000 else: Q_B_dis -= dQ_Batt - kWh_dis -= dQ_Batt*V_bat_Sum/1000 - cum_bp_kwh_out-= dQ_Batt*V_bat_Sum/1000 - - return() + kWh_dis -= dQ_Batt * V_bat_Sum / 1000 + cum_bp_kwh_out -= dQ_Batt * V_bat_Sum / 1000 + + return () -def gpio_decode(xdata,adr,self): + +def gpio_decode(xdata, adr, self): # need to add Dbus channel for device temp - global Vt_ref,Tbat,T_Cells + global Vt_ref, Tbat, T_Cells try: - s = float(0x3fff)/float(xdata+1) + s = float(0x3FFF) / float(xdata + 1) t = math.log(s) - u = t/0.01998 - T_Cells[adr] = u-12.74 + u = t / 0.01998 + T_Cells[adr] = u - 12.74 except Exception as e: - print("gpio_dec",e) - print("gpio_dec",adr,"{:04x}".format(xdata)) + print("gpio_dec", e) + print("gpio_dec", adr, "{:04x}".format(xdata)) T_Cells[adr] = 25 t_min = 100 t_max = 0 - for i in range(0,4): + for i in range(0, 4): if T_Cells[i] > t_max: t_max = T_Cells[i] - imax = i + imax = i if T_Cells[i] < t_min: t_min = T_Cells[i] imin = i - print('gpio') + print("gpio") self.temp1 = t_max self.temp2 = t_min - self.temp3 = (T_Cells[5]+T_Cells[6] )/2 + self.temp3 = (T_Cells[5] + T_Cells[6]) / 2 self.temp_max_no = imax self.temp_min_no = imin - return() + return () + -def cell_balance(V_Cells,vc_min,vc_max,self): - global bal_count,bal_stat,bal_stat2 +def cell_balance(V_Cells, vc_min, vc_max, self): + global bal_count, bal_stat, bal_stat2 # need to add dbus channel for displaing balancing as 8 bit bianry? -# f = spi_xfer_MAX17(1,0x80,0x00) - # bal_stat = f[3]>>14 + # f = spi_xfer_MAX17(1,0x80,0x00) + # bal_stat = f[3]>>14 bal_stat = 0 - if bal_stat ==3: - spi_xfer_MAX17(0,0x80,0x0) - spi_xfer_MAX17(0,0x6f,0x00) + if bal_stat == 3: + spi_xfer_MAX17(0, 0x80, 0x0) + spi_xfer_MAX17(0, 0x6F, 0x00) print("bal reset") - return() - if (bal_stat)&1 >0: - #print("bal run") - return() # Balancing in progress - if (bal_stat) == 2: #balancing complete - #print(511,"Bal Complete") - for i in range (0x6f,0x81): - spi_xfer_MAX17(0,i,0x00) + return () + if (bal_stat) & 1 > 0: + # print("bal run") + return () # Balancing in progress + if (bal_stat) == 2: # balancing complete + # print(511,"Bal Complete") + for i in range(0x6F, 0x81): + spi_xfer_MAX17(0, i, 0x00) else: - f = spi_xfer_MAX17(0,0x80,0) - if f[0] !=0: + f = spi_xfer_MAX17(0, 0x80, 0) + if f[0] != 0: stat_clr() cb_sum = 0 - cb_duty = int((vc_max-vc_min-0.01)*500) - if cb_duty >15: - cb_duty = 0xf -# max_cell = (f[3]>>8)&0x07 -# min_cell = f[3]&0x07 -# missing read to minmax cell register? - - for i in range (1,9): - Vc_t = int((V_Cells[i-1]-vc_min)/(vc_max-vc_min)*15) + cb_duty = int((vc_max - vc_min - 0.01) * 500) + if cb_duty > 15: + cb_duty = 0xF + # max_cell = (f[3]>>8)&0x07 + # min_cell = f[3]&0x07 + # missing read to minmax cell register? + + for i in range(1, 9): + Vc_t = int((V_Cells[i - 1] - vc_min) / (vc_max - vc_min) * 15) if Vc_t < 0: - print(517,"<0") - Vc_t = 0 # remove -ve - if Vc_t >=0 and V_Cells[i-1]>3.35: - bal_count[i-1]+=Vc_t - self.cells[i-1].balance = 1 - if bal_count[i-1]>65535: - for j in range(0,8): - bal_count[j] = bal_count[j]>>1 - print("cba",i,Vc_t) + print(517, "<0") + Vc_t = 0 # remove -ve + if Vc_t >= 0 and V_Cells[i - 1] > 3.35: + bal_count[i - 1] += Vc_t + self.cells[i - 1].balance = 1 + if bal_count[i - 1] > 65535: + for j in range(0, 8): + bal_count[j] = bal_count[j] >> 1 + print("cba", i, Vc_t) else: Vc_t = 0 - print("cb",i,Vc_t) - self.cells[i-1].balance = 0 + print("cb", i, Vc_t) + self.cells[i - 1].balance = 0 + + return () # tewst + - return() #tewst # cb_sum += Vc_t # spi_xfer_MAX17(0,0x70+i,Vc_t) #set cell timers # f = spi_xfer_MAX17(1,0x70+i,0) # and read back @@ -473,51 +485,55 @@ def cell_balance(V_Cells,vc_min,vc_max,self): # print("prt write rjt",f[3]&1) # if cb_sum !=0: # #enable cells & start timer -# f = spi_xfer_MAX17(0,0x6f,0x1fe) +# f = spi_xfer_MAX17(0,0x6f,0x1fe) # if f[0] != 0: # stat_clr() # #R_bal_stat() Temporary for diagnostic # xdata = 0x2002 | cb_duty<<4 # xdata = xdata%0xc7ff -# f = spi_xfer_MAX17(0,0x80,xdata) +# f = spi_xfer_MAX17(0,0x80,xdata) # print(480,"{:04x}".format(f[3]),"{:02x}".format(f[0])) # f = spi_xfer_MAX17(1,0x80,0) # print(481,"{:04x}".format(f[3]),"{:02x}".format(f[0])) -# return() +# return() + def R_bal_stat(): - for i in range(0x6f,0x84): - f = spi_xfer_MAX17(1,i,0x00) - print("{:02x}".format(i),"{:04x}".format(f[3]),"{:02x}".format(f[0])) - return() + for i in range(0x6F, 0x84): + f = spi_xfer_MAX17(1, i, 0x00) + print("{:02x}".format(i), "{:04x}".format(f[3]), "{:02x}".format(f[0])) + return () + def stat_clr(): - for i in range(2,7): - spi_xfer_MAX17(0,i,0) - return() + for i in range(2, 7): + spi_xfer_MAX17(0, i, 0) + return () + def die_temp(): - return(35) - global Tj,tmaxp,Fan_run_b - f= spi_xfer_MAX17(1,0x57,0) # read diag 1 register - Vptat = f[3]>>2 - Vptat = Vptat/0x4000*2.3077 - Tj = Vptat/0.0032+8.3-273 + return 35 + global Tj, tmaxp, Fan_run_b + f = spi_xfer_MAX17(1, 0x57, 0) # read diag 1 register + Vptat = f[3] >> 2 + Vptat = Vptat / 0x4000 * 2.3077 + Tj = Vptat / 0.0032 + 8.3 - 273 self.temp4 = Tj - if Tj >45: + if Tj > 45: Fan_run_b = True elif Tj < 40: - Fan_run_b = False - - return(Tj) + Fan_run_b = False + + return Tj + def inpins(self): - global chg_in,Load_in,Genrun,Fan_run_b,chg_out,p_loadshed,p_charging,p_genrun - if p_charging==True: + global chg_in, Load_in, Genrun, Fan_run_b, chg_out, p_loadshed, p_charging, p_genrun + if p_charging == True: self.charge_fet = True - chg_out.on() + chg_out.on() else: - self.charge_fet = False + self.charge_fet = False chg_out.off() if p_loadshed == False: @@ -527,7 +543,7 @@ def inpins(self): self.discharge_fet = False load_out.off() p_gernun = True - if p_genrun==True : + if p_genrun == True: Genrun.on() else: Genrun.off() @@ -535,67 +551,66 @@ def inpins(self): Fan_run.on() else: Fan_run.off() - return() + return () + def data_cycle(self): - global err_no,V_Cells,T_Cells, vc_max - #print("data_cycle") - spi_xfer_MAX17(0,0x66,0xe21) - #f = spi_xfer_MAX17(1,0x66,0x00) - #scn_dn = f[3]>>15 - #dat_rdy = (f[3]&0x2000)>>13 - dat_rdy = 1 #test - while dat_rdy == 0 : - f = spi_xfer_MAX17(1,0x66,0x00) + global err_no, V_Cells, T_Cells, vc_max + # print("data_cycle") + spi_xfer_MAX17(0, 0x66, 0xE21) + # f = spi_xfer_MAX17(1,0x66,0x00) + # scn_dn = f[3]>>15 + # dat_rdy = (f[3]&0x2000)>>13 + dat_rdy = 1 # test + while dat_rdy == 0: + f = spi_xfer_MAX17(1, 0x66, 0x00) time.sleep(0.005) - scn_dn = (f[3]&0x8000)>>15 - dat_rdy = (f[3]&0x2000)>>13 - + scn_dn = (f[3] & 0x8000) >> 15 + dat_rdy = (f[3] & 0x2000) >> 13 + Tj = die_temp() - #f = spi_xfer_MAX17(1,0x66,0x00) # scan ctrl - #scn_dn = f[3]&0x8000>>15 - #dat_rdy = f[3]&0x2000>>13 - spi_xfer_MAX17(0,0x83,0x1)# manual xfer - f = spi_xfer_MAX17(0,0x66,0x1e28) - if f[0]> 0: + # f = spi_xfer_MAX17(1,0x66,0x00) # scan ctrl + # scn_dn = f[3]&0x8000>>15 + # dat_rdy = f[3]&0x2000>>13 + spi_xfer_MAX17(0, 0x83, 0x1) # manual xfer + f = spi_xfer_MAX17(0, 0x66, 0x1E28) + if f[0] > 0: stat_clr() - #V_bat_sum = 0 - #for i in range(72,0x50): + # V_bat_sum = 0 + # for i in range(72,0x50): # f= spi_xfer_MAX17(1,i,0) # v = vblk_dec((f[3]>>2),0.000305,i-72) #no change # V_bat_sum += v # V_Cells[i-72] = v # cb_b = v_cell_d(self) # time.sleep(0.005) - V_Cells = [3.0,3.1,3.2,3.3,3.4,3.5,3.55,3.55] + V_Cells = [3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.55, 3.55] cb_b = v_cell_d(self) - self.voltage = 26.8 #V_bat_sum + self.voltage = 26.8 # V_bat_sum for i in range(self.cell_count): self.cells[i].voltage = V_Cells[i] - if (vc_del >0.015 and Ai >1.0 and err_no <16 ) or cb_b == True or vc_max>3.45: - self.poll_interval = 3000 - cell_balance(V_Cells,vc_min,vc_max,self) + if (vc_del > 0.015 and Ai > 1.0 and err_no < 16) or cb_b == True or vc_max > 3.45: + self.poll_interval = 3000 + cell_balance(V_Cells, vc_min, vc_max, self) else: - spi_xfer_MAX17(0,0x80,0x00) - spi_xfer_MAX17(0,0x6f,0x00) + spi_xfer_MAX17(0, 0x80, 0x00) + spi_xfer_MAX17(0, 0x6F, 0x00) self.poll_interval = 1000 - -# f= spi_xfer_MAX17(1,0x47,0) -# CSA(f[3]>>2,self) - CSA(0x2014,self) - -# f= spi_xfer_MAX17(1,0x55,0) # remeber to deduct V0 (AMPS) from V blk. -# vblk_dec((f[3]>>2),0.003967,22) -# f= spi_xfer_MAX17(1,0x56,0) -# vblk_dec(f[3],0.00122,2) #02 - for i in range(0x59,0x5f,1): - # f= spi_xfer_MAX17(1,i,0) - # gpio_decode(f[3]>>2,i-89,self) #49-64 - gpio_decode(0x2000+i,i-89,self) - time.sleep(0.005) + # f= spi_xfer_MAX17(1,0x47,0) + # CSA(f[3]>>2,self) + CSA(0x2014, self) + + # f= spi_xfer_MAX17(1,0x55,0) # remeber to deduct V0 (AMPS) from V blk. + # vblk_dec((f[3]>>2),0.003967,22) + # f= spi_xfer_MAX17(1,0x56,0) + # vblk_dec(f[3],0.00122,2) #02 + for i in range(0x59, 0x5F, 1): + # f= spi_xfer_MAX17(1,i,0) + # gpio_decode(f[3]>>2,i-89,self) #49-64 + gpio_decode(0x2000 + i, i - 89, self) + time.sleep(0.005) stat_scan(self) - print('sys tmp', self.temp_max_no) - return(True) - + print("sys tmp", self.temp_max_no) + return True diff --git a/etc/dbus-serialbattery/util_max17853.py b/etc/dbus-serialbattery/util_max17853.py index 2c7ca035..02e06e4a 100644 --- a/etc/dbus-serialbattery/util_max17853.py +++ b/etc/dbus-serialbattery/util_max17853.py @@ -5,15 +5,12 @@ def init_spi(self): - global spi, Q_time, Q_Batt, Q_B_chg, kWh_dis, kWh_chg, cum_bp_kwh_in,\ - cum_bp_kwh_out, Q_B_dis, Q_nom, SOH, R_shunt, Vt_ref, V_bat_Sum, Ai, Ai_offs,\ - Tj, Tbat, bal_stat, bal_stat2, p_genrun, p_charging, p_loadshed, Fan_run_b, V_Cells,\ - Ah_b_max, Ah_b_min, T_Cells, err_no, Q_Cycles, bal_count, chg_out, load_out, Genrun, Fan_run + global spi, Q_time, Q_Batt, Q_B_chg, kWh_dis, kWh_chg, cum_bp_kwh_in, cum_bp_kwh_out, Q_B_dis, Q_nom, SOH, R_shunt, Vt_ref, V_bat_Sum, Ai, Ai_offs, Tj, Tbat, bal_stat, bal_stat2, p_genrun, p_charging, p_loadshed, Fan_run_b, V_Cells, Ah_b_max, Ah_b_min, T_Cells, err_no, Q_Cycles, bal_count, chg_out, load_out, Genrun, Fan_run # temp home for BMS constants err_no = 0 Q_time = 0 - Q_Batt = 0.5*self.inst_capacity + Q_Batt = 0.5 * self.inst_capacity Q_B_chg = 0 Q_Cycles = 0 kWh_dis = 0 @@ -24,8 +21,8 @@ def init_spi(self): Q_nom = self.inst_capacity SOH = 1 R_shunt = 0.025 - V_Cells = [0]*self.cell_count - T_Cells = [11]*self.cell_count + V_Cells = [0] * self.cell_count + T_Cells = [11] * self.cell_count Vt_ref = 3.299 V_bat_Sum = 25 Ai = 0 @@ -36,7 +33,7 @@ def init_spi(self): Tbat = 25 bal_stat = 0 bal_stat2 = 0 - bal_count = [0]*self.cell_count + bal_count = [0] * self.cell_count p_genrun = False p_charging = True p_loadshed = False @@ -51,29 +48,29 @@ def init_spi(self): spi.open(0, 0) spi.max_speed_hz = 500000 spi.mode = 0 - return (spi) + return spi def CrcA_MAX17(InputWord, WORD_LEN): CRC_LEN = 3 CRC_POLY = 0x0B CRC_SEED = 0x000 - CRCMask = (CRC_POLY << (WORD_LEN-1)) + CRCMask = CRC_POLY << (WORD_LEN - 1) # /* Clear the CRC bit in the data frame*/ LeftAlignedWord = InputWord << CRC_LEN TestBitMask = 1 << (WORD_LEN + 2) - BitCount = (WORD_LEN) + BitCount = WORD_LEN - while (0 != BitCount): + while 0 != BitCount: BitCount -= 1 - if (0 != (LeftAlignedWord & TestBitMask)): # is and - LeftAlignedWord ^= CRCMask # is xor + if 0 != (LeftAlignedWord & TestBitMask): # is and + LeftAlignedWord ^= CRCMask # is xor CRCMask >>= 1 TestBitMask >>= 1 - return (LeftAlignedWord) # returns word with CRC apended; crc test. + return LeftAlignedWord # returns word with CRC apended; crc test. def spi_xfer_MAX17(RW, Adr, xdata): @@ -90,16 +87,16 @@ def spi_xfer_MAX17(RW, Adr, xdata): txword2 = 0 ^ RW << 3 txword1 ^= Adr << 7 txword1 ^= crca << 4 - txword1 ^= xdata & 0xf000 >> 12 - txword2 ^= xdata & 0xfff << 4 + txword1 ^= xdata & 0xF000 >> 12 + txword2 ^= xdata & 0xFFF << 4 txword2 ^= crcb txdata[0] = 0 ^ RW << 7 ^ Adr >> 1 # (txword1)>>8 fadr = Adr & 1 gadr = fadr << 7 txdata[1] = gadr ^ crca << 4 ^ xdata >> 12 # (txword1&0x00ff) - txdata[2] = (xdata >> 4) & 0xff # (txword2)>>8 + txdata[2] = (xdata >> 4) & 0xFF # (txword2)>>8 # (txword2&0x00ff) - txdata[3] = 0 ^ (xdata << 4) & 0xff ^ RW << 3 ^ crcb & 0x7 + txdata[3] = 0 ^ (xdata << 4) & 0xFF ^ RW << 3 ^ crcb & 0x7 rxdata = spi.xfer(txdata) # @@ -113,12 +110,12 @@ def spi_xfer_MAX17(RW, Adr, xdata): rxok = rxdata[0] >> 3 & 1 # crc check n-1 else: radr = Adr - rdat = 0 ^ ((rxdata[1] & 0x0f) << 16 ^ rxdata[2] << 8 ^ rxdata[3]) >> 4 + rdat = 0 ^ ((rxdata[1] & 0x0F) << 16 ^ rxdata[2] << 8 ^ rxdata[3]) >> 4 rcrc = rxdata[3] & 0x07 rxok = (rxdata[3] >> 3) & 0x01 - time.sleep(.01) - return(flags, crcs, radr, rdat, rcrc, rxok) + time.sleep(0.01) + return (flags, crcs, radr, rdat, rcrc, rxok) def init_max(self): @@ -128,86 +125,84 @@ def init_max(self): init_spi(self) time.sleep(0.1) for i in range(1, 7): - spi_xfer_MAX17(0, i, 0x00) # clear por - spi_xfer_MAX17(0, 0x14, 0x02) # set spi int on AL out + spi_xfer_MAX17(0, i, 0x00) # clear por + spi_xfer_MAX17(0, 0x14, 0x02) # set spi int on AL out spi_xfer_MAX17(0, 0x15, 0x04) # disable spi to spi_xfer_MAX17(0, 0x16, 0x00) # enable gpio anlg in - t_cell = self.cell_count+1 + t_cell = self.cell_count + 1 tc = 0x2000 tc = tc | t_cell << 8 | t_cell << 4 | t_cell - spi_xfer_MAX17(0, 0x18, tc) # top cell selection - spi_xfer_MAX17(0, 0x19, 0x3faf) # IRQ enable + spi_xfer_MAX17(0, 0x18, tc) # top cell selection + spi_xfer_MAX17(0, 0x19, 0x3FAF) # IRQ enable ov = 0x4000 for i in range(1, t_cell): ov |= 1 << i - spi_xfer_MAX17(0, 0x1a, ov) # Over voltage enable - spi_xfer_MAX17(0, 0x1b, ov) # Under voltage enable - spi_xfer_MAX17(0, 0x1c, 0xf) # Aux Over voltage enable 0-5 - spi_xfer_MAX17(0, 0x1d, 0xf) # Aux Under voltage enable 0-5 - ovc = int((self.V_C_max-0.1)/0.000305) # V_Cell max - 100mV + spi_xfer_MAX17(0, 0x1A, ov) # Over voltage enable + spi_xfer_MAX17(0, 0x1B, ov) # Under voltage enable + spi_xfer_MAX17(0, 0x1C, 0xF) # Aux Over voltage enable 0-5 + spi_xfer_MAX17(0, 0x1D, 0xF) # Aux Under voltage enable 0-5 + ovc = int((self.V_C_max - 0.1) / 0.000305) # V_Cell max - 100mV # over voltage clear thr 3.5V/.305mV <<2 - spi_xfer_MAX17(0, 0x1f, ovc << 2) - ovs = int((self.V_C_max)/0.000305) # V_Cell max - spi_xfer_MAX17(0, 0x20, ovs << 2) # over voltage set thr 3.6V/.305mV <<2 - uvc = int((self.V_C_min+0.1)/0.000305) # V_Cell min - 100mV + spi_xfer_MAX17(0, 0x1F, ovc << 2) + ovs = int((self.V_C_max) / 0.000305) # V_Cell max + spi_xfer_MAX17(0, 0x20, ovs << 2) # over voltage set thr 3.6V/.305mV <<2 + uvc = int((self.V_C_min + 0.1) / 0.000305) # V_Cell min - 100mV # under voltage clear thr 2.6V/.305mV <<2 spi_xfer_MAX17(0, 0x21, uvc << 2) - uvs = int((self.V_C_min)/0.000305) # V_Cell min - spi_xfer_MAX17(0, 0x22, uvs << 2) # under voltage set thr 2.5V/.305mV <<2 - spi_xfer_MAX17(0, 0x23, 0x514) # cell mismatch set thr 0.1V/.305mV <<2 - bovc = int((self.max_battery_voltage-0.25) / - 0.003967) # max battery volt - 0.25 + uvs = int((self.V_C_min) / 0.000305) # V_Cell min + spi_xfer_MAX17(0, 0x22, uvs << 2) # under voltage set thr 2.5V/.305mV <<2 + spi_xfer_MAX17(0, 0x23, 0x514) # cell mismatch set thr 0.1V/.305mV <<2 + bovc = int((self.max_battery_voltage - 0.25) / 0.003967) # max battery volt - 0.25 spi_xfer_MAX17(0, 0x28, bovc << 2) # block ov clear thr 3.967mV <<2 - bovs = int((self.max_battery_voltage)/0.003967) # max battery volt + bovs = int((self.max_battery_voltage) / 0.003967) # max battery volt spi_xfer_MAX17(0, 0x29, bovs << 2) # block ov set thr 3.967mV <<2 - buvc = int((self.min_battery_voltage+0.25) / - 0.003967) # max battery volt + 0.25 - spi_xfer_MAX17(0, 0x2a, buvc << 2) # block uv cl thr 3.967mV <<2 - buvs = int((self.min_battery_voltage)/0.003967) # max battery volt - spi_xfer_MAX17(0, 0x2b, buvs << 2) # block uv set thr 0.9407/0.201mV <<2 + buvc = int((self.min_battery_voltage + 0.25) / 0.003967) # max battery volt + 0.25 + spi_xfer_MAX17(0, 0x2A, buvc << 2) # block uv cl thr 3.967mV <<2 + buvs = int((self.min_battery_voltage) / 0.003967) # max battery volt + spi_xfer_MAX17(0, 0x2B, buvs << 2) # block uv set thr 0.9407/0.201mV <<2 # Aux under temp clear T cell min + 5c - Neg temp coeff!! - tovc = xtemp(self.T_C_min+5) + tovc = xtemp(self.T_C_min + 5) # Aux undertemp clear thr V/3.967mV <<2 spi_xfer_MAX17(0, 0x30, tovc << 2) - tovs = xtemp(self.T_C_min) # Aux under temp set T cell min - spi_xfer_MAX17(0, 0x31, tovs) # Aux under temp set thr V/3.967mV <<2 + tovs = xtemp(self.T_C_min) # Aux under temp set T cell min + spi_xfer_MAX17(0, 0x31, tovs) # Aux under temp set thr V/3.967mV <<2 # Aux over temp clear T cell max - 5c - Neg temp coeff!! - tuvc = xtemp(self.T_C_max-5) - spi_xfer_MAX17(0, 0x32, tuvc << 2) # Aux uv cl thr V/3.967mV <<2 + tuvc = xtemp(self.T_C_max - 5) + spi_xfer_MAX17(0, 0x32, tuvc << 2) # Aux uv cl thr V/3.967mV <<2 # Aux over temp set T cell max - Neg temp coeff!! tuvs = xtemp(self.T_C_max) - spi_xfer_MAX17(0, 0x33, tuvs << 2) # Aux uv set thr 20.8V/3.967mV <<2 - spi_xfer_MAX17(0, 0x5f, 0x01) # ADC Polarity - spi_xfer_MAX17(0, 0x62, 0x4800) # ADCQ CFG - spi_xfer_MAX17(0, 0x63, 0x303) # BALSWDLY 3 x 96uS + spi_xfer_MAX17(0, 0x33, tuvs << 2) # Aux uv set thr 20.8V/3.967mV <<2 + spi_xfer_MAX17(0, 0x5F, 0x01) # ADC Polarity + spi_xfer_MAX17(0, 0x62, 0x4800) # ADCQ CFG + spi_xfer_MAX17(0, 0x63, 0x303) # BALSWDLY 3 x 96uS cms = 0x4000 for i in range(0, t_cell): cms |= 1 << i spi_xfer_MAX17(0, 0x64, cms) # cell measure enable - spi_xfer_MAX17(0, 0x65, 0x803F) # filter init, AUX meas enable - spi_xfer_MAX17(0, 0x66, 0xe21) # configure and init scan + spi_xfer_MAX17(0, 0x65, 0x803F) # filter init, AUX meas enable + spi_xfer_MAX17(0, 0x66, 0xE21) # configure and init scan spi_xfer_MAX17(0, 0x80, 0x00) # reset Bal CTRL - spi_xfer_MAX17(0, 0x6f, 0x1fe) - spi_xfer_MAX17(0, 0x7e, 0x01) # set bal uv thr = mincell - spi_xfer_MAX17(0, 0x6b, 1) # set die temp diag 1. - return() + spi_xfer_MAX17(0, 0x6F, 0x1FE) + spi_xfer_MAX17(0, 0x7E, 0x01) # set bal uv thr = mincell + spi_xfer_MAX17(0, 0x6B, 1) # set die temp diag 1. + return () def xtemp(temp): - t = temp+12.74 - s = math.exp(0.01988*t) - r = int(0x3fff/s) - return(r) + t = temp + 12.74 + s = math.exp(0.01988 * t) + r = int(0x3FFF / s) + return r def vblk_dec(xdata, ref, adr): global V_bat_Sum, VBS_max, VBS_min, min_rst_en, Q_Batt - vblock = xdata*ref + vblock = xdata * ref # print(adr,"{:04x}".format(xdata),vblock) if adr == 22: V_bat_Sum = vblock - return(vblock) + return vblock def stat_scan(self): @@ -224,7 +219,7 @@ def stat_scan(self): en = err_dec(st_wd1, st_wd2, fema1, self) # print("stat",en) - return(en) + return en def err_dec(st_wd1, st_wd2, fema1, self): @@ -235,7 +230,7 @@ def err_dec(st_wd1, st_wd2, fema1, self): if st_wd1 & 0x8 > 0: err_no = 10 err_msg = "Cal Error" - if st_wd1 & 0x10 > 0 and st_wd2 & 0xd0 > 0: + if st_wd1 & 0x10 > 0 and st_wd2 & 0xD0 > 0: err_no = 9 err_msg = "SPI Error" if st_wd1 & 0x80 > 0: @@ -308,7 +303,7 @@ def err_dec(st_wd1, st_wd2, fema1, self): err_msg = "No Error" # store_reg([err_no],0) # print(328,err_no) - return(err_no) + return err_no def v_cell_d(self): @@ -338,40 +333,38 @@ def v_cell_d(self): vc_del = vc_max - vc_min # current control done elsewhere. - if vc_min < (self.V_C_min+0.05) and vc_min > 0: + if vc_min < (self.V_C_min + 0.05) and vc_min > 0: p_genrun = True p_loadshed = True Q_Batt = 0 - elif vc_min > self.V_C_min+0.15: + elif vc_min > self.V_C_min + 0.15: p_loadshed = False - if vc_max > self.V_C_max-0.05: + if vc_max > self.V_C_max - 0.05: p_charging = False Q_Batt = Q_nom - elif vc_max < self.V_C_max-0.15: + elif vc_max < self.V_C_max - 0.15: p_charging = True inpins(self) - return(b_lim) + return b_lim def CSA(xdata, self): global R_shunt, Ai, Ai_offs - Ai = (xdata*0.000305-2.5)/R_shunt + Ai_offs + Ai = (xdata * 0.000305 - 2.5) / R_shunt + Ai_offs self.current = Ai calc_Ah(Ai, self) - return(Ai) + return Ai def calc_Ah(Ai, self): - global Q_Batt, Q_time, Q_B_chg, Q_B_dis, Ah_b_max, Ah_b_min,\ - x_soc_min, x_soc_max, x_Soc, Q_nom, SOH, kWh_chg, kWh_dis, V_bat_Sum,\ - cum_bp_kwh_in, cum_bp_kwh_out, p_genrun, Q_Cycles + global Q_Batt, Q_time, Q_B_chg, Q_B_dis, Ah_b_max, Ah_b_min, x_soc_min, x_soc_max, x_Soc, Q_nom, SOH, kWh_chg, kWh_dis, V_bat_Sum, cum_bp_kwh_in, cum_bp_kwh_out, p_genrun, Q_Cycles if Q_time == 0: Q_time = time.time() t_Q = time.time() - d_Qt = t_Q-Q_time + d_Qt = t_Q - Q_time Q_time = t_Q - dQ_Batt = Ai*d_Qt/3600 + dQ_Batt = Ai * d_Qt / 3600 Q_Batt += dQ_Batt if Q_Batt > Q_nom: Q_Batt = Q_nom @@ -382,40 +375,40 @@ def calc_Ah(Ai, self): if Q_Batt < Ah_b_min: Ah_b_min = Q_Batt - x_Soc = Q_Batt/Q_nom*100 + x_Soc = Q_Batt / Q_nom * 100 self.soc = x_Soc - self.capacity_remain = x_Soc*Q_nom/100 + self.capacity_remain = x_Soc * Q_nom / 100 if x_Soc < 20: p_genrun = True elif x_Soc > 35: p_genrun = False - SOH = (1-cum_bp_kwh_out/Q_nom*0.00005)*100 - Q_act = Q_nom*SOH/100 - Q_Cycles = cum_bp_kwh_out/Q_nom*.00005 + SOH = (1 - cum_bp_kwh_out / Q_nom * 0.00005) * 100 + Q_act = Q_nom * SOH / 100 + Q_Cycles = cum_bp_kwh_out / Q_nom * 0.00005 self.cycles = Q_Cycles # Need to convert SOH to cycles... # or add soh as dbus channel if Ai > 0: Q_B_chg += dQ_Batt - kWh_chg += dQ_Batt*V_bat_Sum/1000 - cum_bp_kwh_in += dQ_Batt*V_bat_Sum/1000 + kWh_chg += dQ_Batt * V_bat_Sum / 1000 + cum_bp_kwh_in += dQ_Batt * V_bat_Sum / 1000 else: Q_B_dis -= dQ_Batt - kWh_dis -= dQ_Batt*V_bat_Sum/1000 - cum_bp_kwh_out -= dQ_Batt*V_bat_Sum/1000 + kWh_dis -= dQ_Batt * V_bat_Sum / 1000 + cum_bp_kwh_out -= dQ_Batt * V_bat_Sum / 1000 - return() + return () def gpio_decode(xdata, adr, self): # need to add Dbus channel for device temp global Vt_ref, Tbat, T_Cells try: - s = float(0x3fff)/float(xdata+1) + s = float(0x3FFF) / float(xdata + 1) t = math.log(s) - u = t/0.01998 - T_Cells[adr] = u-12.74 + u = t / 0.01998 + T_Cells[adr] = u - 12.74 except Exception as e: print("gpio_dec", e) print("gpio_dec", adr, "{:04x}".format(xdata)) @@ -431,12 +424,12 @@ def gpio_decode(xdata, adr, self): t_min = T_Cells[i] imin = i - self.temp1 = (T_Cells[0]+T_Cells[1])/2 - self.temp2 = (T_Cells[2]+T_Cells[3])/2 - self.temp3 = (T_Cells[5]+T_Cells[6])/2 + self.temp1 = (T_Cells[0] + T_Cells[1]) / 2 + self.temp2 = (T_Cells[2] + T_Cells[3]) / 2 + self.temp3 = (T_Cells[5] + T_Cells[6]) / 2 self.temp_max_no = imax self.temp_min_no = imin - return() + return () def cell_balance(V_Cells, vc_min, vc_max, self): @@ -446,88 +439,88 @@ def cell_balance(V_Cells, vc_min, vc_max, self): bal_stat = f[3] >> 14 if bal_stat == 3: spi_xfer_MAX17(0, 0x80, 0x0) - spi_xfer_MAX17(0, 0x6f, 0x00) + spi_xfer_MAX17(0, 0x6F, 0x00) print("bal reset") - return() + return () if (bal_stat) & 1 > 0: - #print("bal run") - return() # Balancing in progress + # print("bal run") + return () # Balancing in progress if (bal_stat) == 2: # balancing complete - #print(511,"Bal Complete") - for i in range(0x6f, 0x81): + # print(511,"Bal Complete") + for i in range(0x6F, 0x81): spi_xfer_MAX17(0, i, 0x00) else: f = spi_xfer_MAX17(0, 0x80, 0) if f[0] != 0: stat_clr() cb_sum = 0 - cb_duty = int((vc_max-vc_min-0.01)*500) + cb_duty = int((vc_max - vc_min - 0.01) * 500) if cb_duty > 15: - cb_duty = 0xf + cb_duty = 0xF max_cell = (f[3] >> 8) & 0x07 min_cell = f[3] & 0x07 for i in range(1, 9): - Vc_t = int((V_Cells[i-1]-vc_min)/(vc_max-vc_min)*15) + Vc_t = int((V_Cells[i - 1] - vc_min) / (vc_max - vc_min) * 15) if Vc_t < 0: print(517, "<0") Vc_t = 0 # remove -ve - if Vc_t >= 0 and V_Cells[i-1] > 3.35: - bal_count[i-1] += Vc_t - self.cells[i-1].balance = True - if bal_count[i-1] > 65535: + if Vc_t >= 0 and V_Cells[i - 1] > 3.35: + bal_count[i - 1] += Vc_t + self.cells[i - 1].balance = True + if bal_count[i - 1] > 65535: for j in range(0, 8): bal_count[j] = bal_count[j] >> 1 else: Vc_t = 0 - self.cells[i-1].balance = False + self.cells[i - 1].balance = False cb_sum += Vc_t - spi_xfer_MAX17(0, 0x70+i, Vc_t) # set cell timers - f = spi_xfer_MAX17(1, 0x70+i, 0) # and read back + spi_xfer_MAX17(0, 0x70 + i, Vc_t) # set cell timers + f = spi_xfer_MAX17(1, 0x70 + i, 0) # and read back if f[3] != Vc_t: print(471, "Can't set T bal :", i) f = spi_xfer_MAX17(1, 3, 0) print("prt write rjt", f[3] & 1) if cb_sum != 0: # enable cells & start timer - f = spi_xfer_MAX17(0, 0x6f, 0x1fe) + f = spi_xfer_MAX17(0, 0x6F, 0x1FE) if f[0] != 0: stat_clr() # R_bal_stat() Temporary for diagnostic xdata = 0x2002 | cb_duty << 4 - xdata = xdata % 0xc7ff + xdata = xdata % 0xC7FF f = spi_xfer_MAX17(0, 0x80, xdata) # print(480,"{:04x}".format(f[3]),"{:02x}".format(f[0])) f = spi_xfer_MAX17(1, 0x80, 0) # print(481,"{:04x}".format(f[3]),"{:02x}".format(f[0])) - return() + return () def R_bal_stat(): - for i in range(0x6f, 0x84): + for i in range(0x6F, 0x84): f = spi_xfer_MAX17(1, i, 0x00) print("{:02x}".format(i), "{:04x}".format(f[3]), "{:02x}".format(f[0])) - return() + return () def stat_clr(): for i in range(2, 7): spi_xfer_MAX17(0, i, 0) - return() + return () def die_temp(self): global Tj, tmaxp, Fan_run_b f = spi_xfer_MAX17(1, 0x57, 0) # read diag 1 register Vptat = f[3] >> 2 - Vptat = Vptat/0x4000*2.3077 - Tj = Vptat/0.0032+8.3-273 + Vptat = Vptat / 0x4000 * 2.3077 + Tj = Vptat / 0.0032 + 8.3 - 273 self.temp4 = Tj if Tj > 45: Fan_run_b = True elif Tj < 40: Fan_run_b = False - return(Tj) + return Tj def inpins(self): @@ -554,13 +547,13 @@ def inpins(self): Fan_run.on() else: Fan_run.off() - return() + return () def data_cycle(self): global err_no, T_Cells, vc_max # print("data_cycle") - spi_xfer_MAX17(0, 0x66, 0xe21) + spi_xfer_MAX17(0, 0x66, 0xE21) f = spi_xfer_MAX17(1, 0x66, 0x00) scn_dn = f[3] >> 15 dat_rdy = (f[3] & 0x2000) >> 13 @@ -575,15 +568,15 @@ def data_cycle(self): scn_dn = f[3] & 0x8000 >> 15 dat_rdy = f[3] & 0x2000 >> 13 spi_xfer_MAX17(0, 0x83, 0x1) # manual xfer - f = spi_xfer_MAX17(0, 0x66, 0x1e28) + f = spi_xfer_MAX17(0, 0x66, 0x1E28) if f[0] > 0: stat_clr() V_bat_sum = 0 for i in range(72, 0x50): f = spi_xfer_MAX17(1, i, 0) - v = vblk_dec((f[3] >> 2), 0.000305, i-72) # no change + v = vblk_dec((f[3] >> 2), 0.000305, i - 72) # no change V_bat_sum += v - V_Cells[i-72] = v + V_Cells[i - 72] = v cb_b = v_cell_d(self) time.sleep(0.005) self.voltage = V_bat_sum @@ -594,7 +587,7 @@ def data_cycle(self): cell_balance(V_Cells, vc_min, vc_max, self) else: spi_xfer_MAX17(0, 0x80, 0x00) - spi_xfer_MAX17(0, 0x6f, 0x00) + spi_xfer_MAX17(0, 0x6F, 0x00) self.poll_interval = 1000 f = spi_xfer_MAX17(1, 0x47, 0) CSA(f[3] >> 2, self) @@ -602,9 +595,9 @@ def data_cycle(self): vblk_dec((f[3] >> 2), 0.003967, 22) f = spi_xfer_MAX17(1, 0x56, 0) vblk_dec(f[3], 0.00122, 2) # 02 - for i in range(0x59, 0x5f, 1): + for i in range(0x59, 0x5F, 1): f = spi_xfer_MAX17(1, i, 0) - gpio_decode(f[3] >> 2, i-89, self) # 49-64 + gpio_decode(f[3] >> 2, i - 89, self) # 49-64 time.sleep(0.005) stat_scan(self) - return(True) + return True diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 6ebbe7f4..6c61a7c0 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -2,7 +2,7 @@ import logging import serial from time import sleep -from struct import * +from struct import unpack_from import bisect # Logging @@ -13,31 +13,31 @@ # battery types # if not specified: baud = 9600 battery_types = [ - {'bms' : "LltJbd"}, - {'bms' : "Ant", "baud" : 19200}, - {"bms" : "Daly", "address" : b"\x40"}, - {"bms" : "Daly", "address" : b"\x80"}, - {"bms" : "Jkbms", "baud" : 115200}, -# {"bms" : "Sinowealth"}, - {"bms" : "Lifepower"}, - {"bms" : "Renogy", "address": b"\x30"}, - {"bms" : "Renogy", "address": b"\xF7"}, - {"bms" : "Ecs", "baud" : 19200}, -# {"bms" : "MNB"}, + {"bms": "LltJbd"}, + {"bms": "Ant", "baud": 19200}, + {"bms": "Daly", "address": b"\x40"}, + {"bms": "Daly", "address": b"\x80"}, + {"bms": "Jkbms", "baud": 115200}, + # {"bms" : "Sinowealth"}, + {"bms": "Lifepower"}, + {"bms": "Renogy", "address": b"\x30"}, + {"bms": "Renogy", "address": b"\xF7"}, + {"bms": "Ecs", "baud": 19200}, + # {"bms" : "MNB"}, ] # Constants - Need to dynamically get them in future DRIVER_VERSION = 0.14 -DRIVER_SUBVERSION = '.3' +DRIVER_SUBVERSION = ".3" zero_char = chr(48) -degree_sign = u'\N{DEGREE SIGN}' +degree_sign = "\N{DEGREE SIGN}" # Choose the mode for voltage / current limitations (True / False) # False is a Step mode. This is the default with limitations on hard boundary steps # True "Linear" # New linear limitations by WaldemarFech for smoother values LINEAR_LIMITATION_ENABLE = False -######### Cell Voltage limitation ######### +# -------- Cell Voltage limitation --------- # Description: # Maximal charge / discharge current will be in-/decreased depending on min- and max-cell-voltages # Example: 18cells * 3.55V/cell = 63.9V max charge voltage. 18 * 2.7V = 48,6V min discharge voltage @@ -49,13 +49,13 @@ DCCM_CV_ENABLE = True # Set Steps to reduce battery current. The current will be changed linear between those steps -CELL_VOLTAGES_WHILE_CHARGING = [3.55, 3.50, 3.45, 3.30] -MAX_CHARGE_CURRENT_CV = [ 0, 2, 30, 60] +CELL_VOLTAGES_WHILE_CHARGING = [3.55, 3.50, 3.45, 3.30] +MAX_CHARGE_CURRENT_CV = [0, 2, 30, 60] -CELL_VOLTAGES_WHILE_DISCHARGING = [2.70, 2.80, 2.90, 3.10] -MAX_DISCHARGE_CURRENT_CV = [ 0, 5, 30, 60] +CELL_VOLTAGES_WHILE_DISCHARGING = [2.70, 2.80, 2.90, 3.10] +MAX_DISCHARGE_CURRENT_CV = [0, 5, 30, 60] -######### Temperature limitation ######### +# -------- Temperature limitation --------- # Description: # Maximal charge / discharge current will be in-/decreased depending on temperature # Example: The temperature limit will be monitored to control the currents. If there are two temperature senors, @@ -66,20 +66,20 @@ DCCM_T_ENABLE = True # Set Steps to reduce battery current. The current will be changed linear between those steps -TEMPERATURE_LIMITS_WHILE_CHARGING = [55, 40, 35, 5, 2, 0] -MAX_CHARGE_CURRENT_T = [ 0, 28, 60, 60, 28, 0] +TEMPERATURE_LIMITS_WHILE_CHARGING = [55, 40, 35, 5, 2, 0] +MAX_CHARGE_CURRENT_T = [0, 28, 60, 60, 28, 0] -TEMPERATURE_LIMITS_WHILE_DISCHARGING = [55, 40, 35, 5, 0, -20] -MAX_DISCHARGE_CURRENT_T = [ 0, 28, 60, 60, 28, 0] +TEMPERATURE_LIMITS_WHILE_DISCHARGING = [55, 40, 35, 5, 0, -20] +MAX_DISCHARGE_CURRENT_T = [0, 28, 60, 60, 28, 0] # if the cell voltage reaches 3.55V, then reduce current battery-voltage by 0.01V # if the cell voltage goes over 3.6V, then the maximum penalty will not be exceeded # there will be a sum of all penalties for each cell, which exceeds the limits -PENALTY_AT_CELL_VOLTAGE = [3.45, 3.55, 3.6] -PENALTY_BATTERY_VOLTAGE = [0.01, 1.0, 2.0] # this voltage will be subtracted +PENALTY_AT_CELL_VOLTAGE = [3.45, 3.55, 3.6] +PENALTY_BATTERY_VOLTAGE = [0.01, 1.0, 2.0] # this voltage will be subtracted -######### SOC limitation ######### +# -------- SOC limitation --------- # Description: # Maximal charge / discharge current will be increased / decreased depending on State of Charge, see CC_SOC_LIMIT1 etc. # The State of Charge (SoC) charge / discharge current will be in-/decreased depending on SOC. @@ -88,7 +88,7 @@ MIN_CELL_VOLTAGE = 2.9 MAX_CELL_VOLTAGE = 3.45 FLOAT_CELL_VOLTAGE = 3.35 -MAX_VOLTAGE_TIME_SEC = 15*60 +MAX_VOLTAGE_TIME_SEC = 15 * 60 SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90 # battery Current limits @@ -100,25 +100,25 @@ # Discharge current control management enable (True/False). DCCM_SOC_ENABLE = True -#charge current soc limits +# charge current soc limits CC_SOC_LIMIT1 = 98 CC_SOC_LIMIT2 = 95 CC_SOC_LIMIT3 = 91 -#charge current limits +# charge current limits CC_CURRENT_LIMIT1 = 5 -CC_CURRENT_LIMIT2 = MAX_BATTERY_CHARGE_CURRENT/4 -CC_CURRENT_LIMIT3 = MAX_BATTERY_CHARGE_CURRENT/2 +CC_CURRENT_LIMIT2 = MAX_BATTERY_CHARGE_CURRENT / 4 +CC_CURRENT_LIMIT3 = MAX_BATTERY_CHARGE_CURRENT / 2 -#discharge current soc limits +# discharge current soc limits DC_SOC_LIMIT1 = 10 DC_SOC_LIMIT2 = 20 DC_SOC_LIMIT3 = 30 -#discharge current limits +# discharge current limits DC_CURRENT_LIMIT1 = 5 -DC_CURRENT_LIMIT2 = MAX_BATTERY_DISCHARGE_CURRENT/4 -DC_CURRENT_LIMIT3 = MAX_BATTERY_DISCHARGE_CURRENT/2 +DC_CURRENT_LIMIT2 = MAX_BATTERY_DISCHARGE_CURRENT / 4 +DC_CURRENT_LIMIT3 = MAX_BATTERY_DISCHARGE_CURRENT / 2 # Charge voltage control management enable (True/False). CVCM_ENABLE = False @@ -126,28 +126,29 @@ # Simulate Midpoint graph (True/False). MIDPOINT_ENABLE = False -#soc low levels +# soc low levels SOC_LOW_WARNING = 20 SOC_LOW_ALARM = 10 # Daly settings -# Battery capacity (amps) if the BMS does not support reading it +# Battery capacity (amps) if the BMS does not support reading it BATTERY_CAPACITY = 50 # Invert Battery Current. Default non-inverted. Set to -1 to invert INVERT_CURRENT_MEASUREMENT = 1 # TIME TO SOC settings [Valid values 0-100, but I don't recommend more that 20 intervals] # Set of SoC percentages to report on dbus. The more you specify the more it will impact system performance. -# TIME_TO_SOC_POINTS = [100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0] # Every 5% SoC +# TIME_TO_SOC_POINTS = [100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0] +# Every 5% SoC # TIME_TO_SOC_POINTS = [100, 95, 90, 85, 75, 50, 25, 20, 10, 0] -TIME_TO_SOC_POINTS = [] # No data set to disable +TIME_TO_SOC_POINTS = [] # No data set to disable # Specify TimeToSoc value type: [Valid values 1,2,3] # TIME_TO_SOC_VALUE_TYPE = 1 # Seconds # TIME_TO_SOC_VALUE_TYPE = 2 # Time string HH:MN:SC -TIME_TO_SOC_VALUE_TYPE = 3 # Both Seconds and time str " [days, HR:MN:SC]" +TIME_TO_SOC_VALUE_TYPE = 3 # Both Seconds and time str " [days, HR:MN:SC]" # Specify how many loop cycles between each TimeToSoc updates TIME_TO_SOC_LOOP_CYCLES = 5 -# Include TimeToSoC points when moving away from the SoC point. [Valid values True,False] +# Include TimeToSoC points when moving away from the SoC point. [Valid values True,False] # These will be as negative time. Disabling this improves performance slightly. TIME_TO_SOC_INC_FROM = False @@ -165,66 +166,83 @@ LIPRO_END_ADDRESS = 4 LIPRO_CELL_COUNT = 15 + def constrain(val, min_val, max_val): if min_val > max_val: min_val, max_val = max_val, min_val return min(max_val, max(min_val, val)) + def mapRange(inValue, inMin, inMax, outMin, outMax): return outMin + (((inValue - inMin) / (inMax - inMin)) * (outMax - outMin)) + def mapRangeConstrain(inValue, inMin, inMax, outMin, outMax): return constrain(mapRange(inValue, inMin, inMax, outMin, outMax), outMin, outMax) + def calcLinearRelationship(inValue, inArray, outArray): - if inArray[0] > inArray[-1]: # change compare-direction in array + if inArray[0] > inArray[-1]: # change compare-direction in array return calcLinearRelationship(inValue, inArray[::-1], outArray[::-1]) else: - + # Handle out of bounds if inValue <= inArray[0]: return outArray[0] if inValue >= inArray[-1]: return outArray[-1] - + # else calculate linear current between the setpoints idx = bisect.bisect(inArray, inValue) - upperIN = inArray[idx - 1] # begin with idx 0 as max value + upperIN = inArray[idx - 1] # begin with idx 0 as max value upperOUT = outArray[idx - 1] - lowerIN = inArray[idx] - lowerOUT = outArray[idx] - return mapRangeConstrain(inValue, lowerIN, upperIN, lowerOUT, upperOUT) + lowerIN = inArray[idx] + lowerOUT = outArray[idx] + return mapRangeConstrain(inValue, lowerIN, upperIN, lowerOUT, upperOUT) + def calcStepRelationship(inValue, inArray, outArray, returnLower): - if inArray[0] > inArray[-1]: # change compare-direction in array + if inArray[0] > inArray[-1]: # change compare-direction in array return calcStepRelationship(inValue, inArray[::-1], outArray[::-1], returnLower) - + # Handle out of bounds if inValue <= inArray[0]: return outArray[0] if inValue >= inArray[-1]: return outArray[-1] - + # else get index between the setpoints idx = bisect.bisect(inArray, inValue) - - return outArray[idx] if returnLower else outArray[idx-1] + + return outArray[idx] if returnLower else outArray[idx - 1] + def is_bit_set(tmp): return False if tmp == zero_char else True + def kelvin_to_celsius(kelvin_temp): return kelvin_temp - 273.1 -def format_value(value, prefix, suffix): - return None if value is None else ('' if prefix is None else prefix) + \ - str(value) + \ - ('' if suffix is None else suffix) -def read_serial_data(command, port, baud, length_pos, length_check, length_fixed=None, length_size=None): +def format_value(value, prefix, suffix): + return ( + None + if value is None + else ("" if prefix is None else prefix) + + str(value) + + ("" if suffix is None else suffix) + ) + + +def read_serial_data( + command, port, baud, length_pos, length_check, length_fixed=None, length_size=None +): try: with serial.Serial(port, baudrate=baud, timeout=0.1) as ser: - return read_serialport_data(ser, command, length_pos, length_check, length_fixed, length_size) + return read_serialport_data( + ser, command, length_pos, length_check, length_fixed, length_size + ) except serial.SerialException as e: logger.error(e) @@ -232,7 +250,7 @@ def read_serial_data(command, port, baud, length_pos, length_check, length_fixed # Open the serial port -# Return variable for the openned port +# Return variable for the openned port def open_serial_port(port, baud): ser = None tries = 3 @@ -243,57 +261,68 @@ def open_serial_port(port, baud): except serial.SerialException as e: logger.error(e) tries -= 1 - + return ser + # Read data from previously openned serial port -def read_serialport_data(ser, command, length_pos, length_check, length_fixed=None, length_size=None): +def read_serialport_data( + ser, command, length_pos, length_check, length_fixed=None, length_size=None +): try: ser.flushOutput() ser.flushInput() ser.write(command) length_byte_size = 1 - if length_size is not None: - if length_size.upper() == 'H': + if length_size is not None: + if length_size.upper() == "H": length_byte_size = 2 - elif length_size.upper() == 'I' or length_size.upper() == 'L': + elif length_size.upper() == "I" or length_size.upper() == "L": length_byte_size = 4 count = 0 toread = ser.inWaiting() - while toread < (length_pos+length_byte_size): + while toread < (length_pos + length_byte_size): sleep(0.005) toread = ser.inWaiting() count += 1 if count > 50: logger.error(">>> ERROR: No reply - returning") return False - - #logger.info('serial data toread ' + str(toread)) + + # logger.info('serial data toread ' + str(toread)) res = ser.read(toread) if length_fixed is not None: length = length_fixed else: - if len(res) < (length_pos+length_byte_size): - logger.error(">>> ERROR: No reply - returning [len:" + str(len(res)) + "]") + if len(res) < (length_pos + length_byte_size): + logger.error( + ">>> ERROR: No reply - returning [len:" + str(len(res)) + "]" + ) return False - length_size = length_size if length_size is not None else 'B' - length = unpack_from('>'+length_size, res,length_pos)[0] - - #logger.info('serial data length ' + str(length)) + length_size = length_size if length_size is not None else "B" + length = unpack_from(">" + length_size, res, length_pos)[0] + + # logger.info('serial data length ' + str(length)) count = 0 data = bytearray(res) while len(data) <= length + length_check: res = ser.read(length + length_check) data.extend(res) - #logger.info('serial data length ' + str(len(data))) + # logger.info('serial data length ' + str(len(data))) sleep(0.005) count += 1 if count > 150: - logger.error(">>> ERROR: No reply - returning [len:" + str(len(data)) + "/" + str(length + length_check) + "]") + logger.error( + ">>> ERROR: No reply - returning [len:" + + str(len(data)) + + "/" + + str(length + length_check) + + "]" + ) return False return data diff --git a/requirements.txt b/requirements.txt index 4cffd76d..b8b16d03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pyserial==3.5 velib_python==2.80 -minimalmodbus==2.0.1 \ No newline at end of file +minimalmodbus==2.0.1