Browse Source

Merge branch 'Argafal-hms' into hms

pull/985/head
lumapu 1 year ago
parent
commit
363d26b5b2
  1. 8
      Getting_Started.md
  2. 13
      User_Manual.md
  3. 3
      src/CHANGES.md
  4. 2
      src/defines.h
  5. 4
      src/hm/hmRadio.h
  6. 24
      src/hms/esp32_3wSpi.h
  7. 2
      src/web/RestApi.h
  8. 3
      tools/rpi/ahoy.yml.example
  9. 29
      tools/rpi/hoymiles/__init__.py
  10. 10
      tools/rpi/hoymiles/__main__.py
  11. 8
      tools/rpi/hoymiles/decoders/__init__.py

8
Getting_Started.md

@ -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.

13
User_Manual.md

@ -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

3
src/CHANGES.md

@ -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

2
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 6
#define VERSION_PATCH 12
#define VERSION_PATCH 13
//-------------------------------------
typedef struct {

4
src/hm/hmRadio.h

@ -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

24
src/hms/esp32_3wSpi.h

@ -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>

2
src/web/RestApi.h

@ -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"];

3
tools/rpi/ahoy.yml.example

@ -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

29
tools/rpi/hoymiles/__init__.py

@ -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

10
tools/rpi/hoymiles/__main__.py

@ -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}')

8
tools/rpi/hoymiles/decoders/__init__.py

@ -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…
Cancel
Save