Skip to content

Commit 66f01aa

Browse files
committed
🐛 multiple fixes
- return to default connection parameters from lib60870 (10s,15s,10s) - increase default (connection) command timeout from 100ms to 10000ms to fit to connection parameters - increase default (server) select timeout from 100ms to 10000ms to fit to connection parameters - fix server responds only with last station to general interrogation - fix all periodic tasks (periodic monitoring reports, selection timeout) - Connection interrogation command to global common address blocks until all stations send ACT_TERM, not just first station
1 parent 0f89c75 commit 66f01aa

11 files changed

Lines changed: 159 additions & 118 deletions

File tree

c104/__init__.pyi

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class Client:
274274
"""
275275
This class represents a local client and provides access to meta information and connected remote servers
276276
"""
277-
def __init__(self, tick_rate_ms: int = 100, command_timeout_ms: int = 100, transport_security: TransportSecurity | None = None) -> None:
277+
def __init__(self, tick_rate_ms: int = 100, command_timeout_ms: int = 10000, transport_security: TransportSecurity | None = None) -> None:
278278
"""
279279
create a new 104er client
280280
@@ -289,7 +289,7 @@ class Client:
289289
290290
Example
291291
-------
292-
>>> my_client = c104.Client(tick_rate_ms=100, command_timeout_ms=100)
292+
>>> my_client = c104.Client(tick_rate_ms=100, command_timeout_ms=10000)
293293
"""
294294
def add_connection(self, ip: str, port: int = 2404, init: Init | None = Init.ALL) -> Connection | None:
295295
"""
@@ -2478,7 +2478,7 @@ class Server:
24782478
"""
24792479
This class represents a local server and provides access to meta information and containing stations
24802480
"""
2481-
def __init__(self, ip: str = "0.0.0.0", port: int = 2404, tick_rate_ms: int = 100, select_timeout_ms = 100, max_connections: int = 0, transport_security: TransportSecurity | None = None) -> None:
2481+
def __init__(self, ip: str = "0.0.0.0", port: int = 2404, tick_rate_ms: int = 100, select_timeout_ms = 10000, max_connections: int = 0, transport_security: TransportSecurity | None = None) -> None:
24822482
"""
24832483
create a new 104er server
24842484
@@ -2499,7 +2499,7 @@ class Server:
24992499
25002500
Example
25012501
-------
2502-
>>> my_server = c104.Server(ip="0.0.0.0", port=2404, tick_rate_ms=100, select_timeout_ms=100, max_connections=0)
2502+
>>> my_server = c104.Server(ip="0.0.0.0", port=2404, tick_rate_ms=100, select_timeout_ms=10000, max_connections=0)
25032503
"""
25042504
def add_station(self, common_address: int) -> Station | None:
25052505
"""

docs/source/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ Features
1212
Fixes
1313
^^^^^^
1414

15+
- return to default connection parameters from lib60870 (10s,15s,10s)
16+
- increase default (connection) command timeout from 100ms to 10000ms to fit to connection parameters
17+
- increase default (server) select timeout from 100ms to 10000ms to fit to connection parameters
18+
- fix server uses correct cause of transmission for interrogation response #57
19+
- fix server responds only with last station to general interrogation
20+
- fix all periodic tasks (periodic monitoring reports, selection timeout)
21+
- Connection interrogation command to global common address blocks until all stations send ACT_TERM, not just first station
1522
- fix docstring for c104.ProtocolParameters timings: unit is seconds not milliseconds
1623
- support try-except blocks in callbacks instead of catching all errors and removing the callback
1724
- prevent invalid pointer access from lib60870 callbacks

src/Client.cpp

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -361,38 +361,33 @@ void Client::scheduleDataPointTimer() {
361361
}
362362

363363
void Client::schedulePeriodicTask(const std::function<void()> &task,
364-
int interval) {
365-
{
366-
if (interval < 50) {
367-
throw std::out_of_range(
368-
"The interval for periodic tasks must be 1000ms at minimum.");
369-
}
370-
auto periodic = std::make_shared<std::function<void()>>(
371-
[]() {}); // Empty function initially
372-
std::weak_ptr<std::function<void()>> weakPeriodic =
373-
periodic; // Weak reference
374-
std::weak_ptr<Client> weakSelf =
375-
shared_from_this(); // Weak reference to `this`
376-
377-
*periodic = [weakSelf, task, interval, weakPeriodic]() {
378-
auto self = weakSelf.lock();
379-
if (!self)
380-
return; // Prevent running if `this` was destroyed
381-
382-
// Schedule next execution
383-
if (auto periodicLock = weakPeriodic.lock()) { // Try to lock weak_ptr
384-
self->scheduleTask(*periodicLock, interval);
385-
}
386-
task();
387-
};
388-
// Schedule first execution
389-
scheduleTask(*periodic, interval);
364+
std::int_fast32_t interval) {
365+
if (interval < 50) {
366+
throw std::out_of_range(
367+
"The interval for periodic tasks must be 50ms at minimum.");
390368
}
391369

370+
std::weak_ptr<Client> weakSelf =
371+
shared_from_this(); // Weak reference to `this`
372+
373+
auto periodicCallback = [weakSelf, task, interval]() {
374+
auto self = weakSelf.lock();
375+
if (!self)
376+
return; // Prevent running if `this` was destroyed
377+
378+
// Schedule next execution
379+
self->schedulePeriodicTask(task, interval); // Reschedule itself
380+
381+
task();
382+
};
383+
// Schedule first execution
384+
scheduleTask(periodicCallback, interval);
385+
392386
runThread_wait.notify_one();
393387
}
394388

395-
void Client::scheduleTask(const std::function<void()> &task, int delay) {
389+
void Client::scheduleTask(const std::function<void()> &task,
390+
std::int_fast32_t delay) {
396391
{
397392
std::lock_guard<std::mutex> lock(runThread_mutex);
398393
if (delay < 0) {

src/Client.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ class Client : public std::enable_shared_from_this<Client> {
5757
* @param tick_rate_ms Tick rate in milliseconds for the Client execution
5858
* loop. Defaults to 100.
5959
* @param timeout_ms Timeout in milliseconds for Client operations. Defaults
60-
* to 100.
60+
* to 10000.
6161
* @param transport_security Shared pointer to a TransportSecurity object for
6262
* remote communication. Defaults to nullptr.
6363
* @return A shared pointer to the newly created Client instance.
6464
*/
6565
[[nodiscard]] static std::shared_ptr<Client> create(
6666
std::uint_fast16_t tick_rate_ms = 100,
67-
std::uint_fast16_t timeout_ms = 100,
67+
std::uint_fast16_t timeout_ms = 10000,
6868
std::shared_ptr<Remote::TransportSecurity> transport_security = nullptr) {
6969
// Not using std::make_shared because the constructor is private.
7070
return std::shared_ptr<Client>(
@@ -278,10 +278,11 @@ class Client : public std::enable_shared_from_this<Client> {
278278
* @param task A callable object representing the task to be executed
279279
* periodically.
280280
* @param interval The interval in milliseconds at which the task should be
281-
* executed. Must be at least 1000ms. Throws std::out_of_range if the interval
281+
* executed. Must be at least 50ms. Throws std::out_of_range if the interval
282282
* is less than 50ms.
283283
*/
284-
void schedulePeriodicTask(const std::function<void()> &task, int interval);
284+
void schedulePeriodicTask(const std::function<void()> &task,
285+
std::int_fast32_t interval);
285286

286287
/**
287288
* @brief Schedules a task to be executed after a specified delay (or
@@ -294,7 +295,8 @@ class Client : public std::enable_shared_from_this<Client> {
294295
* @param delay The delay in milliseconds before the task is executed. A
295296
* negative delay executes the task immediately.
296297
*/
297-
void scheduleTask(const std::function<void()> &task, int delay = 0);
298+
void scheduleTask(const std::function<void()> &task,
299+
std::int_fast32_t delay = 0);
298300

299301
private:
300302
/**
@@ -321,10 +323,10 @@ class Client : public std::enable_shared_from_this<Client> {
321323
void scheduleDataPointTimer();
322324

323325
/// @brief minimum interval between two periodic tasks
324-
const std::uint_fast16_t tickRate_ms{1000};
326+
const std::uint_fast16_t tickRate_ms{100};
325327

326328
/// @brief timeout in milliseconds before an inactive connection gets closed
327-
const std::uint_fast16_t commandTimeout_ms{100};
329+
const std::uint_fast16_t commandTimeout_ms{10000};
328330

329331
/// @brief tls handler
330332
const std::shared_ptr<Remote::TransportSecurity> security{nullptr};

src/Server.cpp

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ Server::Server(const std::string &bind_ip, const std::uint_fast16_t tcp_port,
100100

101101
appLayerParameters = CS104_Slave_getAppLayerParameters(slave);
102102

103-
// reduce lib60870-C connection timeout down to 2s
104-
auto param = CS104_Slave_getConnectionParameters(slave);
105-
param->t0 = 2; // socket connect timeout
106-
param->t1 = 2; // acknowledgement timeout
107-
param->t2 = 1; // acknowledgement interval
103+
// use lib60870-C defaults for connection timeouts
104+
// auto param = CS104_Slave_getConnectionParameters(slave);
105+
// param->t0 = 10; // socket connect timeout
106+
// param->t1 = 15; // acknowledgement timeout
107+
// param->t2 = 10; // acknowledgement interval
108108

109109
// Function pointers for custom handler functions
110110
void *key = static_cast<void *>(this);
@@ -243,38 +243,33 @@ void Server::scheduleDataPointTimer() {
243243
}
244244

245245
void Server::schedulePeriodicTask(const std::function<void()> &task,
246-
int interval) {
247-
{
248-
if (interval < 50) {
249-
throw std::out_of_range(
250-
"The interval for periodic tasks must be 1000ms at minimum.");
251-
}
252-
auto periodic = std::make_shared<std::function<void()>>(
253-
[]() {}); // Empty function initially
254-
std::weak_ptr<std::function<void()>> weakPeriodic =
255-
periodic; // Weak reference
256-
std::weak_ptr<Server> weakSelf =
257-
shared_from_this(); // Weak reference to `this`
246+
std::int_fast32_t interval) {
247+
if (interval < 50) {
248+
throw std::out_of_range(
249+
"The interval for periodic tasks must be 50ms at minimum.");
250+
}
258251

259-
*periodic = [weakSelf, task, interval, weakPeriodic]() {
260-
auto self = weakSelf.lock();
261-
if (!self)
262-
return; // Prevent running if `this` was destroyed
252+
std::weak_ptr<Server> weakSelf =
253+
shared_from_this(); // Weak reference to `this`
263254

264-
// Schedule next execution
265-
if (auto periodicLock = weakPeriodic.lock()) { // Try to lock weak_ptr
266-
self->scheduleTask(*periodicLock, interval);
267-
}
268-
task();
269-
};
270-
// Schedule first execution
271-
scheduleTask(*periodic, interval);
272-
}
255+
auto periodicCallback = [weakSelf, task, interval]() {
256+
auto self = weakSelf.lock();
257+
if (!self)
258+
return; // Prevent running if `this` was destroyed
259+
260+
// Schedule next execution
261+
self->schedulePeriodicTask(task, interval); // Reschedule itself
262+
263+
task();
264+
};
265+
// Schedule first execution
266+
scheduleTask(periodicCallback, interval);
273267

274268
runThread_wait.notify_one();
275269
}
276270

277-
void Server::scheduleTask(const std::function<void()> &task, int delay) {
271+
void Server::scheduleTask(const std::function<void()> &task,
272+
std::int_fast32_t delay) {
278273
{
279274
std::lock_guard<std::mutex> lock(runThread_mutex);
280275
if (delay < 0) {
@@ -741,44 +736,48 @@ void Server::onUnexpectedMessage(
741736
std::shared_ptr<Remote::Message::IncomingMessage> message,
742737
UnexpectedMessageCause cause) {
743738
CS101_ASDU asdu = message->getAsdu();
739+
740+
// manipulate and send copy instead of original ASDU
741+
CS101_ASDU cp = CS101_ASDU_clone(asdu, nullptr);
744742
switch (cause) {
745743
case INVALID_TYPE_ID:
746744
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Invalid type id");
747-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_TYPE_ID);
748-
IMasterConnection_sendASDU(connection, asdu);
745+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_TYPE_ID);
746+
IMasterConnection_sendASDU(connection, cp);
749747
break;
750748
case MISMATCHED_TYPE_ID:
751749
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Mismatching type id");
752-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_TYPE_ID);
753-
IMasterConnection_sendASDU(connection, asdu);
750+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_TYPE_ID);
751+
IMasterConnection_sendASDU(connection, cp);
754752
break;
755753
case UNKNOWN_TYPE_ID:
756754
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Unknown type id");
757-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_TYPE_ID);
758-
IMasterConnection_sendASDU(connection, asdu);
755+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_TYPE_ID);
756+
IMasterConnection_sendASDU(connection, cp);
759757
break;
760758
case INVALID_COT:
761759
case UNKNOWN_COT:
762760
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Invalid COT");
763-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
764-
IMasterConnection_sendASDU(connection, asdu);
761+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_COT);
762+
IMasterConnection_sendASDU(connection, cp);
765763
break;
766764
case UNKNOWN_CA:
767765
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Unknown CA");
768-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
769-
IMasterConnection_sendASDU(connection, asdu);
766+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_CA);
767+
IMasterConnection_sendASDU(connection, cp);
770768
break;
771769
case UNKNOWN_IOA:
772770
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Unknown IOA");
773-
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
774-
IMasterConnection_sendASDU(connection, asdu);
771+
CS101_ASDU_setCOT(cp, CS101_COT_UNKNOWN_IOA);
772+
IMasterConnection_sendASDU(connection, cp);
775773
break;
776774
case UNIMPLEMENTED_GROUP:
777775
DEBUG_PRINT(Debug::Server, "on_unexpected_message] Unimplemented group");
778776
break;
779777
default: {
780778
}
781779
}
780+
CS101_ASDU_destroy(cp);
782781

783782
if (py_onUnexpectedMessage.is_set()) {
784783
std::weak_ptr<Server> weakSelf =
@@ -1102,40 +1101,46 @@ void Server::sendActivationConfirmation(IMasterConnection connection,
11021101
if (!isExistingConnection(connection))
11031102
return;
11041103

1105-
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
1106-
CS101_ASDU_setNegative(asdu, negative);
1104+
// manipulate copy instead of original asdu
1105+
CS101_ASDU cp = CS101_ASDU_clone(asdu, nullptr);
1106+
CS101_ASDU_setCOT(cp, CS101_COT_ACTIVATION_CON);
1107+
CS101_ASDU_setNegative(cp, negative);
11071108

11081109
if (isGlobalCommonAddress(CS101_ASDU_getCA(asdu))) {
11091110
DEBUG_PRINT(Debug::Server, "send_activation_confirmation] to all MTUs");
11101111
std::lock_guard<Module::GilAwareMutex> const st_lock(station_mutex);
11111112
for (auto &s : stations) {
1112-
CS101_ASDU_setCA(asdu, s->getCommonAddress());
1113-
IMasterConnection_sendASDU(connection, asdu);
1113+
CS101_ASDU_setCA(cp, s->getCommonAddress());
1114+
IMasterConnection_sendASDU(connection, cp);
11141115
}
11151116
} else {
11161117
DEBUG_PRINT(Debug::Server,
11171118
"send_activation_confirmation] to requesting MTU");
1118-
IMasterConnection_sendASDU(connection, asdu);
1119+
IMasterConnection_sendASDU(connection, cp);
11191120
}
1121+
CS101_ASDU_destroy(cp);
11201122
}
11211123

11221124
void Server::sendActivationTermination(IMasterConnection connection,
11231125
CS101_ASDU asdu) {
11241126
if (!isExistingConnection(connection))
11251127
return;
11261128

1127-
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_TERMINATION);
1129+
// manipulate copy instead of original asdu
1130+
CS101_ASDU cp = CS101_ASDU_clone(asdu, nullptr);
1131+
CS101_ASDU_setCOT(cp, CS101_COT_ACTIVATION_TERMINATION);
11281132

11291133
if (isGlobalCommonAddress(CS101_ASDU_getCA(asdu))) {
11301134

11311135
std::lock_guard<Module::GilAwareMutex> const st_lock(station_mutex);
11321136
for (auto &s : stations) {
1133-
CS101_ASDU_setCA(asdu, s->getCommonAddress());
1134-
IMasterConnection_sendASDU(connection, asdu);
1137+
CS101_ASDU_setCA(cp, s->getCommonAddress());
1138+
IMasterConnection_sendASDU(connection, cp);
11351139
}
11361140
} else {
1137-
IMasterConnection_sendASDU(connection, asdu);
1141+
IMasterConnection_sendASDU(connection, cp);
11381142
}
1143+
CS101_ASDU_destroy(cp);
11391144
}
11401145

11411146
void Server::sendEndOfInitialization(const std::uint_fast16_t commonAddress,
@@ -1332,7 +1337,7 @@ bool Server::interrogationHandler(void *parameter, IMasterConnection connection,
13321337
}
13331338
} catch (const std::exception &e) {
13341339
DEBUG_PRINT(Debug::Server,
1335-
"Invalid point message for counter interrogation: " +
1340+
"Invalid point message for interrogation: " +
13361341
std::string(e.what()));
13371342
}
13381343
}

0 commit comments

Comments
 (0)