Merge branch 'development'

This commit is contained in:
helgeerbe 2023-04-26 15:02:40 +02:00
commit 83b42a36b9
73 changed files with 1631 additions and 613 deletions

View File

@ -4,6 +4,11 @@ This is a fork from the Hoymiles project OpenDTU.
![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/helgeerbe/OpenDTU?label=based%20on%20original%20OpenDTU) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/helgeerbe/OpenDTU?label=based%20on%20original%20OpenDTU)
<mark>This README is kept similar to the openDTU project for process reasons.</mark>
<mark>Please refer to the [openDTU-onBattery readme ](README_onBattery.md) for project documentation.</mark>
## Extensions to the original OpenDTU ## Extensions to the original OpenDTU
This project is still under development and adds following features: This project is still under development and adds following features:
@ -191,6 +196,12 @@ Topics for 3 phases of a power meter is configurable. Given is an example for th
| huawei/output_temp | R | Output air temperature | °C | | huawei/output_temp | R | Output air temperature | °C |
| huawei/efficiency | R | Efficiency | Percentage | | huawei/efficiency | R | Efficiency | Percentage |
## Power Limiter topics
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| powerlimiter/cmd/disable | W | Power Limiter disable override for external PL control | 0 / 1 |
| powerlimiter/status/disabled | R | Power Limiter disable override status | 0 / 1 |
## Currently supported Inverters ## Currently supported Inverters
| Model | Required RF Module | DC Inputs | MPP-Tracker | AC Phases | | Model | Required RF Module | DC Inputs | MPP-Tracker | AC Phases |
@ -503,6 +514,7 @@ A documentation of the Web API can be found here: [Web-API Documentation](docs/W
* If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information. * If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information.
* Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/) * Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/)
* When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards. * When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards.
* Make sure to connect one inverter only to one DTU (Original, Ahoy, OpenDTU doesn't make a difference). If you query a inverter by multiple DTUs you will get strange peaks in your values.
## Related Projects ## Related Projects

174
README_onBattery.md Normal file
View File

@ -0,0 +1,174 @@
# OpenDTU-OnBattery
This is a fork from the Hoymiles project [OpenDTU](https://github.com/tbnobody/OpenDTU). This project is still under development but is being used on a day to day basis as well.
![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/helgeerbe/OpenDTU?label=based%20on%20original%20OpenDTU)
## Features
* Hoymiles inverter support for live data and display of various inverter internal information. (Partial) support for multiple inverters.
* MQTT support (with TLS) with partial Home Assistant MQTT Auto Discovery
* Automatic inverter power control of a selected Hoymiles inverter to compensate the currently used energy in the household.
* Energy meter support with interface options to HTTP JSON based power meters (e.g. Tasmota), MQTT based power meters (e.g. Shelly 3EM) or SDM power meters.
* Support for Victron MPPT charge controller using Ve.Direct. cf. Ve.direct: https://www.victronenergy.com/support-and-downloads/technical-information.
* Generic voltage based battery support using Victron MPPT charge controller or Hoymiles inverter voltage values to start / stop inverter power output. (with load compensation)
* Pylontech battery support via CAN bus interface. State of Charge reported by BMS is used to start / stop inverter power output. Battery data is exported via MQTT (no support for home assistant auto discovery).
* Support for Huawei R4850G2 power supply unit that can act as AC charging source. [Overview](https://www.beyondlogic.org/review-huawei-r4850g2-power-supply-53-5vdc-3kw/)
* Other features from [OpenDTU](https://github.com/tbnobody/OpenDTU) maintained
## Hardware
To get started with this project you will need to assemble a few hardware components that allow interfacing with the desired devices. What is needed depends on the use-case but may consist of:
* ESP32 board that contains the CPU and WIFI connectivity
* NRF24L01+ or CMT2300A radio board to interface with the inverter. Please check the list of the supported inverters below for the board needed.
* 3.3V / 5V logic level shifter to interface with the Victron MPPT charge controller
* SN65HVD230 CAN bus transceiver to interface with a Pylontech battery
* MCP2515 SPI / CAN bus transceiver to interface with the Huawei AC PSU
* Relais board + 3.3V / 5 V logic level shifter to switch the slot detect on the Huawei AC PSU
* Display [Display](docs/Display.md)
More detailed information on the hardware can be found in the [Hardware and flashing](docs/hardware_flash.md) document.
### Currently supported Inverters
| Model | Required RF Module | DC Inputs | MPP-Tracker | AC Phases |
| --------------------| ------------------ | --------- | ----------- | --------- |
| Hoymiles HM-300 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-350 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-400 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-600 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-700 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-800 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-1000 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HM-1200 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HM-1500 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HMS-300 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-350 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-400 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-450 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-500 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-600 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-700 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-800 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-900 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-1000 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-1600 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMS-1800 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMS-2000 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMT-1800 | CMT2300A | 6 | 3 | 3 |
| Hoymiles HMT-2250 | CMT2300A | 6 | 3 | 3 |
| Solenso SOL-H350 | NRF24L01+ | 1 | 1 | 1 |
| Solenso SOL-H400 | NRF24L01+ | 1 | 1 | 1 |
| Solenso SOL-H800 | NRF24L01+ | 2 | 2 | 1 |
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
**TSUN compatibility remark:**
Compatibility with OpenDTU is most likely related to the serial number of the inverter. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
## Screenshots
Several screenshots of the frontend can be found here: [Screenshots](docs/screenshots/README.md)
## Configuration and usage
### First configuration
* After the [initial flashing](docs/hardware_flash.md#flashing-and-starting-up) of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42".
* Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1)
* Navigate to Settings --> Network Settings and enter your WiFi credentials. The username to access the config menu is "admin" and the password the same as for accessing the Access Point (default: "openDTU42").
* OpenDTU then simultaneously connects to your WiFi AP with these credentials. Navigate to Info --> Network and look into section "Network Interface (Station)" for the IP address received via DHCP.
* If your WiFi AP uses an allow-list for MAC-addresses, please be aware that the ESP32 has two different MAC addresses for its AP and client modes, they are also listed at Info --> Network.
* When OpenDTU is connected to a configured WiFI AP, the "OpenDTU-*" Access Point is closed after 3 minutes.
* OpenDTU needs access to a working NTP server to get the current date & time. Both are sent to the inverter with each request. Default NTP server is pool.ntp.org. If your network has different requirements please change accordingly (Settings --> NTP Settings).
* Activate Ve.direct, Battery and the AC Charger according to the available hardware
* Configure a Power Meter to provide a data source for the current consumption
* Configure the Dynamic Power Limiter according to the used battery. Documentation about the power limiter interface and states can be found below.
* If desired connect to a home automation system using MQTT or the Webapi.
* A documentation of all available MQTT Topics can be found here: [MQTT Documentation](docs/MQTT_Topics.md)
* A documentation of the Web API can be found here: [Web-API Documentation](docs/Web-API.md)
* Home Assistant auto discovery is supported. [Example image](https://user-images.githubusercontent.com/59169507/217558862-a83846c5-6070-43cd-9a0b-90a8b2e2e8c6.png)
### Dynamic Power Limiter Interface
![image](https://user-images.githubusercontent.com/59169507/222155765-9fff47a4-8ffa-42cf-8671-6359288e0cab.png)
#### Power Limiter States
![PowerLimiterInverterStates](https://github.com/helgeerbe/OpenDTU-OnBattery/blob/development/docs/PowerLimiterInverterStates.png)
## Troubleshooting
* First: When there is no light on the solar panels, the inverter completely turns off and does not answer to OpenDTU! So if you assembled your OpenDTU in the evening, wait until tomorrow.
* When there is no data received from the inverter(s) - try to reduce the distance between the openDTU and the inverter (e.g. move it to the window towards the roof)
* Under Settings -> DTU Settings you can increase the transmit power "PA level". Default is "minimum".
* The NRF24L01+ needs relatively much current. With bad power supply (and especially bad cables!) a 10 µF capacitor soldered directly to the NRF24L01+ board connector brings more stability (pin 1+2 are the power supply). Note the polarity of the capacitor…
* You can try to use an USB power supply with 1 A or more instead of connecting the ESP32 to the computer.
* Try a different USB cable. Once again, a stable power source is important. Some USB cables are made of much plastic and very little copper inside.
* Double check that you have a radio module NRF24L01+ with a plus sign at the end. NRF24L01 module without the plus are not compatible with this project.
* There is no possibility of auto-discovering the inverters. Double check you have entered the serial numbers of the inverters correctly.
* OpenDTU needs access to a working NTP server to get the current date & time.
* If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information.
* Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/)
* When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards.
## Background
This project was started from [this](https://www.mikrocontroller.net/topic/525778) discussion (Mikrocontroller.net).
It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with their cloud access. With a lot of reverse engineering the Hoymiles protocol was decrypted and analyzed.
## Features for developers
### Status
[![OpenDTU Build](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml)
[![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml)
### Core technologies used
* The microcontroller part
* Build with Arduino PlatformIO Framework for the ESP32
* Uses a fork of [ESPAsyncWebserver](https://github.com/yubox-node-org/ESPAsyncWebServer) and [espMqttClient](https://github.com/bertmelis/espMqttClient)
* The WebApp part
* Build with [Vue.js](https://vuejs.org)
* Source is written in TypeScript
### Breaking changes
Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | grep BREAKING`
```code
* 59f43a8 2023-04-17 BREAKING CHANGE: Web API Endpoint /api/devinfo/status requires GET parameter inv=
* 318136d 2023-03-15 BREAKING CHANGE: Updated partition table: Make sure you have a configuration backup and completly reflash the device!
* 3b7aef6 2023-02-13 BREAKING CHANGE: Web API!
* d4c838a 2023-02-06 BREAKING CHANGE: Prometheus API!
* daf847e 2022-11-14 BREAKING CHANGE: Removed deprecated config parsing method
* 69b675b 2022-11-01 BREAKING CHANGE: Structure WebAPI /api/livedata/status changed
* 27ed4e3 2022-10-31 BREAKING: Change power factor from percent value to value between 0 and 1
```
### Building
* Building the WebApp
* The WebApp can be build using yarn
```bash
cd webapp
yarn install
yarn build
```
* The updated output is placed in the 'webapp_dist' directory
* It is only necessary to build the webapp when you made changes to it
* Building the microcontroller firmware
* Visual Studio Code with the PlatformIO Extension is required for building
## Related Projects
* [Ahoy](https://github.com/grindylow/ahoy)
* [DTU Simulator](https://github.com/Ziyatoe/DTUsimMI1x00-Hoymiles)
* [OpenDTU extended to talk to Victrons MPPT battery chargers (Ve.Direct)](https://github.com/helgeerbe/OpenDTU_VeDirect)

View File

@ -20,6 +20,22 @@ To change the device profile, navigate to the "Device Manager" and selected the
"en": 4, "en": 4,
"cs": 5 "cs": 5
}, },
"victron": {
"rx": 22,
"tx": 21
},
"battery": {
"rx": 27,
"tx": 14
},
"huawei": {
"miso": 12,
"mosi": 13,
"clk": 26,
"irq": 25,
"power": 33,
"cs": 15
},
"eth": { "eth": {
"enabled": false, "enabled": false,
"phy_addr": -1, "phy_addr": -1,
@ -109,5 +125,15 @@ The json file can contain multiple profiles. Each profile requires a name and di
| display.clk | number | Clock Pin (e.g. SCL for i2c displays) required for SSD1306 and SH1106. Use 255 for not assigned pins. | | display.clk | number | Clock Pin (e.g. SCL for i2c displays) required for SSD1306 and SH1106. Use 255 for not assigned pins. |
| display.cs | number | Chip Select Pin required for PCD8544. Use 255 for not assigned pins. | | display.cs | number | Chip Select Pin required for PCD8544. Use 255 for not assigned pins. |
| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. | | display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. |
| victron.rx | number | Victron Ve.direct Rx pin |
| victron.tx | number | Victron Ve.direct Tx pin |
| battery.rx | number | Pylontech CAN bus battery Rx pin |
| battery.tx | number | Pylontech CAN bus battery Tx pin |
| huawei.miso | number | MISO Pin for Huawei CAN bus interface |
| huawei.mosi | number | MOSI Pin for Huawei CAN bus interface |
| huawei.clk | number | CLK Pin for Huawei CAN bus interface |
| huawei.cs | number | CS Pin for Huawei CAN bus interface |
| huawei.irq | number | IRQ Pin for Huawei CAN bus interface |
| huawei.power | number | Power Pin for Huawei power control (e.g. using slot detect) |
| led.led0 | number | LED pin for network indication. Blinking = WLAN connected but NTP & MQTT (if enabled) disconnected. On = WLAN, NTP, MQTT connected. Off = Network not connected | | led.led0 | number | LED pin for network indication. Blinking = WLAN connected but NTP & MQTT (if enabled) disconnected. On = WLAN, NTP, MQTT connected. Off = Network not connected |
| led.led1 | number | LED pin for inverter indication. On = All inverters reachable & producing. Blinking = All inverters reachable but not producing. Off = At least one inverter is not reachable. Only inverters with polling enabled are considered. | | led.led1 | number | LED pin for inverter indication. On = All inverters reachable & producing. Blinking = All inverters reachable but not producing. Off = At least one inverter is not reachable. Only inverters with polling enabled are considered. |

View File

@ -64,7 +64,7 @@
"clk_mode": 3 "clk_mode": 3
}, },
"display": { "display": {
"type": 2, "type": 3,
"data": 33, "data": 33,
"clk": 32 "clk": 32
} }

View File

@ -1,6 +1,7 @@
# MQTT Topics # MQTT Topics
The base topic, as configured in the web GUI is prepended to all follwing topics. The base topic, as configured in the web GUI is prepended to all following topics.
Serial will be replaced with the serial number of the respective device.
## General topics ## General topics
@ -12,6 +13,20 @@ The base topic, as configured in the web GUI is prepended to all follwing topics
| dtu/status | R | Indicates whether OpenDTU network is reachable | online / offline | | dtu/status | R | Indicates whether OpenDTU network is reachable | online / offline |
| dtu/uptime | R | Time in seconds since startup | seconds | | dtu/uptime | R | Time in seconds since startup | seconds |
## Inverter total topicss
Enabled inverter means, that only inverters with "Poll inverter data" enabled are considered.
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| ac/power | R | Sum of AC active power of all enabled inverters | W |
| ac/yieldtotal | R | Sum of energy converted to AC since reset watt hours of all enabled inverters | Kilo watt hours (kWh) |
| ac/yieldday | R | Sum of energy converted to AC per day in watt hours of all enabled inverters | Watt hours (Wh)
| ac/is_valid | R | Indicator whether all enabled inverters where reachable | 0 or 1 |
| dc/power | R | Sum of DC power of all enabled inverters | Watt (W) |
| dc/irradiation | R | Produced power of all enabled inverter stripes with defined irradiation settings divided by sum of all enabled inverters irradiation | % |
| dc/is_valid | R | Indicator whether all enabled inverters where reachable | 0 or 1 |
## Inverter specific topics ## Inverter specific topics
serial will be replaced with the serial number of the inverter. serial will be replaced with the serial number of the inverter.
@ -72,3 +87,85 @@ cmd topics are used to set values. Status topics are updated from values set in
| [serial]/cmd/limit_nonpersistent_absolute | W | Set the inverter limit as a absolute value. The value will reset to the last persistent value at night without power. The updated value will set immediatly within the inverter but show up in the web GUI and limit_relative topic after around 4 minutes. If you are using a already known inverter (known Hardware ID), the updated value will show up within a few seconds. The value must be published non-retained, otherwise it will be ignored! | Watt (W) | | [serial]/cmd/limit_nonpersistent_absolute | W | Set the inverter limit as a absolute value. The value will reset to the last persistent value at night without power. The updated value will set immediatly within the inverter but show up in the web GUI and limit_relative topic after around 4 minutes. If you are using a already known inverter (known Hardware ID), the updated value will show up within a few seconds. The value must be published non-retained, otherwise it will be ignored! | Watt (W) |
| [serial]/cmd/power | W | Turn the inverter on (1) or off (0) | 0 or 1 | | [serial]/cmd/power | W | Turn the inverter on (1) or off (0) | 0 or 1 |
| [serial]/cmd/restart | W | Restarts the inverters (also resets YieldDay) | 1 | | [serial]/cmd/restart | W | Restarts the inverters (also resets YieldDay) | 1 |
### Victron MPPT topics
#### General
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| victron/[serial]/PID | R | Product description | text |
| victron/[serial]/SER | R | Serial number | text |
| victron/[serial]/FW | R | Firmware number | int |
| victron/[serial]/LOAD | R | Load output state | ON / OFF |
| victron/[serial]/CS | R | State of operation | text e. g. "Bulk" |
| victron/[serial]/ERR | R | Error code | text e. g. "No error" |
| victron/[serial]/OR | R | Off reasen | text e. g. "Not off" |
| victron/[serial]/MPPT | R | Tracker operation mode | text e. g. "MPP Tracker active" |
| victron/[serial]/HSDS | R | Day sequence number (0...364) | int in days |
#### Battery output
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| victron/[serial]/V | R | Voltage | Volt (V) |
| victron/[serial]/I | R | Current | Ampere (A) |
#### Solar input
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| victron/[serial]/VPV | R | Voltage | Volt (V) |
| victron/[serial]/PPV | R | Power | Watt (W) |
| victron/[serial]/H19 | R | Yield total (user resettable counter) | Kilo watt hours (kWh) |
| victron/[serial]/H20 | R | Yield today | Kilo watt hours (kWh) |
| victron/[serial]/H21 | R | Maximum power today | Watt (W) |
| victron/[serial]/H22 | R | Yield yesterday | Kilo watt hours (kWh) |
| victron/[serial]/H23 | R | Maximum power yesterday | Watt (W) |
### Pylontech battery topics
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| battery/settings/chargeVoltage | R | Voltage | Volt (V) |
| battery/settings/chargeCurrentLimitation | R | BMS requested max. charge current | Ampere (A) |
| battery/settings/dischargeCurrentLimitation | R | BMS requested max. discharge current | Ampere (A) |
| battery/stateOfCharge | R | State of Health | % |
| battery/stateOfHealth | R | State of Charge | % |
| battery/voltage | R | Actual voltage | Volt (V) |
| battery/current | R | Actual current | Ampere (A) |
| battery/temperature" | R | Actual temperature | °C |
| battery/alarm/overCurrentDischarge | R | Alarm: High discharge current | 0 / 1 |
| battery/alarm/underTemperature | R | Alarm: Low temperature | 0 / 1 |
| battery/alarm/overTemperature | R | Alarm: High temperature | 0 / 1 |
| battery/alarm/underVoltage | R | Alarm: Low voltage | 0 / 1 |
| battery/alarm/overVoltage | R | Alarm: High voltage | 0 / 1 |
| battery/alarm/bmsInternal | R | Alarm: BMS internal | 0 / 1 |
| battery/warning/highCurrentDischarge | R | Warning: High discharge current | 0 / 1 |
| battery/warning/lowTemperature | R | Warning: Low temperature | 0 / 1 |
| battery/warning/highTemperature | R | Warning: High temperature | 0 / 1 |
| battery/warning/lowVoltage | R | Warning: Low voltage | 0 / 1 |
| battery/warning/highVoltage | R | Warning: High voltage | 0 / 1 |
| battery/warning/bmsInternal | R | Warning: BMS internal | 0 / 1 |
| battery/manufacturer | R | Manufacturer | String |
| battery/charging/chargeEnabled | R | Charge enabled flag | 0 / 1 |
| battery/charging/dischargeEnabled | R | Discharge enabled flag | 0 / 1 |
| battery/charging/chargeImmediately | R | Charge immediately flag | 0 / 1 |
### Huawei AC charger topics
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| huawei/cmd/limit_online_voltage | W | Online voltage (i.e. CAN bus connected) | Volt (V) |
| huawei/cmd/limit_online_current | W | Online current (i.e. CAN bus connected) | Ampere (A) |
| huawei/cmd/power | W | Controls output pin GPIO to drive solid state relais | 0 / 1 |
| huawei/data_age | R | How old the data is | Seconds |
| huawei/input_voltage | R | Input voltage | Volt (V) |
| huawei/input_current | R | Input current | Ampere (A) |
| huawei/input_power | R | Input power | Watt (W) |
| huawei/output_voltage | R | Output voltage | Volt (V) |
| huawei/output_current | R | Output current | Ampere (A) |
| huawei/max_output_current | R | Maximum output current (set using the online limit) | Ampere (A) |
| huawei/output_power | R | Output power | Watt (W) |
| huawei/input_temp | R | Input air temperature | °C |
| huawei/output_temp | R | Output air temperature | °C |
| huawei/efficiency | R | Efficiency | Percentage |

View File

@ -4,7 +4,7 @@ Information in JSON format can be obtained through the web API
## List of URLs ## List of URLs
may be incomplete This list may be incomplete
| GET/POST | Auth required | URL | | GET/POST | Auth required | URL |
| -------- | --- | -- | | -------- | --- | -- |
@ -38,6 +38,12 @@ may be incomplete
| Get+Post | yes | /api/security/config | | Get+Post | yes | /api/security/config |
| Get | yes | /api/security/authenticate | | Get | yes | /api/security/authenticate |
| Get | no | /api/system/status | | Get | no | /api/system/status |
| Get | no | /api/verdirectlivedata/status |
| Get | no | /api/huawei/status |
| Get | no | /api/huawei/config |
| Get | no | /api/huawei/limit/config |
| Get | no | /api/battery/status |
| Get | no | /api/powerlimiter/status |
## Examples of Use ## Examples of Use
@ -488,6 +494,32 @@ $ curl --no-progress-meter http://192.168.10.10/api/eventlog/status?inv=11418186
} }
``` ```
### Victron REST-API (/api/verdirectlivedata/status):
````JSON
{
"data_age":0,
"age_critical":false,
"PID":"SmartSolar MPPT 100|30",
"SER":"XXX",
"FW":"159",
"LOAD":"ON",
"CS":"Bulk",
"ERR":"No error",
"OR":"Not off",
"MPPT":"MPP Tracker active",
"HSDS":{"v":46,"u":"Days"},
"V":{"v":26.36,"u":"V"},
"I":{"v":3.4,"u":"A"},
"VPV":{"v":37.13,"u":"V"},
"PPV":{"v":93,"u":"W"},
"H19":{"v":83.16,"u":"kWh"},
"H20":{"v":1.39,"u":"kWh"},
"H21":{"v":719,"u":"W"},
"H22":{"v":1.43,"u":"kWh"},
"H23":{"v":737,"u":"W"}
}
````
#### combine curl and jq #### combine curl and jq
`jq` can filter specific fields from json output. `jq` can filter specific fields from json output.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

191
docs/hardware_flash.md Normal file
View File

@ -0,0 +1,191 @@
# Hardware, building and flashing Firmware
## Hardware you need
### ESP32 board
For ease of use, buy a "ESP32 DEVKIT DOIT" or "ESP32 NodeMCU Development Board" with an ESP32-S3 or ESP-WROOM-32 chipset on it.
Sample Picture:
![NodeMCU-ESP32](nodemcu-esp32.png)
Also supported: Board with Ethernet-Connector and Power-over-Ethernet [Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware)
### NRF24L01+ radio board (See inverter table above for supported inverters)
The PLUS sign is IMPORTANT! There are different variants available, with antenna on the printed circuit board or external antenna.
Sample picture:
![nrf24l01plus](nrf24l01plus.png)
Buy your hardware from a trusted source, at best from a dealer/online shop in your country where you have support and the right to return non-functional hardware.
When you want to buy from Amazon, AliExpress, eBay etc., take note that there is a lot of low-quality or fake hardware offered. Read customer comments and ratings carefully!
A heavily incomplete list of trusted hardware shops in Germany is:
* [AZ-Delivery](https://www.az-delivery.de/)
* [Makershop](https://www.makershop.de/)
* [Berrybase](https://www.berrybase.de/)
This list is for your convenience only, the project is not related to any of these shops.
### CMT2300A radio board (See inverter table above for supported inverters)
It is important to get a module which supports SPI communication. The following modules are currently supported:
* EBYTE E49-900M20S
The CMT2300A uses 3-Wire half duplex SPI communication. Due to this fact it currently requires a separate SPI bus. If you want to run the CMT2300A module on the same ESP32 as a NRF24L01+ module or a PCD8544 display make sure you get a ESP which supports 2 SPI busses. Currently the SPI bus host is hardcoded to number 2. This may change in future.
### 3.3V / 5V logic level converter
The logic level converter is used to interface with the Victron MPPT charge controller and the relay board. It converts the 3.3V logic level used by the ESP32 to 5V logic used by the other devices.
### SN65HVD230 CAN bus transceiver
The SN65HVD230 CAN bus transceiver is used to interface with the Pylontech battery. It leverages the CAN bus controller of the ESP32. This CAN bus operates at 500kbit/s
### MCP2515 CAN bus module
The MCP2515 CAN bus module consists of a CAN bus controller and a CAN bus transceiver and is used to interface with the Huawei AC charger. This CAN bus operates at 125kbit/s. The module is connected via SPI and currently requires a separate SPI bus. If you want to use the Huawei AC charger make sure to get an ESP which supports 2 SPI busses. Currently the SPI bus host is hardcoded to number 2. This may change in future. Please note: Using the Huawei AC charger in combination with the CMT2300A radio board is not supported at the moment.
MCP2515 CAN bus modules that are widely available are designed for 5V supply voltage. To make them work with 3.3V / the ESP32 a modification is required. [This modification is described here.](https://forums.raspberrypi.com/viewtopic.php?t=141052)
### Relay module
The Huawei PSU can be switched on / off using the slot detect port. This is done by this relay.
### Power supply
Use a power supply with 5 V and 1 A. The USB cable connected to your PC/Notebook may be powerful enough or may be not.
## Wiring up the NRF24L01+ module
### Schematic
![Schematic](Wiring_ESP32_Schematic.png)
### Symbolic view
![Symbolic](Wiring_ESP32_Symbol.png)
### Change pin assignment
Its possible to change all the pins of the NRF24L01+ module, the Display, the LED etc.
The recommend way to change the pin assignment is by creating a custom [device profile](DeviceProfiles.md).
It is also possible to create a custom environment and compile the source yourself. This can be achieved by copying one of the [env:....] sections from 'platformio.ini' to 'platformio_override.ini' and editing the 'platformio_override.ini' file and add/change one or more of the following lines to the 'build_flags' parameter:
```makefile
-DHOYMILES_PIN_MISO=19
-DHOYMILES_PIN_MOSI=23
-DHOYMILES_PIN_SCLK=18
-DHOYMILES_PIN_IRQ=16
-DHOYMILES_PIN_CE=4
-DHOYMILES_PIN_CS=5
-DVICTRON_PIN_TX=21
-DVICTRON_PIN_RX=22
-DPYLONTECH_PIN_RX=27
-DPYLONTECH_PIN_TX=14
-DHUAWEI_PIN_MISO=12
-DHUAWEI_PIN_MOSI=13
-DHUAWEI_PIN_SCLK=26
-DHUAWEI_PIN_IRQ=25
-DHUAWEI_PIN_CS=15
-DHUAWEI_PIN_POWER=33
```
It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy.
## Flashing and starting up
### with Visual Studio Code
* Install [Visual Studio Code](https://code.visualstudio.com/download) (from now named "vscode")
* In Visual Studio Code, install the [PlatformIO Extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide)
* Install git and enable git in vscode - [git download](https://git-scm.com/downloads/) - [Instructions](https://www.jcchouinard.com/install-git-in-vscode/)
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur): Inside vscode open the command palette by pressing `CTRL` + `SHIFT` + `P`. Enter `git clone`, add the repository-URL `https://github.com/tbnobody/OpenDTU`. Next you have to choose (or create) a target directory.
* In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" and "platformio_override.ini" file)
* Adjust the COM port in the file "platformio_override.ini" for your USB-to-serial-converter. It occurs twice:
* upload_port
* monitor_port
* Select the arrow button in the blue bottom status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
* Under Linux, if the upload fails with error messages "Could not open /dev/ttyUSB0, the port doesn't exist", you can check via ```ls -la /dev/tty*``` to which group your port belongs to, and then add your user this group via ```sudo adduser <yourusername> dialout``` (if you are using ```arch-linux``` use: ```sudo gpasswd -a <yourusername> uucp```, this method requires a logout/login of the affected user).
* There are two videos showing these steps:
* [Git Clone and compilation](https://youtu.be/9cA_esv3zeA)
* [Full installation and compilation](https://youtu.be/xs6TqHn7QWM)
### on the commandline with PlatformIO Core
* Install [PlatformIO Core](https://platformio.org/install/cli)
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur)
* Adjust the COM port in the file "platformio_override.ini". It occurs twice:
* upload_port
* monitor_port
* build: `platformio run -e generic`
* upload to esp module: `platformio run -e generic -t upload`
* other options:
* clean the sources: `platformio run -e generic -t clean`
* erase flash: `platformio run -e generic -t erase`
### using the pre-compiled .bin files
The pre-compiled files can be found on the [github page](https://github.com/tbnobody/OpenDTU) in the tab "Actions" and the sub menu "OpenDTU Build". Just choose the latest build from the master branch (search for "master" in the blue font text but click on the white header text!). You need to be logged in with your github account to download the files.
Use a ESP32 flash tool of your choice (see next chapter) and flash the `.bin` files to the right addresses:
| Address | File |
| ---------| ---------------------- |
| 0x1000 | bootloader.bin |
| 0x8000 | partitions.bin |
| 0xe000 | boot_app0.bin |
| 0x10000 | opendtu-*.bin |
For further updates you can just use the web interface and upload the `opendtu-*.bin` file.
#### Flash with esptool.py (Linux)
```bash
esptool.py --port /dev/ttyUSB0 --chip esp32 --before default_reset --after hard_reset \
write_flash --flash_mode dout --flash_freq 40m --flash_size detect \
0x1000 bootloader.bin \
0x8000 partitions.bin \
0xe000 boot_app0.bin \
0x10000 opendtu-generic.bin
```
#### Flash with Espressif Flash Download Tool (Windows)
[Download link](https://www.espressif.com/en/support/download/other-tools)
* On startup, select Chip Type -> "ESP32" / WorkMode -> "Develop"
* Prepare all settings (see picture). Make sure to uncheck the `DoNotChgBin` option. Otherwise you may get errors like "invalid header".
* ![flash tool image](esp32_flash_download_tool.png)
* Press "Erase" button on screen. Look into the terminal window, you should see dots appear. Then press the "Boot" button on the ESP32 board. Wait for "FINISH" to see if flashing/erasing is done.
* To program, press "Start" on screen, then the "Boot" button.
* When flashing is complete (FINISH appears) then press the Reset button on the ESP32 board (or powercycle ) to start the OpenDTU application.
#### Flash with ESP_Flasher (Windows)
Users report that [ESP_Flasher](https://github.com/Jason2866/ESP_Flasher/releases/) is suitable for flashing OpenDTU on Windows.
#### Flash with [ESP_Flasher](https://espressif.github.io/esptool-js/) - web version
It is also possible to flash it via the web tools which might be more convenient and is platform independent.
## Flashing an Update using "Over The Air" OTA Update
Once you have your OpenDTU running and connected to WLAN, you can do further updates through the web interface.
Navigate to Settings --> Firmware upgrade and press the browse button. Select the firmware file from your local computer.
You'll find the firmware file (after a successful build process) under `.pio/build/generic/firmware.bin`.
If you downloaded a precompiled zip archive, unpack it and choose `opendtu-generic.bin`.
After the successful upload, the OpenDTU immediately restarts into the new firmware.
## Builds
Different builds from existing installations can be found here [Builds](builds/README.md)
Like to show your own build? Just send me a Pull Request.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -44,7 +44,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
{ FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT }, { FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT },
{ FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE },
{ FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE },
{ FLD_PRA, DEVICE_CLS_REACTIVE_POWER, STATE_CLS_MEASUREMENT } { FLD_Q, DEVICE_CLS_REACTIVE_POWER, STATE_CLS_MEASUREMENT }
}; };
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass_t)) #define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass_t))

View File

@ -33,7 +33,7 @@ private:
FLD_PF, FLD_PF,
FLD_EFF, FLD_EFF,
FLD_IRR, FLD_IRR,
FLD_PRA FLD_Q
}; };
}; };

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <TimeoutHelper.h>
class MqttHandleInverterTotalClass {
public:
void init();
void loop();
private:
TimeoutHelper _lastPublish;
};
extern MqttHandleInverterTotalClass MqttHandleInverterTotal;

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
class MqttHandlePowerLimiterClass {
public:
void init();
void loop();
private:
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
uint32_t _lastPublishStats;
uint32_t _lastPublish;
};
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;

View File

@ -8,10 +8,8 @@
#include <memory> #include <memory>
typedef enum { typedef enum {
STATE_DISCOVER = 0, SHUTDOWN = 0,
STATE_OFF, ACTIVE
STATE_CONSUME_SOLAR_POWER_ONLY,
STATE_NORMAL_OPERATION
} plStates; } plStates;
typedef enum { typedef enum {
@ -26,13 +24,16 @@ public:
void loop(); void loop();
plStates getPowerLimiterState(); plStates getPowerLimiterState();
int32_t getLastRequestedPowewrLimit(); int32_t getLastRequestedPowewrLimit();
void setDisable(bool disable);
bool getDisable();
private: private:
uint32_t _lastCommandSent = 0;
uint32_t _lastLoop = 0; uint32_t _lastLoop = 0;
int32_t _lastRequestedPowerLimit = 0; int32_t _lastRequestedPowerLimit = 0;
uint32_t _lastLimitSetTime = 0; uint32_t _lastLimitSetTime = 0;
plStates _plState = STATE_DISCOVER; plStates _plState = ACTIVE;
bool _disabled = false;
bool _batteryDischargeEnabled = false;
float _powerMeter1Power; float _powerMeter1Power;
float _powerMeter2Power; float _powerMeter2Power;

View File

@ -40,6 +40,6 @@ private:
{ FLD_PF, METRIC_TYPE_GAUGE }, { FLD_PF, METRIC_TYPE_GAUGE },
{ FLD_EFF, METRIC_TYPE_GAUGE }, { FLD_EFF, METRIC_TYPE_GAUGE },
{ FLD_IRR, METRIC_TYPE_GAUGE }, { FLD_IRR, METRIC_TYPE_GAUGE },
{ FLD_PRA, METRIC_TYPE_GAUGE } { FLD_Q, METRIC_TYPE_GAUGE }
}; };
}; };

View File

@ -3,10 +3,16 @@
#include <driver/spi_master.h> #include <driver/spi_master.h>
#include <esp_rom_gpio.h> // for esp_rom_gpio_connect_out_signal #include <esp_rom_gpio.h> // for esp_rom_gpio_connect_out_signal
SemaphoreHandle_t paramLock=NULL;
#define SPI_PARAM_LOCK() do {} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS)
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock)
spi_device_handle_t spi_reg, spi_fifo; spi_device_handle_t spi_reg, spi_fifo;
void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed) void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed)
{ {
paramLock = xSemaphoreCreateMutex();
spi_bus_config_t buscfg = { spi_bus_config_t buscfg = {
.mosi_io_num = pin_sdio, .mosi_io_num = pin_sdio,
.miso_io_num = -1, // single wire MOSI/MISO .miso_io_num = -1, // single wire MOSI/MISO
@ -62,7 +68,9 @@ void cmt_spi3_write(uint8_t addr, uint8_t dat)
.tx_buffer = &tx_data, .tx_buffer = &tx_data,
.rx_buffer = NULL .rx_buffer = NULL
}; };
SPI_PARAM_LOCK();
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
SPI_PARAM_UNLOCK();
delayMicroseconds(100); delayMicroseconds(100);
} }
@ -76,7 +84,9 @@ uint8_t cmt_spi3_read(uint8_t addr)
.tx_buffer = &tx_data, .tx_buffer = &tx_data,
.rx_buffer = &rx_data .rx_buffer = &rx_data
}; };
SPI_PARAM_LOCK();
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
SPI_PARAM_UNLOCK();
delayMicroseconds(100); delayMicroseconds(100);
return rx_data; return rx_data;
} }
@ -92,11 +102,13 @@ void cmt_spi3_write_fifo(const uint8_t* buf, uint16_t len)
.rx_buffer = NULL .rx_buffer = NULL
}; };
SPI_PARAM_LOCK();
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < len; i++) {
tx_data = ~buf[i]; // negate buffer contents tx_data = ~buf[i]; // negate buffer contents
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
delayMicroseconds(4); // > 4 us delayMicroseconds(4); // > 4 us
} }
SPI_PARAM_UNLOCK();
} }
void cmt_spi3_read_fifo(uint8_t* buf, uint16_t len) void cmt_spi3_read_fifo(uint8_t* buf, uint16_t len)
@ -110,9 +122,11 @@ void cmt_spi3_read_fifo(uint8_t* buf, uint16_t len)
.rx_buffer = &rx_data .rx_buffer = &rx_data
}; };
SPI_PARAM_LOCK();
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < len; i++) {
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
delayMicroseconds(4); // > 4 us delayMicroseconds(4); // > 4 us
buf[i] = rx_data; buf[i] = rx_data;
} }
SPI_PARAM_UNLOCK();
} }

View File

@ -54,7 +54,7 @@ void HoymilesClass::loop()
} }
} }
if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isIdle()) { if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) {
_messageOutput->print("Fetch inverter: "); _messageOutput->print("Fetch inverter: ");
_messageOutput->println(iv->serial(), HEX); _messageOutput->println(iv->serial(), HEX);

View File

@ -66,12 +66,12 @@ bool HoymilesRadio::isInitialized()
return _isInitialized; return _isInitialized;
} }
bool HoymilesRadio::isConfigured()
{
return _isConfigured;
}
bool HoymilesRadio::isIdle() bool HoymilesRadio::isIdle()
{ {
return !_busyFlag; return !_busyFlag;
} }
bool HoymilesRadio::isQueueEmpty()
{
return _commandQueue.size() == 0;
}

View File

@ -12,8 +12,8 @@ public:
virtual void setDtuSerial(uint64_t serial); virtual void setDtuSerial(uint64_t serial);
bool isIdle(); bool isIdle();
bool isQueueEmpty();
bool isInitialized(); bool isInitialized();
bool isConfigured();
template <typename T> template <typename T>
T* enqueCommand() T* enqueCommand()
@ -34,6 +34,5 @@ protected:
serial_u _dtuSerial; serial_u _dtuSerial;
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue; std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
bool _isInitialized = false; bool _isInitialized = false;
bool _isConfigured = false;
bool _busyFlag = false; bool _busyFlag = false;
}; };

View File

@ -63,7 +63,6 @@ void HoymilesRadio_CMT::init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int
cmtSwitchDtuFreq(_inverterTargetFrequency); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched cmtSwitchDtuFreq(_inverterTargetFrequency); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched
_isConfigured = true;
if (!_radio->isChipConnected()) { if (!_radio->isChipConnected()) {
Hoymiles.getMessageOutput()->println("CMT: Connection error!!"); Hoymiles.getMessageOutput()->println("CMT: Connection error!!");
return; return;

View File

@ -23,7 +23,6 @@ void HoymilesRadio_NRF::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t
_radio->setAddressWidth(5); _radio->setAddressWidth(5);
_radio->setRetries(0, 0); _radio->setRetries(0, 0);
_radio->maskIRQ(true, true, false); // enable only receiving interrupts _radio->maskIRQ(true, true, false); // enable only receiving interrupts
_isConfigured = true;
if (!_radio->isChipConnected()) { if (!_radio->isChipConnected()) {
Hoymiles.getMessageOutput()->println("NRF: Connection error!!"); Hoymiles.getMessageOutput()->println("NRF: Connection error!!");
return; return;

View File

@ -57,5 +57,5 @@ protected:
uint64_t _routerAddress; uint64_t _routerAddress;
private: private:
void convertSerialToPacketId(uint8_t buffer[], uint64_t serial); static void convertSerialToPacketId(uint8_t buffer[], uint64_t serial);
}; };

View File

@ -4,6 +4,30 @@
*/ */
#include "HMS_1CH.h" #include "HMS_1CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HMS_1CH::HMS_1CH(HoymilesRadio* radio, uint64_t serial) HMS_1CH::HMS_1CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -19,7 +43,12 @@ String HMS_1CH::typeName()
return "HMS-300, HMS-350, HMS-400, HMS-450, HMS-500"; return "HMS-300, HMS-350, HMS-400, HMS-450, HMS-500";
} }
const std::list<byteAssign_t>* HMS_1CH::getByteAssignment() const byteAssign_t* HMS_1CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HMS_1CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -9,30 +9,6 @@ public:
explicit HMS_1CH(HoymilesRadio* radio, uint64_t serial); explicit HMS_1CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -4,6 +4,37 @@
*/ */
#include "HMS_2CH.h" #include "HMS_2CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HMS_2CH::HMS_2CH(HoymilesRadio* radio, uint64_t serial) HMS_2CH::HMS_2CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -19,7 +50,12 @@ String HMS_2CH::typeName()
return "HMS-600, HMS-700, HMS-800, HMS-900, HMS-1000"; return "HMS-600, HMS-700, HMS-800, HMS-900, HMS-1000";
} }
const std::list<byteAssign_t>* HMS_2CH::getByteAssignment() const byteAssign_t* HMS_2CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HMS_2CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -9,37 +9,6 @@ public:
explicit HMS_2CH(HoymilesRadio* radio, uint64_t serial); explicit HMS_2CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -4,6 +4,51 @@
*/ */
#include "HMS_4CH.h" #include "HMS_4CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 30, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 34, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 46, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 28, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 32, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 36, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 48, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 42, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 58, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 56, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 52, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 60, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 62, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 64, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HMS_4CH::HMS_4CH(HoymilesRadio* radio, uint64_t serial) HMS_4CH::HMS_4CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -19,7 +64,12 @@ String HMS_4CH::typeName()
return "HMS-1600, HMS-1800, HMS-2000"; return "HMS-1600, HMS-1800, HMS-2000";
} }
const std::list<byteAssign_t>* HMS_4CH::getByteAssignment() const byteAssign_t* HMS_4CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HMS_4CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -8,51 +8,6 @@ public:
explicit HMS_4CH(HoymilesRadio* radio, uint64_t serial); explicit HMS_4CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 30, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 34, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 46, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 28, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 32, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 36, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 48, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 42, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 58, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 56, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 52, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 60, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 62, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 64, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -4,6 +4,74 @@
*/ */
#include "HMT_6CH.h" #include "HMT_6CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_DC, CH4, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_IDC, UNIT_A, 48, 2, 100, false, 2 },
{ TYPE_DC, CH4, FLD_PDC, UNIT_W, 52, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_YT, UNIT_KWH, 56, 4, 1000, false, 3 },
{ TYPE_DC, CH4, FLD_YD, UNIT_WH, 64, 2, 1, false, 0 },
{ TYPE_DC, CH4, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH4, CMD_CALC, false, 3 },
{ TYPE_DC, CH5, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_IDC, UNIT_A, 50, 2, 100, false, 2 },
{ TYPE_DC, CH5, FLD_PDC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_YT, UNIT_KWH, 60, 4, 1000, false, 3 },
{ TYPE_DC, CH5, FLD_YD, UNIT_WH, 66, 2, 1, false, 0 },
{ TYPE_DC, CH5, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH5, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 74, 2, 10, false, 1 }, // dummy
{ TYPE_AC, CH0, FLD_UAC_1N, UNIT_V, 68, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_2N, UNIT_V, 70, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_3N, UNIT_V, 72, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_12, UNIT_V, 74, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_23, UNIT_V, 76, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_31, UNIT_V, 78, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 80, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 82, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 84, 2, 10, true, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 86, 2, 100, false, 2 }, // dummy
{ TYPE_AC, CH0, FLD_IAC_1, UNIT_A, 86, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_2, UNIT_A, 88, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_3, UNIT_A, 90, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 92, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 94, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 96, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HMT_6CH::HMT_6CH(HoymilesRadio* radio, uint64_t serial) HMT_6CH::HMT_6CH(HoymilesRadio* radio, uint64_t serial)
: HMT_Abstract(radio, serial) {}; : HMT_Abstract(radio, serial) {};
@ -19,7 +87,12 @@ String HMT_6CH::typeName()
return F("HMT-1800, HMT-2250"); return F("HMT-1800, HMT-2250");
} }
const std::list<byteAssign_t>* HMT_6CH::getByteAssignment() const byteAssign_t* HMT_6CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HMT_6CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -8,74 +8,6 @@ public:
explicit HMT_6CH(HoymilesRadio* radio, uint64_t serial); explicit HMT_6CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_DC, CH4, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_IDC, UNIT_A, 48, 2, 100, false, 2 },
{ TYPE_DC, CH4, FLD_PDC, UNIT_W, 52, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_YT, UNIT_KWH, 56, 4, 1000, false, 3 },
{ TYPE_DC, CH4, FLD_YD, UNIT_WH, 64, 2, 1, false, 0 },
{ TYPE_DC, CH4, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH4, CMD_CALC, false, 3 },
{ TYPE_DC, CH5, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_IDC, UNIT_A, 50, 2, 100, false, 2 },
{ TYPE_DC, CH5, FLD_PDC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_YT, UNIT_KWH, 60, 4, 1000, false, 3 },
{ TYPE_DC, CH5, FLD_YD, UNIT_WH, 66, 2, 1, false, 0 },
{ TYPE_DC, CH5, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH5, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 74, 2, 10, false, 1 }, // dummy
{ TYPE_AC, CH0, FLD_UAC_1N, UNIT_V, 68, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_2N, UNIT_V, 70, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_3N, UNIT_V, 72, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_12, UNIT_V, 74, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_23, UNIT_V, 76, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_31, UNIT_V, 78, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 80, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 82, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 84, 2, 10, true, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 86, 2, 100, false, 2 }, // dummy
{ TYPE_AC, CH0, FLD_IAC_1, UNIT_A, 86, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_2, UNIT_A, 88, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_3, UNIT_A, 90, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 92, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 94, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 96, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -4,6 +4,30 @@
*/ */
#include "HM_1CH.h" #include "HM_1CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HM_1CH::HM_1CH(HoymilesRadio* radio, uint64_t serial) HM_1CH::HM_1CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -32,7 +56,12 @@ String HM_1CH::typeName()
return "HM-300, HM-350, HM-400"; return "HM-300, HM-350, HM-400";
} }
const std::list<byteAssign_t>* HM_1CH::getByteAssignment() const byteAssign_t* HM_1CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HM_1CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -9,30 +9,6 @@ public:
explicit HM_1CH(HoymilesRadio* radio, uint64_t serial); explicit HM_1CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -5,6 +5,37 @@
*/ */
#include "HM_2CH.h" #include "HM_2CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 8, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 10, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HM_2CH::HM_2CH(HoymilesRadio* radio, uint64_t serial) HM_2CH::HM_2CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -33,7 +64,12 @@ String HM_2CH::typeName()
return "HM-600, HM-700, HM-800"; return "HM-600, HM-700, HM-800";
} }
const std::list<byteAssign_t>* HM_2CH::getByteAssignment() const byteAssign_t* HM_2CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HM_2CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -8,37 +8,6 @@ public:
explicit HM_2CH(HoymilesRadio* radio, uint64_t serial); explicit HM_2CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 8, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 10, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -4,6 +4,51 @@
*/ */
#include "HM_4CH.h" #include "HM_4CH.h"
static const byteAssign_t byteAssignment[] = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, CALC_UDC_CH, CH0, CMD_CALC, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, CALC_UDC_CH, CH2, CMD_CALC, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 54, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 52, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 48, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 56, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 58, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 60, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
HM_4CH::HM_4CH(HoymilesRadio* radio, uint64_t serial) HM_4CH::HM_4CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -32,7 +77,12 @@ String HM_4CH::typeName()
return "HM-1000, HM-1200, HM-1500"; return "HM-1000, HM-1200, HM-1500";
} }
const std::list<byteAssign_t>* HM_4CH::getByteAssignment() const byteAssign_t* HM_4CH::getByteAssignment()
{ {
return &byteAssignment; return byteAssignment;
}
uint8_t HM_4CH::getByteAssignmentSize()
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
} }

View File

@ -8,51 +8,6 @@ public:
explicit HM_4CH(HoymilesRadio* radio, uint64_t serial); explicit HM_4CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const std::list<byteAssign_t>* getByteAssignment(); const byteAssign_t* getByteAssignment();
uint8_t getByteAssignmentSize();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, CALC_UDC_CH, CH0, CMD_CALC, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, CALC_UDC_CH, CH2, CMD_CALC, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 54, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 52, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 48, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 56, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 58, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 60, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
}; };

View File

@ -31,7 +31,7 @@ void InverterAbstract::init()
// Not possible in constructor --> virtual function // Not possible in constructor --> virtual function
// Not possible in verifyAllFragments --> Because no data if nothing is ever received // Not possible in verifyAllFragments --> Because no data if nothing is ever received
// It has to be executed because otherwise the getChannelCount method in stats always returns 0 // It has to be executed because otherwise the getChannelCount method in stats always returns 0
_statisticsParser.get()->setByteAssignment(getByteAssignment()); _statisticsParser.get()->setByteAssignment(getByteAssignment(), getByteAssignmentSize());
} }
uint64_t InverterAbstract::serial() uint64_t InverterAbstract::serial()

View File

@ -37,7 +37,8 @@ public:
void setName(const char* name); void setName(const char* name);
const char* name(); const char* name();
virtual String typeName() = 0; virtual String typeName() = 0;
virtual const std::list<byteAssign_t>* getByteAssignment() = 0; virtual const byteAssign_t* getByteAssignment() = 0;
virtual uint8_t getByteAssignmentSize() = 0;
bool isProducing(); bool isProducing();
bool isReachable(); bool isReachable();

View File

@ -28,9 +28,10 @@ const calcFunc_t calcFunctions[] = {
{ CALC_IRR_CH, &calcIrradiation } { CALC_IRR_CH, &calcIrradiation }
}; };
void StatisticsParser::setByteAssignment(const std::list<byteAssign_t>* byteAssignment) void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size)
{ {
_byteAssignment = byteAssignment; _byteAssignment = byteAssignment;
_byteAssignmentSize = size;
} }
void StatisticsParser::clearBuffer() void StatisticsParser::clearBuffer()
@ -51,9 +52,9 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t
const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{ {
for (auto const& i : *_byteAssignment) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) {
if (i.type == type && i.ch == channel && i.fieldId == fieldId) { if (_byteAssignment[i].type == type && _byteAssignment[i].ch == channel && _byteAssignment[i].fieldId == fieldId) {
return &i; return &_byteAssignment[i];
} }
} }
return NULL; return NULL;
@ -172,9 +173,9 @@ const char* StatisticsParser::getChannelTypeName(ChannelType_t type)
std::list<ChannelNum_t> StatisticsParser::getChannelsByType(ChannelType_t type) std::list<ChannelNum_t> StatisticsParser::getChannelsByType(ChannelType_t type)
{ {
std::list<ChannelNum_t> l; std::list<ChannelNum_t> l;
for (auto const& b : *_byteAssignment) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) {
if (b.type == type) { if (_byteAssignment[i].type == type) {
l.push_back(b.ch); l.push_back(_byteAssignment[i].ch);
} }
} }
l.unique(); l.unique();

View File

@ -17,7 +17,7 @@ enum UnitId_t {
UNIT_HZ, UNIT_HZ,
UNIT_C, UNIT_C,
UNIT_PCT, UNIT_PCT,
UNIT_VA, UNIT_VAR,
UNIT_NONE UNIT_NONE
}; };
const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", "" }; const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", "" };
@ -37,7 +37,7 @@ enum FieldId_t {
FLD_PF, FLD_PF,
FLD_EFF, FLD_EFF,
FLD_IRR, FLD_IRR,
FLD_PRA, FLD_Q,
FLD_EVT_LOG, FLD_EVT_LOG,
// HMT only // HMT only
FLD_UAC_1N, FLD_UAC_1N,
@ -107,7 +107,7 @@ public:
void clearBuffer(); void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void setByteAssignment(const std::list<byteAssign_t>* byteAssignment); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size);
const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
@ -137,7 +137,8 @@ private:
uint8_t _statisticLength = 0; uint8_t _statisticLength = 0;
uint16_t _stringMaxPower[CH_CNT]; uint16_t _stringMaxPower[CH_CNT];
const std::list<byteAssign_t>* _byteAssignment; const byteAssign_t* _byteAssignment;
uint8_t _byteAssignmentSize;
std::list<fieldSettings_t> _fieldSettings; std::list<fieldSettings_t> _fieldSettings;
uint32_t _rxFailureCount = 0; uint32_t _rxFailureCount = 0;

View File

@ -60,6 +60,7 @@ VeDirectFrameHandler::VeDirectFrameHandler() :
_state(IDLE), _state(IDLE),
_checksum(0), _checksum(0),
_textPointer(0), _textPointer(0),
_hexSize(0),
_name(""), _name(""),
_value(""), _value(""),
_tmpFrame(), _tmpFrame(),
@ -99,7 +100,9 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
{ {
//if (mStop) return; //if (mStop) return;
if ( (inbyte == ':') && (_state != CHECKSUM) ) { if ( (inbyte == ':') && (_state != CHECKSUM) ) {
_prevState = _state; //hex frame can interrupt TEXT
_state = RECORD_HEX; _state = RECORD_HEX;
_hexSize = 0;
} }
if (_state != RECORD_HEX) { if (_state != RECORD_HEX) {
_checksum += inbyte; _checksum += inbyte;
@ -177,10 +180,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
break; break;
} }
case RECORD_HEX: case RECORD_HEX:
if (hexRxEvent(inbyte)) { _state = hexRxEvent(inbyte);
_checksum = 0;
_state = IDLE;
}
break; break;
} }
} }
@ -279,10 +279,27 @@ void VeDirectFrameHandler::logE(const char * module, const char * error) {
/* /*
* hexRxEvent * hexRxEvent
* This function included for continuity and possible future use. * This function records hex answers or async messages
*/ */
bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
return true; // stubbed out for future int ret=RECORD_HEX; // default - continue recording until end of frame
switch (inbyte) {
case '\n':
// restore previous state
ret=_prevState;
break;
default:
_hexSize++;
if (_hexSize>=VE_MAX_HEX_LEN) { // oops -buffer overflow - something went wrong, we abort
logE(MODULE,"hexRx buffer overflow - aborting read");
_hexSize=0;
ret=IDLE;
}
}
return ret;
} }
bool VeDirectFrameHandler::isDataValid() { bool VeDirectFrameHandler::isDataValid() {
@ -510,6 +527,9 @@ String VeDirectFrameHandler::getPidAsString(uint16_t pid)
case 0XA10F: case 0XA10F:
strPID = "BlueSolar MPPT VE.Can 150|100"; strPID = "BlueSolar MPPT VE.Can 150|100";
break; break;
case 0XA110:
strPID = "SmartSolar MPPT RS 450|100";
break;
case 0XA112: case 0XA112:
strPID = "BlueSolar MPPT VE.Can 250|70"; strPID = "BlueSolar MPPT VE.Can 250|70";
break; break;

View File

@ -23,6 +23,8 @@
#define VE_MAX_NAME_LEN 9 // VE.Direct Protocol: max name size is 9 including /0 #define VE_MAX_NAME_LEN 9 // VE.Direct Protocol: max name size is 9 including /0
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0 #define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
typedef struct { typedef struct {
uint16_t PID; // product id uint16_t PID; // product id
@ -69,12 +71,14 @@ private:
void textRxEvent(char *, char *); void textRxEvent(char *, char *);
void frameEndEvent(bool); // copy temp struct to public struct void frameEndEvent(bool); // copy temp struct to public struct
void logE(const char *, const char *); void logE(const char *, const char *);
bool hexRxEvent(uint8_t); int hexRxEvent(uint8_t);
//bool mStop; // not sure what Victron uses this for, not using //bool mStop; // not sure what Victron uses this for, not using
int _state; // current state int _state; // current state
int _prevState; // previous state
uint8_t _checksum; // checksum value uint8_t _checksum; // checksum value
char * _textPointer; // pointer to the private buffer we're writing to, name or value char * _textPointer; // pointer to the private buffer we're writing to, name or value
int _hexSize; // length of hex buffer
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
veStruct _tmpFrame{}; // private struct for received name and value pairs veStruct _tmpFrame{}; // private struct for received name and value pairs

View File

@ -0,0 +1,13 @@
diff --git a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
index 12be5f8..8505f73 100644
--- a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
+++ b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
@@ -737,7 +737,7 @@ void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len)
IPAddress AsyncWebSocketClient::remoteIP() const
{
if (!_client)
- return IPAddress(0U);
+ return IPAddress((uint32_t)0);
return _client->remoteIP();
}

View File

@ -0,0 +1,78 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2023 Thomas Basler and others
#
import os
import subprocess
import re
Import("env")
def getPatchPath(env):
return os.path.join(env["PROJECT_DIR"], "patches", env.GetProjectOption('custom_patches'))
def is_tool(name):
"""Check whether `name` is on PATH and marked as executable."""
# from whichcraft import which
from shutil import which
return which(name) is not None
def replaceInFile(in_file, out_file, text, subs, flags=0):
"""
Function for replacing content for the given file
Taken from https://www.studytonight.com/python-howtos/search-and-replace-a-text-in-a-file-in-python
"""
if os.path.exists(in_file):
with open(in_file, "rb") as infile:
with open(out_file, "wb") as outfile:
#read the file contents
file_contents = infile.read()
text_pattern = re.compile(re.escape(text), flags)
file_contents = text_pattern.sub(subs, file_contents.decode('utf-8'))
outfile.seek(0)
outfile.truncate()
outfile.write(file_contents.encode())
def main():
if (env.GetProjectOption('custom_patches', '') == ''):
print('No custom_patches specified')
return
if (not is_tool('git')):
print('Git not found. Will not apply custom patches!')
return
directory = getPatchPath(env)
if (not os.path.isdir(directory)):
print('Patch directory not found: ' + directory)
return
for file in os.listdir(directory):
if (not file.endswith('.patch')):
continue
fullPath = os.path.join(directory, file)
preparePath = fullPath + '.prepare'
replaceInFile(fullPath, preparePath, '$$$env$$$', env['PIOENV'])
print('Working on patch: ' + fullPath + '... ', end='')
# Check if patch was already applied
process = subprocess.run(['git', 'apply', '--reverse', '--check', preparePath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if (process.returncode == 0):
print('already applied')
os.remove(preparePath)
continue
# Apply patch
process = subprocess.run(['git', 'apply', preparePath])
if (process.returncode == 0):
print('applied')
else:
print('failed')
os.remove(preparePath)
main()

View File

@ -37,7 +37,8 @@ lib_deps =
mobizt/FirebaseJson @ ^3.0.6 mobizt/FirebaseJson @ ^3.0.6
extra_scripts = extra_scripts =
pre:auto_firmware_version.py pre:pio-scripts/auto_firmware_version.py
pre:pio-scripts/patch_apply.py
board_build.partitions = partitions_custom.csv board_build.partitions = partitions_custom.csv
board_build.filesystem = littlefs board_build.filesystem = littlefs
@ -71,6 +72,19 @@ build_flags = ${env.build_flags}
-DHUAWEI_PIN_POWER=33 -DHUAWEI_PIN_POWER=33
[env:generic_esp32c3]
board = esp32dev
board_build.mcu = esp32c3
custom_patches = esp32c3
build_flags = ${env.build_flags}
-DHOYMILES_PIN_MISO=9
-DHOYMILES_PIN_MOSI=10
-DHOYMILES_PIN_SCLK=8
-DHOYMILES_PIN_IRQ=3
-DHOYMILES_PIN_CE=4
-DHOYMILES_PIN_CS=5
[env:olimex_esp32_poe] [env:olimex_esp32_poe]
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware ; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware
board = esp32-poe board = esp32-poe

View File

@ -22,7 +22,7 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw
return; return;
} }
spi = new SPIClass(VSPI); spi = new SPIClass(HSPI);
spi->begin(huawei_clk, huawei_miso, huawei_mosi, huawei_cs); spi->begin(huawei_clk, huawei_miso, huawei_mosi, huawei_cs);
pinMode(huawei_cs, OUTPUT); pinMode(huawei_cs, OUTPUT);
digitalWrite(huawei_cs, HIGH); digitalWrite(huawei_cs, HIGH);

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "MqttHandleInverterTotal.h"
#include "Configuration.h"
#include "MqttSettings.h"
#include <Hoymiles.h>
MqttHandleInverterTotalClass MqttHandleInverterTotal;
void MqttHandleInverterTotalClass::init()
{
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
}
void MqttHandleInverterTotalClass::loop()
{
if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) {
return;
}
if (_lastPublish.occured()) {
float totalAcPower = 0;
float totalDcPower = 0;
float totalDcPowerIrr = 0;
float totalDcPowerIrrInst = 0;
float totalAcYieldDay = 0;
float totalAcYieldTotal = 0;
uint8_t totalAcPowerDigits = 0;
uint8_t totalDcPowerDigits = 0;
uint8_t totalAcYieldDayDigits = 0;
uint8_t totalAcYieldTotalDigits = 0;
bool totalReachable = true;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr || !inv->getEnablePolling()) {
continue;
}
if (!inv->isReachable()) {
totalReachable = false;
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
totalAcPower += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalAcPowerDigits = max<uint8_t>(totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC));
totalAcYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalAcYieldDayDigits = max<uint8_t>(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD));
totalAcYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
totalAcYieldTotalDigits = max<uint8_t>(totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT));
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) {
totalDcPower += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcPowerDigits = max<uint8_t>(totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC));
if (inv->Statistics()->getStringMaxPower(c) > 0) {
totalDcPowerIrr += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcPowerIrrInst += inv->Statistics()->getStringMaxPower(c);
}
}
}
MqttSettings.publish("ac/power", String(totalAcPower, static_cast<unsigned int>(totalAcPowerDigits)));
MqttSettings.publish("ac/yieldtotal", String(totalAcYieldTotal, static_cast<unsigned int>(totalAcYieldTotalDigits)));
MqttSettings.publish("ac/yieldday", String(totalAcYieldDay, static_cast<unsigned int>(totalAcYieldDayDigits)));
MqttSettings.publish("ac/is_valid", String(totalReachable));
MqttSettings.publish("dc/power", String(totalDcPower, static_cast<unsigned int>(totalAcPowerDigits)));
MqttSettings.publish("dc/irradiation", String(totalDcPowerIrrInst > 0 ? totalDcPowerIrr / totalDcPowerIrrInst * 100.0f : 0, 3));
MqttSettings.publish("dc/is_valid", String(totalReachable));
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
}
}

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
*/
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "MqttHandlePowerLimiter.h"
#include "PowerLimiter.h"
#include <ctime>
#define TOPIC_SUB_POWER_LIMITER "disable"
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
void MqttHandlePowerLimiterClass::init()
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
String topic = MqttSettings.getPrefix();
MqttSettings.subscribe(String(topic + "powerlimiter/cmd/" + TOPIC_SUB_POWER_LIMITER).c_str(), 0, std::bind(&MqttHandlePowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
_lastPublish = millis();
}
void MqttHandlePowerLimiterClass::loop()
{
if (!MqttSettings.getConnected() ) {
return;
}
const CONFIG_T& config = Configuration.get();
if ((millis() - _lastPublish) > (config.Mqtt_PublishInterval * 1000) ) {
MqttSettings.publish("powerlimiter/status/disabled", String(PowerLimiter.getDisable()));
yield();
_lastPublish = millis();
}
}
void MqttHandlePowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
{
const CONFIG_T& config = Configuration.get();
char token_topic[MQTT_MAX_TOPIC_STRLEN + 40]; // respect all subtopics
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
char* setting;
char* rest = &token_topic[strlen(config.Mqtt_Topic)];
strtok_r(rest, "/", &rest); // Remove "powerlimiter"
strtok_r(rest, "/", &rest); // Remove "cmd"
setting = strtok_r(rest, "/", &rest);
if (setting == NULL) {
return;
}
char* str = new char[len + 1];
memcpy(str, payload, len);
str[len] = '\0';
uint8_t payload_val = atoi(str);
delete[] str;
if (!strcmp(setting, TOPIC_SUB_POWER_LIMITER)) {
if(payload_val == 1) {
MessageOutput.println("Power limiter disabled");
PowerLimiter.setDisable(true);
return;
}
if(payload_val == 0) {
MessageOutput.println("Power limiter enabled");
PowerLimiter.setDisable(false);
return;
}
MessageOutput.println("Power limiter enable / disable - unknown command received. Please use 0 or 1");
}
}

View File

@ -23,13 +23,10 @@ void PowerLimiterClass::loop()
{ {
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
if (!config.PowerLimiter_Enabled // Run inital checks to make sure we have met the basic conditions
|| !config.PowerMeter_Enabled if (!config.PowerMeter_Enabled
|| !Hoymiles.isAllRadioIdle() || !Hoymiles.isAllRadioIdle()
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) { || (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
if (!config.PowerLimiter_Enabled)
_plState = STATE_DISCOVER; // ensure STATE_DISCOVER is set, if PowerLimiter will be enabled.
return; return;
} }
@ -40,106 +37,64 @@ void PowerLimiterClass::loop()
return; return;
} }
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC); // Make sure inverter is turned off if PL is disabled by user/MQTT
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC); // Make sure inverter is turned off when low battery threshold is reached
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor); if (((!config.PowerLimiter_Enabled || _disabled) && _plState != SHUTDOWN)
|| isStopThresholdReached(inverter)) {
if (inverter->isProducing()) {
MessageOutput.printf("PL initiated inverter shutdown.\r\n");
inverter->sendPowerControlRequest(false);
} else {
_plState = SHUTDOWN;
}
return;
}
// Return if power limiter is disabled
if (!config.PowerLimiter_Enabled || _disabled) {
return;
}
// At this point the PL is enabled but we could still be in the shutdown state
_plState = ACTIVE;
// If the last inverter update is too old, don't do anything. // If the last inverter update is too old, don't do anything.
// If the last inverter update was before the last limit updated, don't do anything. // If the last inverter update was before the last limit updated, don't do anything.
// Also give the Power meter 3 seconds time to recognize power changes because of the last set limit // Also give the Power meter 3 seconds time to recognize power changes after the last set limit
// and also because the Hoymiles MPPT might not react immediately. // as the Hoymiles MPPT might not react immediately.
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000 if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000
|| inverter->Statistics()->getLastUpdate() <= _lastLimitSetTime || inverter->Statistics()->getLastUpdate() <= _lastLimitSetTime
|| PowerMeter.getLastPowerMeterUpdate() <= (_lastLimitSetTime + 3000)) { || PowerMeter.getLastPowerMeterUpdate() <= (_lastLimitSetTime + 3000)) {
return; return;
} }
// Printout some stats
if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)) { if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)) {
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n", MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n",
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing()); dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
} }
while(true) {
switch(_plState) {
case STATE_DISCOVER:
if (!inverter->isProducing() || isStopThresholdReached(inverter)) {
_plState = STATE_OFF;
}
else if (canUseDirectSolarPower()) {
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
}
else {
_plState = STATE_NORMAL_OPERATION;
}
break;
case STATE_OFF:
// if on turn off
if (inverter->isProducing()) {
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
dcVoltage, correctedDcVoltage);
setNewPowerLimit(inverter, -1);
return;
}
// do nothing if battery is empty // Battery charging cycle conditions
if (isStopThresholdReached(inverter)) // The battery can only be discharged after a full charge in the
return; // EMPTY_WHEN_FULL case
// check for possible state changes if (isStopThresholdReached(inverter)) {
if (canUseDirectSolarPower()) { // Disable battery discharge when empty
_plState = STATE_CONSUME_SOLAR_POWER_ONLY; _batteryDischargeEnabled = false;
} } else if (!canUseDirectSolarPower() ||
if (isStartThresholdReached(inverter)) { config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT) {
_plState = STATE_NORMAL_OPERATION; // Enable battery discharge
} _batteryDischargeEnabled = true;
return;
break;
case STATE_CONSUME_SOLAR_POWER_ONLY: {
int32_t newPowerLimit = calcPowerLimit(inverter, true);
if (isStopThresholdReached(inverter)) {
_plState = STATE_OFF;
break;
}
if (isStartThresholdReached(inverter)) {
_plState = STATE_NORMAL_OPERATION;
break;
}
if (!canUseDirectSolarPower()) {
if (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT)
_plState = STATE_NORMAL_OPERATION;
else
_plState = STATE_OFF;
break;
}
setNewPowerLimit(inverter, newPowerLimit);
return;
break;
}
case STATE_NORMAL_OPERATION: {
int32_t newPowerLimit = calcPowerLimit(inverter, false);
if (isStopThresholdReached(inverter)) {
_plState = STATE_OFF;
break;
}
if (!isStartThresholdReached(inverter) && canUseDirectSolarPower() && (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT)) {
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
break;
}
// check if grid power consumption is not within the upper and lower threshold of the target consumption
if (newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
_lastRequestedPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
_lastRequestedPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis) ) {
return;
}
setNewPowerLimit(inverter, newPowerLimit);;
return;
break;
}
}
} }
// This checks if the battery discharge start conditions are met for the EMPTY_WHEN_FULL case
if (isStartThresholdReached(inverter) && config.PowerLimiter_BatteryDrainStategy == EMPTY_WHEN_FULL) {
_batteryDischargeEnabled = true;
}
// Calculate and set Power Limit
int32_t newPowerLimit = calcPowerLimit(inverter, !_batteryDischargeEnabled);
setNewPowerLimit(inverter, newPowerLimit);
} }
plStates PowerLimiterClass::getPowerLimiterState() { plStates PowerLimiterClass::getPowerLimiterState() {
@ -150,6 +105,14 @@ int32_t PowerLimiterClass::getLastRequestedPowewrLimit() {
return _lastRequestedPowerLimit; return _lastRequestedPowerLimit;
} }
bool PowerLimiterClass::getDisable() {
return _disabled;
}
void PowerLimiterClass::setDisable(bool disable) {
_disabled = disable;
}
bool PowerLimiterClass::canUseDirectSolarPower() bool PowerLimiterClass::canUseDirectSolarPower()
{ {
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
@ -167,69 +130,114 @@ bool PowerLimiterClass::canUseDirectSolarPower()
return true; return true;
} }
int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly) int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly)
{ {
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
int32_t newPowerLimit = round(PowerMeter.getPowerTotal()); int32_t newPowerLimit = round(PowerMeter.getPowerTotal());
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF); // Safety check, return on too old power meter values
int32_t victronChargePower = this->getDirectSolarPower(); if (millis() - PowerMeter.getLastPowerMeterUpdate() > (30 * 1000)
int32_t adjustedVictronChargePower = victronChargePower * (efficency > 0.0 ? (efficency / 100.0) : 1.0); // if inverter is off, use 1.0 && (millis() - inverter->Statistics()->getLastUpdate()) > (config.Dtu_PollInterval * 10 * 1000)) {
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s, powerConsumption: %d \r\n",
victronChargePower, efficency, consumeSolarPowerOnly ? "true" : "false", newPowerLimit);
// Safety check: Are the power meter values not too old?
// Are the reported inverter data not too old?
if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)
&& millis() - inverter->Statistics()->getLastUpdate() < (15 * 1000)) {
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
// If the inverter the behind the power meter (part of measurement),
// the produced power of this inverter has also to be taken into account.
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
newPowerLimit += static_cast<int>(acPower);
}
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
int32_t upperPowerLimit = config.PowerLimiter_UpperPowerLimit;
if (consumeSolarPowerOnly && (upperPowerLimit > adjustedVictronChargePower)) {
// Battery voltage too low, use Victron solar power (corrected by efficency factor) only
upperPowerLimit = adjustedVictronChargePower;
}
if (newPowerLimit > upperPowerLimit)
newPowerLimit = upperPowerLimit;
} else {
// If the power meter values are older than 30 seconds, // If the power meter values are older than 30 seconds,
// set the limit to config.PowerLimiter_LowerPowerLimit for safety reasons. // and the Inverter Stats are older then 10x the poll interval
newPowerLimit = config.PowerLimiter_LowerPowerLimit; // set the limit to 0W for safety reasons.
MessageOutput.println("[PowerLimiterClass::loop] Power Meter/Inverter values too old. Using 0W (i.e. disable inverter)");
return 0;
} }
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
// If the inverter the behind the power meter (part of measurement),
// the produced power of this inverter has also to be taken into account.
// We don't use FLD_PAC from the statistics, because that
// data might be too old and unreliable.
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
newPowerLimit += static_cast<int>(acPower);
}
// We're not trying to hit 0 exactly but take an offset into account
// This means we never fully compensate the used power with the inverter
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
// Check if the new value is within the limits of the hysteresis and
// if we're not limited to Solar Power only (i.e. we can discharge the battery)
// If things did not change much we just use the old setting
if (newPowerLimit >= (-config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
newPowerLimit <= (+config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
!consumeSolarPowerOnly ) {
MessageOutput.println("[PowerLimiterClass::loop] reusing old limit");
return _lastRequestedPowerLimit;
}
// We should use Victron solar power only (corrected by efficiency factor)
if (consumeSolarPowerOnly) {
float efficiency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
int32_t victronChargePower = this->getDirectSolarPower();
int32_t adjustedVictronChargePower = victronChargePower * (efficiency > 0.0 ? (efficiency / 100.0) : 1.0); // if inverter is off, use 1.0
MessageOutput.printf("[PowerLimiterClass::loop] Consuming Solar Power Only -> victronChargePower: %d, efficiency: %.2f, powerConsumption: %d \r\n",
victronChargePower, efficiency, newPowerLimit);
// Limit power to solar power only
if (adjustedVictronChargePower < newPowerLimit)
newPowerLimit = adjustedVictronChargePower;
}
// Respect power limit
if (newPowerLimit > config.PowerLimiter_UpperPowerLimit)
newPowerLimit = config.PowerLimiter_UpperPowerLimit;
MessageOutput.printf("[PowerLimiterClass::loop] newPowerLimit: %d\r\n", newPowerLimit); MessageOutput.printf("[PowerLimiterClass::loop] newPowerLimit: %d\r\n", newPowerLimit);
return newPowerLimit; return newPowerLimit;
} }
void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit) void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit)
{ {
if(_lastRequestedPowerLimit != newPowerLimit) { CONFIG_T& config = Configuration.get();
CONFIG_T& config = Configuration.get();
// if limit too low turn inverter offf // Start the inverter in case it's inactive and if the requested power is high enough
if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) { if (!inverter->isProducing() && newPowerLimit > config.PowerLimiter_LowerPowerLimit) {
if (inverter->isProducing()) { MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter..."); inverter->sendPowerControlRequest(true);
inverter->sendPowerControlRequest(false); }
_lastCommandSent = millis();
} // Stop the inverter if limit is below threshold.
newPowerLimit = config.PowerLimiter_LowerPowerLimit; // We'll also set the power limit to the lower value in this case
} else if (!inverter->isProducing()) { if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter..."); if (inverter->isProducing()) {
inverter->sendPowerControlRequest(true); MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
_lastCommandSent = millis(); inverter->sendPowerControlRequest(false);
} }
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
}
// Set the actual limit. We'll only do this is if the limit is in the right range
// and differs from the last requested value
if( _lastRequestedPowerLimit != newPowerLimit &&
/* newPowerLimit > config.PowerLimiter_LowerPowerLimit && --> This will always be true given the check above, kept for code readability */
newPowerLimit <= config.PowerLimiter_UpperPowerLimit ) {
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit); MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
inverter->sendActivePowerControlRequest(newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
int32_t effPowerLimit = newPowerLimit;
std::list<ChannelNum_t> dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC);
int dcProdChnls = 0, dcTotalChnls = dcChnls.size();
for (auto& c : dcChnls) {
if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 1.0) {
dcProdChnls++;
}
}
if (dcProdChnls > 0) {
effPowerLimit = round(newPowerLimit * static_cast<float>(dcTotalChnls) / dcProdChnls);
uint16_t inverterMaxPower = inverter->DevInfo()->getMaxPower();
if (effPowerLimit > inverterMaxPower) {
effPowerLimit = inverterMaxPower;
}
}
inverter->sendActivePowerControlRequest(effPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
_lastRequestedPowerLimit = newPowerLimit; _lastRequestedPowerLimit = newPowerLimit;
// wait for the next inverter update (+ 3 seconds to make sure the limit got applied) // wait for the next inverter update (+ 3 seconds to make sure the limit got applied)
_lastLimitSetTime = millis(); _lastLimitSetTime = millis();

View File

@ -38,12 +38,10 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
auto inv = Hoymiles.getInverterBySerial(serial); auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) { if (inv != nullptr) {
String serial = inv->serialString();
uint8_t logEntryCount = inv->EventLog()->getEntryCount(); uint8_t logEntryCount = inv->EventLog()->getEntryCount();
root[serial]["count"] = logEntryCount; root["count"] = logEntryCount;
JsonArray eventsArray = root[serial].createNestedArray("events"); JsonArray eventsArray = root.createNestedArray("events");
for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) { for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) {
JsonObject eventsObject = eventsArray.createNestedObject(); JsonObject eventsObject = eventsArray.createNestedObject();

View File

@ -119,17 +119,18 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
config.PowerLimiter_Enabled = root[F("enabled")].as<bool>(); config.PowerLimiter_Enabled = root[F("enabled")].as<bool>();
PowerLimiter.setDisable(false); // User input clears the PL internal disable flag
config.PowerLimiter_SolarPassThroughEnabled = root[F("solar_passtrough_enabled")].as<bool>(); config.PowerLimiter_SolarPassThroughEnabled = root[F("solar_passtrough_enabled")].as<bool>();
config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>(); config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>();
config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>(); config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>(); config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>();
config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>(); config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>();
config.PowerLimiter_TargetPowerConsumption = root[F("target_power_consumption")].as<uint32_t>(); config.PowerLimiter_TargetPowerConsumption = root[F("target_power_consumption")].as<int32_t>();
config.PowerLimiter_TargetPowerConsumptionHysteresis = root[F("target_power_consumption_hysteresis")].as<uint32_t>(); config.PowerLimiter_TargetPowerConsumptionHysteresis = root[F("target_power_consumption_hysteresis")].as<int32_t>();
config.PowerLimiter_LowerPowerLimit = root[F("lower_power_limit")].as<uint32_t>(); config.PowerLimiter_LowerPowerLimit = root[F("lower_power_limit")].as<int32_t>();
config.PowerLimiter_UpperPowerLimit = root[F("upper_power_limit")].as<uint32_t>(); config.PowerLimiter_UpperPowerLimit = root[F("upper_power_limit")].as<int32_t>();
config.PowerLimiter_BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as<float>(); config.PowerLimiter_BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as<uint32_t>();
config.PowerLimiter_BatterySocStopThreshold = root[F("battery_soc_stop_threshold")].as<float>(); config.PowerLimiter_BatterySocStopThreshold = root[F("battery_soc_stop_threshold")].as<uint32_t>();
config.PowerLimiter_VoltageStartThreshold = root[F("voltage_start_threshold")].as<float>(); config.PowerLimiter_VoltageStartThreshold = root[F("voltage_start_threshold")].as<float>();
config.PowerLimiter_VoltageStartThreshold = static_cast<int>(config.PowerLimiter_VoltageStartThreshold * 100) / 100.0; config.PowerLimiter_VoltageStartThreshold = static_cast<int>(config.PowerLimiter_VoltageStartThreshold * 100) / 100.0;
config.PowerLimiter_VoltageStopThreshold = root[F("voltage_stop_threshold")].as<float>(); config.PowerLimiter_VoltageStopThreshold = root[F("voltage_stop_threshold")].as<float>();

View File

@ -86,7 +86,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
addField(stream, serial, i, inv, t, c, FLD_F); addField(stream, serial, i, inv, t, c, FLD_F);
addField(stream, serial, i, inv, t, c, FLD_T); addField(stream, serial, i, inv, t, c, FLD_T);
addField(stream, serial, i, inv, t, c, FLD_PF); addField(stream, serial, i, inv, t, c, FLD_PF);
addField(stream, serial, i, inv, t, c, FLD_PRA); addField(stream, serial, i, inv, t, c, FLD_Q);
addField(stream, serial, i, inv, t, c, FLD_EFF); addField(stream, serial, i, inv, t, c, FLD_EFF);
addField(stream, serial, i, inv, t, c, FLD_IRR); addField(stream, serial, i, inv, t, c, FLD_IRR);
} }

View File

@ -5,6 +5,7 @@
#include "WebApi_sysstatus.h" #include "WebApi_sysstatus.h"
#include "Configuration.h" #include "Configuration.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "PinMapping.h"
#include "WebApi.h" #include "WebApi.h"
#include <AsyncJson.h> #include <AsyncJson.h>
#include <Hoymiles.h> #include <Hoymiles.h>
@ -69,11 +70,11 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
root["uptime"] = esp_timer_get_time() / 1000000; root["uptime"] = esp_timer_get_time() / 1000000;
root["nrf_configured"] = Hoymiles.getRadioNrf()->isConfigured(); root["nrf_configured"] = PinMapping.isValidNrf24Config();
root["nrf_connected"] = Hoymiles.getRadioNrf()->isConnected(); root["nrf_connected"] = Hoymiles.getRadioNrf()->isConnected();
root["nrf_pvariant"] = Hoymiles.getRadioNrf()->isPVariant(); root["nrf_pvariant"] = Hoymiles.getRadioNrf()->isPVariant();
root["cmt_configured"] = Hoymiles.getRadioCmt()->isConfigured(); root["cmt_configured"] = PinMapping.isValidCmt2300Config();
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected(); root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
response->setLength(); response->setLength();

View File

@ -7,6 +7,8 @@
#include "MessageOutput.h" #include "MessageOutput.h"
#include "WebApi.h" #include "WebApi.h"
#include "Battery.h" #include "Battery.h"
#include "Huawei_can.h"
#include "PowerMeter.h"
#include "VeDirectFrameHandler.h" #include "VeDirectFrameHandler.h"
#include "defaults.h" #include "defaults.h"
#include <AsyncJson.h> #include <AsyncJson.h>
@ -145,7 +147,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
addField(chanTypeObj, i, inv, t, c, FLD_F); addField(chanTypeObj, i, inv, t, c, FLD_F);
addField(chanTypeObj, i, inv, t, c, FLD_T); addField(chanTypeObj, i, inv, t, c, FLD_T);
addField(chanTypeObj, i, inv, t, c, FLD_PF); addField(chanTypeObj, i, inv, t, c, FLD_PF);
addField(chanTypeObj, i, inv, t, c, FLD_PRA); addField(chanTypeObj, i, inv, t, c, FLD_Q);
addField(chanTypeObj, i, inv, t, c, FLD_EFF); addField(chanTypeObj, i, inv, t, c, FLD_EFF);
if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) { if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) {
addField(chanTypeObj, i, inv, t, c, FLD_IRR); addField(chanTypeObj, i, inv, t, c, FLD_IRR);
@ -197,10 +199,17 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
JsonObject huaweiObj = root.createNestedObject("huawei"); JsonObject huaweiObj = root.createNestedObject("huawei");
huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled; huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled;
const RectifierParameters_t * rp = HuaweiCan.get();
addTotalField(huaweiObj, "Power", rp->output_power, "W", 2);
JsonObject batteryObj = root.createNestedObject("battery"); JsonObject batteryObj = root.createNestedObject("battery");
batteryObj[F("enabled")] = Configuration.get().Battery_Enabled; batteryObj[F("enabled")] = Configuration.get().Battery_Enabled;
addTotalField(batteryObj, "soc", Battery.stateOfCharge, "%", 0); addTotalField(batteryObj, "soc", Battery.stateOfCharge, "%", 0);
JsonObject powerMeterObj = root.createNestedObject("power_meter");
powerMeterObj[F("enabled")] = Configuration.get().PowerMeter_Enabled;
addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(), "W", 1);
} }
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic) void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic)

View File

@ -14,8 +14,10 @@
#include "MqttHandleHass.h" #include "MqttHandleHass.h"
#include "MqttHandleVedirectHass.h" #include "MqttHandleVedirectHass.h"
#include "MqttHandleInverter.h" #include "MqttHandleInverter.h"
#include "MqttHandleInverterTotal.h"
#include "MqttHandleVedirect.h" #include "MqttHandleVedirect.h"
#include "MqttHandleHuawei.h" #include "MqttHandleHuawei.h"
#include "MqttHandlePowerLimiter.h"
#include "MqttSettings.h" #include "MqttSettings.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "NtpSettings.h" #include "NtpSettings.h"
@ -100,10 +102,12 @@ void setup()
MqttSettings.init(); MqttSettings.init();
MqttHandleDtu.init(); MqttHandleDtu.init();
MqttHandleInverter.init(); MqttHandleInverter.init();
MqttHandleInverterTotal.init();
MqttHandleVedirect.init(); MqttHandleVedirect.init();
MqttHandleHass.init(); MqttHandleHass.init();
MqttHandleVedirectHass.init(); MqttHandleVedirectHass.init();
MqttHandleHuawei.init(); MqttHandleHuawei.init();
MqttHandlePowerLimiter.init();
MessageOutput.println("done"); MessageOutput.println("done");
// Initialize WebApi // Initialize WebApi
@ -204,6 +208,8 @@ void loop()
yield(); yield();
MqttHandleInverter.loop(); MqttHandleInverter.loop();
yield(); yield();
MqttHandleInverterTotal.loop();
yield();
MqttHandleVedirect.loop(); MqttHandleVedirect.loop();
yield(); yield();
MqttHandleHass.loop(); MqttHandleHass.loop();
@ -212,6 +218,8 @@ void loop()
yield(); yield();
MqttHandleHuawei.loop(); MqttHandleHuawei.loop();
yield(); yield();
MqttHandlePowerLimiter.loop();
yield();
WebApi.loop(); WebApi.loop();
yield(); yield();
Display.loop(); Display.loop();

View File

@ -23,21 +23,22 @@
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.10.0", "@intlify/unplugin-vue-i18n": "^0.10.0",
"@rushstack/eslint-patch": "^1.2.0", "@rushstack/eslint-patch": "^1.2.0",
"@tsconfig/node18": "^2.0.0",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/node": "^18.15.11", "@types/node": "^18.16.0",
"@types/spark-md5": "^3.0.2", "@types/spark-md5": "^3.0.2",
"@vitejs/plugin-vue": "^4.1.0", "@vitejs/plugin-vue": "^4.1.0",
"@vue/eslint-config-typescript": "^11.0.2", "@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.3.2",
"eslint": "^8.38.0", "eslint": "^8.39.0",
"eslint-plugin-vue": "^9.11.0", "eslint-plugin-vue": "^9.11.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.62.0", "sass": "^1.62.0",
"terser": "^5.16.9", "terser": "^5.17.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.2.1", "vite": "^4.3.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.1.0", "vite-plugin-css-injected-by-js": "^3.1.0",
"vue-tsc": "^1.2.0" "vue-tsc": "^1.4.4"
} }
} }

View File

@ -89,9 +89,9 @@
</div> </div>
</div> </div>
</div> </div>
<div v-show="totalBattData.enabled"> <div v-show="totalBattData.enabled || powerMeterData.enabled || huaweiData.enabled">
<div class="row row-cols-1 row-cols-md-3 g-3"> <div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col"> <div class="col" v-show="totalBattData.enabled">
<div class="card"> <div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.BatterySoc') }}</div> <div class="card-header text-bg-success">{{ $t('invertertotalinfo.BatterySoc') }}</div>
<div class="card-body card-text text-center"> <div class="card-body card-text text-center">
@ -105,12 +105,40 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col" v-show="powerMeterData.enabled">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.HomePower') }}</div>
<div class="card-body card-text text-center">
<h2>
{{ $n(powerMeterData.Power.v, 'decimal', {
minimumFractionDigits: powerMeterData.Power.d,
maximumFractionDigits: powerMeterData.Power.d
}) }}
<small class="text-muted">{{ powerMeterData.Power.u }}</small>
</h2>
</div>
</div>
</div>
<div class="col" v-show="huaweiData.enabled">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.HuaweiPower') }}</div>
<div class="card-body card-text text-center">
<h2>
{{ $n(huaweiData.Power.v, 'decimal', {
minimumFractionDigits: huaweiData.Power.d,
maximumFractionDigits: huaweiData.Power.d
}) }}
<small class="text-muted">{{ huaweiData.Power.u }}</small>
</h2>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { Battery, Total, Vedirect } from '@/types/LiveDataStatus'; import type { Battery, Total, Vedirect, Huawei, PowerMeter } from '@/types/LiveDataStatus';
import { defineComponent, type PropType } from 'vue'; import { defineComponent, type PropType } from 'vue';
export default defineComponent({ export default defineComponent({
@ -118,6 +146,8 @@ export default defineComponent({
totalData: { type: Object as PropType<Total>, required: true }, totalData: { type: Object as PropType<Total>, required: true },
totalVeData: { type: Object as PropType<Vedirect>, required: true }, totalVeData: { type: Object as PropType<Vedirect>, required: true },
totalBattData: { type: Object as PropType<Battery>, required: true }, totalBattData: { type: Object as PropType<Battery>, required: true },
powerMeterData: { type: Object as PropType<Huawei>, required: true },
huaweiData: { type: Object as PropType<PowerMeter>, required: true },
}, },
}); });
</script> </script>

View File

@ -12,6 +12,9 @@
<BIconSun width="30" height="30" class="d-inline-block align-text-top" /> <BIconSun width="30" height="30" class="d-inline-block align-text-top" />
</span> </span>
OpenDTU OpenDTU
<span class="text-info">
<BIconBatteryCharging width="20" height="20" class="d-inline-block align-text-top" />
</span>
</router-link> </router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
@ -128,7 +131,7 @@
<script lang="ts"> <script lang="ts">
import { isLoggedIn, logout } from '@/utils/authentication'; import { isLoggedIn, logout } from '@/utils/authentication';
import { BIconEgg, BIconSun, BIconTree } from 'bootstrap-icons-vue'; import { BIconEgg, BIconSun, BIconTree, BIconBatteryCharging } from 'bootstrap-icons-vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import LocaleSwitcher from './LocaleSwitcher.vue'; import LocaleSwitcher from './LocaleSwitcher.vue';
import ThemeSwitcher from './ThemeSwitcher.vue'; import ThemeSwitcher from './ThemeSwitcher.vue';
@ -138,6 +141,7 @@ export default defineComponent({
BIconEgg, BIconEgg,
BIconSun, BIconSun,
BIconTree, BIconTree,
BIconBatteryCharging,
LocaleSwitcher, LocaleSwitcher,
ThemeSwitcher, ThemeSwitcher,
}, },

View File

@ -338,7 +338,9 @@
"MpptTotalYieldTotal": "MPPT Gesamtertrag Insgesamt", "MpptTotalYieldTotal": "MPPT Gesamtertrag Insgesamt",
"MpptTotalYieldDay": "MPPT Gesamtertrag Heute", "MpptTotalYieldDay": "MPPT Gesamtertrag Heute",
"MpptTotalPower": "MPPT Gesamtleistung", "MpptTotalPower": "MPPT Gesamtleistung",
"BatterySoc": "Ladezustand" "BatterySoc": "Ladezustand",
"HomePower": "Leistung / Netz",
"HuaweiPower": "Huawei AC Leistung"
}, },
"inverterchannelproperty": { "inverterchannelproperty": {
"Power": "Leistung", "Power": "Leistung",
@ -522,10 +524,10 @@
"InverterIdHint": "Wähle den Wechselrichter an dem die Batterie hängt.", "InverterIdHint": "Wähle den Wechselrichter an dem die Batterie hängt.",
"InverterChannelId": "Kanal ID", "InverterChannelId": "Kanal ID",
"InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt.", "InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt.",
"TargetPowerConsumption": "Erlaubter Stromverbrauch", "TargetPowerConsumption": "Angestrebter Netzbezung",
"TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch.", "TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz.",
"TargetPowerConsumptionHysteresis": "Hysterese für den Zielstromverbrauch", "TargetPowerConsumptionHysteresis": "Hysterese für den angestrebten Netzbezug",
"TargetPowerConsumptionHysteresisHint": "Wert um den der Zielstromverbrauch schwanken darf, ohne dass nachgeregelt wird.", "TargetPowerConsumptionHysteresisHint": "Wert um den der angestrebte Netzbezug schwanken darf, ohne dass nachgeregelt wird.",
"LowerPowerLimit": "Unteres Leistungslimit", "LowerPowerLimit": "Unteres Leistungslimit",
"UpperPowerLimit": "Oberes Leistungslimit", "UpperPowerLimit": "Oberes Leistungslimit",
"PowerMeters": "Leistungsmesser", "PowerMeters": "Leistungsmesser",

View File

@ -338,7 +338,9 @@
"MpptTotalYieldTotal": "MPPT Total Yield Total", "MpptTotalYieldTotal": "MPPT Total Yield Total",
"MpptTotalYieldDay": "MPPT Total Yield Day", "MpptTotalYieldDay": "MPPT Total Yield Day",
"MpptTotalPower": "MPPT Total Power", "MpptTotalPower": "MPPT Total Power",
"BatterySoc": "State of charge" "BatterySoc": "State of charge",
"HomePower": "Grid Power",
"HuaweiPower": "Huawei AC Power"
}, },
"inverterchannelproperty": { "inverterchannelproperty": {
"Power": "Power", "Power": "Power",
@ -523,10 +525,10 @@
"InverterIdHint": "Select proper inverter ID where battery is connected to.", "InverterIdHint": "Select proper inverter ID where battery is connected to.",
"InverterChannelId": "Channel ID", "InverterChannelId": "Channel ID",
"InverterChannelIdHint": "Select proper channel where battery is connected to.", "InverterChannelIdHint": "Select proper channel where battery is connected to.",
"TargetPowerConsumption": "Target power consumption", "TargetPowerConsumption": "Target power consumption from grid",
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.", "TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
"TargetPowerConsumptionHysteresis": "Hysteresis for power consumption", "TargetPowerConsumptionHysteresis": "Hysteresis for power consumption from grid",
"TargetPowerConsumptionHysteresisHint": "Value around which the target power consumption fluctuates without readjustment.", "TargetPowerConsumptionHysteresisHint": "Value around which the target grid power consumption fluctuates without readjustment.",
"LowerPowerLimit": "Lower power limit", "LowerPowerLimit": "Lower power limit",
"UpperPowerLimit": "Upper power limit", "UpperPowerLimit": "Upper power limit",
"PowerMeters": "Power meter", "PowerMeters": "Power meter",

View File

@ -337,7 +337,9 @@
"MpptTotalYieldTotal": "MPPT rendement total", "MpptTotalYieldTotal": "MPPT rendement total",
"MpptTotalYieldDay": "MPPT rendement du jour", "MpptTotalYieldDay": "MPPT rendement du jour",
"MpptTotalPower": "MPPT puissance de l'installation", "MpptTotalPower": "MPPT puissance de l'installation",
"BatterySoc": "State of charge" "BatterySoc": "State of charge",
"HomePower": "Grid Power",
"HuaweiPower": "Huawei AC Power"
}, },
"inverterchannelproperty": { "inverterchannelproperty": {
"Power": "Puissance", "Power": "Puissance",

View File

@ -54,6 +54,7 @@ export interface Vedirect {
export interface Huawei { export interface Huawei {
enabled: boolean; enabled: boolean;
Power: ValueObject;
} }
export interface Battery { export interface Battery {
@ -61,6 +62,11 @@ export interface Battery {
soc: ValueObject; soc: ValueObject;
} }
export interface PowerMeter {
enabled: boolean;
Power: ValueObject;
}
export interface LiveData { export interface LiveData {
inverters: Inverter[]; inverters: Inverter[];
total: Total; total: Total;
@ -68,4 +74,5 @@ export interface LiveData {
vedirect: Vedirect; vedirect: Vedirect;
huawei: Huawei; huawei: Huawei;
battery: Battery; battery: Battery;
power_meter: PowerMeter;
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true"> <BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true">
<HintView :hints="liveData.hints" /> <HintView :hints="liveData.hints" />
<InverterTotalInfo :totalData="liveData.total" :totalVeData="liveData.vedirect" :totalBattData="liveData.battery"/><br /> <InverterTotalInfo :totalData="liveData.total" :totalVeData="liveData.vedirect" :totalBattData="liveData.battery" :powerMeterData="liveData.power_meter" :huaweiData="liveData.huawei"/><br />
<div class="row gy-3"> <div class="row gy-3">
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]"> <div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical"> <div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
@ -546,7 +546,7 @@ export default defineComponent({
fetch("/api/eventlog/status?inv=" + serial, { headers: authHeader() }) fetch("/api/eventlog/status?inv=" + serial, { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router)) .then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => { .then((data) => {
this.eventLogList = data[serial]; this.eventLogList = data;
this.eventLogLoading = false; this.eventLogLoading = false;
}); });

View File

@ -104,7 +104,7 @@
</CardElement> </CardElement>
</div> </div>
<div v-for="(max, index) in selectedInverterData.channel" :key="`${index}`"> <div v-for="(ch, index) in selectedInverterData.channel" :key="`${index}`">
<div class="row g-2"> <div class="row g-2">
<div class="col-md"> <div class="col-md">
<label :for="`inverter-name_${index}`" class="col-form-label"> <label :for="`inverter-name_${index}`" class="col-form-label">
@ -114,7 +114,7 @@
<div class="d-flex mb-2"> <div class="d-flex mb-2">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" :id="`inverter-name_${index}`" <input type="text" class="form-control" :id="`inverter-name_${index}`"
maxlength="31" v-model="selectedInverterData.channel[index].name" /> maxlength="31" v-model="ch.name" />
</div> </div>
</div> </div>
</div> </div>
@ -128,7 +128,7 @@
<div class="d-flex mb-2"> <div class="d-flex mb-2">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" :id="`inverter-max_${index}`" <input type="number" class="form-control" :id="`inverter-max_${index}`"
min="0" v-model="selectedInverterData.channel[index].max_power" min="0" v-model="ch.max_power"
:aria-describedby="`inverter-maxDescription_${index} inverter-customizer`" /> :aria-describedby="`inverter-maxDescription_${index} inverter-customizer`" />
<span class="input-group-text" <span class="input-group-text"
:id="`inverter-maxDescription_${index}`">W<sub>p</sub><sup>*</sup></span> :id="`inverter-maxDescription_${index}`">W<sub>p</sub><sup>*</sup></span>
@ -143,7 +143,7 @@
<div class="d-flex mb-2"> <div class="d-flex mb-2">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" :id="`inverter-ytoffset_${index}`" <input type="number" class="form-control" :id="`inverter-ytoffset_${index}`"
min="0" v-model="selectedInverterData.channel[index].yield_total_offset" min="0" v-model="ch.yield_total_offset"
:aria-describedby="`inverter-ytoffsetDescription_${index} inverter-customizer`" /> :aria-describedby="`inverter-ytoffsetDescription_${index} inverter-customizer`" />
<span class="input-group-text" <span class="input-group-text"
:id="`inverter-ytoffsetDescription_${index}`">kWh</span> :id="`inverter-ytoffsetDescription_${index}`">kWh</span>

View File

@ -65,7 +65,7 @@
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="targetPowerConsumption" <input type="number" class="form-control" id="targetPowerConsumption"
placeholder="75" min="0" v-model="powerLimiterConfigList.target_power_consumption" placeholder="75" v-model="powerLimiterConfigList.target_power_consumption"
aria-describedby="targetPowerConsumptionDescription" required/> aria-describedby="targetPowerConsumptionDescription" required/>
<span class="input-group-text" id="targetPowerConsumptionDescription">W</span> <span class="input-group-text" id="targetPowerConsumptionDescription">W</span>
</div> </div>

View File

@ -1,8 +1,17 @@
{ {
"extends": "@vue/tsconfig/tsconfig.node.json", "extends": [
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], "@tsconfig/node18/tsconfig.json",
"compilerOptions": { "@vue/tsconfig/tsconfig.json"
"composite": true, ],
"types": ["node"] "include": [
} "vite.config.*",
"vitest.config.*",
"cypress.config.*"
],
"compilerOptions": {
"composite": true,
"types": [
"node"
]
}
} }

View File

@ -1,13 +1,19 @@
{ {
"extends": "@vue/tsconfig/tsconfig.web.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": { "compilerOptions": {
"ignoreDeprecations": "5.0",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },
"lib": ["ES2021", "DOM"] "lib": ["ES2021", "DOM"],
"moduleResolution": "Node",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}, },
"references": [ "references": [

View File

@ -151,10 +151,10 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@8.38.0": "@eslint/js@8.39.0":
version "8.38.0" version "8.39.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.38.0.tgz#73a8a0d8aa8a8e6fe270431c5e72ae91b5337892" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b"
integrity sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g== integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==
"@humanwhocodes/config-array@^0.11.8": "@humanwhocodes/config-array@^0.11.8":
version "0.11.8" version "0.11.8"
@ -344,6 +344,11 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
"@tsconfig/node18@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tsconfig/node18/-/node18-2.0.0.tgz#58b52430d00913dc26c4459fbc7f05396e47886a"
integrity sha512-uI/B0ShkiEwTk036pncXucVlj4y11EW6mycQvCEzC1PkR2TBvdQZ5Wf96dp+XXWAc70FEDfvwTqanoaDpP6rPw==
"@types/bootstrap@^5.2.6": "@types/bootstrap@^5.2.6":
version "5.2.6" version "5.2.6"
resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.6.tgz#e861b3aa1f4a1434da0bf50fbaa372b6f7e64d2f" resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.6.tgz#e861b3aa1f4a1434da0bf50fbaa372b6f7e64d2f"
@ -361,10 +366,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/node@^18.15.11": "@types/node@^18.16.0":
version "18.15.11" version "18.16.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.0.tgz#4668bc392bb6938637b47e98b1f2ed5426f33316"
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== integrity sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==
"@types/spark-md5@^3.0.2": "@types/spark-md5@^3.0.2":
version "3.0.2" version "3.0.2"
@ -490,49 +495,49 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073"
integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ== integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==
"@volar/language-core@1.3.0-alpha.0": "@volar/language-core@1.4.1":
version "1.3.0-alpha.0" version "1.4.1"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.3.0-alpha.0.tgz#4924b4cbc37dbce5f3845c1d2b2811938223a980" resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.4.1.tgz#66b5758252e35c4e5e71197ca7fa0344d306442c"
integrity sha512-W3uMzecHPcbwddPu4SJpUcPakRBK/y/BP+U0U6NiPpUX1tONLC4yCawt+QBJqtgJ+sfD6ztf5PyvPL3hQRqfOA== integrity sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ==
dependencies: dependencies:
"@volar/source-map" "1.3.0-alpha.0" "@volar/source-map" "1.4.1"
"@volar/source-map@1.3.0-alpha.0": "@volar/source-map@1.4.1":
version "1.3.0-alpha.0" version "1.4.1"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.3.0-alpha.0.tgz#c45d51ecb9759604d29fb80211d2fc9765e5559c" resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.4.1.tgz#e3b561775c742508e5e1f28609a4787c98056715"
integrity sha512-jSdizxWFvDTvkPYZnO6ew3sBZUnS0abKCbuopkc0JrIlFbznWC/fPH3iPFIMS8/IIkRxq1Jh9VVG60SmtsdaMQ== integrity sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA==
dependencies: dependencies:
muggle-string "^0.2.2" muggle-string "^0.2.2"
"@volar/typescript@1.3.0-alpha.0": "@volar/typescript@1.4.1":
version "1.3.0-alpha.0" version "1.4.1"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.3.0-alpha.0.tgz#f79bbc9939016700812b18191c47eb035913c6c3" resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.4.1.tgz#a013419e6f029155e5467443f3ab72815da608b5"
integrity sha512-5UItyW2cdH2mBLu4RrECRNJRgtvvzKrSCn2y3v/D61QwIDkGx4aeil6x8RFuUL5TFtV6QvVHXnsOHxNgd+sCow== integrity sha512-phTy6p9yG6bgMIKQWEeDOi/aeT0njZsb1a/G1mrEuDsLmAn24Le4gDwSsGNhea6Uhu+3gdpUZn2PmZXa+WG2iQ==
dependencies: dependencies:
"@volar/language-core" "1.3.0-alpha.0" "@volar/language-core" "1.4.1"
"@volar/vue-language-core@1.2.0": "@volar/vue-language-core@1.4.4":
version "1.2.0" version "1.4.4"
resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-1.2.0.tgz#a600aa93c6a4e89bf2b525b7e876b39e3afdfb9b" resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-1.4.4.tgz#6c1cf289cd1caa0f433a0765d288cf6fbe54dff5"
integrity sha512-w7yEiaITh2WzKe6u8ZdeLKCUz43wdmY/OqAmsB/PGDvvhTcVhCJ6f0W/RprZL1IhqH8wALoWiwEh/Wer7ZviMQ== integrity sha512-c3hL6un+CfoOlusGvpypcodmk9ke/ImrWIUc0GkgI+imoQpUGzgu3tEQWlPs604R7AhxeZwWUi8hQNfax0R/zA==
dependencies: dependencies:
"@volar/language-core" "1.3.0-alpha.0" "@volar/language-core" "1.4.1"
"@volar/source-map" "1.3.0-alpha.0" "@volar/source-map" "1.4.1"
"@vue/compiler-dom" "^3.2.47" "@vue/compiler-dom" "^3.2.0"
"@vue/compiler-sfc" "^3.2.47" "@vue/compiler-sfc" "^3.2.0"
"@vue/reactivity" "^3.2.47" "@vue/reactivity" "^3.2.0"
"@vue/shared" "^3.2.47" "@vue/shared" "^3.2.0"
minimatch "^6.1.6" minimatch "^9.0.0"
muggle-string "^0.2.2" muggle-string "^0.2.2"
vue-template-compiler "^2.7.14" vue-template-compiler "^2.7.14"
"@volar/vue-typescript@1.2.0": "@volar/vue-typescript@1.4.4":
version "1.2.0" version "1.4.4"
resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-1.2.0.tgz#825dab4624a116d8be21efbf0c4a7bd6dec51d37" resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-1.4.4.tgz#6c4a86010fc94965cce554ea1424bc8878ba216c"
integrity sha512-zjmRi9y3J1EkG+pfuHp8IbHmibihrKK485cfzsHjiuvJMGrpkWvlO5WVEk8oslMxxeGC5XwBFE9AOlvh378EPA== integrity sha512-L61Fk15jlJw3QtIddD4cVE5jei5i6zbLJRiaEMYDDnUKB259/qUrdvnMfnZUFVyDwlevzdstjtaUyreeG/0nPQ==
dependencies: dependencies:
"@volar/typescript" "1.3.0-alpha.0" "@volar/typescript" "1.4.1"
"@volar/vue-language-core" "1.2.0" "@volar/vue-language-core" "1.4.4"
"@vue/compiler-core@3.2.47": "@vue/compiler-core@3.2.47":
version "3.2.47" version "3.2.47"
@ -544,7 +549,7 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map "^0.6.1" source-map "^0.6.1"
"@vue/compiler-dom@3.2.47", "@vue/compiler-dom@^3.2.47": "@vue/compiler-dom@3.2.47", "@vue/compiler-dom@^3.2.0":
version "3.2.47" version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ== integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
@ -552,7 +557,7 @@
"@vue/compiler-core" "3.2.47" "@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47" "@vue/shared" "3.2.47"
"@vue/compiler-sfc@3.2.47", "@vue/compiler-sfc@^3.2.47": "@vue/compiler-sfc@3.2.47", "@vue/compiler-sfc@^3.2.0", "@vue/compiler-sfc@^3.2.47":
version "3.2.47" version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ== integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
@ -601,7 +606,7 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.25.7" magic-string "^0.25.7"
"@vue/reactivity@3.2.47", "@vue/reactivity@^3.2.47": "@vue/reactivity@3.2.47", "@vue/reactivity@^3.2.0":
version "3.2.47" version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ== integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
@ -633,15 +638,15 @@
"@vue/compiler-ssr" "3.2.47" "@vue/compiler-ssr" "3.2.47"
"@vue/shared" "3.2.47" "@vue/shared" "3.2.47"
"@vue/shared@3.2.47", "@vue/shared@^3.2.47": "@vue/shared@3.2.47", "@vue/shared@^3.2.0":
version "3.2.47" version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ== integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
"@vue/tsconfig@^0.1.3": "@vue/tsconfig@^0.3.2":
version "0.1.3" version "0.3.2"
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz#4a61dbd29783d01ddab504276dcf0c2b6988654f" resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.3.2.tgz#612ba0b6aefde5ac1a513545eee7d4ed01c407f5"
integrity sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg== integrity sha512-jWzZbGyrZAEbHYGn0kPzJ+MMtIkIxb0+hL5+RghBowyOxMRs9jMdp5XvpXz3wgCzjRZiUucy29042HBe9cxoYA==
acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
version "5.3.2" version "5.3.2"
@ -1035,6 +1040,14 @@ eslint-scope@^7.1.1:
esrecurse "^4.3.0" esrecurse "^4.3.0"
estraverse "^5.2.0" estraverse "^5.2.0"
eslint-scope@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-utils@^2.1.0: eslint-utils@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
@ -1069,15 +1082,15 @@ eslint-visitor-keys@^3.4.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
eslint@^8.38.0: eslint@^8.39.0:
version "8.38.0" version "8.39.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.38.0.tgz#a62c6f36e548a5574dd35728ac3c6209bd1e2f1a" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1"
integrity sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg== integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0" "@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.0.2" "@eslint/eslintrc" "^2.0.2"
"@eslint/js" "8.38.0" "@eslint/js" "8.39.0"
"@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8" "@nodelib/fs.walk" "^1.2.8"
@ -1087,7 +1100,7 @@ eslint@^8.38.0:
debug "^4.3.2" debug "^4.3.2"
doctrine "^3.0.0" doctrine "^3.0.0"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
eslint-scope "^7.1.1" eslint-scope "^7.2.0"
eslint-visitor-keys "^3.4.0" eslint-visitor-keys "^3.4.0"
espree "^9.5.1" espree "^9.5.1"
esquery "^1.4.2" esquery "^1.4.2"
@ -1741,10 +1754,10 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^6.1.6: minimatch@^9.0.0:
version "6.2.0" version "9.0.0"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56"
integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
@ -2038,7 +2051,7 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.10.0, resolve@^1.22.1: resolve@^1.10.0:
version "1.22.1" version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@ -2059,10 +2072,10 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rollup@^3.18.0: rollup@^3.20.2:
version "3.19.1" version "3.20.6"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.19.1.tgz#2b3a31ac1ff9f3afab2e523fa687fef5b0ee20fc" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd"
integrity sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg== integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
@ -2108,6 +2121,13 @@ semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
semver@^7.3.8:
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"
shebang-command@^1.2.0: shebang-command@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -2268,10 +2288,10 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
terser@^5.16.9: terser@^5.17.1:
version "5.16.9" version "5.17.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69"
integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2" "@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0" acorn "^8.5.0"
@ -2385,15 +2405,14 @@ vite-plugin-css-injected-by-js@^3.1.0:
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.1.0.tgz#d1160c975d40f256692e2465832e6ff18c22b3a3" resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.1.0.tgz#d1160c975d40f256692e2465832e6ff18c22b3a3"
integrity sha512-qogCmpocZfcbSAYZQjS88ieIY0PzLUm7RkLFWFgAxkXdz3N6roZbSTNTxeIOj5IxFbZWACUPuVBBoo6qCuXDcw== integrity sha512-qogCmpocZfcbSAYZQjS88ieIY0PzLUm7RkLFWFgAxkXdz3N6roZbSTNTxeIOj5IxFbZWACUPuVBBoo6qCuXDcw==
vite@^4.2.1: vite@^4.3.1:
version "4.2.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.1.tgz#6c2eb337b0dfd80a9ded5922163b94949d7fc254" resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.1.tgz#9badb1377f995632cdcf05f32103414db6fbb95a"
integrity sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg== integrity sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==
dependencies: dependencies:
esbuild "^0.17.5" esbuild "^0.17.5"
postcss "^8.4.21" postcss "^8.4.21"
resolve "^1.22.1" rollup "^3.20.2"
rollup "^3.18.0"
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
@ -2435,13 +2454,14 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.2.0" he "^1.2.0"
vue-tsc@^1.2.0: vue-tsc@^1.4.4:
version "1.2.0" version "1.4.4"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.2.0.tgz#2b64b960cc96208492541394423ace589a461be6" resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.4.4.tgz#bf487be671220ed47de62cf47092b622432e1599"
integrity sha512-rIlzqdrhyPYyLG9zxsVRa+JEseeS9s8F2BbVVVWRRsTZvJO2BbhLEb2HW3MY+DFma0378tnIqs+vfTzbcQtRFw== integrity sha512-2XsCjF2mLo6gwOVcOpngwJkP8GzYQjNh20A+Pr2FGdsWzr9jjXJ0k08/DfcslfncsuCrTrnWtb4KEL3gcDtlNA==
dependencies: dependencies:
"@volar/vue-language-core" "1.2.0" "@volar/vue-language-core" "1.4.4"
"@volar/vue-typescript" "1.2.0" "@volar/vue-typescript" "1.4.4"
semver "^7.3.8"
vue@^3.2.47: vue@^3.2.47:
version "3.2.47" version "3.2.47"

Binary file not shown.