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

How to calculate transmission time from pyPMU to pyPDC? Results here #28

Closed Pratyush-das closed 5 months ago

Pratyush-das commented 3 years ago

Hi all, I have a pyPMU and a pyPDC instance running on the same windows virtual machine. Essentially they are synchronized to the same clock. The pyPMU instance receives phasor data from another source and sends the data as synchrophasor packets to the pyPDC. The pyPDC parses the synchrophasor data, extracts the time stamp(data['time']) and then compares with the current time (time.time()).

The results for this comparison is as follows for data rates 10,25,50,100 fps. RR = reporting rate or Data Rate in frames per sec

RR Transmission  time (avg)
fps ms
10 99.59
25 39.23
50 19.5
100 9.43

As seen in the table the time difference between the synchrophasor time stamp from the PMU, and the time when it is being parsed in the PDC is same as 1\RR. I do not understand why? Any insight? I was under the impression that some random or constant delay will take place in the communication.

The codes are as follows pyPMU

## Import necessary libraries
import math
import numpy as np
import socket
import struct
from synchrophasor.frame import ConfigFrame2
from synchrophasor.pmu import Pmu
import time
# from synchrophasor.frame import ConfigFrame2
# from time import time
from synchrophasor.frame import CommandFrame
from datetime import datetime

##
"""
randomPMU will listen on ip:port for incoming connections.
After request to start sending measurements - random
values and values from OPAL RT model. It will also receive RR from OPAL RT 
and configure frame rate accordingly.
"""

if __name__ == "__main__":

    ## Configure socket for Phasor data ##

    UDP_IP = "10.10.114.22"
    UDP_PORT = 8201 #UDP phasor values 32 bytes (V,phi,P)
    sock_ph = socket.socket(socket.AF_INET,  # Internet
                         socket.SOCK_DGRAM)  # UDP
    sock_ph.bind((UDP_IP, UDP_PORT))
    print("socket bound, waiting for data...")

    ## Configure socket for RR Value ##

    UDP_PORT2 = 600  # OPAL sends RR to this port
    sock_rr = socket.socket(socket.AF_INET,  # Internet
                            socket.SOCK_DGRAM)  # UDP
    sock_rr.bind((UDP_IP, UDP_PORT2))  # binding is required for receiving not sending
    sock_rr.settimeout(1 / 10000)  # you dont want to wait forever a data that might not come
    flag = False

    ## Configure socket for RR Value send to CA algorithm ##
    UDP_PORT3 = 9901
    sock_rr_send = socket.socket(socket.AF_INET,  # Internet
                                  socket.SOCK_DGRAM)  # UDP
    ## PMU configuration ##

    pmu = Pmu(ip="10.10.114.22", port=1410) #original port config
    ## connect to another VM 10.10.114.23
    #pmu = Pmu(ip="10.10.114.22", port=1410)
    pmu.logger.setLevel("DEBUG")

    cfg = ConfigFrame2(1,  # PMU_ID
                       1000000,  # TIME_BASE
                       1,  # Number of PMUs included in data frame
                       "PDAS_bus2",  # Station name
                       1410,  # Data-stream ID(s)
                       (True, True, True, True),  # Data format - POLAR; PH - REAL; AN - REAL; FREQ - REAL;
                       3,  # Number of phasors(was 3)
                       1,  # Number of analog values
                       1,  # Number of digital status words
                       ["VA", "VB", "VC", "ANALOG1", "BREAKER 1 STATUS",
                        "BREAKER 2 STATUS", "BREAKER 3 STATUS", "BREAKER 4 STATUS", "BREAKER 5 STATUS",
                        "BREAKER 6 STATUS", "BREAKER 7 STATUS", "BREAKER 8 STATUS", "BREAKER 9 STATUS",
                        "BREAKER A STATUS", "BREAKER B STATUS", "BREAKER C STATUS", "BREAKER D STATUS",
                        "BREAKER E STATUS", "BREAKER F STATUS", "BREAKER G STATUS"],  # Channel Names
                       [(0, "v"), (0, "v"),
                        (0, "v")],  # Conversion factor for phasor channels - (float representation, not important)
                       [(1, "pow")],  # Conversion factor for analog channels
                       [(0x0000, 0xffff)],  # Mask words for digital status words
                       50,  # Nominal frequency
                       1,  # Configuration change count
                       50)  # Rate of phasor data transmission) 50 default here

    pmu.set_configuration(cfg)
    pmu.set_header("Random PMU: Modified by Pratyush")

    pmu.run()

    ############## C37.118 Reporting Rates #########################

    rr_list = [5, 10, 25, 50, 100]  # list of available RR as per standard
    First = True
    phasor_list = []
    flag = False
    while True:
        #print(pmu.cfg2,type(pmu.cfg2))
        cframe = pmu.cfg2
        config_rr = cframe.get_data_rate()
        pmu_rr = str(config_rr).encode("ascii") # int to bytes e.g. 50 to b'50'
        sock_rr_send.sendto(pmu_rr, (UDP_IP, UDP_PORT3))
        #print(pmu_rr)
        try:
            aware_rr = sock_rr.recv(1024)
            #drr = int(struct.unpack('d', aware_rr[8:16])[0]) # if coming from OPAL
            del_rr = int(aware_rr.decode("utf-8")) # the received value is +1 or -1
            #drr = crr + del_rr
            drr=del_rr
            flag = True
        except socket.timeout:  # to catch the error essentially it will only store drr if there is something to store and set the flag
            pass
        if pmu.clients:
            ## Socket Values
            ## Listen to the socket

            raw = sock_ph.recv(32)
            if First:
                tm_strt = datetime.now()
                First = False
            else:
                pass
            #print(raw)
            header, mag, angle = struct.unpack('ddd', raw)
            phasor = (mag, angle)
            Vol_A=raw
            VA = float(mag)
            phi_A = float(angle)
            VB = VA
            phi_B = phi_A+(math.pi) * 2 / 3
            VC = VA
            phi_C = phi_A-(math.pi) * 2 / 3
            phasor_list.append([VA, phi_A, VB, phi_B, VC, phi_C])
            # print(len(phasor_list))
            if len(phasor_list) >= (1000 / config_rr):
                phasor_array = np.array(phasor_list)
                #y = np.mean(phasor_array, axis=0)
                y = phasor_array[-1] #latest value
                time_stp = datetime.now()
                sim_time = (time_stp - tm_strt).total_seconds()
                # print(y, time_stp, sim_time)
                phasor_list = []
                pmu.send_data(phasors=[(y[0], y[1]), (y[2], y[3]), (y[4], y[5])], analog=[9.91], digital=[0x0001])
                # print([(VA,phi_A),(VB,phi_B),(VC,phi_C),datetime.now()])
            #if len(phasor_list) > (1000 / config_rr):

        if flag: # if it received something it changes the RR
            pmu.set_data_rate(drr)
            #time.sleep(1/ 1000)
            flag=False

    pmu.join()

pyPDC

from synchrophasor.pdc import Pdc
from synchrophasor.frame import DataFrame
from synchrophasor.frame import ConfigFrame2
import time
from synchrophasor.frame import CommandFrame
import copy
import pickle
import sys
import socket
import struct

"""
PDC will connect to pmu_ip:pmu_port and send request
for header message, configuration and eventually
to start sending measurements.
Here it receives data from PMUs, calculates FPS and Data Rate.
Prints and Saves Configured FPS, actual measured FPS and Data Rate along with phasor measurements
"""

if __name__ == "__main__":
    tt_list = []

    ############## Measured Reporting Rates send to OPAL #########################

    UDP_IP_OPAL = "10.10.114.21"
    # UDP_PORT1fps_send = 8301
    # sock_rr_send1 = socket.socket(socket.AF_INET,  # Internet
    #                               socket.SOCK_DGRAM)  # UDP
    UDP_PORT1fps_send = 8301

    sock_rr_send1 = socket.socket(socket.AF_INET,  # Internet
                                  socket.SOCK_DGRAM)  # UDP

    #init_time = int(time())
    #time_delay = 5
    pdc = Pdc(pdc_id=7, pmu_ip="10.10.114.22", 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
    cnfg_fps_default = config.get_data_rate()
    fps = cnfg_fps_default
    pdc.start()  # Request to start sending measurements
    # print(fps)

    timestamps = []
    alldata = []
    first = True
    i = 0
    # ## Configure socket for RR Value Change status##
    # UDP_IP = "10.10.114.22"
    # UDP_PORT = 603  # whether RR is changed or not, if = 1, RR change initiated else 0
    # sock_ch = socket.socket(socket.AF_INET,  # Internet
    #                         socket.SOCK_DGRAM)  # UDP
    # sock_ch.bind((UDP_IP, UDP_PORT))  # binding is required for receiving not sending
    # sock_ch.settimeout(1 / 100)  # you dont want to wait forever a data that might not come
    # change = 0
    try:
        while True:
            # try:
            #     # listening to rr change signal
            #     change = sock_ch.recv(1024)
            #     change = int(change.decode("utf-8"))
            #     #print(change)
            # except socket.timeout:  # if nothing comes
            #     pass
            # # timing asking each 60s or 1 minute:
            # if  change == 1: #time() >= (init_time + time_delay) or
            #     #init_time += time_delay # this is not a pretty way to do it but i think is clear what is happening
            #
            #     # asking the CFG2:
            #     pdc.pmu_socket.sendto(CommandFrame(pdc.pdc_id, 'cfg2').convert2bytes(), pdc.pmu_address)
            #     change = 0
                #print("Config frame received")
            # 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()
                print("config frame received and data rate is %f" %cnfg_fps)

            if type(data) == DataFrame:
                data = data.get_measurements()
                # print(type(data),sys.getsizeof(data)) # Type and size of object in bytes
                # i+=1
                timestamps.append(data['time'])
                if first:
                    timestrt = data["time"]  # unix timestamp; gives time in seconds
                    cnfg_fps = copy.copy(cnfg_fps_default)
                    first = False
                    data_size = 9999
                    data_rate = 9999
                else:
                    timestp = data["time"]
                    pdc_time = time.time()
                    transmission_time = (pdc_time-timestp)*1000
                    tt_list.append(transmission_time)
                    acctual_fps = 1 / (timestp - timestrt)
                    data_size = sys.getsizeof(data)  # in bits
                    data_rate = ((data_size) * 8) / (1024 * 1024 * (timestp - timestrt))  # in Mbps
                    timestrt = timestp

                    #print("Transmission time is %f ms"%transmission_time)

                    # print(cnfg_fps, "fps",acctual_fps,"fps",data_rate,"Mbps")
                    print("Configured FPS = %s ; Actual FPS = %f, Data Rate = %f Mbps" %
                          (cnfg_fps, acctual_fps, data_rate),"PMU time=",timestp,"pdc time=",time.time())
                    fps = acctual_fps
                    timestamp = time.time()
                    dev_id = 91
                    msg_id = i
                    msg_len = 16
                    send_opal = struct.pack('=hihdd', dev_id, msg_id, msg_len, timestamp, fps)
                    sock_rr_send1.sendto(send_opal, (UDP_IP_OPAL, UDP_PORT1fps_send))  # send actual fps to OPAL
                    i = i + 1

                alldata.extend((data, fps, cnfg_fps,data_rate,data_size))
            if not data:
                #pdc.quit()  # Close connection
                print("Data not coming. Warning !!")
                # print(data,type(data))
                # break
    except ConnectionResetError:
        # pdc.stop()
        # pdc.quit()
        print(tt_list)
        timestring = time.strftime("%Y%m%d-%H%M")
        with open("sync2_%s.pickle"%timestring, "wb+")as handle:
            pickle.dump(alldata, handle)
        print("A file has been written....")