# AB Legacy — RSLogix symbol & data-table import ablegacy-11 / [#254](https://github.com/dohertj2/lmxopcua/issues/254) — bulk-import RSLogix 500 / 5 symbol exports into the AB Legacy driver. Saves operators from hand-typing every `N7:0` / `F8:12` / `B3:0/5` row of a several-hundred-tag PLC into `appsettings.json`. ## Supported formats — v1 | Format | Status | Notes | |---|---|---| | `.CSV` "Database Export" | **supported** | Header columns `Symbol,Address,Description,DataType,Scope`; quoted fields, doubled-quote escapes, comment lines (`;` / `#`) all honoured | | `.SLC` text export | **supported** | RSLogix 500's "Save As Text" emits the same column shape — point the importer at the file directly | | `.RSS` (RSLogix 500 binary project) | **out of scope** | Proprietary; no parser ships in libplctag or any community project. Export to CSV first | | `.RSP` (RSLogix 5 binary project) | **out of scope** | Same as `.RSS` | The binary `.RSS` / `.RSP` non-goal isn't a "we don't have time" decision — Rockwell's binary format is undocumented + tied to RSLogix's internal page layout, and the only known parsers are commercial IDE plugins. v1 ships with text/CSV only and a clean abstraction (`IRsLogixImporter`) so a binary parser can slot in later without reshaping the call sites. ## CSV column reference | Column | Required | Notes | |---|---|---| | `Symbol` | yes | OPC UA tag name. RSLogix symbols are already stable; the importer uses them verbatim | | `Address` | yes | PCCC address. File letter implies `DataType` (see below); the importer's resolution wins over the CSV's `DataType` column | | `Description` | no | Parsed but currently unused — `AbLegacyTagDefinition` has no `Description` field at the v2 schema layer (see [#248](https://github.com/dohertj2/lmxopcua/issues/248)). Held in the column contract for future schema bumps | | `DataType` | no | RSLogix-supplied (`INT` / `REAL` / `BOOL` / `TIMER` / …). Ignored at import time; the importer derives the type from the file letter | | `Scope` | no | `Global` (default when blank) or `Local:N` for ladder-file-N-scoped tags. Acts as a filter when `--scope` is set on the CLI | ### File-letter → `AbLegacyDataType` mapping | Letter | Example | Maps to | Notes | |---|---|---|---| | `N` | `N7:0` | `Int` (signed 16-bit) | | | `F` | `F8:0` | `Float` (32-bit IEEE-754) | | | `B` | `B3:0/0` | `Bit` | Bit-within-word also forces Bit when `BitIndex` is set | | `L` | `L9:0` | `Long` (signed 32-bit) | SLC 5/05+ only | | `ST` | `ST10:0` | `String` | 82-byte fixed-length + length word | | `T` | `T4:0.ACC` | `TimerElement` | Sub-element implied by `.ACC` / `.PRE` / `.EN` / `.DN` | | `C` | `C5:0.ACC` | `CounterElement` | | | `R` | `R6:0.LEN` | `ControlElement` | | | `A` | `A14:0` | `AnalogInt` | Older hardware | | `I` / `O` / `S` | `I:0/0` | `Int` (or `Bit` with bit suffix) | I/O + status files | | `PD` / `MG` / `PLS` / `BT` | `PD9:0` | `PidElement` etc. | Family-gated; PD/MG common on SLC500 + PLC-5; PLS/BT PLC-5 only | | `RTC` / `HSC` / `DLS` / … | `RTC:0.YR` | `MicroLogixFunctionFile` | MicroLogix 1100 / 1400 only | A bit suffix (`/N`) on any file letter forces `Bit`, regardless of the file letter's normal classification — `N7:0/3` parses as Bit, not Int. ## Scope filter The `Scope` column distinguishes program-scoped tags (`Local:1`, `Local:2`, …) from globals. RSLogix exports usually mix both. The CLI's `--scope` flag (and `ImportOptions.ScopeFilter` at the API level) keeps only the rows whose `Scope` value matches case-insensitively; rows with no `Scope` column count as `Global`. ```powershell # Import only the Global symbols otopcua-ablegacy-cli import-rslogix ` --file plc-export.csv ` --device ab://192.168.1.20/1,0 ` --scope Global # Import only the file-2 program-scope tags otopcua-ablegacy-cli import-rslogix ` --file plc-export.csv ` --device ab://192.168.1.20/1,0 ` --scope Local:2 ``` ## CLI subcommand — `import-rslogix` ```powershell otopcua-ablegacy-cli import-rslogix --help ``` | Flag | Default | Purpose | |---|---|---| | `-f` / `--file` | **required** | Path to the CSV export | | `-d` / `--device` | **required** | Canonical AB Legacy gateway URI every imported tag binds to | | `--emit` | `appsettings-fragment` | `appsettings-fragment` (JSON) or `summary` (one-line counter) | | `-o` / `--output` | stdout | Optional path; when set the JSON fragment is written there + summary line goes to stdout | | `--scope` | none | Optional Scope filter (case-insensitive) | | `--max-rows` | unlimited | Defensive cap on rows imported | | `--strict` | off | Fail-fast on the first malformed row (default permissive: skip + log) | ### `appsettings-fragment` output shape The default `--emit appsettings-fragment` mode writes a JSON object whose `Tags` array is shaped like the `AbLegacyDriverConfigDto.Tags` array — paste straight into the driver-instance config under `Drivers//Config/Tags`. ```json { "Tags": [ { "Name": "MotorSpeed", "DeviceHostAddress": "ab://192.168.1.20/1,0", "Address": "N7:0", "DataType": "Int", "Writable": true }, … ] } ``` ### Summary line `--emit summary` writes a single line: ``` Imported 142 tag(s), skipped 3, errors 0. ``` `Skipped` covers Scope-filter rejections + missing-required-field rows; `errors` covers rows whose `Address` failed to parse as a PCCC address. ## API surface — `IRsLogixImporter` + `AddRsLogixImport` For server-side / bootstrap use-cases the importer is also reachable via: ```csharp using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Import; var options = new AbLegacyDriverOptions { Devices = [new AbLegacyDeviceOptions("ab://192.168.1.20/1,0")], }; // Append imported tags onto an existing options object. var updated = options.AddRsLogixImport( path: @"C:\plc\plc-export.csv", deviceHostAddress: "ab://192.168.1.20/1,0", out var result); // result.ParsedCount / SkippedCount / ErrorCount surface the import telemetry. Console.WriteLine($"Imported {result.ParsedCount} tags"); ``` For a hand-managed importer instance (e.g. supplying a custom `ILogger`) call `new RsLogixSymbolImport(logger).Parse(stream, deviceHostAddress, opts)` directly. ## Operational notes - The importer is **additive** — `AddRsLogixImport` concatenates onto the existing `Tags` list rather than replacing it. Hand-rolled tags (system-status variables, computed fields the operator added by hand) survive a re-import. - Re-imports are not idempotent today — calling `AddRsLogixImport` twice will produce duplicate tag rows. Operators are expected to either start from a clean options object or de-duplicate themselves; a future schema rev may add a `replace=true` switch. - Description metadata is dropped on the floor — see the column reference above. When [#248](https://github.com/dohertj2/lmxopcua/issues/248) lands a `Description` field on `AbLegacyTagDefinition` the importer will start populating it without further changes to the CSV contract.