Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .codespellrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[codespell]
ignore-words-list = ot
skip = **/sunspec/models/smdx/smdx_*.xml,
**/sunspec/models/smdx/CHANGELOG,
**/templates/mydesigner/**
skip = **/templates/mydesigner/**
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **SCEPTRE App**: Type annotations on every app function, `Final` on module constants.
- **SCEPTRE App**: Validation failures raise `error.AppError` instead of calling `sys.exit(1)`, matching the app contract.
- **SCEPTRE App**: `metadata.simulator` matches case-insensitively in both stages, as validation already did; a miscased name used to silently get the default config.
- **Build System**: `package-data` now ships the SunSpec models, the `mydesigner` SCADA tree and the infrastructure table; an installed wheel was missing all three.
- **Build System**: `package-data` ships the `mydesigner` SCADA tree and infrastructure table.

### Removed
- **SCEPTRE App**: The `reg_config` manual register-map path. It was dead code: the app never populated it, so every device always took the automatic numbering branch. Output is unchanged.
- **SCEPTRE App**: SunSpec has been burned away by the SCORCHing heat of 3000 Suns and thus no longer exists in this repo.

### Fixed
- **SCEPTRE App**: With more than one PowerWorld provider, only the last one's `hil_tags` reached the object list, and only the last one's `objects.txt` was written. Tags now aggregate across providers and every PowerWorld provider gets the combined `objects.txt`.
- **SCEPTRE App**: Every `sunspec` inverter raised `KeyError`: the SunSpec register mappings are keyed `PowerDistribution` but received `power-distribution`. The whole protocol was unusable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL

- **SCEPTRE App**: `fep` hosts raised `TypeError`, built without the required `device_subtype`.
- **SCEPTRE App**: Per-device register overrides raised `RuntimeError` in `SceptreMetadataParser`, which popped keys while iterating a live dict view.
- **SCEPTRE App**: A `power-transmission` inverter raised `TypeError`, passing `infrastructure` twice into `Device()`.
Expand Down
8 changes: 3 additions & 5 deletions src/python/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,10 @@ scheduling fields, and write only the resulting JSON to stdout. Extend

## Bundled Runtime Data

The SCEPTRE app ships configuration tables, SunSpec XML models, and the
MyDesigner template tree; Ignition ships a Jython 2.7 template tree; SCORCH
ships scripts and binary payloads. Treat all of it as runtime package data.
The SCEPTRE app ships configuration tables and the MyDesigner template tree;
Ignition ships a Jython 2.7 template tree; SCORCH ships scripts and binary
payloads. Treat all of it as runtime package data.

- Leave `phenix_apps/apps/sceptre/protocols/sunspec/` exactly as-is. It is
excluded from Ruff and codespell for that reason.
- `phenix_apps/apps/ignition/templates/` is Jython 2.7 and is excluded from
Ruff and vulture. Do not modernize it to Python 3 syntax.
- Do not reformat, regenerate, or replace bundled third-party or binary assets
Expand Down
3 changes: 1 addition & 2 deletions src/python/phenix_apps/apps/sceptre/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The **SCEPTRE app** builds industrial control system (ICS/SCADA) experiments. A
simulation *provider* (PowerWorld, PyPower, Simulink, RTDS, ...) models a
physical process; *field devices* expose that process over real industrial
protocols (DNP3, Modbus, BACnet, IEC 60870-5-104, SunSpec); and an *OPC server*,
protocols (DNP3, Modbus, BACnet, IEC 60870-5-104); and an *OPC server*,
*SCADA server*, *HMIs*, *engineer workstations* and a *historian* sit on top of
them the way they would in a real plant.

Expand Down Expand Up @@ -367,5 +367,4 @@ Pre-existing, and pinned by a test rather than silently carried.
| **The ELK restart script points at one provider** | `sceptre_provider_restart.py` is rendered with the last provider's address, so the others cannot be restarted from ELK. Visible in the golden fixture, which has four providers. |
| **Inconsistent mgmt-vlan filtering** | The OPC/SCADA/historian code compares `vlan != "mgmt"` case-sensitively; the field device code lowercases first. `MGMT` is therefore excluded in one and not the other. Both now go through `hosts.non_mgmt(case_sensitive=...)`, so the split is visible at each call site and pinned by `test_hosts.py`. |
| **`Register.addresses` is global state** | Class-level mutable state reset per `FieldDeviceConfig`, so register numbering is order-dependent. Tests reset it via an autouse fixture. |
| **SunSpec register maps are unreachable** | `SunSpecDevice.Register.mappings` is keyed `PowerDistribution`/`PowerTransmission`, but a device carries the lowercase infrastructure name, so any inverter on the `sunspec` protocol raises `KeyError`. Pinned in `infrastructures_golden.txt`. |
| **`reg_config` is never populated** | Nothing in the app builds a manual register map, so the `reg_config` branch in `Register.__init__` is unreachable. Only `protocols/*` remains in the ruff per-file-ignores; everything else in the app is fully linted. |
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
# range the register range for the whole infrastructure, which OpcConfig
# reads. A device type repeats it only when it differs.
# devices device type -> the fields it exposes per field type. An
# inverter's analog-read-write list holds SunSpec model numbers
# rather than field names.
# inverter's analog-read-write list still holds the SunSpec model
# numbers it was built from when SunSpec was supported; on other
# protocols they become registers named after the numbers.
#
# Adding a device type here is enough for validation to accept it and for
# create_device to build it; tests/test_infrastructures.py pins every
Expand Down
11 changes: 0 additions & 11 deletions src/python/phenix_apps/apps/sceptre/configs/registers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

from typing import ClassVar

import phenix_apps.apps.sceptre.protocols.sunspec as sunspec


class Device:
def __init__(
Expand Down Expand Up @@ -36,15 +34,6 @@ def __init__(

def __generate_register_list(self) -> None:
for field_type, fields in self.fields.items():
if self.protocol == "sunspec":
# Only an inverter has a SunSpec map, and its models live in the
# analog-read-write list.
if self.device_type == "inverter" and field_type == "analog-read-write":
sunspec.SunSpecDevice(
self.infrastructure, self.device_name, self.registers
).generate_registers(fields)
continue

self.registers += [
Register(
self.device_name,
Expand Down
35 changes: 8 additions & 27 deletions src/python/phenix_apps/apps/sceptre/field_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"bacnet": "47808-local",
"goose": "61850",
"iec60870-5-104": "2404-local",
"sunspec": "502-local",
}

# Reported as 0 when unused: dashboards expect the key to exist.
Expand Down Expand Up @@ -139,34 +138,16 @@ def fd_servers(self) -> None:

config_file = fd_directory / "config.xml"

sceptre_type = "field-device"
if "sunspec" in parsed.devices_by_protocol:
sceptre_type = "sunspec"

if sceptre_type == "sunspec":
self.render(
"sunspec.mako",
config_file,
name=fd_config.name,
cycle_time=fd_cycle_time,
infra=fd_config.infrastructure_name,
device=fd_config.protocols[0].devices[0],
ipaddr=fd_config.ipaddr,
publish_endpoint=fd_config.publish_endpoint,
server_endpoint=fd_config.server_endpoint,
devname=fd_config.protocols[0].devices[0].registers[0].devname,
)
else:
self.render(
"fd_server.mako",
config_file,
fd_config=fd_config,
logic=fd_logic,
cycle_time=fd_cycle_time,
)
self.render(
"fd_server.mako",
config_file,
fd_config=fd_config,
logic=fd_logic,
cycle_time=fd_cycle_time,
)

self.app.render_sceptre_start(
fd_, name=sceptre_type, needrestart=needrestart
fd_, name="field-device", needrestart=needrestart
)

# What the ELK dashboards read: which ports this device listens on.
Expand Down
1 change: 0 additions & 1 deletion src/python/phenix_apps/apps/sceptre/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ class SceptreMetadataParser:
"dnp3",
"dnp3-serial",
"modbus",
"sunspec",
"iec60870-5-104",
]

Expand Down
Empty file.
Loading
Loading