mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-29 18:26:21 +02:00
Merge branch 'Argafal-hms' into hms
This commit is contained in:
commit
363d26b5b2
11 changed files with 89 additions and 17 deletions
|
@ -217,6 +217,14 @@ Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities t
|
|||
|
||||
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
||||
|
||||
#### Flashing on Linux with `esptool.py` (ESP32)
|
||||
1. install [esptool.py](https://docs.espressif.com/projects/esptool/en/latest/esp32/) if you haven't already.
|
||||
2. download and extract the latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases)
|
||||
3. `cd ahoy_v<XXX> && cp *esp32.bin esp32.bin`
|
||||
4. Perhaps you need to replace `/dev/ttyUSB0` to match your acual device in the following command. Execute it afterwards: `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 0x10000 esp32.bin`
|
||||
5. Unplug and replug your device.
|
||||
6. Open a serial monitor (e.g. Putty) @ 115200 Baud. You should see some messages regarding wifi.
|
||||
|
||||
## Connect to your Ahoy DTU
|
||||
|
||||
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
||||
|
|
|
@ -321,6 +321,19 @@ Send Power Limit:
|
|||
- A persistent limit is only needed if you want to throttle your inverter permanently or you can use it to set a start value on the battery, which is then always the switch-on limit when switching on, otherwise it would ramp up to 100% without regulation, which is continuous load is not healthy.
|
||||
- You can set a new limit in the turn-off state, which is then used for on (switching on again), otherwise the last limit from before the turn-off is used, but of course this only applies if DC voltage is applied the whole time.
|
||||
- If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit.
|
||||
### Update your AHOY-DTU Firmware
|
||||
To update your AHOY-DTU, you have to download the latest firmware package.
|
||||
Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip) available for download.
|
||||
As soon as you have downloaded the firmware package, unzip it. On the WebUI, navigate to Update and press on select firmware file.
|
||||
From the unzipped files, select the right .bin file for your hardware and needs.
|
||||
- If you use an ESP8266, select the file ending with esp8266.bin
|
||||
- If you use an ESP8266 with prometheus, select the file ending with esp8266_prometheus.bin
|
||||
- If you use an ESP32, select the file ending with esp32.bin
|
||||
- If you use an ESP32 with prometheus, select the file ending with esp32_prometheus.bin
|
||||
|
||||
Note: if you want to use prometheus, the usage of an ESP32 is recommended, since the ESP8266 is at its performance limits and therefore can cause stability issues.
|
||||
|
||||
After selecting the right firmware file, press update. Your AHOY-DTU will now install the new firmware and reboot.
|
||||
|
||||
## Additional Notes
|
||||
### MI Inverters
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Development Changes
|
||||
|
||||
## 0.6.13 - 2023-05-16
|
||||
* merge PR #934 (fix JSON API) and #944 (update manual)
|
||||
|
||||
## 0.6.12 - 2023-04-28
|
||||
* improved MqTT
|
||||
* fix menu active item
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 12
|
||||
#define VERSION_PATCH 13
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
|
|
@ -84,8 +84,8 @@ class HmRadio {
|
|||
DTU_RADIO_ID = ((uint64_t)(((dtuSn >> 24) & 0xFF) | ((dtuSn >> 8) & 0xFF00) | ((dtuSn << 8) & 0xFF0000) | ((dtuSn << 24) & 0xFF000000)) << 8) | 0x01;
|
||||
|
||||
#ifdef ESP32
|
||||
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||
mSpi = new SPIClass(FSPI);
|
||||
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||
mSpi = new SPIClass(HSPI);
|
||||
#else
|
||||
mSpi = new SPIClass(VSPI);
|
||||
#endif
|
||||
|
|
|
@ -11,12 +11,21 @@
|
|||
#include "driver/spi_master.h"
|
||||
#include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
#define CLK_PIN 6
|
||||
#define MOSI_PIN 5
|
||||
#else
|
||||
#define CLK_PIN 18
|
||||
#define MOSI_PIN 23
|
||||
#define MISO_PIN -1
|
||||
#endif
|
||||
|
||||
#define SPI_CLK 1 * 1000 * 1000 // 1MHz
|
||||
|
||||
#define SPI_PARAM_LOCK() \
|
||||
do { \
|
||||
} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS)
|
||||
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock)
|
||||
|
||||
// for ESP32 this is the so-called HSPI
|
||||
// for ESP32-S2/S3/C3 this nomenclature does not really exist anymore,
|
||||
// it is simply the first externally usable hardware SPI master controller
|
||||
|
@ -30,9 +39,10 @@ class esp32_3wSpi {
|
|||
}
|
||||
|
||||
void setup(uint8_t pinCsb = CSB_PIN, uint8_t pinFcsb = FCSB_PIN) { //, uint8_t pinGpio3 = GPIO3_PIN) {
|
||||
paramLock = xSemaphoreCreateMutex();
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = MOSI_PIN,
|
||||
.miso_io_num = MISO_PIN,
|
||||
.miso_io_num = -1, // single wire MOSI/MISO
|
||||
.sclk_io_num = CLK_PIN,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
|
@ -93,7 +103,9 @@ class esp32_3wSpi {
|
|||
.tx_buffer = &tx_data,
|
||||
.rx_buffer = NULL
|
||||
};
|
||||
SPI_PARAM_LOCK();
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
||||
SPI_PARAM_UNLOCK();
|
||||
delayMicroseconds(100);
|
||||
}
|
||||
|
||||
|
@ -110,7 +122,10 @@ class esp32_3wSpi {
|
|||
.tx_buffer = NULL,
|
||||
.rx_buffer = &rx_data
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
||||
SPI_PARAM_UNLOCK();
|
||||
delayMicroseconds(100);
|
||||
return rx_data;
|
||||
}
|
||||
|
@ -126,11 +141,13 @@ class esp32_3wSpi {
|
|||
.rx_buffer = NULL
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
tx_data = ~buf[i]; // negate buffer contents
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
||||
delayMicroseconds(4); // > 4 us
|
||||
}
|
||||
SPI_PARAM_UNLOCK();
|
||||
}
|
||||
|
||||
void readFifo(uint8_t buf[], uint8_t len) {
|
||||
|
@ -145,16 +162,19 @@ class esp32_3wSpi {
|
|||
.rx_buffer = &rx_data
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
||||
delayMicroseconds(4); // > 4 us
|
||||
buf[i] = rx_data;
|
||||
}
|
||||
SPI_PARAM_UNLOCK();
|
||||
}
|
||||
|
||||
private:
|
||||
spi_device_handle_t spi_reg, spi_fifo;
|
||||
bool mInitialized;
|
||||
SemaphoreHandle_t paramLock = NULL;
|
||||
};
|
||||
#else
|
||||
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4>
|
||||
|
|
|
@ -563,7 +563,7 @@ class RestApi {
|
|||
|
||||
if(F("power") == jsonIn[F("cmd")])
|
||||
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
|
||||
else if(F("restart") == jsonIn[F("restart")])
|
||||
else if(F("restart") == jsonIn[F("cmd")])
|
||||
accepted = iv->setDevControlRequest(Restart);
|
||||
else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 6)) {
|
||||
iv->powerLimit[0] = jsonIn["val"];
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
ahoy:
|
||||
interval: 5
|
||||
transmit_retries: 5
|
||||
|
||||
logging:
|
||||
filename: 'hoymiles.log'
|
||||
# DEBUG, INFO, WARNING, ERROR, FATAL
|
||||
level: 'INFO'
|
||||
max_log_filesize: 1000000
|
||||
max_log_files: 1
|
||||
|
||||
sunset:
|
||||
disabled: false
|
||||
|
|
|
@ -297,8 +297,8 @@ class InverterPacketFragment:
|
|||
|
||||
class HoymilesNRF:
|
||||
"""Hoymiles NRF24 Interface"""
|
||||
tx_channel_id = 0
|
||||
tx_channel_list = [40]
|
||||
tx_channel_id = 2
|
||||
tx_channel_list = [3,23,40,61,75]
|
||||
rx_channel_id = 0
|
||||
rx_channel_list = [3,23,40,61,75]
|
||||
rx_channel_ack = False
|
||||
|
@ -332,6 +332,12 @@ class HoymilesNRF:
|
|||
:rtype: bool
|
||||
"""
|
||||
|
||||
self.next_tx_channel()
|
||||
|
||||
if HOYMILES_TRANSACTION_LOGGING:
|
||||
c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
logging.debug(f'{c_datetime} Transmit {len(packet)} bytes channel {self.tx_channel}: {hexify_payload(packet)}')
|
||||
|
||||
if not txpower:
|
||||
txpower = self.txpower
|
||||
|
||||
|
@ -363,13 +369,13 @@ class HoymilesNRF:
|
|||
"""
|
||||
Receive Packets
|
||||
|
||||
:param timeout: receive timeout in nanoseconds (default: 12e8)
|
||||
:param timeout: receive timeout in nanoseconds (default: 5e8)
|
||||
:type timeout: int
|
||||
:yields: fragment
|
||||
"""
|
||||
|
||||
if not timeout:
|
||||
timeout=12e8
|
||||
timeout=5e8
|
||||
|
||||
self.radio.setChannel(self.rx_channel)
|
||||
self.radio.setAutoAck(False)
|
||||
|
@ -415,7 +421,7 @@ class HoymilesNRF:
|
|||
self.radio.setChannel(self.rx_channel)
|
||||
self.radio.startListening()
|
||||
|
||||
time.sleep(0.005)
|
||||
time.sleep(0.004)
|
||||
|
||||
def next_rx_channel(self):
|
||||
"""
|
||||
|
@ -433,6 +439,15 @@ class HoymilesNRF:
|
|||
return True
|
||||
return False
|
||||
|
||||
def next_tx_channel(self):
|
||||
"""
|
||||
Select next channel from hop list
|
||||
|
||||
"""
|
||||
self.tx_channel_id = self.tx_channel_id + 1
|
||||
if self.tx_channel_id >= len(self.tx_channel_list):
|
||||
self.tx_channel_id = 0
|
||||
|
||||
@property
|
||||
def tx_channel(self):
|
||||
"""
|
||||
|
@ -612,10 +627,6 @@ class InverterTransaction:
|
|||
|
||||
packet = self.tx_queue.pop(0)
|
||||
|
||||
if HOYMILES_TRANSACTION_LOGGING:
|
||||
c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
logging.debug(f'{c_datetime} Transmit {len(packet)} | {hexify_payload(packet)}')
|
||||
|
||||
self.radio.transmit(packet, txpower=self.txpower)
|
||||
|
||||
wait = False
|
||||
|
|
|
@ -19,6 +19,7 @@ import yaml
|
|||
from yaml.loader import SafeLoader
|
||||
import hoymiles
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
################################################################################
|
||||
""" Signal Handler """
|
||||
|
@ -127,6 +128,7 @@ def main_loop(ahoy_config):
|
|||
dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu')
|
||||
sunset.sun_status2mqtt(dtu_ser, dtu_name)
|
||||
loop_interval = ahoy_config.get('interval', 1)
|
||||
transmit_retries = ahoy_config.get('transmit_retries', 5)
|
||||
|
||||
try:
|
||||
do_init = True
|
||||
|
@ -143,7 +145,7 @@ def main_loop(ahoy_config):
|
|||
sys.exit(999)
|
||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
||||
poll_inverter(inverter, dtu_ser, do_init, 3)
|
||||
poll_inverter(inverter, dtu_ser, do_init, transmit_retries)
|
||||
do_init = False
|
||||
|
||||
if loop_interval > 0:
|
||||
|
@ -298,6 +300,8 @@ def init_logging(ahoy_config):
|
|||
log_config = ahoy_config.get('logging')
|
||||
fn = 'hoymiles.log'
|
||||
lvl = logging.ERROR
|
||||
max_log_filesize = 1000000
|
||||
max_log_files = 1
|
||||
if log_config:
|
||||
fn = log_config.get('filename', fn)
|
||||
level = log_config.get('level', 'ERROR')
|
||||
|
@ -311,9 +315,11 @@ def init_logging(ahoy_config):
|
|||
lvl = logging.ERROR
|
||||
elif level == 'FATAL':
|
||||
lvl = logging.FATAL
|
||||
max_log_filesize = log_config.get('max_log_filesize', max_log_filesize)
|
||||
max_log_files = log_config.get('max_log_files', max_log_files)
|
||||
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||
lvl = logging.DEBUG
|
||||
logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
||||
logging.basicConfig(handlers=[RotatingFileHandler(fn, maxBytes=max_log_filesize, backupCount=max_log_files)], format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
||||
dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu')
|
||||
logging.info(f'start logging for {dtu_name} with level: {logging.root.level}')
|
||||
|
||||
|
|
|
@ -515,9 +515,17 @@ class Hm300Decode0B(StatusResponse):
|
|||
""" reactive power """
|
||||
return self.unpack('>H', 20)[0]/10
|
||||
@property
|
||||
def powerfactor(self):
|
||||
""" Powerfactor """
|
||||
return self.unpack('>H', 24)[0]/1000
|
||||
@property
|
||||
def temperature(self):
|
||||
""" Inverter temperature in °C """
|
||||
return self.unpack('>h', 26)[0]/10
|
||||
@property
|
||||
def event_count(self):
|
||||
""" Event counter """
|
||||
return self.unpack('>H', 28)[0]
|
||||
|
||||
class Hm300Decode0C(Hm300Decode0B):
|
||||
""" 1121-series mirco-inverters status data """
|
||||
|
|
Loading…
Add table
Reference in a new issue