├── examples ├── __init__.py ├── rawtestdata.csv └── writertest.py ├── .DS_Store ├── src ├── __pycache__ │ └── __init__.cpython-36.pyc ├── __init__.py └── writer.py ├── setup.py ├── LICENSE └── README.md /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relihanl/comtradehandlers/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relihanl/comtradehandlers/HEAD/src/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Initialize the module. 5 | """ 6 | __version__ = "" 7 | __date__ = "" 8 | 9 | # Load the main functions 10 | from comtradehandlers import * -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md') as file: 4 | long_description = file.read() 5 | 6 | setup(name='comtradehandlers', 7 | version='0.1.0', 8 | description='Support for IEEE COMTRADE readers and writers', 9 | long_description=long_description, 10 | url='', 11 | author='Liam Relihan', 12 | author_email='liam.relihan@resourcekraft.com', 13 | license='MIT', 14 | packages=['comtradehandlers'], 15 | package_dir={ 'comtradehandlers': 'src', 16 | 'examples':'examples'}, 17 | keywords=['COMTRADE', 'smartgrid', 'oscilloscope','power quality','power'] 18 | ) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Liam Relihan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | 4 | Its [wikipedia](https://en.wikipedia.org/wiki/Comtrade) entry describes COMTRADE (Common format for Transient Data Exchange for power systems) as "a file format for storing oscillography and status data related to transient power system disturbances". 5 | 6 | This package supports file handlers for the COMTRADE format. It currently has the following limitations: 7 | - currently only has a writer. A COMTRADE reader will come later 8 | - Developed with reference to the "IEC 60255-24 Edition 2.0 2013-04, IEEE Std C37.111" standards document 9 | - the writer only supports ASCII format (1999 version) for the moment 10 | 11 | Usage 12 | ===== 13 | Use the writer as follows: 14 | ```python 15 | from comtradehandlers import writer 16 | import csv 17 | import datetime 18 | # lets use now as the start time 19 | start_time = datetime.datetime.now() 20 | 21 | # ...and 20 mills later as the trigger time 22 | trigger_time = start_time + datetime.timedelta(milliseconds=20) 23 | 24 | comtradeWriter = writer.ComtradeWriter("test3.cfg", start_time, trigger_time,rec_dev_id=250) 25 | 26 | created_id = comtradeWriter.add_digital_channel("RELAY1", 0, 0, 0) 27 | print("Created new digital channel " + str(created_id)) 28 | 29 | created_id = comtradeWriter.add_digital_channel("RELAY2", 0, 0, 0) 30 | print("Created new digital channel " + str(created_id)) 31 | 32 | 33 | 34 | # A Current 35 | created_id = comtradeWriter.add_analog_channel("IA", "A", "I", uu="A", skew=0, min=-500, max=500, primary=1, 36 | secondary=1) 37 | print("Created new analog channel " + str(created_id)) 38 | 39 | # B Current 40 | created_id = comtradeWriter.add_analog_channel("IB", "B", "I", uu="A", skew=0, min=-500, max=500, primary=1, 41 | secondary=1) 42 | print("Created new analog channel " + str(created_id)) 43 | 44 | # C Current 45 | created_id = comtradeWriter.add_analog_channel("IC", "C", "I", uu="A", skew=0, min=-500, max=500, primary=1, 46 | secondary=1) 47 | print("Created new analog channel " + str(created_id)) 48 | 49 | # open a CSV file with raw data to insert. column 0 contains a microsecond offset from start_time 50 | with open('outdata.csv', 'r') as csvfile: 51 | datareader = csv.reader(csvfile, delimiter=',', quotechar='|') 52 | next(datareader, None) # skip the header 53 | for row in datareader: 54 | comtradeWriter.add_sample_record(row[0], 55 | [row[1], row[2], row[3]], 56 | [row[4], row[5]]) 57 | 58 | comtradeWriter.set_header_content("Data generated by comtradehandler from a CSV file. Enjoy!") 59 | 60 | comtradeWriter.finalize() 61 | ``` 62 | 63 | See the example for more code. 64 | 65 | Practically, to understand the various parameters ("uu", "primary", "secondary"), you will need to read the standard and be somewhat familiar with the problem domain. 66 | 67 | Requirements 68 | ============ 69 | 70 | You need Python 3.4 or later to use this package. 71 | 72 | Contact 73 | ======= 74 | 75 | This package was authored by Liam Relihan at www.resourcekraft.com. Contact him at liam.relihan@resourcekraft.com 76 | 77 | License 78 | ======= 79 | 80 | This package is licensed under the terms of the MIT License (see the file 81 | LICENSE). 82 | -------------------------------------------------------------------------------- /examples/rawtestdata.csv: -------------------------------------------------------------------------------- 1 | Offset,A:M,A:a,B:M,B:a,C:M,C:a,freq,ROCOF,DIG1,DIG2 2 | 0,200,0,200,2.0944,300,4.1888,50,0,0,0 3 | 20000,200,0,200,2.0944,300,4.1888,50,0,0,0 4 | 40000,150,0,200,2.0944,300,4.1888,50,0,1,0 5 | 60000,120,0,200,2.0944,300,4.1888,50,0,1,1 6 | 80000,125,0,200,2.0944,300,4.1888,50,0,1,1 7 | 100000,120,0,200,2.0944,300,4.1888,50,0,1,1 8 | 120000,105,0,200,2.0944,300,4.1888,50,0,1,1 9 | 140000,100,0,200,2.0944,300,4.1888,50,0,1,1 10 | 160000,100,0,200,2.0944,300,4.1888,50,0,1,1 11 | 180000,100,0,200,2.0944,300,4.1888,50,0,1,1 12 | 200000,100,0,200,2.0944,300,4.1888,50,0,1,1 13 | 220000,100,0,200,2.0944,300,4.1888,50,0,1,1 14 | 240000,100,0,200,2.0944,300,4.1888,50,0,1,1 15 | 260000,100,0,200,2.0944,300,4.1888,50,0,1,1 16 | 280000,100,0,200,2.0944,300,4.1888,50,0,1,1 17 | 300000,100,0,200,2.0944,300,4.1888,50,0,1,1 18 | 320000,100,0,200,2.0944,300,4.1888,50,0,1,1 19 | 340000,100,0,200,2.0944,300,4.1888,50,0,1,1 20 | 360000,100,0,200,2.0944,300,4.1888,50,0,1,1 21 | 380000,100,0,200,2.0944,300,4.1888,50,0,1,1 22 | 400000,100,0,200,2.0944,300,4.1888,50,0,1,1 23 | 420000,100,0,200,2.0944,300,4.1888,50,0,1,1 24 | 440000,100,0,200,2.0944,300,4.1888,50,0,1,1 25 | 460000,100,0,200,2.0944,300,4.1888,50,0,1,1 26 | 480000,100,0,200,2.0944,300,4.1888,50,0,1,1 27 | 500000,100,0,200,2.0944,300,4.1888,50,0,1,1 28 | 520000,150,0,200,2.0944,300,4.1888,50,0,1,1 29 | 540000,160,0,200,2.0944,300,4.1888,50,0,1,1 30 | 560000,170,0,200,2.0944,300,4.1888,50,0,1,1 31 | 580000,160,0,200,2.0944,300,4.1888,50,0,1,1 32 | 600000,150,0,200,2.0944,300,4.1888,50,0,1,1 33 | 620000,140,0,200,2.0944,300,4.1888,50,0,1,1 34 | 640000,130,0,200,2.0944,300,4.1888,50,0,1,1 35 | 660000,120,0,200,2.0944,300,4.1888,50,0,1,1 36 | 680000,110,0,200,2.0944,300,4.1888,50,0,1,1 37 | 700000,100,0,200,2.0944,300,4.1888,50,0,1,1 38 | 720000,100,0,200,2.0944,300,4.1888,50,0,1,1 39 | 740000,100,0,200,2.0944,300,4.1888,50,0,1,1 40 | 760000,100,0,200,2.0944,300,4.1888,50,0,1,1 41 | 780000,100,0,200,2.0944,300,4.1888,50,0,1,1 42 | 800000,100,0,200,2.0944,300,4.1888,50,0,1,1 43 | 820000,100,0,200,2.0944,300,4.1888,50,0,1,1 44 | 840000,100,0,200,2.0944,300,4.1888,50,0,1,1 45 | 860000,100,0,200,2.0944,300,4.1888,50,0,1,1 46 | 880000,100,0,200,2.0944,300,4.1888,50,0,1,1 47 | 900000,100,0,200,2.0944,300,4.1888,50,0,1,1 48 | 920000,100,0,200,2.0944,300,4.1888,50,0,1,1 49 | 940000,100,0,200,2.0944,300,4.1888,50,0,1,1 50 | 960000,100,0,200,2.0944,300,4.1888,50,0,1,1 51 | 980000,100,0,200,2.0944,300,4.1888,50,0,1,1 52 | 1000000,100,0,200,2.0944,300,4.1888,50,0,1,1 53 | 1020000,100,0,200,2.0944,300,4.1888,50,0,1,1 54 | 1040000,100,0,200,2.0944,300,4.1888,50,0,1,1 55 | 1060000,100,0,200,2.0944,300,4.1888,50,0,1,1 56 | 1080000,100,0,200,2.0944,300,4.1888,50,0,1,1 57 | 1100000,100,0,200,2.0944,300,4.1888,50,0,1,1 58 | 1120000,100,0,200,2.0944,300,4.1888,50,0,1,1 59 | 1140000,100,0,200,2.0944,300,4.1888,50,0,1,1 60 | 1160000,100,0,200,2.0944,300,4.1888,50,0,1,1 61 | 1180000,100,0,200,2.0944,300,4.1888,50,0,1,1 62 | 1200000,100,0,200,2.0944,300,4.1888,50,0,1,1 63 | 1220000,100,0,200,2.0944,300,4.1888,50,0,1,1 64 | 1240000,100,0,200,2.0944,300,4.1888,50,0,1,1 65 | 1260000,100,0,200,2.0944,300,4.1888,50,0,1,1 66 | 1280000,100,0,200,2.0944,300,4.1888,50,0,1,1 67 | 1300000,100,0,200,2.0944,300,4.1888,50,0,1,1 68 | 1320000,100,0,200,2.0944,300,4.1888,50,0,1,1 69 | 1340000,100,0,200,2.0944,300,4.1888,50,0,1,1 70 | 1360000,100,0,200,2.0944,300,4.1888,50,0,1,1 71 | 1380000,100,0,200,2.0944,300,4.1888,50,0,1,1 72 | 1400000,100,0,200,2.0944,300,4.1888,50,0,1,1 73 | 1420000,100,0,200,2.0944,300,4.1888,50,0,1,1 74 | 1440000,100,0,200,2.0944,300,4.1888,50,0,1,1 75 | 1460000,100,0,200,2.0944,300,4.1888,50,0,1,1 76 | 1480000,100,0,200,2.0944,300,4.1888,50,0,1,1 77 | 1500000,100,0,200,2.0944,300,4.1888,50,0,1,1 78 | 1520000,100,0,200,2.0944,300,4.1888,50,0,1,1 79 | 1540000,100,0,200,2.0944,300,4.1888,50,0,1,1 80 | 1560000,100,0,200,2.0944,300,4.1888,50,0,1,1 81 | 1580000,100,0,200,2.0944,300,4.1888,50,0,1,1 82 | 1600000,100,0,200,2.0944,300,4.1888,50,0,1,1 83 | 1620000,100,0,200,2.0944,300,4.1888,50,0,1,1 84 | 1640000,100,0,200,2.0944,300,4.1888,50,0,1,1 85 | 1660000,100,0,200,2.0944,300,4.1888,50,0,1,1 86 | 1680000,100,0,200,2.0944,300,4.1888,50,0,1,1 87 | 1700000,100,0,200,2.0944,300,4.1888,50,0,1,1 88 | 1720000,100,0,200,2.0944,300,4.1888,50,0,1,1 89 | 1740000,100,0,200,2.0944,300,4.1888,50,0,1,1 90 | 1760000,100,0,200,2.0944,300,4.1888,50,0,1,1 91 | 1780000,100,0,200,2.0944,300,4.1888,50,0,1,1 92 | 1800000,100,0,200,2.0944,300,4.1888,50,0,1,1 93 | 1820000,100,0,200,2.0944,300,4.1888,50,0,1,1 94 | 1840000,100,0,200,2.0944,300,4.1888,50,0,1,1 95 | 1860000,100,0,200,2.0944,300,4.1888,50,0,1,1 96 | 1880000,100,0,200,2.0944,300,4.1888,50,0,1,1 97 | 1900000,100,0,200,2.0944,300,4.1888,50,0,1,1 98 | 1920000,100,0,200,2.0944,300,4.1888,50,0,1,1 99 | 1940000,100,0,200,2.0944,300,4.1888,50,0,1,1 100 | 1960000,100,0,200,2.0944,300,4.1888,50,0,1,1 -------------------------------------------------------------------------------- /examples/writertest.py: -------------------------------------------------------------------------------- 1 | from comtradehandlers import writer 2 | 3 | 4 | import csv 5 | import datetime 6 | 7 | 8 | """ 9 | FIRST FILE 10 | """ 11 | 12 | 13 | """ 14 | The following code simulates phasor data within the COMTRADE format. See the following for more information: 15 | PSRC H8 Application of COMTRADE for Synchrophasor Data 16 | Approved by IEEE PSRC Subcommittee H on May 13, 2010 as a PSRC Report 17 | Schema for Phasor Data Using the COMTRADE File Standard 18 | """ 19 | start_time = datetime.datetime.now() 20 | trigger_time = start_time + datetime.timedelta(milliseconds=20) 21 | comtradeWriter = writer.ComtradeWriter("test2.cfg", start_time, trigger_time,rec_dev_id=250) 22 | 23 | created_id = comtradeWriter.add_digital_channel("RELAY1", 0, 0, 0) 24 | print("Created new digital channel " + str(created_id)) 25 | 26 | created_id = comtradeWriter.add_digital_channel("RELAY2", 0, 0, 0) 27 | print("Created new digital channel " + str(created_id)) 28 | 29 | # A Current Phasor 30 | created_id = comtradeWriter.add_analog_channel("LK:INC:Am", "Am", "Inom", uu="A", skew=0, min=0, max=250, primary=1, 31 | secondary=1) 32 | print("Created new analog channel " + str(created_id)) 33 | 34 | created_id = comtradeWriter.add_analog_channel("LK:INC:Aa", "Aa", "", uu="rad", skew=0, min=0, max=7, primary=1, 35 | secondary=1) 36 | print("Created new analog channel " + str(created_id)) 37 | 38 | # B Current Phasor 39 | created_id = comtradeWriter.add_analog_channel("LK:INC:Bm", "Bm", "Inom", uu="A", skew=0, min=0, max=250, primary=1, 40 | secondary=1) 41 | print("Created new analog channel " + str(created_id)) 42 | 43 | created_id = comtradeWriter.add_analog_channel("LK:INC:Ba", "Ba", "", uu="rad", skew=0, min=0, max=7, primary=1, 44 | secondary=1) 45 | print("Created new analog channel " + str(created_id)) 46 | 47 | # C Current Phasor 48 | created_id = comtradeWriter.add_analog_channel("LK:INC:Cm", "Cm", "Inom", uu="A", skew=0, min=0, max=250, primary=1, 49 | secondary=1) 50 | print("Created new analog channel " + str(created_id)) 51 | 52 | created_id = comtradeWriter.add_analog_channel("LK:INC:Ca", "Ca", "", uu="rad", skew=0, min=0, max=7, primary=1, 53 | secondary=1) 54 | print("Created new analog channel " + str(created_id)) 55 | 56 | # frequency 57 | created_id = comtradeWriter.add_analog_channel("LK:INC:F", "F", "", uu="hz", a=0.001, b=50, skew=0, min=0, max=60, 58 | primary=1, secondary=1) 59 | print("Created new analog channel " + str(created_id)) 60 | 61 | # ROCOF 62 | created_id = comtradeWriter.add_analog_channel("LK:INC:df", "df", "", uu="hz", a=0.01, b=0, skew=0, min=-50, max=50, 63 | primary=1, secondary=1) 64 | print("Created new analog channel " + str(created_id)) 65 | 66 | with open('rawtestdata.csv', 'r') as csvfile: 67 | datareader = csv.reader(csvfile, delimiter=',', quotechar='|') 68 | next(datareader, None) # skip the header 69 | for row in datareader: 70 | comtradeWriter.add_sample_record(row[0], 71 | [row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]], 72 | [row[9], row[10]]) 73 | 74 | comtradeWriter.set_header_content("Data generated by Liam Relihan from a CSV file. Enjoy!") 75 | 76 | comtradeWriter.finalize() 77 | 78 | """ 79 | SECOND FILE 80 | """ 81 | 82 | start_time = datetime.datetime.now() 83 | trigger_time = start_time + datetime.timedelta(milliseconds=20) 84 | comtradeWriter2 = writer.ComtradeWriter("test3.cfg", start_time, trigger_time,rec_dev_id=250) 85 | 86 | created_id = comtradeWriter2.add_digital_channel("RELAY1", 0, 0, 0) 87 | print("Created new digital channel " + str(created_id)) 88 | 89 | created_id = comtradeWriter2.add_digital_channel("RELAY2", 0, 0, 0) 90 | print("Created new digital channel " + str(created_id)) 91 | 92 | 93 | 94 | # A Current 95 | created_id = comtradeWriter2.add_analog_channel("IA", "A", "I", uu="A", skew=0, min=-500, max=500, primary=1, 96 | secondary=1) 97 | print("Created new analog channel " + str(created_id)) 98 | 99 | # B Current 100 | created_id = comtradeWriter2.add_analog_channel("IB", "B", "I", uu="A", skew=0, min=-500, max=500, primary=1, 101 | secondary=1) 102 | print("Created new analog channel " + str(created_id)) 103 | 104 | # C Current 105 | created_id = comtradeWriter2.add_analog_channel("IC", "C", "I", uu="A", skew=0, min=-500, max=500, primary=1, 106 | secondary=1) 107 | print("Created new analog channel " + str(created_id)) 108 | 109 | 110 | with open('outdata.csv', 'r') as csvfile: 111 | datareader = csv.reader(csvfile, delimiter=',', quotechar='|') 112 | next(datareader, None) # skip the header 113 | for row in datareader: 114 | print(" 0="+row[0]+" 1="+row[1]+" 2="+row[2]+" 3="+row[3]+" 4="+row[4]+" 5="+row[5]) 115 | comtradeWriter2.add_sample_record(row[0], 116 | [row[1], row[2], row[3]], 117 | [row[4], row[5]]) 118 | 119 | comtradeWriter2.set_header_content("Data generated by Liam Relihan from a CSV file. Enjoy!") 120 | 121 | comtradeWriter2.finalize() 122 | -------------------------------------------------------------------------------- /src/writer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import datetime 5 | 6 | 7 | class ComtradeWriter: 8 | """ 9 | A python Class to write IEEE Comtrade files. 10 | 11 | Based upon IEC 60255-24 Edition 2.0 2013-04, IEEE Std C37.111 12 | 13 | TODO: Currently only supports ASCII data files. Add support for binary files later 14 | 15 | """ 16 | 17 | 18 | def __init__(self, filename, start, trigger, station_name="STN", rec_dev_id="", rev_year="1999", 19 | lf=50, timemult=1.0): 20 | 21 | self.clear() 22 | 23 | self.filename = filename 24 | self.station_name = station_name 25 | self.rec_dev_id = rec_dev_id 26 | if rev_year not in ['1991', '1999', '2013']: 27 | raise ValueError('Invalid rev_year used to create writer') 28 | self.rev_year = rev_year 29 | self.lf = lf 30 | self.start = start 31 | self.trigger = trigger 32 | self.timemult = timemult 33 | 34 | datafilename = self.filename[0:-4] + '.dat' 35 | 36 | self.data_file_handler = open(datafilename, 'w') 37 | 38 | def clear(self): 39 | self.filename = '' 40 | self.config_file_handler = 0 41 | self.data_file_handler = 0 42 | 43 | self.station_name = '' 44 | self.rec_dev_id = '' 45 | self.rev_year = 1999 46 | 47 | self.TT = 0 48 | self.A = 0 49 | self.D = 0 50 | 51 | self.An = [] 52 | self.Ach_id = [] 53 | self.Aph = [] 54 | self.Accbm = [] 55 | self.uu = [] 56 | self.a = [] 57 | self.b = [] 58 | self.skew = [] 59 | self.min = [] 60 | self.max = [] 61 | self.primary = [] 62 | self.secondary = [] 63 | self.PS = [] 64 | # Digital channel information: 65 | self.Dn = [] 66 | self.Dch_id = [] 67 | self.Dph = [] 68 | self.Dccbm = [] 69 | self.y = [] 70 | self.lf = 0 71 | self.nrates = 0 72 | self.samp = [] 73 | self.endsamp = [] 74 | # Date/time stamps: 75 | # defined by: [dd,mm,yyyy,hh,mm,ss.ssssss] 76 | self.start = "01/01/2000,00:00:00.000000" 77 | self.trigger = "01/01/2000,00:00:00.000000" 78 | # Data file type: we are locking this into ASCII for the moment 79 | self.ft = 'ASCII' 80 | # Time stamp multiplication factor: 81 | self.timemult = 1.0 82 | self.DatFileContent = '' 83 | 84 | # header data for .HDR file. No file is written if header equals None 85 | self.header = None 86 | 87 | # the number of the next sample in the data file 88 | self.next_sample_number = 1 89 | 90 | def finalize(self): 91 | """closes the writer by writing out and/or closing all of the remaining files""" 92 | self.__writeCFGFile(self.filename) 93 | if self.data_file_handler: 94 | self.data_file_handler.write("\x1A") 95 | self.data_file_handler.close() 96 | 97 | if self.header: 98 | headerfilename = self.filename[0:-4] + '.hdr' 99 | 100 | headerfilehandler = open(headerfilename, 'w') 101 | headerfilehandler.write(self.header) 102 | headerfilehandler.close() 103 | 104 | return 105 | 106 | def set_header_content(self, content): 107 | """Sets the optional header content that gets written into the .HDR file""" 108 | self.header = content 109 | 110 | def add_analog_channel(self, id: str, ph: str, ccbm: str, uu: str = "", a=1.0, b=0.0, skew=0.0, min=0.0, max=0.0, 111 | primary=1.0, secondary=1.0, PS="P"): 112 | """adds an analog channel. All channels should be added before any data is added""" 113 | if PS not in ['p', 'P', 's', 'S']: 114 | raise ValueError('Invalid PS value used to add analog channel. Only valid values are p, P, s, S') 115 | 116 | self.A += 1 117 | self.TT += 1 118 | self.An.append(self.A) 119 | self.Ach_id.append(id) 120 | self.Aph.append(ph) 121 | self.Accbm.append(ccbm) 122 | self.uu.append(uu) 123 | self.a.append(a) 124 | self.b.append(b) 125 | self.skew.append(skew) 126 | self.min.append(min) 127 | self.max.append(max) 128 | self.primary.append(primary) 129 | self.secondary.append(secondary) 130 | self.PS.append(PS) 131 | return self.A 132 | 133 | def add_digital_channel(self, id, ph, ccbm, y): 134 | """adds a digital channel. All channels should be added before any data is added""" 135 | self.D += 1 136 | self.TT += 1 137 | self.Dn.append(self.D) 138 | self.Dch_id.append(id) 139 | self.Dph.append(ph) 140 | self.Dccbm.append(ccbm) 141 | self.y.append(y) 142 | return self.D 143 | 144 | def add_sample_record(self, offset, analog_data, digital_data): 145 | """adds a record for a particular offset from the start time stamp. The number of analog and digital 146 | data samples should match the number of analog and digital (respectively) channels already created. 147 | Failure to do so, will result in exceptions""" 148 | self.data_file_handler.write(str(self.next_sample_number) 149 | + ", " 150 | + str(offset) 151 | + ", " 152 | + ", ".join(analog_data) 153 | + ", " 154 | + ", ".join(digital_data) 155 | + "\r\n") 156 | 157 | self.next_sample_number += 1 158 | 159 | def __get_formatted_comtrade_ts(self, ts): 160 | """Given a time stamp, returns a formatted string in the format 01/01/2000,00:00:00.000000""" 161 | return ts.strftime('%d/%m/%Y,%H:%M:%S') + ('.%06d' % ts.microsecond) 162 | 163 | def __writeCFGFile(self, write_filename): 164 | """ 165 | Writes the Comtrade header file (.cfg). 166 | """ 167 | 168 | self.config_file_handler = open(write_filename, 'w') 169 | 170 | # write first line: 171 | self.config_file_handler.write(",".join((self.station_name, str(self.rec_dev_id), str(self.rev_year))) + "\r\n") 172 | 173 | # write second line: 174 | self.config_file_handler.write(",".join((str(self.TT), str(self.A) + "A", str(self.D) + "D")) + "\r\n") 175 | 176 | # writing analog channel lines: 177 | for i in range(self.A): 178 | self.config_file_handler.write(",".join((str(self.An[i]), 179 | str(self.Ach_id[i]), 180 | str(self.Aph[i]), 181 | str(self.Accbm[i]), 182 | str(self.uu[i]), 183 | str(self.a[i]), 184 | str(self.b[i]), 185 | str(self.skew[i]), 186 | str(self.min[i]), 187 | str(self.max[i]), 188 | str(self.primary[i]), 189 | str(self.secondary[i]), 190 | str(self.PS[i]))) + "\r\n") 191 | 192 | # writing digital channel lines: 193 | for i in range(self.D): 194 | self.config_file_handler.write(",".join((str(self.Dn[i]), 195 | str(self.Dch_id[i]), 196 | str(self.Dph[i]), 197 | str(self.Dccbm[i]), 198 | str(self.Dccbm[i]), 199 | str(self.y[i]))) + "\r\n") 200 | 201 | # write line frequency: 202 | self.config_file_handler.write(str(self.lf) + "\r\n") 203 | 204 | # Read sampling rates: 205 | self.config_file_handler.write(str(self.nrates) + "\r\n") 206 | 207 | if (self.nrates==0): 208 | self.config_file_handler.write("0,"+ str(self.next_sample_number -1)+ "\r\n") 209 | else: 210 | for i in range(self.nrates): # @UnusedVariable 211 | self.config_file_handler.write(",".join((str(self.samp[i]), 212 | str(self.endsamp[i]) 213 | )) + "\r\n") 214 | 215 | # write start date and time ([dd,mm,yyyy,hh,mm,ss.ssssss]): 216 | self.config_file_handler.write(self.__get_formatted_comtrade_ts(self.start) + "\r\n") 217 | 218 | # write trigger date and time ([dd,mm,yyyy,hh,mm,ss.ssssss]): 219 | self.config_file_handler.write(self.__get_formatted_comtrade_ts(self.trigger) + "\r\n") 220 | 221 | # Write file type: 222 | self.config_file_handler.write(self.ft + "\r\n") 223 | 224 | # Write time multiplication factor: 225 | self.config_file_handler.write(str(int(self.timemult)) + "\r\n") 226 | 227 | # END READING .CFG FILE. 228 | self.config_file_handler.close() # Close file. 229 | 230 | 231 | --------------------------------------------------------------------------------