iicsys / pypmu

pyPMU - Python implementation of the IEEE C37.118 synchrophasor standard
BSD 3-Clause "New" or "Revised" License
60 stars 46 forks source link

tinyPDC: get_config() ; PdcError("Invalid Configuration message received") ; FPS, Data Rate monitoring in Real Time #24

Closed Pratyush-das closed 5 months ago

Pratyush-das commented 4 years ago

https://github.com/iicsys/pypmu/blob/66e6c495a91efb8a6018061ff7955a6654b1404d/examples/tinyPDC.py#L19

Hi all, I am trying to monitor the configured frame rate continuously since I will be changing it dynamically from the PMU end. I was tinkering with the code of tinyPDC and noticed that if I use config = pdc.get_config() # Get configuration from PMU before I use pdc.start() # Request to start sending measurements I get Config2 and this is what I was sending from the PMU. But after the request to start sending measurements I cannot fetch the configuration anymore. I get the PdcError PdcError("Invalid Configuration message received") Somehow, config = pdc.get_config() is no longer a "ConfigFrame2". Why is that? Before it was <synchrophasor.frame.ConfigFrame2 object at 0x000001509F83B5C0> After pdc.start() it becomes <synchrophasor.frame.DataFrame object at 0x00000276DBCDCC18> (I suppressed the error in the pdc.py to get it) Why can't I check the configuration after the PDC starts?

poledna commented 4 years ago

it isn't that you can't read check the CFG2 after pdc.start(). when you use the method pdc.get_config() what you do is to ask the PMU the CFG then you wait patiently to the data arrive and when it arrives the program parses the information and updates the class attribute pmu_cfg2. The only type of package that it expects is a config frame it doesn't expect anything other than that, to the extent that it will raise an error if it is a dataframe. The issue is that after the pdc.start() the PMU will send at each interval the data, no matter what, so if in the time you took to ask has for some timing reason passed more time than it should've, you might ask a CFG2 to the PMU but it will already have in its buffer a dataframe package so that first you'll receive a dataframe Then and only than a CFG2, and thats the issue. Other than the CFG2 will take a "timeslot" of the buffer, but you'll have to read the pmu.py code to check if it is correct. but changing the priority of the cfg2frame is a timing issue of its own. as the pmu only sends data at a sleep(delay) interval and you've added something to the buffer then it won't correctly send the buffer so be mindful of that.

the method pdc.get() is extremely powerful, it can parse almost anything in the IEEEC37.118. the solution to your issue is simple, don't expect a CFG2 frame, just expect something. but you have to ask the PMU the CFG2. the following code will ask the CFG2 frame. and not expect ONLY a dataframe it will expect, in the manner of pdc.get(), anything, when it receives the CFG2 frame pdc.get method will parse and update, but then you won't have clearly the CFG2 frame, as you'd have with config=pdc.get_config(), you'll have to explicitly take it out of the PDC handler using something as: config = pdc.pmu_cfg2 that will give you the cfg2.

this probably wasn't my best explanation so if something wasn't understood please ask


from synchrophasor.pdc import Pdc
from synchrophasor.frame import DataFrame
from synchrophasor.frame import CommandFrame #don't forget me!
"""
tinyPDC will connect to pmu_ip:pmu_port and send request
for header message, configuration and eventually
to start sending measurements.
"""

if __name__ == "__main__":

    pdc = Pdc(pdc_id=7, pmu_ip="192.168.1.104", pmu_port=1410)
    pdc.logger.setLevel("DEBUG")

    pdc.run()  # Connect to PMU

    header = pdc.get_header()  # Get header message from PMU
    config = pdc.get_config()  # Get configuration from PMU

    pdc.start()  # Request to start sending measurements
    iterator=0
    while True:
        if iterator%2==0:
            pdc.pmu_socket.sendto(CommandFrame(pdc.pdc_id, 'cfg2').convert2bytes(), pdc.pmu_address)#asking CFG2 frame
        data = pdc.get()  # Keep receiving data
        print('\t',type(data))
        iterator+=1
Pratyush-das commented 4 years ago

Hi Yuri, thank you for the insight. I used an if statement and now I can separate ConfigFrame2 and DataFrame. I could understand what was going wrong before. I am not an expert in Python and honestly, I don't know if my method is OK. Here is how I did it:

  try:
        while True:
            # config = pdc.get_config()
            data = pdc.get()  # Keep receiving data
            if type(data) == ConfigFrame2:
                #config = pdc.get_config()  # version="cfg2"
                cnfg_fps = data.get_data_rate()
                #config = pdc.get_config() #version="cfg2"
                # fps = config.get_data_rate()
                #hdr=pdc.get_header() #get the header

            if type(data) == DataFrame:
                data=data.get_measurements()
Pratyush-das commented 4 years ago

Hello Yuri, does my code make sense? It gets rid of the error, but I am not sure if it creates any timing problem. Is it asking for a config frame everytime, same as the data frame? so if I count the time between phasor data packets, will I get a lower frame rate, because the config frame is coming as fast as the data frames? Please help. Pratyush

poledna commented 4 years ago

you need this line of code to ask the PMU to send the Data from the code you've shown (I don't know what else there is in the code you've only sent a snippet) you're not asking it. what is the use of the try? what exception are you catching? to ask use: pmu_socket.sendto(CommandFrame(pdc.pdc_id, 'cfg2').convert2bytes(), pdc.pmu_address)#asking CFG2 frame tweaking your code a bit:

# try: # not sure why you're using a try it shouldn't be necessary
while True: 
    pmu_socket.sendto(CommandFrame(pdc.pdc_id, 'cfg2').convert2bytes(), pdc.pmu_address)
    data = pdc.get()  # Keep receiving data
    if type(data) == ConfigFrame2: 
        cnfg_fps = data.get_data_rate()
        fps = config.get_data_rate()
    if type(data) == DataFrame:
        data=data.get_measurements()

this code would ask EVERY SINGLE TIME THE CFG2 so it wouldn't be practical because normally you wouldnt change it sooo frequently. so i'd suggest asking it at a fixed interval. I suggest trying this code and posting the results on the matter of frame/rate. For example OpenPDC asks the CFG2 every minute! so i'd do the same. the code would be something like this:

from time import time
init_time=int(time())
while True: 
    # timing asking each 60s or 1 minute:
    if time()>=(init_time+60):
        init_time += 60 # this is not a pretty way to do it but i think is clear what is happening
       # asking the CFG2:
        pmu_socket.sendto(CommandFrame(pdc.pdc_id, 'cfg2').convert2bytes(), pdc.pmu_address)
   # getting the data
    data = pdc.get()  # Keep receiving data
    if type(data) == ConfigFrame2: # so you'll never enter this if
        cnfg_fps = data.get_data_rate()
        fps = config.get_data_rate()
    if type(data) == DataFrame:
        data=data.get_measurements()
    iterator++

but i'd suggest tweaking it a bit to fit your needs. i don't know your parameters. i didn't actually try this code i've made from what seems logic to me. i suggest trying this code and analyzing the results.

you'll only have issues with timing if you ask too much the CFG2 (let's say one CFG2 every sample). if it is obligatory you know the cfg2 every sample, then you might need to adjust the pmu.py file to add this. i'll try to keep this simple so i'll only talk about this possibility in this issue if it is necessary, but to that I'd suggest further study in the case and show us where your issue lies upon. and bring data and the changes you've tried. on further notice if even you're not sure about the timing it is not an issue, yet. i suggest trying it analyzing it and bringing data to us so that we can help improve, trying to help when you don't know what your issue is, is significantly harder! So summing up, try to enlighten us on which error your code was stopping at that required the try except, also try to send a program with minimum functionality so that your issue is reproducible.