Device: Add com keepalive

pull/76/merge
Kyle Schwarz 2025-08-06 17:17:36 -04:00
parent c5bce795c6
commit 6b60804174
6 changed files with 137 additions and 8 deletions

View File

@ -533,6 +533,7 @@ if(LIBICSNEO_BUILD_UNIT_TESTS)
test/unit/ringbuffertest.cpp
test/unit/apperrordecodertest.cpp
test/unit/windowsstrings.cpp
test/unit/periodictest.cpp
)
target_link_libraries(libicsneo-unit-tests gtest gtest_main)

View File

@ -421,7 +421,8 @@ bool Device::disableLogData() {
}
bool Device::goOnline() {
if(!enableNetworkCommunication(true))
static constexpr uint32_t onlineTimeoutMs = 5000;
if(!enableNetworkCommunication(true, onlineTimeoutMs))
return false;
auto startTime = std::chrono::system_clock::now();
@ -450,13 +451,19 @@ bool Device::goOnline() {
return false;
}
// (re)start the keeponline
keeponline = std::make_unique<Periodic>([this] { return enableNetworkCommunication(true, onlineTimeoutMs); }, std::chrono::milliseconds(onlineTimeoutMs / 4));
online = true;
forEachExtension([](const std::shared_ptr<DeviceExtension>& ext) { ext->onGoOnline(); return true; });
return true;
}
bool Device::goOffline() {
keeponline.reset();
forEachExtension([](const std::shared_ptr<DeviceExtension>& ext) { ext->onGoOffline(); return true; });
if(isDisconnected()) {
@ -3482,13 +3489,14 @@ bool Device::writeMACsecConfig(const MACsecMessage& message, uint16_t binaryInde
return writeBinaryFile(raw, binaryIndex);
}
bool Device::enableNetworkCommunication(bool enable) {
bool Device::enableNetworkCommunication(bool enable, uint32_t timeout) {
bool sendMsg = false;
if(!com->driver->enableCommunication(enable, sendMsg)) {
return false;
}
if(sendMsg) {
if(!com->sendCommand(Command::EnableNetworkCommunication, enable)) {
const uint8_t* i = (uint8_t*)&timeout;
if(!com->sendCommand(Command::EnableNetworkCommunication, {enable, 0, 0, 0, i[0], i[1], i[2], i[3]})) {
return false;
}
}

View File

@ -0,0 +1,53 @@
#ifndef __PERIODIC_H__
#define __PERIODIC_H__
#ifdef __cplusplus
#include <condition_variable>
#include <mutex>
#include <functional>
#include <chrono>
#include <thread>
namespace icsneo {
class Periodic {
public:
using Callback = std::function<bool(void)>;
Periodic(Callback&& callback, const std::chrono::milliseconds& period) :
thread(&Periodic::loop, this, std::move(callback), period)
{}
~Periodic() {
{
std::scoped_lock lk(mutex);
stop = true;
}
cv.notify_all();
thread.join();
}
private:
void loop(Callback&& callback, const std::chrono::milliseconds& period) {
while (true) {
{
std::unique_lock lk(mutex);
cv.wait_for(lk, period, [&]{ return stop; });
if(stop) {
break;
}
}
if (!callback()) {
break;
}
}
}
bool stop = false;
std::condition_variable cv;
std::mutex mutex;
std::thread thread;
};
} // icsneo
#endif // __cplusplus
#endif // __PERIODIC_H__

View File

@ -16,6 +16,7 @@
#include <chrono>
#include "icsneo/api/eventmanager.h"
#include "icsneo/api/lifetime.h"
#include "icsneo/api/periodic.h"
#include "icsneo/device/neodevice.h"
#include "icsneo/device/idevicesettings.h"
#include "icsneo/device/nullsettings.h"
@ -1037,7 +1038,10 @@ private:
*/
std::optional<uint64_t> getVSADiskSize();
bool enableNetworkCommunication(bool enable);
bool enableNetworkCommunication(bool enable, uint32_t timeout = 0);
// Keeponline (keepalive for online)
std::unique_ptr<Periodic> keeponline;
};
}

View File

@ -14,13 +14,14 @@ bool Servd::Enabled() {
return enabled ? enabled[0] == '1' : false;
}
std::vector<std::string> split(const std::string_view& str, char delim = ' ')
{
std::vector<std::string> split(const std::string_view& str, char delim = ' ') {
if(str.empty())
return {};
std::vector<std::string> ret;
size_t tail = 0;
size_t head = 0;
while (head < str.size()) {
if (str[head] == delim) {
while(head < str.size()) {
if(str[head] == delim) {
ret.emplace_back(&str[tail], head - tail);
tail = head + 1;
}

View File

@ -0,0 +1,62 @@
#include "icsneo/api/periodic.h"
#include "gtest/gtest.h"
#include <condition_variable>
using namespace icsneo;
// no wait, make sure stop works
TEST(PeriodicTest, StartStop)
{
const auto start = std::chrono::steady_clock::now();
Periodic p([] { return true; }, std::chrono::milliseconds(1000));
const auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
EXPECT_LT(delta.count(), 100); // hopefully enough
}
// time single cycle
TEST(PeriodicTest, OneCycle)
{
std::condition_variable cv;
std::mutex mutex;
uint8_t cycles = 0;
const auto start = std::chrono::steady_clock::now();
{
Periodic p([&] {
{
std::scoped_lock lk(mutex);
++cycles;
}
cv.notify_one();
return true;
}, std::chrono::seconds(1));
std::unique_lock<std::mutex> lk(mutex);
cv.wait_for(lk, std::chrono::seconds(2), [&]{ return cycles > 0; });
}
const auto delta = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start);
EXPECT_EQ(delta.count(), 1);
EXPECT_EQ(cycles, 1);
}
TEST(PeriodicTest, TenCycles)
{
std::condition_variable cv;
std::mutex mutex;
uint8_t cycles = 0;
const auto start = std::chrono::steady_clock::now();
{
Periodic p([&] {
{
std::scoped_lock lk(mutex);
++cycles;
}
cv.notify_one();
return true;
}, std::chrono::milliseconds(100));
std::unique_lock<std::mutex> lk(mutex);
cv.wait_for(lk, std::chrono::seconds(2), [&]{ return cycles >= 10; });
}
const auto delta = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start);
EXPECT_EQ(delta.count(), 1);
EXPECT_EQ(cycles, 10);
}