From e762832d324062bcb33aef0694cc4c2db24eee54 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 6 May 2023 10:52:33 +0200 Subject: [PATCH] Some improvements and fixes (#468) * added balancing switch status * added balancing switch status * changed default config file - added missing descriptions - changed name * indentation was wrong * adding fix for Venus OS >= v3.00~14 QML files * added cell balancing status for JKBMS * changed ConsumedAmpHours should be None instead of 0, if not available * TimeToSoc fixes - Added: Show TimeToSoc only, if enabled - Changed: description in config file - Changed: default value TIME_TO_SOC_VALUE_TYPE = 1 * resorted functions * fix CPU overload - changed: how often Time-To-Soc is calculated in seconds * fix Time-to-Go and Time-to-SoC - changed: Time-To-Go has to be always a positive int since it's used from dbus-systemcalc-py - changed: how Time-to-SoC is string is generated and displayed (format like Time-To-Go) * small fixes * Show MOSFET temperature from JKBMS Pull request from @baphomett https://github.com/Louisvdw/dbus-serialbattery/pull/440 * bump version to 1.0.0 respecting semantic versioning * added uninstall script and fixed reinstall - `/data/conf/serial-starter.d` was not recreated if deleted * small fixes - added description in utils.py - change qml files in destination not source * typo * fixed cell balancing color for cells 13-24 * updated description in default config file * Updated logging to get relevant informations * clearer config file * make config file clearer * fix #421 - https://github.com/Louisvdw/dbus-serialbattery/issues/421 * missing removals * corrected typo, added missing +x in release.yml * added info for new issue * fix black lint errors * optimize installation scripts * added script to install directly from repository * fixed serial-starter.d https://github.com/Louisvdw/dbus-serialbattery/issues/520 * smaller fixes * fix #351 * fix System Switch in IO page * fix #239 by @TimGFoley * fix black lint errors * run code analyse only on code change * fix #311 * Separate Time-To-Go and Time-To-SoC activation * remove duplicated and incorrect log message on handle_changed_setting * properly log max battery currents on startup * optimize uninstall script * fix typo in log_settings * add charge mode display * Added: Temp name for sensor 1 & 2 Added the possibility to give a name to temperature sensor 1 & 2 This allows to see which sensor is low and high * Added: Choose how battery temperature is assembled * fix black lint errors * Added/Changed alarms for JKBMS * Added: HighInternalTemperature alarm for JKBMS * Changed: Temperature alarm to not trigger all in the same condition * Removed Alarms/HighCellVoltage It does not exist on the dbus * Changed: Moved charge mode from IO page to Paramaters page * Merge branch 'master' into master * Added: Show (dis)charge current limitation reason * Added changelog * corrected file permissions * Update readme * Small word case changes * fix dbus-daemon memory leak `poll_battery` is already called from the dbus mainloop in a separate thread. Opening a new thread here (and especially never closing it) will accumulate threads and leads to problems like `dbus-daemon` memory usage increasing over time, leading to eventual reboot by @seidler2547 * Added: Show specific TimeToSoC points in GUI Only if 0%, 10%, 20%, 80%, 90% and/or 100% are selected * fix black lint error * removed wildcard import, fixed black lint errors * removed wildcard import, fixed black lint errors * remove old log message in handle_changed_setting() * simplified condition for Time-To-Go/Soc * fix renogy import * added BMS info and cleanup * MNB * Revov * Sinowealth * moved BMS to subfolder * Added self.unique_identifier to the battery class Used to identify a BMS when multiple BMS are connected planned for future use * fix small errors * added linear voltage recalculation interval In the config file can now be defined how often CVL, CCL and DCL is recalculated * revert Daly adaption * replaced penalty voltage calculation with automatically calculated penalty voltages to simplify config max voltage is kept until batteries are balanced * flake config change * updated changelog * disabled ANT BMS by default https://github.com/Louisvdw/dbus-serialbattery/issues/479 * fix typo * fixed wrong variable assignment `str` instead of `int` * updated battery template * updated nightly script * Fix for #450 https://github.com/Louisvdw/dbus-serialbattery/issues/450 * Read charge/discharge limit JKBMS https://github.com/Louisvdw/dbus-serialbattery/issues/4 * updated release workflow * updated readme * deploy to github pages only on changes in master or docusaurus branch * cleanup * GitHub pages config change * Renamed scripts for better reading #532 * update docusaurus dependencies * limitation reason cleanup * changed default config settings FLOAT_CELL_VOLTAGE from 3.350V to 3.375V LINEAR_LIMITATION_ENABLE from False to True * small typo fixes * updated changelog * fix disconnection behaviour & small fixes * on disconnect, show '---' after 10s and 'not connected' after 60s by @transistorgit * small fixes in shell script * added restart driver script * fixed file permission * Added: apply max voltage if CVCM_ENABLE is False before float voltage was applied * Added: BMS disconnect behaviour * Choose to block charge/discharge on disconnect * Trigger Venus OS alarm * Changed: Remove wildcard import from dbushelper.py * Added: Show additional data in device page * show self.unique_identifier as serial number * show self.production as device name * Added: JKBMS unique identifier & fixed data length * move config.ini before update * read production date by @tranistorgit this adds the battery production date --------- Co-authored-by: Daniel Hillenbrand --- .flake8 | 22 +- .github/workflows/github-pages.yml | 7 +- .github/workflows/release.yml | 12 +- .gitignore | 6 +- CHANGELOG.md | 71 + conf/serial-starter.d | 3 - docs/docs/general/install.md | 14 +- docs/yarn.lock | 820 +++--- etc/dbus-serialbattery/README.md | 5 +- etc/dbus-serialbattery/battery.py | 509 +++- etc/dbus-serialbattery/{ => bms}/ant.py | 28 +- .../{ => bms}/battery_template.py | 41 +- etc/dbus-serialbattery/{ => bms}/daly.py | 156 +- etc/dbus-serialbattery/{ => bms}/ecs.py | 27 +- .../{ => bms}/hlpdatabms4s.py | 2 +- .../{ => bms}/hlpdatabms4s_miniterm.py | 2378 ++++++++--------- etc/dbus-serialbattery/{ => bms}/jkbms.py | 163 +- etc/dbus-serialbattery/{ => bms}/lifepower.py | 24 +- etc/dbus-serialbattery/{ => bms}/lltjbd.py | 44 +- etc/dbus-serialbattery/{ => bms}/mnb.py | 18 +- .../mnb_test_max17853.py} | 0 .../mnb_utils_max17853.py} | 0 etc/dbus-serialbattery/{ => bms}/renogy.py | 54 +- etc/dbus-serialbattery/{ => bms}/revov.py | 13 +- etc/dbus-serialbattery/{ => bms}/seplos.py | 1 - .../{ => bms}/sinowealth.py | 28 +- etc/dbus-serialbattery/config.default.ini | 224 ++ etc/dbus-serialbattery/dbus-serialbattery.py | 47 +- etc/dbus-serialbattery/dbushelper.py | 215 +- etc/dbus-serialbattery/default_config.ini | 138 - etc/dbus-serialbattery/disable.sh | 24 + etc/dbus-serialbattery/disabledriver.sh | 3 - etc/dbus-serialbattery/install-local.sh | 23 + etc/dbus-serialbattery/install-nightly.sh | 65 + etc/dbus-serialbattery/install-qml.sh | 88 + .../{installrelease.sh => install-release.sh} | 11 +- etc/dbus-serialbattery/installlocal.sh | 4 - etc/dbus-serialbattery/installqml.sh | 17 - etc/dbus-serialbattery/qml/PageBattery.qml | 43 +- .../qml/PageBatteryCellVoltages.qml | 18 +- .../qml/PageBatteryParameters.qml | 50 + .../qml/PageBatterySetup.qml | 2 +- etc/dbus-serialbattery/qml/PageLynxIonIo.qml | 74 + etc/dbus-serialbattery/reinstall-local.sh | 79 + etc/dbus-serialbattery/reinstalllocal.sh | 28 - etc/dbus-serialbattery/restart-driver.sh | 27 + etc/dbus-serialbattery/restartservice.sh | 2 - etc/dbus-serialbattery/restore-gui.sh | 27 + etc/dbus-serialbattery/restoregui.sh | 11 - etc/dbus-serialbattery/start-serialbattery.sh | 4 +- etc/dbus-serialbattery/uninstall.sh | 25 + etc/dbus-serialbattery/utils.py | 243 +- 52 files changed, 3671 insertions(+), 2267 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 conf/serial-starter.d rename etc/dbus-serialbattery/{ => bms}/ant.py (84%) rename etc/dbus-serialbattery/{ => bms}/battery_template.py (65%) rename etc/dbus-serialbattery/{ => bms}/daly.py (74%) rename etc/dbus-serialbattery/{ => bms}/ecs.py (89%) rename etc/dbus-serialbattery/{ => bms}/hlpdatabms4s.py (99%) rename etc/dbus-serialbattery/{ => bms}/hlpdatabms4s_miniterm.py (97%) rename etc/dbus-serialbattery/{ => bms}/jkbms.py (57%) rename etc/dbus-serialbattery/{ => bms}/lifepower.py (90%) rename etc/dbus-serialbattery/{ => bms}/lltjbd.py (82%) rename etc/dbus-serialbattery/{ => bms}/mnb.py (91%) rename etc/dbus-serialbattery/{test_max17853.py => bms/mnb_test_max17853.py} (100%) rename etc/dbus-serialbattery/{util_max17853.py => bms/mnb_utils_max17853.py} (100%) rename etc/dbus-serialbattery/{ => bms}/renogy.py (80%) rename etc/dbus-serialbattery/{ => bms}/revov.py (95%) rename etc/dbus-serialbattery/{ => bms}/seplos.py (99%) rename etc/dbus-serialbattery/{ => bms}/sinowealth.py (91%) create mode 100644 etc/dbus-serialbattery/config.default.ini delete mode 100644 etc/dbus-serialbattery/default_config.ini create mode 100755 etc/dbus-serialbattery/disable.sh delete mode 100755 etc/dbus-serialbattery/disabledriver.sh create mode 100755 etc/dbus-serialbattery/install-local.sh create mode 100755 etc/dbus-serialbattery/install-nightly.sh create mode 100755 etc/dbus-serialbattery/install-qml.sh rename etc/dbus-serialbattery/{installrelease.sh => install-release.sh} (54%) delete mode 100755 etc/dbus-serialbattery/installlocal.sh delete mode 100755 etc/dbus-serialbattery/installqml.sh create mode 100644 etc/dbus-serialbattery/qml/PageBatteryParameters.qml create mode 100644 etc/dbus-serialbattery/qml/PageLynxIonIo.qml create mode 100755 etc/dbus-serialbattery/reinstall-local.sh delete mode 100755 etc/dbus-serialbattery/reinstalllocal.sh create mode 100755 etc/dbus-serialbattery/restart-driver.sh delete mode 100755 etc/dbus-serialbattery/restartservice.sh create mode 100755 etc/dbus-serialbattery/restore-gui.sh delete mode 100755 etc/dbus-serialbattery/restoregui.sh create mode 100755 etc/dbus-serialbattery/uninstall.sh diff --git a/.flake8 b/.flake8 index 1b884f99..99572bc6 100644 --- a/.flake8 +++ b/.flake8 @@ -1,23 +1,15 @@ [flake8] max-line-length = 120 +per-file-ignores = + ./etc/dbus-serialbattery/utils.py: E501 exclude = - ./etc/dbus-serialbattery/ant.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/bms/battery_template.py, + ./etc/dbus-serialbattery/bms/mnb_test_max17853.py, + ./etc/dbus-serialbattery/bms/mnb_utils_max17853.py, + ./etc/dbus-serialbattery/bms/revov.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, ./velib_python venv extend-ignore: - # E203 whitespace fefore ':' conflicts with black code formatting. Will be ignored in flake8 + # E203 whitespace before ':' conflicts with black code formatting. Will be ignored in flake8 E203 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 0b30880c..ef3c00ab 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -5,7 +5,9 @@ on: # Runs on pushes targeting the default branch push: # Run on changes in the master branch - #branches: [master]: + branches: + - master + - docusaurus # Run on changes in the docs folder paths: @@ -37,6 +39,9 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + # execute only in Louisvdw repository + if: github.repository_owner == 'Louisvdw' + #defaults: # run: # working-directory: 'docs' # Here the path to the folder where package-lock.json is located. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fb9a608..672ecf61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,18 +11,20 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - name: build release archive run: | + find . -type f -name "*.py" -exec chmod +x {} \; find . -type f -name "*.sh" -exec chmod +x {} \; + find . -type f -name "run" -exec chmod +x {} \; tar -czvf venus-data.tar.gz \ --mode='a+rwX' \ - --exclude battery_template.py \ --exclude __pycache__ \ - --exclude restartservice.sh \ - --exclude revov.py \ - --exclude test_max17853.py \ - conf/serial-starter.d \ + --exclude bms/battery_template.py \ + --exclude bms/revov.py \ + --exclude bms/test_max17853.py \ etc/dbus-serialbattery/ + - name: Release uses: softprops/action-gh-release@v1 with: diff --git a/.gitignore b/.gitignore index 9066deee..59bf8fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -151,6 +151,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# VS Code +.vscode/ + # Custom for this repo venus-data.tar.gz BMS-trials @@ -158,6 +161,7 @@ BMS-trials .project .pydevproject *.prefs +etc/dbus-serialbattery/config.ini # Local Clone of velib_python -velib_python \ No newline at end of file +velib_python diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0b4b6a1f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +## v1.0.0 + +### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` gets lost. The changes in the `config.ini` will persists future updates. + +* Added: `self.unique_identifier` to the battery class. Used to identify a BMS when multiple BMS are connected - planned for future use by @mr-manuel +* Added: Alert is triggered, when BMS communication is lost by @mr-manuel +* Added: Apply max voltage, if `CVCM_ENABLE` is `False`. Before float voltage was applied by @mr-manuel +* Added: Balancing status for JKBMS by @mr-manuel +* Added: Balancing switch status for JKBMS by @mr-manuel +* Added: Balancing switch status to the GUI -> SerialBattery -> IO by @mr-manuel +* Added: Block charge/discharge when BMS communication is lost. Can be enabled trough the config file by @mr-manuel +* Added: Charge Mode display by @mr-manuel +* Added: Choose how battery temperature is assembled (mean temp 1 & 2, only temp 1 or only temp 2) by @mr-manuel +* Added: Config file by @ppuetsch +* Added: Create empty `config.ini` for easier user usage by @mr-manuel +* Added: Daly BMS - Read capacity https://github.com/Louisvdw/dbus-serialbattery/pull/594 by @transistorgit +* Added: Daly BMS - Read production date and build unique identifier by @transistorgit +* Added: Driver uninstall script by @mr-manuel +* Added: Fix for Venus OS >= v3.00~14 showing unused items https://github.com/Louisvdw/dbus-serialbattery/issues/469 by @mr-manuel +* Added: HighInternalTemperature alarm (MOSFET) for JKBMS by @mr-manuel +* Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @baphomett +* Added: JKBMS - Uniqie identifier and show "User Private Data" field that can be set in the JKBMS App to identify the BMS in a multi battery environment by @mr-manuel +* Added: Post install notes by @mr-manuel +* Added: Read charge/discharge limits from JKBMS by @mr-manuel +* Added: Recalculation interval in linear mode for CVL, CCL and DCL by @mr-manuel +* Added: Reset values to None, if battery goes offline (not reachable for 10s). Fixes https://github.com/Louisvdw/dbus-serialbattery/issues/193 https://github.com/Louisvdw/dbus-serialbattery/issues/64 by @transistorgit +* Added: Script to install directly from repository by @mr-manuel +* Added: Show charge mode (absorption, bulk, ...) in Parameters page by @mr-manuel +* Added: Show charge/discharge limitation reason by @mr-manuel +* Added: Show MOSFET temperature for JKBMS https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @baphomett +* Added: Show serial number (used for unique identifier) and device name (custom BMS field) in the remote console/GUI to identify a BMS in a multi battery environment by @mr-manuel +* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected by @mr-manuel +* Added: Show TimeToGo in GUI only, if enabled by @mr-manuel +* Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman +* Added: Support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 by @wollew +* Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable) by @mr-manuel +* Changed: `reinstall-local.sh` to recreate `/data/conf/serial-starter.d`, if deleted by `disable.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel +* Changed: Added QML to `restore-gui.sh` by @mr-manuel +* Changed: Bash output by @mr-manuel +* Changed: Default config file by @mr-manuel + * Added missing descriptions to make it much clearer to understand by @mr-manuel + * Changed name from `default_config.ini` to `config.default.ini` https://github.com/Louisvdw/dbus-serialbattery/pull/412#issuecomment-1434287942 by @mr-manuel + * Changed TimeToSoc default value `TIME_TO_SOC_VALUE_TYPE` from `Both seconds and time string " [d h m s]"` to `1 Seconds` by @mr-manuel + * Changed TimeToSoc description by @mr-manuel + * Changed value positions, added groups and much clearer descriptions by @mr-manuel +* Changed: Default FLOAT_CELL_VOLTAGE from 3.350 V to 3.375 V by @mr-manuel +* Changed: Default LINEAR_LIMITATION_ENABLE from False to True by @mr-manuel +* Changed: Disabled ANT BMS by default https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/450 by @mr-manuel +* Changed: Fixed black lint errors by @mr-manuel +* Changed: Fixed cell balancing background for cells 17-24 by @mr-manuel +* Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 by @mr-manuel +* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek +* Changed: Logging to get relevant data by @mr-manuel +* Changed: Moved BMS scripts to subfolder by @mr-manuel +* Changed: Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced by @mr-manuel +* Changed: Removed all wildcard imports and fixed black lint errors by @mr-manuel +* Changed: Renamed scripts for better reading #532 by @mr-manuel +* Changed: Reworked and optimized installation scripts by @mr-manuel +* Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel +* Changed: Serial-Starter file is now created from `reinstall-local.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520 by @mr-manuel +* Changed: Temperature alarm changed in order to not trigger all in the same condition for JKBMS by @mr-manuel +* Changed: Time-To-Soc repetition from cycles to seconds. Minimum value is every 5 seconds. This prevents CPU overload and ensures system stability. Renamed `TIME_TO_SOC_LOOP_CYCLES` to `TIME_TO_SOC_RECALCULATE_EVERY` by @mr-manuel +* Changed: Time-To-Soc string from `days, HR:MN:SC` to `d h m s` (same as Time-To-Go) by @mr-manuel diff --git a/conf/serial-starter.d b/conf/serial-starter.d deleted file mode 100644 index e25054dc..00000000 --- a/conf/serial-starter.d +++ /dev/null @@ -1,3 +0,0 @@ -service sbattery dbus-serialbattery -alias default gps:vedirect:sbattery -alias rs485 cgwacs:fzsonick:imt:modbus:sbattery diff --git a/docs/docs/general/install.md b/docs/docs/general/install.md index 28e13577..c4b8ad45 100644 --- a/docs/docs/general/install.md +++ b/docs/docs/general/install.md @@ -74,9 +74,9 @@ In [VRM](https://vrm.victronenergy.com/) look under the device list for your ins 2. Run these commands to install or update to the latest release version. ```bash - wget -O /tmp/installrelease.sh https://raw.githubusercontent.com/Louisvdw/dbus-serialbattery/master/etc/dbus-serialbattery/installrelease.sh + wget -O /tmp/install-release.sh https://raw.githubusercontent.com/Louisvdw/dbus-serialbattery/master/etc/dbus-serialbattery/install-release.sh - bash /tmp/installrelease.sh + bash /tmp/install-release.sh reboot ``` @@ -138,7 +138,7 @@ If you use the cell voltage limits, temperature limits or SoC limits you also ne ### Settings location/path -💡 After updating the settings reboot the device or run `/data/etc/dbus-serialbattery/reinstalllocal.sh` to apply the changes. +💡 After updating the settings reboot the device or run `/data/etc/dbus-serialbattery/reinstall-local.sh` to apply the changes. #### Driver version `<= v0.14.3` Edit `/data/etc/dbus-serialbattery/utils.py` to update the constants. @@ -204,7 +204,7 @@ Edit `/data/etc/dbus-serialbattery/dbus-serialbattery.py` and uncommented (witho You can disable the driver so that it will not be run by the GX device. To do that run the following command in SSH. ```bash -bash /data/etc/dbus-serialbattery/disabledriver.sh +bash /data/etc/dbus-serialbattery/disable.sh ``` You also need to configure your MPPTs to run in `Stand alone mode` again. Follow the Victron guide for [Err 67 - BMS Connection lost](https://www.victronenergy.com/live/mppt-error-codes#err_67_-_bms_connection_lost). @@ -213,7 +213,7 @@ You also need to configure your MPPTs to run in `Stand alone mode` again. Follow To enable the driver again you can run the installer. ```bash -bash /data/etc/dbus-serialbattery/reinstalllocal.sh +bash /data/etc/dbus-serialbattery/reinstall-local.sh ``` ## Uninstall/remove the driver @@ -236,11 +236,11 @@ rm -rf /opt/victronenergy/service/dbus-serialbattery rm -rf /opt/victronenergy/service-templates/dbus-serialbattery rm -rf /opt/victronenergy/dbus-serialbattery -# kill if running +# kill driver, if running pkill -f "python .*/dbus-serialbattery.py" # remove install-script from rc.local -sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstalllocal.sh/d" /data/rc.local +sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local ``` > If after the uninstall for some reason several items in the GUI were red, DO NOT reboot your GX device. See [Uninstalling driver bricked my cerbo #576](https://github.com/Louisvdw/dbus-serialbattery/issues/576) diff --git a/docs/yarn.lock b/docs/yarn.lock index 7a9d50f1..00c2befe 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2,24 +2,24 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.7.4": - version "1.7.4" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.4.tgz#85ff36b2673654a393c8c505345eaedd6eaa4f70" - integrity sha512-daoLpQ3ps/VTMRZDEBfU8ixXd+amZcNJ4QSP3IERGyzqnL5Ch8uSRFt/4G8pUvW9c3o6GA4vtVv4I4lmnkdXyg== +"@algolia/autocomplete-core@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.8.2.tgz#8d758c8652742e2761450d2b615a841fca24e10e" + integrity sha512-mTeshsyFhAqw/ebqNsQpMtbnjr+qVOSKXArEj4K0d7sqc8It1XD0gkASwecm9mF/jlOQ4Z9RNg1HbdA8JPdRwQ== dependencies: - "@algolia/autocomplete-shared" "1.7.4" + "@algolia/autocomplete-shared" "1.8.2" -"@algolia/autocomplete-preset-algolia@1.7.4": - version "1.7.4" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.4.tgz#610ee1d887962f230b987cba2fd6556478000bc3" - integrity sha512-s37hrvLEIfcmKY8VU9LsAXgm2yfmkdHT3DnA3SgHaY93yjZ2qL57wzb5QweVkYuEBZkT2PIREvRoLXC2sxTbpQ== +"@algolia/autocomplete-preset-algolia@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.8.2.tgz#706e87f94c5f198c0e90502b97af09adeeddcc79" + integrity sha512-J0oTx4me6ZM9kIKPuL3lyU3aB8DEvpVvR6xWmHVROx5rOYJGQcZsdG4ozxwcOyiiu3qxMkIbzntnV1S1VWD8yA== dependencies: - "@algolia/autocomplete-shared" "1.7.4" + "@algolia/autocomplete-shared" "1.8.2" -"@algolia/autocomplete-shared@1.7.4": - version "1.7.4" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.4.tgz#78aea1140a50c4d193e1f06a13b7f12c5e2cbeea" - integrity sha512-2VGCk7I9tA9Ge73Km99+Qg87w0wzW4tgUruvWAn/gfey1ZXgmxZtyIRBebk35R1O8TbK77wujVtCnpsGpRy1kg== +"@algolia/autocomplete-shared@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.8.2.tgz#e6972df5c6935a241f16e4909aa82902338e029d" + integrity sha512-b6Z/X4MczChMcfhk6kfRmBzPgjoPzuS9KGR4AFsiLulLNRAAqhP+xZTKtMnZGhLuc61I20d5WqlId02AZvcO6g== "@algolia/cache-browser-local-storage@4.17.0": version "4.17.0" @@ -145,10 +145,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" - integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": + version "7.21.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" + integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== "@babel/core@7.12.9": version "7.12.9" @@ -173,32 +173,32 @@ source-map "^0.5.0" "@babel/core@^7.18.6", "@babel/core@^7.19.6": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" - integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" + integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helpers" "^7.21.5" + "@babel/parser" "^7.21.8" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.4" - "@babel/types" "^7.21.4" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" - integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== +"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" + integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== dependencies: - "@babel/types" "^7.21.4" + "@babel/types" "^7.21.5" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -211,45 +211,46 @@ "@babel/types" "^7.18.6" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb" + integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" - integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== dependencies: - "@babel/compat-data" "^7.21.4" + "@babel/compat-data" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18" - integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02" + integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-replace-supers" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" + semver "^6.3.0" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5" - integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc" + integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.3.1" + semver "^6.3.0" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -263,17 +264,10 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" + integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" @@ -290,12 +284,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" - integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== +"@babel/helper-member-expression-to-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0" + integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.21.5" "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" @@ -304,19 +298,19 @@ dependencies: "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" + integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-simple-access" "^7.21.5" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -330,10 +324,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -345,24 +339,24 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" - integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" + integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== +"@babel/helper-simple-access@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" + integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" @@ -378,10 +372,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" + integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" @@ -403,14 +397,14 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" + integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/highlight@^7.18.6": version "7.18.6" @@ -421,10 +415,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" - integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== +"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -622,6 +616,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -636,7 +637,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.21.4": +"@babel/plugin-syntax-jsx@^7.21.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== @@ -706,12 +707,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-arrow-functions@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== +"@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-async-to-generator@^7.20.7": version "7.20.7" @@ -751,12 +752,12 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== +"@babel/plugin-transform-computed-properties@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" + integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" "@babel/plugin-transform-destructuring@^7.21.3": @@ -789,12 +790,12 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" - integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== +"@babel/plugin-transform-for-of@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" + integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -827,14 +828,14 @@ "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-commonjs@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7" - integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== +"@babel/plugin-transform-modules-commonjs@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" + integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== dependencies: - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-simple-access" "^7.21.5" "@babel/plugin-transform-modules-systemjs@^7.20.11": version "7.20.11" @@ -913,15 +914,15 @@ "@babel/plugin-transform-react-jsx" "^7.18.6" "@babel/plugin-transform-react-jsx@^7.18.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz#656b42c2fdea0a6d8762075d58ef9d4e3c4ab8a2" - integrity sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.5.tgz#bd98f3b429688243e4fa131fe1cbb2ef31ce6f38" + integrity sha512-ELdlq61FpoEkHO6gFRpfj0kUgSwQTGoaEU8eMRoS8Dv3v6e7BjEAj5WMtIBRdHUeAioMhKP5HyxNzNnP+heKbA== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.21.0" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-jsx" "^7.21.4" + "@babel/types" "^7.21.5" "@babel/plugin-transform-react-pure-annotations@^7.18.6": version "7.18.6" @@ -931,12 +932,12 @@ "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== +"@babel/plugin-transform-regenerator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" + integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": @@ -1004,12 +1005,12 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-unicode-escapes@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" + integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -1020,13 +1021,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.4.tgz#a952482e634a8dd8271a3fe5459a16eb10739c58" - integrity sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" + integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== dependencies: - "@babel/compat-data" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/compat-data" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" @@ -1051,6 +1052,7 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -1060,22 +1062,22 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.20.7" + "@babel/plugin-transform-arrow-functions" "^7.21.5" "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.21.0" "@babel/plugin-transform-classes" "^7.21.0" - "@babel/plugin-transform-computed-properties" "^7.20.7" + "@babel/plugin-transform-computed-properties" "^7.21.5" "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.21.0" + "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.20.11" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-modules-systemjs" "^7.20.11" "@babel/plugin-transform-modules-umd" "^7.18.6" "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" @@ -1083,17 +1085,17 @@ "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.21.3" "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.20.5" + "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" "@babel/plugin-transform-spread" "^7.20.7" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-escapes" "^7.21.5" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.21.4" + "@babel/types" "^7.21.5" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1124,14 +1126,14 @@ "@babel/plugin-transform-react-pure-annotations" "^7.18.6" "@babel/preset-typescript@^7.18.6": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz#b913ac8e6aa8932e47c21b01b4368d8aa239a529" - integrity sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" + integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-syntax-jsx" "^7.21.4" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-typescript" "^7.21.3" "@babel/regjsgen@^0.8.0": @@ -1140,17 +1142,17 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs3@^7.18.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.21.0.tgz#6e4939d9d9789ff63e2dc58e88f13a3913a24eba" - integrity sha512-TDD4UJzos3JJtM+tHX+w2Uc+KWj7GV+VKKFdMVd2Rx8sdA19hcc3P3AHFYd5LVOw+pYuSd5lICC3gm52B6Rwxw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.21.5.tgz#a6d4e132ab1cb2fae2354f02284ebb6e07b4f7d8" + integrity sha512-FRqFlFKNazWYykft5zvzuEl1YyTDGsIRrjV9rvxvYkUC7W/ueBng1X68Xd6uRMzAaJ0xMKn08/wem5YS1lpX8w== dependencies: core-js-pure "^3.25.1" regenerator-runtime "^0.13.11" "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.8.4": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== dependencies: regenerator-runtime "^0.13.11" @@ -1163,28 +1165,28 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" - integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== +"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" + integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== dependencies: "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/generator" "^7.21.5" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.4" - "@babel/types" "^7.21.4" + "@babel/parser" "^7.21.5" + "@babel/types" "^7.21.5" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.4.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" - integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== +"@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.4.4": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== dependencies: - "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-string-parser" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -1198,19 +1200,19 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@3.3.3": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.3.3.tgz#f9346c9e24602218341f51b8ba91eb9109add434" - integrity sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg== +"@docsearch/css@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.3.4.tgz#533719eac0aa3934318074e7e981e633727ad2fd" + integrity sha512-vDwCDoVXDgopw/hvr0zEADew2wWaGP8Qq0Bxhgii1Ewz2t4fQeyJwIRN/mWADeLFYPVkpz8TpEbxya/i6Tm0WA== "@docsearch/react@^3.1.1": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.3.3.tgz#907b6936a565f880b4c0892624b4f7a9f132d298" - integrity sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q== + version "3.3.4" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.3.4.tgz#d49cf9e5d939145c9fe688113c5bdf41975d8ae7" + integrity sha512-aeOf1WC5zMzBEi2SI6WWznOmIo9rnpN4p7a3zHXxowVciqlI4HsZGtOR9nFOufLeolv7HibwLlaM0oyUqJxasw== dependencies: - "@algolia/autocomplete-core" "1.7.4" - "@algolia/autocomplete-preset-algolia" "1.7.4" - "@docsearch/css" "3.3.3" + "@algolia/autocomplete-core" "1.8.2" + "@algolia/autocomplete-preset-algolia" "1.8.2" + "@docsearch/css" "3.3.4" algoliasearch "^4.0.0" "@docusaurus/core@2.4.0": @@ -1941,9 +1943,9 @@ "@types/node" "*" "@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#9fd20b3974bdc2bcd4ac6567e2e0f6885cb2cf41" + integrity sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig== dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" @@ -1972,18 +1974,19 @@ "@types/json-schema" "*" "@types/estree@*", "@types/estree@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.33" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" - integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + version "4.17.34" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz#c119e85b75215178bc127de588e93100698ab4cc" + integrity sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" + "@types/send" "*" "@types/express@*", "@types/express@^4.17.13": version "4.17.17" @@ -2013,9 +2016,9 @@ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== "@types/http-proxy@^1.17.8": - version "1.17.10" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.10.tgz#e576c8e4a0cc5c6a138819025a88e167ebb38d6c" - integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g== + version "1.17.11" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" + integrity sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA== dependencies: "@types/node" "*" @@ -2055,10 +2058,15 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/node@*": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "18.16.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" + integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== "@types/node@^17.0.5": version "17.0.45" @@ -2117,9 +2125,9 @@ "@types/react" "*" "@types/react@*": - version "18.0.35" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.35.tgz#192061cb1044fe01f2d3a94272cd35dd50502741" - integrity sha512-6Laome31HpetaIUGFWl1VQ3mdSImwxtFZ39rh059a1MNnKGqBpC88J6NJ8n/Is3Qx7CefDGLgf/KhN/sYCf7ag== + version "18.2.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" + integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2142,6 +2150,14 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" @@ -2188,125 +2204,125 @@ dependencies: "@types/yargs-parser" "*" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" + integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4" + integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1" + integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba" + integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9" + integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f" + integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e" + integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60" + integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a" + integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a" + integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d" + integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/helper-wasm-section" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-opt" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + "@webassemblyjs/wast-printer" "1.11.5" + +"@webassemblyjs/wasm-gen@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1" + integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wasm-opt@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a" + integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + +"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2" + integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wast-printer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98" + integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA== + dependencies: + "@webassemblyjs/ast" "1.11.5" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": @@ -2367,7 +2383,7 @@ ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv-keywords@^5.0.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== @@ -2384,7 +2400,7 @@ ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.9.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -2773,9 +2789,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: - version "1.0.30001478" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz#0ef8a1cf8b16be47a0f9fc4ecfc952232724b32a" - integrity sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw== + version "1.0.30001482" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz#8b3fad73dc35b2674a5c96df2d4f9f1c561435de" + integrity sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ== ccount@^1.0.0: version "1.1.0" @@ -2956,9 +2972,9 @@ colord@^2.9.1: integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== combine-promises@^1.1.0: version "1.1.0" @@ -3113,11 +3129,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig-typescript-loader@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz#c4259ce474c9df0f32274ed162c0447c951ef073" - integrity sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q== - cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" @@ -3441,9 +3452,9 @@ dns-equal@^1.0.0: integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== dns-packet@^5.2.2: - version "5.5.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.5.0.tgz#f59cbf3396c130957c56a6ad5fd3959ccdc30065" - integrity sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA== + version "5.6.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.0.tgz#2202c947845c7a63c23ece58f2f70ff6ab4c2f7d" + integrity sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" @@ -3484,7 +3495,7 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" -domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: +domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== @@ -3501,13 +3512,13 @@ domutils@^2.5.2, domutils@^2.8.0: domhandler "^4.2.0" domutils@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" - integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== dependencies: dom-serializer "^2.0.0" domelementtype "^2.3.0" - domhandler "^5.0.1" + domhandler "^5.0.3" dot-case@^3.0.4: version "3.0.4" @@ -3545,9 +3556,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.284: - version "1.4.363" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.363.tgz#a3d51d16169d8f212f00bafb78cf0667e24c3647" - integrity sha512-ReX5qgmSU7ybhzMuMdlJAdYnRhT90UB3k9M05O5nF5WH3wR5wgdJjXw0uDeFyKNhmglmQiOxkAbzrP0hMKM59g== + version "1.4.380" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.380.tgz#195dc59d930c6b74efbee6f0e6a267ce4af5ed91" + integrity sha512-XKGdI4pWM78eLH2cbXJHiBnWUwFSzZM7XujsB6stDiGu9AeSqziedP6amNLpJzE3i0rLTcfAwdCTs5ecP5yeSg== emoji-regex@^8.0.0: version "8.0.0" @@ -3581,10 +3592,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.13.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" + integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4322,9 +4333,9 @@ html-void-elements@^1.0.0: integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== html-webpack-plugin@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + version "5.5.1" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz#826838e31b427f5f7f30971f8d8fa2422dfa6763" + integrity sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -4754,10 +4765,15 @@ jest-worker@^29.1.2: merge-stream "^2.0.0" supports-color "^8.0.0" +jiti@^1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd" + integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg== + joi@^17.6.0: - version "17.9.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.1.tgz#74899b9fa3646904afa984a11df648eca66c9018" - integrity sha512-FariIi9j6QODKATGBrEX7HZcja8Bsh3rfdGYy/Sb65sGlZWK/QWesU1ghk7aJWDj95knjXlQfSmzFSPPkLVsfw== + version "17.9.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" + integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -5051,9 +5067,9 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^3.1.2, memfs@^3.4.3: - version "3.5.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.0.tgz#9da86405fca0a539addafd37dbd452344fd1c0bd" - integrity sha512-yK6o8xVJlQerz57kvPROwTMgx5WtGwC2ZxDtOUsnGl49rHjYkfQoPNZPCKH73VdLE1BwBu/+Fx/NL8NYMUw2aA== + version "3.5.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.1.tgz#f0cd1e2bfaef58f6fe09bfb9c2288f07fea099ec" + integrity sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA== dependencies: fs-monkey "^1.0.3" @@ -5176,7 +5192,7 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" -nanoid@^3.3.4: +nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== @@ -5607,12 +5623,12 @@ postcss-discard-unused@^5.1.0: postcss-selector-parser "^6.0.5" postcss-loader@^7.0.0: - version "7.2.4" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.2.4.tgz#2884f4ca172de633b2cf1f93dc852968f0632ba9" - integrity sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w== + version "7.3.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.0.tgz#05991c1e490d8ff86ef18358d87db3b5b2dcb5f5" + integrity sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw== dependencies: cosmiconfig "^8.1.3" - cosmiconfig-typescript-loader "^4.3.0" + jiti "^1.18.2" klona "^2.0.6" semver "^7.3.8" @@ -5796,17 +5812,17 @@ postcss-reduce-transforms@^5.1.0: postcss-value-parser "^4.2.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + version "6.0.12" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz#2efae5ffab3c8bfb2b7fbf0c426e3bca616c4abb" + integrity sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" postcss-sort-media-queries@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.3.0.tgz#f48a77d6ce379e86676fc3f140cf1b10a06f6051" - integrity sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg== + version "4.4.1" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz#04a5a78db3921eb78f28a1a781a2e68e65258128" + integrity sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw== dependencies: sort-css-media-queries "2.1.0" @@ -5836,11 +5852,11 @@ postcss-zindex@^5.1.0: integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + version "8.4.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab" + integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -6415,9 +6431,9 @@ run-parallel@^1.1.9: queue-microtask "^1.2.2" rxjs@^7.5.4: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" @@ -6467,24 +6483,24 @@ schema-utils@^2.6.5: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" + integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.1.tgz#eb2d042df8b01f4b5c276a2dfd41ba0faab72e8d" + integrity sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ== dependencies: "@types/json-schema" "^7.0.9" - ajv "^8.8.0" + ajv "^8.9.0" ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" + ajv-keywords "^5.1.0" section-matter@^1.0.0: version "1.0.0" @@ -6524,9 +6540,9 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8: - version "7.4.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.4.0.tgz#8481c92feffc531ab1e012a8ffc15bdd3a0f4318" - integrity sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw== + version "7.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" @@ -6945,9 +6961,9 @@ terser-webpack-plugin@^5.3.3, terser-webpack-plugin@^5.3.7: terser "^5.16.5" terser@^5.10.0, terser@^5.16.5: - version "5.16.9" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" - integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== + version "5.17.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69" + integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -7192,9 +7208,9 @@ unpipe@1.0.0, unpipe@~1.0.0: integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -7386,9 +7402,9 @@ webpack-dev-middleware@^5.3.1: schema-utils "^4.0.0" webpack-dev-server@^4.9.3: - version "4.13.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz#d97445481d78691efe6d9a3b230833d802fc31f9" - integrity sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw== + version "4.13.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.3.tgz#9feb740b8b56b886260bae1360286818a221bae8" + integrity sha512-KqqzrzMRSRy5ePz10VhjyL27K2dxqwXQLP5rAKwRJBPUahe7Z2bBWzHw37jeb8GCPKxZRO79ZdQUAPesMh/Nug== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" @@ -7435,20 +7451,20 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.73.0: - version "5.79.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.79.0.tgz#8552b5da5a26e4e25842c08a883e08fc7740547a" - integrity sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg== + version "5.81.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.81.0.tgz#27a2e8466c8b4820d800a8d90f06ef98294f9956" + integrity sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" + enhanced-resolve "^5.13.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -7458,7 +7474,7 @@ webpack@^5.73.0: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.1.2" tapable "^2.1.1" terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" @@ -7525,9 +7541,9 @@ widest-line@^4.0.1: string-width "^5.0.1" wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== wrap-ansi@^7.0.0: version "7.0.0" diff --git a/etc/dbus-serialbattery/README.md b/etc/dbus-serialbattery/README.md index 127c1df8..de6c727e 100644 --- a/etc/dbus-serialbattery/README.md +++ b/etc/dbus-serialbattery/README.md @@ -1,2 +1,5 @@ # dbus-serialbattery -See [README on GitHub](https://github.com/Louisvdw/dbus-serialbattery/blob/master/README.md) \ No newline at end of file + +* See [README](https://github.com/Louisvdw/dbus-serialbattery/blob/master/README.md) on GitHub + +* See [Documentation](https://louisvdw.github.io/dbus-serialbattery/) on GitHub Pages diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 15021526..c45eecce 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -5,7 +5,6 @@ import utils import logging import math -from datetime import timedelta from time import time from abc import ABC, abstractmethod @@ -15,9 +14,10 @@ class Protection(object): This class holds Warning and alarm states for different types of Checks They are of type integer, 2 represents an Alarm, 1 a Warning, 0 if everything is fine """ + ALARM = 2 WARNING = 1 - NOALARM = 0 + OK = 0 def __init__(self): self.voltage_high: int = None @@ -32,6 +32,7 @@ def __init__(self): self.temp_low_charge: int = None self.temp_high_discharge: int = None self.temp_low_discharge: int = None + self.temp_high_internal: int = None class Cell: @@ -61,8 +62,18 @@ def __init__(self, port, baud, address): self.type = "Generic" self.poll_interval = 1000 self.online = True - self.hardware_version = None + self.cell_count = None + # max battery charge/discharge current + self.max_battery_charge_current = None + self.max_battery_discharge_current = None + + self.init_values() + + # used to identify a BMS when multiple BMS are connected - planned for future use + self.unique_identifier = None + + def init_values(self): self.voltage = None self.current = None self.capacity_remain = None @@ -73,9 +84,10 @@ def __init__(self, port, baud, address): self.protection = Protection() self.version = None self.soc = None + self.time_to_soc_update = 0 self.charge_fet = None self.discharge_fet = None - self.cell_count = None + self.balance_fet = None self.temp_sensors = None self.temp1 = None self.temp2 = None @@ -84,6 +96,12 @@ def __init__(self, port, baud, address): self.control_charging = None self.control_voltage = None self.allow_max_voltage = True + self.charge_mode = None + self.charge_limitation = None + self.discharge_limitation = None + self.linear_cvl_last_set = 0 + self.linear_ccl_last_set = 0 + self.linear_dcl_last_set = 0 self.max_voltage_start_time = None self.control_current = None self.control_previous_total = None @@ -92,11 +110,6 @@ def __init__(self, port, baud, address): self.control_charge_current = None self.control_allow_charge = None self.control_allow_discharge = None - # max battery charge/discharge current - self.max_battery_charge_current = None - self.max_battery_discharge_current = None - - self.time_to_soc_update = utils.TIME_TO_SOC_LOOP_CYCLES @abstractmethod def test_connection(self) -> bool: @@ -140,22 +153,27 @@ def to_temp(self, sensor: int, value: float) -> None: :param value: the sensor value :return: """ + if sensor == 0: + self.temp_mos = min(max(value, -20), 100) if sensor == 1: self.temp1 = min(max(value, -20), 100) if sensor == 2: self.temp2 = min(max(value, -20), 100) - if sensor == "mos": - self.temp_mos = min(max(value, -20), 100) def manage_charge_voltage(self) -> None: """ manages the charge voltage by setting self.control_voltage :return: None """ - if utils.LINEAR_LIMITATION_ENABLE: - self.manage_charge_voltage_linear() + if utils.CVCM_ENABLE: + if utils.LINEAR_LIMITATION_ENABLE: + self.manage_charge_voltage_linear() + else: + self.manage_charge_voltage_step() + # on CVCM_ENABLE = False apply max voltage else: - self.manage_charge_voltage_step() + self.control_voltage = round((utils.MAX_CELL_VOLTAGE * self.cell_count), 3) + self.charge_mode = "Keep always max voltage" def manage_charge_voltage_linear(self) -> None: """ @@ -163,31 +181,109 @@ def manage_charge_voltage_linear(self) -> None: :return: None """ foundHighCellVoltage = False - if utils.CVCM_ENABLE: - currentBatteryVoltage = 0 - penaltySum = 0 - for i in range(self.cell_count): - cv = self.get_cell_voltage(i) - if cv: - currentBatteryVoltage += cv - - if cv >= utils.PENALTY_AT_CELL_VOLTAGE[0]: - foundHighCellVoltage = True - penaltySum += utils.calcLinearRelationship( - cv, - utils.PENALTY_AT_CELL_VOLTAGE, - utils.PENALTY_BATTERY_VOLTAGE, - ) - self.voltage = currentBatteryVoltage # for testing - - if foundHighCellVoltage: - # Keep penalty above min battery voltage - self.control_voltage = max( - currentBatteryVoltage - penaltySum, - utils.MIN_CELL_VOLTAGE * self.cell_count, - ) - else: - self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count + voltageSum = 0 + penaltySum = 0 + tDiff = 0 + + try: + if utils.CVCM_ENABLE: + # calculate battery sum + for i in range(self.cell_count): + voltage = self.get_cell_voltage(i) + if voltage: + voltageSum += voltage + + # calculate penalty sum to prevent single cell overcharge by using current cell voltage + if voltage > utils.MAX_CELL_VOLTAGE: + # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second + foundHighCellVoltage = True + penaltySum += voltage - utils.MAX_CELL_VOLTAGE - 0.010 + + voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() + + if self.max_voltage_start_time is None: + if ( + utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum + and voltageDiff + <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL + and self.allow_max_voltage + ): + self.max_voltage_start_time = time() + elif ( + # utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc + voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT + and not self.allow_max_voltage + ): + self.allow_max_voltage = True + else: + tDiff = time() - self.max_voltage_start_time + # if utils.MAX_VOLTAGE_TIME_SEC < tDiff: + # keep max voltage for 300 more seconds + if 300 < tDiff: + self.allow_max_voltage = False + self.max_voltage_start_time = None + + # INFO: battery will only switch to Absorption, if all cells are balanced. + # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. + if foundHighCellVoltage and self.allow_max_voltage: + # set CVL only once every LINEAR_RECALCULATION_EVERY seconds + if ( + int(time()) - self.linear_cvl_last_set + >= utils.LINEAR_RECALCULATION_EVERY + ): + self.linear_cvl_last_set = int(time()) + + # Keep penalty above min battery voltage + self.control_voltage = round( + max( + voltageSum - penaltySum, + utils.MIN_CELL_VOLTAGE * self.cell_count, + ), + 3, + ) + + self.charge_mode = ( + "Bulk dynamic" + # + " (vS: " + # + str(round(voltageSum, 2)) + # + " - pS: " + # + str(round(penaltySum, 2)) + # + ")" + if self.max_voltage_start_time is None + else "Absorption dynamic" + # + "(vS: " + # + str(round(voltageSum, 2)) + # + " - pS: " + # + str(round(penaltySum, 2)) + # + ")" + ) + + elif self.allow_max_voltage: + self.control_voltage = round( + (utils.MAX_CELL_VOLTAGE * self.cell_count), 3 + ) + self.charge_mode = ( + "Bulk" if self.max_voltage_start_time is None else "Absorption" + ) + + else: + self.control_voltage = round( + (utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3 + ) + self.charge_mode = "Float" + + if ( + self.allow_max_voltage + and self.get_balancing() + and voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT + ): + self.charge_mode += " + Balancing" + + self.charge_mode += " (Linear Mode)" + + except TypeError: + self.control_voltage = None + self.charge_mode = "--" def manage_charge_voltage_step(self) -> None: """ @@ -195,70 +291,186 @@ def manage_charge_voltage_step(self) -> None: :return: None """ voltageSum = 0 - if utils.CVCM_ENABLE: - for i in range(self.cell_count): - voltage = self.get_cell_voltage(i) - if voltage: - voltageSum += voltage + tDiff = 0 - if self.max_voltage_start_time is None: - if ( - utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum - and self.allow_max_voltage - ): - self.max_voltage_start_time = time() - else: + try: + if utils.CVCM_ENABLE: + # calculate battery sum + for i in range(self.cell_count): + voltage = self.get_cell_voltage(i) + if voltage: + voltageSum += voltage + + if self.max_voltage_start_time is None: + # check if max voltage is reached and start timer to keep max voltage if ( + utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum + and self.allow_max_voltage + ): + # example 2 + self.max_voltage_start_time = time() + + # check if reset soc is greater than battery soc + # this prevents flapping between max and float voltage + elif ( utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc and not self.allow_max_voltage ): self.allow_max_voltage = True + + # do nothing + else: + pass + + # timer started + else: + tDiff = time() - self.max_voltage_start_time + if utils.MAX_VOLTAGE_TIME_SEC < tDiff: + self.allow_max_voltage = False + self.max_voltage_start_time = None + + else: + pass + + if self.allow_max_voltage: + self.control_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.charge_mode = ( + "Bulk" if self.max_voltage_start_time is None else "Absorption" + ) + else: - tDiff = time() - self.max_voltage_start_time - if utils.MAX_VOLTAGE_TIME_SEC < tDiff: - self.max_voltage_start_time = None - self.allow_max_voltage = False - - if self.allow_max_voltage: - # Keep penalty above min battery voltage - self.control_voltage = max( - utils.MAX_CELL_VOLTAGE * self.cell_count, - utils.MIN_CELL_VOLTAGE * self.cell_count, - ) - else: - self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count + self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count + self.charge_mode = "Float" + + self.charge_mode += " (Step Mode)" + + except TypeError: + self.control_voltage = None + self.charge_mode = "--" def manage_charge_current(self) -> None: # Manage Charge Current Limitations - charge_limits = [self.max_battery_charge_current] - if utils.CCCM_SOC_ENABLE: - charge_limits.append(self.calcMaxChargeCurrentReferringToSoc()) + charge_limits = {utils.MAX_BATTERY_CHARGE_CURRENT: "Config Limit"} + + # if values are not the same, then the limit was read also from the BMS + if utils.MAX_BATTERY_CHARGE_CURRENT != self.max_battery_charge_current: + charge_limits.update({self.max_battery_charge_current: "BMS Limit"}) + if utils.CCCM_CV_ENABLE: - charge_limits.append(self.calcMaxChargeCurrentReferringToCellVoltage()) + tmp = self.calcMaxChargeCurrentReferringToCellVoltage() + if self.max_battery_charge_current != tmp: + if tmp in charge_limits: + charge_limits.update({tmp: charge_limits[tmp] + ", Cell Voltage"}) + else: + charge_limits.update({tmp: "Cell Voltage"}) + if utils.CCCM_T_ENABLE: - charge_limits.append(self.calcMaxChargeCurrentReferringToTemperature()) + tmp = self.calcMaxChargeCurrentReferringToTemperature() + if self.max_battery_charge_current != tmp: + if tmp in charge_limits: + charge_limits.update({tmp: charge_limits[tmp] + ", Temp"}) + else: + charge_limits.update({tmp: "Temp"}) + + if utils.CCCM_SOC_ENABLE: + tmp = self.calcMaxChargeCurrentReferringToSoc() + if self.max_battery_charge_current != tmp: + if tmp in charge_limits: + charge_limits.update({tmp: charge_limits[tmp] + ", SoC"}) + else: + charge_limits.update({tmp: "SoC"}) + + # do not set CCL immediately, but only + # - after LINEAR_RECALCULATION_EVERY passed + # - if CCL changes to 0 + # - if CCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE + ccl = round(min(charge_limits), 3) # gets changed after finished testing + diff = ( + abs(self.control_charge_current - ccl) + if self.control_charge_current is not None + else 0 + ) + if ( + int(time()) - self.linear_ccl_last_set >= utils.LINEAR_RECALCULATION_EVERY + or ccl == 0 + or ( + diff + >= self.control_charge_current + * utils.LINEAR_RECALCULATION_ON_PERC_CHANGE + / 100 + ) + ): + self.linear_ccl_last_set = int(time()) - self.control_charge_current = min(charge_limits) + self.control_charge_current = ccl + + self.charge_limitation = charge_limits[min(charge_limits)] if self.control_charge_current == 0: self.control_allow_charge = False else: self.control_allow_charge = True + ##### + # Manage Discharge Current Limitations - discharge_limits = [self.max_battery_discharge_current] - if utils.DCCM_SOC_ENABLE: - discharge_limits.append(self.calcMaxDischargeCurrentReferringToSoc()) + discharge_limits = {utils.MAX_BATTERY_DISCHARGE_CURRENT: "Config Limit"} + + # if values are not the same, then the limit was read also from the BMS + if utils.MAX_BATTERY_DISCHARGE_CURRENT != self.max_battery_discharge_current: + discharge_limits.update({self.max_battery_discharge_current: "BMS Limit"}) + if utils.DCCM_CV_ENABLE: - discharge_limits.append( - self.calcMaxDischargeCurrentReferringToCellVoltage() - ) + tmp = self.calcMaxDischargeCurrentReferringToCellVoltage() + if self.max_battery_discharge_current != tmp: + if tmp in discharge_limits: + discharge_limits.update( + {tmp: discharge_limits[tmp] + ", Cell Voltage"} + ) + else: + discharge_limits.update({tmp: "Cell Voltage"}) + if utils.DCCM_T_ENABLE: - discharge_limits.append( - self.calcMaxDischargeCurrentReferringToTemperature() + tmp = self.calcMaxDischargeCurrentReferringToTemperature() + if self.max_battery_discharge_current != tmp: + if tmp in discharge_limits: + discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"}) + else: + discharge_limits.update({tmp: "Temp"}) + + if utils.DCCM_SOC_ENABLE: + tmp = self.calcMaxDischargeCurrentReferringToSoc() + if self.max_battery_discharge_current != tmp: + if tmp in discharge_limits: + discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"}) + else: + discharge_limits.update({tmp: "SoC"}) + + # do not set DCL immediately, but only + # - after LINEAR_RECALCULATION_EVERY passed + # - if DCL changes to 0 + # - if DCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE + dcl = round(min(discharge_limits), 3) # gets changed after finished testing + diff = ( + abs(self.control_discharge_current - dcl) + if self.control_discharge_current is not None + else 0 + ) + if ( + int(time()) - self.linear_dcl_last_set >= utils.LINEAR_RECALCULATION_EVERY + or dcl == 0 + or ( + diff + >= self.control_discharge_current + * utils.LINEAR_RECALCULATION_ON_PERC_CHANGE + / 100 ) + ): + self.linear_dcl_last_set = int(time()) + + self.control_discharge_current = dcl - self.control_discharge_current = min(discharge_limits) + self.discharge_limitation = discharge_limits[min(discharge_limits)] if self.control_discharge_current == 0: self.control_allow_discharge = False @@ -451,7 +663,7 @@ def get_capacity_remain(self) -> Union[float, None]: return self.capacity * self.soc / 100 return None - def get_timetosoc(self, socnum, crntPrctPerSec) -> str: + def get_timeToSoc(self, socnum, crntPrctPerSec, onlyNumber=False) -> str: if self.current > 0: diffSoc = socnum - self.soc else: @@ -462,17 +674,47 @@ def get_timetosoc(self, socnum, crntPrctPerSec) -> str: secondstogo = int(diffSoc / crntPrctPerSec) ttgStr = "" - if utils.TIME_TO_SOC_VALUE_TYPE & 1: + if onlyNumber or utils.TIME_TO_SOC_VALUE_TYPE & 1: ttgStr += str(secondstogo) - if utils.TIME_TO_SOC_VALUE_TYPE & 2: + if not onlyNumber and utils.TIME_TO_SOC_VALUE_TYPE & 2: ttgStr += " [" - if utils.TIME_TO_SOC_VALUE_TYPE & 2: - ttgStr += str(timedelta(seconds=secondstogo)) + if not onlyNumber and utils.TIME_TO_SOC_VALUE_TYPE & 2: + ttgStr += self.get_secondsToString(secondstogo) + if utils.TIME_TO_SOC_VALUE_TYPE & 1: ttgStr += "]" return ttgStr + def get_secondsToString(self, timespan, precision=3) -> str: + """ + Transforms seconds to a string in the format: 1d 1h 1m 1s (Victron Style) + :param precision: + 0 = 1d + 1 = 1d 1h + 2 = 1d 1h 1m + 3 = 1d 1h 1m 1s + + This was added, since timedelta() returns strange values, if time is negative + e.g.: seconds: -70245 + --> timedelta output: -1 day, 4:29:15 + --> calculation: -1 day + 4:29:15 + --> real value -19:30:45 + """ + tmp = "" if timespan >= 0 else "-" + timespan = abs(timespan) + + m, s = divmod(timespan, 60) + h, m = divmod(m, 60) + d, h = divmod(h, 24) + + tmp += (str(d) + "d ") if d > 0 else "" + tmp += (str(h) + "h ") if precision >= 1 and h > 0 else "" + tmp += (str(m) + "m ") if precision >= 2 and m > 0 else "" + tmp += (str(s) + "s ") if precision == 3 and s > 0 else "" + + return tmp.rstrip() + def get_min_cell_voltage(self) -> Union[float, None]: min_voltage = None if hasattr(self, "cell_min_voltage"): @@ -541,8 +783,10 @@ def get_midvoltage(self) -> Tuple[Union[float, None], Union[float, None]]: # get the midpoint of the battery midpoint = half1voltage + extra return ( - midpoint, - (half2voltage - half1voltage) / (half2voltage + half1voltage) * 100, + abs(midpoint), + abs( + (half2voltage - half1voltage) / (half2voltage + half1voltage) * 100 + ), ) except ValueError: return None, None @@ -564,19 +808,53 @@ def extract_from_temp_values(self, extractor) -> Union[float, None]: return None def get_temp(self) -> Union[float, None]: - return self.extract_from_temp_values( - extractor=lambda temp1, temp2: round((float(temp1) + float(temp2)) / 2, 2) - ) + try: + if utils.TEMP_BATTERY == 1: + return self.temp1 + elif utils.TEMP_BATTERY == 2: + return self.temp2 + else: + return self.extract_from_temp_values( + extractor=lambda temp1, temp2: round( + (float(temp1) + float(temp2)) / 2, 2 + ) + ) + except TypeError: + return None def get_min_temp(self) -> Union[float, None]: - return self.extract_from_temp_values( - extractor=lambda temp1, temp2: min(temp1, temp2) - ) + try: + return self.extract_from_temp_values( + extractor=lambda temp1, temp2: min(temp1, temp2) + ) + except TypeError: + return None + + def get_min_temp_id(self) -> Union[str, None]: + try: + if self.temp1 < self.temp2: + return utils.TEMP_1_NAME + else: + return utils.TEMP_2_NAME + except TypeError: + return None def get_max_temp(self) -> Union[float, None]: - return self.extract_from_temp_values( - extractor=lambda temp1, temp2: max(temp1, temp2) - ) + try: + return self.extract_from_temp_values( + extractor=lambda temp1, temp2: max(temp1, temp2) + ) + except TypeError: + return None + + def get_max_temp_id(self) -> Union[str, None]: + try: + if self.temp1 > self.temp2: + return utils.TEMP_1_NAME + else: + return utils.TEMP_2_NAME + except TypeError: + return None def get_mos_temp(self) -> Union[float, None]: if self.temp_mos is not None: @@ -597,22 +875,47 @@ def log_cell_data(self) -> bool: return True def log_settings(self) -> None: - logger.info(f"Battery {self.type} connected to dbus from {self.port}") - logger.info("=== Settings ===") cell_counter = len(self.cells) + logger.info(f"Battery {self.type} connected to dbus from {self.port}") + logger.info("========== Settings ==========") + 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"> LINEAR LIMITATION ENABLE: {utils.LINEAR_LIMITATION_ENABLE}") + logger.info( + f"> MAX BATTERY CHARGE CURRENT: {utils.MAX_BATTERY_CHARGE_CURRENT}A | " + + f"MAX BATTERY DISCHARGE CURRENT: {utils.MAX_BATTERY_DISCHARGE_CURRENT}A" + ) + if ( + ( + utils.MAX_BATTERY_CHARGE_CURRENT != self.max_battery_charge_current + or utils.MAX_BATTERY_DISCHARGE_CURRENT + != self.max_battery_discharge_current + ) + and self.max_battery_charge_current is not None + and self.max_battery_discharge_current is not None + ): + logger.info( + f"> MAX BATTERY CHARGE CURRENT: {self.max_battery_charge_current}A | " + + f"MAX BATTERY DISCHARGE CURRENT: {self.max_battery_discharge_current}A (read from BMS)" + ) + logger.info(f"> CVCM: {utils.CVCM_ENABLE}") logger.info( - f"> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%" + f"> MIN CELL VOLTAGE: {utils.MIN_CELL_VOLTAGE}V | MAX CELL VOLTAGE: {utils.MAX_CELL_VOLTAGE}V" ) - logger.info(f"> Cell count {self.cell_count} | cells populated {cell_counter}") logger.info( - f"> CCCM SOC {utils.CCCM_SOC_ENABLE} | DCCM SOC {utils.DCCM_SOC_ENABLE}" + f"> CCCM CV: {str(utils.CCCM_CV_ENABLE).ljust(5)} | DCCM CV: {utils.DCCM_CV_ENABLE}" ) logger.info( - f"> CCCM CV {utils.CCCM_CV_ENABLE} | DCCM CV {utils.DCCM_CV_ENABLE}" + f"> CCCM T: {str(utils.CCCM_T_ENABLE).ljust(5)} | DCCM T: {utils.DCCM_T_ENABLE}" ) - logger.info(f"> CCCM T {utils.CCCM_T_ENABLE} | DCCM T {utils.DCCM_T_ENABLE}") logger.info( - f"> MIN_CELL_VOLTAGE {utils.MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {utils.MAX_CELL_VOLTAGE}V" + f"> CCCM SOC: {str(utils.CCCM_SOC_ENABLE).ljust(5)} | DCCM SOC: {utils.DCCM_SOC_ENABLE}" ) + if self.unique_identifier is not None: + logger.info(f"Serial Number/Unique Identifier: {self.unique_identifier}") return diff --git a/etc/dbus-serialbattery/ant.py b/etc/dbus-serialbattery/bms/ant.py similarity index 84% rename from etc/dbus-serialbattery/ant.py rename to etc/dbus-serialbattery/bms/ant.py index 07b58d11..124f036f 100644 --- a/etc/dbus-serialbattery/ant.py +++ b/etc/dbus-serialbattery/bms/ant.py @@ -1,7 +1,12 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * + +# disable ANT BMS by default as it causes other issues but can be enabled manually +# https://github.com/Louisvdw/dbus-serialbattery/issues/479 + +from battery import Battery +from utils import read_serial_data, logger +import utils +from struct import unpack_from class Ant(Battery): @@ -25,8 +30,9 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -34,8 +40,8 @@ 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_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT self.version = "ANT BMS V2.0" logger.info(self.hardware_version) return True @@ -59,8 +65,8 @@ def read_status_data(self): self.current = 0.0 if current == 0 else current / -10 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 + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count cell_max_no, cell_max_voltage, cell_min_no, cell_min_voltage = unpack_from( ">bhbh", status_data, 115 @@ -95,9 +101,9 @@ def read_status_data(self): ) self.protection.voltage_cell_low = ( 2 - if self.cell_min_voltage < MIN_CELL_VOLTAGE - 0.1 + if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE - 0.1 else 1 - if self.cell_min_voltage < MIN_CELL_VOLTAGE + if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE else 0 ) self.protection.temp_high_charge = ( diff --git a/etc/dbus-serialbattery/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py similarity index 65% rename from etc/dbus-serialbattery/battery_template.py rename to etc/dbus-serialbattery/bms/battery_template.py index e921efc0..e6148e65 100644 --- a/etc/dbus-serialbattery/battery_template.py +++ b/etc/dbus-serialbattery/bms/battery_template.py @@ -1,7 +1,13 @@ # -*- coding: utf-8 -*- + +# NOTES +# Please also update the feature comparison table, if you are adding a new BMS +# https://louisvdw.github.io/dbus-serialbattery/general/features/#bms-feature-comparison + from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import is_bit_set, read_serial_data, logger +import utils +from struct import unpack_from class BatteryTemplate(Battery): @@ -20,8 +26,12 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: - pass + # get first data to show in startup log + if result: + self.refresh_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -30,12 +40,23 @@ def get_settings(self): # 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 - 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.capacity = ( + utils.BATTERY_CAPACITY # if possible replace constant with value read from BMS + ) + self.max_battery_charge_current = ( + utils.MAX_BATTERY_CHARGE_CURRENT # if possible replace constant with value read from BMS + ) + self.max_battery_discharge_current = ( + utils.MAX_BATTERY_DISCHARGE_CURRENT # if possible replace constant with value read from BMS + ) + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count + + # provide a unique identifier from the BMS to identify a BMS, if multiple same BMS are connected + # e.g. the serial number + # If there is no such value, please leave the line commented. In this case the capacity is used, + # since it can be changed by small amounts to make a battery unique. On +/- 5 Ah you can identify 11 batteries + # self.unique_identifier = str() return True def refresh_data(self): diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/bms/daly.py similarity index 74% rename from etc/dbus-serialbattery/daly.py rename to etc/dbus-serialbattery/bms/daly.py index 5d05ad2a..9978e648 100644 --- a/etc/dbus-serialbattery/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import open_serial_port, logger +import utils +from struct import unpack_from +from time import sleep class Daly(Battery): @@ -30,6 +32,9 @@ def __init__(self, port, baud, address): command_temp = b"\x96" command_cell_balance = b"\x97" command_alarm = b"\x98" + command_rated_params = b"\x50" + command_batt_details = b"\x53" + BATTERYTYPE = "Daly" LENGTH_CHECK = 1 LENGTH_POS = 3 @@ -37,21 +42,30 @@ def __init__(self, port, baud, address): TEMP_ZERO_CONSTANT = 40 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 result = False try: - ser = open_serial_port(self.port, self.baud_rate) - if ser is not None: + with open_serial_port(self.port, self.baud_rate) as ser: + self.read_production_date(ser) result = self.read_status_data(ser) - ser.close() - except: - pass + self.read_soc_data(ser) + + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result def get_settings(self): - self.capacity = BATTERY_CAPACITY - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.capacity = utils.BATTERY_CAPACITY + with open_serial_port(self.port, self.baud_rate) as ser: + self.read_capacity(ser) + + self.unique_identifier = str(self.production) + "_" + str(self.capacity) + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT return True def refresh_data(self): @@ -68,7 +82,7 @@ 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) - + result = result and self.read_balance_state(ser) # 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 @@ -95,8 +109,8 @@ def read_status_data(self, ser): 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 + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.hardware_version = "DalyBMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) @@ -104,20 +118,21 @@ 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 + crntMinValid = -(utils.MAX_BATTERY_DISCHARGE_CURRENT * 2.1) + crntMaxValid = utils.MAX_BATTERY_CHARGE_CURRENT * 1.3 triesValid = 2 while triesValid > 0: + triesValid -= 1 soc_data = self.read_serial_data_daly(ser, self.command_soc) # check if connection success if soc_data is False: - return False + continue voltage, tmp, current, soc = unpack_from(">hhhh", soc_data) current = ( (current - self.CURRENT_ZERO_CONSTANT) / -10 - * INVERT_CURRENT_MEASUREMENT + * utils.INVERT_CURRENT_MEASUREMENT ) if crntMinValid < current < crntMaxValid: self.voltage = voltage / 10 @@ -126,8 +141,6 @@ def read_soc_data(self, ser): return True logger.warning("read_soc_data - triesValid " + str(triesValid)) - triesValid -= 1 - return False def read_alarm_data(self, ser): @@ -262,7 +275,7 @@ def read_cells_volts(self, ser): return False frameCell = [0, 0, 0] - lowMin = MIN_CELL_VOLTAGE / 2 + lowMin = utils.MIN_CELL_VOLTAGE / 2 frame = 0 bufIdx = 0 @@ -274,9 +287,9 @@ def read_cells_volts(self, ser): # logger.warning("data " + bytes(cells_volts_data).hex()) - while ( - bufIdx < len(cells_volts_data) - 4 - ): # we at least need 4 bytes to extract the identifiers + while bufIdx <= len(cells_volts_data) - ( + 4 + 8 + 1 + ): # we at least need 13 bytes to extract the identifiers + 8 bytes payload + checksum b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx) if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08: ( @@ -289,19 +302,20 @@ def read_cells_volts(self, ser): ) = unpack_from(">BhhhBB", cells_volts_data, bufIdx + 4) if sum(cells_volts_data[bufIdx : bufIdx + 12]) & 0xFF != chk: logger.warning("bad cell voltages checksum") - return False - 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 - ) + else: + 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 += 13 # BBBBBhhhBB -> 13 byte else: + bufIdx += 1 # step through buffer to find valid start logger.warning("bad cell voltages header") return True @@ -326,6 +340,20 @@ def read_cell_voltage_range_data(self, ser): self.cell_min_voltage = cell_min_voltage / 1000 return True + def read_balance_state(self, ser): + balance_data = self.read_serial_data_daly(ser, self.command_cell_balance) + # check if connection success + if balance_data is False: + logger.debug("read_balance_state") + return False + + bitdata = unpack_from(">Q", balance_data)[0] + + mask = 1 << 48 + for i in range(len(self.cells)): + self.cells[i].balance = True if bitdata & mask else False + mask >>= 1 + def read_temperature_range_data(self, ser): minmax_data = self.read_serial_data_daly(ser, self.command_minmax_temp) # check if connection success @@ -355,6 +383,28 @@ def read_fed_data(self, ser): self.capacity_remain = capacity_remain / 1000 return True + def read_capacity(self, ser): + capa_data = self.read_serial_data_daly(ser, self.command_rated_params) + # check if connection success + if capa_data is False: + logger.warning("read_capacity") + return False + + (capacity, cell_volt) = unpack_from(">LL", capa_data) + self.capacity = capacity / 1000 + return True + + def read_production_date(self, ser): + production = self.read_serial_data_daly(ser, self.command_batt_details) + # check if connection success + if production is False: + logger.warning("read_production_date") + return False + + (_, _, year, month, day) = unpack_from(">BBBBB", production) + self.production = f"({year + 2000}{month:02d}{day:02d})" + return True + def generate_command(self, command): buffer = bytearray(self.command_base) buffer[1] = self.command_address[0] # Always serial 40 or 80 @@ -367,15 +417,41 @@ def read_serial_data_daly(self, ser, command): ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK ) if data is False: + logger.info("No reply to cmd " + bytes(command).hex()) return False - start, flag, command_ret, length = unpack_from("BBBB", data) - checksum = sum(data[:-1]) & 0xFF + if len(data) <= 12: + logger.debug("Too short reply to cmd " + bytes(command).hex()) + return False - if start == 165 and length == 8 and len(data) > 12 and checksum == data[12]: - return data[4 : length + 4] + # search sentence start + try: + idx = data.index(0xA5) + except ValueError: + logger.debug( + "No Sentence Start found for reply to cmd " + bytes(command).hex() + ) + return False + + if len(data[idx:]) <= 12: + logger.debug("Too short reply to cmd " + bytes(command).hex()) + return False + + if data[12 + idx] != sum(data[idx : 12 + idx]) & 0xFF: + logger.debug("Bad checksum in reply to cmd " + bytes(command).hex()) + return False + + _, _, _, length = unpack_from(">BBBB", data, idx) + + if length == 8: + return data[4 + idx : length + 4 + idx] else: - logger.error(">>> ERROR: Incorrect Reply") + logger.debug( + ">>> ERROR: Incorrect Reply to CMD " + + bytes(command).hex() + + ": 0x" + + bytes(data).hex() + ) return False # Read data from previously openned serial port diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/bms/ecs.py similarity index 89% rename from etc/dbus-serialbattery/ecs.py rename to etc/dbus-serialbattery/bms/ecs.py index d8bc7295..a50e6072 100644 --- a/etc/dbus-serialbattery/ecs.py +++ b/etc/dbus-serialbattery/bms/ecs.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import logger +import utils import minimalmodbus @@ -33,7 +33,7 @@ def test_connection(self): # Trying to find Green Meter ID try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.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): @@ -46,13 +46,18 @@ def test_connection(self): self.find_LiPro_cells() + # get first data to show in startup log + self.refresh_data() + return self.get_settings() except IOError: return False def find_LiPro_cells(self): # test for LiPro cell devices - for cell_address in range(LIPRO_START_ADDRESS, LIPRO_END_ADDRESS + 1): + for cell_address in range( + utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1 + ): try: mbdev = minimalmodbus.Instrument(self.port, cell_address) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN @@ -74,11 +79,11 @@ def get_settings(self): # 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 - self.cell_count = LIPRO_CELL_COUNT - 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.cell_count = utils.LIPRO_CELL_COUNT + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.temp_sensors = 2 return self.read_status_data() @@ -94,7 +99,7 @@ def refresh_data(self): def read_status_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN self.max_battery_discharge_current = abs( @@ -120,7 +125,7 @@ def read_status_data(self): def read_soc_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN self.voltage = ( diff --git a/etc/dbus-serialbattery/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py similarity index 99% rename from etc/dbus-serialbattery/hlpdatabms4s.py rename to etc/dbus-serialbattery/bms/hlpdatabms4s.py index 1bacf395..d4a350b1 100644 --- a/etc/dbus-serialbattery/hlpdatabms4s.py +++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py @@ -202,7 +202,7 @@ def read_serial_data2(command, port, baud, time, min_len): try: with serial.Serial(port, baudrate=baud, timeout=0.5) as ser: ret = read_serialport_data2(ser, command, time, min_len) - if not ret is False: + if ret is True: return ret return False diff --git a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py b/etc/dbus-serialbattery/bms/hlpdatabms4s_miniterm.py similarity index 97% rename from etc/dbus-serialbattery/hlpdatabms4s_miniterm.py rename to etc/dbus-serialbattery/bms/hlpdatabms4s_miniterm.py index 80cb5414..22df77fd 100644 --- a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py +++ b/etc/dbus-serialbattery/bms/hlpdatabms4s_miniterm.py @@ -1,1190 +1,1188 @@ -#!/usr/bin/env python -# -# Very simple serial terminal -# -# This file is part of pySerial. https://github.com/pyserial/pyserial -# (C)2002-2020 Chris Liechti -# -# SPDX-License-Identifier: BSD-3-Clause -# -# Modified to enable managing HLPdataBMS4S in Venus OS - -from __future__ import absolute_import - -import codecs -import os -import sys -import threading - -import serial -from serial.tools.list_ports import comports -from serial.tools import hexlify_codec - -# from time import sleep - -# pylint: disable=wrong-import-order,wrong-import-position - -codecs.register(lambda c: hexlify_codec.getregentry() if c == "hexlify" else None) - -try: - raw_input -except NameError: - # pylint: disable=redefined-builtin,invalid-name - raw_input = input # in python3 it's "raw" - unichr = chr - - -def key_description(character): - """generate a readable description for a key""" - ascii_code = ord(character) - if ascii_code < 32: - return "Ctrl+{:c}".format(ord("@") + ascii_code) - else: - return repr(character) - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -class ConsoleBase(object): - """OS abstraction for console (input/output codec, no echo)""" - - def __init__(self, miniterm): - self.miniterm = miniterm - if sys.version_info >= (3, 0): - self.byte_output = sys.stdout.buffer - else: - self.byte_output = sys.stdout - self.output = sys.stdout - - def setup(self): - """Set console to read single characters, no echo""" - - def cleanup(self): - """Restore default console settings""" - - def getkey(self): - """Read a single key from the console""" - return None - - def write_bytes(self, byte_string): - """Write bytes (already encoded)""" - self.byte_output.write(byte_string) - self.byte_output.flush() - - def write(self, text): - """Write string""" - self.output.write(text) - self.output.flush() - - def cancel(self): - """Cancel getkey operation""" - - # - - - - - - - - - - - - - - - - - - - - - - - - - # context manager: - # switch terminal temporary to normal mode (e.g. to get user input) - - def __enter__(self): - self.cleanup() - return self - - def __exit__(self, *args, **kwargs): - self.setup() - - -if os.name == "nt": # noqa - import msvcrt - import ctypes - import platform - - class Out(object): - """file-like wrapper that uses os.write""" - - def __init__(self, fd): - self.fd = fd - - def flush(self): - pass - - def write(self, s): - os.write(self.fd, s) - - class Console(ConsoleBase): - fncodes = { - ";": "\x1bOP", # F1 - "<": "\x1bOQ", # F2 - "=": "\x1bOR", # F3 - ">": "\x1bOS", # F4 - "?": "\x1b[15~", # F5 - "@": "\x1b[17~", # F6 - "A": "\x1b[18~", # F7 - "B": "\x1b[19~", # F8 - "C": "\x1b[20~", # F9 - "D": "\x1b[21~", # F10 - } - navcodes = { - "H": "\x1b[A", # UP - "P": "\x1b[B", # DOWN - "K": "\x1b[D", # LEFT - "M": "\x1b[C", # RIGHT - "G": "\x1b[H", # HOME - "O": "\x1b[F", # END - "R": "\x1b[2~", # INSERT - "S": "\x1b[3~", # DELETE - "I": "\x1b[5~", # PAGE UP - "Q": "\x1b[6~", # PAGE DOWN - } - - def __init__(self, miniterm): - super(Console, self).__init__(miniterm) - self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() - self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() - ctypes.windll.kernel32.SetConsoleOutputCP(65001) - ctypes.windll.kernel32.SetConsoleCP(65001) - # ANSI handling available through SetConsoleMode since Windows 10 v1511 - # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 - if ( - platform.release() == "10" - and int(platform.version().split(".")[2]) > 10586 - ): - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - import ctypes.wintypes as wintypes - - if not hasattr(wintypes, "LPDWORD"): # PY2 - wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) - SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode - GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode - GetStdHandle = ctypes.windll.kernel32.GetStdHandle - mode = wintypes.DWORD() - GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) - if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: - SetConsoleMode( - GetStdHandle(-11), - mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) - self._saved_cm = mode - self.output = codecs.getwriter("UTF-8")(Out(sys.stdout.fileno()), "replace") - # the change of the code page is not propagated to Python, manually fix it - sys.stderr = codecs.getwriter("UTF-8")(Out(sys.stderr.fileno()), "replace") - sys.stdout = self.output - self.output.encoding = "UTF-8" # needed for input - - def __del__(self): - ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) - ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) - try: - ctypes.windll.kernel32.SetConsoleMode( - ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm - ) - except AttributeError: # in case no _saved_cm - pass - - def getkey(self): - while True: - z = msvcrt.getwch() - if z == unichr(13): - return unichr(10) - elif z is unichr(0) or z is unichr(0xE0): - try: - code = msvcrt.getwch() - if z is unichr(0): - return self.fncodes[code] - else: - return self.navcodes[code] - except KeyError: - pass - else: - return z - - def cancel(self): - # CancelIo, CancelSynchronousIo do not seem to work when using - # getwch, so instead, send a key to the window with the console - hwnd = ctypes.windll.kernel32.GetConsoleWindow() - ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0D, 0) - -elif os.name == "posix": - import atexit - import termios - import fcntl - import signal - - class Console(ConsoleBase): - def __init__(self, miniterm): - super(Console, self).__init__(miniterm) - self.fd = sys.stdin.fileno() - self.old = termios.tcgetattr(self.fd) - atexit.register(self.cleanup) - signal.signal(signal.SIGINT, self.sigint) - if sys.version_info < (3, 0): - self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) - else: - self.enc_stdin = sys.stdin - - def setup(self): - new = termios.tcgetattr(self.fd) - new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG - new[6][termios.VMIN] = 1 - new[6][termios.VTIME] = 0 - termios.tcsetattr(self.fd, termios.TCSANOW, new) - - def getkey(self): - c = self.enc_stdin.read(1) - if c == unichr(0x7F): - c = unichr(8) # map the BS key (which yields DEL) to backspace - return c - - def cancel(self): - fcntl.ioctl(self.fd, termios.TIOCSTI, b"\0") - - def cleanup(self): - termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) - - def sigint(self, sig, frame): - """signal handler for a clean exit on SIGINT""" - self.miniterm.stop() - self.cancel() - -else: - raise NotImplementedError( - "Sorry no implementation for your platform ({}) available.".format(sys.platform) - ) - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -class Transform(object): - """do-nothing: forward all data unchanged""" - - def rx(self, text): - """text received from serial port""" - return text - - def tx(self, text): - """text to be sent to serial port""" - return text - - def echo(self, text): - """text to be sent but displayed on console""" - return text - - -class CRLF(Transform): - """ENTER sends CR+LF""" - - def tx(self, text): - return text.replace("\n", "\r\n") - - -class CR(Transform): - """ENTER sends CR""" - - def rx(self, text): - return text.replace("\r", "\n") - - def tx(self, text): - return text.replace("\n", "\r") - - -class LF(Transform): - """ENTER sends LF""" - - -class NoTerminal(Transform): - """remove typical terminal control codes from input""" - - REPLACEMENT_MAP = dict( - (x, 0x2400 + x) for x in range(32) if unichr(x) not in "\r\n\b\t" - ) - REPLACEMENT_MAP.update( - { - 0x7F: 0x2421, # DEL - 0x9B: 0x2425, # CSI - } - ) - - def rx(self, text): - return text.translate(self.REPLACEMENT_MAP) - - echo = rx - - -class NoControls(NoTerminal): - """Remove all control codes, incl. CR+LF""" - - REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) - REPLACEMENT_MAP.update( - { - 0x20: 0x2423, # visual space - 0x7F: 0x2421, # DEL - 0x9B: 0x2425, # CSI - } - ) - - -class Printable(Transform): - """Show decimal code for all non-ASCII characters and replace most control codes""" - - def rx(self, text): - r = [] - for c in text: - if " " <= c < "\x7f" or c in "\r\n\b\t": - r.append(c) - elif c < " ": - r.append(unichr(0x2400 + ord(c))) - else: - r.extend(unichr(0x2080 + ord(d) - 48) for d in "{:d}".format(ord(c))) - r.append(" ") - return "".join(r) - - echo = rx - - -class Colorize(Transform): - """Apply different colors for received and echo""" - - def __init__(self): - # XXX make it configurable, use colorama? - self.input_color = "\x1b[37m" - self.echo_color = "\x1b[31m" - - def rx(self, text): - return self.input_color + text - - def echo(self, text): - return self.echo_color + text - - -class DebugIO(Transform): - """Print what is sent and received""" - - def rx(self, text): - sys.stderr.write(" [RX:{!r}] ".format(text)) - sys.stderr.flush() - return text - - def tx(self, text): - sys.stderr.write(" [TX:{!r}] ".format(text)) - sys.stderr.flush() - return text - - -# other ideas: -# - add date/time for each newline -# - insert newline after: a) timeout b) packet end character - -EOL_TRANSFORMATIONS = { - "crlf": CRLF, - "cr": CR, - "lf": LF, -} - -TRANSFORMATIONS = { - "direct": Transform, # no transformation - "default": NoTerminal, - "nocontrol": NoControls, - "printable": Printable, - "colorize": Colorize, - "debug": DebugIO, -} - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def ask_for_port(): - """\ - Show a list of ports and ask the user for a choice. To make selection - easier on systems with long device names, also allow the input of an - index. - """ - sys.stderr.write("\n--- Available ports:\n") - ports = [] - for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): - sys.stderr.write("--- {:2}: {:20} {!r}\n".format(n, port, desc)) - ports.append(port) - while True: - sys.stderr.write("--- Enter port index or full name: ") - port = raw_input("") - try: - index = int(port) - 1 - if not 0 <= index < len(ports): - sys.stderr.write("--- Invalid index!\n") - continue - except ValueError: - pass - else: - port = ports[index] - return port - - -class Miniterm(object): - """\ - Terminal application. Copy data from serial port to console and vice versa. - Handle special keys from the console to show menu etc. - """ - - def __init__(self, serial_instance, echo=False, eol="crlf", filters=()): - self.console = Console(self) - self.serial = serial_instance - self.echo = True - self.raw = False - self.input_encoding = "UTF-8" - self.output_encoding = "UTF-8" - self.eol = eol - self.filters = filters - self.update_transformations() - self.exit_character = unichr(0x1D) # GS/CTRL+] - self.menu_character = unichr(0x14) # Menu: CTRL+T - self.alive = None - self._reader_alive = None - self.receiver_thread = None - self.rx_decoder = None - self.tx_decoder = None - self.tx_encoder = None - self.no_write = False - self.remove_no_write = False - - def _start_reader(self): - """Start reader thread""" - self._reader_alive = True - # start serial->console thread - self.receiver_thread = threading.Thread(target=self.reader, name="rx") - self.receiver_thread.daemon = True - self.receiver_thread.start() - - def _stop_reader(self): - """Stop reader thread only, wait for clean exit of thread""" - self._reader_alive = False - if hasattr(self.serial, "cancel_read"): - self.serial.cancel_read() - self.receiver_thread.join() - - def start(self): - """start worker threads""" - self.alive = True - self._start_reader() - # enter console->serial loop - self.transmitter_thread = threading.Thread(target=self.writer, name="tx") - self.transmitter_thread.daemon = True - self.transmitter_thread.start() - self.console.setup() - - def stop(self): - """set flag to stop worker threads""" - self.alive = False - - def join(self, transmit_only=False): - """wait for worker threads to terminate""" - self.transmitter_thread.join() - if not transmit_only: - if hasattr(self.serial, "cancel_read"): - self.serial.cancel_read() - self.receiver_thread.join() - - def close(self): - self.serial.close() - - def update_transformations(self): - """take list of transformation classes and instantiate them for rx and tx""" - transformations = [EOL_TRANSFORMATIONS[self.eol]] + [ - TRANSFORMATIONS[f] for f in self.filters - ] - self.tx_transformations = [t() for t in transformations] - self.rx_transformations = list(reversed(self.tx_transformations)) - - def set_rx_encoding(self, encoding, errors="replace"): - """set encoding for received data""" - self.input_encoding = encoding - self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) - - def set_tx_encoding(self, encoding, errors="replace"): - """set encoding for transmitted data""" - self.output_encoding = encoding - self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) - - def dump_port_settings(self): - """Write current settings to sys.stderr""" - sys.stderr.write( - "\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( - p=self.serial - ) - ) - sys.stderr.write( - "--- RTS: {:8} DTR: {:8} BREAK: {:8}\n".format( - ("active" if self.serial.rts else "inactive"), - ("active" if self.serial.dtr else "inactive"), - ("active" if self.serial.break_condition else "inactive"), - ) - ) - try: - sys.stderr.write( - "--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n".format( - ("active" if self.serial.cts else "inactive"), - ("active" if self.serial.dsr else "inactive"), - ("active" if self.serial.ri else "inactive"), - ("active" if self.serial.cd else "inactive"), - ) - ) - except serial.SerialException: - # on RFC 2217 ports, it can happen if no modem state notification was - # yet received. ignore this error. - pass - sys.stderr.write( - "--- software flow control: {}\n".format( - "active" if self.serial.xonxoff else "inactive" - ) - ) - sys.stderr.write( - "--- hardware flow control: {}\n".format( - "active" if self.serial.rtscts else "inactive" - ) - ) - sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) - sys.stderr.write( - "--- serial output encoding: {}\n".format(self.output_encoding) - ) - sys.stderr.write("--- EOL: {}\n".format(self.eol.upper())) - sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) - - def reader(self): - """loop and copy serial->console""" - data = b"" - while self.alive and self._reader_alive: - # read all that is there or wait for one byte - try: - data2 = self.serial.read(self.serial.in_waiting or 1) - except Exception: - data2 = b"" - if data2: - if self.remove_no_write is True: - self.remove_no_write = False - self.no_write = False - data = data2 - else: - data += data2 - if b"\n" in data: - if self.raw: - self.console.write_bytes(data) - else: - text = self.rx_decoder.decode(data) - for transformation in self.rx_transformations: - text = transformation.rx(text) - if b"m1\n" in data: - self.no_write = True - if self.no_write is False: - self.console.write(text) - data = b"" - - def writer(self): - """\ - Loop and copy console->serial until self.exit_character character is - found. When self.menu_character is found, interpret the next key - locally. - """ - menu_active = False - try: - while self.alive: - try: - c = self.console.getkey() - self.remove_no_write = True - except KeyboardInterrupt: - c = "\x03" - if not self.alive: - break - if menu_active: - self.handle_menu_key(c) - menu_active = False - elif c == self.menu_character: - menu_active = True # next char will be for menu - elif c == self.exit_character: - self.stop() # exit app - break - else: - # ~ if self.raw: - text = c - for transformation in self.tx_transformations: - text = transformation.tx(text) - self.serial.write(self.tx_encoder.encode(text)) - if self.echo: - echo_text = c - for transformation in self.tx_transformations: - echo_text = transformation.echo(echo_text) - self.console.write(echo_text) - except Exception: - self.alive = False - raise - - def handle_menu_key(self, c): - """Implement a simple menu / settings""" - if c == self.menu_character or c == self.exit_character: - # Menu/exit character again -> send itself - self.serial.write(self.tx_encoder.encode(c)) - if self.echo: - self.console.write(c) - elif c == "\x15": # CTRL+U -> upload file - self.upload_file() - elif c in "\x08hH?": # CTRL+H, h, H, ? -> Show help - sys.stderr.write(self.get_help_text()) - elif c == "\x12": # CTRL+R -> Toggle RTS - self.serial.rts = not self.serial.rts - sys.stderr.write( - "--- RTS {} ---\n".format("active" if self.serial.rts else "inactive") - ) - elif c == "\x04": # CTRL+D -> Toggle DTR - self.serial.dtr = not self.serial.dtr - sys.stderr.write( - "--- DTR {} ---\n".format("active" if self.serial.dtr else "inactive") - ) - elif c == "\x02": # CTRL+B -> toggle BREAK condition - self.serial.break_condition = not self.serial.break_condition - sys.stderr.write( - "--- BREAK {} ---\n".format( - "active" if self.serial.break_condition else "inactive" - ) - ) - elif c == "\x05": # CTRL+E -> toggle local echo - self.echo = not self.echo - sys.stderr.write( - "--- local echo {} ---\n".format("active" if self.echo else "inactive") - ) - elif c == "\x06": # CTRL+F -> edit filters - self.change_filter() - elif c == "\x0c": # CTRL+L -> EOL mode - modes = list(EOL_TRANSFORMATIONS) # keys - eol = modes.index(self.eol) + 1 - if eol >= len(modes): - eol = 0 - self.eol = modes[eol] - sys.stderr.write("--- EOL: {} ---\n".format(self.eol.upper())) - self.update_transformations() - elif c == "\x01": # CTRL+A -> set encoding - self.change_encoding() - elif c == "\x09": # CTRL+I -> info - self.dump_port_settings() - # ~ elif c == '\x01': # CTRL+A -> cycle escape mode - # ~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode - elif c in "pP": # P -> change port - self.change_port() - elif c in "zZ": # S -> suspend / open port temporarily - self.suspend_port() - elif c in "bB": # B -> change baudrate - self.change_baudrate() - elif c == "8": # 8 -> change to 8 bits - self.serial.bytesize = serial.EIGHTBITS - self.dump_port_settings() - elif c == "7": # 7 -> change to 8 bits - self.serial.bytesize = serial.SEVENBITS - self.dump_port_settings() - elif c in "eE": # E -> change to even parity - self.serial.parity = serial.PARITY_EVEN - self.dump_port_settings() - elif c in "oO": # O -> change to odd parity - self.serial.parity = serial.PARITY_ODD - self.dump_port_settings() - elif c in "mM": # M -> change to mark parity - self.serial.parity = serial.PARITY_MARK - self.dump_port_settings() - elif c in "sS": # S -> change to space parity - self.serial.parity = serial.PARITY_SPACE - self.dump_port_settings() - elif c in "nN": # N -> change to no parity - self.serial.parity = serial.PARITY_NONE - self.dump_port_settings() - elif c == "1": # 1 -> change to 1 stop bits - self.serial.stopbits = serial.STOPBITS_ONE - self.dump_port_settings() - elif c == "2": # 2 -> change to 2 stop bits - self.serial.stopbits = serial.STOPBITS_TWO - self.dump_port_settings() - elif c == "3": # 3 -> change to 1.5 stop bits - self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE - self.dump_port_settings() - elif c in "xX": # X -> change software flow control - self.serial.xonxoff = c == "X" - self.dump_port_settings() - elif c in "rR": # R -> change hardware flow control - self.serial.rtscts = c == "R" - self.dump_port_settings() - elif c in "qQ": - self.stop() # Q -> exit app - else: - sys.stderr.write( - "--- unknown menu character {} --\n".format(key_description(c)) - ) - - def upload_file(self): - """Ask user for filename and send its contents""" - sys.stderr.write("\n--- File to upload: ") - sys.stderr.flush() - with self.console: - filename = sys.stdin.readline().rstrip("\r\n") - if filename: - try: - with open(filename, "rb") as f: - sys.stderr.write("--- Sending file {} ---\n".format(filename)) - while True: - block = f.read(1024) - if not block: - break - self.serial.write(block) - # Wait for output buffer to drain. - self.serial.flush() - sys.stderr.write(".") # Progress indicator. - sys.stderr.write("\n--- File {} sent ---\n".format(filename)) - except IOError as e: - sys.stderr.write( - "--- ERROR opening file {}: {} ---\n".format(filename, e) - ) - - def change_filter(self): - """change the i/o transformations""" - sys.stderr.write("\n--- Available Filters:\n") - sys.stderr.write( - "\n".join( - "--- {:<10} = {.__doc__}".format(k, v) - for k, v in sorted(TRANSFORMATIONS.items()) - ) - ) - sys.stderr.write( - "\n--- Enter new filter name(s) [{}]: ".format(" ".join(self.filters)) - ) - with self.console: - new_filters = sys.stdin.readline().lower().split() - if new_filters: - for f in new_filters: - if f not in TRANSFORMATIONS: - sys.stderr.write("--- unknown filter: {!r}\n".format(f)) - break - else: - self.filters = new_filters - self.update_transformations() - sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) - - def change_encoding(self): - """change encoding on the serial port""" - sys.stderr.write( - "\n--- Enter new encoding name [{}]: ".format(self.input_encoding) - ) - with self.console: - new_encoding = sys.stdin.readline().strip() - if new_encoding: - try: - codecs.lookup(new_encoding) - except LookupError: - sys.stderr.write("--- invalid encoding name: {}\n".format(new_encoding)) - else: - self.set_rx_encoding(new_encoding) - self.set_tx_encoding(new_encoding) - sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) - sys.stderr.write( - "--- serial output encoding: {}\n".format(self.output_encoding) - ) - - def change_baudrate(self): - """change the baudrate""" - sys.stderr.write("\n--- Baudrate: ") - sys.stderr.flush() - with self.console: - backup = self.serial.baudrate - try: - self.serial.baudrate = int(sys.stdin.readline().strip()) - except ValueError as e: - sys.stderr.write("--- ERROR setting baudrate: {} ---\n".format(e)) - self.serial.baudrate = backup - else: - self.dump_port_settings() - - def change_port(self): - """Have a conversation with the user to change the serial port""" - with self.console: - try: - port = ask_for_port() - except KeyboardInterrupt: - port = None - if port and port != self.serial.port: - # reader thread needs to be shut down - self._stop_reader() - # save settings - settings = self.serial.getSettingsDict() - try: - new_serial = serial.serial_for_url(port, do_not_open=True) - # restore settings and open - new_serial.applySettingsDict(settings) - new_serial.rts = self.serial.rts - new_serial.dtr = self.serial.dtr - new_serial.open() - new_serial.break_condition = self.serial.break_condition - except Exception as e: - sys.stderr.write("--- ERROR opening new port: {} ---\n".format(e)) - new_serial.close() - else: - self.serial.close() - self.serial = new_serial - sys.stderr.write( - "--- Port changed to: {} ---\n".format(self.serial.port) - ) - # and restart the reader thread - self._start_reader() - - def suspend_port(self): - """\ - open port temporarily, allow reconnect, exit and port change to get - out of the loop - """ - # reader thread needs to be shut down - self._stop_reader() - self.serial.close() - sys.stderr.write("\n--- Port closed: {} ---\n".format(self.serial.port)) - do_change_port = False - while not self.serial.is_open: - sys.stderr.write( - "--- Quit: {exit} | p: port change | any other key to reconnect ---\n".format( - exit=key_description(self.exit_character) - ) - ) - k = self.console.getkey() - if k == self.exit_character: - self.stop() # exit app - break - elif k in "pP": - do_change_port = True - break - try: - self.serial.open() - except Exception as e: - sys.stderr.write("--- ERROR opening port: {} ---\n".format(e)) - if do_change_port: - self.change_port() - else: - # and restart the reader thread - self._start_reader() - sys.stderr.write("--- Port opened: {} ---\n".format(self.serial.port)) - - def get_help_text(self): - """return the help text""" - # help text, starts with blank line! - return """ ---- pySerial ({version}) - miniterm - help ---- ---- {exit:8} Exit program (alias {menu} Q) ---- {menu:8} Menu escape key, followed by: ---- Menu keys: ---- {menu:7} Send the menu character itself to remote ---- {exit:7} Send the exit character itself to remote ---- {info:7} Show info ---- {upload:7} Upload file (prompt will be shown) ---- {repr:7} encoding ---- {filter:7} edit filters ---- Toggles: ---- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK ---- {echo:7} echo {eol:7} EOL ---- ---- Port settings ({menu} followed by the following): ---- p change port ---- 7 8 set data bits ---- N E O S M change parity (None, Even, Odd, Space, Mark) ---- 1 2 3 set stop bits (1, 2, 1.5) ---- b change baud rate ---- x X disable/enable software flow control ---- r R disable/enable hardware flow control -""".format( - version=getattr(serial, "VERSION", "unknown version"), - exit=key_description(self.exit_character), - menu=key_description(self.menu_character), - rts=key_description("\x12"), - dtr=key_description("\x04"), - brk=key_description("\x02"), - echo=key_description("\x05"), - info=key_description("\x09"), - upload=key_description("\x15"), - repr=key_description("\x01"), - filter=key_description("\x06"), - eol=key_description("\x0c"), - ) - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# default args can be used to override when calling main() from an other script -# e.g to create a miniterm-my-device.py -def main( - default_port=None, - default_baudrate=9600, - default_rts=None, - default_dtr=None, - serial_instance=None, -): - """Command line tool, entry point""" - - import argparse - - parser = argparse.ArgumentParser( - description="Miniterm - A simple terminal program for the serial port." - ) - - parser.add_argument( - "port", - nargs="?", - help='serial port name ("-" to show port list)', - default=default_port, - ) - - parser.add_argument( - "baudrate", - nargs="?", - type=int, - help="set baud rate, default: %(default)s", - default=default_baudrate, - ) - - group = parser.add_argument_group("port settings") - - group.add_argument( - "--parity", - choices=["N", "E", "O", "S", "M"], - type=lambda c: c.upper(), - help="set parity, one of {N E O S M}, default: N", - default="N", - ) - - group.add_argument( - "--rtscts", - action="store_true", - help="enable RTS/CTS flow control (default off)", - default=False, - ) - - group.add_argument( - "--xonxoff", - action="store_true", - help="enable software flow control (default off)", - default=False, - ) - - group.add_argument( - "--rts", - type=int, - help="set initial RTS line state (possible values: 0, 1)", - default=default_rts, - ) - - group.add_argument( - "--dtr", - type=int, - help="set initial DTR line state (possible values: 0, 1)", - default=default_dtr, - ) - - group.add_argument( - "--non-exclusive", - dest="exclusive", - action="store_false", - help="disable locking for native ports", - default=True, - ) - - group.add_argument( - "--ask", - action="store_true", - help="ask again for port when open fails", - default=False, - ) - - group = parser.add_argument_group("data handling") - - group.add_argument( - "-e", - "--echo", - action="store_true", - help="enable local echo (default off)", - default=False, - ) - - group.add_argument( - "--encoding", - dest="serial_port_encoding", - metavar="CODEC", - help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s", - default="UTF-8", - ) - - group.add_argument( - "-f", - "--filter", - action="append", - metavar="NAME", - help="add text transformation", - default=[], - ) - - group.add_argument( - "--eol", - choices=["CR", "LF", "CRLF"], - type=lambda c: c.upper(), - help="end of line mode", - default="CRLF", - ) - - group.add_argument( - "--raw", - action="store_true", - help="Do no apply any encodings/transformations", - default=False, - ) - - group = parser.add_argument_group("hotkeys") - - group.add_argument( - "--exit-char", - type=int, - metavar="NUM", - help="Unicode of special character that is used to exit the application, default: %(default)s", - default=0x1D, - ) # GS/CTRL+] - - group.add_argument( - "--menu-char", - type=int, - metavar="NUM", - help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s", - default=0x14, - ) # Menu: CTRL+T - - group = parser.add_argument_group("diagnostics") - - group.add_argument( - "-q", - "--quiet", - action="store_true", - help="suppress non-error messages", - default=False, - ) - - group.add_argument( - "--develop", - action="store_true", - help="show Python traceback on error", - default=False, - ) - - args = parser.parse_args() - - if args.menu_char == args.exit_char: - parser.error("--exit-char can not be the same as --menu-char") - - if args.filter: - if "help" in args.filter: - sys.stderr.write("Available filters:\n") - sys.stderr.write( - "\n".join( - "{:<10} = {.__doc__}".format(k, v) - for k, v in sorted(TRANSFORMATIONS.items()) - ) - ) - sys.stderr.write("\n") - sys.exit(1) - filters = args.filter - else: - filters = ["default"] - - while serial_instance is None: - # no port given on command line -> ask user now - if args.port is None or args.port == "-": - try: - args.port = ask_for_port() - except KeyboardInterrupt: - sys.stderr.write("\n") - parser.error("user aborted and port is not given") - else: - if not args.port: - parser.error("port is not given") - try: - serial_instance = serial.serial_for_url( - args.port, - args.baudrate, - parity=args.parity, - rtscts=args.rtscts, - xonxoff=args.xonxoff, - do_not_open=True, - ) - - if not hasattr(serial_instance, "cancel_read"): - # enable timeout for alive flag polling if cancel_read is not available - serial_instance.timeout = 1 - - if args.dtr is not None: - if not args.quiet: - sys.stderr.write( - "--- forcing DTR {}\n".format( - "active" if args.dtr else "inactive" - ) - ) - serial_instance.dtr = args.dtr - if args.rts is not None: - if not args.quiet: - sys.stderr.write( - "--- forcing RTS {}\n".format( - "active" if args.rts else "inactive" - ) - ) - serial_instance.rts = args.rts - - if isinstance(serial_instance, serial.Serial): - serial_instance.exclusive = args.exclusive - - serial_instance.open() - except serial.SerialException as e: - sys.stderr.write("could not open port {!r}: {}\n".format(args.port, e)) - if args.develop: - raise - if not args.ask: - sys.exit(1) - else: - args.port = "-" - else: - break - - miniterm = Miniterm( - serial_instance, echo=args.echo, eol=args.eol.lower(), filters=filters - ) - miniterm.exit_character = unichr(args.exit_char) - miniterm.menu_character = unichr(args.menu_char) - miniterm.raw = args.raw - miniterm.set_rx_encoding(args.serial_port_encoding) - miniterm.set_tx_encoding(args.serial_port_encoding) - - if not args.quiet: - sys.stderr.write( - "--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n".format( - p=miniterm.serial - ) - ) - sys.stderr.write( - "--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n".format( - key_description(miniterm.exit_character), - key_description(miniterm.menu_character), - key_description(miniterm.menu_character), - key_description("\x08"), - ) - ) - sys.stderr.write( - "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format( # noqa: F522 - p=miniterm.serial - ) - ) - sys.stderr.write( - "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format( # noqa: F522 - p=miniterm.serial - ) - ) - - miniterm.start() - try: - miniterm.join(True) - except KeyboardInterrupt: - pass - if not args.quiet: - sys.stderr.write("\n--- exit ---\n") - miniterm.join() - miniterm.close() - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -if __name__ == "__main__": - main() +#!/usr/bin/env python +# +# Very simple serial terminal +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C)2002-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Modified to enable managing HLPdataBMS4S in Venus OS + +from __future__ import absolute_import + +import codecs +import os +import sys +import threading + +import serial +from serial.tools.list_ports import comports +from serial.tools import hexlify_codec + +# pylint: disable=wrong-import-order,wrong-import-position + +codecs.register(lambda c: hexlify_codec.getregentry() if c == "hexlify" else None) + +try: + raw_input +except NameError: + # pylint: disable=redefined-builtin,invalid-name + raw_input = input # in python3 it's "raw" + unichr = chr + + +def key_description(character): + """generate a readable description for a key""" + ascii_code = ord(character) + if ascii_code < 32: + return "Ctrl+{:c}".format(ord("@") + ascii_code) + else: + return repr(character) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ConsoleBase(object): + """OS abstraction for console (input/output codec, no echo)""" + + def __init__(self, miniterm): + self.miniterm = miniterm + if sys.version_info >= (3, 0): + self.byte_output = sys.stdout.buffer + else: + self.byte_output = sys.stdout + self.output = sys.stdout + + def setup(self): + """Set console to read single characters, no echo""" + + def cleanup(self): + """Restore default console settings""" + + def getkey(self): + """Read a single key from the console""" + return None + + def write_bytes(self, byte_string): + """Write bytes (already encoded)""" + self.byte_output.write(byte_string) + self.byte_output.flush() + + def write(self, text): + """Write string""" + self.output.write(text) + self.output.flush() + + def cancel(self): + """Cancel getkey operation""" + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager: + # switch terminal temporary to normal mode (e.g. to get user input) + + def __enter__(self): + self.cleanup() + return self + + def __exit__(self, *args, **kwargs): + self.setup() + + +if os.name == "nt": # noqa + import msvcrt + import ctypes + import platform + + class Out(object): + """file-like wrapper that uses os.write""" + + def __init__(self, fd): + self.fd = fd + + def flush(self): + pass + + def write(self, s): + os.write(self.fd, s) + + class Console(ConsoleBase): + fncodes = { + ";": "\x1bOP", # F1 + "<": "\x1bOQ", # F2 + "=": "\x1bOR", # F3 + ">": "\x1bOS", # F4 + "?": "\x1b[15~", # F5 + "@": "\x1b[17~", # F6 + "A": "\x1b[18~", # F7 + "B": "\x1b[19~", # F8 + "C": "\x1b[20~", # F9 + "D": "\x1b[21~", # F10 + } + navcodes = { + "H": "\x1b[A", # UP + "P": "\x1b[B", # DOWN + "K": "\x1b[D", # LEFT + "M": "\x1b[C", # RIGHT + "G": "\x1b[H", # HOME + "O": "\x1b[F", # END + "R": "\x1b[2~", # INSERT + "S": "\x1b[3~", # DELETE + "I": "\x1b[5~", # PAGE UP + "Q": "\x1b[6~", # PAGE DOWN + } + + def __init__(self, miniterm): + super(Console, self).__init__(miniterm) + self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() + self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + ctypes.windll.kernel32.SetConsoleCP(65001) + # ANSI handling available through SetConsoleMode since Windows 10 v1511 + # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 + if ( + platform.release() == "10" + and int(platform.version().split(".")[2]) > 10586 + ): + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + import ctypes.wintypes as wintypes + + if not hasattr(wintypes, "LPDWORD"): # PY2 + wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) + SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode + GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode + GetStdHandle = ctypes.windll.kernel32.GetStdHandle + mode = wintypes.DWORD() + GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) + if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: + SetConsoleMode( + GetStdHandle(-11), + mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + self._saved_cm = mode + self.output = codecs.getwriter("UTF-8")(Out(sys.stdout.fileno()), "replace") + # the change of the code page is not propagated to Python, manually fix it + sys.stderr = codecs.getwriter("UTF-8")(Out(sys.stderr.fileno()), "replace") + sys.stdout = self.output + self.output.encoding = "UTF-8" # needed for input + + def __del__(self): + ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) + ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) + try: + ctypes.windll.kernel32.SetConsoleMode( + ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm + ) + except AttributeError: # in case no _saved_cm + pass + + def getkey(self): + while True: + z = msvcrt.getwch() + if z == unichr(13): + return unichr(10) + elif z is unichr(0) or z is unichr(0xE0): + try: + code = msvcrt.getwch() + if z is unichr(0): + return self.fncodes[code] + else: + return self.navcodes[code] + except KeyError: + pass + else: + return z + + def cancel(self): + # CancelIo, CancelSynchronousIo do not seem to work when using + # getwch, so instead, send a key to the window with the console + hwnd = ctypes.windll.kernel32.GetConsoleWindow() + ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0D, 0) + +elif os.name == "posix": + import atexit + import termios + import fcntl + import signal + + class Console(ConsoleBase): + def __init__(self, miniterm): + super(Console, self).__init__(miniterm) + self.fd = sys.stdin.fileno() + self.old = termios.tcgetattr(self.fd) + atexit.register(self.cleanup) + signal.signal(signal.SIGINT, self.sigint) + if sys.version_info < (3, 0): + self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) + else: + self.enc_stdin = sys.stdin + + def setup(self): + new = termios.tcgetattr(self.fd) + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG + new[6][termios.VMIN] = 1 + new[6][termios.VTIME] = 0 + termios.tcsetattr(self.fd, termios.TCSANOW, new) + + def getkey(self): + c = self.enc_stdin.read(1) + if c == unichr(0x7F): + c = unichr(8) # map the BS key (which yields DEL) to backspace + return c + + def cancel(self): + fcntl.ioctl(self.fd, termios.TIOCSTI, b"\0") + + def cleanup(self): + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) + + def sigint(self, sig, frame): + """signal handler for a clean exit on SIGINT""" + self.miniterm.stop() + self.cancel() + +else: + raise NotImplementedError( + "Sorry no implementation for your platform ({}) available.".format(sys.platform) + ) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +class Transform(object): + """do-nothing: forward all data unchanged""" + + def rx(self, text): + """text received from serial port""" + return text + + def tx(self, text): + """text to be sent to serial port""" + return text + + def echo(self, text): + """text to be sent but displayed on console""" + return text + + +class CRLF(Transform): + """ENTER sends CR+LF""" + + def tx(self, text): + return text.replace("\n", "\r\n") + + +class CR(Transform): + """ENTER sends CR""" + + def rx(self, text): + return text.replace("\r", "\n") + + def tx(self, text): + return text.replace("\n", "\r") + + +class LF(Transform): + """ENTER sends LF""" + + +class NoTerminal(Transform): + """remove typical terminal control codes from input""" + + REPLACEMENT_MAP = dict( + (x, 0x2400 + x) for x in range(32) if unichr(x) not in "\r\n\b\t" + ) + REPLACEMENT_MAP.update( + { + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + } + ) + + def rx(self, text): + return text.translate(self.REPLACEMENT_MAP) + + echo = rx + + +class NoControls(NoTerminal): + """Remove all control codes, incl. CR+LF""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) + REPLACEMENT_MAP.update( + { + 0x20: 0x2423, # visual space + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + } + ) + + +class Printable(Transform): + """Show decimal code for all non-ASCII characters and replace most control codes""" + + def rx(self, text): + r = [] + for c in text: + if " " <= c < "\x7f" or c in "\r\n\b\t": + r.append(c) + elif c < " ": + r.append(unichr(0x2400 + ord(c))) + else: + r.extend(unichr(0x2080 + ord(d) - 48) for d in "{:d}".format(ord(c))) + r.append(" ") + return "".join(r) + + echo = rx + + +class Colorize(Transform): + """Apply different colors for received and echo""" + + def __init__(self): + # XXX make it configurable, use colorama? + self.input_color = "\x1b[37m" + self.echo_color = "\x1b[31m" + + def rx(self, text): + return self.input_color + text + + def echo(self, text): + return self.echo_color + text + + +class DebugIO(Transform): + """Print what is sent and received""" + + def rx(self, text): + sys.stderr.write(" [RX:{!r}] ".format(text)) + sys.stderr.flush() + return text + + def tx(self, text): + sys.stderr.write(" [TX:{!r}] ".format(text)) + sys.stderr.flush() + return text + + +# other ideas: +# - add date/time for each newline +# - insert newline after: a) timeout b) packet end character + +EOL_TRANSFORMATIONS = { + "crlf": CRLF, + "cr": CR, + "lf": LF, +} + +TRANSFORMATIONS = { + "direct": Transform, # no transformation + "default": NoTerminal, + "nocontrol": NoControls, + "printable": Printable, + "colorize": Colorize, + "debug": DebugIO, +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def ask_for_port(): + """\ + Show a list of ports and ask the user for a choice. To make selection + easier on systems with long device names, also allow the input of an + index. + """ + sys.stderr.write("\n--- Available ports:\n") + ports = [] + for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): + sys.stderr.write("--- {:2}: {:20} {!r}\n".format(n, port, desc)) + ports.append(port) + while True: + sys.stderr.write("--- Enter port index or full name: ") + port = raw_input("") + try: + index = int(port) - 1 + if not 0 <= index < len(ports): + sys.stderr.write("--- Invalid index!\n") + continue + except ValueError: + pass + else: + port = ports[index] + return port + + +class Miniterm(object): + """\ + Terminal application. Copy data from serial port to console and vice versa. + Handle special keys from the console to show menu etc. + """ + + def __init__(self, serial_instance, echo=False, eol="crlf", filters=()): + self.console = Console(self) + self.serial = serial_instance + self.echo = True + self.raw = False + self.input_encoding = "UTF-8" + self.output_encoding = "UTF-8" + self.eol = eol + self.filters = filters + self.update_transformations() + self.exit_character = unichr(0x1D) # GS/CTRL+] + self.menu_character = unichr(0x14) # Menu: CTRL+T + self.alive = None + self._reader_alive = None + self.receiver_thread = None + self.rx_decoder = None + self.tx_decoder = None + self.tx_encoder = None + self.no_write = False + self.remove_no_write = False + + def _start_reader(self): + """Start reader thread""" + self._reader_alive = True + # start serial->console thread + self.receiver_thread = threading.Thread(target=self.reader, name="rx") + self.receiver_thread.daemon = True + self.receiver_thread.start() + + def _stop_reader(self): + """Stop reader thread only, wait for clean exit of thread""" + self._reader_alive = False + if hasattr(self.serial, "cancel_read"): + self.serial.cancel_read() + self.receiver_thread.join() + + def start(self): + """start worker threads""" + self.alive = True + self._start_reader() + # enter console->serial loop + self.transmitter_thread = threading.Thread(target=self.writer, name="tx") + self.transmitter_thread.daemon = True + self.transmitter_thread.start() + self.console.setup() + + def stop(self): + """set flag to stop worker threads""" + self.alive = False + + def join(self, transmit_only=False): + """wait for worker threads to terminate""" + self.transmitter_thread.join() + if not transmit_only: + if hasattr(self.serial, "cancel_read"): + self.serial.cancel_read() + self.receiver_thread.join() + + def close(self): + self.serial.close() + + def update_transformations(self): + """take list of transformation classes and instantiate them for rx and tx""" + transformations = [EOL_TRANSFORMATIONS[self.eol]] + [ + TRANSFORMATIONS[f] for f in self.filters + ] + self.tx_transformations = [t() for t in transformations] + self.rx_transformations = list(reversed(self.tx_transformations)) + + def set_rx_encoding(self, encoding, errors="replace"): + """set encoding for received data""" + self.input_encoding = encoding + self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) + + def set_tx_encoding(self, encoding, errors="replace"): + """set encoding for transmitted data""" + self.output_encoding = encoding + self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) + + def dump_port_settings(self): + """Write current settings to sys.stderr""" + sys.stderr.write( + "\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( + p=self.serial + ) + ) + sys.stderr.write( + "--- RTS: {:8} DTR: {:8} BREAK: {:8}\n".format( + ("active" if self.serial.rts else "inactive"), + ("active" if self.serial.dtr else "inactive"), + ("active" if self.serial.break_condition else "inactive"), + ) + ) + try: + sys.stderr.write( + "--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n".format( + ("active" if self.serial.cts else "inactive"), + ("active" if self.serial.dsr else "inactive"), + ("active" if self.serial.ri else "inactive"), + ("active" if self.serial.cd else "inactive"), + ) + ) + except serial.SerialException: + # on RFC 2217 ports, it can happen if no modem state notification was + # yet received. ignore this error. + pass + sys.stderr.write( + "--- software flow control: {}\n".format( + "active" if self.serial.xonxoff else "inactive" + ) + ) + sys.stderr.write( + "--- hardware flow control: {}\n".format( + "active" if self.serial.rtscts else "inactive" + ) + ) + sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) + sys.stderr.write( + "--- serial output encoding: {}\n".format(self.output_encoding) + ) + sys.stderr.write("--- EOL: {}\n".format(self.eol.upper())) + sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) + + def reader(self): + """loop and copy serial->console""" + data = b"" + while self.alive and self._reader_alive: + # read all that is there or wait for one byte + try: + data2 = self.serial.read(self.serial.in_waiting or 1) + except Exception: + data2 = b"" + if data2: + if self.remove_no_write is True: + self.remove_no_write = False + self.no_write = False + data = data2 + else: + data += data2 + if b"\n" in data: + if self.raw: + self.console.write_bytes(data) + else: + text = self.rx_decoder.decode(data) + for transformation in self.rx_transformations: + text = transformation.rx(text) + if b"m1\n" in data: + self.no_write = True + if self.no_write is False: + self.console.write(text) + data = b"" + + def writer(self): + """\ + Loop and copy console->serial until self.exit_character character is + found. When self.menu_character is found, interpret the next key + locally. + """ + menu_active = False + try: + while self.alive: + try: + c = self.console.getkey() + self.remove_no_write = True + except KeyboardInterrupt: + c = "\x03" + if not self.alive: + break + if menu_active: + self.handle_menu_key(c) + menu_active = False + elif c == self.menu_character: + menu_active = True # next char will be for menu + elif c == self.exit_character: + self.stop() # exit app + break + else: + # ~ if self.raw: + text = c + for transformation in self.tx_transformations: + text = transformation.tx(text) + self.serial.write(self.tx_encoder.encode(text)) + if self.echo: + echo_text = c + for transformation in self.tx_transformations: + echo_text = transformation.echo(echo_text) + self.console.write(echo_text) + except Exception: + self.alive = False + raise + + def handle_menu_key(self, c): + """Implement a simple menu / settings""" + if c == self.menu_character or c == self.exit_character: + # Menu/exit character again -> send itself + self.serial.write(self.tx_encoder.encode(c)) + if self.echo: + self.console.write(c) + elif c == "\x15": # CTRL+U -> upload file + self.upload_file() + elif c in "\x08hH?": # CTRL+H, h, H, ? -> Show help + sys.stderr.write(self.get_help_text()) + elif c == "\x12": # CTRL+R -> Toggle RTS + self.serial.rts = not self.serial.rts + sys.stderr.write( + "--- RTS {} ---\n".format("active" if self.serial.rts else "inactive") + ) + elif c == "\x04": # CTRL+D -> Toggle DTR + self.serial.dtr = not self.serial.dtr + sys.stderr.write( + "--- DTR {} ---\n".format("active" if self.serial.dtr else "inactive") + ) + elif c == "\x02": # CTRL+B -> toggle BREAK condition + self.serial.break_condition = not self.serial.break_condition + sys.stderr.write( + "--- BREAK {} ---\n".format( + "active" if self.serial.break_condition else "inactive" + ) + ) + elif c == "\x05": # CTRL+E -> toggle local echo + self.echo = not self.echo + sys.stderr.write( + "--- local echo {} ---\n".format("active" if self.echo else "inactive") + ) + elif c == "\x06": # CTRL+F -> edit filters + self.change_filter() + elif c == "\x0c": # CTRL+L -> EOL mode + modes = list(EOL_TRANSFORMATIONS) # keys + eol = modes.index(self.eol) + 1 + if eol >= len(modes): + eol = 0 + self.eol = modes[eol] + sys.stderr.write("--- EOL: {} ---\n".format(self.eol.upper())) + self.update_transformations() + elif c == "\x01": # CTRL+A -> set encoding + self.change_encoding() + elif c == "\x09": # CTRL+I -> info + self.dump_port_settings() + # ~ elif c == '\x01': # CTRL+A -> cycle escape mode + # ~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode + elif c in "pP": # P -> change port + self.change_port() + elif c in "zZ": # S -> suspend / open port temporarily + self.suspend_port() + elif c in "bB": # B -> change baudrate + self.change_baudrate() + elif c == "8": # 8 -> change to 8 bits + self.serial.bytesize = serial.EIGHTBITS + self.dump_port_settings() + elif c == "7": # 7 -> change to 8 bits + self.serial.bytesize = serial.SEVENBITS + self.dump_port_settings() + elif c in "eE": # E -> change to even parity + self.serial.parity = serial.PARITY_EVEN + self.dump_port_settings() + elif c in "oO": # O -> change to odd parity + self.serial.parity = serial.PARITY_ODD + self.dump_port_settings() + elif c in "mM": # M -> change to mark parity + self.serial.parity = serial.PARITY_MARK + self.dump_port_settings() + elif c in "sS": # S -> change to space parity + self.serial.parity = serial.PARITY_SPACE + self.dump_port_settings() + elif c in "nN": # N -> change to no parity + self.serial.parity = serial.PARITY_NONE + self.dump_port_settings() + elif c == "1": # 1 -> change to 1 stop bits + self.serial.stopbits = serial.STOPBITS_ONE + self.dump_port_settings() + elif c == "2": # 2 -> change to 2 stop bits + self.serial.stopbits = serial.STOPBITS_TWO + self.dump_port_settings() + elif c == "3": # 3 -> change to 1.5 stop bits + self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE + self.dump_port_settings() + elif c in "xX": # X -> change software flow control + self.serial.xonxoff = c == "X" + self.dump_port_settings() + elif c in "rR": # R -> change hardware flow control + self.serial.rtscts = c == "R" + self.dump_port_settings() + elif c in "qQ": + self.stop() # Q -> exit app + else: + sys.stderr.write( + "--- unknown menu character {} --\n".format(key_description(c)) + ) + + def upload_file(self): + """Ask user for filename and send its contents""" + sys.stderr.write("\n--- File to upload: ") + sys.stderr.flush() + with self.console: + filename = sys.stdin.readline().rstrip("\r\n") + if filename: + try: + with open(filename, "rb") as f: + sys.stderr.write("--- Sending file {} ---\n".format(filename)) + while True: + block = f.read(1024) + if not block: + break + self.serial.write(block) + # Wait for output buffer to drain. + self.serial.flush() + sys.stderr.write(".") # Progress indicator. + sys.stderr.write("\n--- File {} sent ---\n".format(filename)) + except IOError as e: + sys.stderr.write( + "--- ERROR opening file {}: {} ---\n".format(filename, e) + ) + + def change_filter(self): + """change the i/o transformations""" + sys.stderr.write("\n--- Available Filters:\n") + sys.stderr.write( + "\n".join( + "--- {:<10} = {.__doc__}".format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()) + ) + ) + sys.stderr.write( + "\n--- Enter new filter name(s) [{}]: ".format(" ".join(self.filters)) + ) + with self.console: + new_filters = sys.stdin.readline().lower().split() + if new_filters: + for f in new_filters: + if f not in TRANSFORMATIONS: + sys.stderr.write("--- unknown filter: {!r}\n".format(f)) + break + else: + self.filters = new_filters + self.update_transformations() + sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) + + def change_encoding(self): + """change encoding on the serial port""" + sys.stderr.write( + "\n--- Enter new encoding name [{}]: ".format(self.input_encoding) + ) + with self.console: + new_encoding = sys.stdin.readline().strip() + if new_encoding: + try: + codecs.lookup(new_encoding) + except LookupError: + sys.stderr.write("--- invalid encoding name: {}\n".format(new_encoding)) + else: + self.set_rx_encoding(new_encoding) + self.set_tx_encoding(new_encoding) + sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) + sys.stderr.write( + "--- serial output encoding: {}\n".format(self.output_encoding) + ) + + def change_baudrate(self): + """change the baudrate""" + sys.stderr.write("\n--- Baudrate: ") + sys.stderr.flush() + with self.console: + backup = self.serial.baudrate + try: + self.serial.baudrate = int(sys.stdin.readline().strip()) + except ValueError as e: + sys.stderr.write("--- ERROR setting baudrate: {} ---\n".format(e)) + self.serial.baudrate = backup + else: + self.dump_port_settings() + + def change_port(self): + """Have a conversation with the user to change the serial port""" + with self.console: + try: + port = ask_for_port() + except KeyboardInterrupt: + port = None + if port and port != self.serial.port: + # reader thread needs to be shut down + self._stop_reader() + # save settings + settings = self.serial.getSettingsDict() + try: + new_serial = serial.serial_for_url(port, do_not_open=True) + # restore settings and open + new_serial.applySettingsDict(settings) + new_serial.rts = self.serial.rts + new_serial.dtr = self.serial.dtr + new_serial.open() + new_serial.break_condition = self.serial.break_condition + except Exception as e: + sys.stderr.write("--- ERROR opening new port: {} ---\n".format(e)) + new_serial.close() + else: + self.serial.close() + self.serial = new_serial + sys.stderr.write( + "--- Port changed to: {} ---\n".format(self.serial.port) + ) + # and restart the reader thread + self._start_reader() + + def suspend_port(self): + """\ + open port temporarily, allow reconnect, exit and port change to get + out of the loop + """ + # reader thread needs to be shut down + self._stop_reader() + self.serial.close() + sys.stderr.write("\n--- Port closed: {} ---\n".format(self.serial.port)) + do_change_port = False + while not self.serial.is_open: + sys.stderr.write( + "--- Quit: {exit} | p: port change | any other key to reconnect ---\n".format( + exit=key_description(self.exit_character) + ) + ) + k = self.console.getkey() + if k == self.exit_character: + self.stop() # exit app + break + elif k in "pP": + do_change_port = True + break + try: + self.serial.open() + except Exception as e: + sys.stderr.write("--- ERROR opening port: {} ---\n".format(e)) + if do_change_port: + self.change_port() + else: + # and restart the reader thread + self._start_reader() + sys.stderr.write("--- Port opened: {} ---\n".format(self.serial.port)) + + def get_help_text(self): + """return the help text""" + # help text, starts with blank line! + return """ +--- pySerial ({version}) - miniterm - help +--- +--- {exit:8} Exit program (alias {menu} Q) +--- {menu:8} Menu escape key, followed by: +--- Menu keys: +--- {menu:7} Send the menu character itself to remote +--- {exit:7} Send the exit character itself to remote +--- {info:7} Show info +--- {upload:7} Upload file (prompt will be shown) +--- {repr:7} encoding +--- {filter:7} edit filters +--- Toggles: +--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK +--- {echo:7} echo {eol:7} EOL +--- +--- Port settings ({menu} followed by the following): +--- p change port +--- 7 8 set data bits +--- N E O S M change parity (None, Even, Odd, Space, Mark) +--- 1 2 3 set stop bits (1, 2, 1.5) +--- b change baud rate +--- x X disable/enable software flow control +--- r R disable/enable hardware flow control +""".format( + version=getattr(serial, "VERSION", "unknown version"), + exit=key_description(self.exit_character), + menu=key_description(self.menu_character), + rts=key_description("\x12"), + dtr=key_description("\x04"), + brk=key_description("\x02"), + echo=key_description("\x05"), + info=key_description("\x09"), + upload=key_description("\x15"), + repr=key_description("\x01"), + filter=key_description("\x06"), + eol=key_description("\x0c"), + ) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# default args can be used to override when calling main() from an other script +# e.g to create a miniterm-my-device.py +def main( + default_port=None, + default_baudrate=9600, + default_rts=None, + default_dtr=None, + serial_instance=None, +): + """Command line tool, entry point""" + + import argparse + + parser = argparse.ArgumentParser( + description="Miniterm - A simple terminal program for the serial port." + ) + + parser.add_argument( + "port", + nargs="?", + help='serial port name ("-" to show port list)', + default=default_port, + ) + + parser.add_argument( + "baudrate", + nargs="?", + type=int, + help="set baud rate, default: %(default)s", + default=default_baudrate, + ) + + group = parser.add_argument_group("port settings") + + group.add_argument( + "--parity", + choices=["N", "E", "O", "S", "M"], + type=lambda c: c.upper(), + help="set parity, one of {N E O S M}, default: N", + default="N", + ) + + group.add_argument( + "--rtscts", + action="store_true", + help="enable RTS/CTS flow control (default off)", + default=False, + ) + + group.add_argument( + "--xonxoff", + action="store_true", + help="enable software flow control (default off)", + default=False, + ) + + group.add_argument( + "--rts", + type=int, + help="set initial RTS line state (possible values: 0, 1)", + default=default_rts, + ) + + group.add_argument( + "--dtr", + type=int, + help="set initial DTR line state (possible values: 0, 1)", + default=default_dtr, + ) + + group.add_argument( + "--non-exclusive", + dest="exclusive", + action="store_false", + help="disable locking for native ports", + default=True, + ) + + group.add_argument( + "--ask", + action="store_true", + help="ask again for port when open fails", + default=False, + ) + + group = parser.add_argument_group("data handling") + + group.add_argument( + "-e", + "--echo", + action="store_true", + help="enable local echo (default off)", + default=False, + ) + + group.add_argument( + "--encoding", + dest="serial_port_encoding", + metavar="CODEC", + help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s", + default="UTF-8", + ) + + group.add_argument( + "-f", + "--filter", + action="append", + metavar="NAME", + help="add text transformation", + default=[], + ) + + group.add_argument( + "--eol", + choices=["CR", "LF", "CRLF"], + type=lambda c: c.upper(), + help="end of line mode", + default="CRLF", + ) + + group.add_argument( + "--raw", + action="store_true", + help="Do no apply any encodings/transformations", + default=False, + ) + + group = parser.add_argument_group("hotkeys") + + group.add_argument( + "--exit-char", + type=int, + metavar="NUM", + help="Unicode of special character that is used to exit the application, default: %(default)s", + default=0x1D, + ) # GS/CTRL+] + + group.add_argument( + "--menu-char", + type=int, + metavar="NUM", + help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s", + default=0x14, + ) # Menu: CTRL+T + + group = parser.add_argument_group("diagnostics") + + group.add_argument( + "-q", + "--quiet", + action="store_true", + help="suppress non-error messages", + default=False, + ) + + group.add_argument( + "--develop", + action="store_true", + help="show Python traceback on error", + default=False, + ) + + args = parser.parse_args() + + if args.menu_char == args.exit_char: + parser.error("--exit-char can not be the same as --menu-char") + + if args.filter: + if "help" in args.filter: + sys.stderr.write("Available filters:\n") + sys.stderr.write( + "\n".join( + "{:<10} = {.__doc__}".format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()) + ) + ) + sys.stderr.write("\n") + sys.exit(1) + filters = args.filter + else: + filters = ["default"] + + while serial_instance is None: + # no port given on command line -> ask user now + if args.port is None or args.port == "-": + try: + args.port = ask_for_port() + except KeyboardInterrupt: + sys.stderr.write("\n") + parser.error("user aborted and port is not given") + else: + if not args.port: + parser.error("port is not given") + try: + serial_instance = serial.serial_for_url( + args.port, + args.baudrate, + parity=args.parity, + rtscts=args.rtscts, + xonxoff=args.xonxoff, + do_not_open=True, + ) + + if not hasattr(serial_instance, "cancel_read"): + # enable timeout for alive flag polling if cancel_read is not available + serial_instance.timeout = 1 + + if args.dtr is not None: + if not args.quiet: + sys.stderr.write( + "--- forcing DTR {}\n".format( + "active" if args.dtr else "inactive" + ) + ) + serial_instance.dtr = args.dtr + if args.rts is not None: + if not args.quiet: + sys.stderr.write( + "--- forcing RTS {}\n".format( + "active" if args.rts else "inactive" + ) + ) + serial_instance.rts = args.rts + + if isinstance(serial_instance, serial.Serial): + serial_instance.exclusive = args.exclusive + + serial_instance.open() + except serial.SerialException as e: + sys.stderr.write("could not open port {!r}: {}\n".format(args.port, e)) + if args.develop: + raise + if not args.ask: + sys.exit(1) + else: + args.port = "-" + else: + break + + miniterm = Miniterm( + serial_instance, echo=args.echo, eol=args.eol.lower(), filters=filters + ) + miniterm.exit_character = unichr(args.exit_char) + miniterm.menu_character = unichr(args.menu_char) + miniterm.raw = args.raw + miniterm.set_rx_encoding(args.serial_port_encoding) + miniterm.set_tx_encoding(args.serial_port_encoding) + + if not args.quiet: + sys.stderr.write( + "--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n".format( + p=miniterm.serial + ) + ) + sys.stderr.write( + "--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n".format( + key_description(miniterm.exit_character), + key_description(miniterm.menu_character), + key_description(miniterm.menu_character), + key_description("\x08"), + ) + ) + sys.stderr.write( + "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format( # noqa: F522 + p=miniterm.serial + ) + ) + sys.stderr.write( + "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format( # noqa: F522 + p=miniterm.serial + ) + ) + + miniterm.start() + try: + miniterm.join(True) + except KeyboardInterrupt: + pass + if not args.quiet: + sys.stderr.write("\n--- exit ---\n") + miniterm.join() + miniterm.close() + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + main() diff --git a/etc/dbus-serialbattery/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py similarity index 57% rename from etc/dbus-serialbattery/jkbms.py rename to etc/dbus-serialbattery/bms/jkbms.py index 86d8716b..5e09f8e2 100644 --- a/etc/dbus-serialbattery/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -3,6 +3,7 @@ from utils import is_bit_set, read_serial_data, logger import utils from struct import unpack_from +from re import sub class Jkbms(Battery): @@ -18,6 +19,9 @@ def __init__(self, port, baud, address): command_status = b"\x4E\x57\x00\x13\x00\x00\x00\x00\x06\x03\x00\x00\x00\x00\x00\x00\x68\x00\x00\x01\x29" 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 try: return self.read_status_data() except Exception as err: @@ -28,8 +32,6 @@ 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 = 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 @@ -81,18 +83,20 @@ def read_status_data(self): unpack_from(">xH", celldata, c * 3 + 1)[0] / 1000 ) + # MOSFET temperature + offset = cellbyte_count + 3 + temp_mos = unpack_from(">H", self.get_data(status_data, b"\x80", offset, 2))[0] + self.to_temp(0, temp_mos if temp_mos < 99 else (100 - temp_mos)) + + # Temperature sensors offset = cellbyte_count + 6 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] self.to_temp(1, temp1 if temp1 < 99 else (100 - temp1)) self.to_temp(2, temp2 if temp2 < 99 else (100 - temp2)) - # MOSFET temperature - offset = cellbyte_count + 3 - temp_mos = unpack_from(">H", self.get_data(status_data, b"\x80", offset, 2))[0] - self.to_temp("mos", temp_mos if temp_mos < 99 else (100 - temp_mos)) - offset = cellbyte_count + 12 voltage = unpack_from(">H", self.get_data(status_data, b"\x83", offset, 2))[0] self.voltage = voltage / 100 @@ -105,6 +109,18 @@ def read_status_data(self): else (current - self.CURRENT_ZERO_CONSTANT) / 100 ) + # Continued discharge current + offset = cellbyte_count + 66 + self.max_battery_discharge_current = float( + unpack_from(">H", self.get_data(status_data, b"\x97", offset, 2))[0] + ) + + # Continued charge current + offset = cellbyte_count + 72 + self.max_battery_charge_current = float( + unpack_from(">H", self.get_data(status_data, b"\x99", offset, 2))[0] + ) + offset = cellbyte_count + 18 self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0] @@ -124,56 +140,157 @@ def read_status_data(self): 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] ) + offset = cellbyte_count + 84 + self.to_balance_bits( + unpack_from(">B", self.get_data(status_data, b"\x9D", offset, 1))[0] + ) + + # User Private Data filed in APP offset = cellbyte_count + 155 - self.production = unpack_from( - ">8s", self.get_data(status_data, b"\xB4", offset, 8) - )[0].decode() + self.production = sub( + " +", + " ", + ( + unpack_from(">8s", self.get_data(status_data, b"\xB4", offset, 8))[0] + .decode() + .replace("\x00", " ") + .strip() + ), + ) + self.production = self.production if self.production != "Input Us" else None + offset = cellbyte_count + 174 self.version = unpack_from( ">15s", self.get_data(status_data, b"\xB7", offset, 15) )[0].decode() + offset = cellbyte_count + 197 + self.unique_identifier = sub( + " +", + " ", + ( + unpack_from(">24s", self.get_data(status_data, b"\xBA", offset, 24))[0] + .decode() + .replace("\x00", " ") + .replace("Input Userda", "") + .strip() + ), + ) + + # show wich cells are balancing + if self.get_min_cell() is not None and self.get_max_cell() is not None: + for c in range(self.cell_count): + if self.balancing and ( + self.get_min_cell() == c or self.get_max_cell() == c + ): + self.cells[c].balance = True + else: + self.cells[c].balance = False + # logger.info(self.hardware_version) return True def to_fet_bits(self, byte_data): - 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]) + tmp = bin(byte_data)[2:].rjust(3, utils.zero_char) + self.charge_fet = is_bit_set(tmp[2]) + self.discharge_fet = is_bit_set(tmp[1]) + self.balancing = is_bit_set(tmp[0]) + + def to_balance_bits(self, byte_data): + tmp = bin(byte_data)[2:] + self.balance_fet = is_bit_set(tmp) + + def get_balancing(self): + return 1 if self.balancing else 0 + + def get_min_cell(self): + min_voltage = 9999 + min_cell = None + 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 + ): + min_voltage = self.cells[c].voltage + min_cell = c + return min_cell + + def get_max_cell(self): + max_voltage = 0 + max_cell = None + 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 + ): + max_voltage = self.cells[c].voltage + max_cell = c + return max_cell def to_protection_bits(self, byte_data): + """ + Bit 0: Low capacity alarm: 1 warning only, 0 nomal -> OK + Bit 1: MOS tube overtemperature alarm: 1 alarm, 0 nomal -> OK + Bit 2: Charge over voltage alarm: 1 alarm, 0 nomal -> OK + Bit 3: Discharge undervoltage alarm: 1 alarm, 0 nomal -> OK + Bit 4: Battery overtemperature alarm: 1 alarm, 0 nomal -> OK + Bit 5: Charge overcurrent alarm: 1 alarm, 0 nomal -> OK + Bit 6: discharge over current alarm: 1 alarm, 0 nomal -> OK + Bit 7: core differential pressure alarm: 1 alarm, 0 nomal -> OK + Bit 8: overtemperature alarm in the battery box: 1 alarm, 0 nomal -> OK + Bit 9: Battery low temperature alarm: 1 alarm, 0 nomal -> OK + Bit 10: Unit overvoltage: 1 alarm, 0 nomal -> OK + Bit 11: Unit undervoltage: 1 alarm, 0 nomal -> OK + Bit 12:309_A protection: 1 alarm, 0 nomal + Bit 13:309_B protection: 1 alarm, 0 nomal + """ pos = 13 tmp = bin(byte_data)[15 - pos :].rjust(pos + 1, utils.zero_char) # logger.debug(tmp) + + # low capacity alarm 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 + # MOSFET temperature alarm + self.protection.temp_high_internal = 2 if is_bit_set(tmp[pos - 1]) else 0 + # charge over voltage alarm self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0 + # discharge under voltage alarm self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0 + # charge overcurrent alarm self.protection.current_over = 1 if is_bit_set(tmp[pos - 5]) else 0 + # discharge over current alarm self.protection.current_under = 1 if is_bit_set(tmp[pos - 6]) else 0 + # core differential pressure alarm OR unit overvoltage alarm 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 = ( + # unit undervoltage alarm + self.protection.voltage_cell_low = 1 if is_bit_set(tmp[pos - 11]) else 0 + # battery overtemperature alarm OR overtemperature alarm in the battery box + alarm_temp_high = ( 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 ) + # battery low temperature alarm + alarm_temp_low = 1 if is_bit_set(tmp[pos - 9]) else 0 + # check if low/high temp alarm arise during charging + self.protection.temp_high_charge = ( + 1 if self.current > 0 and alarm_temp_high == 1 else 0 + ) self.protection.temp_low_charge = ( - 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + 1 if self.current > 0 and alarm_temp_low == 1 else 0 ) + # check if low/high temp alarm arise during discharging self.protection.temp_high_discharge = ( - 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + 1 if self.current <= 0 and alarm_temp_high == 1 else 0 ) self.protection.temp_low_discharge = ( - 1 if is_bit_set(tmp[pos - 4]) or is_bit_set(tmp[pos - 8]) else 0 + 1 if self.current <= 0 and alarm_temp_low == 1 else 0 ) def read_serial_data_jkbms(self, command: str) -> bool: @@ -200,7 +317,7 @@ def read_serial_data_jkbms(self, command: str) -> bool: s = sum(data[0:-4]) if start == 0x4E57 and end == 0x68 and s == crc_lo: - return data[10 : length - 19] + return data[10 : length - 7] elif s != crc_lo: logger.error( "CRC checksum mismatch: Expected 0x%04x, Got 0x%04x" % (crc_lo, s) diff --git a/etc/dbus-serialbattery/lifepower.py b/etc/dbus-serialbattery/bms/lifepower.py similarity index 90% rename from etc/dbus-serialbattery/lifepower.py rename to etc/dbus-serialbattery/bms/lifepower.py index cc3058c0..84c93a37 100644 --- a/etc/dbus-serialbattery/lifepower.py +++ b/etc/dbus-serialbattery/bms/lifepower.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import read_serial_data, logger +import utils +from struct import unpack_from import re @@ -24,14 +25,21 @@ 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 - return self.read_status_data() + result = False + try: + result = self.read_status_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False + + return result 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_current = MAX_BATTERY_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_current = utils.MAX_BATTERY_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT hardware_version = self.read_serial_data_eg4(self.command_hardware_version) if hardware_version: # I get some characters that I'm not able to figure out the encoding, probably chinese so I discard it @@ -89,8 +97,8 @@ def read_status_data(self): # Cells self.cell_count = len(groups[0]) - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.cells = [Cell(True) for _ in range(0, self.cell_count)] for i, cell in enumerate(self.cells): diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py similarity index 82% rename from etc/dbus-serialbattery/lltjbd.py rename to etc/dbus-serialbattery/bms/lltjbd.py index 6bc26c6e..a74697cc 100644 --- a/etc/dbus-serialbattery/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import is_bit_set, read_serial_data, logger +import utils +from struct import unpack_from +import struct class LltJbdProtection(Protection): @@ -59,18 +61,25 @@ def __init__(self, port, baud, address): 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. + # Return True if success, False for failure result = False try: result = self.read_hardware_data() - except: - pass + # get first data to show in startup log + if result: + self.refresh_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result def get_settings(self): self.read_gen_data() - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT return True def refresh_data(self): @@ -79,7 +88,7 @@ def refresh_data(self): return result def to_protection_bits(self, byte_data): - tmp = bin(byte_data)[2:].rjust(13, zero_char) + tmp = bin(byte_data)[2:].rjust(13, utils.zero_char) self.protection.voltage_high = 2 if is_bit_set(tmp[10]) else 0 self.protection.voltage_low = 2 if is_bit_set(tmp[9]) else 0 @@ -92,7 +101,11 @@ def to_protection_bits(self, byte_data): # 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 + 2 + if self.soc < utils.SOC_LOW_ALARM + else 1 + if self.soc < utils.SOC_LOW_WARNING + else 0 ) # extra protection flags for LltJbd @@ -107,17 +120,17 @@ def to_cell_bits(self, byte_data, byte_data_high): for c in self.cells: self.cells.remove(c) # get up to the first 16 cells - tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), zero_char) + tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), utils.zero_char) for bit in reversed(tmp): 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, utils.zero_char) for bit in reversed(tmp): self.cells.append(Cell(is_bit_set(bit))) 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]) @@ -145,19 +158,20 @@ def read_gen_data(self): ) = unpack_from(">HhHHHHhHHBBBBB", gen_data) self.voltage = voltage / 100 self.current = current / 100 - self.soc = 100 * capacity_remain / capacity + self.soc = round(100 * capacity_remain / capacity, 2) self.capacity_remain = capacity_remain / 100 self.capacity = capacity / 100 self.to_cell_bits(balance, balance2) self.version = float(str(version >> 4 & 0x0F) + "." + str(version & 0x0F)) self.to_fet_bits(fet) self.to_protection_bits(protection) - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count + # 0 = MOS, 1 = temp 1, 2 = temp 2 for t in range(self.temp_sensors): temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0] - self.to_temp(t + 1, kelvin_to_celsius(temp1 / 10)) + self.to_temp(t, utils.kelvin_to_celsius(temp1 / 10)) return True diff --git a/etc/dbus-serialbattery/mnb.py b/etc/dbus-serialbattery/bms/mnb.py similarity index 91% rename from etc/dbus-serialbattery/mnb.py rename to etc/dbus-serialbattery/bms/mnb.py index 8e91bd5b..1ad1126e 100644 --- a/etc/dbus-serialbattery/mnb.py +++ b/etc/dbus-serialbattery/bms/mnb.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- + +# disable MNB battery by default +# https://github.com/Louisvdw/dbus-serialbattery/commit/65241cbff36feb861ff43dbbcfb2b495f14a01ce +# remove duplicate MNB lines +# https://github.com/Louisvdw/dbus-serialbattery/commit/23afec33c2fd87fd4d4c53516f0a25f290643c82 + from battery import Protection, Battery, Cell -from struct import * +from utils import logger +from bms.mnb_utils_max17853 import data_cycle, init_max -# from test_max17853 import *#{these two lines are mutually} -from util_max17853 import * # {exclusive. use test for testing} +# from struct import * +# from bms.mnb_test_max17853 import * # use test for testing class MNBProtection(Protection): @@ -90,8 +97,9 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result diff --git a/etc/dbus-serialbattery/test_max17853.py b/etc/dbus-serialbattery/bms/mnb_test_max17853.py similarity index 100% rename from etc/dbus-serialbattery/test_max17853.py rename to etc/dbus-serialbattery/bms/mnb_test_max17853.py diff --git a/etc/dbus-serialbattery/util_max17853.py b/etc/dbus-serialbattery/bms/mnb_utils_max17853.py similarity index 100% rename from etc/dbus-serialbattery/util_max17853.py rename to etc/dbus-serialbattery/bms/mnb_utils_max17853.py diff --git a/etc/dbus-serialbattery/renogy.py b/etc/dbus-serialbattery/bms/renogy.py similarity index 80% rename from etc/dbus-serialbattery/renogy.py rename to etc/dbus-serialbattery/bms/renogy.py index 6f1c6c6f..acfe2335 100644 --- a/etc/dbus-serialbattery/renogy.py +++ b/etc/dbus-serialbattery/bms/renogy.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * +from battery import Battery, Cell +from utils import read_serial_data, unpack_from, logger +import utils +from struct import unpack import struct @@ -16,7 +18,8 @@ def __init__(self, port, baud, address): LENGTH_CHECK = 4 LENGTH_POS = 2 - # command bytes [Address field][Function code (03 = Read register)][Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)] + # 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 @@ -44,9 +47,12 @@ def test_connection(self): result = False try: result = self.read_gen_data() - except: - logger.exception("Unexpected exception encountered") - pass + # get first data to show in startup log + if result: + self.refresh_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -54,11 +60,11 @@ 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_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.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_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count return True def refresh_data(self): @@ -140,12 +146,30 @@ def read_cell_data(self): return True def read_temp_data(self): - temp1 = self.read_serial_data_renogy(self.command_bms_temp1) - temp2 = self.read_serial_data_renogy(self.command_bms_temp2) + # Check to see how many Enviromental Temp Sensors this battery has, it may have none. + num_env_temps = self.read_serial_data_renogy(self.command_env_temp_count) + logger.info("Number of Enviromental Sensors = %s", num_env_temps) + + if num_env_temps == 0: + return False + + if num_env_temps == 1: + temp1 = self.read_serial_data_renogy(self.command_env_temp1) + if temp1 is False: return False - self.temp1 = unpack(">H", temp1)[0] / 10 - self.temp2 = unpack(">H", temp2)[0] / 10 + else: + self.temp1 = unpack(">H", temp1)[0] / 10 + logger.info("temp1 = %s °C", temp1) + + if num_env_temps == 2: + temp2 = self.read_serial_data_renogy(self.command_env_temp2) + + if temp2 is False: + return False + else: + self.temp2 = unpack(">H", temp2)[0] / 10 + logger.info("temp2 = %s °C", temp2) return True @@ -185,7 +209,7 @@ def read_serial_data_renogy(self, command): return False start, flag, length = unpack_from("BBB", data) - checksum = unpack_from(">H", data, length + 3) + # checksum = unpack_from(">H", data, length + 3) if flag == 3: return data[3 : length + 3] diff --git a/etc/dbus-serialbattery/revov.py b/etc/dbus-serialbattery/bms/revov.py similarity index 95% rename from etc/dbus-serialbattery/revov.py rename to etc/dbus-serialbattery/bms/revov.py index d3fc2563..17764989 100755 --- a/etc/dbus-serialbattery/revov.py +++ b/etc/dbus-serialbattery/bms/revov.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- + +# Deprecate Revov driver - replaced by LifePower +# https://github.com/Louisvdw/dbus-serialbattery/pull/353/commits/c3ac9558fc86b386e5a6aefb313408165c86d240 + from battery import Protection, Battery, Cell from utils import * from struct import * import struct - # Author: L Sheed # Date: 3 May 2022 # Version 0.1.3 @@ -51,8 +54,12 @@ def test_connection(self): result = False try: result = self.read_gen_data() - except: - pass + # get first data to show in startup log + if result: + self.refresh_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result diff --git a/etc/dbus-serialbattery/seplos.py b/etc/dbus-serialbattery/bms/seplos.py similarity index 99% rename from etc/dbus-serialbattery/seplos.py rename to etc/dbus-serialbattery/bms/seplos.py index c9519fd0..083d6bc4 100644 --- a/etc/dbus-serialbattery/seplos.py +++ b/etc/dbus-serialbattery/bms/seplos.py @@ -5,7 +5,6 @@ import serial - class Seplos(Battery): def __init__(self, port, baud, address=0x00): super(Seplos, self).__init__(port, baud, address) diff --git a/etc/dbus-serialbattery/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py similarity index 91% rename from etc/dbus-serialbattery/sinowealth.py rename to etc/dbus-serialbattery/bms/sinowealth.py index f5021ff5..7a9d9fdb 100755 --- a/etc/dbus-serialbattery/sinowealth.py +++ b/etc/dbus-serialbattery/bms/sinowealth.py @@ -1,7 +1,12 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * + +# disable Sinowealth by default as it causes other issues but can be enabled manually +# https://github.com/Louisvdw/dbus-serialbattery/commit/7aab4c850a5c8d9c205efefc155fe62bb527da8e + +from battery import Battery, Cell +from utils import kelvin_to_celsius, read_serial_data, logger +import utils +from struct import unpack_from class Sinowealth(Battery): @@ -33,25 +38,30 @@ def __init__(self, port, baud, address): LENGTH_POS = 0 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 result = False try: result = self.read_status_data() result = result and self.read_remaining_capacity() result = result and self.read_pack_config_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False + return result 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 + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT if self.cell_count is None: 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.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini new file mode 100644 index 00000000..bff9a681 --- /dev/null +++ b/etc/dbus-serialbattery/config.default.ini @@ -0,0 +1,224 @@ +[DEFAULT] + +; --------- Battery Current limits --------- +MAX_BATTERY_CHARGE_CURRENT = 50.0 +MAX_BATTERY_DISCHARGE_CURRENT = 60.0 + +; --------- 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 = 2.900 +; Max voltage can seen as absorption voltage +MAX_CELL_VOLTAGE = 3.450 +FLOAT_CELL_VOLTAGE = 3.375 + +; --------- 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 = False + +; --------- 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 + +; Specify in seconds how often the linear values should be recalculated +LINEAR_RECALCULATION_EVERY = 60 +; 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 = 5 + + +; --------- 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 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.050V +; Charge voltage control management enable (True/False). +CVCM_ENABLE = True + +; -- 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 = 0.010 +; 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 = 0.080 + +; -- CVL reset based on SoC option (step mode) +; Specify how long the max voltage should be kept, if reached then switch to float voltage +MAX_VOLTAGE_TIME_SEC = 900 +; Specify SoC where CVL limit is reset to max voltage, if value gets below +SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90 + + +; --------- 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 +; Discharge current control management referring to cell-voltage enable (True/False). +DCCM_CV_ENABLE = True + +; 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 = 3.55, 3.50, 3.45, 3.30 +MAX_CHARGE_CURRENT_CV_FRACTION = 0, 0.05, 0.5, 1 + +CELL_VOLTAGES_WHILE_DISCHARGING = 2.70, 2.80, 2.90, 3.10 +MAX_DISCHARGE_CURRENT_CV_FRACTION = 0, 0.1, 0.5, 1 + + +; --------- 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 +; Charge current control management referring to temperature enable (True/False). +DCCM_T_ENABLE = True + +; 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 = 0, 2, 5, 10, 15, 20, 35, 40, 55 +MAX_CHARGE_CURRENT_T_FRACTION = 0, 0.1, 0.2, 0.4, 0.8, 1, 1, 0.4, 0 + +TEMPERATURE_LIMITS_WHILE_DISCHARGING = -20, 0, 5, 10, 15, 45, 55 +MAX_DISCHARGE_CURRENT_T_FRACTION = 0, 0.2, 0.3, 0.4, 1, 1, 0 + + +; --------- 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 +; Discharge current control management enable (True/False). +DCCM_SOC_ENABLE = True + +; Charge current soc limits +CC_SOC_LIMIT1 = 98 +CC_SOC_LIMIT2 = 95 +CC_SOC_LIMIT3 = 91 + +; Charge current limits +CC_CURRENT_LIMIT1_FRACTION = 0.1 +CC_CURRENT_LIMIT2_FRACTION = 0.3 +CC_CURRENT_LIMIT3_FRACTION = 0.5 + +; Discharge current soc limits +DC_SOC_LIMIT1 = 10 +DC_SOC_LIMIT2 = 20 +DC_SOC_LIMIT3 = 30 + +; Discharge current limits +DC_CURRENT_LIMIT1_FRACTION = 0.1 +DC_CURRENT_LIMIT2_FRACTION = 0.3 +DC_CURRENT_LIMIT3_FRACTION = 0.5 + + +; --------- Time-To-Go --------- +; Description: Calculates the time to go shown in the GUI +; Recalculation is done based on TIME_TO_SOC_RECALCULATE_EVERY +TIME_TO_GO_ENABLE = True + +; --------- 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 = +; 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 = 1 +; Specify in seconds how often the TimeToSoc should be recalculated +; Minimum are 5 seconds to prevent CPU overload +TIME_TO_SOC_RECALCULATE_EVERY = 60 +; 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 + + +; --------- Additional settings --------- +; Specify only one BMS type to load else leave empty to try to load all availabe +; LltJbd, Ant, Daly, Daly, Jkbms, Lifepower, Renogy, Renogy, Ecs +BMS_TYPE = + +; Publish the config settings to the dbus path "/Info/Config/" +PUBLISH_CONFIG_VALUES = 1 + +; 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 = 1 + +; Simulate Midpoint graph (True/False). +MIDPOINT_ENABLE = False + + +; Battery temperature +; Specifiy how the battery temperature is assembled +; 0 Get mean of temperature sensor 1 and temperature sensor 2 +; 1 Get only temperature from temperature sensor 1 +; 2 Get only temperature from temperature sensor 2 +TEMP_BATTERY = 0 + +; Temperature sensor 1 name +TEMP_1_NAME = Temp 1 + +; Temperature sensor 2 name +TEMP_2_NAME = Temp 2 + + +; --------- 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 = 20 +SOC_LOW_ALARM = 10 + +; -- Daly settings +; 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 + +; -- ESC GreenMeter and Lipro device settings +GREENMETER_ADDRESS = 1 +LIPRO_START_ADDRESS = 2 +LIPRO_END_ADDRESS = 4 +LIPRO_CELL_COUNT = 15 diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 5b95a531..5f7088d0 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -4,7 +4,8 @@ from time import sleep from dbus.mainloop.glib import DBusGMainLoop -from threading import Thread + +# from threading import Thread ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582 import sys if sys.version_info.major == 2: @@ -19,31 +20,35 @@ from utils import logger import utils from battery import Battery -from lltjbd import LltJbd -from daly import Daly -from ant import Ant -from jkbms import Jkbms -from seplos import Seplos - -# from sinowealth import Sinowealth -from renogy import Renogy -from ecs import Ecs -from lifepower import Lifepower -from hlpdatabms4s import HLPdataBMS4S + +# import battery classes +from bms.daly import Daly +from bms.ecs import Ecs +from bms.hlpdatabms4s import HLPdataBMS4S +from bms.jkbms import Jkbms +from bms.lifepower import Lifepower +from bms.lltjbd import LltJbd +from bms.renogy import Renogy +from bms.seplos import Seplos + +# from bms.ant import Ant +# from bms.mnb import MNB +# from bms.sinowealth import Sinowealth supported_bms_types = [ - {'bms': HLPdataBMS4S, "baud": 9600}, - {"bms": LltJbd, "baud": 9600}, - {"bms": Ant, "baud": 19200}, {"bms": Daly, "baud": 9600, "address": b"\x40"}, {"bms": Daly, "baud": 9600, "address": b"\x80"}, + {"bms": Ecs, "baud": 19200}, + {"bms": HLPdataBMS4S, "baud": 9600}, {"bms": Jkbms, "baud": 115200}, - # {"bms" : Sinowealth}, {"bms": Lifepower, "baud": 9600}, + {"bms": LltJbd, "baud": 9600}, {"bms": Renogy, "baud": 9600, "address": b"\x30"}, {"bms": Renogy, "baud": 9600, "address": b"\xF7"}, - {"bms": Ecs, "baud": 19200}, {"bms": Seplos, "baud": 19200}, + # {"bms": Ant, "baud": 19200}, + # {"bms": MNB, "baud": 9600}, + # {"bms": Sinowealth}, ] expected_bms_types = [ battery_type @@ -51,16 +56,13 @@ if battery_type["bms"].__name__ == utils.BMS_TYPE or utils.BMS_TYPE == "" ] +logger.info("") 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)) - # Thread will die with us if deamon - poller.daemon = True - poller.start() + helper.publish_battery(loop) return True def get_battery(_port) -> Union[Battery, None]: @@ -102,6 +104,7 @@ def get_port() -> str: port = get_port() battery: Battery = get_battery(port) + # exit if no battery could be found if battery is None: logger.error("ERROR >>> No battery connection at " + port) sys.exit(1) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index c52c10df..cb3bc2d0 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -4,6 +4,7 @@ import platform import dbus import traceback +from time import time # Victron packages sys.path.insert( @@ -13,10 +14,10 @@ "/opt/victronenergy/dbus-systemcalc-py/ext/velib_python", ), ) -from vedbus import VeDbusService -from settingsdevice import SettingsDevice -import battery -from utils import * +from vedbus import VeDbusService # noqa: E402 +from settingsdevice import SettingsDevice # noqa: E402 +from utils import logger, publish_config_variables # noqa: E402 +import utils # noqa: E402 def get_bus(): @@ -33,6 +34,7 @@ def __init__(self, battery): self.instance = 1 self.settings = None self.error_count = 0 + self.block_because_disconnect = False self._dbusservice = VeDbusService( "com.victronenergy.battery." + self.battery.port[self.battery.port.rfind("/") + 1 :], @@ -93,10 +95,6 @@ def handle_changed_setting(self, setting, oldvalue, newvalue): 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'] def setup_vedbus(self): # Set up dbus service and device instance @@ -124,13 +122,19 @@ def setup_vedbus(self): "/ProductName", "SerialBattery(" + self.battery.type + ")" ) self._dbusservice.add_path( - "/FirmwareVersion", str(DRIVER_VERSION) + DRIVER_SUBVERSION + "/FirmwareVersion", str(utils.DRIVER_VERSION) + utils.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( + "/Serial", self.battery.unique_identifier, writeable=True + ) + self._dbusservice.add_path( + "/DeviceName", self.battery.production, writeable=True + ) # Create static battery info self._dbusservice.add_path( @@ -154,6 +158,11 @@ def setup_vedbus(self): writeable=True, gettextcallback=lambda p, v: "{:0.2f}A".format(v), ) + + self._dbusservice.add_path("/Info/ChargeMode", None, writeable=True) + self._dbusservice.add_path("/Info/ChargeLimitation", None, writeable=True) + self._dbusservice.add_path("/Info/DischargeLimitation", None, writeable=True) + self._dbusservice.add_path( "/System/NrOfCellsPerBattery", self.battery.cell_count, writeable=True ) @@ -183,9 +192,6 @@ def setup_vedbus(self): 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) @@ -223,7 +229,9 @@ def setup_vedbus(self): # Create battery extras self._dbusservice.add_path("/System/MinCellTemperature", None, writeable=True) + self._dbusservice.add_path("/System/MinTemperatureCellId", None, writeable=True) self._dbusservice.add_path("/System/MaxCellTemperature", None, writeable=True) + self._dbusservice.add_path("/System/MaxTemperatureCellId", None, writeable=True) self._dbusservice.add_path("/System/MOSTemperature", None, writeable=True) self._dbusservice.add_path( "/System/MaxCellVoltage", @@ -244,13 +252,14 @@ def setup_vedbus(self): 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) + self._dbusservice.add_path("/Io/AllowToBalance", 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/HighCellVoltage", None, writeable=True) ## does not exist on the dbus 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) @@ -262,13 +271,17 @@ def setup_vedbus(self): 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) + self._dbusservice.add_path("/Alarms/BmsCable", None, writeable=True) + self._dbusservice.add_path( + "/Alarms/HighInternalTemperature", None, writeable=True + ) # cell voltages - if BATTERY_CELL_DATA_FORMAT > 0: + if utils.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) + if (utils.BATTERY_CELL_DATA_FORMAT & 2) else "/Voltages/Cell%s" ) self._dbusservice.add_path( @@ -277,11 +290,11 @@ def setup_vedbus(self): writeable=True, gettextcallback=lambda p, v: "{:0.3f}V".format(v), ) - if BATTERY_CELL_DATA_FORMAT & 1: + if utils.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" + pathbase = "Cell" if (utils.BATTERY_CELL_DATA_FORMAT & 2) else "Voltages" self._dbusservice.add_path( "/%s/Sum" % pathbase, None, @@ -295,14 +308,21 @@ def setup_vedbus(self): 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) - # Create TimeToGO item - self._dbusservice.add_path("/TimeToGo", None, writeable=True) + # Create TimeToSoC items only if enabled + if self.battery.capacity is not None: + # Create TimeToGo item + if utils.TIME_TO_GO_ENABLE: + self._dbusservice.add_path("/TimeToGo", None, writeable=True) + + # Create TimeToSoc items + if len(utils.TIME_TO_SOC_POINTS) > 0: + for num in utils.TIME_TO_SOC_POINTS: + self._dbusservice.add_path( + "/TimeToSoC/" + str(num), None, writeable=True + ) - logger.info(f"publish config values = {PUBLISH_CONFIG_VALUES}") - if PUBLISH_CONFIG_VALUES == 1: + logger.info(f"publish config values = {utils.PUBLISH_CONFIG_VALUES}") + if utils.PUBLISH_CONFIG_VALUES == 1: publish_config_variables(self._dbusservice) return True @@ -315,41 +335,60 @@ def publish_battery(self, loop): if success: self.error_count = 0 self.battery.online = True + + # unblock charge/discharge, if it was blocked when battery went offline + if utils.BLOCK_ON_DISCONNECT: + self.block_because_disconnect = False + else: self.error_count += 1 # If the battery is offline for more than 10 polls (polled every second for most batteries) if self.error_count >= 10: self.battery.online = False + self.battery.init_values() + + # block charge/discharge + if utils.BLOCK_ON_DISCONNECT: + self.block_because_disconnect = True + # Has it completely failed if self.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() + # This is to mannage CCL\DCL + self.battery.manage_charge_current() + # publish all the data from the battery object to dbus self.publish_dbus() - except: + except Exception: traceback.print_exc() loop.quit() 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["/Soc"] = ( + round(self.battery.soc, 2) if self.battery.soc is not None else None + ) + self._dbusservice["/Dc/0/Voltage"] = ( + round(self.battery.voltage, 2) if self.battery.voltage is not None else None + ) + self._dbusservice["/Dc/0/Current"] = ( + round(self.battery.current, 2) if self.battery.current is not None else None + ) + self._dbusservice["/Dc/0/Power"] = ( + round(self.battery.voltage * self.battery.current, 2) + if self.battery.current is not None and self.battery.current is not None + else None ) self._dbusservice["/Dc/0/Temperature"] = self.battery.get_temp() self._dbusservice["/Capacity"] = self.battery.get_capacity_remain() self._dbusservice["/ConsumedAmphours"] = ( - 0 + None if self.battery.capacity is None or self.battery.get_capacity_remain() is None else self.battery.capacity - self.battery.get_capacity_remain() @@ -364,30 +403,52 @@ def publish_dbus(self): 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 + 1 + if self.battery.charge_fet + and self.battery.control_allow_charge + and self.block_because_disconnect is False + else 0 ) self._dbusservice["/Io/AllowToDischarge"] = ( 1 - if self.battery.discharge_fet and self.battery.control_allow_discharge + if self.battery.discharge_fet + and self.battery.control_allow_discharge + and self.block_because_disconnect is False else 0 ) + self._dbusservice["/Io/AllowToBalance"] = 1 if self.battery.balance_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) + if ( + self.battery.charge_fet is None + or (self.battery.charge_fet and self.battery.control_allow_charge) + ) + and self.block_because_disconnect is False else 1 ) self._dbusservice["/System/NrOfModulesBlockingDischarge"] = ( - 0 if self.battery.discharge_fet is None or self.battery.discharge_fet else 1 + 0 + if (self.battery.discharge_fet is None or self.battery.discharge_fet) + and self.block_because_disconnect is False + 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/MinTemperatureCellId" + ] = self.battery.get_min_temp_id() self._dbusservice["/System/MaxCellTemperature"] = self.battery.get_max_temp() + self._dbusservice[ + "/System/MaxTemperatureCellId" + ] = self.battery.get_max_temp_id() self._dbusservice["/System/MOSTemperature"] = self.battery.get_mos_temp() + # Voltage control + self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage + # Charge control self._dbusservice[ "/Info/MaxChargeCurrent" @@ -396,8 +457,12 @@ def publish_dbus(self): "/Info/MaxDischargeCurrent" ] = self.battery.control_discharge_current - # Voltage control - self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage + # Voltage and charge control info + self._dbusservice["/Info/ChargeMode"] = self.battery.charge_mode + self._dbusservice["/Info/ChargeLimitation"] = self.battery.charge_limitation + self._dbusservice[ + "/Info/DischargeLimitation" + ] = self.battery.discharge_limitation # Updates from cells self._dbusservice["/System/MinVoltageCellId"] = self.battery.get_min_cell_desc() @@ -441,64 +506,84 @@ def publish_dbus(self): self._dbusservice[ "/Alarms/LowTemperature" ] = self.battery.protection.temp_low_discharge + self._dbusservice["/Alarms/BmsCable"] = ( + 2 if self.block_because_disconnect else 0 + ) + self._dbusservice[ + "/Alarms/HighInternalTemperature" + ] = self.battery.protection.temp_high_internal # cell voltages - if BATTERY_CELL_DATA_FORMAT > 0: + if utils.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) + if (utils.BATTERY_CELL_DATA_FORMAT & 2) else "/Voltages/Cell%s" ) self._dbusservice[cellpath % (str(i + 1))] = voltage - if BATTERY_CELL_DATA_FORMAT & 1: + if utils.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" + pathbase = ( + "Cell" if (utils.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: + except Exception: pass - # Update TimeToSoC + # Update TimeToGo and/or TimeToSoC try: if ( self.battery.capacity is not None - and len(TIME_TO_SOC_POINTS) > 0 - and self.battery.time_to_soc_update == 0 + and (utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0) + and ( + int(time()) - self.battery.time_to_soc_update + >= utils.TIME_TO_SOC_RECALCULATE_EVERY + ) ): - self.battery.time_to_soc_update = TIME_TO_SOC_LOOP_CYCLES + self.battery.time_to_soc_update = int(time()) 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) + # Update TimeToGo item + if utils.TIME_TO_GO_ENABLE: + # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py + self._dbusservice["/TimeToGo"] = ( + abs( + int( + self.battery.get_timeToSoc( + utils.SOC_LOW_WARNING, crntPrctPerSec, True + ) + ) + ) if self.battery.current else None ) - # Update TimeToGo - self._dbusservice["/TimeToGo"] = ( - self.battery.get_timetosoc(SOC_LOW_WARNING, crntPrctPerSec) - if self.battery.current - else None - ) + # Update TimeToSoc items + if len(utils.TIME_TO_SOC_POINTS) > 0: + for num in utils.TIME_TO_SOC_POINTS: + 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: + except Exception: pass - logger.debug("logged to dbus [%s]" % str(round(self.battery.soc, 2))) - self.battery.log_cell_data() + if self.battery.soc is not None: + logger.debug("logged to dbus [%s]" % str(round(self.battery.soc, 2))) + self.battery.log_cell_data() diff --git a/etc/dbus-serialbattery/default_config.ini b/etc/dbus-serialbattery/default_config.ini deleted file mode 100644 index b9d96465..00000000 --- a/etc/dbus-serialbattery/default_config.ini +++ /dev/null @@ -1,138 +0,0 @@ -[DEFAULT] -LINEAR_LIMITATION_ENABLE = False - -; battery Current limits -MAX_BATTERY_CHARGE_CURRENT = 70.0 -MAX_BATTERY_DISCHARGE_CURRENT = 90.0 - -; -------- 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 -; ... but 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 -; Discharge current control management referring to cell-voltage enable (True/False). -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_FRACTION = 0,0.05,0.5,1 - -CELL_VOLTAGES_WHILE_DISCHARGING = 2.70,2.80,2.90,3.10 -MAX_DISCHARGE_CURRENT_CV_FRACTION = 0,0.1,0.5,1 - -; -------- 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, -; 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 -; Charge current control management referring to temperature enable (True/False). -DCCM_T_ENABLE = True - -; Set Steps to reduce battery current. The current will be changed linear between those steps -TEMPERATURE_LIMITS_WHILE_CHARGING = 0,2,5,10,15,20,35,40,55 -MAX_CHARGE_CURRENT_T_FRACTION = 0,0.1,0.2,0.4,0.8,1,1,0.4,0 - -TEMPERATURE_LIMITS_WHILE_DISCHARGING = -20,0,5,10,15,45,55 -MAX_DISCHARGE_CURRENT_T_FRACTION = 0,.2,.3,.4,1,1,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 -; this voltage will be subtracted -PENALTY_BATTERY_VOLTAGE = 0.01,1.0,2.0 - - -; -------- 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. -; Example: 16cells * 3.45V/cell = 55,2V max charge voltage. 16*2.9V = 46,4V min discharge voltage -; Cell min/max voltages - used with the cell count to get the min/max battery voltage -MIN_CELL_VOLTAGE = 2.9 -MAX_CELL_VOLTAGE = 3.45 -FLOAT_CELL_VOLTAGE = 3.35 -MAX_VOLTAGE_TIME_SEC = 900 -SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90 - -; Charge current control management enable (True/False). -CCCM_SOC_ENABLE = True -; Discharge current control management enable (True/False). -DCCM_SOC_ENABLE = True - -; charge current soc limits -CC_SOC_LIMIT1 = 98 -CC_SOC_LIMIT2 = 95 -CC_SOC_LIMIT3 = 91 - -; charge current limits -CC_CURRENT_LIMIT1_FRACTION = 0.1 -CC_CURRENT_LIMIT2_FRACTION = 0.3 -CC_CURRENT_LIMIT3_FRACTION = 0.5 - -; discharge current soc limits -DC_SOC_LIMIT1 = 10 -DC_SOC_LIMIT2 = 20 -DC_SOC_LIMIT3 = 30 - -; discharge current limits -DC_CURRENT_LIMIT1_FRACTION = 0.1 -DC_CURRENT_LIMIT2_FRACTION = 0.3 -DC_CURRENT_LIMIT3_FRACTION = 0.5 - -; Charge voltage control management enable (True/False). -CVCM_ENABLE = False - -; Simulate Midpoint graph (True/False). -MIDPOINT_ENABLE = False - -; 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 = 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, 75, 50, 25, 20, 10, 0] -; No data set to disable -TIME_TO_SOC_POINTS = -; 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 -; Both Seconds and time str " [days, HR:MN:SC]" -TIME_TO_SOC_VALUE_TYPE = 3 -; 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] -; These will be as negative time. Disabling this improves performance slightly. -TIME_TO_SOC_INC_FROM = False - - -; 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 = 1 - -; Settings for ESC GreenMeter and Lipro devices -GREENMETER_ADDRESS = 1 -LIPRO_START_ADDRESS = 2 -LIPRO_END_ADDRESS = 4 -LIPRO_CELL_COUNT = 15 - -PUBLISH_CONFIG_VALUES = 1 - -BMS_TYPE = \ No newline at end of file diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh new file mode 100755 index 00000000..866f9fc7 --- /dev/null +++ b/etc/dbus-serialbattery/disable.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +DRIVERNAME=dbus-serialbattery + +# handle read only mounts +sh /opt/victronenergy/swupdate-scripts/remount-rw.sh + +# remove files +rm -f /data/conf/serial-starter.d/$DRIVERNAME.conf + +# kill driver, if running. It gets restarted by the service daemon +pkill -f "python .*/$DRIVERNAME.py" + +# remove install script from rc.local +sed -i "/sh \/data\/etc\/$DRIVERNAME\/reinstall-local.sh/d" /data/rc.local + + +### needed for upgrading from older versions | start ### +# remove old install script from rc.local +sed -i "/sh \/data\/etc\/$DRIVERNAME\/reinstalllocal.sh/d" /data/rc.local +### needed for upgrading from older versions | end ### diff --git a/etc/dbus-serialbattery/disabledriver.sh b/etc/dbus-serialbattery/disabledriver.sh deleted file mode 100755 index 80150089..00000000 --- a/etc/dbus-serialbattery/disabledriver.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -set -x -rm -f /data/conf/serial-starter.d \ No newline at end of file diff --git a/etc/dbus-serialbattery/install-local.sh b/etc/dbus-serialbattery/install-local.sh new file mode 100755 index 00000000..61ffccf1 --- /dev/null +++ b/etc/dbus-serialbattery/install-local.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# remove comment for easier troubleshooting +#set -x + +# backup config.ini +if [ -f "/data/etc/dbus-serialbattery/config.ini" ]; then + mv /data/etc/dbus-serialbattery/config.ini /data/etc/config.ini +fi + +# remove old driver +rm -rf /data/etc/dbus-serialbattery + +# extract driver +tar -zxf ./venus-data.tar.gz -C /data + +# restore config.ini +if [ -f "/data/etc/config.ini" ]; then + mv /data/etc/config.ini /data/etc/dbus-serialbattery/config.ini +fi + +# install driver +sh /data/etc/dbus-serialbattery/reinstall-local.sh diff --git a/etc/dbus-serialbattery/install-nightly.sh b/etc/dbus-serialbattery/install-nightly.sh new file mode 100755 index 00000000..061699b8 --- /dev/null +++ b/etc/dbus-serialbattery/install-nightly.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +PS3="Select the branch from wich you want to install the current code (possible bugs included): " + +select branch in master jkbms_ble quit +do + case $branch in + master) + echo "Selected branch: $branch" + #echo "Selected number: $REPLY" + break + ;; + jkbms_ble) + echo "Selected branch: $branch" + #echo "Selected number: $REPLY" + break + ;; + quit) + exit 0 + ;; + *) + echo "Invalid option $REPLY" + ;; + esac +done + + +cd /tmp + +# clean already extracted folder +rm -rf /tmp/dbus-serialbattery-$branch + +# download driver +wget -O $branch.zip https://github.com/Louisvdw/dbus-serialbattery/archive/refs/heads/$branch.zip + +# extract archive +unzip -q $branch.zip + +# backup config.ini +if [ -f "/data/etc/dbus-serialbattery/config.ini" ]; then + mv /data/etc/dbus-serialbattery/config.ini /data/etc/config.ini +fi + +# remove old driver +rm -rf /data/etc/dbus-serialbattery + +# copy driver +cp -rf /tmp/dbus-serialbattery-$branch/etc/dbus-serialbattery/ /data/etc + +# restore config.ini +if [ -f "/data/etc/config.ini" ]; then + mv /data/etc/config.ini /data/etc/dbus-serialbattery/config.ini +fi + +# set permissions +chmod +x /data/etc/dbus-serialbattery/*.sh +chmod +x /data/etc/dbus-serialbattery/*.py +chmod +x /data/etc/dbus-serialbattery/service/run +chmod +x /data/etc/dbus-serialbattery/service/log/run + +# run install script +bash /data/etc/dbus-serialbattery/reinstall-local.sh diff --git a/etc/dbus-serialbattery/install-qml.sh b/etc/dbus-serialbattery/install-qml.sh new file mode 100755 index 00000000..4ce93910 --- /dev/null +++ b/etc/dbus-serialbattery/install-qml.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +# elaborate version string for better comparing +# https://github.com/kwindrem/SetupHelper/blob/ebaa65fcf23e2bea6797f99c1c41174143c1153c/updateFileSets#L56-L81 +function versionStringToNumber () +{ + local local p4="" ; local p5="" ; local p5="" + local major=""; local minor="" + + # first character should be 'v' so first awk parameter will be empty and is not prited into the read command + # + # version number formats: v2.40, v2.40~6, v2.40-large-7, v2.40~6-large-7 + # so we must adjust how we use paramters read from the version string + # and parsed by awk + # if no beta make sure release is greater than any beta (i.e., a beta portion of 999) + + read major minor p4 p5 p6 <<< $(echo $1 | awk -v FS='[v.~-]' '{print $2, $3, $4, $5, $6}') + ((versionNumber = major * 1000000000 + minor * 1000000)) + if [ -z $p4 ] || [ $p4 = "large" ]; then + ((versionNumber += 999)) + else + ((versionNumber += p4)) + fi + if [ ! -z $p4 ] && [ $p4 = "large" ]; then + ((versionNumber += p5 * 1000)) + large=$p5 + elif [ ! -z $p6 ]; then + ((versionNumber += p6 * 1000)) + fi +} + +# backup old PageBattery.qml once. New firmware upgrade will remove the backup +if [ ! -f /opt/victronenergy/gui/qml/PageBattery.qml.backup ]; then + cp /opt/victronenergy/gui/qml/PageBattery.qml /opt/victronenergy/gui/qml/PageBattery.qml.backup +fi +# backup old PageBatteryParameters.qml once. New firmware upgrade will remove the backup +if [ ! -f /opt/victronenergy/gui/qml/PageBatteryParameters.qml.backup ]; then + cp /opt/victronenergy/gui/qml/PageBatteryParameters.qml /opt/victronenergy/gui/qml/PageBatteryParameters.qml.backup +fi +# backup old PageLynxIonIo.qml once. New firmware upgrade will remove the backup +if [ ! -f /opt/victronenergy/gui/qml/PageLynxIonIo.qml.backup ]; then + cp /opt/victronenergy/gui/qml/PageLynxIonIo.qml /opt/victronenergy/gui/qml/PageLynxIonIo.qml.backup +fi +# copy new PageBattery.qml +cp /data/etc/dbus-serialbattery/qml/PageBattery.qml /opt/victronenergy/gui/qml/ +# copy new PageBatteryCellVoltages +cp /data/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml /opt/victronenergy/gui/qml/ +# copy new PageBatteryParameters.qml +cp /data/etc/dbus-serialbattery/qml/PageBatteryParameters.qml /opt/victronenergy/gui/qml/ +# copy new PageBatterySetup +cp /data/etc/dbus-serialbattery/qml/PageBatterySetup.qml /opt/victronenergy/gui/qml/ +# copy new PageLynxIonIo.qml +cp /data/etc/dbus-serialbattery/qml/PageLynxIonIo.qml /opt/victronenergy/gui/qml/ + + +# get current Venus OS version +versionStringToNumber $(head -n 1 /opt/victronenergy/version) +((venusVersionNumber = $versionNumber)) + +# revert to VisualItemModel, if Venus OS older than v3.00~14 (v3.00~14 uses VisibleItemModel) +versionStringToNumber "v3.00~14" + +# change in Victron directory, else the files are "broken" if upgrading from v2 to v3 +qmlDir="/opt/victronenergy/gui/qml" + +if (( $venusVersionNumber < $versionNumber )); then + echo -n "Venus OS $(head -n 1 /opt/victronenergy/version) is older than v3.00~14. Replacing VisibleItemModel with VisualItemModel... " + fileList="$qmlDir/PageBattery.qml" + fileList+=" $qmlDir/PageBatteryCellVoltages.qml" + fileList+=" $qmlDir/PageBatteryParameters.qml" + fileList+=" $qmlDir/PageBatterySetup.qml" + fileList+=" $qmlDir/PageLynxIonIo.qml" + for file in $fileList ; do + sed -i -e 's/VisibleItemModel/VisualItemModel/' "$file" + done + echo "done." +fi + + +# stop gui +svc -d /service/gui +# sleep 1 sec +sleep 1 +# start gui +svc -u /service/gui diff --git a/etc/dbus-serialbattery/installrelease.sh b/etc/dbus-serialbattery/install-release.sh similarity index 54% rename from etc/dbus-serialbattery/installrelease.sh rename to etc/dbus-serialbattery/install-release.sh index 2904bbde..f95b2c92 100755 --- a/etc/dbus-serialbattery/installrelease.sh +++ b/etc/dbus-serialbattery/install-release.sh @@ -1,5 +1,10 @@ #!/bin/bash -set -x + +# remove comment for easier troubleshooting +#set -x + +# download latest release curl -s https://api.github.com/repos/Louisvdw/dbus-serialbattery/releases/latest | grep "browser_download_url.*gz" | cut -d : -f 2,3 | tr -d \" | wget -O venus-data.tar.gz -qi - -tar -zxf ./venus-data.tar.gz -C /data -sh /data/etc/dbus-serialbattery/reinstalllocal.sh + +# extract and install driver +sh /data/etc/dbus-serialbattery/install-local.sh diff --git a/etc/dbus-serialbattery/installlocal.sh b/etc/dbus-serialbattery/installlocal.sh deleted file mode 100755 index 909a3e67..00000000 --- a/etc/dbus-serialbattery/installlocal.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -x -tar -zxf ./venus-data.tar.gz -C /data -sh /data/etc/dbus-serialbattery/reinstalllocal.sh \ No newline at end of file diff --git a/etc/dbus-serialbattery/installqml.sh b/etc/dbus-serialbattery/installqml.sh deleted file mode 100755 index b877ba8a..00000000 --- a/etc/dbus-serialbattery/installqml.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -x -#backup old PageBattery.qml once. New firmware upgrade will remove the backup -if [ ! -f /opt/victronenergy/gui/qml/PageBattery.qml.backup ]; then - cp /opt/victronenergy/gui/qml/PageBattery.qml /opt/victronenergy/gui/qml/PageBattery.qml.backup -fi -#copy new PageBattery.qml -cp /data/etc/dbus-serialbattery/qml/PageBattery.qml /opt/victronenergy/gui/qml/ -#copy new PageBatteryCellVoltages -cp /data/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml /opt/victronenergy/gui/qml/ -cp /data/etc/dbus-serialbattery/qml/PageBatterySetup.qml /opt/victronenergy/gui/qml/ -#stop gui -svc -d /service/gui -#sleep 1 sec -sleep 1 -#start gui -svc -u /service/gui diff --git a/etc/dbus-serialbattery/qml/PageBattery.qml b/etc/dbus-serialbattery/qml/PageBattery.qml index 868a979d..286ce74c 100644 --- a/etc/dbus-serialbattery/qml/PageBattery.qml +++ b/etc/dbus-serialbattery/qml/PageBattery.qml @@ -39,7 +39,7 @@ MbPage { } } - model: VisualItemModel { + model: VisibleItemModel { MbItemOptions { description: qsTr("Switch") bind: service.path("/Mode") @@ -118,7 +118,7 @@ MbPage { displayUnit: user.temperatureUnit } } - + MbItemValue { description: qsTr("MOSFET temperature") show: item.valid @@ -190,6 +190,42 @@ MbPage { show: item.seen } + MbItemValue { + description: qsTr("Time-to-SoC 0%") + item.bind: service.path("/TimeToSoC/0") + show: item.seen + } + + MbItemValue { + description: qsTr("Time-to-SoC 10%") + item.bind: service.path("/TimeToSoC/10") + show: item.seen + } + + MbItemValue { + description: qsTr("Time-to-SoC 20%") + item.bind: service.path("/TimeToSoC/20") + show: item.seen + } + + MbItemValue { + description: qsTr("Time-to-SoC 80%") + item.bind: service.path("/TimeToSoC/80") + show: item.seen + } + + MbItemValue { + description: qsTr("Time-to-SoC 90%") + item.bind: service.path("/TimeToSoC/90") + show: item.seen + } + + MbItemValue { + description: qsTr("Time-to-SoC 100%") + item.bind: service.path("/TimeToSoC/100") + show: item.seen + } + MbItemOptions { description: qsTr("Relay state") bind: service.path("/Relay/0/State") @@ -310,6 +346,7 @@ MbPage { MbSubMenu { property VBusItem allowToCharge: VBusItem { bind: service.path("/Io/AllowToCharge") } + property VBusItem allowToBalance: VBusItem { bind: service.path("/Io/AllowToBalance") } description: qsTr("IO") subpage: Component { @@ -318,7 +355,7 @@ MbPage { bindPrefix: service.path("") } } - show: allowToCharge.valid + show: allowToCharge.valid || allowToBalance.valid } MbSubMenu { diff --git a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml index cbe1b49d..112564df 100644 --- a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml +++ b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml @@ -78,7 +78,7 @@ MbPage { property string c24: _b24.valid && _b24.text == "1" ? "#ff0000" : "#ddd" title: service.description + " | Cell Voltages" - model: VisualItemModel { + model: VisibleItemModel { MbItemRow { description: qsTr("Cells Sum") @@ -142,10 +142,10 @@ MbPage { 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 } + MbTextBlock { item: volt17; width: 70; height: 20; color: c17 }, + MbTextBlock { item: volt18; width: 70; height: 20; color: c18 }, + MbTextBlock { item: volt19; width: 70; height: 20; color: c19 }, + MbTextBlock { item: volt20; width: 70; height: 20; color: c20 } ] } MbItemRow { @@ -153,10 +153,10 @@ MbPage { 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 } + MbTextBlock { item: volt21; width: 70; height: 20; color: c21 }, + MbTextBlock { item: volt22; width: 70; height: 20; color: c22 }, + MbTextBlock { item: volt23; width: 70; height: 20; color: c23 }, + MbTextBlock { item: volt24; width: 70; height: 20; color: c24 } ] } } diff --git a/etc/dbus-serialbattery/qml/PageBatteryParameters.qml b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml new file mode 100644 index 00000000..b95161a3 --- /dev/null +++ b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml @@ -0,0 +1,50 @@ +import QtQuick 1.1 +import com.victron.velib 1.0 + +MbPage { + id: root + + property variant service + + model: VisibleItemModel { + + MbItemValue { + description: qsTr("Charge Mode") + item.bind: service.path("/Info/ChargeMode") + show: item.valid + } + + MbItemValue { + description: qsTr("Charge Voltage Limit (CVL)") + item.bind: service.path("/Info/MaxChargeVoltage") + } + + MbItemValue { + description: qsTr("Charge Limitation") + item.bind: service.path("/Info/ChargeLimitation") + show: item.valid + } + + MbItemValue { + description: qsTr("Charge Current Limit (CCL)") + item.bind: service.path("/Info/MaxChargeCurrent") + } + + MbItemValue { + description: qsTr("Discharge Limitation") + item.bind: service.path("/Info/DischargeLimitation") + show: item.valid + } + + MbItemValue { + description: qsTr("Discharge Current Limit (DCL)") + item.bind: service.path("/Info/MaxDischargeCurrent") + } + + MbItemValue { + description: qsTr("Low Voltage Disconnect (always ignored)") + item.bind: service.path("/Info/BatteryLowVoltage") + showAccessLevel: User.AccessService + } + } +} diff --git a/etc/dbus-serialbattery/qml/PageBatterySetup.qml b/etc/dbus-serialbattery/qml/PageBatterySetup.qml index 5f732dbb..4d8e8fe1 100644 --- a/etc/dbus-serialbattery/qml/PageBatterySetup.qml +++ b/etc/dbus-serialbattery/qml/PageBatterySetup.qml @@ -36,7 +36,7 @@ MbPage { title: service.description + " | Cell Voltages" - model: VisualItemModel { + model: VisibleItemModel { MbSpinBox { description: qsTr("Maximum charge current") diff --git a/etc/dbus-serialbattery/qml/PageLynxIonIo.qml b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml new file mode 100644 index 00000000..e6ad7106 --- /dev/null +++ b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml @@ -0,0 +1,74 @@ +import QtQuick 1.1 +import "utils.js" as Utils + +MbPage { + id: root + property string bindPrefix + + model: VisibleItemModel { + MbItemOptions { + id: systemSwitch + description: qsTr("System Switch") + bind: Utils.path(bindPrefix, "/SystemSwitch") + readonly: true + show: item.valid + possibleValues:[ + MbOption{description: qsTr("Disabled"); value: 0}, + MbOption{description: qsTr("Enabled"); value: 1} + ] + } + + MbItemOptions { + description: qsTr("Allow to charge") + bind: Utils.path(bindPrefix, "/Io/AllowToCharge") + readonly: true + possibleValues:[ + MbOption{description: qsTr("No"); value: 0}, + MbOption{description: qsTr("Yes"); value: 1} + ] + } + + MbItemOptions { + description: qsTr("Allow to discharge") + bind: Utils.path(bindPrefix, "/Io/AllowToDischarge") + readonly: true + possibleValues:[ + MbOption{description: qsTr("No"); value: 0}, + MbOption{description: qsTr("Yes"); value: 1} + ] + } + + MbItemOptions { + description: qsTr("Allow to balance") + bind: service.path("/Io/AllowToBalance") + readonly: true + show: item.valid + possibleValues:[ + MbOption{description: qsTr("No"); value: 0}, + MbOption{description: qsTr("Yes"); value: 1} + ] + } + + MbItemOptions { + description: qsTr("External relay") + bind: Utils.path(bindPrefix, "/Io/ExternalRelay") + readonly: true + show: item.valid + possibleValues:[ + MbOption{description: qsTr("Inactive"); value: 0}, + MbOption{description: qsTr("Active"); value: 1} + ] + } + + MbItemOptions { + description: qsTr("Programmable Contact") + bind: Utils.path(bindPrefix, "/Io/ProgrammableContact") + readonly: true + show: item.valid + possibleValues:[ + MbOption{description: qsTr("Inactive"); value: 0}, + MbOption{description: qsTr("Active"); value: 1} + ] + } + } +} diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh new file mode 100755 index 00000000..f9e527d1 --- /dev/null +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +DRIVERNAME=dbus-serialbattery + +# handle read only mounts +bash /opt/victronenergy/swupdate-scripts/remount-rw.sh + +# install +rm -rf /opt/victronenergy/service/$DRIVERNAME +rm -rf /opt/victronenergy/service-templates/$DRIVERNAME +rm -rf /opt/victronenergy/$DRIVERNAME +mkdir /opt/victronenergy/$DRIVERNAME +mkdir /opt/victronenergy/$DRIVERNAME/bms +cp -f /data/etc/$DRIVERNAME/* /opt/victronenergy/$DRIVERNAME &>/dev/null +cp -f /data/etc/$DRIVERNAME/bms/* /opt/victronenergy/$DRIVERNAME/bms &>/dev/null +cp -rf /data/etc/$DRIVERNAME/service /opt/victronenergy/service-templates/$DRIVERNAME +bash /data/etc/$DRIVERNAME/install-qml.sh + +# check if serial-starter.d was deleted +serialstarter_path="/data/conf/serial-starter.d" +serialstarter_file="$serialstarter_path/dbus-serialbattery.conf" + +# check if folder is a file (older versions of this driver < v1.0.0) +if [ -f $serialstarter_path ]; then + rm -f $serialstarter_path +fi + +# check if folder exists +if [ ! -d $serialstarter_path ]; then + mkdir $serialstarter_path +fi + +# check if file exists +if [ ! -f $serialstarter_file ]; then + echo "service sbattery dbus-serialbattery" >> $serialstarter_file + echo "alias default gps:vedirect:sbattery" >> $serialstarter_file + echo "alias rs485 cgwacs:fzsonick:imt:modbus:sbattery" >> $serialstarter_file +fi + +# add install-script to rc.local to be ready for firmware update +filename=/data/rc.local +if [ ! -f $filename ]; then + echo "#!/bin/bash" >> $filename + chmod 755 $filename +fi +grep -qxF "sh /data/etc/$DRIVERNAME/reinstall-local.sh" $filename || echo "sh /data/etc/$DRIVERNAME/reinstall-local.sh" >> $filename + +# add empty config.ini, if it does not exist to make it easier for users to add custom settings +filename=/data/etc/$DRIVERNAME/config.ini +if [ ! -f $filename ]; then + echo "[DEFAULT]" > $filename + echo "" >> $filename + echo "; If you want to add custom settings, then check the settings you want to change in \"config.default.ini\"" >> $filename + echo "; and add them below to persist future driver updates." >> $filename + echo "" >> $filename +fi + + +### needed for upgrading from older versions | start ### +# remove old install script from rc.local +sed -i "/sh \/data\/etc\/$DRIVERNAME\/reinstalllocal.sh/d" /data/rc.local +### needed for upgrading from older versions | end ### + + +# kill driver, if running. It gets restarted by the service daemon +pkill -f "python .*/$DRIVERNAME.py" + +# install notes +echo +echo +echo "SERIAL battery connection: The installation is complete. You don't have to do anything more." +echo +echo "CUSTOM SETTINGS: If you want to add custom settings, then check the settings you want to change in \"/data/etc/dbus-serialbattery/config.default.ini\"" +echo " and add them to \"/data/etc/dbus-serialbattery/config.ini\" to persist future driver updates." +echo +echo diff --git a/etc/dbus-serialbattery/reinstalllocal.sh b/etc/dbus-serialbattery/reinstalllocal.sh deleted file mode 100755 index cb7dbedb..00000000 --- a/etc/dbus-serialbattery/reinstalllocal.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -set -x - -DRIVERNAME=dbus-serialbattery - -#handle read only mounts -sh /opt/victronenergy/swupdate-scripts/remount-rw.sh - -#install -rm -rf /opt/victronenergy/service/$DRIVERNAME -rm -rf /opt/victronenergy/service-templates/$DRIVERNAME -rm -rf /opt/victronenergy/$DRIVERNAME -mkdir /opt/victronenergy/$DRIVERNAME -cp -f /data/etc/$DRIVERNAME/* /opt/victronenergy/$DRIVERNAME &>/dev/null -cp -rf /data/etc/$DRIVERNAME/service /opt/victronenergy/service-templates/$DRIVERNAME -sh /data/etc/$DRIVERNAME/installqml.sh - -#restart if running -pkill -f "python .*/$DRIVERNAME.py" - -# add install-script to rc.local to be ready for firmware update -filename=/data/rc.local -if [ ! -f $filename ]; then - echo "#!/bin/bash" >> $filename - chmod 755 $filename -fi -grep -qxF "sh /data/etc/$DRIVERNAME/reinstalllocal.sh" $filename || echo "sh /data/etc/$DRIVERNAME/reinstalllocal.sh" >> $filename - diff --git a/etc/dbus-serialbattery/restart-driver.sh b/etc/dbus-serialbattery/restart-driver.sh new file mode 100755 index 00000000..853a8b97 --- /dev/null +++ b/etc/dbus-serialbattery/restart-driver.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +# copy config.ini in case it was changed +cp -f /data/etc/dbus-serialbattery/config.ini /opt/victronenergy/dbus-serialbattery/config.ini + +# would not restart ble services +# svc -d -u /service/dbus-serialbattery + +# kill driver, if running. It gets restarted by the service daemon +pkill -f "python .*/$DRIVERNAME.py" + + +# get BMS list from config file +bluetooth_bms=$(awk -F "=" '/^BLUETOOTH_BMS/ {print $2}' /data/etc/dbus-serialbattery/config.ini) +# clear whitespaces +bluetooth_bms_clean="$(echo $bluetooth_bms | sed 's/\s*,\s*/,/g')" +# split into array +IFS="," read -r -a bms_array <<< "$bluetooth_bms_clean" +length=${#bms_array[@]} + +# restart bluetooth service, if Bluetooth BMS configured +if [ $length -gt 0 ]; then + /etc/init.d/bluetooth restart +fi diff --git a/etc/dbus-serialbattery/restartservice.sh b/etc/dbus-serialbattery/restartservice.sh deleted file mode 100755 index 51ecf027..00000000 --- a/etc/dbus-serialbattery/restartservice.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -svc -d -u /service/dbus-serialbattery diff --git a/etc/dbus-serialbattery/restore-gui.sh b/etc/dbus-serialbattery/restore-gui.sh new file mode 100755 index 00000000..861f3410 --- /dev/null +++ b/etc/dbus-serialbattery/restore-gui.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +# restore original backup +if [ -f /opt/victronenergy/gui/qml/PageBattery.qml.backup ]; then + cp -f /opt/victronenergy/gui/qml/PageBattery.qml.backup /opt/victronenergy/gui/qml/PageBattery.qml + echo "PageBattery.qml was restored." +fi +# restore original backup +if [ -f /opt/victronenergy/gui/qml/PageBatteryParameters.qml.backup ]; then + cp -f /opt/victronenergy/gui/qml/PageBatteryParameters.qml.backup /opt/victronenergy/gui/qml/PageBatteryParameters.qml + echo "PageBatteryParameters.qml was restored." +fi +# restore original backup +if [ -f /opt/victronenergy/gui/qml/PageLynxIonIo.qml.backup ]; then + cp -f /opt/victronenergy/gui/qml/PageLynxIonIo.qml.backup /opt/victronenergy/gui/qml/PageLynxIonIo.qml + echo "PageLynxIonIo.qml was restored." +fi + +#stop gui +svc -d /service/gui +#sleep 1 sec +sleep 1 +#start gui +svc -u /service/gui diff --git a/etc/dbus-serialbattery/restoregui.sh b/etc/dbus-serialbattery/restoregui.sh deleted file mode 100755 index f26bf9cc..00000000 --- a/etc/dbus-serialbattery/restoregui.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -x -#restore original backup -cp -f /opt/victronenergy/gui/qml/PageBattery.qml.backup /opt/victronenergy/gui/qml/PageBattery.qml - -#stop gui -svc -d /service/gui -#sleep 1 sec -sleep 1 -#start gui -svc -u /service/gui diff --git a/etc/dbus-serialbattery/start-serialbattery.sh b/etc/dbus-serialbattery/start-serialbattery.sh index 38de1d58..6f29f840 100755 --- a/etc/dbus-serialbattery/start-serialbattery.sh +++ b/etc/dbus-serialbattery/start-serialbattery.sh @@ -1,5 +1,7 @@ #!/bin/bash -set -x + +# remove comment for easier troubleshooting +#set -x . /opt/victronenergy/serial-starter/run-service.sh diff --git a/etc/dbus-serialbattery/uninstall.sh b/etc/dbus-serialbattery/uninstall.sh new file mode 100755 index 00000000..b1a1d16d --- /dev/null +++ b/etc/dbus-serialbattery/uninstall.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# remove comment for easier troubleshooting +#set -x + +# handle read only mounts +sh /opt/victronenergy/swupdate-scripts/remount-rw.sh + +# remove files, don't use variables here, since on an error the whole /opt/victronenergy gets deleted +rm -f /data/conf/serial-starter.d/dbus-serialbattery.conf +rm -rf /opt/victronenergy/service/dbus-serialbattery +rm -rf /opt/victronenergy/service-templates/dbus-serialbattery +rm -rf /opt/victronenergy/dbus-serialbattery + +# kill if running +pkill -f "python .*/dbus-serialbattery.py" + +# remove install-script from rc.local +sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local + + +### needed for upgrading from older versions | start ### +# remove old install script from rc.local +sed -i "/sh \/data\/etc\/$DRIVERNAME\/reinstalllocal.sh/d" /data/rc.local +### needed for upgrading from older versions | end ### diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 77685193..77bc4853 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -17,7 +17,7 @@ config = configparser.ConfigParser() path = Path(__file__).parents[0] -default_config_file_path = path.joinpath("default_config.ini").absolute().__str__() +default_config_file_path = path.joinpath("config.default.ini").absolute().__str__() custom_config_file_path = path.joinpath("config.ini").absolute().__str__() config.read([default_config_file_path, custom_config_file_path]) @@ -35,34 +35,97 @@ def _get_list_from_config( # if not specified: baud = 9600 # Constants - Need to dynamically get them in future -DRIVER_VERSION = 0.14 -DRIVER_SUBVERSION = ".3" +DRIVER_VERSION = "1.0" +DRIVER_SUBVERSION = ".0 (20230505)" zero_char = chr(48) 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 = "True" == config["DEFAULT"]["LINEAR_LIMITATION_ENABLE"] - -# battery Current limits +# --------- Battery Current limits --------- MAX_BATTERY_CHARGE_CURRENT = float(config["DEFAULT"]["MAX_BATTERY_CHARGE_CURRENT"]) MAX_BATTERY_DISCHARGE_CURRENT = float( config["DEFAULT"]["MAX_BATTERY_DISCHARGE_CURRENT"] ) -# -------- 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 -# ... but the (dis)charge current will be (in-/)decreased, if even ONE SINGLE BATTERY CELL reaches the limits +# --------- 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"]) + +# --------- 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 +# 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 +# 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.050V +# 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 +CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = float( + config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"] +) + +# -- CVL Reset based on SoC option +# Reset max voltage after +MAX_VOLTAGE_TIME_SEC = float(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) +# Specify SoC where CVL limit is reset to max voltage +SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = float( + 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 +# 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) ) @@ -81,9 +144,9 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) -# -------- Temperature limitation --------- -# Description: -# Maximal charge / discharge current will be in-/decreased depending on temperature + +# --------- 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). @@ -91,7 +154,8 @@ def _get_list_from_config( # 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 +# 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) ) @@ -110,42 +174,22 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) -# 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 = _get_list_from_config( - "DEFAULT", "PENALTY_AT_CELL_VOLTAGE", lambda v: float(v) -) -PENALTY_BATTERY_VOLTAGE = _get_list_from_config( - "DEFAULT", "PENALTY_BATTERY_VOLTAGE", lambda v: float(v) -) - - -# -------- 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. -# Example: 16cells * 3.45V/cell = 55,2V max charge voltage. 16*2.9V = 46,4V min discharge voltage -# Cell min/max voltages - used with the cell count to get the min/max battery voltage -MIN_CELL_VOLTAGE = float(config["DEFAULT"]["MIN_CELL_VOLTAGE"]) -MAX_CELL_VOLTAGE = float(config["DEFAULT"]["MAX_CELL_VOLTAGE"]) -FLOAT_CELL_VOLTAGE = float(config["DEFAULT"]["FLOAT_CELL_VOLTAGE"]) -MAX_VOLTAGE_TIME_SEC = float(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) -SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = float( - config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"] -) +# --------- 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 +# 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 +# Charge current limits CC_CURRENT_LIMIT1 = MAX_BATTERY_CHARGE_CURRENT * float( config["DEFAULT"]["CC_CURRENT_LIMIT1_FRACTION"] ) @@ -156,12 +200,12 @@ def _get_list_from_config( config["DEFAULT"]["CC_CURRENT_LIMIT3_FRACTION"] ) -# discharge current soc limits +# 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 +# Discharge current limits DC_CURRENT_LIMIT1 = MAX_BATTERY_DISCHARGE_CURRENT * float( config["DEFAULT"]["DC_CURRENT_LIMIT1_FRACTION"] ) @@ -172,57 +216,96 @@ def _get_list_from_config( config["DEFAULT"]["DC_CURRENT_LIMIT3_FRACTION"] ) -# Charge voltage control management enable (True/False). -CVCM_ENABLE = "True" == config["DEFAULT"]["CVCM_ENABLE"] + +# --------- 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 only one BMS type to load else leave empty to try to load all availabe +# LltJbd, Ant, Daly, Daly, Jkbms, Lifepower, Renogy, Renogy, Ecs +BMS_TYPE = config["DEFAULT"]["BMS_TYPE"] + +# 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"] -# soc low levels +# 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"] + + +# --------- 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 +# -- 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"]) -# 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, 75, 50, 25, 20, 10, 0] -TIME_TO_SOC_POINTS = _get_list_from_config("DEFAULT", "TIME_TO_SOC_POINTS") -# 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 = int(config["DEFAULT"]["TIME_TO_SOC_VALUE_TYPE"]) -# Specify how many loop cycles between each TimeToSoc updates -TIME_TO_SOC_LOOP_CYCLES = int(config["DEFAULT"]["TIME_TO_SOC_LOOP_CYCLES"]) -# 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"] - - -# 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"]) - -# Settings for ESC GreenMeter and Lipro devices +# -- ESC GreenMeter and Lipro device settings GREENMETER_ADDRESS = int(config["DEFAULT"]["GREENMETER_ADDRESS"]) LIPRO_START_ADDRESS = int(config["DEFAULT"]["LIPRO_START_ADDRESS"]) LIPRO_END_ADDRESS = int(config["DEFAULT"]["LIPRO_END_ADDRESS"]) LIPRO_CELL_COUNT = int(config["DEFAULT"]["LIPRO_CELL_COUNT"]) -PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) - -BMS_TYPE = config["DEFAULT"]["BMS_TYPE"] - +# --------- Functions --------- def constrain(val, min_val, max_val): if min_val > max_val: min_val, max_val = max_val, min_val