Skip to content

Feature-Request: Allow to set the c104.type of a point #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
georgwerner-lemonbeat opened this issue Oct 18, 2024 · 6 comments
Open
Labels
enhancement New feature or request question Further information is requested

Comments

@georgwerner-lemonbeat
Copy link

Hello,

we have a scenario where we might want to transmit a point in two different variations.

  1. As Type 1 -> M_SP_NA_1 (Single Info without timestamp)
  2. As Type 30 -> M_SP_TB_1 (Single Info with timestamp)

So basically the underlying data-type shall not change.
Depending on the scenario (Interrogation, Spontaneous) we would like to send the value either with or without timestamp.

Unfortunately there is no setter for the type of a point.

Is it possible to allow setting the type of an already changes point?

Thanks a lot in advance
Georg

@m-unkel
Copy link
Collaborator

m-unkel commented Oct 23, 2024

Hello Georg,

Thank you for your feedback.

Currently, it was a design choice to have different information object addresses (IOAs) for distinct types. This approach helps avoid unexpected payload formats and minimizes repeated type checking, ensuring that each data type has its own distinct identity in the communication flow.

One potential solution would be to implement another abstraction layer in your code that internally manages two separate points (one for each type) but exposes them as a single logical entity externally.

Alternatively, you could consider sending a timestamp with a known "unset" state (such as 0) when you want the client to ignore it. However, this approach would require you to have control over the client as well, to ensure the client-side logic disregards the zero timestamp and retains the previous valid one.

May I ask, what specific challenges are you facing with sending a timestamp too frequently? Is it that the correct timestamp value isn't available at the time of transmission, or is there another reason for preferring to omit it in certain scenarios?

Looking forward to your feedback.
Best
Martin

@georgwerner-lemonbeat
Copy link
Author

Hello Martin,

thanks a lot for your help and solution proposals.

Fortunately we now agreed in our integration-prototype not mix message-types for already defined datapoints, thus following your design decision.

Switching the datatype of an already created entity would also allow that you can witch from value-report to step-command.
So I thinks it's good to stick to fixed types.

Answering your question:
We don't have any challenges sending the timestamp too frequently. The request to change the message type without having a timestamp for interrogation responses came from our test-partner (the reason for this is unclear).
I guess somewhow our partner also came to the conclusion that mixing types is not a good idea.


We also have been working on another topic, setting the daylight-saving-time bit for messages.
I created a fork and implemented a prototype only for one message type.

This approach is not very generic and must be adapted for every message type.

Would great to have your feedback on this.

Shall I open another thread for this?

(Hint: We know that using the SU-BIT in iec104 according to the Din-Norm is not recommended).

Thanks in advance
Georg

@m-unkel m-unkel added enhancement New feature or request question Further information is requested labels Nov 7, 2024
@m-unkel
Copy link
Collaborator

m-unkel commented Nov 7, 2024

Hello Georg,
Thank you for your PR.

Regarding the timestamps, we can continue the discussion in this thread; next time, please feel free to open a parallel issue. I saw your pull request, but you closed it again. Generally, a contributor license agreement would be necessary for a pull request of this magnitude.

The timestamp in the protocol also comes with an invalid flag, which can be used for the previously mentioned marking of invalid timestamps instead of a defined "unset" state. There is the possibility of creating a wrapper class (i.e., c104.DateTime) that can be converted to native Python datetime.datetime objects and also provides access to the SU bit, IV bit, and SB bit. There are already functions for the bit flags in the lib60870-C, so encoding and decoding do not need to be programmed from scratch.

However, the question remains where the state of DST is stored. In your PR, the state was assigned to the server. But does it really make sense for state to vary per server/client instance of a running process? Otherwise, I would tend to consider a static class property as a global configuration that is valid for all servers and clients in the running Python process (c104.DateTime.summerTime = True).

Best
Martin

@m-unkel
Copy link
Collaborator

m-unkel commented Nov 7, 2024

In general, this approach could be used to flag all timestamps implicitly added by this module as substituted. For example, if I change the value of a point that has a type with an associated timestamp by using point.value = ... and not by using point.info = ....
Since this would be a breaking change, this behavior should also be configurable through an option. And by default, it should behave as it did before.

@m-unkel
Copy link
Collaborator

m-unkel commented Mar 5, 2025

The requested feature has been implemented. I am currently working on an example that demonstrates its use for internal clock synchronization via the clock sync command.

Since this implementation introduces breaking changes, I was unable to include it in the latest release—apologies for the slow progress. If you'd like, you can verify the new feature by installing it from the source branch.

pip install git+https://github.com/Fraunhofer-FIT-DIEN/iec104-python.git@features/datetime

This example showcases different options a server could use to take advantage of the datetime feature:

import c104
import time
from datetime import datetime, timezone, timedelta

def main():
    # server and station preparation
    server = c104.Server()

    station = server.add_station(common_address=47)
    station.auto_time_substituted = False

    point = station.add_point(io_address=11, type=c104.Type.M_ME_TD_1)

    # start
    server.start()

    while not server.has_active_connections:
        print("Waiting for connection")
        time.sleep(1)

    time.sleep(1)

    print("transmit point 0 (none)")
    dt0 = c104.DateTime(datetime.now(timezone.utc))
    dt0.daylight_saving_time = False
    dt0.invalid = True
    dt0.substituted = True
    point.info = c104.NormalizedInfo(
        c104.NormalizedFloat(0.34),
        c104.Quality(),
        dt0
    )
    point.transmit(c104.Cot.SPONTANEOUS)

    time.sleep(1)

    print("transmit point 1 (Offset)")
    dt1 = c104.DateTime(datetime.now(timezone(timedelta(seconds=3600))))
    dt1.daylight_saving_time = False
    print(dt1.value.isoformat())
    point.info = c104.NormalizedInfo(
        c104.NormalizedFloat(-0.34),
        c104.Quality(),
        dt1
    )
    point.transmit(c104.Cot.SPONTANEOUS)

    time.sleep(1)

    print("transmit point 2 (DST)")
    dt2 = c104.DateTime(datetime.now(timezone.utc))
    dt2.daylight_saving_time = True
    print(dt2.value.isoformat())
    point.info = c104.NormalizedInfo(
        c104.NormalizedFloat(0.35),
        c104.Quality(),
        dt2
    )
    point.transmit(c104.Cot.SPONTANEOUS)

    time.sleep(1)

    print("transmit point 3 (Station offset)")
    station.timezone_offset = 14400
    station.daylight_saving_time = True
    dt3 = c104.DateTime(datetime.now(timezone(timedelta(seconds=14400))))
    dt3.daylight_saving_time = False
    print(dt3.value.isoformat())
    point.info = c104.NormalizedInfo(
        c104.NormalizedFloat(-0.35),
        c104.Quality(),
        dt3
    )
    point.transmit(c104.Cot.SPONTANEOUS)

    time.sleep(1)

    print("transmit point 4 (Station offset+DST)")
    station.timezone_offset = 14400
    dt4 = c104.DateTime(datetime.now(timezone(timedelta(seconds=14400))))
    dt4.daylight_saving_time = True
    print(dt4.value.isoformat())
    point.info = c104.NormalizedInfo(
        c104.NormalizedFloat(0.36),
        c104.Quality(),
        dt4
    )
    point.transmit(c104.Cot.SPONTANEOUS)

    time.sleep(1)

    print("transmit point 5 (auto substituted)")
    point.value = c104.NormalizedFloat(0.0)
    point.transmit(c104.Cot.SPONTANEOUS)


    server.stop()


if __name__ == "__main__":
    print()
    print("START simple server")
    print()
    main()

@m-unkel
Copy link
Collaborator

m-unkel commented Mar 5, 2025

One additional thought: For version 3.0, we are considering using the same point for messages with and without a timestamp. As per the standard, COT periodic messages will use messages without a timestamp, while all other cases will include a timestamp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants