Metadata-Version: 2.4
Name: GEiedlib
Version: 0.1.0
Summary: Read the file formats a GE protective relay project is made of: the EnerVista UR Setup .urs settings export
Author: Guilherme Marini
License-Expression: AGPL-3.0-or-later
Project-URL: Homepage, https://github.com/GuilhermeMarini/GElib
Project-URL: Source, https://github.com/GuilhermeMarini/GElib
Keywords: ge,multilin,enervista,ur,urs,t60,protection,relay,commissioning,iec61850
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Manufacturing
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Dynamic: license-file

# GElib

Read the file formats a GE protective relay project is made of — the ones
EnerVista UR Setup produces.

Nothing here talks to a relay, opens a socket, or knows what a web request is.

```python
from pathlib import Path
from gelib import urs

doc = urs.parse(Path("relay.urs").read_bytes())

print(doc.header.model, doc.header.firmware)     # T60 820
print(len(doc.settings), doc.groups())           # 16111 [1, 2, 3, 4, 5, 6]

curve = doc.get("UR_DATA_GROUND_TOC_X_CURVE")
print(curve.raw, curve.decode)                   # 4 IEC Curve B

assert doc.serialize() == Path("relay.urs").read_bytes()   # byte for byte
```

| module | what it reads |
|---|---|
| `gelib.urs` | `.urs`, the settings export: every setting of a UR, at its group, element and index |

## Round-trip is the contract

`parse(data).serialize() == data`, byte for byte — the same rule the SEL side
holds for a `SET_D<n>.TXT`, and for the same reason: these bytes describe what
a protection relay does. It is kept by emitting the original bytes of every
line, which is what carries the INI tail, the `LN` records and the occasional
line that is not text at all. Line endings are stored per line and never per
file: the reference export mixes 101,289 CRLF with 3,381 bare LF, and a
serializer that picks one rewrites 3,381 lines nobody touched.

Measured on that export, a T60's 6.28 MB and 128,867 lines: 16,111 settings
over 1,051 names, all six setting groups, 1,024 FlexLogic entries, 542 `LN`
records. Parsed in 109 ms.

## What this does not do

**It does not write an edited file.** The header ends in a 40-character token
that is very probably a checksum over the file. What it covers is unknown, so
it is carried verbatim and never recomputed, and no write is offered that
might produce a file UR Setup refuses. Settling that needs one experiment in
EnerVista: change a byte, reopen.

**It does not read IEC 61850.** A GE UR's `.icd` and `.cid` are ordinary SCL,
and [SELlib](https://github.com/GuilhermeMarini/sellib)'s `scl` module reads
them today with no GE-specific code — a T60's `.cid` gives up its IEDs, its
GSE control blocks with MAC and APPID, and its data model. A second reader for
one standard format would be two things to keep right instead of one.

## Install

```bash
pip install GEiedlib
```

Three names, and the reason is worth one line: the repository is **GElib**,
the import is **`gelib`**, and the distribution on PyPI is **`GEiedlib`** --
because `GElib` is taken there by an unrelated project (a group equivariant
neural network library) and the match is exact.

## Status

Alpha. The reader is complete and proven against a real 6.3 MB export; there
is no writer, and the section above says why.

## Licence

AGPL-3.0-or-later.
