Skip to content

Commit

Permalink
Feature: Support HMS/HMT inverters in different countries with differ…
Browse files Browse the repository at this point in the history
…ent frequency bands

Thanks to @Fribur, @homeautomation2022 and @stefan123t
  • Loading branch information
tbnobody committed Jan 14, 2024
1 parent 188c468 commit c20caf8
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 60 deletions.
1 change: 1 addition & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ struct CONFIG_T {
struct {
int8_t PaLevel;
uint32_t Frequency;
uint8_t CountryMode;
} Cmt;
} Dtu;

Expand Down
3 changes: 2 additions & 1 deletion include/WebApi_dtu.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class WebApiDtuClass {
void onDtuAdminPost(AsyncWebServerRequest* request);

AsyncWebServer* _server;
};
bool _performReload = false;
};
3 changes: 2 additions & 1 deletion include/WebApi_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum WebApiError {
DtuPollZero,
DtuInvalidPowerLevel,
DtuInvalidCmtFrequency,
DtuInvalidCmtCountry,

ConfigBase = 3000,
ConfigNotDeleted,
Expand Down Expand Up @@ -89,4 +90,4 @@ enum WebApiError {

HardwareBase = 12000,
HardwarePinMappingLength,
};
};
1 change: 1 addition & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
#define DTU_NRF_PA_LEVEL 0U
#define DTU_CMT_PA_LEVEL 0
#define DTU_CMT_FREQUENCY 865000000U
#define DTU_CMT_COUNTRY_MODE 0U

#define MQTT_HASS_ENABLED false
#define MQTT_HASS_EXPIRE true
Expand Down
2 changes: 0 additions & 2 deletions lib/CMT2300a/cmt2300a_params_860.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@
#include "cmt2300a_defs.h"
#include <stdint.h>

#define CMT_BASE_FREQ_860 860000000

/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
static uint8_t g_cmt2300aCmtBank_860[CMT2300A_CMT_BANK_SIZE] = {
0x00,
Expand Down
2 changes: 0 additions & 2 deletions lib/CMT2300a/cmt2300a_params_900.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@
#include "cmt2300a_defs.h"
#include <stdint.h>

#define CMT_BASE_FREQ_900 900000000

/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
static uint8_t g_cmt2300aCmtBank_900[CMT2300A_CMT_BANK_SIZE] = {
0x00,
Expand Down
11 changes: 2 additions & 9 deletions lib/CMT2300a/cmt2300wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,16 +243,9 @@ bool CMT2300A::rxFifoAvailable()
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
}

uint32_t CMT2300A::getBaseFrequency()
uint32_t CMT2300A::getBaseFrequency() const
{
switch (_frequencyBand) {
case FrequencyBand_t::BAND_900:
return CMT_BASE_FREQ_900;
break;
default:
return CMT_BASE_FREQ_860;
break;
}
return getBaseFrequency(_frequencyBand);
}

FrequencyBand_t CMT2300A::getFrequencyBand() const
Expand Down
16 changes: 15 additions & 1 deletion lib/CMT2300a/cmt2300wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset
#define CMT_SPI_SPEED 4000000 // 4 MHz

#define CMT_BASE_FREQ_900 900000000
#define CMT_BASE_FREQ_860 860000000

enum FrequencyBand_t {
BAND_860,
BAND_900,
Expand Down Expand Up @@ -91,7 +94,18 @@ class CMT2300A {

bool rxFifoAvailable();

uint32_t getBaseFrequency();
uint32_t getBaseFrequency() const;
static constexpr uint32_t getBaseFrequency(FrequencyBand_t band)
{
switch (band) {
case FrequencyBand_t::BAND_900:
return CMT_BASE_FREQ_900;
break;
default:
return CMT_BASE_FREQ_860;
break;
}
}

FrequencyBand_t getFrequencyBand() const;
void setFrequencyBand(const FrequencyBand_t mode);
Expand Down
46 changes: 28 additions & 18 deletions lib/Hoymiles/src/HoymilesRadio_CMT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@
#include <FunctionalInterrupt.h>
#include <frozen/map.h>

struct CountryFrequencyDefinition_t {
FrequencyBand_t Band;
uint32_t Freq_Legal_Min;
uint32_t Freq_Legal_Max;
uint32_t Freq_Default;
uint32_t Freq_StartUp;
};

constexpr CountryFrequencyDefinition_t make_value(FrequencyBand_t Band, uint32_t Freq_Legal_Min, uint32_t Freq_Legal_Max, uint32_t Freq_Default, uint32_t Freq_StartUp)
{
CountryFrequencyDefinition_t v = { Band, Freq_Legal_Min, Freq_Legal_Max, Freq_Default, Freq_StartUp };
// frequency can not be lower than actual initailized base freq + 250000
uint32_t minFrequency = CMT2300A::getBaseFrequency(Band) + HoymilesRadio_CMT::getChannelWidth();

// =923500, 0xFF does not work
uint32_t maxFrequency = CMT2300A::getBaseFrequency(Band) + 0xFE * HoymilesRadio_CMT::getChannelWidth();

CountryFrequencyDefinition_t v = { Band, minFrequency, maxFrequency, Freq_Legal_Min, Freq_Legal_Max, Freq_Default, Freq_StartUp };
return v;
}

Expand Down Expand Up @@ -54,6 +52,25 @@ uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) con
return (frequency - _radio->getBaseFrequency()) / getChannelWidth(); // frequency to channel
}

std::vector<CountryFrequencyList_t> HoymilesRadio_CMT::getCountryFrequencyList()
{
std::vector<CountryFrequencyList_t> v;
for (const auto& [key, value] : countryDefinition) {
CountryFrequencyList_t s;
s.mode = key;
s.definition.Band = value.Band;
s.definition.Freq_Default = value.Freq_Default;
s.definition.Freq_StartUp = value.Freq_StartUp;
s.definition.Freq_Min = value.Freq_Min;
s.definition.Freq_Max = value.Freq_Max;
s.definition.Freq_Legal_Max = value.Freq_Legal_Max;
s.definition.Freq_Legal_Min = value.Freq_Legal_Min;

v.push_back(s);
}
return v;
}

bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_frequency)
{
const uint8_t toChannel = getChannelFromFrequency(to_frequency);
Expand Down Expand Up @@ -207,19 +224,12 @@ bool HoymilesRadio_CMT::isConnected() const

uint32_t HoymilesRadio_CMT::getMinFrequency() const
{
// frequency can not be lower than actual initailized base freq + 250000
return _radio->getBaseFrequency() + getChannelWidth();
return countryDefinition.at(_countryMode).Freq_Min;
}

uint32_t HoymilesRadio_CMT::getMaxFrequency() const
{
// =923500, 0xFF does not work
return _radio->getBaseFrequency() + 0xFE * getChannelWidth();
}

uint32_t HoymilesRadio_CMT::getChannelWidth()
{
return FH_OFFSET * CMT2300A_ONE_STEP_SIZE;
return countryDefinition.at(_countryMode).Freq_Max;
}

CountryModeId_t HoymilesRadio_CMT::getCountryMode() const
Expand Down
24 changes: 23 additions & 1 deletion lib/Hoymiles/src/HoymilesRadio_CMT.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cmt2300wrapper.h>
#include <memory>
#include <queue>
#include <vector>

// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
Expand All @@ -20,6 +21,22 @@ enum CountryModeId_t {
MODE_EU,
MODE_US,
MODE_BR,
CountryModeId_Max
};

struct CountryFrequencyDefinition_t {
FrequencyBand_t Band;
uint32_t Freq_Min;
uint32_t Freq_Max;
uint32_t Freq_Legal_Min;
uint32_t Freq_Legal_Max;
uint32_t Freq_Default;
uint32_t Freq_StartUp;
};

struct CountryFrequencyList_t {
CountryModeId_t mode;
CountryFrequencyDefinition_t definition;
};

class HoymilesRadio_CMT : public HoymilesRadio {
Expand All @@ -34,7 +51,10 @@ class HoymilesRadio_CMT : public HoymilesRadio {

uint32_t getMinFrequency() const;
uint32_t getMaxFrequency() const;
static uint32_t getChannelWidth();
static constexpr uint32_t getChannelWidth()
{
return FH_OFFSET * CMT2300A_ONE_STEP_SIZE;
}

CountryModeId_t getCountryMode() const;
void setCountryMode(CountryModeId_t mode);
Expand All @@ -44,6 +64,8 @@ class HoymilesRadio_CMT : public HoymilesRadio {
uint32_t getFrequencyFromChannel(const uint8_t channel) const;
uint8_t getChannelFromFrequency(const uint32_t frequency) const;

std::vector<CountryFrequencyList_t> getCountryFrequencyList();

private:
void ARDUINO_ISR_ATTR handleInt1();
void ARDUINO_ISR_ATTR handleInt2();
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ bool ConfigurationClass::write()
dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel;
dtu["cmt_pa_level"] = config.Dtu.Cmt.PaLevel;
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode;

JsonObject security = doc.createNestedObject("security");
security["password"] = config.Security.Password;
Expand Down Expand Up @@ -263,6 +264,7 @@ bool ConfigurationClass::read()
config.Dtu.Nrf.PaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL;
config.Dtu.Cmt.PaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL;
config.Dtu.Cmt.Frequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY;
config.Dtu.Cmt.CountryMode = dtu["cmt_country_mode"] | DTU_CMT_COUNTRY_MODE;

JsonObject security = doc["security"];
strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password));
Expand Down
4 changes: 3 additions & 1 deletion src/InverterSettings.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "InverterSettings.h"
#include "Configuration.h"
Expand Down Expand Up @@ -45,6 +45,8 @@ void InverterSettingsClass::init(Scheduler& scheduler)

if (PinMapping.isValidCmt2300Config()) {
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
MessageOutput.println(F(" Setting country mode... "));
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
MessageOutput.println(F(" Setting CMT target frequency... "));
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
}
Expand Down
58 changes: 43 additions & 15 deletions src/WebApi_dtu.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2023 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_dtu.h"
#include "Configuration.h"
Expand All @@ -21,6 +21,18 @@ void WebApiDtuClass::init(AsyncWebServer& server)

void WebApiDtuClass::loop()
{
if (_performReload) {
// Execute stuff in main thread to avoid busy SPI bus
CONFIG_T& config = Configuration.get();
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel);
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial);
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial);
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
Hoymiles.setPollInterval(config.Dtu.PollInterval);
_performReload = false;
}
}

void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
Expand All @@ -45,10 +57,20 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
root["cmt_enabled"] = Hoymiles.getRadioCmt()->isInitialized();
root["cmt_palevel"] = config.Dtu.Cmt.PaLevel;
root["cmt_frequency"] = config.Dtu.Cmt.Frequency;
root["cmt_min_freq"] = Hoymiles.getRadioCmt()->getMinFrequency();
root["cmt_max_freq"] = Hoymiles.getRadioCmt()->getMaxFrequency();
root["cmt_country"] = config.Dtu.Cmt.CountryMode;
root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth();

auto data = root.createNestedArray("country_def");
auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList();
for (const auto& definition : countryDefs) {
auto obj = data.createNestedObject();
obj["freq_default"] = definition.definition.Freq_Default;
obj["freq_min"] = definition.definition.Freq_Min;
obj["freq_max"] = definition.definition.Freq_Max;
obj["freq_legal_min"] = definition.definition.Freq_Legal_Min;
obj["freq_legal_max"] = definition.definition.Freq_Legal_Max;
}

response->setLength();
request->send(response);
}
Expand Down Expand Up @@ -96,7 +118,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("pollinterval")
&& root.containsKey("nrf_palevel")
&& root.containsKey("cmt_palevel")
&& root.containsKey("cmt_frequency"))) {
&& root.containsKey("cmt_frequency")
&& root.containsKey("cmt_country"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
Expand Down Expand Up @@ -136,14 +159,23 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}

if (root["cmt_frequency"].as<uint32_t>() < Hoymiles.getRadioCmt()->getMinFrequency()
|| root["cmt_frequency"].as<uint32_t>() > Hoymiles.getRadioCmt()->getMaxFrequency()
|| root["cmt_frequency"].as<uint32_t>() % 250 > 0) {
if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) {
retMsg["message"] = "Invalid country setting!";
retMsg["code"] = WebApiError::DtuInvalidCmtCountry;
response->setLength();
request->send(response);
return;
}

auto FrequencyDefinition = Hoymiles.getRadioCmt()->getCountryFrequencyList()[root["cmt_country"].as<CountryModeId_t>()].definition;
if (root["cmt_frequency"].as<uint32_t>() < FrequencyDefinition.Freq_Min
|| root["cmt_frequency"].as<uint32_t>() > FrequencyDefinition.Freq_Max
|| root["cmt_frequency"].as<uint32_t>() % Hoymiles.getRadioCmt()->getChannelWidth() > 0) {

retMsg["message"] = "Invalid CMT frequency setting!";
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
retMsg["param"]["min"] = Hoymiles.getRadioCmt()->getMinFrequency();
retMsg["param"]["max"] = Hoymiles.getRadioCmt()->getMaxFrequency();
retMsg["param"]["min"] = FrequencyDefinition.Freq_Min;
retMsg["param"]["max"] = FrequencyDefinition.Freq_Max;
response->setLength();
request->send(response);
return;
Expand All @@ -157,16 +189,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
config.Dtu.Cmt.Frequency = root["cmt_frequency"].as<uint32_t>();
config.Dtu.Cmt.CountryMode = root["cmt_country"].as<CountryModeId_t>();

WebApi.writeConfig(retMsg);

response->setLength();
request->send(response);

Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel);
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial);
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial);
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
Hoymiles.setPollInterval(config.Dtu.PollInterval);
_performReload = true;
}
8 changes: 7 additions & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"2002": "Das Abfraginterval muss größer als 0 sein!",
"2003": "Ungültige Sendeleistung angegeben!",
"2004": "Die Frequenz muss zwischen {min} und {max} kHz liegen und ein vielfaches von 250kHz betragen!",
"2005": "Ungültige Landesauswahl!",
"3001": "Nichts gelöscht!",
"3002": "Konfiguration zurückgesetzt. Starte jetzt neu...",
"4001": "@:apiresponse.2001",
Expand Down Expand Up @@ -359,9 +360,14 @@
"CmtPaLevel": "CMT2300A Sendeleistung:",
"NrfPaLevelHint": "Verwendet für HM-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtPaLevelHint": "Verwendet für HMS/HMT-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtCountry": "Region/Land:",
"CmtCountryHint": "Jedes Land hat unterschiedliche Frequenzzuteilungen.",
"country_0": "Europa ({min}MHz - {max}MHz)",
"country_1": "Nordamerika ({min}MHz - {max}MHz)",
"country_2": "Brasilien ({min}MHz - {max}MHz)",
"CmtFrequency": "CMT2300A Frequenz:",
"CmtFrequencyHint": "Stelle sicher, dass du nur Frequenzen verwendet werden welche im entsprechenden Land erlaubt sind! Nach einer Frequenzänderung kann es bis zu 15min dauern bis eine Verbindung hergestellt wird.",
"CmtFrequencyWarning": "Die ausgewählte Frequenz befindet außerhalb des in der EU zugelassenen Bereiches. Vergewissere dich, dass mit dieser Auswahl keine lokalen Regularien verletzt werden.",
"CmtFrequencyWarning": "Die gewählte Frequenz liegt außerhalb des zulässigen Bereichs in der gewählten Region/dem Land. Vergewissere dich, dass mit dieser Auswahl keine lokalen Regularien verletzt werden.",
"MHz": "{mhz} MHz",
"dBm": "{dbm} dBm",
"Min": "Minimum ({db} dBm)",
Expand Down
Loading

0 comments on commit c20caf8

Please sign in to comment.