Feature: New handling of command queue
Goal of this change is to prevent a overflow in the command queue by flooding it with MQTT commands and therefor also prevent the reading of the inverter data. To achieve this it is now possible to specify a insert type for each queue element.
This commit is contained in:
parent
0061d5e159
commit
8acae28c59
@ -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()) {
|
if (iv->getZeroValuesIfUnreachable() && !iv->isReachable()) {
|
||||||
iv->Statistics()->zeroRuntimeData();
|
iv->Statistics()->zeroRuntimeData();
|
||||||
@ -119,6 +119,7 @@ void HoymilesClass::loop()
|
|||||||
iv->sendGridOnProFileParaRequest();
|
iv->sendGridOnProFileParaRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_messageOutput->printf("Queue size - NRF: %ld CMT: %ld\r\n", _radioNrf->getQueueSize(), _radioCmt->getQueueSize());
|
||||||
_lastPoll = millis();
|
_lastPoll = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +230,7 @@ void HoymilesClass::removeInverterBySerial(const uint64_t serial)
|
|||||||
for (uint8_t i = 0; i < _inverters.size(); i++) {
|
for (uint8_t i = 0; i < _inverters.size(); i++) {
|
||||||
if (_inverters[i]->serial() == serial) {
|
if (_inverters[i]->serial() == serial) {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
_inverters[i]->getRadio()->removeCommands(_inverters[i].get());
|
||||||
_inverters.erase(_inverters.begin() + i);
|
_inverters.erase(_inverters.begin() + i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,6 +156,11 @@ bool HoymilesRadio::isInitialized() const
|
|||||||
return _isInitialized;
|
return _isInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio::removeCommands(InverterAbstract* inv)
|
||||||
|
{
|
||||||
|
_commandQueue.removeAllEntriesForInverter(inv);
|
||||||
|
}
|
||||||
|
|
||||||
bool HoymilesRadio::isIdle() const
|
bool HoymilesRadio::isIdle() const
|
||||||
{
|
{
|
||||||
return !_busyFlag;
|
return !_busyFlag;
|
||||||
@ -165,3 +170,8 @@ bool HoymilesRadio::isQueueEmpty() const
|
|||||||
{
|
{
|
||||||
return _commandQueue.size() == 0;
|
return _commandQueue.size() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned long HoymilesRadio::getQueueSize() const
|
||||||
|
{
|
||||||
|
return _commandQueue.size();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
#include "commands/CommandAbstract.h"
|
#include "commands/CommandAbstract.h"
|
||||||
|
#include "queue/CommandQueue.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <ThreadSafeQueue.h>
|
|
||||||
#include <TimeoutHelper.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 {
|
class HoymilesRadio {
|
||||||
public:
|
public:
|
||||||
@ -14,11 +20,47 @@ public:
|
|||||||
|
|
||||||
bool isIdle() const;
|
bool isIdle() const;
|
||||||
bool isQueueEmpty() const;
|
bool isQueueEmpty() const;
|
||||||
|
unsigned long getQueueSize() const;
|
||||||
bool isInitialized() const;
|
bool isInitialized() const;
|
||||||
|
|
||||||
|
void removeCommands(InverterAbstract* inv);
|
||||||
|
|
||||||
void enqueCommand(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);
|
_commandQueue.push(cmd);
|
||||||
|
|
||||||
|
DEBUG_PRINT("Queue size after: %ld\r\n", _commandQueue.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -38,7 +80,7 @@ protected:
|
|||||||
void handleReceivedPackage();
|
void handleReceivedPackage();
|
||||||
|
|
||||||
serial_u _dtuSerial;
|
serial_u _dtuSerial;
|
||||||
ThreadSafeQueue<std::shared_ptr<CommandAbstract>> _commandQueue;
|
CommandQueue _commandQueue;
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
bool _busyFlag = false;
|
bool _busyFlag = false;
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,15 @@ ActivePowerControlCommand::ActivePowerControlCommand(InverterAbstract* inv, cons
|
|||||||
|
|
||||||
String ActivePowerControlCommand::getCommandName() const
|
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)
|
void ActivePowerControlCommand::setActivePowerLimit(const float limit, const PowerLimitControlType type)
|
||||||
@ -89,7 +97,7 @@ float ActivePowerControlCommand::getLimit() const
|
|||||||
return l / 10;
|
return l / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerLimitControlType ActivePowerControlCommand::getType()
|
PowerLimitControlType ActivePowerControlCommand::getType() const
|
||||||
{
|
{
|
||||||
return (PowerLimitControlType)((static_cast<uint16_t>(_payload[14]) << 8) | _payload[15]);
|
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);
|
explicit ActivePowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0);
|
||||||
|
|
||||||
virtual String getCommandName() const;
|
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 bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id);
|
||||||
virtual void gotTimeout();
|
virtual void gotTimeout();
|
||||||
|
|
||||||
void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent);
|
void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent);
|
||||||
float getLimit() const;
|
float getLimit() const;
|
||||||
PowerLimitControlType getType();
|
PowerLimitControlType getType() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -138,3 +138,9 @@ uint8_t CommandAbstract::getMaxRetransmitCount() const
|
|||||||
{
|
{
|
||||||
return MAX_RETRANSMIT_COUNT;
|
return MAX_RETRANSMIT_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CommandAbstract::areSameParameter(CommandAbstract* other)
|
||||||
|
{
|
||||||
|
return this->getCommandName() == other->getCommandName()
|
||||||
|
&& this->_targetAddress == other->getTargetAddress();
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,18 @@
|
|||||||
|
|
||||||
class InverterAbstract;
|
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 {
|
class CommandAbstract {
|
||||||
public:
|
public:
|
||||||
explicit CommandAbstract(InverterAbstract* inv, const uint64_t router_address = 0);
|
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
|
// Sets the amount how often a missing fragment is re-requested if it was not available
|
||||||
virtual uint8_t getMaxRetransmitCount() const;
|
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:
|
protected:
|
||||||
uint8_t _payload[RF_LEN];
|
uint8_t _payload[RF_LEN];
|
||||||
uint8_t _payload_size;
|
uint8_t _payload_size;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ public:
|
|||||||
explicit PowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0);
|
explicit PowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0);
|
||||||
|
|
||||||
virtual String getCommandName() const;
|
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 bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id);
|
||||||
virtual void gotTimeout();
|
virtual void gotTimeout();
|
||||||
|
|||||||
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 <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <deque>
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class ThreadSafeQueue {
|
class ThreadSafeQueue {
|
||||||
@ -33,14 +33,14 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
T tmp = _queue.front();
|
T tmp = _queue.front();
|
||||||
_queue.pop();
|
_queue.pop_front();
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(const T& item)
|
void push(const T& item)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
_queue.push(item);
|
_queue.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
T front()
|
T front()
|
||||||
@ -49,6 +49,10 @@ public:
|
|||||||
return _queue.front();
|
return _queue.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::deque<T> _queue;
|
||||||
|
mutable std::mutex _mutex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Moved out of public interface to prevent races between this
|
// Moved out of public interface to prevent races between this
|
||||||
// and pop().
|
// and pop().
|
||||||
@ -56,7 +60,4 @@ private:
|
|||||||
{
|
{
|
||||||
return _queue.empty();
|
return _queue.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::queue<T> _queue;
|
|
||||||
mutable std::mutex _mutex;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user