Compare commits
46 Commits
2024.11.20
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
653efb41a2 | ||
|
|
571ba2f350 | ||
|
|
220cfbf7ae | ||
|
|
db130f646e | ||
|
|
5510c9ff57 | ||
|
|
ebf4e921ee | ||
|
|
d068542c94 | ||
|
|
19fa310f43 | ||
|
|
87772cb76b | ||
|
|
50207a42bf | ||
|
|
a0e6942537 | ||
|
|
c37397acca | ||
|
|
498afe377b | ||
|
|
d43ac7fb92 | ||
|
|
11105944be | ||
|
|
c7fa4ff212 | ||
|
|
96ba58af8c | ||
|
|
d485d1b820 | ||
|
|
8f60a3a12a | ||
|
|
940027ab19 | ||
|
|
24b3f27364 | ||
|
|
5265c6281f | ||
|
|
8acae28c59 | ||
|
|
0061d5e159 | ||
|
|
5d14454185 | ||
|
|
58382be16c | ||
|
|
2edec642fb | ||
|
|
726a08ec2c | ||
|
|
d775ee9e89 | ||
|
|
1c1fcbea51 | ||
|
|
bf89fd7558 | ||
|
|
8247070aae | ||
|
|
241ee1e99d | ||
|
|
a75543c309 | ||
|
|
1c5a3cf6fe | ||
|
|
b2dcac549c | ||
|
|
37b173071e | ||
|
|
041ae7bae7 | ||
|
|
680863fb00 | ||
|
|
8297591853 | ||
|
|
cc3290be8e | ||
|
|
9bfded055a | ||
|
|
2b07e3c2c8 | ||
|
|
eac2e2fb39 | ||
|
|
d843ac6422 | ||
|
|
33a9b7454c |
@ -20,7 +20,7 @@ public:
|
||||
void unsubscribe(const String& topic);
|
||||
|
||||
String getPrefix() const;
|
||||
String getClientId();
|
||||
String getClientId() const;
|
||||
|
||||
private:
|
||||
void NetworkEvent(network_event event);
|
||||
|
||||
@ -59,6 +59,8 @@ public:
|
||||
bool init(const String& deviceMapping);
|
||||
PinMapping_t& get();
|
||||
|
||||
bool isMappingSelected() const { return _mappingSelected; }
|
||||
|
||||
bool isValidNrf24Config() const;
|
||||
bool isValidCmt2300Config() const;
|
||||
bool isValidW5500Config() const;
|
||||
@ -68,6 +70,8 @@ public:
|
||||
|
||||
private:
|
||||
PinMapping_t _pinMapping;
|
||||
|
||||
bool _mappingSelected = false;
|
||||
};
|
||||
|
||||
extern PinMappingClass PinMapping;
|
||||
|
||||
@ -12,7 +12,7 @@ public:
|
||||
|
||||
private:
|
||||
AsyncWebSocket _ws;
|
||||
AuthenticationMiddleware _simpleDigestAuth;
|
||||
AsyncAuthenticationMiddleware _simpleDigestAuth;
|
||||
|
||||
Task _wsCleanupTask;
|
||||
void wsCleanupTaskCb();
|
||||
|
||||
@ -25,7 +25,7 @@ private:
|
||||
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
|
||||
AsyncWebSocket _ws;
|
||||
AuthenticationMiddleware _simpleDigestAuth;
|
||||
AsyncAuthenticationMiddleware _simpleDigestAuth;
|
||||
|
||||
uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 };
|
||||
|
||||
|
||||
@ -45,7 +45,9 @@
|
||||
"Refreshing": "Refrescando",
|
||||
"Pull": "Tira hacia abajo para refrescar",
|
||||
"Release": "Soltar para refrescar",
|
||||
"Close": "Cerrar"
|
||||
"Close": "Cerrar",
|
||||
"Yes": "Yes",
|
||||
"No": "No"
|
||||
},
|
||||
"wait": {
|
||||
"NotReady": "OpenDTU is not yet ready",
|
||||
@ -193,7 +195,10 @@
|
||||
"FirmwareVersion": "Versión del firmware",
|
||||
"FirmwareBuildDate": "Fecha de construcción del firmware",
|
||||
"HardwarePartNumber": "Número de parte de hardware",
|
||||
"HardwareVersion": "Versión de hardware"
|
||||
"HardwareVersion": "Versión de hardware",
|
||||
"SupportsPowerDistributionLogic": "'Power Distribution Logic' supported",
|
||||
"Yes": "@:base.Yes",
|
||||
"No": "@:base.No"
|
||||
},
|
||||
"gridprofile": {
|
||||
"NoInfo": "@:devinfo.NoInfo",
|
||||
@ -637,7 +642,8 @@
|
||||
"TimeSync": "El reloj aún no ha sido sincronizado. Sin un reloj correctamente ajustado, no se realizan solicitudes al inversor. Esto es normal poco después del inicio. Sin embargo, después de un tiempo de ejecución más largo (>1 minuto), indica que el servidor NTP no es accesible.",
|
||||
"TimeSyncLink": "Por favor, verifica la configuración de tu hora.",
|
||||
"DefaultPassword": "Estás utilizando la contraseña predeterminada para la interfaz web y el punto de acceso de emergencia. Esto potencialmente es inseguro.",
|
||||
"DefaultPasswordLink": "Por favor, cambia la contraseña."
|
||||
"DefaultPasswordLink": "Por favor, cambia la contraseña.",
|
||||
"PinMappingIssue": "You are using a generic firmware image, but have not yet uploaded a file with device profiles (<code>pin_mapping.json</code>) or have not selected a profile defined there. Please refer to the <a href=\"https://opendtu.solar/firmware/device_profiles/\" target=\"_blank\" class=\"alert-link\">documentation</a> for details."
|
||||
},
|
||||
"deviceadmin": {
|
||||
"DeviceManager": "Administrador de Dispositivos",
|
||||
|
||||
@ -45,7 +45,9 @@
|
||||
"Refreshing": "Aggiorna",
|
||||
"Pull": "Trascina in basso per aggiornare",
|
||||
"Release": "Rilascia per aggiornare",
|
||||
"Close": "Chiudi"
|
||||
"Close": "Chiudi",
|
||||
"Yes": "Yes",
|
||||
"No": "No"
|
||||
},
|
||||
"wait": {
|
||||
"NotReady": "OpenDTU is not yet ready",
|
||||
@ -193,7 +195,10 @@
|
||||
"FirmwareVersion": "Versione Firmware",
|
||||
"FirmwareBuildDate": "Data Firmware",
|
||||
"HardwarePartNumber": "Hardware Part Number",
|
||||
"HardwareVersion": "Hardware Version"
|
||||
"HardwareVersion": "Hardware Version",
|
||||
"SupportsPowerDistributionLogic": "'Power Distribution Logic' supported",
|
||||
"Yes": "@:base.Yes",
|
||||
"No": "@:base.No"
|
||||
},
|
||||
"gridprofile": {
|
||||
"NoInfo": "@:devinfo.NoInfo",
|
||||
@ -637,7 +642,8 @@
|
||||
"TimeSync": "La Data/Ora non sono state sincronizzate, ed in tal caso non è possibile eseguire richieste all'inverter. Questa condizione è normale appena avviato, tuttavia dopo un po' (>1 minuto), questa situazione potrebbe indicare un problema di accesso al server NTP.",
|
||||
"TimeSyncLink": "Controlla le impostazioni Data/Ora.",
|
||||
"DefaultPassword": "Stai usando la password di default per accedere all'interfaccia web e per la modalità Access Point di emergenza. Questo può portare ad un rischio di sicurezza.",
|
||||
"DefaultPasswordLink": "Per favore cambia la password."
|
||||
"DefaultPasswordLink": "Per favore cambia la password.",
|
||||
"PinMappingIssue": "You are using a generic firmware image, but have not yet uploaded a file with device profiles (<code>pin_mapping.json</code>) or have not selected a profile defined there. Please refer to the <a href=\"https://opendtu.solar/firmware/device_profiles/\" target=\"_blank\" class=\"alert-link\">documentation</a> for details."
|
||||
},
|
||||
"deviceadmin": {
|
||||
"DeviceManager": "Device-Manager",
|
||||
|
||||
@ -19,6 +19,12 @@ CpuTemperatureClass CpuTemperature;
|
||||
|
||||
float CpuTemperatureClass::read()
|
||||
{
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
// Disabling temperature reading for ESP32-S2 models as it might lead to WDT resets.
|
||||
// See: https://github.com/espressif/esp-idf/issues/8088
|
||||
return NAN;
|
||||
#endif
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
float temperature = NAN;
|
||||
|
||||
@ -57,7 +57,7 @@ void HoymilesClass::loop()
|
||||
}
|
||||
}
|
||||
|
||||
if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) {
|
||||
if (iv != nullptr && iv->getRadio()->isInitialized()) {
|
||||
|
||||
if (iv->getZeroValuesIfUnreachable() && !iv->isReachable()) {
|
||||
iv->Statistics()->zeroRuntimeData();
|
||||
@ -119,6 +119,7 @@ void HoymilesClass::loop()
|
||||
iv->sendGridOnProFileParaRequest();
|
||||
}
|
||||
|
||||
_messageOutput->printf("Queue size - NRF: %" PRId32 " CMT: %" PRId32 "\r\n", _radioNrf->getQueueSize(), _radioCmt->getQueueSize());
|
||||
_lastPoll = millis();
|
||||
}
|
||||
|
||||
@ -229,6 +230,7 @@ void HoymilesClass::removeInverterBySerial(const uint64_t serial)
|
||||
for (uint8_t i = 0; i < _inverters.size(); i++) {
|
||||
if (_inverters[i]->serial() == serial) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_inverters[i]->getRadio()->removeCommands(_inverters[i].get());
|
||||
_inverters.erase(_inverters.begin() + i);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -156,6 +156,16 @@ bool HoymilesRadio::isInitialized() const
|
||||
return _isInitialized;
|
||||
}
|
||||
|
||||
void HoymilesRadio::removeCommands(InverterAbstract* inv)
|
||||
{
|
||||
_commandQueue.removeAllEntriesForInverter(inv);
|
||||
}
|
||||
|
||||
uint8_t HoymilesRadio::countSimilarCommands(std::shared_ptr<CommandAbstract> cmd)
|
||||
{
|
||||
return _commandQueue.countSimilarCommands(cmd);
|
||||
}
|
||||
|
||||
bool HoymilesRadio::isIdle() const
|
||||
{
|
||||
return !_busyFlag;
|
||||
@ -165,3 +175,8 @@ bool HoymilesRadio::isQueueEmpty() const
|
||||
{
|
||||
return _commandQueue.size() == 0;
|
||||
}
|
||||
|
||||
uint32_t HoymilesRadio::getQueueSize() const
|
||||
{
|
||||
return _commandQueue.size();
|
||||
}
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "commands/CommandAbstract.h"
|
||||
#include "queue/CommandQueue.h"
|
||||
#include "types.h"
|
||||
#include <ThreadSafeQueue.h>
|
||||
#include <TimeoutHelper.h>
|
||||
#include <memory>
|
||||
|
||||
#ifdef HOY_DEBUG_QUEUE
|
||||
#define DEBUG_PRINT(fmt, args...) Serial.printf(fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_PRINT(fmt, args...) /* Don't do anything in release builds */
|
||||
#endif
|
||||
|
||||
class HoymilesRadio {
|
||||
public:
|
||||
@ -14,11 +20,48 @@ public:
|
||||
|
||||
bool isIdle() const;
|
||||
bool isQueueEmpty() const;
|
||||
uint32_t getQueueSize() const;
|
||||
bool isInitialized() const;
|
||||
|
||||
void removeCommands(InverterAbstract* inv);
|
||||
uint8_t countSimilarCommands(std::shared_ptr<CommandAbstract> cmd);
|
||||
|
||||
void enqueCommand(std::shared_ptr<CommandAbstract> cmd)
|
||||
{
|
||||
DEBUG_PRINT("Queue size before: %ld\r\n", _commandQueue.size());
|
||||
DEBUG_PRINT("Handling command %s with type %d\r\n", cmd.get()->getCommandName().c_str(), static_cast<uint8_t>(cmd.get()->getQueueInsertType()));
|
||||
switch (cmd.get()->getQueueInsertType()) {
|
||||
case QueueInsertType::RemoveOldest:
|
||||
_commandQueue.removeDuplicatedEntries(cmd);
|
||||
break;
|
||||
case QueueInsertType::ReplaceExistent:
|
||||
// Checks if the queue already contains a command like the new one
|
||||
// and replaces the existing one with the new one.
|
||||
// (The new one will not be pushed at the end of the queue)
|
||||
if (_commandQueue.countSimilarCommands(cmd) > 0) {
|
||||
DEBUG_PRINT(" ... existing entry will be replaced\r\n");
|
||||
_commandQueue.replaceEntries(cmd);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case QueueInsertType::RemoveNewest:
|
||||
// Checks if the queue already contains a command like the new one
|
||||
// and drops the new one. The new one will not be inserted.
|
||||
if (_commandQueue.countSimilarCommands(cmd) > 0) {
|
||||
DEBUG_PRINT(" ... new entry will be dropped\r\n");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case QueueInsertType::AllowMultiple:
|
||||
// Dont do anything, just fall through and insert the command.
|
||||
break;
|
||||
}
|
||||
|
||||
// Push the command into the queue if we reach this position of the code
|
||||
DEBUG_PRINT(" ... new entry will be appended\r\n");
|
||||
_commandQueue.push(cmd);
|
||||
|
||||
DEBUG_PRINT("Queue size after: %ld\r\n", _commandQueue.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -38,7 +81,7 @@ protected:
|
||||
void handleReceivedPackage();
|
||||
|
||||
serial_u _dtuSerial;
|
||||
ThreadSafeQueue<std::shared_ptr<CommandAbstract>> _commandQueue;
|
||||
CommandQueue _commandQueue;
|
||||
bool _isInitialized = false;
|
||||
bool _busyFlag = false;
|
||||
|
||||
|
||||
@ -44,7 +44,15 @@ ActivePowerControlCommand::ActivePowerControlCommand(InverterAbstract* inv, cons
|
||||
|
||||
String ActivePowerControlCommand::getCommandName() const
|
||||
{
|
||||
return "ActivePowerControl";
|
||||
char buffer[30];
|
||||
snprintf(buffer, sizeof(buffer), "ActivePowerControl (%02X)", getType());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool ActivePowerControlCommand::areSameParameter(CommandAbstract* other)
|
||||
{
|
||||
return CommandAbstract::areSameParameter(other)
|
||||
&& this->getType() == static_cast<ActivePowerControlCommand*>(other)->getType();
|
||||
}
|
||||
|
||||
void ActivePowerControlCommand::setActivePowerLimit(const float limit, const PowerLimitControlType type)
|
||||
@ -79,7 +87,10 @@ bool ActivePowerControlCommand::handleResponse(const fragment_t fragment[], cons
|
||||
}
|
||||
}
|
||||
_inv->SystemConfigPara()->setLastUpdateCommand(millis());
|
||||
_inv->SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK);
|
||||
std::shared_ptr<ActivePowerControlCommand> cmd(std::shared_ptr<ActivePowerControlCommand>(), this);
|
||||
if (_inv->getRadio()->countSimilarCommands(cmd) == 1) {
|
||||
_inv->SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -89,7 +100,7 @@ float ActivePowerControlCommand::getLimit() const
|
||||
return l / 10;
|
||||
}
|
||||
|
||||
PowerLimitControlType ActivePowerControlCommand::getType()
|
||||
PowerLimitControlType ActivePowerControlCommand::getType() const
|
||||
{
|
||||
return (PowerLimitControlType)((static_cast<uint16_t>(_payload[14]) << 8) | _payload[15]);
|
||||
}
|
||||
|
||||
@ -15,11 +15,13 @@ public:
|
||||
explicit ActivePowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0);
|
||||
|
||||
virtual String getCommandName() const;
|
||||
virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::RemoveOldest; }
|
||||
virtual bool areSameParameter(CommandAbstract* other);
|
||||
|
||||
virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id);
|
||||
virtual void gotTimeout();
|
||||
|
||||
void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent);
|
||||
float getLimit() const;
|
||||
PowerLimitControlType getType();
|
||||
PowerLimitControlType getType() const;
|
||||
};
|
||||
|
||||
@ -138,3 +138,9 @@ uint8_t CommandAbstract::getMaxRetransmitCount() const
|
||||
{
|
||||
return MAX_RETRANSMIT_COUNT;
|
||||
}
|
||||
|
||||
bool CommandAbstract::areSameParameter(CommandAbstract* other)
|
||||
{
|
||||
return this->getCommandName() == other->getCommandName()
|
||||
&& this->_targetAddress == other->getTargetAddress();
|
||||
}
|
||||
|
||||
@ -11,6 +11,18 @@
|
||||
|
||||
class InverterAbstract;
|
||||
|
||||
enum class QueueInsertType {
|
||||
AllowMultiple,
|
||||
// Remove from beginning of the queue
|
||||
RemoveOldest,
|
||||
|
||||
// Don't insert command if it already exist
|
||||
RemoveNewest,
|
||||
|
||||
// Replace the existing entry in the queue by the one to be added
|
||||
ReplaceExistent,
|
||||
};
|
||||
|
||||
class CommandAbstract {
|
||||
public:
|
||||
explicit CommandAbstract(InverterAbstract* inv, const uint64_t router_address = 0);
|
||||
@ -46,6 +58,10 @@ public:
|
||||
// Sets the amount how often a missing fragment is re-requested if it was not available
|
||||
virtual uint8_t getMaxRetransmitCount() const;
|
||||
|
||||
// Returns whether multiple instances of this command are allowed in the command queue.
|
||||
virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::RemoveNewest; }
|
||||
virtual bool areSameParameter(CommandAbstract* other);
|
||||
|
||||
protected:
|
||||
uint8_t _payload[RF_LEN];
|
||||
uint8_t _payload_size;
|
||||
|
||||
@ -8,6 +8,7 @@ public:
|
||||
explicit PowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0);
|
||||
|
||||
virtual String getCommandName() const;
|
||||
virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::AllowMultiple; }
|
||||
|
||||
virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id);
|
||||
virtual void gotTimeout();
|
||||
|
||||
@ -30,7 +30,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HERF_1CH::HERF_1CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial) {};
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HERF_1CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -37,7 +37,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial) {};
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HERF_2CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
@ -48,7 +50,7 @@ bool HERF_2CH::isValidSerial(const uint64_t serial)
|
||||
|
||||
String HERF_2CH::typeName() const
|
||||
{
|
||||
return "HERF-800-2T";
|
||||
return "HERF-600/800-2T";
|
||||
}
|
||||
|
||||
const byteAssign_t* HERF_2CH::getByteAssignment() const
|
||||
|
||||
@ -5,7 +5,9 @@
|
||||
#include "HERF_4CH.h"
|
||||
|
||||
HERF_4CH::HERF_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_4CH(radio, serial) {};
|
||||
: HM_4CH(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HERF_4CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -29,7 +29,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMS_Abstract(radio, serial) {};
|
||||
: HMS_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMS_1CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -29,18 +29,20 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMS_Abstract(radio, serial) {};
|
||||
: HMS_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMS_1CHv2::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
// serial >= 0x112500000000 && serial <= 0x1125ffffffff
|
||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||
return preSerial == 0x1125;
|
||||
return preSerial == 0x1125 || preSerial == 0x1400;
|
||||
}
|
||||
|
||||
String HMS_1CHv2::typeName() const
|
||||
{
|
||||
return "HMS-500-1T v2";
|
||||
return "HMS-450/500-1T v2";
|
||||
}
|
||||
|
||||
const byteAssign_t* HMS_1CHv2::getByteAssignment() const
|
||||
|
||||
@ -36,7 +36,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMS_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMS_Abstract(radio, serial) {};
|
||||
: HMS_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMS_2CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -50,7 +50,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMS_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMS_Abstract(radio, serial) {};
|
||||
: HMS_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMS_4CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
@ -73,3 +75,10 @@ uint8_t HMS_4CH::getByteAssignmentSize() const
|
||||
{
|
||||
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
|
||||
}
|
||||
|
||||
bool HMS_4CH::supportsPowerDistributionLogic()
|
||||
{
|
||||
// This feature was added in inverter firmware version 01.01.12 and
|
||||
// will limit the AC output instead of limiting the DC inputs.
|
||||
return DevInfo()->getFwBuildVersion() >= 10112U;
|
||||
}
|
||||
|
||||
@ -10,4 +10,5 @@ public:
|
||||
String typeName() const;
|
||||
const byteAssign_t* getByteAssignment() const;
|
||||
uint8_t getByteAssignmentSize() const;
|
||||
bool supportsPowerDistributionLogic() final;
|
||||
};
|
||||
@ -59,7 +59,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMT_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMT_Abstract(radio, serial) {};
|
||||
: HMT_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMT_4CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -73,7 +73,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HMT_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HMT_Abstract(radio, serial) {};
|
||||
: HMT_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HMT_6CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -12,7 +12,7 @@ HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
EventLog()->setMessageType(AlarmMessageType_t::HMT);
|
||||
};
|
||||
}
|
||||
|
||||
bool HMT_Abstract::sendChangeChannelRequest()
|
||||
{
|
||||
@ -26,4 +26,4 @@ bool HMT_Abstract::sendChangeChannelRequest()
|
||||
_radio->enqueCommand(cmdChannel);
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@ -29,7 +29,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HM_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial) {};
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HM_1CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -37,7 +37,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HM_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial) {};
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HM_2CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -50,7 +50,9 @@ static const byteAssign_t byteAssignment[] = {
|
||||
};
|
||||
|
||||
HM_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||
: HM_Abstract(radio, serial) {};
|
||||
: HM_Abstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HM_4CH::isValidSerial(const uint64_t serial)
|
||||
{
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
#include "commands/SystemConfigParaCommand.h"
|
||||
|
||||
HM_Abstract::HM_Abstract(HoymilesRadio* radio, const uint64_t serial)
|
||||
: InverterAbstract(radio, serial) {};
|
||||
: InverterAbstract(radio, serial)
|
||||
{
|
||||
}
|
||||
|
||||
bool HM_Abstract::sendStatsRequest()
|
||||
{
|
||||
@ -217,3 +219,8 @@ bool HM_Abstract::sendGridOnProFileParaRequest()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HM_Abstract::supportsPowerDistributionLogic()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ public:
|
||||
bool sendRestartControlRequest();
|
||||
bool resendPowerControlRequest();
|
||||
bool sendGridOnProFileParaRequest();
|
||||
bool supportsPowerDistributionLogic() override;
|
||||
|
||||
private:
|
||||
uint8_t _lastAlarmLogCnt = 0;
|
||||
|
||||
@ -103,6 +103,9 @@ public:
|
||||
virtual bool sendChangeChannelRequest();
|
||||
virtual bool sendGridOnProFileParaRequest() = 0;
|
||||
|
||||
// This feature will limit the AC output instead of limiting the DC inputs.
|
||||
virtual bool supportsPowerDistributionLogic() = 0;
|
||||
|
||||
HoymilesRadio* getRadio();
|
||||
|
||||
AlarmLogParser* EventLog();
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
# Class overview
|
||||
|
||||
| Class | Models | Serial range |
|
||||
| --------------| --------------------------- | ------------- -- |
|
||||
| ------------- | --------------------------- | ---------------- |
|
||||
| HM_1CH | HM-300/350/400-1T | 1121 |
|
||||
| HM_2CH | HM-600/700/800-2T | 1141 |
|
||||
| HM_4CH | HM-1000/1200/1500-4T | 1161 |
|
||||
| HMS_1CH | HMS-300/350/400/450/500-1T | 1124 |
|
||||
| HMS_1CHv2 | HMS-500-1T v2 | 1125 |
|
||||
| HMS_1CHv2 | HMS-450/500-1T v2 | 1125, 1400 |
|
||||
| HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144, 1410 |
|
||||
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
||||
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
||||
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
||||
| HERF_1CH | HERF 300 | 2841 |
|
||||
| HERF_2CH | HERF 800 | 2821 |
|
||||
| HERF_2CH | HERF 600/800 | 2821 |
|
||||
| HERF_4CH | HERF 1800 | 2801 |
|
||||
|
||||
@ -81,6 +81,7 @@ const devInfo_t devInfo[] = {
|
||||
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01
|
||||
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" }, // 01
|
||||
|
||||
{ { 0xF1, 0x01, 0x10, ALL }, 600, "HERF-600" }, // 00
|
||||
{ { 0xF1, 0x01, 0x14, ALL }, 800, "HERF-800" }, // 00
|
||||
{ { 0xF1, 0x01, 0x24, ALL }, 1600, "HERF-1600" }, // 00
|
||||
{ { 0xF1, 0x01, 0x22, ALL }, 1800, "HERF-1800" }, // 00
|
||||
|
||||
51
lib/Hoymiles/src/queue/CommandQueue.cpp
Normal file
51
lib/Hoymiles/src/queue/CommandQueue.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2024 Thomas Basler and others
|
||||
*/
|
||||
#include "CommandQueue.h"
|
||||
#include "../inverters/InverterAbstract.h"
|
||||
#include <algorithm>
|
||||
|
||||
void CommandQueue::removeAllEntriesForInverter(InverterAbstract* inv)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
auto it = std::remove_if(_queue.begin(), _queue.end(),
|
||||
[&inv](std::shared_ptr<CommandAbstract> v) -> bool { return v.get()->getTargetAddress() == inv->serial(); });
|
||||
_queue.erase(it, _queue.end());
|
||||
}
|
||||
|
||||
void CommandQueue::removeDuplicatedEntries(std::shared_ptr<CommandAbstract> cmd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
auto it = std::remove_if(_queue.begin() + 1, _queue.end(),
|
||||
[&cmd](std::shared_ptr<CommandAbstract> v) -> bool {
|
||||
return cmd->areSameParameter(v.get())
|
||||
&& cmd.get()->getQueueInsertType() == QueueInsertType::RemoveOldest;
|
||||
});
|
||||
_queue.erase(it, _queue.end());
|
||||
}
|
||||
|
||||
void CommandQueue::replaceEntries(std::shared_ptr<CommandAbstract> cmd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
std::replace_if(_queue.begin() + 1, _queue.end(),
|
||||
[&cmd](std::shared_ptr<CommandAbstract> v)-> bool {
|
||||
return cmd.get()->getQueueInsertType() == QueueInsertType::ReplaceExistent
|
||||
&& cmd->areSameParameter(v.get());
|
||||
},
|
||||
cmd
|
||||
);
|
||||
}
|
||||
|
||||
uint8_t CommandQueue::countSimilarCommands(std::shared_ptr<CommandAbstract> cmd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
return std::count_if(_queue.begin(), _queue.end(),
|
||||
[&cmd](std::shared_ptr<CommandAbstract> v) -> bool {
|
||||
return cmd->areSameParameter(v.get());
|
||||
});
|
||||
}
|
||||
17
lib/Hoymiles/src/queue/CommandQueue.h
Normal file
17
lib/Hoymiles/src/queue/CommandQueue.h
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "../commands/CommandAbstract.h"
|
||||
#include <ThreadSafeQueue.h>
|
||||
#include <memory>
|
||||
|
||||
class InverterAbstract;
|
||||
|
||||
class CommandQueue : public ThreadSafeQueue<std::shared_ptr<CommandAbstract>> {
|
||||
public:
|
||||
void removeAllEntriesForInverter(InverterAbstract* inv);
|
||||
void removeDuplicatedEntries(std::shared_ptr<CommandAbstract> cmd);
|
||||
void replaceEntries(std::shared_ptr<CommandAbstract> cmd);
|
||||
|
||||
uint8_t countSimilarCommands(std::shared_ptr<CommandAbstract> cmd);
|
||||
};
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
|
||||
template <typename T>
|
||||
class ThreadSafeQueue {
|
||||
@ -33,14 +33,14 @@ public:
|
||||
return {};
|
||||
}
|
||||
T tmp = _queue.front();
|
||||
_queue.pop();
|
||||
_queue.pop_front();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void push(const T& item)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_queue.push(item);
|
||||
_queue.push_back(item);
|
||||
}
|
||||
|
||||
T front()
|
||||
@ -49,6 +49,10 @@ public:
|
||||
return _queue.front();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::deque<T> _queue;
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
private:
|
||||
// Moved out of public interface to prevent races between this
|
||||
// and pop().
|
||||
@ -56,7 +60,4 @@ private:
|
||||
{
|
||||
return _queue.empty();
|
||||
}
|
||||
|
||||
std::queue<T> _queue;
|
||||
mutable std::mutex _mutex;
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ def get_firmware_specifier_build_flag():
|
||||
build_version = get_build_version()
|
||||
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
||||
build_branch = get_build_branch()
|
||||
build_flag += " -D AUTO_GIT_BRANCH=\\\"" + branch_name + "\\\""
|
||||
build_flag += " -D AUTO_GIT_BRANCH=\\\"" + build_branch + "\\\""
|
||||
return (build_flag)
|
||||
|
||||
|
||||
|
||||
@ -130,4 +130,5 @@ def esp32_create_combined_bin(source, target, env):
|
||||
esptool.main(cmd)
|
||||
|
||||
|
||||
env.AddPostAction("buildprog", esp32_create_combined_bin)
|
||||
from SCons.Script import AlwaysBuild
|
||||
AlwaysBuild(env.AddPostAction("buildprog", esp32_create_combined_bin))
|
||||
|
||||
@ -30,6 +30,7 @@ build_flags =
|
||||
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128
|
||||
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
||||
-DEMC_TASK_STACK_SIZE=6400
|
||||
; -DHOY_DEBUG_QUEUE
|
||||
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
|
||||
; Have to remove -Werror because of
|
||||
; https://github.com/espressif/arduino-esp32/issues/9044 and
|
||||
@ -41,10 +42,10 @@ build_unflags =
|
||||
-std=gnu++11
|
||||
|
||||
lib_deps =
|
||||
mathieucarbou/ESPAsyncWebServer @ 3.3.22
|
||||
bblanchon/ArduinoJson @ 7.2.0
|
||||
mathieucarbou/ESPAsyncWebServer @ 3.6.0
|
||||
bblanchon/ArduinoJson @ 7.3.0
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.7.0
|
||||
nrf24/RF24 @ 1.4.10
|
||||
nrf24/RF24 @ 1.4.11
|
||||
olikraus/U8g2 @ 2.36.2
|
||||
buelowp/sunset @ 1.1.7
|
||||
arkhipenko/TaskScheduler @ 3.8.5
|
||||
@ -78,6 +79,7 @@ upload_protocol = esptool
|
||||
[env:generic_esp32]
|
||||
board = esp32dev
|
||||
build_flags = ${env.build_flags}
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
|
||||
|
||||
[env:generic_esp32_16mb_psram]
|
||||
@ -86,6 +88,7 @@ board_build.flash_mode = qio
|
||||
board_build.partitions = partitions_custom_16mb.csv
|
||||
board_upload.flash_size = 16MB
|
||||
build_flags = ${env.build_flags}
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
-DBOARD_HAS_PSRAM
|
||||
-mfix-esp32-psram-cache-issue
|
||||
|
||||
@ -94,6 +97,7 @@ build_flags = ${env.build_flags}
|
||||
board = esp32-c3-devkitc-02
|
||||
custom_patches = ${env.custom_patches}
|
||||
build_flags = ${env.build_flags}
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
|
||||
|
||||
[env:generic_esp32c3_usb]
|
||||
@ -102,11 +106,13 @@ custom_patches = ${env.custom_patches}
|
||||
build_flags = ${env.build_flags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
|
||||
|
||||
[env:generic_esp32s3]
|
||||
board = esp32-s3-devkitc-1
|
||||
build_flags = ${env.build_flags}
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
|
||||
|
||||
[env:generic_esp32s3_usb]
|
||||
@ -115,6 +121,7 @@ upload_protocol = esp-builtin
|
||||
build_flags = ${env.build_flags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DPIN_MAPPING_REQUIRED=1
|
||||
|
||||
|
||||
[env:generic]
|
||||
|
||||
@ -353,6 +353,8 @@ void ConfigurationClass::migrate()
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::skipBom(f);
|
||||
|
||||
JsonDocument doc;
|
||||
|
||||
// Deserialize the JSON document
|
||||
|
||||
@ -32,7 +32,7 @@ void MqttHandleDtuClass::loop()
|
||||
return;
|
||||
}
|
||||
|
||||
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
||||
MqttSettings.publish("dtu/uptime", String(esp_timer_get_time() / 1000000));
|
||||
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
||||
MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname());
|
||||
MqttSettings.publish("dtu/heap/size", String(ESP.getHeapSize()));
|
||||
|
||||
@ -70,6 +70,7 @@ void MqttHandleHassClass::publishConfig()
|
||||
publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE);
|
||||
publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE);
|
||||
publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE);
|
||||
publishDtuSensor("DC Power", "dc/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE);
|
||||
|
||||
publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||
|
||||
|
||||
@ -179,7 +179,7 @@ String MqttSettingsClass::getPrefix() const
|
||||
return Configuration.get().Mqtt.Topic;
|
||||
}
|
||||
|
||||
String MqttSettingsClass::getClientId()
|
||||
String MqttSettingsClass::getClientId() const
|
||||
{
|
||||
String clientId = Configuration.get().Mqtt.ClientId;
|
||||
if (clientId == "") {
|
||||
|
||||
@ -287,8 +287,7 @@ void NetworkSettingsClass::applyConfig()
|
||||
MessageOutput.print("new credentials... ");
|
||||
WiFi.begin(
|
||||
Configuration.get().WiFi.Ssid,
|
||||
Configuration.get().WiFi.Password,
|
||||
WIFI_ALL_CHANNEL_SCAN);
|
||||
Configuration.get().WiFi.Password);
|
||||
} else {
|
||||
MessageOutput.print("existing credentials... ");
|
||||
WiFi.begin();
|
||||
|
||||
@ -212,6 +212,8 @@ bool PinMappingClass::init(const String& deviceMapping)
|
||||
for (uint8_t i = 0; i < doc.size(); i++) {
|
||||
String devName = doc[i]["name"] | "";
|
||||
if (devName == deviceMapping) {
|
||||
_mappingSelected = true;
|
||||
|
||||
strlcpy(_pinMapping.name, devName.c_str(), sizeof(_pinMapping.name));
|
||||
_pinMapping.nrf24_clk = doc[i]["nrf24"]["clk"] | HOYMILES_PIN_SCLK;
|
||||
_pinMapping.nrf24_cs = doc[i]["nrf24"]["cs"] | HOYMILES_PIN_CS;
|
||||
|
||||
@ -129,10 +129,14 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
return;
|
||||
}
|
||||
|
||||
bool performRestart = false;
|
||||
|
||||
{
|
||||
auto guard = Configuration.getWriteGuard();
|
||||
auto& config = guard.getConfig();
|
||||
|
||||
performRestart = root["curPin"]["name"].as<String>() != config.Dev_PinMapping;
|
||||
|
||||
strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as<String>().c_str(), sizeof(config.Dev_PinMapping));
|
||||
config.Display.Rotation = root["display"]["rotation"].as<uint8_t>();
|
||||
config.Display.PowerSafe = root["display"]["power_safe"].as<bool>();
|
||||
@ -149,7 +153,6 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
}
|
||||
|
||||
auto const& config = Configuration.get();
|
||||
bool performRestart = root["curPin"]["name"].as<String>() != config.Dev_PinMapping;
|
||||
|
||||
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
|
||||
Display.setOrientation(config.Display.Rotation);
|
||||
|
||||
@ -35,6 +35,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
||||
root["hw_model_name"] = inv->DevInfo()->getHwModelName();
|
||||
root["max_power"] = inv->DevInfo()->getMaxPower();
|
||||
root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr();
|
||||
root["pdl_supported"] = inv->supportsPowerDistributionLogic();
|
||||
}
|
||||
|
||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||
|
||||
@ -5,11 +5,10 @@
|
||||
#include "WebApi_firmware.h"
|
||||
#include "Configuration.h"
|
||||
#include "RestartHelper.h"
|
||||
#include "Update.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Update.h>
|
||||
|
||||
void WebApiFirmwareClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||
{
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
#include "defaults.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
#ifndef PIN_MAPPING_REQUIRED
|
||||
#define PIN_MAPPING_REQUIRED 0
|
||||
#endif
|
||||
|
||||
WebApiWsLiveClass::WebApiWsLiveClass()
|
||||
: _ws("/livedata")
|
||||
, _wsCleanupTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&WebApiWsLiveClass::wsCleanupTaskCb, this))
|
||||
@ -125,6 +129,8 @@ void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
||||
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
||||
hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
|
||||
hintObj["default_password"] = strcmp(Configuration.get().Security.Password, ACCESS_POINT_PASSWORD) == 0;
|
||||
|
||||
hintObj["pin_mapping_issue"] = PIN_MAPPING_REQUIRED && !PinMapping.isMappingSelected();
|
||||
}
|
||||
|
||||
void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv)
|
||||
|
||||
@ -88,7 +88,7 @@ void setup()
|
||||
|
||||
// Load PinMapping
|
||||
MessageOutput.print("Reading PinMapping... ");
|
||||
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||
if (PinMapping.init(Configuration.get().Dev_PinMapping)) {
|
||||
MessageOutput.print("found valid mapping ");
|
||||
} else {
|
||||
MessageOutput.print("using default config ");
|
||||
@ -96,7 +96,7 @@ void setup()
|
||||
const auto& pin = PinMapping.get();
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize WiFi
|
||||
// Initialize Network
|
||||
MessageOutput.print("Initialize Network... ");
|
||||
NetworkSettings.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
@ -17,35 +17,35 @@
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons-vue": "^1.11.3",
|
||||
"mitt": "^3.0.1",
|
||||
"sortablejs": "^1.15.3",
|
||||
"sortablejs": "^1.15.6",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vue": "^3.5.12",
|
||||
"vue-i18n": "10.0.4",
|
||||
"vue-router": "^4.4.5"
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "11.0.1",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^5.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/node": "^22.10.6",
|
||||
"@types/pulltorefreshjs": "^0.1.7",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@types/spark-md5": "^3.0.5",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.4.2",
|
||||
"pulltorefreshjs": "^0.1.22",
|
||||
"sass": "=1.77.6",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.10",
|
||||
"terser": "^5.37.0",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2",
|
||||
"vue-tsc": "^2.1.10"
|
||||
"vue-tsc": "^2.2.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div :class="{ 'container-xxl': !isWideScreen, 'container-fluid': isWideScreen }" role="main">
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-sm-11">
|
||||
<h1>
|
||||
<div class="row mb-3">
|
||||
<div :class="'align-content-center ' + (showReload ? 'col-10' : 'col-12')">
|
||||
<h1 class="mb-0">
|
||||
{{ title }}
|
||||
<span
|
||||
v-if="showWebSocket"
|
||||
@ -14,10 +14,10 @@
|
||||
></span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-sm-1" v-if="showReload">
|
||||
<div class="col-2 align-content-center" v-if="showReload">
|
||||
<button
|
||||
type="button"
|
||||
class="float-end btn btn-outline-primary"
|
||||
class="float-end btn btn-outline-primary fs-5"
|
||||
@click="$emit('reload')"
|
||||
v-tooltip
|
||||
:title="$t('base.Reload')"
|
||||
|
||||
@ -46,6 +46,12 @@
|
||||
<td>{{ $t('devinfo.HardwareVersion') }}</td>
|
||||
<td>{{ devInfoList.hw_version }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('devinfo.SupportsPowerDistributionLogic') }}</td>
|
||||
<td>
|
||||
<StatusBadge :status="devInfoList.pdl_supported" true_text="devinfo.Yes" false_text="devinfo.No" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
@ -53,6 +59,7 @@
|
||||
<script lang="ts">
|
||||
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
||||
import StatusBadge from '@/components/StatusBadge.vue';
|
||||
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
||||
import { defineComponent, type PropType } from 'vue';
|
||||
|
||||
@ -60,6 +67,7 @@ export default defineComponent({
|
||||
components: {
|
||||
BootstrapAlert,
|
||||
BIconInfoSquare,
|
||||
StatusBadge,
|
||||
},
|
||||
props: {
|
||||
devInfoList: { type: Object as PropType<DevInfoStatus>, required: true },
|
||||
|
||||
@ -1,16 +1,36 @@
|
||||
<template>
|
||||
<BootstrapAlert :show="hints.radio_problem" variant="danger">
|
||||
<BIconBroadcast class="fs-4" /> {{ $t('hints.RadioProblem') }}
|
||||
<div class="d-flex">
|
||||
<div class="align-content-center"><BIconBroadcast class="fs-4" /></div>
|
||||
<div class="align-content-center ms-3">{{ $t('hints.RadioProblem') }}</div>
|
||||
</div>
|
||||
</BootstrapAlert>
|
||||
|
||||
<BootstrapAlert :show="hints.time_sync" variant="danger">
|
||||
<BIconClock class="fs-4" /> {{ $t('hints.TimeSync') }}
|
||||
<a @click="gotoTimeSettings" href="#" class="alert-link">{{ $t('hints.TimeSyncLink') }}</a>
|
||||
<div class="d-flex">
|
||||
<div class="align-content-center"><BIconClock class="fs-4" /></div>
|
||||
<div class="align-content-center ms-3">
|
||||
{{ $t('hints.TimeSync') }}
|
||||
<a @click="gotoTimeSettings" href="#" class="alert-link">{{ $t('hints.TimeSyncLink') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</BootstrapAlert>
|
||||
|
||||
<BootstrapAlert :show="hints.default_password" variant="danger">
|
||||
<BIconExclamationCircle class="fs-4" /> {{ $t('hints.DefaultPassword') }}
|
||||
<a @click="gotoPasswordSettings" href="#" class="alert-link">{{ $t('hints.DefaultPasswordLink') }}</a>
|
||||
<div class="d-flex">
|
||||
<div class="align-content-center"><BIconExclamationCircle class="fs-4" /></div>
|
||||
<div class="align-content-center ms-3">
|
||||
{{ $t('hints.DefaultPassword') }}
|
||||
<a @click="gotoPasswordSettings" href="#" class="alert-link">{{ $t('hints.DefaultPasswordLink') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</BootstrapAlert>
|
||||
|
||||
<BootstrapAlert :show="hints.pin_mapping_issue" variant="warning">
|
||||
<div class="d-flex">
|
||||
<div class="align-content-center"><BIconExclamationCircle class="fs-4" /></div>
|
||||
<div class="align-content-center ms-3" v-html="$t('hints.PinMappingIssue')"></div>
|
||||
</div>
|
||||
</BootstrapAlert>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3">
|
||||
<div class="col">
|
||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldTotal')">
|
||||
<CardElement centerContent textVariant="text-bg-primary" :text="$t('invertertotalinfo.TotalYieldTotal')">
|
||||
<h2>
|
||||
{{
|
||||
$n(totalData.YieldTotal.v, 'decimal', {
|
||||
@ -14,7 +14,7 @@
|
||||
</CardElement>
|
||||
</div>
|
||||
<div class="col">
|
||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldDay')">
|
||||
<CardElement centerContent textVariant="text-bg-primary" :text="$t('invertertotalinfo.TotalYieldDay')">
|
||||
<h2>
|
||||
{{
|
||||
$n(totalData.YieldDay.v, 'decimal', {
|
||||
@ -27,7 +27,7 @@
|
||||
</CardElement>
|
||||
</div>
|
||||
<div class="col">
|
||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalPower')">
|
||||
<CardElement centerContent textVariant="text-bg-primary" :text="$t('invertertotalinfo.TotalPower')">
|
||||
<h2>
|
||||
{{
|
||||
$n(totalData.Power.v, 'decimal', {
|
||||
|
||||
@ -30,7 +30,9 @@
|
||||
"Refreshing": "Aktualisieren",
|
||||
"Pull": "Zum Aktualisieren nach unten ziehen",
|
||||
"Release": "Loslassen zum Aktualisieren",
|
||||
"Close": "Schließen"
|
||||
"Close": "Schließen",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein"
|
||||
},
|
||||
"wait": {
|
||||
"NotReady": "OpenDTU ist noch nicht bereit",
|
||||
@ -179,7 +181,10 @@
|
||||
"FirmwareVersion": "Firmware-Version",
|
||||
"FirmwareBuildDate": "Firmware-Erstellungsdatum",
|
||||
"HardwarePartNumber": "Hardware-Teilenummer",
|
||||
"HardwareVersion": "Hardware-Version"
|
||||
"HardwareVersion": "Hardware-Version",
|
||||
"SupportsPowerDistributionLogic": "'Power Distribution Logic' unterstützt",
|
||||
"Yes": "@:base.Yes",
|
||||
"No": "@:base.No"
|
||||
},
|
||||
"gridprofile": {
|
||||
"NoInfo": "@:devinfo.NoInfo",
|
||||
@ -627,7 +632,8 @@
|
||||
"TimeSync": "Die Uhr wurde noch nicht synchronisiert. Ohne eine korrekt eingestellte Uhr werden keine Anfragen an den Wechselrichter gesendet. Dies ist kurz nach dem Start normal. Nach einer längeren Laufzeit (>1 Minute) bedeutet es jedoch, dass der NTP-Server nicht erreichbar ist.",
|
||||
"TimeSyncLink": "Bitte überprüfen Sie Ihre Zeiteinstellungen.",
|
||||
"DefaultPassword": "Sie verwenden das Standardpasswort für die Weboberfläche und den Notfall Access Point. Dies ist potenziell unsicher.",
|
||||
"DefaultPasswordLink": "Bitte ändern Sie das Passwort."
|
||||
"DefaultPasswordLink": "Bitte ändern Sie das Passwort.",
|
||||
"PinMappingIssue": "Sie verwenden eine generische Firmware, haben jedoch noch keine Datei mit Verdrahtungsprofilen (<code>pin_mapping.json</code>) hochgeladen oder kein dort definiertes Profil ausgewählt. Details hierzu entnehmen Sie bitte der <a href=\"https://opendtu.solar/firmware/device_profiles/\" target=\"_blank\" class=\"alert-link\">Dokumentation</a>."
|
||||
},
|
||||
"deviceadmin": {
|
||||
"DeviceManager": "Hardware-Einstellungen",
|
||||
|
||||
@ -30,7 +30,9 @@
|
||||
"Refreshing": "Refreshing",
|
||||
"Pull": "Pull down to refresh",
|
||||
"Release": "Release to refresh",
|
||||
"Close": "Close"
|
||||
"Close": "Close",
|
||||
"Yes": "Yes",
|
||||
"No": "No"
|
||||
},
|
||||
"wait": {
|
||||
"NotReady": "OpenDTU is not yet ready",
|
||||
@ -179,7 +181,10 @@
|
||||
"FirmwareVersion": "Firmware Version",
|
||||
"FirmwareBuildDate": "Firmware Build Date",
|
||||
"HardwarePartNumber": "Hardware Part Number",
|
||||
"HardwareVersion": "Hardware Version"
|
||||
"HardwareVersion": "Hardware Version",
|
||||
"SupportsPowerDistributionLogic": "'Power Distribution Logic' supported",
|
||||
"Yes": "@:base.Yes",
|
||||
"No": "@:base.No"
|
||||
},
|
||||
"gridprofile": {
|
||||
"NoInfo": "@:devinfo.NoInfo",
|
||||
@ -627,7 +632,8 @@
|
||||
"TimeSync": "The clock has not yet been synchronised. Without a correctly set clock, no requests are made to the inverter. This is normal shortly after the start. However, after a longer runtime (>1 minute), it indicates that the NTP server is not accessible.",
|
||||
"TimeSyncLink": "Please check your time settings.",
|
||||
"DefaultPassword": "You are using the default password for the web interface and the emergency access point. This is potentially insecure.",
|
||||
"DefaultPasswordLink": "Please change the password."
|
||||
"DefaultPasswordLink": "Please change the password.",
|
||||
"PinMappingIssue": "You are using a generic firmware image, but have not yet uploaded a file with device profiles (<code>pin_mapping.json</code>) or have not selected a profile defined there. Please refer to the <a href=\"https://opendtu.solar/firmware/device_profiles/\" target=\"_blank\" class=\"alert-link\">documentation</a> for details."
|
||||
},
|
||||
"deviceadmin": {
|
||||
"DeviceManager": "Device-Manager",
|
||||
|
||||
@ -30,7 +30,9 @@
|
||||
"Refreshing": "Refreshing",
|
||||
"Pull": "Pull down to refresh",
|
||||
"Release": "Release to refresh",
|
||||
"Close": "Fermer"
|
||||
"Close": "Fermer",
|
||||
"Yes": "Yes",
|
||||
"No": "No"
|
||||
},
|
||||
"wait": {
|
||||
"NotReady": "OpenDTU is not yet ready",
|
||||
@ -179,7 +181,10 @@
|
||||
"FirmwareVersion": "Version du firmware",
|
||||
"FirmwareBuildDate": "Date de création du firmware",
|
||||
"HardwarePartNumber": "Numéro d'article matériel",
|
||||
"HardwareVersion": "Version du matériel"
|
||||
"HardwareVersion": "Version du matériel",
|
||||
"SupportsPowerDistributionLogic": "'Power Distribution Logic' supported",
|
||||
"Yes": "@:base.Yes",
|
||||
"No": "@:base.No"
|
||||
},
|
||||
"gridprofile": {
|
||||
"NoInfo": "@:devinfo.NoInfo",
|
||||
@ -609,7 +614,8 @@
|
||||
"TimeSync": "L'horloge n'a pas encore été synchronisée. Sans une horloge correctement réglée, aucune demande n'est adressée à l'onduleur. Ceci est normal peu de temps après le démarrage. Cependant, après un temps de fonctionnement plus long (>1 minute), cela indique que le serveur NTP n'est pas accessible.",
|
||||
"TimeSyncLink": "Veuillez vérifier vos paramètres horaires.",
|
||||
"DefaultPassword": "Vous utilisez le mot de passe par défaut pour l'interface Web et le point d'accès d'urgence. Ceci est potentiellement non sécurisé.",
|
||||
"DefaultPasswordLink": "Merci de changer le mot de passe."
|
||||
"DefaultPasswordLink": "Merci de changer le mot de passe.",
|
||||
"PinMappingIssue": "Vous utilisez une image générique du micrologiciel, mais vous n'avez pas encore téléchargé un fichier contenant les profils de l'appareil (<code>pin_mapping.json</code>) ou vous n'avez pas sélectionné un profil défini dans ce fichier. Veuillez vous référer à la <a href=\"https://opendtu.solar/firmware/device_profiles/\" target=\"_blank\" class=\"alert-link\">documentation</a> pour plus de détails."
|
||||
},
|
||||
"deviceadmin": {
|
||||
"DeviceManager": "Gestionnaire de périphériques",
|
||||
|
||||
@ -8,4 +8,5 @@ export interface DevInfoStatus {
|
||||
hw_version: number;
|
||||
hw_model_name: string;
|
||||
max_power: number;
|
||||
pdl_supported: boolean;
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ export interface Hints {
|
||||
time_sync: boolean;
|
||||
default_password: boolean;
|
||||
radio_problem: boolean;
|
||||
pin_mapping_issue: boolean;
|
||||
}
|
||||
|
||||
export interface LiveData {
|
||||
|
||||
@ -31,12 +31,22 @@
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-2">
|
||||
<BIconXCircleFill class="fs-4" v-if="!inverter.reachable" />
|
||||
<BIconExclamationCircleFill
|
||||
class="fs-4"
|
||||
v-if="inverter.reachable && !inverter.producing"
|
||||
/>
|
||||
<BIconCheckCircleFill class="fs-4" v-if="inverter.reachable && inverter.producing" />
|
||||
<span
|
||||
v-if="inverter.AC"
|
||||
class="badge"
|
||||
:class="{
|
||||
'text-bg-secondary': !inverter.poll_enabled,
|
||||
'text-bg-danger': inverter.poll_enabled && !inverter.reachable,
|
||||
'text-bg-warning':
|
||||
inverter.poll_enabled && inverter.reachable && !inverter.producing,
|
||||
'text-bg-success':
|
||||
inverter.poll_enabled && inverter.reachable && inverter.producing,
|
||||
}"
|
||||
>
|
||||
{{ $n(inverter.AC[0]?.Power?.v || 0, 'decimalNoDigits') }}
|
||||
{{ inverter.AC[0].Power?.u }}
|
||||
</span>
|
||||
<span v-else class="badge text-bg-light">-</span>
|
||||
</div>
|
||||
<div class="ms-auto me-auto">
|
||||
{{ inverter.name }}
|
||||
@ -70,7 +80,7 @@
|
||||
'text-bg-tertiary': !inverter.poll_enabled,
|
||||
'text-bg-danger': inverter.poll_enabled && !inverter.reachable,
|
||||
'text-bg-warning': inverter.poll_enabled && inverter.reachable && !inverter.producing,
|
||||
'text-bg-primary': inverter.poll_enabled && inverter.reachable && inverter.producing,
|
||||
'text-bg-success': inverter.poll_enabled && inverter.reachable && inverter.producing,
|
||||
}"
|
||||
>
|
||||
<div class="p-1 flex-grow-1">
|
||||
@ -513,9 +523,7 @@ import * as bootstrap from 'bootstrap';
|
||||
import {
|
||||
BIconArrowCounterclockwise,
|
||||
BIconBroadcast,
|
||||
BIconCheckCircleFill,
|
||||
BIconCpu,
|
||||
BIconExclamationCircleFill,
|
||||
BIconInfoCircle,
|
||||
BIconJournalText,
|
||||
BIconOutlet,
|
||||
@ -523,7 +531,6 @@ import {
|
||||
BIconSpeedometer,
|
||||
BIconToggleOff,
|
||||
BIconToggleOn,
|
||||
BIconXCircleFill,
|
||||
} from 'bootstrap-icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
@ -540,9 +547,7 @@ export default defineComponent({
|
||||
ModalDialog,
|
||||
BIconArrowCounterclockwise,
|
||||
BIconBroadcast,
|
||||
BIconCheckCircleFill,
|
||||
BIconCpu,
|
||||
BIconExclamationCircleFill,
|
||||
BIconInfoCircle,
|
||||
BIconJournalText,
|
||||
BIconOutlet,
|
||||
@ -550,7 +555,6 @@ export default defineComponent({
|
||||
BIconSpeedometer,
|
||||
BIconToggleOff,
|
||||
BIconToggleOn,
|
||||
BIconXCircleFill,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -383,8 +383,8 @@ export default defineComponent({
|
||||
return {
|
||||
modal: {} as bootstrap.Modal,
|
||||
modalDelete: {} as bootstrap.Modal,
|
||||
newInverterData: {} as Inverter,
|
||||
selectedInverterData: {} as Inverter,
|
||||
newInverterData: { serial: '' } as Inverter,
|
||||
selectedInverterData: { serial: '' } as Inverter,
|
||||
inverters: [] as Inverter[],
|
||||
dataLoading: true,
|
||||
alert: {} as AlertResponse,
|
||||
@ -440,7 +440,7 @@ export default defineComponent({
|
||||
},
|
||||
onSubmit() {
|
||||
this.callInverterApiEndpoint('add', JSON.stringify(this.newInverterData));
|
||||
this.newInverterData = {} as Inverter;
|
||||
this.newInverterData = { serial: '' } as Inverter;
|
||||
},
|
||||
onDelete() {
|
||||
this.callInverterApiEndpoint('del', JSON.stringify({ id: this.selectedInverterData.id }));
|
||||
|
||||
1709
webapp/yarn.lock
1709
webapp/yarn.lock
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue
Block a user