From ff4c88617191e52a81c14d1130a9c98ab088936f Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 11:56:57 +0200 Subject: [PATCH 1/3] Removed comments from utils.py This should make more clear that there are no values to change --- etc/dbus-serialbattery/utils.py | 172 +------------------------------- 1 file changed, 4 insertions(+), 168 deletions(-) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 3e6cfa58..4cc98732 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,11 +37,13 @@ def _get_list_from_config( ) -# Constants - Need to dynamically get them in future +# Constants DRIVER_VERSION = "1.0.20230902dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" +# save config values to constants + # --------- Battery Current limits --------- MAX_BATTERY_CHARGE_CURRENT = float(config["DEFAULT"]["MAX_BATTERY_CHARGE_CURRENT"]) MAX_BATTERY_DISCHARGE_CURRENT = float( @@ -49,11 +51,9 @@ def _get_list_from_config( ) # --------- Cell Voltages --------- -# Description: Cell min/max voltages which are used to calculate the min/max battery voltage -# Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage MIN_CELL_VOLTAGE = float(config["DEFAULT"]["MIN_CELL_VOLTAGE"]) MAX_CELL_VOLTAGE = float(config["DEFAULT"]["MAX_CELL_VOLTAGE"]) -# Max voltage can seen as absorption voltage + FLOAT_CELL_VOLTAGE = float(config["DEFAULT"]["FLOAT_CELL_VOLTAGE"]) if FLOAT_CELL_VOLTAGE > MAX_CELL_VOLTAGE: FLOAT_CELL_VOLTAGE = MAX_CELL_VOLTAGE @@ -66,23 +66,12 @@ def _get_list_from_config( ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -# Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS) -# Has to be higher as the MAX_CELL_VOLTAGE BULK_CELL_VOLTAGE = float(config["DEFAULT"]["BULK_CELL_VOLTAGE"]) if BULK_CELL_VOLTAGE < MAX_CELL_VOLTAGE: BULK_CELL_VOLTAGE = MAX_CELL_VOLTAGE logger.error( ">>> ERROR: BULK_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -# Specify after how many days the bulk voltage should be reached again -# The timer is reset when the bulk voltage is reached -# Leave empty if you don't want to use this -# Example: Value is set to 15 -# day 1: bulk reached once -# day 16: bulk reached twice -# day 31: bulk not reached since it's very cloudy -# day 34: bulk reached since the sun came out -# day 49: bulk reached again, since last time it took 3 days to reach bulk voltage BULK_AFTER_DAYS = ( int(config["DEFAULT"]["BULK_AFTER_DAYS"]) if config["DEFAULT"]["BULK_AFTER_DAYS"] != "" @@ -90,93 +79,31 @@ def _get_list_from_config( ) # --------- BMS disconnect behaviour --------- -# Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the -# BMS on purpose, then you have to restart the driver/system to reset the block. -# False: Charge and discharge is not blocked on BMS communication loss -# True: Charge and discharge is blocked on BMS communication loss, it's unblocked when connection is established -# again or the driver/system is restarted BLOCK_ON_DISCONNECT = "True" == config["DEFAULT"]["BLOCK_ON_DISCONNECT"] # --------- Charge mode --------- -# 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 is a linear mode: -# For CCL and DCL the values between the steps are calculated for smoother values (by WaldemarFech) -# For CVL max battery voltage is calculated dynamically in order that the max cell voltage is not exceeded LINEAR_LIMITATION_ENABLE = "True" == config["DEFAULT"]["LINEAR_LIMITATION_ENABLE"] - -# Specify in seconds how often the penalty should be recalculated LINEAR_RECALCULATION_EVERY = int(config["DEFAULT"]["LINEAR_RECALCULATION_EVERY"]) -# Specify in percent when the linear values should be recalculated immediately -# Example: 5 for a immediate change, when the value changes by more than 5% LINEAR_RECALCULATION_ON_PERC_CHANGE = int( config["DEFAULT"]["LINEAR_RECALCULATION_ON_PERC_CHANGE"] ) - # --------- Charge Voltage limitation (affecting CVL) --------- -# Description: Limit max charging voltage (MAX_CELL_VOLTAGE * cell count), switch from max voltage to float -# voltage (FLOAT_CELL_VOLTAGE * cell count) and back -# False: Max charging voltage is always kept -# True: Max charging voltage is reduced based on charge mode -# Step mode: After max voltage is reached for MAX_VOLTAGE_TIME_SEC it switches to float voltage. After -# SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage. -# Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to -# CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to float voltage after 300 (fixed) -# additional seconds. -# After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT -# OR -# SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT -# it switches back to max voltage. -# Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to -# float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is -# once below 90% -# OR -# The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float -# voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of -# 55.2V again if max cell difference is above 0.080V or SoC below 90%. -# Charge voltage control management enable (True/False). CVCM_ENABLE = "True" == config["DEFAULT"]["CVCM_ENABLE"] - -# -- CVL reset based on cell voltage diff (linear mode) -# Specify cell voltage diff where CVL limit is kept until diff is equal or lower CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL = float( config["DEFAULT"]["CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL"] ) -# Specify cell voltage diff where CVL limit is reset to max voltage, if value get above -# the cells are considered as imbalanced, if the cell diff exceeds 5% of the nominal cell voltage -# e.g. 3.2 V * 5 / 100 = 0.160 V CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = float( config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"] ) -# -- CVL reset based on SoC option (step mode & linear mode) -# Specify how long the max voltage should be kept -# Step mode: If reached then switch to float voltage -# Linear mode: If cells are balanced keep max voltage for further MAX_VOLTAGE_TIME_SEC seconds MAX_VOLTAGE_TIME_SEC = int(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) -# Specify SoC where CVL limit is reset to max voltage -# Step mode: If SoC gets below -# Linear mode: If cells are unbalanced or if SoC gets below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = int( config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"] ) - - -# --------- Cell Voltage Current limitation (affecting CCL/DCL) --------- -# Description: Maximal charge / discharge current will be in-/decreased depending on min and max cell voltages -# Example: 18 cells * 3.55V/cell = 63.9V max charge voltage -# 18 cells * 2.70V/cell = 48.6V min discharge voltage -# But in reality not all cells reach the same voltage at the same time. The (dis)charge current -# will be (in-/)decreased, if even ONE SINGLE BATTERY CELL reaches the limits - -# Charge current control management referring to cell-voltage enable (True/False). CCCM_CV_ENABLE = "True" == config["DEFAULT"]["CCCM_CV_ENABLE"] -# Discharge current control management referring to cell-voltage enable (True/False). DCCM_CV_ENABLE = "True" == config["DEFAULT"]["DCCM_CV_ENABLE"] -# Set steps to reduce battery current -# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True CELL_VOLTAGES_WHILE_CHARGING = _get_list_from_config( "DEFAULT", "CELL_VOLTAGES_WHILE_CHARGING", lambda v: float(v) ) @@ -203,18 +130,10 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) - # --------- Temperature limitation (affecting CCL/DCL) --------- -# 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, -# then the worst case will be calculated and the more secure lower current will be set. -# Charge current control management referring to temperature enable (True/False). CCCM_T_ENABLE = "True" == config["DEFAULT"]["CCCM_T_ENABLE"] -# Charge current control management referring to temperature enable (True/False). DCCM_T_ENABLE = "True" == config["DEFAULT"]["DCCM_T_ENABLE"] -# Set steps to reduce battery current -# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True TEMPERATURE_LIMITS_WHILE_CHARGING = _get_list_from_config( "DEFAULT", "TEMPERATURE_LIMITS_WHILE_CHARGING", lambda v: float(v) ) @@ -233,22 +152,14 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) - # --------- SOC limitation (affecting CCL/DCL) --------- -# Description: Maximal charge / discharge current will be increased / decreased depending on State of Charge, -# see CC_SOC_LIMIT1 etc. -# Example: The SoC limit will be monitored to control the currents. -# Charge current control management enable (True/False). CCCM_SOC_ENABLE = "True" == config["DEFAULT"]["CCCM_SOC_ENABLE"] -# Discharge current control management enable (True/False). DCCM_SOC_ENABLE = "True" == config["DEFAULT"]["DCCM_SOC_ENABLE"] -# Charge current soc limits CC_SOC_LIMIT1 = float(config["DEFAULT"]["CC_SOC_LIMIT1"]) CC_SOC_LIMIT2 = float(config["DEFAULT"]["CC_SOC_LIMIT2"]) CC_SOC_LIMIT3 = float(config["DEFAULT"]["CC_SOC_LIMIT3"]) -# Charge current limits CC_CURRENT_LIMIT1 = MAX_BATTERY_CHARGE_CURRENT * float( config["DEFAULT"]["CC_CURRENT_LIMIT1_FRACTION"] ) @@ -259,12 +170,10 @@ def _get_list_from_config( config["DEFAULT"]["CC_CURRENT_LIMIT3_FRACTION"] ) -# Discharge current soc limits DC_SOC_LIMIT1 = float(config["DEFAULT"]["DC_SOC_LIMIT1"]) DC_SOC_LIMIT2 = float(config["DEFAULT"]["DC_SOC_LIMIT2"]) DC_SOC_LIMIT3 = float(config["DEFAULT"]["DC_SOC_LIMIT3"]) -# Discharge current limits DC_CURRENT_LIMIT1 = MAX_BATTERY_DISCHARGE_CURRENT * float( config["DEFAULT"]["DC_CURRENT_LIMIT1_FRACTION"] ) @@ -275,114 +184,53 @@ def _get_list_from_config( config["DEFAULT"]["DC_CURRENT_LIMIT3_FRACTION"] ) - # --------- Time-To-Go --------- -# Description: Calculates the time to go shown in the GUI TIME_TO_GO_ENABLE = "True" == config["DEFAULT"]["TIME_TO_GO_ENABLE"] # --------- Time-To-Soc --------- -# Description: Calculates the time to a specific SoC -# Example: TIME_TO_SOC_POINTS = 50, 25, 15, 0 -# 6h 24m remaining until 50% SoC -# 17h 36m remaining until 25% SoC -# 22h 5m remaining until 15% SoC -# 28h 48m remaining until 0% SoC -# Set of SoC percentages to report on dbus and MQTT. The more you specify the more it will impact system performance. -# [Valid values 0-100, comma separated list. More that 20 intervals are not recommended] -# Example: TIME_TO_SOC_POINTS = 100, 95, 90, 85, 75, 50, 25, 20, 10, 0 -# Leave empty to disable TIME_TO_SOC_POINTS = _get_list_from_config( "DEFAULT", "TIME_TO_SOC_POINTS", lambda v: int(v) ) -# Specify TimeToSoc value type [Valid values 1, 2, 3] -# 1 Seconds -# 2 Time string d h m s -# 3 Both seconds and time string " [d h m s]" TIME_TO_SOC_VALUE_TYPE = int(config["DEFAULT"]["TIME_TO_SOC_VALUE_TYPE"]) -# Specify in seconds how often the TimeToSoc should be recalculated -# Minimum are 5 seconds to prevent CPU overload TIME_TO_SOC_RECALCULATE_EVERY = ( int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"]) if int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"]) > 5 else 5 ) -# 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 = "True" == config["DEFAULT"]["TIME_TO_SOC_INC_FROM"] - # --------- Additional settings --------- -# Specify one or more BMS types to load else leave empty to try to load all available -# -- Available BMS: -# Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos -# -- Available BMS, but disabled by default (just enter one or more below and it will be enabled): -# ANT, MNB, Sinowealth BMS_TYPE = _get_list_from_config("DEFAULT", "BMS_TYPE", lambda v: str(v)) -# Exclute this serial devices from the driver startup -# Example: /dev/ttyUSB2, /dev/ttyUSB4 EXCLUDED_DEVICES = _get_list_from_config( "DEFAULT", "EXCLUDED_DEVICES", lambda v: str(v) ) -# Enter custom battery names here or change it over the GUI -# Example: -# /dev/ttyUSB0:My first battery -# /dev/ttyUSB0:My first battery, /dev/ttyUSB1:My second battery CUSTOM_BATTERY_NAMES = _get_list_from_config( "DEFAULT", "CUSTOM_BATTERY_NAMES", lambda v: str(v) ) -# Auto reset SoC -# If on, then SoC is reset to 100%, if the value switches from absorption to float voltage -# Currently only working for Daly BMS AUTO_RESET_SOC = "True" == config["DEFAULT"]["AUTO_RESET_SOC"] -# Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) -# Select the format of cell data presented on dbus [Valid values 0,1,2,3] -# 0 Do not publish all the cells (only the min/max cell data as used by the default GX) -# 1 Format: /Voltages/Cell (also available for display on Remote Console) -# 2 Format: /Cell/#/Volts -# 3 Both formats 1 and 2 BATTERY_CELL_DATA_FORMAT = int(config["DEFAULT"]["BATTERY_CELL_DATA_FORMAT"]) -# Simulate Midpoint graph (True/False). MIDPOINT_ENABLE = "True" == config["DEFAULT"]["MIDPOINT_ENABLE"] -# Battery temperature -# Specifiy how the battery temperature is assembled -# 0 Get mean of temp sensor 1 and temp sensor 2 -# 1 Get only temp from temp sensor 1 -# 2 Get only temp from temp sensor 2 TEMP_BATTERY = int(config["DEFAULT"]["TEMP_BATTERY"]) -# Temperature sensor 1 name TEMP_1_NAME = config["DEFAULT"]["TEMP_1_NAME"] - -# Temperature sensor 2 name TEMP_2_NAME = config["DEFAULT"]["TEMP_2_NAME"] - -# Temperature sensor 3 name TEMP_3_NAME = config["DEFAULT"]["TEMP_3_NAME"] - -# Temperature sensor 2 name TEMP_4_NAME = config["DEFAULT"]["TEMP_4_NAME"] - # --------- BMS specific settings --------- - -# -- LltJbd settings -# SoC low levels -# NOTE: SOC_LOW_WARNING is also used to calculate the Time-To-Go even if you are not using a LltJbd BMS SOC_LOW_WARNING = float(config["DEFAULT"]["SOC_LOW_WARNING"]) SOC_LOW_ALARM = float(config["DEFAULT"]["SOC_LOW_ALARM"]) # -- Daly settings -# Battery capacity (amps) if the BMS does not support reading it BATTERY_CAPACITY = float(config["DEFAULT"]["BATTERY_CAPACITY"]) -# Invert Battery Current. Default non-inverted. Set to -1 to invert INVERT_CURRENT_MEASUREMENT = int(config["DEFAULT"]["INVERT_CURRENT_MEASUREMENT"]) # -- ESC GreenMeter and Lipro device settings @@ -396,19 +244,7 @@ def _get_list_from_config( "DEFAULT", "HELTEC_MODBUS_ADDR", lambda v: int(v) ) - # --------- Battery monitor specific settings --------- -# If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported -# from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage, -# since max voltage is never reached. -# Example: -# cell count: 16 -# MAX_CELL_VOLTAGE = 3.45 -# max voltage calculated = 16 * 3.45 = 55.20 -# CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS -# now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures -# 55.05 V the max voltage is never reached for the driver and max voltage is kept forever. -# Set VOLTAGE_DROP to 0.15 VOLTAGE_DROP = float(config["DEFAULT"]["VOLTAGE_DROP"]) From 048db0a52ff1831f22379ed29c275b92efebfc59 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 13:17:48 +0200 Subject: [PATCH 2/3] Updated changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dbac3e2..bfa909be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Current average of the last 5 minutes by @mr-manuel -* Added: Daly BMS: Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit +* Added: Daly BMS - Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: LLT/JBD BMS - Discharge / Charge Mosfet and disable / enable balancer switching over remote console/GUI with https://github.com/Louisvdw/dbus-serialbattery/pull/761 by @idstein +* Added: LLT/JBD BMS - Show balancer state in GUI under the IO page with https://github.com/Louisvdw/dbus-serialbattery/pull/763 by @idstein * Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Added: Temperature names to dbus and mqtt by @mr-manuel @@ -24,6 +26,8 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: LLT/JBD - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein +* Changed: LLT/JBD BMS - SOC different in Xiaoxiang app and dbus-serialbattery with https://github.com/Louisvdw/dbus-serialbattery/pull/760 by @idstein * Changed: Make CCL and DCL limiting messages more clear by @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich * Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel From 54055b18dedbeac791d59864b82cf4035b0585f3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 22:30:48 +0200 Subject: [PATCH 3/3] possible fix for LLT/JBS connection problems https://github.com/Louisvdw/dbus-serialbattery/issues/769 https://github.com/Louisvdw/dbus-serialbattery/issues/777 --- etc/dbus-serialbattery/bms/lltjbd_ble.py | 13 +++++++++---- etc/dbus-serialbattery/reinstall-local.sh | 5 +++++ etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py index 27d7e927..65e1d4b7 100644 --- a/etc/dbus-serialbattery/bms/lltjbd_ble.py +++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py @@ -3,6 +3,7 @@ import atexit import functools import threading +import sys from asyncio import CancelledError from typing import Union, Optional from utils import logger @@ -55,8 +56,14 @@ async def bt_main_loop(self): self.device = await BleakScanner.find_device_by_address( self.address, cb=dict(use_bdaddr=True) ) - except Exception as e: - logger.error(">>> ERROR: Bluetooth stack failed.", e) + + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) self.device = None await asyncio.sleep(0.5) @@ -173,8 +180,6 @@ def read_serial_data_llt(self, command): if __name__ == "__main__": - import sys - bat = LltJbd_Ble("Foo", -1, sys.argv[1]) if not bat.test_connection(): logger.error(">>> ERROR: Unable to connect") diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 48585e3e..4a9cd98d 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -175,6 +175,11 @@ if [ "$length" -gt 0 ]; then opkg update opkg install python3-misc python3-pip pip3 install bleak + # pip3 install bleak==0.20.2 + # pip3 install bleak==0.21.0 + pip3 install dbus-fast==1.87.0 + # pip3 install dbus-fast==1.87.3 + # pip3 install dbus-fast==1.87.4 echo "done." echo diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 4cc98732..8575c1a9 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230902dev" +DRIVER_VERSION = "1.0.20230904dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}"