diff --git a/README.md b/README.md index 3f44bbf..580b193 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Synchrophasor module represents implementation of IEEE C37.118.2 standard in Python. `synchrophasor` module is still in development phase but we have a few very interesting tools. -### Notes: +### Notes: ##### - Latest Alpha release: v1.0.0-alpha ##### - We need some beta testers! @@ -15,20 +15,20 @@ Synchrophasor module is made to be easy to install and run. ### Prerequisites ### -You will need `python3` to run module correctly. Check +You will need `python3` to run module correctly. Check your Python version: ``` python --version ``` If you're using Python 2 version you can install [Python 3](https://www.python.org/downloads/) alongside with -Python 2. +Python 2. ### Installing ### * Install using `pip` for Python 3: `pip3 install synchrophasor` * You can download this project as `zip` file and extract it or clone it -using git `git clone https://github.com/iicsys/pypmu.git` and then run +using git `git clone https://github.com/iicsys/pypmu.git` and then run `python3 setup.py install` inside project folder. ### Running the tests ### @@ -36,12 +36,9 @@ using git `git clone https://github.com/iicsys/pypmu.git` and then run Right now we only have one test for frame encapsulation validation. You can check it like this: ``` -python3 tests/validate_frames.py +nose2 ``` -If ```AssertionError``` is **not** shown you're good to go! - - ## Usage - What do we have so far? ## Inside examples folder you will find a few useful examples that utilize @@ -64,7 +61,7 @@ sp.run() ### TinyPMU ### With only few lines of code you can bring up a PMU simulator which will -send constant phasor measurements to connected PDCs. +send constant phasor measurements to connected PDCs. ``` from synchrophasor.pmu import Pmu @@ -87,9 +84,9 @@ pmu.join() ### TinyPDC ### -Here's an example of a very simple PDC. tinyPDC supports only one +Here's an example of a very simple PDC. tinyPDC supports only one connection to a PMU and still cannot understand measurements or -configuration but with your help we can learn tinyPDC to read +configuration but with your help we can learn tinyPDC to read Data Frames and Configuration Frames ``` @@ -113,7 +110,7 @@ while True: ## Applications ## -If you really don't want to know what is inside of these scripts we've +If you really don't want to know what is inside of these scripts we've prepared applications for you inside `apps` folder. Make them executable like this: @@ -163,7 +160,7 @@ do not hesitate to contact us: or 2. Fork this repo. 3. Create new branch: `git checkout -b fixing-your-stupid-bug` 4. Commit changes: `git commit -m 'There you go! Fixed your stupid bug.'` -5. Push changes to the branch: `git push origin fixing-your-stupid-bug` +5. Push changes to the branch: `git push origin fixing-your-stupid-bug` 6. Submit pull request. ## Credits ## @@ -179,7 +176,7 @@ Please check [LICENSE.txt](LICENSE.txt). ## References ## * C37.118.2-2011 - IEEE Standard for Synchrophasor Data Transfer for Power Systems, [>>](http://standards.ieee.org/findstds/standard/C37.118.2-2011.html) - + ## Citations ## _If you use the pyPMU code for your research, please cite the following publication:_ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cafd3e0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nose2 diff --git a/tests/test_frame.py b/tests/test_frame.py new file mode 100644 index 0000000..6a3e278 --- /dev/null +++ b/tests/test_frame.py @@ -0,0 +1,250 @@ +import unittest +import binascii +from synchrophasor.frame import ConfigFrame2 +from synchrophasor.frame import DataFrame +from synchrophasor.frame import HeaderFrame +from synchrophasor.frame import CommandFrame + +__author__ = "Stevan Sandi" +__copyright__ = "Copyright (c) 2016, Tomo Popovic, Stevan Sandi, Bozo Krstajic" +__credits__ = [] +__license__ = "BSD-3" +__version__ = "1.0.0-alpha" + + +def _cfg1pmu(): + return ConfigFrame2( + pmu_id_code=7734, + time_base=1000000, + num_pmu=1, + station_name="Station A", + id_code=7734, + data_format=(False, False, True, False), + phasor_num=4, + analog_num=3, + digital_num=1, + channel_names=[ + "VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", + "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" + ], + ph_units=[ + (915527, "v"), + (915527, "v"), + (915527, "v"), + (45776, "i"), + ], + an_units=[ + (1, "pow"), + (1, "rms"), + (1, "peak"), + ], + dig_units=[ + (0x0000, 0xffff), + ], + f_nom=60, + cfg_count=22, + data_rate=30, + soc=1149577200, + frasec=(463000, "-", False, True, 6)) + + +def _cfg2pmus(): + return ConfigFrame2( + pmu_id_code=7734, + time_base=1000000, + num_pmu=2, + station_name=[ + "Station A", + "Station A", + ], + id_code=[7734, 7734], + data_format=[ + (False, False, True, False), + (False, False, True, False), + ], + phasor_num=[4, 4], + analog_num=[3, 3], + digital_num=[1, 1], + channel_names=[ + [ + "VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", + "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" + ], + [ + "VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", + "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" + ] + ], + ph_units=[ + [(915527, "v"), (915527, "v"), (915527, "v"), (45776, "i")], + [(915527, "v"), (915527, "v"), (915527, "v"), (45776, "i")], + ], + an_units=[ + [(1, "pow"), (1, "rms"), (1, "peak")], + [(1, "pow"), (1, "rms"), (1, "peak")], + ], + dig_units=[ + [(0x0000, 0xffff)], + [(0x0000, 0xffff)], + ], + f_nom=[60, 60], + cfg_count=[22, 22], + data_rate=30, + soc=1149577200, + frasec=(463000, "-", False, True, 6)) + + +class TestConfigFrame2_convert2bytes(unittest.TestCase): + + def test_1_pmu(self): + wnt = ""\ + "aa3101c61e36448527f056071098000f4240000153746174696f6e2041202020"\ + "202020201e360004000400030001564120202020202020202020202020205642"\ + "2020202020202020202020202020564320202020202020202020202020204931"\ + "2020202020202020202020202020414e414c4f4731202020202020202020414e"\ + "414c4f4732202020202020202020414e414c4f47332020202020202020204252"\ + "45414b4552203120535441545553425245414b45522032205354415455534252"\ + "45414b4552203320535441545553425245414b45522034205354415455534252"\ + "45414b4552203520535441545553425245414b45522036205354415455534252"\ + "45414b4552203720535441545553425245414b45522038205354415455534252"\ + "45414b4552203920535441545553425245414b45522041205354415455534252"\ + "45414b4552204220535441545553425245414b45522043205354415455534252"\ + "45414b4552204420535441545553425245414b45522045205354415455534252"\ + "45414b4552204620535441545553425245414b4552204720535441545553000d"\ + "f847000df847000df8470100b2d00000000101000001020000010000ffff0000"\ + "0016001ed5d1" + + self.assertEqual(_str(_cfg1pmu().convert2bytes()), wnt) + + def test_2_pmus(self): + wnt = ""\ + "aa3103741e36448527f056071098000f4240000253746174696f6e2041202020"\ + "202020201e360004000400030001564120202020202020202020202020205642"\ + "2020202020202020202020202020564320202020202020202020202020204931"\ + "2020202020202020202020202020414e414c4f4731202020202020202020414e"\ + "414c4f4732202020202020202020414e414c4f47332020202020202020204252"\ + "45414b4552203120535441545553425245414b45522032205354415455534252"\ + "45414b4552203320535441545553425245414b45522034205354415455534252"\ + "45414b4552203520535441545553425245414b45522036205354415455534252"\ + "45414b4552203720535441545553425245414b45522038205354415455534252"\ + "45414b4552203920535441545553425245414b45522041205354415455534252"\ + "45414b4552204220535441545553425245414b45522043205354415455534252"\ + "45414b4552204420535441545553425245414b45522045205354415455534252"\ + "45414b4552204620535441545553425245414b4552204720535441545553000d"\ + "f847000df847000df8470100b2d00000000101000001020000010000ffff0000"\ + "001653746174696f6e2041202020202020201e36000400040003000156412020"\ + "2020202020202020202020205642202020202020202020202020202056432020"\ + "20202020202020202020202049312020202020202020202020202020414e414c"\ + "4f4731202020202020202020414e414c4f4732202020202020202020414e414c"\ + "4f4733202020202020202020425245414b455220312053544154555342524541"\ + "4b4552203220535441545553425245414b455220332053544154555342524541"\ + "4b4552203420535441545553425245414b455220352053544154555342524541"\ + "4b4552203620535441545553425245414b455220372053544154555342524541"\ + "4b4552203820535441545553425245414b455220392053544154555342524541"\ + "4b4552204120535441545553425245414b455220422053544154555342524541"\ + "4b4552204320535441545553425245414b455220442053544154555342524541"\ + "4b4552204520535441545553425245414b455220462053544154555342524541"\ + "4b4552204720535441545553000df847000df847000df8470100b2d000000001"\ + "01000001020000010000ffff00000016001e20e8" + + self.assertEqual(_str(_cfg2pmus().convert2bytes()), wnt) + + +class TestDataFrame_convert2bytes(unittest.TestCase): + + def test_1_pmu(self): + frm = DataFrame( + pmu_id_code=7734, + stat=("ok", True, "timestamp", False, False, False, 0, "<10", 0), + phasors=[(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], + freq=2500, + dfreq=0, + analog=[100, 1000, 10000], + digital=[0x3c12], + cfg=_cfg1pmu(), + soc=1149580800, + frasec=16817, + ) + + wnt = ""\ + "aa0100341e3644853600000041b10000392b0000e36ace7ce36a318304440000"\ + "09c4000042c80000447a0000461c40003c12d43f" + + self.assertEqual(_str(frm.convert2bytes()), wnt) + + def test_2_pmus(self): + frm = DataFrame( + pmu_id_code=7734, + stat=[("ok", True, "timestamp", False, False, False, 0, "<10", 0), + ("ok", True, "timestamp", False, False, False, 0, "<10", 0)], + phasors=[ + [(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], + [(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], + ], + freq=[2500, 2500], + dfreq=[0, 0], + analog=[[100, 1000, 10000], [100, 1000, 10000]], + digital=[[0x3c12], [0x3c12]], + cfg=_cfg2pmus(), + soc=1149580800, + frasec=16817, + ) + + wnt = ""\ + "aa0100581e3644853600000041b10000392b0000e36ace7ce36a318304440000"\ + "09c4000042c80000447a0000461c40003c120000392b0000e36ace7ce36a3183"\ + "0444000009c4000042c80000447a0000461c40003c12bd52" + + self.assertEqual(_str(frm.convert2bytes()), wnt) + + +class TestHeaderFrame_convert2bytes(unittest.TestCase): + + def test_ok(self): + frm = HeaderFrame( + pmu_id_code=7734, + header="Hello I'm Header Frame.", + soc=1149591600, + frasec=(770000, "+", False, False, 15), + ) + + wnt = ""\ + "aa1100271e36448560300f0bbfd048656c6c6f2049276d204865616465722046"\ + "72616d652e17cc" + + self.assertEqual(_str(frm.convert2bytes()), wnt) + + +class TestCommandFrame_convert2bytes(unittest.TestCase): + + def test_ok(self): + frm = CommandFrame( + pmu_id_code=7734, + command="start", + extended_frame=None, + soc=1149591600, + frasec=(770000, "+", False, False, 15), + ) + + wnt = "aa4100121e36448560300f0bbfd00002ce00" + + self.assertEqual(_str(frm.convert2bytes()), wnt) + + +def _str(bs): + return str(binascii.hexlify(bs), "utf-8") diff --git a/tests/validate_frames.py b/tests/validate_frames.py deleted file mode 100644 index 231bbf1..0000000 --- a/tests/validate_frames.py +++ /dev/null @@ -1,140 +0,0 @@ -import binascii - -from synchrophasor.frame import CommandFrame -from synchrophasor.frame import ConfigFrame2 -from synchrophasor.frame import DataFrame -from synchrophasor.frame import HeaderFrame - - -__author__ = "Stevan Sandi" -__copyright__ = "Copyright (c) 2016, Tomo Popovic, Stevan Sandi, Bozo Krstajic" -__credits__ = [] -__license__ = "BSD-3" -__version__ = "1.0.0-alpha" - - -cfg_hex_string = "aa3101c61e36448527f056071098000f4240000153746174696f6e2041202020202020201e360004000400030001564120" \ - "20202020202020202020202020564220202020202020202020202020205643202020202020202020202020202049312020" \ - "202020202020202020202020414e414c4f4731202020202020202020414e414c4f4732202020202020202020414e414c4f" \ - "4733202020202020202020425245414b4552203120535441545553425245414b4552203220535441545553425245414b45" \ - "52203320535441545553425245414b4552203420535441545553425245414b4552203520535441545553425245414b4552" \ - "203620535441545553425245414b4552203720535441545553425245414b4552203820535441545553425245414b455220" \ - "3920535441545553425245414b4552204120535441545553425245414b4552204220535441545553425245414b45522043" \ - "20535441545553425245414b4552204420535441545553425245414b4552204520535441545553425245414b4552204620" \ - "535441545553425245414b4552204720535441545553000df847000df847000df8470100b2d00000000101000001020000" \ - "010000ffff00000016001ed5d1" - -cfg = ConfigFrame2(7734, 1000000, 1, "Station A", 7734, (False, False, True, False), 4, 3, 1, - ["VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", "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"], - [(915527, "v"), (915527, "v"), (915527, "v"), (45776, "i")], - [(1, "pow"), (1, "rms"), (1, "peak")], [(0x0000, 0xffff)], 60, 22, 30, - 1149577200, (463000, "-", False, True, 6)) - -# cfg.set_soc(1149577200) -# cfg.set_frasec(463000, "-", False, True, 6) - -cfg_hex_result = str(binascii.hexlify(cfg.convert2bytes()), "utf-8") - -assert cfg_hex_result == cfg_hex_string, "Configuration Frame v2 Error." - -cfg_multi_hex_string = "aa3103741e36448527f056071098000f4240000253746174696f6e2041202020202020201e360004000400030001" \ - "56412020202020202020202020202020564220202020202020202020202020205643202020202020202020202020" \ - "202049312020202020202020202020202020414e414c4f4731202020202020202020414e414c4f47322020202020" \ - "20202020414e414c4f4733202020202020202020425245414b4552203120535441545553425245414b4552203220" \ - "535441545553425245414b4552203320535441545553425245414b4552203420535441545553425245414b455220" \ - "3520535441545553425245414b4552203620535441545553425245414b4552203720535441545553425245414b45" \ - "52203820535441545553425245414b4552203920535441545553425245414b455220412053544154555342524541" \ - "4b4552204220535441545553425245414b4552204320535441545553425245414b45522044205354415455534252" \ - "45414b4552204520535441545553425245414b4552204620535441545553425245414b4552204720535441545553" \ - "000df847000df847000df8470100b2d00000000101000001020000010000ffff0000001653746174696f6e204120" \ - "2020202020201e360004000400030001564120202020202020202020202020205642202020202020202020202020" \ - "20205643202020202020202020202020202049312020202020202020202020202020414e414c4f47312020202020" \ - "20202020414e414c4f4732202020202020202020414e414c4f4733202020202020202020425245414b4552203120" \ - "535441545553425245414b4552203220535441545553425245414b4552203320535441545553425245414b455220" \ - "3420535441545553425245414b4552203520535441545553425245414b4552203620535441545553425245414b45" \ - "52203720535441545553425245414b4552203820535441545553425245414b455220392053544154555342524541" \ - "4b4552204120535441545553425245414b4552204220535441545553425245414b45522043205354415455534252" \ - "45414b4552204420535441545553425245414b4552204520535441545553425245414b4552204620535441545553" \ - "425245414b4552204720535441545553000df847000df847000df8470100b2d00000000101000001020000010000" \ - "ffff00000016001e20e8" - -cfgm = ConfigFrame2(7734, 1000000, 2, ["Station A", "Station A"], [7734, 7734], [(False, False, True, False), - (False, False, True, False)], - [4, 4], [3, 3], [1, 1], - [["VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", "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"], - ["VA", "VB", "VC", "I1", "ANALOG1", "ANALOG2", "ANALOG3", "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"]], - [[(915527, "v"), (915527, "v"), (915527, "v"), (45776, "i")], - [(915527, "v"), (915527, "v"), (915527, "v"), (45776, "i")]], - [[(1, "pow"), (1, "rms"), (1, "peak")], [(1, "pow"), (1, "rms"), (1, "peak")]], - [[(0x0000, 0xffff)], [(0x0000, 0xffff)]], [60, 60], [22, 22], 30, - 1149577200, (463000, "-", False, True, 6)) - -# cfgm.set_soc(1149577200) -# cfgm.set_frasec(463000, "-", False, True, 6) - -cfg_multi_hex_result = str(binascii.hexlify(cfgm.convert2bytes()), "utf-8") -assert cfg_multi_hex_result == cfg_multi_hex_string, "Configuration Frame v2 Multistreaming Error." - - -data_hex_string = "aa0100341e3644853600000041b10000392b0000e36ace7ce36a31830444000009c4000042c80000447a0000461c4000" \ - "3c12d43f" - -df = DataFrame(7734, ("ok", True, "timestamp", False, False, False, 0, "<10", 0), - [(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], 2500, 0, [100, 1000, 10000], [0x3c12], cfg, - 1149580800, 16817) - -# df.set_soc(1149580800) -# df.set_frasec(16817) - -data_hex_result = str(binascii.hexlify(df.convert2bytes()), "utf-8") - -assert data_hex_string == data_hex_result, "Data Frame Error." - -data_multi_hex_string = "aa0100581e3644853600000041b10000392b0000e36ace7ce36a31830444000009c4000042c80000447a0000" \ - "461c40003c120000392b0000e36ace7ce36a31830444000009c4000042c80000447a0000461c40003c12bd52" - -dfm = DataFrame(7734, [("ok", True, "timestamp", False, False, False, 0, "<10", 0), - ("ok", True, "timestamp", False, False, False, 0, "<10", 0)], - [[(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], - [(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)]], [2500, 2500], [0, 0], - [[100, 1000, 10000], [100, 1000, 10000]], [[0x3c12], [0x3c12]], cfgm, 1149580800, 16817) - -# dfm.set_soc(1149580800) -# dfm.set_frasec(16817) - -data_multi_hex_result = str(binascii.hexlify(dfm.convert2bytes()), "utf-8") - -assert data_multi_hex_string == data_multi_hex_result, "Data Frame Multistreaming Error." - - -hf_hex_string = "aa1100271e36448560300f0bbfd048656c6c6f2049276d20486561646572204672616d652e17cc" - -hf = HeaderFrame(7734, "Hello I'm Header Frame.", 1149591600, (770000, "+", False, False, 15)) -# hf.set_soc(1149591600) -# hf.set_frasec(770000, time_quality=15) - -hf_hex_result = str(binascii.hexlify(hf.convert2bytes()), "utf-8") - -assert hf_hex_string == hf_hex_result, "Header Frame Error." - -cf_hex_string = "aa4100121e36448560300f0bbfd00002ce00" - -cf = CommandFrame(7734, "start", None, 1149591600, (770000, "+", False, False, 15)) -# cf.set_soc(1149591600) -# cf.set_frasec(770000, time_quality=15) - -cf_hex_result = str(binascii.hexlify(cf.convert2bytes()), "utf-8") - -assert cfg_hex_result == cfg_hex_string, "Command Frame Error."