Skip to content

Commit f6ff1a8

Browse files
committed
✨ add feature to remove points and stations #47
1 parent 6ba8a76 commit f6ff1a8

13 files changed

+282
-25
lines changed

c104/__init__.pyi

+55-1
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,24 @@ class Connection:
891891
>>>
892892
>>> my_connection.on_unexpected_message(callable=con_on_unexpected_message)
893893
"""
894+
def remove_station(self, common_address: int) -> bool:
895+
"""
896+
removes an existing station from this connection
897+
898+
Parameters
899+
----------
900+
common_address: int
901+
station common address (value between 1 and 65534)
902+
903+
Returns
904+
-------
905+
bool
906+
True if the station was successfully removed, otherwise False.
907+
908+
Example
909+
-------
910+
>>> my_connection.remove_station(common_address=12)
911+
"""
894912
def test(self, common_address: int, with_time: bool = True, wait_for_response: bool = True) -> bool:
895913
"""
896914
send a test command to the remote terminal unit (server)
@@ -2714,6 +2732,24 @@ class Server:
27142732
>>>
27152733
>>> my_server.on_unexpected_message(callable=sv_on_unexpected_message)
27162734
"""
2735+
def remove_station(self, common_address: int) -> bool:
2736+
"""
2737+
removes an existing station from this server
2738+
2739+
Parameters
2740+
----------
2741+
common_address: int
2742+
station common address (value between 1 and 65534)
2743+
2744+
Returns
2745+
-------
2746+
bool
2747+
True if the station was successfully removed, otherwise False.
2748+
2749+
Example
2750+
-------
2751+
>>> station_3.remove_station(common_address=12)
2752+
"""
27172753
def start(self) -> None:
27182754
"""
27192755
open local server socket for incoming connections
@@ -3085,6 +3121,24 @@ class Station:
30853121
-------
30863122
>>> point_11 = my_station.get_point(io_address=11)
30873123
"""
3124+
def remove_point(self, io_address: int) -> bool:
3125+
"""
3126+
remove an existing point from this station
3127+
3128+
Parameters
3129+
----------
3130+
io_address: int
3131+
point information object address (value between 0 and 16777215)
3132+
3133+
Returns
3134+
-------
3135+
bool
3136+
information, if point was removed
3137+
3138+
Example
3139+
-------
3140+
>>> sv_station_1.remove_point(io_address=34566)
3141+
"""
30883142
def signal_initialized(self, cause: Coi) -> None:
30893143
"""
30903144
signal end of initialization for this station to connected clients
@@ -3986,4 +4040,4 @@ def set_debug_mode(mode: Debug) -> None:
39864040
-------
39874041
>>> c104.set_debug_mode(mode=c104.Debug.Client|c104.Debug.Connection)
39884042
"""
3989-
__version__: str = '2.1.1'
4043+
__version__: str = '2.2.0'

docs/source/changelog.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Change log
22
==========
33

4+
v2.2.0
5+
-------
6+
7+
Features
8+
^^^^^^^^
9+
10+
- Add **Server.remove_station(common_address: int)** and **Connection.remove_station(common_address: int)**- methods to enable the removal of stations.
11+
- Add **Station.remove_point(io_address: int)** method to allow the removal of points and facilitate reassigning the ``io_address`` to another point type.
12+
413
v2.1.1
514
-------
615

docs/source/conf.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
copyright = "2020-2025, Fraunhofer Institute for Applied Information Technology FIT"
1414
author = "Martin Unkel <[email protected]>"
1515

16-
release = "2.1"
17-
version = "2.1.1"
16+
release = "2.2"
17+
version = "2.2.0"
1818

1919

2020
# -- General configuration ---------------------------------------------------

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "c104"
3-
version = "2.1.1"
3+
version = "2.2.0"
44
description = "A Python module to simulate SCADA and RTU communication over protocol 60870-5-104 to research ICT behavior in power grids."
55
readme = {file = "README.md", content-type = "text/markdown"}
66
license = {file = "LICENSE"}

src/Server.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,30 @@ Server::addStation(std::uint_fast16_t commonAddress) {
384384
return station;
385385
}
386386

387+
bool Server::removeStation(std::uint_fast16_t commonAddress) {
388+
std::lock_guard<Module::GilAwareMutex> const lock(station_mutex);
389+
390+
DEBUG_PRINT(Debug::Server,
391+
"remove_station] CA " + std::to_string(commonAddress));
392+
393+
size_t originalSize = stations.size();
394+
395+
stations.erase(
396+
std::remove_if(
397+
stations.begin(), stations.end(),
398+
[commonAddress](const std::shared_ptr<Object::Station> &station) {
399+
// Check if the current DataPoint matches the provided address
400+
if (station->getCommonAddress() == commonAddress) {
401+
station->detach();
402+
return true;
403+
}
404+
return false;
405+
}),
406+
stations.end());
407+
408+
return (stations.size() < originalSize); // Success if the size decreased
409+
}
410+
387411
void Server::cleanupSelections() {
388412
auto now = std::chrono::steady_clock::now();
389413
std::lock_guard<Module::GilAwareMutex> const lock(selection_mutex);

src/Server.h

+23-7
Original file line numberDiff line numberDiff line change
@@ -216,29 +216,45 @@ class Server : public std::enable_shared_from_this<Server> {
216216

217217
/**
218218
* @brief Get a list of all Stations
219-
* @return vector with object stationer
219+
* @return vector with station objects
220220
*/
221221
Object::StationVector getStations() const;
222222

223223
/**
224-
* @brief Get a Station that exists at this NetworkStation and is identified
225-
* via information object address
226-
* @return Stationer to Station or nullptr
224+
* @brief Retrieves a Station that exists on this Server, identified by the
225+
* given information object address.
226+
* @param ca The common address (CA) that uniquely identifies the Station.
227+
* @return A shared pointer to the Station if it exists, or nullptr if no
228+
* matching Station is found.
227229
*/
228230
std::shared_ptr<Object::Station>
229231
getStation(std::uint_fast16_t commonAddress) const;
230232

231233
/**
232-
* @brief Test if Stations exists at this NetworkStation
233-
* @return information on availability of child Station objects
234+
* @brief Checks whether a Station with the given common address exists on
235+
* this Server.
236+
* @param commonAddress The common address (CA) used to identify the Station.
237+
* @return True if a Station with the specified common address exists,
238+
* otherwise false.
234239
*/
235240
bool hasStation(std::uint_fast16_t commonAddress) const;
236241

237242
/**
238-
* @brief Add a Station to this Station
243+
* @brief Adds a new Station to this Server.
244+
* @param commonAddress The common address (CA) that uniquely identifies the
245+
* new Station.
246+
* @return A shared pointer to the newly added Station.
239247
*/
240248
std::shared_ptr<Object::Station> addStation(std::uint_fast16_t commonAddress);
241249

250+
/**
251+
* @brief Removes an existing Station from this Server.
252+
*
253+
* @param commonAddress The common address (CA) of the Station to be removed.
254+
* @return True if the Station was successfully removed, otherwise false.
255+
*/
256+
bool removeStation(std::uint_fast16_t commonAddress);
257+
242258
/**
243259
* @brief Get a reference to the protocol parameters to be able to read and
244260
* update these

src/object/DataPoint.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1236,3 +1236,5 @@ bool DataPoint::transmit(const CS101_CauseOfTransmission cause) {
12361236
}
12371237
return connection->transmit(shared_from_this(), cause);
12381238
}
1239+
1240+
void DataPoint::detach() { station.reset(); }

src/object/DataPoint.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class DataPoint : public std::enable_shared_from_this<DataPoint> {
109109
const IEC60870_5_TypeID type;
110110

111111
/// @brief parent Station object (not owning pointer)
112-
const std::weak_ptr<Station> station;
112+
std::weak_ptr<Station> station;
113113

114114
/// @brief IEC60870-5 remote address of a related measurement DataPoint
115115
std::atomic_uint_fast32_t relatedInformationObjectAddress{0};
@@ -393,6 +393,12 @@ class DataPoint : public std::enable_shared_from_this<DataPoint> {
393393
*/
394394
bool transmit(CS101_CauseOfTransmission cause = CS101_COT_UNKNOWN_COT);
395395

396+
/**
397+
* @brief Remove reference to station, do not call this method, this is called
398+
* by Station::removePoint
399+
*/
400+
void detach();
401+
396402
/**
397403
* @brief Converts the current DataPoint object to a string representation,
398404
* including its various properties.

src/object/Station.cpp

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2020-2024 Fraunhofer Institute for Applied Information Technology
2+
* Copyright 2020-2025 Fraunhofer Institute for Applied Information Technology
33
* FIT
44
*
55
* This file is part of iec104-python.
@@ -129,13 +129,44 @@ std::shared_ptr<DataPoint> Station::addPoint(
129129
return point;
130130
}
131131

132+
bool Station::removePoint(const std::uint_fast32_t informationObjectAddress) {
133+
std::scoped_lock<Module::GilAwareMutex> const lock(points_mutex);
134+
135+
DEBUG_PRINT(Debug::Station,
136+
"remove_point] IOA " + std::to_string(informationObjectAddress));
137+
138+
size_t originalSize = points.size();
139+
140+
// Use std::remove_if to find and remove the entry
141+
points.erase(std::remove_if(points.begin(), points.end(),
142+
[informationObjectAddress](
143+
const std::shared_ptr<DataPoint> &point) {
144+
// Check if the current DataPoint matches the
145+
// provided address
146+
if (point->getInformationObjectAddress() ==
147+
informationObjectAddress) {
148+
point->detach();
149+
return true;
150+
}
151+
return false;
152+
}),
153+
points.end());
154+
155+
return (points.size() < originalSize); // Success if the size decreased
156+
}
157+
132158
bool Station::isLocal() { return !server.expired(); }
133159

134160
void Station::sendEndOfInitialization(const CS101_CauseOfInitialization cause) {
135161
if (auto sv = getServer()) {
136162
return sv->sendEndOfInitialization(commonAddress, cause);
137163
}
138164

139-
throw new std::runtime_error(
165+
throw std::runtime_error(
140166
"Cannot send end of initialization: not a server station");
141167
}
168+
169+
void Station::detach() {
170+
server.reset();
171+
connection.reset();
172+
}

src/object/Station.h

+17-2
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ class Station : public std::enable_shared_from_this<Station> {
8484
const std::uint_fast16_t commonAddress{0};
8585

8686
/// @brief server object reference (only local station)
87-
const std::weak_ptr<Server> server;
87+
std::weak_ptr<Server> server;
8888

8989
/// @brief remote connection object reference (only remote station)
90-
const std::weak_ptr<Remote::Connection> connection;
90+
std::weak_ptr<Remote::Connection> connection;
9191

9292
/// @brief child DataPoint objects (owned by this Station)
9393
DataPointVector points{};
@@ -159,6 +159,15 @@ class Station : public std::enable_shared_from_this<Station> {
159159
bool relatedInformationObjectAutoReturn = false,
160160
CommandTransmissionMode commandMode = DIRECT_COMMAND);
161161

162+
/**
163+
* @brief Removes an existing DataPoint from this Station.
164+
* @param informationObjectAddress The address of the information object to be
165+
* removed.
166+
* @return True if the DataPoint was successfully found and removed, otherwise
167+
* false.
168+
*/
169+
bool removePoint(std::uint_fast32_t informationObjectAddress);
170+
162171
/**
163172
* @brief test if this station belongs to a server instance and not a
164173
* connection (client)
@@ -174,6 +183,12 @@ class Station : public std::enable_shared_from_this<Station> {
174183
*/
175184
void sendEndOfInitialization(CS101_CauseOfInitialization cause);
176185

186+
/**
187+
* @brief Remove reference to station, do not call this method, this is called
188+
* by Station::removePoint
189+
*/
190+
void detach();
191+
177192
/**
178193
* @brief Generates a string representation of the Station object.
179194
*

0 commit comments

Comments
 (0)