DER profile¶
The IEEE 1815.2 point map, and the builder that turns a map and a binding into an outstation. Serving a DER is the guide; this is the reference.
Map¶
py1815.profile.model ¶
The point model: what a loaded profile table says about one point.
A map is a set of points with absolute indices. The tables state some indices relative to a block (the second inverter's fourth analog input), and those are resolved when the map is loaded for a DER of known composition (D37), so nothing here or after it does index arithmetic.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Kind ¶
Bases: StrEnum
The five kinds of point the profile defines, by the tables' own names.
Source code in src/py1815/profile/model.py
Composition
dataclass
¶
How many of each repeating component a DER has.
The equipment blocks of the tables are stated once per unit, and the number of units is the DER's. Zero of a component means its block is not served at all.
Source code in src/py1815/profile/model.py
Point
dataclass
¶
One point, at the index this DER serves it at.
Source code in src/py1815/profile/model.py
MapError ¶
PointMap
dataclass
¶
Every point of the profile, resolved for one DER.
Source code in src/py1815/profile/model.py
py1815.profile.load ¶
Load the profile tables and resolve them into a map for one DER.
The tables are a JSON document py1815.profile.extract writes from the IEEE
1815.2 companion workbook. This library does not carry them (D36): they are
read from wherever the caller keeps its own copy, and :func:default_tables
says where that is unless told otherwise.
Resolution is the one place block arithmetic happens (D37). A point the tables
address relative to an equipment block is repeated once per unit of that
equipment, each at block start + unit * block length + offset; everything
downstream sees absolute indices.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
default_tables ¶
Where the tables are read from when no path is given.
The environment variable if it is set, otherwise a file under the user's home directory. Never a path inside the installed package: the tables are the caller's copy of a document this library may not redistribute, and a location inside the package is one a packaging step could sweep up.
Source code in src/py1815/profile/load.py
read_tables ¶
The tables document at path, or at the default location.
Source code in src/py1815/profile/load.py
load ¶
Read the tables and resolve them for a DER of the given composition.
resolve ¶
The tables as a flat map of absolute indices for one composition.
Source code in src/py1815/profile/load.py
Binding¶
py1815.profile.binding ¶
Binding: how a caller says where each point's value comes from.
A point is bound by callables, one per direction (D38). An input is bound to something that returns its value; an output is bound to something that accepts one. Everything between that and the wire -- scaling, flags, class 0, events, the echo of a control -- is the builder's, so a simulator and a data store bind the same way and neither touches an object encoder.
Values cross this interface in engineering units: watts, volts, percent, a boolean. The tables say how each travels, and that conversion is not the caller's to repeat.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Quality ¶
Bases: Enum
What a source knows about the value it is handing over.
Four states, because four have a representation on the wire. A value
that is merely old has none -- static objects carry no timestamp -- so a
source decides for itself when old becomes COMM_LOST.
Source code in src/py1815/profile/binding.py
Reading
dataclass
¶
One value from a source, with what the source knows about it.
Source code in src/py1815/profile/binding.py
Output
dataclass
¶
One bound output.
Source code in src/py1815/profile/binding.py
Binding ¶
Every point a caller serves, and how.
Built up by calls and handed to the builder, which reads it once. Binding the same address twice is refused: the second binding would silently replace the first, and the caller that wrote both believes both are live.
Source code in src/py1815/profile/binding.py
read ¶
Bind an input (binary, analog or counter) to where its value comes from.
Source code in src/py1815/profile/binding.py
output ¶
output(
kind: Kind,
index: int,
apply: Writer | None = None,
*,
initial: float | bool | None = None,
check: Callable[[float], CommandStatus | None]
| None = None,
status: Reader | None = None,
) -> None
Bind an output (binary or analog) to what a command on it does.
Source code in src/py1815/profile/binding.py
Event policy¶
Which points report events, in which class, and past what deadband: data a
deployment hands to the builder as event_policy. The guide's
Setting the event policy has an example
and the order in which a point's own rule, its kind's rule and the tables are
consulted.
py1815.profile.policy ¶
Event policy: which points report events, in which class, past what deadband.
The tables give every point a default event class, and a default is all it is. What a controlling station wants reported differs from one deployment to the next, so the choice is taken as data: a rule for each kind of point, and exceptions for the points named one at a time. A deployment edits that data and not the code that binds its points.
The policy is a mapping, or the dataclasses a mapping becomes. This module reads no file and defines no file format (D66): a caller that keeps its policy in JSON or YAML loads it with whatever it already uses and hands over the result.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
EventRule
dataclass
¶
What one point, or every point of one kind, is told about its events.
Each field left as None says nothing, and whatever stands behind the rule decides: behind a point's rule, the rule for its kind; behind that, the tables.
Source code in src/py1815/profile/policy.py
decides_reporting
property
¶
Whether the rule says anything about a point reporting at all.
from_mapping
classmethod
¶
A rule from its plain form: class, events and deadband.
Source code in src/py1815/profile/policy.py
EventPolicy
dataclass
¶
A deployment's event reporting: a rule per kind, and exceptions per point.
Three kinds report events and may be named: binary inputs, analog inputs, and counters, whose rule governs the event each freeze logs. A deadband belongs to an analog input and to nothing else.
What can be checked without a map is checked here, so a policy that cannot be right is refused when it is made. Whether the points it names exist is the builder's to check, when it has the map beside it.
Source code in src/py1815/profile/policy.py
from_mapping
classmethod
¶
A policy from plain data, as a configuration file would hold it.
::
{
"defaults": {"AI": {"class": 2, "deadband": 0.5}},
"points": {
"AI537": {"class": 1, "deadband": 500},
"BI12": {"events": False},
},
}
defaults is keyed by kind (BI, AI or CTR) and
points by kind and index together. Either may be left out. A key
this does not know is refused, since a misspelled one would otherwise
be a rule that silently does nothing.
Source code in src/py1815/profile/policy.py
Outstation¶
py1815.profile.outstation ¶
The builder: a point map and a binding in, an IEEE 1815.2 outstation out.
:class:DerOutstation is everything a :class:~py1815.session.Session needs
from the device side. It answers reads from the map, in the variations and
class membership the profile selects; it accepts controls, scaled and range
checked by the tables; it records events with the class each point is given;
and it freezes the counters. The caller supplied values in engineering units
through a :class:~py1815.profile.binding.Binding and never sees an object.
What the profile fixes, and this module therefore does not ask about:
- Binary inputs, analog inputs, counters and frozen counters answer a class 0 read. Output status does not: it is read by naming its group.
- The points that advertise block starting indices are served, and are left out of class 0.
- A "supports" input is static only. It never produces an event.
- A binary output behaves as latched whatever operation commanded it.
- A freeze does not clear the counters it freezes.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
DerOutstation ¶
An IEEE 1815.2 outstation's device side, built from a map and a binding.
Implements the read, control and freeze providers a session takes, so one
object is handed to :class:~py1815.session.Session three times, or
:meth:session does it.
Source code in src/py1815/profile/outstation.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 | |
read_only
property
writable
¶
Whether this outstation reports and refuses every control (D65).
default_variation ¶
served ¶
event_class ¶
The class a point's events are reported in: 1, 2 or 3, or 0 for none.
What is in force, after the event policy and the tables have both had their say. For a counter it is the class its freezes are logged in.
Source code in src/py1815/profile/outstation.py
deadband ¶
An analog input's event deadband in force, in transmitted units.
Zero reports every change of the transmitted value, and is also the answer for a point that reports no events at all.
Source code in src/py1815/profile/outstation.py
value ¶
frozen ¶
Return what counter index held at its last freeze, or None if never frozen.
coverage ¶
Every point of the map against what this outstation serves.
For each point: whether it is bound, served without a binding (and how), or absent, and for a served point the quality it has at this moment. Where a point's value comes from was settled when the outstation was built and is the same in every report. The quality is read now, by asking each source once, so a report is a snapshot.
A report, and no part of what a master is answered with: it buffers no event, changes no output and is read by nothing here (D67).
Source code in src/py1815/profile/outstation.py
now_ms ¶
set_time ¶
Adopt the time a master wrote. Later events and freezes carry it.
read ¶
read_blocks ¶
The objects a read names, as the blocks a response may be split at.
Source code in src/py1815/profile/outstation.py
poll ¶
Read every event-reporting input and buffer what changed.
Called on whatever cadence the caller's sources move at, and again after a control is operated so the inputs that mirror it report the change at once. The first call only records where every point stands: a master learns initial values from its integrity poll, and a buffer of them would be a few hundred events saying nothing happened.
Returns:
| Type | Description |
|---|---|
int
|
How many events were buffered. |
Source code in src/py1815/profile/outstation.py
freeze ¶
Freeze the counters a master named.
Under the DER profile the counters are not cleared, whichever freeze was asked for: the profile has a controlling station compute an interval's energy by subtracting two frozen values, which a counter that restarted from zero would break. Held to Subset Level 2, a freeze-and-clear clears, as the base standard has it.
Source code in src/py1815/profile/outstation.py
freeze_all ¶
select ¶
Whether each control could be operated. Nothing is executed.
operate ¶
Execute each control through its binding, and say how each went.
Source code in src/py1815/profile/outstation.py
session ¶
A session wired to this outstation, with the profile's options set.
Analog events travel in the 32-bit variation without time, which is the profile's selection and the one a Level 2 master is certain to parse; the time is asked for from the first response; and the freeze functions are served only when there are counters to freeze. The first two are defaults a caller may override by naming the option.
Every other option of :class:~py1815.session.Session passes
through. unsolicited=True is the one a profile's consumers ask
about: the session then reports the events :meth:poll buffers to a
master that has enabled their class, without waiting to be polled.
It is off unless named, and nothing about the map or the binding
changes with it.
Source code in src/py1815/profile/outstation.py
Configuration¶
py1815.profile.config ¶
The DER outstation's configuration: every setting in one JSON document.
.. code-block:: json
{
"bind": "0.0.0.0:20000",
"outstation_address": 10,
"unsolicited": true,
"composition": {"meters": 1, "inverters": 2}
}
py1815-der config prints the complete document, with every default filled
in, for editing. py1815-der run --config FILE loads one, and so do
points and profile.
Unknown settings and values of the wrong type are errors that name where they are, so a typing mistake is reported instead of ignored.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
ConfigError ¶
Identity
dataclass
¶
What the DNP3 Device Profile document says about the device.
Source code in src/py1815/profile/config.py
DerConfig
dataclass
¶
The DER outstation's whole configuration.
Source code in src/py1815/profile/config.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
from_mapping
classmethod
¶
Build a configuration from a decoded JSON document.
Raises :class:~py1815.settings.ConfigError for an unknown setting or
a value of the wrong type.
Source code in src/py1815/profile/config.py
load
classmethod
¶
outstation_options ¶
Return the keyword arguments for :class:~py1815.profile.outstation.DerOutstation.
Source code in src/py1815/profile/config.py
session_options ¶
Return the keyword arguments for the outstation's session.
Source code in src/py1815/profile/config.py
describe ¶
Return the complete configuration as a JSON-compatible dict.
Source code in src/py1815/profile/config.py
py1815.settings ¶
Helpers for reading and validating JSON configuration files.
Shared by the master's configuration (:mod:py1815.master.config) and the DER
outstation's (:mod:py1815.profile.config). Each check takes the value and
its location in the document, and raises :class:ConfigError with a message
that names the location.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
ConfigError ¶
seconds ¶
Return a number greater than minimum, of seconds unless unit says otherwise.
Source code in src/py1815/settings.py
integer ¶
Return a whole number from low to high.
Source code in src/py1815/settings.py
boolean ¶
text ¶
section ¶
Return a JSON object whose keys are all in allowed.
Source code in src/py1815/settings.py
read ¶
Read a JSON configuration file and return the decoded document.
Source code in src/py1815/settings.py
Coverage¶
py1815.profile.coverage ¶
Coverage: how much of the profile a built outstation serves.
A deployment that starts from a partial map needs to see what it has and what
it has left. :meth:~py1815.profile.outstation.DerOutstation.coverage answers
with one :class:Entry for every point of the resolved map: where the point's
value comes from, or that it has none, and how it stood when the report was
made. Mandatory points are told apart from optional ones, so the report also
says how far the map is from one a strict build would accept.
It is a report and nothing else. No request is answered from it, and taking one buffers no event and changes no output (D67).
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Source ¶
Bases: Enum
Where a point's value comes from, which is fixed when the outstation is built.
The first four are the ways the builder serves a point, in the order it tries them for an input. An output has only the first: it is served when it is bound, and not otherwise.
Source code in src/py1815/profile/coverage.py
Entry
dataclass
¶
One point of the map, and how this outstation stands on it.
Source code in src/py1815/profile/coverage.py
Coverage
dataclass
¶
Every point of the map against what an outstation serves.
Two reports compare equal when every point stands alike in both, so a deployment can keep one and see what a new binding changed.
Source code in src/py1815/profile/coverage.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
absent
property
¶
The points nothing serves, mandatory and optional alike.
offline
property
¶
The served points that were not online when the report was made.
Each is on the wire, with its ONLINE flag clear: its source could not be reached, it has never been read or written, or it belongs to a function that is disabled. The entry's quality says which.
missing
property
¶
The mandatory points nothing serves: what a strict build refuses over.
conformant
property
¶
Whether every mandatory point is served, which is what strict requires.
entry ¶
The entry for one point, or the error that says the map has none.
Source code in src/py1815/profile/coverage.py
of ¶
The entries whose value comes from any of the sources named.
changed_since ¶
The entries that differ from an earlier report of the same map.
Each is returned as it stands now. A point the earlier report did not hold counts as changed.
Source code in src/py1815/profile/coverage.py
render ¶
The report as text: a summary, a count by source, and a line per point.
A point's line holds its address, M if it is mandatory, where its
value comes from, its quality, and its name. The lines do not depend
on one another, so comparing two renderings line by line shows the
points that moved.
Source code in src/py1815/profile/coverage.py
Curves¶
py1815.profile.curves ¶
The generic curves of IEEE 1815.2 clause 6.1.3, held behind one window.
A DER function that follows a curve does not get two hundred points of its own. The profile gives every curve the same block of points and a selector that says which curve the block is showing, and each function names the curve it uses by number. This module is the store behind that block: the curves, which one is selected, which functions refer to which, and the three rules the standard attaches to them.
- A selector naming a curve that does not exist is refused.
- A curve referred to by an enabled function is locked: its type, its units and its points cannot be changed until the function is disabled or pointed at another curve.
- A function can only be pointed at a curve of a type it follows.
The standard recommends the status for the first two and leaves the third to the implementation, which here answers that the operation is not supported for that curve. Zero as a function's curve number means it uses none.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Curve
dataclass
¶
One curve, as the integers a controlling station wrote.
The scaling of a point depends on the units the curve declares, so the values are kept as they travel and whoever follows the curve applies the scaling for the units it understands.
Source code in src/py1815/profile/curves.py
points
property
¶
The points in use, as written, lowest numbered first.
at ¶
The curve's value at x, in the units its points were written in.
Straight lines between points, and level beyond either end. None for a curve with no points. The points are followed in the order of their independent values, so a curve that doubles back to describe hysteresis is not followed as one.
Source code in src/py1815/profile/curves.py
CurveStore ¶
The curves a DER holds, and the window a controlling station edits them through.
Source code in src/py1815/profile/curves.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | |
referenced ¶
locked ¶
Whether a function that is enabled names this curve.
bind ¶
bind(
binding: Binding,
*,
output: int,
readback: int,
referenced: int,
check: Check | None = None,
) -> None
Bind the curve block: its selector, the fields, the points and the indicator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
binding
|
Binding
|
Where the points are bound. |
required |
output
|
int
|
The analog output index of the selector. The fields and then the points follow it. |
required |
readback
|
int
|
The analog input index the selector reads back at; the inputs are laid out as the outputs are. |
required |
referenced
|
int
|
The binary input saying whether the selected curve is named by a function. |
required |
check
|
Check | None
|
A refusal that applies to every write here, such as a lockout, consulted before this store's own. |
None
|
Source code in src/py1815/profile/curves.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
reference ¶
reference(
binding: Binding,
index: int,
*,
types: Iterable[int],
enabled: Callable[[], bool],
check: Check | None = None,
) -> Callable[[], Curve | None]
Bind a function's curve number, and return how to get the curve it names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
binding
|
Binding
|
Where the output is bound. |
required |
index
|
int
|
The analog output that holds the function's curve number. |
required |
types
|
Iterable[int]
|
The curve types the function follows. |
required |
enabled
|
Callable[[], bool]
|
Whether the function is enabled now, which is what locks the curve it names. |
required |
check
|
Check | None
|
A refusal consulted before this store's own. |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[], Curve | None]
|
A callable giving the curve the function names, or None while it |
Callable[[], Curve | None]
|
names none. |
Source code in src/py1815/profile/curves.py
Device Profile¶
py1815.profile.device_profile ¶
Write the DNP3 Device Profile document for an outstation built here.
IEEE Std 1815 has every device publish a Device Profile: a statement of what it
implements and how it is configured, in an XML form the DNP Users Group
maintains a schema for. This module writes that document, for schema version
2.12.00, from a built :class:~py1815.profile.outstation.DerOutstation and the
:class:~py1815.session.Session serving it.
Everything in it is read from the objects it describes. The point lists come from what the outstation serves, the limits and options from the session's own configuration, and the implementation table from the same facts the session answers by. Nothing is restated by hand, because a profile that can drift from its device is the usual kind, and the reason people distrust them.
What it does not claim. A profile has places for figures only a measurement can supply (clock drift, response time, timestamp error) and for a conformance test result. None has been measured or run for this library, so those elements are left out, which the schema permits. An absent element says "not stated"; a guessed one would say something false.
The schema and its rendering stylesheet are the DNP Users Group's and are not carried here. The document names them, as the standard has an instance do, and validates against a copy the caller holds.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Identity
dataclass
¶
Who made the device and what it is called: the facts no object holds.
Source code in src/py1815/profile/device_profile.py
software ¶
The stated software version, or this library's own.
Source code in src/py1815/profile/device_profile.py
Row
dataclass
¶
One line of the implementation table.
Source code in src/py1815/profile/device_profile.py
Implementation
dataclass
¶
What an outstation answers: object rows, and function codes that carry none.
Source code in src/py1815/profile/device_profile.py
implementation ¶
The implementation table for an outstation as it is configured.
Built from what is served and what the session was given, so a monitor with no outputs lists no controls and an outstation with no counters lists no freezes. The tests hold every row to the session's behavior.
Source code in src/py1815/profile/device_profile.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | |
build ¶
build(
outstation: DerOutstation,
session: Session,
identity: Identity | None = None,
*,
statuses: Sequence[CommandStatus] = (),
today: date | None = None,
) -> ElementTree.Element
The Device Profile document for an outstation and the session serving it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outstation
|
DerOutstation
|
What is served: the points, each one's scaling, and the event class and deadband in force for it. |
required |
session
|
Session
|
How it is served: addresses, fragment sizes, timeouts, and which of time, freezes, controls and events it was given. |
required |
identity
|
Identity | None
|
The vendor, device and version strings, and where it listens. |
None
|
statuses
|
Sequence[CommandStatus]
|
Command statuses the caller's bindings can return, beyond the ones the session and builder answer with themselves. |
()
|
today
|
date | None
|
The date written into the revision history. |
None
|
Source code in src/py1815/profile/device_profile.py
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 | |
render ¶
The document as text, with the declaration and stylesheet reference.
The stylesheet instruction is what lets a browser, or any XSLT processor, show the profile in the Users Group's own layout when the stylesheet sits beside the file.
Source code in src/py1815/profile/device_profile.py
Device Profile, laid out¶
Reads the XML of any Device Profile document into facts, sections and tables, and writes them as one HTML page. The guide's Reading the document, and printing it says what the page holds.
py1815.profile.device_profile_view ¶
Read a DNP3 Device Profile document and lay it out as a page a person can read.
A Device Profile is XML, and a person reads it as a document: what the device is, how each setting may be configured and how it is configured now, the implementation table, and the point lists. This module reads the XML of any Device Profile, whichever edition of the schema it was written for and whoever wrote it, into a plain model of facts, sections and tables, and writes that model as one standalone HTML page that prints cleanly.
The layout is this library's own. The DNP Users Group publishes a
stylesheet that renders a Device Profile in the Users Group's layout. That
stylesheet and the schema are the Users Group's and are not carried here
(D98), so nothing in this module comes from either. A label is the element's
own name made readable (maxReceivedFragmentSize becomes "Max received
fragment size"), which is not the wording the Users Group prints.
What it does not know. It does not know the schema: it cannot say whether a document is valid, which elements an edition requires, or what an element means. It recognizes a handful of element names that every edition shares, such as the document header, the three parts of a setting and the point lists, and lays out everything else by its shape. An element it does not recognize is still shown, by its label and its text. It does not know the namespace either, since each edition has its own: only local names are compared. It states nothing the document does not: a value that is absent is an empty cell.
The document is untrusted input. A document that is not XML, or is not a
Device Profile, is refused with a :class:DeviceProfileError that says what
is wrong. See :func:read for the limits it enforces.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
DeviceProfileError ¶
Fact
dataclass
¶
Table
dataclass
¶
A table: a title, column headings, and rows of text cells.
Every row has a cell for every column. A cell that states several things separates them with a newline, and a cell the document says nothing for is empty.
Source code in src/py1815/profile/device_profile_view.py
Section
dataclass
¶
Document
dataclass
¶
A Device Profile as a person reads it: a title, header facts and sections.
Source code in src/py1815/profile/device_profile_view.py
describe ¶
Return the document as JSON-compatible data.
Source code in src/py1815/profile/device_profile_view.py
read ¶
Read the XML of a Device Profile document into a :class:Document.
The text is read as it is given. Octets are decoded as the document's own declaration says, UTF-8 when it has none.
The document is untrusted. One that declares a document type is
refused, so no entity can be defined to expand or to fetch anything, and
nothing the document refers to is followed. One of more than
:data:MAX_SIZE characters or octets, more than :data:MAX_ELEMENTS
elements, or elements nested more than :data:MAX_DEPTH deep is refused,
and so is one whose tables would have more than :data:MAX_COLUMNS
columns in one table or :data:MAX_CELLS cells between them.
Raises:
| Type | Description |
|---|---|
DeviceProfileError
|
The text is not XML, is not a Device Profile, or is past one of the limits. The message says which. |
Source code in src/py1815/profile/device_profile_view.py
page ¶
Return a Device Profile as one standalone HTML page.
The page carries its own style, runs no script and refers to nothing outside itself, so it can be saved, mailed or opened from a disk. Every piece of text from the document is escaped. Printed, or saved as PDF from a browser, it repeats each table's headings on every page and keeps a row on one page.
Source code in src/py1815/profile/device_profile_view.py
Simulated DER¶
py1815.profile.der ¶
A simulated DER to stand behind the outstation, so it runs with nothing attached.
A three-phase, storage-coupled generator of modest size, modeled just far enough to make every point the profile requires mean something: power that follows a slowly varying source, a meter that reports it, energy counters that accumulate it, a state of charge that moves when the storage is dispatched, and six DER functions a controlling station can enable and watch take effect -- active power limit, charge/discharge, constant vars, constant power factor, and two that follow a curve of voltage: volt-var and volt-watt.
It is a stand-in, not a model of any product, and it is not where DER
behavior belongs: how a DER carries out a function or follows a curve is out
of scope for this package. What a real integration replaces is this module
and nothing else: it binds the same points to its own device or DER simulator
through the same :class:~py1815.profile.binding.Binding.
The indices below are the profile's fixed ones (IEEE 1815.2 clause 5.2 gives the configuration and system blocks absolute indices), named for what this simulation does with each. Names, units and scaling stay in the tables, which are loaded at run time and not carried here.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
Function
dataclass
¶
One DER function this simulation implements, by the points it uses.
Source code in src/py1815/profile/der.py
Ratings
dataclass
¶
The nameplate of the simulated DER.
Source code in src/py1815/profile/der.py
ReferenceDer ¶
The simulated device: its state, how it moves, and how it is bound.
Source code in src/py1815/profile/der.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 | |
nominal_volts
property
¶
What a curve's percent voltage is a percentage of: the reference plus its offset.
energized
property
¶
Whether the DER is started and connected, and so exchanging power.
volt_watt_limit ¶
The active power volt-watt allows at this voltage, or None when it sets no limit.
Source code in src/py1815/profile/der.py
volt_var_target ¶
The reactive power volt-var calls for at this voltage, or None when it calls for none.
Source code in src/py1815/profile/der.py
available_watts ¶
What the primary source could deliver now: a slow swell around 75%.
step ¶
Move the simulation forward.
Source code in src/py1815/profile/der.py
freeze_interval_seconds ¶
How often the counters freeze, or None when they are not to repeat.
Source code in src/py1815/profile/der.py
bind ¶
This DER's binding: every point it serves, and how.
Source code in src/py1815/profile/der.py
Simulation ¶
A simulated DER and its outstation, advanced together.
Kept apart from any event loop so it can be stepped by a test as easily as by a timer: each step moves the device, freezes the counters when their interval has passed, and buffers whatever changed.
Source code in src/py1815/profile/der.py
advance ¶
Move the device forward, freeze if the interval has passed, and poll.
Source code in src/py1815/profile/der.py
build ¶
Build a simulation: the reference DER bound to an outstation for point_map.
options are passed to :class:~py1815.profile.outstation.DerOutstation,
for example read_only=True or event_capacity=500.
Source code in src/py1815/profile/der.py
Probe¶
py1815.profile.probe ¶
A short reader of the objects in an IEEE 1815.2 outstation's responses.
It was the parser of a probe that polled an outstation once; the poll is now
the master's (:mod:py1815.master.poll). The parser stays because tests of
the outstation read its responses with it: it is a second reading of the same
octets, written apart from :mod:py1815.decode, with its own table of sizes.
It reads the objects the IEEE 1815.2 profile selects and stops, saying so, at the first it does not know: an object's width is the only way to find the one after it.
Copyright 2026 DER Security Corp. Licensed under the Apache License, Version 2.0.
ProbeError ¶
Value
dataclass
¶
parse_objects ¶
The static and event objects of a response body, in the order sent.