@mokouliszt/mel-device 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [English](README.md)
4
4
 
5
- 三菱電機 MELSEC / MXコントローラ および MELFAロボットコントローラのデバイス表記を、シリーズとCPU/コントローラ型名を含めて検証する、依存ライブラリなしのnpmパッケージです。
5
+ 三菱電機 MELSEC / MXコントローラ、MELFAロボットコントローラ、およびFRインバータのシーケンス機能のデバイス表記を、シリーズとCPU/コントローラ/インバータ型名を含めて検証する、依存ライブラリなしのnpmパッケージです。
6
6
 
7
- 対応シリーズは iQ-R、iQ-F、MX-R、MX-F、CR800-R/D/Qです。ES Modules と CommonJS の両方から利用できます。
7
+ 対応シリーズは iQ-R、iQ-F、MX-R、MX-F、CR800-R/D/Q、および FR-A800/A800 Plus/F800/E800 インバータ(シーケンス機能とSLMPデバイステーブルの両方)です。ES Modules と CommonJS の両方から利用できます。
8
8
 
9
9
  ## インストール
10
10
 
@@ -46,7 +46,7 @@ isValidDevice("U3E0\\G524287", {
46
46
 
47
47
  `analyzeDevice` は、単なる `true` / `false` ではなく、正規化後の型名、解釈したデバイス、失敗理由、参照マニュアルとページ、実機設定への依存有無を返します。入力欄のバリデーションにはこちらを推奨します。
48
48
 
49
- `model`を省略できるのは、`series`に`CR800-R`、`CR800-D`、`CR800-Q`のいずれかを直接指定した場合だけです。その他のシリーズでは従来どおり型名を指定してください。
49
+ `model`を省略できるのは、`series`に`CR800-R`、`CR800-D`、`CR800-Q`、`FR-A800`、`FR-A800-E`、`FR-F800`、`FR-E800`のような単一機種のショートハンドを直接指定した場合です。`iQ-R`、`iQ-F`、`MX-R`、`MX-F`、`CR800`、`FR800`では従来どおり型名を指定してください。
50
50
 
51
51
  ## 判定モード
52
52
 
@@ -59,7 +59,7 @@ PLCのM/D等はCPUパラメータで点数を変更できるため、型名だ
59
59
  | `configured` | 実機プロジェクトの厳密判定 | `configuredPoints` に渡した実際の点数で判定。可変デバイスの点数が未指定なら `MISSING_CONFIGURATION`。 |
60
60
  | `syntax` | エディタ入力途中・構文チェック | 型名が対応するデバイス文法のみを確認し、点数範囲を無視。 |
61
61
 
62
- CR800-R/D/Qのデバイス範囲はマニュアル上固定されているため、`default`、`maximum`、`configured`はいずれも同じ固定範囲を使用します。
62
+ CR800-R/D/QおよびFRインバータのデバイス範囲はマニュアル上固定されているため、これらのシリーズでは`configuredPoints`を参照しません。ただしインバータでは、`maximum`が後述のSERIAL依存のT/C 32点拡張と未割付のST点数を許可します。インバータが受け付ける点数指定は`configuredPoints.ST`のみです。
63
63
 
64
64
  ```js
65
65
  isValidDevice("D99", {
@@ -97,13 +97,26 @@ analyzeDevice(" u1¥g0 ", {
97
97
  - ワードデバイスのビット指定: `D0.A`
98
98
  - タイマ/カウンタの接点・コイル・現在値: `TS0`, `TC0`, `TN0`, `LCS0` など
99
99
  - ユニットアクセス: `U1\G0`
100
- - CPUバッファメモリアクセス: `U3E0\G0`
100
+ - CPUバッファメモリアクセス: `U3E0\G0`, `U3E0\HG0`
101
101
  - リンクダイレクト: `J1\X0`, `J1\W0`
102
102
  - 桁指定: `K4M100`
103
103
  - 間接指定: `@D10`, `@D10.8`
104
104
  - インデックス修飾: `D10Z2.0`, `D10.8Z2`, `D0LZ0`
105
105
  - ローカルデバイス: `#M0`(対応シリーズ/デバイスのみ)
106
106
 
107
+ ### ユニットアクセス/CPUバッファメモリアクセスの範囲
108
+
109
+ 先頭入出力番号、CPU指定、アドレスをそれぞれマニュアル記載の範囲で判定します。バッファメモリ領域 `G` と定周期通信エリア `HG` は上限が別で、`HG` はMX-R/MX-Fのマニュアルには記載がありません。
110
+
111
+ | シリーズ | `Un\G` 先頭入出力番号 | `U3En\G` | `U3En\HG` |
112
+ | --- | --- | --- | --- |
113
+ | iQ-R | `U00`〜`UFF` | `U3E0`〜`U3E3`、アドレス `0`〜`268435455` | `U3E0`〜`U3E3`、アドレス `0`〜`12287` |
114
+ | iQ-F | マニュアルに記載なし(判定しない) | 非対応 | 非対応 |
115
+ | MX-R | `U00`〜`UFF` | `U3E0`、アドレス `0`〜`524287` | マニュアルに記載なし |
116
+ | MX-F | `U01`〜`UFE` | `U3E0`、アドレス `0`〜`16809983` | マニュアルに記載なし |
117
+
118
+ CR800は独自の固定テーブルで判定します(下記参照)。
119
+
107
120
  ## MELFA CR800
108
121
 
109
122
  推奨表記は、親シリーズとコントローラ種別を分ける方法です。
@@ -169,6 +182,123 @@ CR800-Qの表6-16にある`U3En\G512-G1023`は、表6-13の固定範囲`G10000-G
169
182
 
170
183
  定数(`K100`, `HFF`, 実数・文字列)は「デバイス」ではないため判定対象外です。ラベル、構造体メンバ、SLMPのバイナリデバイスコードも対象外です。
171
184
 
185
+ ## FRインバータ シーケンス機能
186
+
187
+ FR-A800/A800 Plus/F800/E800のシーケンス機能はデバイステーブルが1種類に固定されているため、シリーズを`FR800`、インバータのファミリを型名として扱います。単一ファミリのショートハンドを`series`に直接指定した場合は`model`を省略できます。
188
+
189
+ ```js
190
+ isValidDevice("X8F", { series: "FR-A800" }); // true(Xは16進で144点)
191
+ isValidDevice("X90", { series: "FR-A800" }); // false
192
+
193
+ analyzeDevice("P100", { series: "FR-E800" });
194
+ // valid: true, configurationDependent: true(PデバイスはSERIALの製造年月に依存)
195
+
196
+ analyzeDevice("P100", { series: "FR-A800" }).code; // "DEVICE_NOT_SUPPORTED"
197
+ ```
198
+
199
+ | デバイス | FR-A800 / FR-A800 Plus / FR-F800 | FR-E800 | 基数 |
200
+ | --- | --- | --- | --- |
201
+ | X、Y | `X0~X8F`、`Y0~Y8F`(各144点) | 同左 | 16進 |
202
+ | M | `M0~M127` | 同左 | 10進 |
203
+ | L | 点数なし(シーケンスパラメータで設定可能だがラッチしない) | 同左 | - |
204
+ | T、C | `0~15`、または下記拡張で`0~31` | `0~15` | 10進 |
205
+ | ST | 初期値0点。割付けにより`0~15`、下記拡張で`0~31`まで | 初期値0点。割付けにより`0~15`まで | 10進 |
206
+ | D | `D0~D255` | 同左 | 10進 |
207
+ | P | 記載なし | `P0~P127`、`P2048~P2175` | 10進 |
208
+ | SM、SD | `0~2047`(機能制限あり) | 同左 | 10進 |
209
+ | N | `N0~N14`(MC/MCRのネスティング) | 同左 | 10進 |
210
+
211
+ ### SERIAL(製造年月)依存機能
212
+
213
+ マニュアルには、インバータ定格名板のSERIALが示す製造年月によって対応可否が変わるデバイス機能が2つ記載されています。型名だけでは断定できないため、暗黙に許可せず別扱いで返します。
214
+
215
+ ```js
216
+ analyzeDevice("T16", { series: "FR-A800" });
217
+ // valid: false, code: "REQUIRES_SERIAL_SUPPORT", suggestedMode: "maximum"
218
+
219
+ isValidDevice("T16", { series: "FR-A800", mode: "maximum" }); // true(設定依存)
220
+ isValidDevice("T16", { series: "FR-A800", frFeatures: { extendedTimerPoints: true } }); // true(確定)
221
+ isValidDevice("T16", { series: "FR-A800", frFeatures: { extendedTimerPoints: false } }); // false
222
+ ```
223
+
224
+ | `frFeatures` | 対象 | 意味 |
225
+ | --- | --- | --- |
226
+ | `extendedTimerPoints` | FR-A800、FR-A800-E、FR-A800-CRN、FR-A800-LC、FR-F800、FR-F800-E | T/ST/Cの32点対応 |
227
+ | `pointerDevice` | FR-E800系 | Pデバイス256点対応 |
228
+
229
+ マニュアルが対応と記載していない型名に対して`true`を指定した場合は、範囲を黙って広げず`INVALID_FR_FEATURES`を返します。
230
+
231
+ ### 積算タイマは初期値0点
232
+
233
+ デバイス一覧では積算タイマSTの初期値が0点であり、シーケンスパラメータで割り付けて初めて使用できます。そのためPLCシリーズの点数変更可能デバイスと同じ扱いにしています。
234
+
235
+ ```js
236
+ analyzeDevice("ST0", { series: "FR-A800" });
237
+ // valid: false, code: "REQUIRES_CONFIGURATION", suggestedMode: "maximum"
238
+
239
+ isValidDevice("ST15", { series: "FR-A800", mode: "maximum" }); // true(設定依存)
240
+ isValidDevice("ST15", { series: "FR-A800", mode: "configured", configuredPoints: { ST: 16 } }); // true
241
+ isValidDevice("ST16", { series: "FR-A800", mode: "configured", configuredPoints: { ST: 16 } }); // false
242
+ ```
243
+
244
+ `configuredPoints.ST`の上限は16点、上記の32点拡張対応機種では32点です。
245
+
246
+ ### SLMPデバイステーブル
247
+
248
+ Ethernet仕様のインバータは、外部のSLMPクライアントに対してシーケンス機能とは別のデバイステーブルを公開しています。`frAccess: "slmp"`を指定するとそちらで判定し、結果にSLMPのデバイスコードとアクセス単位が付きます。
249
+
250
+ ```js
251
+ analyzeDevice("X7F", { series: "FR-A800-E", frAccess: "slmp" });
252
+ // valid: true, deviceCode: "H9C", deviceUnit: "bit"
253
+
254
+ isValidDevice("X80", { series: "FR-A800-E", frAccess: "slmp" }); // false(SLMPはH7Fまで)
255
+ isValidDevice("X80", { series: "FR-A800-E" }); // true (シーケンス機能はH8Fまで)
256
+ ```
257
+
258
+ | デバイス | コード | 単位 | 範囲 |
259
+ | --- | --- | --- | --- |
260
+ | SM | `H91` | ビット | `0~2047` |
261
+ | SD | `HA9` | ワード | `0~2047` |
262
+ | X | `H9C` | ビット | `H0~H7F` |
263
+ | Y | `H9D` | ビット | `H0~H7F` |
264
+ | M | `H90` | ビット | `0~127` |
265
+ | D | `HA8` | ワード | `0~255` |
266
+ | W | `HB4` | ワード | `0~8191`(FR-E800のEthernet仕様品のみ) |
267
+ | `TS` / `TC` / `TN` | `HC1` / `HC0` / `HC2` | ビット / ビット / ワード | `0~15` |
268
+ | `STS` / `STC` / `STN`(`SS` / `SC` / `SN`) | `HC7` / `HC6` / `HC8` | ビット / ビット / ワード | 初期値0点、割付けにより`0~15`まで |
269
+ | `CS` / `CC` / `CN` | `HC4` / `HC3` / `HC5` | ビット / ビット / ワード | `0~15` |
270
+
271
+ 補足:
272
+
273
+ - SLMPアクセスの記載はEthernet仕様品のみです。それ以外の型名では`MODEL_NOT_SUPPORTED`を返します。
274
+ - SLMPは接点・コイル・現在値を別デバイスとして指定するため、素の`T0`は無効とし、`TS0`/`TC0`/`TN0`を案内します。
275
+ - 積算タイマの各部は、FR-A800-E/F800-Eのマニュアルでは`STS`/`STC`/`STN`、FR-E800のマニュアルでは`SS`/`SC`/`SN`と表記されます。両方を受け付け、選択した型名のマニュアル表記を警告で示します。
276
+ - 両マニュアルのSLMP表はT/CとSTを16点で頭打ちにしているため、`extendedTimerPoints`では広がりません。
277
+ - リンクレジスタ`W`はパラメータとモニタデータに対応し、PLCシリーズと異なり10進です。`parseDevice("W999", { series: "FR-E800-E" })`のアドレスは999になります。
278
+ - P、N、L、桁指定はSLMPデバイステーブルに含まれません。
279
+
280
+ ### 桁指定と入力デバイス
281
+
282
+ 桁指定はビットデバイスX、Y、Mのみに記載があります。本パッケージは、指定した点数がデバイス範囲内に収まるかも判定します。
283
+
284
+ ```js
285
+ isValidDevice("K4X80", { series: "FR-A800" }); // true(X80~X8F)
286
+ analyzeDevice("K4X8C", { series: "FR-A800" }).code; // "DIGIT_RANGE_OVERFLOW"
287
+ analyzeDevice("K4D0", { series: "FR-A800" }).code; // "DIGIT_MODIFIER_NOT_SUPPORTED"
288
+ ```
289
+
290
+ 入力デバイスXは毎スキャン外部端子やネットワークからリフレッシュされるため、プログラムから駆動できません。`analyzeDevice`はCR800と同じフィールドでこれを返します。
291
+
292
+ ```js
293
+ analyzeDevice("X0", { series: "FR-A800", operation: "write" });
294
+ // valid: true, access: "read-only", operationAllowed: false
295
+ // warnings: インバータ運転状態制御にはSM1200/SM1255、SD1148/SD1149を使用する旨
296
+ ```
297
+
298
+ X/Yの`30H`以降はCC-Linkリモート入出力(`30H~3FH`)とEthernetインバータ間リンク入出力(`40H~8FH`)の領域であるため、領域名を含む警告とともに`configurationDependent: true`を返します。
299
+
300
+ ビット指定、間接指定、インデックス修飾、ローカルデバイス、ユニットアクセス、および`TS`/`TC`/`TN`形式の接点・コイル・現在値表記は、シーケンス機能のマニュアルに記載がないため保守的に無効とします。
301
+
172
302
  ## API
173
303
 
174
304
  ### `isValidDevice(value, options): boolean`
@@ -206,6 +336,7 @@ CR800-Qの表6-16にある`U3En\G512-G1023`は、表6-13の固定範囲`G10000-G
206
336
  - MX-R: MXR300-16/-32/-64、MXR500-128/-256
207
337
  - MX-F: MXF100系のマニュアル対象10機種(`MXF100` をファミリ別名として受付)
208
338
  - MELFA CR800: CR800-R、CR800-D、CR800-Q
339
+ - FRインバータ(`FR800`): FR-A800、FR-A800-E、FR-A800-P、FR-A800-CRN、FR-A800-LC、FR-A800-Plus、FR-F800、FR-F800-E、FR-E800、FR-E800-E、FR-E800-SCE、FR-E800-NC、FR-E806
209
340
 
210
341
  ## 資料
211
342
 
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [日本語](README.ja.md)
4
4
 
5
- A dependency-free npm package for validating Mitsubishi Electric MELSEC/MX controller and MELFA robot controller device notation against a specified series and CPU/controller model.
5
+ A dependency-free npm package for validating Mitsubishi Electric MELSEC/MX controller, MELFA robot controller and FR inverter sequence-function device notation against a specified series and CPU/controller/inverter model.
6
6
 
7
- Supported series are iQ-R, iQ-F, MX-R, MX-F, and CR800-R/D/Q. The package supports both ES Modules and CommonJS.
7
+ Supported series are iQ-R, iQ-F, MX-R, MX-F, CR800-R/D/Q, and the FR-A800/A800 Plus/F800/E800 inverters, covering both their sequence function and their SLMP device table. The package supports both ES Modules and CommonJS.
8
8
 
9
9
  ## Installation
10
10
 
@@ -46,7 +46,7 @@ isValidDevice("U3E0\\G524287", {
46
46
 
47
47
  Instead of returning only `true` or `false`, `analyzeDevice` reports the normalized model, parsed device, reason for failure, referenced manual and page, and whether the result depends on the actual hardware configuration. It is the recommended API for validating user input.
48
48
 
49
- The `model` option may be omitted only when `series` is set directly to `CR800-R`, `CR800-D`, or `CR800-Q`. A model must still be specified for all other series.
49
+ The `model` option may be omitted when `series` is set directly to a single-model shorthand such as `CR800-R`, `CR800-D`, `CR800-Q`, `FR-A800`, `FR-A800-E`, `FR-F800`, or `FR-E800`. A model must still be specified for `iQ-R`, `iQ-F`, `MX-R`, `MX-F`, `CR800`, and `FR800`.
50
50
 
51
51
  ## Validation modes
52
52
 
@@ -59,7 +59,7 @@ The number of available points for PLC devices such as M and D can be changed in
59
59
  | `configured` | Exact validation for an actual project | Validates against the actual point counts supplied in `configuredPoints`. Returns `MISSING_CONFIGURATION` if the point count for a configurable device is omitted. |
60
60
  | `syntax` | In-progress editor input and syntax checking | Checks only whether the model supports the device syntax and ignores point-count ranges. |
61
61
 
62
- The device ranges for CR800-R/D/Q are fixed in the manuals, so `default`, `maximum`, and `configured` all use the same fixed ranges.
62
+ The device ranges for CR800-R/D/Q and for the FR inverter sequence function are fixed in the manuals, so `configuredPoints` is ignored for those series. `maximum` still differs for the inverters, because it opts into the SERIAL-dependent 32-point T/C extension and the unallocated ST points described below. `configuredPoints.ST` is the one point count the inverters accept.
63
63
 
64
64
  ```js
65
65
  isValidDevice("D99", {
@@ -97,13 +97,26 @@ analyzeDevice(" u1¥g0 ", {
97
97
  - Bit specification for word devices: `D0.A`
98
98
  - Timer/counter contacts, coils, and current values: `TS0`, `TC0`, `TN0`, `LCS0`, etc.
99
99
  - Module access: `U1\G0`
100
- - CPU buffer memory access: `U3E0\G0`
100
+ - CPU buffer memory access: `U3E0\G0`, `U3E0\HG0`
101
101
  - Link direct devices: `J1\X0`, `J1\W0`
102
102
  - Digit specification: `K4M100`
103
103
  - Indirect specification: `@D10`, `@D10.8`
104
104
  - Index modification: `D10Z2.0`, `D10.8Z2`, `D0LZ0`
105
105
  - Local devices: `#M0` (supported series/devices only)
106
106
 
107
+ ### Module and CPU buffer access limits
108
+
109
+ The head I/O number, the CPU selector, and the address are all validated against the ranges printed in the manuals. The `G` buffer-memory area and the `HG` cyclic-transmission area have separate limits, and the `HG` area is not listed for MX-R or MX-F at all.
110
+
111
+ | Series | `Un\G` head I/O number | `U3En\G` | `U3En\HG` |
112
+ | --- | --- | --- | --- |
113
+ | iQ-R | `U00`-`UFF` | `U3E0`-`U3E3`, address `0`-`268435455` | `U3E0`-`U3E3`, address `0`-`12287` |
114
+ | iQ-F | not stated in the manual (unchecked) | not supported | not supported |
115
+ | MX-R | `U00`-`UFF` | `U3E0`, address `0`-`524287` | not listed in the manual |
116
+ | MX-F | `U01`-`UFE` | `U3E0`, address `0`-`16809983` | not listed in the manual |
117
+
118
+ CR800 uses its own fixed table; see the section below.
119
+
107
120
  ## MELFA CR800
108
121
 
109
122
  The recommended notation separates the parent series from the controller type.
@@ -169,6 +182,123 @@ Bit specifications, digit specifications, indirect specifications, index modific
169
182
 
170
183
  Constants (`K100`, `HFF`, real numbers, and strings) are outside the validation scope because they are not devices. Labels, structure members, and SLMP binary device codes are also outside the scope.
171
184
 
185
+ ## FR inverter sequence function
186
+
187
+ The FR-A800/A800 Plus/F800/E800 sequence function has one fixed device table, so the series is `FR800` and the inverter family is the model. Single-family shorthands are accepted as `series`, in which case `model` may be omitted.
188
+
189
+ ```js
190
+ isValidDevice("X8F", { series: "FR-A800" }); // true (X is hexadecimal, 144 points)
191
+ isValidDevice("X90", { series: "FR-A800" }); // false
192
+
193
+ analyzeDevice("P100", { series: "FR-E800" });
194
+ // valid: true, configurationDependent: true (the P device depends on the SERIAL production month)
195
+
196
+ analyzeDevice("P100", { series: "FR-A800" }).code; // "DEVICE_NOT_SUPPORTED"
197
+ ```
198
+
199
+ | Device | FR-A800 / FR-A800 Plus / FR-F800 | FR-E800 | Radix |
200
+ | --- | --- | --- | --- |
201
+ | X, Y | `X0-X8F`, `Y0-Y8F` (144 points each) | same | hexadecimal |
202
+ | M | `M0-M127` | same | decimal |
203
+ | L | no points (a latch range can be set but nothing latches) | same | - |
204
+ | T, C | `0-15`, or `0-31` with the extension below | `0-15` | decimal |
205
+ | ST | 0 points by default; allocatable up to `0-15`, or `0-31` with the extension below | 0 points by default; allocatable up to `0-15` | decimal |
206
+ | D | `D0-D255` | same | decimal |
207
+ | P | not listed | `P0-P127`, `P2048-P2175` | decimal |
208
+ | SM, SD | `0-2047` (not every number has a function) | same | decimal |
209
+ | N | `N0-N14` (MC/MCR nesting) | same | decimal |
210
+
211
+ ### SERIAL-dependent features
212
+
213
+ The manual lists two device features whose availability depends on the inverter production month printed on the SERIAL plate. Because the model name alone cannot decide them, they are reported separately instead of being silently allowed.
214
+
215
+ ```js
216
+ analyzeDevice("T16", { series: "FR-A800" });
217
+ // valid: false, code: "REQUIRES_SERIAL_SUPPORT", suggestedMode: "maximum"
218
+
219
+ isValidDevice("T16", { series: "FR-A800", mode: "maximum" }); // true (configurationDependent)
220
+ isValidDevice("T16", { series: "FR-A800", frFeatures: { extendedTimerPoints: true } }); // true (asserted)
221
+ isValidDevice("T16", { series: "FR-A800", frFeatures: { extendedTimerPoints: false } }); // false
222
+ ```
223
+
224
+ | `frFeatures` | Applies to | Meaning |
225
+ | --- | --- | --- |
226
+ | `extendedTimerPoints` | FR-A800, FR-A800-E, FR-A800-CRN, FR-A800-LC, FR-F800, FR-F800-E | 32-point T/ST/C extension |
227
+ | `pointerDevice` | FR-E800 family | 256-point P pointer device |
228
+
229
+ Setting a feature to `true` for a model the manual does not list returns `INVALID_FR_FEATURES` rather than quietly widening the range.
230
+
231
+ ### The accumulating timer starts with no points
232
+
233
+ The device list gives the accumulating timer ST a default of 0 points, so it is only usable once the sequence parameters allocate it. It is therefore treated like the configurable devices of the PLC series rather than like a fixed range.
234
+
235
+ ```js
236
+ analyzeDevice("ST0", { series: "FR-A800" });
237
+ // valid: false, code: "REQUIRES_CONFIGURATION", suggestedMode: "maximum"
238
+
239
+ isValidDevice("ST15", { series: "FR-A800", mode: "maximum" }); // true (configurationDependent)
240
+ isValidDevice("ST15", { series: "FR-A800", mode: "configured", configuredPoints: { ST: 16 } }); // true
241
+ isValidDevice("ST16", { series: "FR-A800", mode: "configured", configuredPoints: { ST: 16 } }); // false
242
+ ```
243
+
244
+ The ceiling for `configuredPoints.ST` is 16 points, or 32 on a model that has the extension above.
245
+
246
+ ### SLMP device table
247
+
248
+ The Ethernet inverters expose a different device table to external SLMP clients. Pass `frAccess: "slmp"` to validate against it; the result then also carries the SLMP device code and access unit.
249
+
250
+ ```js
251
+ analyzeDevice("X7F", { series: "FR-A800-E", frAccess: "slmp" });
252
+ // valid: true, deviceCode: "H9C", deviceUnit: "bit"
253
+
254
+ isValidDevice("X80", { series: "FR-A800-E", frAccess: "slmp" }); // false (the SLMP table stops at H7F)
255
+ isValidDevice("X80", { series: "FR-A800-E" }); // true (the sequence map runs to H8F)
256
+ ```
257
+
258
+ | Device | Code | Unit | Range |
259
+ | --- | --- | --- | --- |
260
+ | SM | `H91` | bit | `0-2047` |
261
+ | SD | `HA9` | word | `0-2047` |
262
+ | X | `H9C` | bit | `H0-H7F` |
263
+ | Y | `H9D` | bit | `H0-H7F` |
264
+ | M | `H90` | bit | `0-127` |
265
+ | D | `HA8` | word | `0-255` |
266
+ | W | `HB4` | word | `0-8191` (FR-E800 Ethernet models only) |
267
+ | `TS` / `TC` / `TN` | `HC1` / `HC0` / `HC2` | bit / bit / word | `0-15` |
268
+ | `STS` / `STC` / `STN` (`SS` / `SC` / `SN`) | `HC7` / `HC6` / `HC8` | bit / bit / word | 0 points by default, allocatable up to `0-15` |
269
+ | `CS` / `CC` / `CN` | `HC4` / `HC3` / `HC5` | bit / bit / word | `0-15` |
270
+
271
+ Points worth knowing:
272
+
273
+ - SLMP access is documented for the Ethernet models only. Any other model returns `MODEL_NOT_SUPPORTED`.
274
+ - The SLMP table addresses the contact, coil, and current value separately, so a bare `T0` is rejected with a message naming `TS0`, `TC0`, and `TN0`.
275
+ - The accumulating-timer parts are spelled `STS`/`STC`/`STN` in the FR-A800-E/F800-E manual and `SS`/`SC`/`SN` in the FR-E800 manual. Both spellings are accepted, and a warning names the spelling the selected model's manual uses.
276
+ - Both SLMP tables cap T/C and ST at 16 points, so `extendedTimerPoints` does not widen them.
277
+ - The link register `W` maps to parameters and monitor data and is decimal, not hexadecimal as on the PLC series. `parseDevice("W999", { series: "FR-E800-E" })` gives address 999.
278
+ - P, N, L, and digit designation are not part of the SLMP device table.
279
+
280
+ ### Digit designation and input devices
281
+
282
+ Digit designation is documented for the bit devices X, Y, and M only, and the package also checks that the designated points fit inside the device range.
283
+
284
+ ```js
285
+ isValidDevice("K4X80", { series: "FR-A800" }); // true (X80-X8F)
286
+ analyzeDevice("K4X8C", { series: "FR-A800" }).code; // "DIGIT_RANGE_OVERFLOW"
287
+ analyzeDevice("K4D0", { series: "FR-A800" }).code; // "DIGIT_MODIFIER_NOT_SUPPORTED"
288
+ ```
289
+
290
+ Input device X is refreshed from the terminal or network on every scan, so a program cannot drive it. `analyzeDevice` reports this through the same fields used for CR800:
291
+
292
+ ```js
293
+ analyzeDevice("X0", { series: "FR-A800", operation: "write" });
294
+ // valid: true, access: "read-only", operationAllowed: false
295
+ // warnings: use SM1200/SM1255 and SD1148/SD1149 for inverter operation control
296
+ ```
297
+
298
+ X/Y addresses from `30H` upward belong to the CC-Link remote area (`30H-3FH`) and the Ethernet inverter-to-inverter link area (`40H-8FH`), so they return `configurationDependent: true` with a warning naming the area.
299
+
300
+ Bit selection, indirect specification, index modification, local devices, module access, and the `TS`/`TC`/`TN` style contact/coil/current notation are not documented for the inverter sequence function and are conservatively rejected.
301
+
172
302
  ## API
173
303
 
174
304
  ### `isValidDevice(value, options): boolean`
@@ -206,6 +336,7 @@ Returns the list of models covered by the manuals.
206
336
  - MX-R: MXR300-16/-32/-64 and MXR500-128/-256
207
337
  - MX-F: the 10 models covered by the MXF100-series manual (`MXF100` is accepted as a family alias)
208
338
  - MELFA CR800: CR800-R, CR800-D, and CR800-Q
339
+ - FR inverters (`FR800`): FR-A800, FR-A800-E, FR-A800-P, FR-A800-CRN, FR-A800-LC, FR-A800-Plus, FR-F800, FR-F800-E, FR-E800, FR-E800-E, FR-E800-SCE, FR-E800-NC, and FR-E806
209
340
 
210
341
  ## References
211
342
 
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const SERIES = ["iQ-R", "iQ-F", "MX-R", "MX-F", "CR800"];
3
+ const SERIES = ["iQ-R", "iQ-F", "MX-R", "MX-F", "CR800", "FR800"];
4
4
 
5
5
  const IQ_R_MODELS = [
6
6
  "R00CPU", "R01CPU", "R02CPU", "R04CPU", "R04ENCPU", "R08CPU", "R08ENCPU",
@@ -13,13 +13,19 @@ const MX_F_MODELS = [
13
13
  "MXF100-Y32N", "MXF100-Y32P", "MXF100-H32N", "MXF100-H32P", "MXF100-Y16R"
14
14
  ];
15
15
  const CR800_MODELS = ["CR800-R", "CR800-D", "CR800-Q"];
16
+ const FR800_MODELS = [
17
+ "FR-A800", "FR-A800-E", "FR-A800-P", "FR-A800-CRN", "FR-A800-LC", "FR-A800-Plus",
18
+ "FR-F800", "FR-F800-E",
19
+ "FR-E800", "FR-E800-E", "FR-E800-SCE", "FR-E800-NC", "FR-E806"
20
+ ];
16
21
 
17
22
  const MODEL_LISTS = {
18
23
  "iQ-R": IQ_R_MODELS,
19
24
  "iQ-F": IQ_F_MODELS,
20
25
  "MX-R": MX_R_MODELS,
21
26
  "MX-F": MX_F_MODELS,
22
- CR800: CR800_MODELS
27
+ CR800: CR800_MODELS,
28
+ FR800: FR800_MODELS
23
29
  };
24
30
 
25
31
  const BIT_PREFIXES = new Set(["X", "DX", "Y", "DY", "M", "L", "B", "F", "SB", "V", "S", "SM", "FX", "FY"]);
@@ -109,7 +115,30 @@ const SOURCES = {
109
115
  "iQ-R": { manual: "SH-082487-J", pages: [65, 408, 409, 410, 435, 436] },
110
116
  "MX-R": { manual: "SH-082640-D", pages: [350, 351, 352, 380, 381] },
111
117
  "MX-F": { manual: "SH-082633-E", pages: [277, 416, 417, 418] },
112
- CR800: { manual: "BFP-A3477-AB", pages: [489, 490, 491, 618, 619, 654, 655, 656, 657] }
118
+ CR800: { manual: "BFP-A3477-AB", pages: [489, 490, 491, 618, 619, 654, 655, 656, 657] },
119
+ FR800: { manual: "IB-0600491-Q", pages: [8, 11, 22, 23, 24, 25, 26, 27, 28, 107, 125, 126, 157] }
120
+ };
121
+
122
+ // CPU buffer memory access devices (U3En\G, U3En\HG).
123
+ // iQ-R SH-082487-J p.433 lists CPU No.1-4 as 3E0-3E3 and separates the G area
124
+ // (0-268435455) from the HG cyclic-transmission area (max 12288 points, p.408).
125
+ // MX-R SH-082640-D p.377/378 and MX-F SH-082633-E p.442/443 only document the
126
+ // 3E0 CPU function part, and neither manual mentions the HG area at all.
127
+ const CPU_BUFFER_RULES = {
128
+ "iQ-R": { units: [0, 1, 2, 3], G: 268435455, HG: 12287 },
129
+ "MX-R": { units: [0], G: 524287 },
130
+ "MX-F": { units: [0], G: 16809983 }
131
+ };
132
+
133
+ // Unit access devices (Un\G). unitMin/unitMax are the documented head I/O
134
+ // number ranges: iQ-R p.432 and MX-R p.376 give 00H-FFH, MX-F p.441 gives
135
+ // 01H-FEH. The iQ-F manual (p.68) does not state a unit-number range, so the
136
+ // unit number is left unchecked for that series.
137
+ const UNIT_ACCESS_RULES = {
138
+ "iQ-R": { unitMin: 0x00, unitMax: 0xff, address: 268435455 },
139
+ "MX-R": { unitMin: 0x00, unitMax: 0xff, address: 268435455 },
140
+ "MX-F": { unitMin: 0x01, unitMax: 0xfe, address: 268435455 },
141
+ "iQ-F": { unitMin: null, unitMax: null, address: 268435455 }
113
142
  };
114
143
 
115
144
  const CR800_RULES = {
@@ -144,6 +173,99 @@ const CR800_ALLOCATION_LABELS = {
144
173
  ccLinkIef: "CC-Link IE Field"
145
174
  };
146
175
 
176
+ // Inverter sequence function (FR-A800/A800 Plus/F800/E800).
177
+ // IB-0600491-Q p.107 (3.9.1 device list) fixes every point count; p.8 lists the
178
+ // features whose availability depends on the SERIAL production date; p.22-28
179
+ // give the X/Y device map, p.125-126 the K1-K8 digit designation rules for
180
+ // X/Y/M, and p.157 the N0-N14 master-control nesting range.
181
+ const FR_COMMON_LIMITS = { X: 0x8f, Y: 0x8f, M: 127, D: 255, SM: 2047, SD: 2047, N: 14 };
182
+ const FR_POINTER_RANGES = [[0, 127], [2048, 2175]];
183
+ const FR_DIGIT_PREFIXES = new Set(["X", "Y", "M"]);
184
+ const FR_FEATURE_NAMES = new Set(["extendedTimerPoints", "pointerDevice"]);
185
+
186
+ // timerPoints: [base, extended]. The 32-point T/ST/C extension is only listed
187
+ // for FR-A800 (excluding FR-A800-P), FR-A800 Plus (FR-A800-CRN/LC) and FR-F800,
188
+ // and only from the January 2021 production month onward (p.8).
189
+ // timerPoints: [base, SERIAL-extended] for T and C. ST always defaults to 0
190
+ // points (p.11 note *8) and is allocated by the sequence parameters up to the
191
+ // same figure. slmp/linkRegister follow the Ethernet manuals; stSpelling is the
192
+ // accumulating-timer spelling each SLMP table uses.
193
+ const FR800_RULES = {
194
+ "FR-A800": { timerPoints: [16, 32], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
195
+ "FR-A800-E": { timerPoints: [16, 32], pointer: false, slmp: true, linkRegister: false, stSpelling: "ST" },
196
+ "FR-A800-P": { timerPoints: [16, 16], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
197
+ "FR-A800-CRN": { timerPoints: [16, 32], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
198
+ "FR-A800-LC": { timerPoints: [16, 32], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
199
+ "FR-A800-Plus": { timerPoints: [16, 16], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
200
+ "FR-F800": { timerPoints: [16, 32], pointer: false, slmp: false, linkRegister: false, stSpelling: "ST" },
201
+ "FR-F800-E": { timerPoints: [16, 32], pointer: false, slmp: true, linkRegister: false, stSpelling: "ST" },
202
+ "FR-E800": { timerPoints: [16, 16], pointer: true, slmp: false, linkRegister: false, stSpelling: "S" },
203
+ "FR-E800-E": { timerPoints: [16, 16], pointer: true, slmp: true, linkRegister: true, stSpelling: "S" },
204
+ "FR-E800-SCE": { timerPoints: [16, 16], pointer: true, slmp: true, linkRegister: true, stSpelling: "S" },
205
+ "FR-E800-NC": { timerPoints: [16, 16], pointer: true, slmp: false, linkRegister: false, stSpelling: "S" },
206
+ "FR-E806": { timerPoints: [16, 16], pointer: true, slmp: true, linkRegister: true, stSpelling: "S" }
207
+ };
208
+
209
+ // SLMP device codes and ranges (IB-0600627-D p.37, IB-0600870-S p.132-133).
210
+ // The SLMP tables cap T/C at 16 points and ST at 16 points regardless of the
211
+ // 32-point sequence-function extension, and cut X/Y at H7F rather than H8F.
212
+ const FR_SLMP_DEVICES = {
213
+ SM: { code: "H91", unit: "bit", max: 2047 },
214
+ SD: { code: "HA9", unit: "word", max: 2047 },
215
+ X: { code: "H9C", unit: "bit", max: 0x7f },
216
+ Y: { code: "H9D", unit: "bit", max: 0x7f },
217
+ M: { code: "H90", unit: "bit", max: 127 },
218
+ D: { code: "HA8", unit: "word", max: 255 },
219
+ W: { code: "HB4", unit: "word", max: 8191, requires: "linkRegister" },
220
+ TS: { code: "HC1", unit: "bit", max: 15 },
221
+ TC: { code: "HC0", unit: "bit", max: 15 },
222
+ TN: { code: "HC2", unit: "word", max: 15 },
223
+ STS: { code: "HC7", unit: "bit", allocated: true },
224
+ STC: { code: "HC6", unit: "bit", allocated: true },
225
+ STN: { code: "HC8", unit: "word", allocated: true },
226
+ CS: { code: "HC4", unit: "bit", max: 15 },
227
+ CC: { code: "HC3", unit: "bit", max: 15 },
228
+ CN: { code: "HC5", unit: "word", max: 15 }
229
+ };
230
+ const FR_SLMP_ST_MAX = 16;
231
+ const FR_SLMP_SPELLING = { STS: "SS", STC: "SC", STN: "SN" };
232
+
233
+ // The accumulating-timer parts are spelled STS/STC/STN in the FR-A800-E/F800-E
234
+ // manual and SS/SC/SN in the FR-E800 manual; both spellings are accepted.
235
+ const FR_TIMER_ALIAS = {
236
+ TS: ["T", "contact"], TC: ["T", "coil"], TN: ["T", "current"],
237
+ STS: ["ST", "contact"], STC: ["ST", "coil"], STN: ["ST", "current"],
238
+ SS: ["ST", "contact"], SC: ["ST", "coil"], SN: ["ST", "current"],
239
+ CS: ["C", "contact"], CC: ["C", "coil"], CN: ["C", "current"]
240
+ };
241
+
242
+ const FR_SERIES_ALIASES = {
243
+ FR800: null, FR8: null,
244
+ FRA800: "FR-A800", FRA800E: "FR-A800-E", FRA800P: "FR-A800-P",
245
+ FRA800CRN: "FR-A800-CRN", FRA800LC: "FR-A800-LC", FRA800PLUS: "FR-A800-Plus",
246
+ FRF800: "FR-F800", FRF800E: "FR-F800-E",
247
+ FRE800: "FR-E800", FRE800E: "FR-E800-E", FRE800SCE: "FR-E800-SCE",
248
+ FRE800NC: "FR-E800-NC", FRE806: "FR-E806"
249
+ };
250
+
251
+ const FR_MODEL_ALIASES = {
252
+ A800: "FR-A800", A800E: "FR-A800-E", A800P: "FR-A800-P",
253
+ A800CRN: "FR-A800-CRN", A800LC: "FR-A800-LC", A800PLUS: "FR-A800-Plus",
254
+ CRN: "FR-A800-CRN", LC: "FR-A800-LC", PLUS: "FR-A800-Plus",
255
+ F800: "FR-F800", F800E: "FR-F800-E",
256
+ E800: "FR-E800", E800E: "FR-E800-E", E800SCE: "FR-E800-SCE",
257
+ E800NC: "FR-E800-NC", E806: "FR-E806"
258
+ };
259
+
260
+ // X/Y map areas (p.22-28). Everything from X30/Y30 upward only exists while the
261
+ // matching network is configured, so those addresses stay configuration-dependent.
262
+ const FR_IO_AREAS = [
263
+ [0x00, 0x1f, "external and built-in option I/O", false],
264
+ [0x20, 0x2f, "inverter system I/O", false],
265
+ [0x30, 0x3f, "CC-Link remote I/O", true],
266
+ [0x40, 0x8f, "Ethernet inverter-to-inverter link I/O", true]
267
+ ];
268
+
147
269
  class MelDeviceError extends Error {
148
270
  constructor(message, result) {
149
271
  super(message);
@@ -153,22 +275,35 @@ class MelDeviceError extends Error {
153
275
  }
154
276
  }
155
277
 
278
+ function compactName(value) {
279
+ return value.normalize("NFKC").trim().toUpperCase().replace(/[\s_-]/g, "");
280
+ }
281
+
156
282
  function canonicalSeries(value) {
157
283
  if (typeof value !== "string") return null;
158
- const compact = value.normalize("NFKC").trim().toUpperCase().replace(/[\s_-]/g, "");
284
+ const compact = compactName(value);
285
+ if (Object.prototype.hasOwnProperty.call(FR_SERIES_ALIASES, compact)) return "FR800";
159
286
  return ({ IQR: "iQ-R", IQF: "iQ-F", MXR: "MX-R", MXF: "MX-F", CR800: "CR800", CR800R: "CR800", CR800D: "CR800", CR800Q: "CR800" })[compact] || null;
160
287
  }
161
288
 
162
289
  function cr800ModelFromSeries(value) {
163
290
  if (typeof value !== "string") return null;
164
- const compact = value.normalize("NFKC").trim().toUpperCase().replace(/[\s_-]/g, "");
165
- return ({ CR800R: "CR800-R", CR800D: "CR800-D", CR800Q: "CR800-Q" })[compact] || null;
291
+ return ({ CR800R: "CR800-R", CR800D: "CR800-D", CR800Q: "CR800-Q" })[compactName(value)] || null;
292
+ }
293
+
294
+ function fr800ModelFromSeries(value) {
295
+ if (typeof value !== "string") return null;
296
+ return FR_SERIES_ALIASES[compactName(value)] || null;
297
+ }
298
+
299
+ function modelFromSeries(value) {
300
+ return cr800ModelFromSeries(value) || fr800ModelFromSeries(value);
166
301
  }
167
302
 
168
303
  function normalizeModel(series, value) {
169
304
  const s = canonicalSeries(series);
170
305
  if (!s) return null;
171
- if (s === "CR800" && value == null) return cr800ModelFromSeries(series);
306
+ if ((s === "CR800" || s === "FR800") && value == null) return modelFromSeries(series);
172
307
  if (typeof value !== "string") return null;
173
308
  const model = value.normalize("NFKC").trim().toUpperCase().replace(/\s+/g, "");
174
309
  if (s === "iQ-R") {
@@ -188,16 +323,20 @@ function normalizeModel(series, value) {
188
323
  return MX_R_MODELS.includes(model) ? model : null;
189
324
  }
190
325
  if (s === "CR800") {
191
- const compact = String(value).normalize("NFKC").trim().toUpperCase().replace(/[\s_-]/g, "");
326
+ const compact = compactName(String(value));
192
327
  return ({ R: "CR800-R", D: "CR800-D", Q: "CR800-Q", CR800R: "CR800-R", CR800D: "CR800-D", CR800Q: "CR800-Q" })[compact] || null;
193
328
  }
329
+ if (s === "FR800") {
330
+ const compact = compactName(String(value));
331
+ return FR_SERIES_ALIASES[compact] || FR_MODEL_ALIASES[compact] || null;
332
+ }
194
333
  if (model === "MXF100") return model;
195
334
  return MX_F_MODELS.includes(model) ? model : null;
196
335
  }
197
336
 
198
337
  function getSupportedModels(series) {
199
338
  const s = canonicalSeries(series);
200
- const inferred = cr800ModelFromSeries(series);
339
+ const inferred = modelFromSeries(series);
201
340
  if (inferred) return [inferred];
202
341
  return s ? [...MODEL_LISTS[s]] : [];
203
342
  }
@@ -224,6 +363,9 @@ function invalid(input, series, model, mode, code, message, extra = {}) {
224
363
  }
225
364
 
226
365
  function radixFor(series, prefix) {
366
+ // Both inverter manuals print X/Y in hexadecimal and every other device,
367
+ // including the link register W, in decimal.
368
+ if (series === "FR800") return prefix === "X" || prefix === "Y" ? 16 : 10;
227
369
  const radix = RADIX[prefix];
228
370
  if (radix === "series-x") return series === "iQ-F" ? 8 : 16;
229
371
  return radix || 10;
@@ -237,13 +379,14 @@ function parseNumber(text, radix) {
237
379
  }
238
380
 
239
381
  function parseDirect(text, series) {
240
- const timerPrefixes = Object.keys(TIMER_ALIAS).sort((a, b) => b.length - a.length);
382
+ const aliases = series === "FR800" ? FR_TIMER_ALIAS : TIMER_ALIAS;
383
+ const timerPrefixes = Object.keys(aliases).sort((a, b) => b.length - a.length);
241
384
  for (const alias of timerPrefixes) {
242
385
  if (!text.startsWith(alias)) continue;
243
386
  const addressText = text.slice(alias.length);
244
387
  const address = parseNumber(addressText, 10);
245
388
  if (address !== null) {
246
- const [prefix, timerPart] = TIMER_ALIAS[alias];
389
+ const [prefix, timerPart] = aliases[alias];
247
390
  return { kind: "direct", prefix, notationPrefix: alias, address, addressText, radix: 10, timerPart };
248
391
  }
249
392
  }
@@ -560,6 +703,265 @@ function analyzeCr800(value, parsed, series, model, mode, normalized, operation,
560
703
  };
561
704
  }
562
705
 
706
+ function validateFrFeatures(features, model) {
707
+ if (features === undefined) return null;
708
+ if (!features || typeof features !== "object" || Array.isArray(features)) {
709
+ return "frFeatures must be an object when supplied.";
710
+ }
711
+ for (const [name, state] of Object.entries(features)) {
712
+ if (!FR_FEATURE_NAMES.has(name)) return `Unknown FR feature: ${name}.`;
713
+ if (typeof state !== "boolean") return `frFeatures.${name} must be boolean.`;
714
+ }
715
+ const rule = FR800_RULES[model];
716
+ if (features.extendedTimerPoints === true && rule.timerPoints[0] === rule.timerPoints[1]) {
717
+ return `${model} is not listed as supporting the 32-point T/ST/C extension.`;
718
+ }
719
+ if (features.pointerDevice === true && !rule.pointer) {
720
+ return `${model} is not listed as supporting the P pointer device.`;
721
+ }
722
+ return null;
723
+ }
724
+
725
+ function frTimerCeiling(model, mode, features) {
726
+ const [base, extended] = FR800_RULES[model].timerPoints;
727
+ const declared = typeof features.extendedTimerPoints === "boolean" ? features.extendedTimerPoints : null;
728
+ if (declared !== null) return { points: declared ? extended : base, dependent: false };
729
+ if (mode === "maximum") return { points: extended, dependent: extended !== base };
730
+ return { points: base, dependent: false, serialExtendedTo: extended !== base ? extended : null };
731
+ }
732
+
733
+ // Sequence-function ranges (IB-0600491-Q p.107). ST has no points until the
734
+ // sequence parameters allocate them, so it behaves like the other configurable
735
+ // devices in this package rather than like a fixed range.
736
+ function frSequenceRange(prefix, model, mode, options, features) {
737
+ const rule = FR800_RULES[model];
738
+ if (prefix === "T" || prefix === "C") {
739
+ const ceiling = frTimerCeiling(model, mode, features);
740
+ return {
741
+ ranges: [[0, ceiling.points - 1]],
742
+ dependent: ceiling.dependent,
743
+ serialExtendedTo: ceiling.serialExtendedTo == null ? null : ceiling.serialExtendedTo - 1
744
+ };
745
+ }
746
+ if (prefix === "ST") {
747
+ const ceiling = frTimerCeiling(model, mode, features);
748
+ if (mode === "configured") {
749
+ const points = options.configuredPoints && options.configuredPoints.ST;
750
+ if (!Number.isInteger(points) || points < 0) return { missingConfiguration: true };
751
+ if (points > ceiling.points) return { invalidConfiguration: true, ceiling: ceiling.points };
752
+ return { ranges: [[0, points - 1]], dependent: false };
753
+ }
754
+ if (mode === "maximum") return { ranges: [[0, ceiling.points - 1]], dependent: true, allocated: true };
755
+ return { ranges: [], dependent: false, allocatableTo: ceiling.points - 1 };
756
+ }
757
+ if (prefix === "P") {
758
+ if (!rule.pointer) return null;
759
+ return { ranges: FR_POINTER_RANGES, dependent: features.pointerDevice !== true };
760
+ }
761
+ const limit = FR_COMMON_LIMITS[prefix];
762
+ if (limit === undefined) return null;
763
+ return { ranges: [[0, limit]], dependent: false };
764
+ }
765
+
766
+ // SLMP ranges (IB-0600627-D p.37, IB-0600870-S p.132-133).
767
+ function frSlmpRange(entry, model, mode, options) {
768
+ if (!entry.allocated) return { ranges: [[0, entry.max]], dependent: false };
769
+ if (mode === "configured") {
770
+ const points = options.configuredPoints && options.configuredPoints.ST;
771
+ if (!Number.isInteger(points) || points < 0) return { missingConfiguration: true };
772
+ if (points > FR_SLMP_ST_MAX) return { invalidConfiguration: true, ceiling: FR_SLMP_ST_MAX };
773
+ return { ranges: [[0, points - 1]], dependent: false };
774
+ }
775
+ if (mode === "maximum") return { ranges: [[0, FR_SLMP_ST_MAX - 1]], dependent: true, allocated: true };
776
+ return { ranges: [], dependent: false, allocatableTo: FR_SLMP_ST_MAX - 1 };
777
+ }
778
+
779
+ function frIoArea(address) {
780
+ return FR_IO_AREAS.find(([start, end]) => address >= start && address <= end) || null;
781
+ }
782
+
783
+ function analyzeFr(value, parsed, series, model, mode, normalized, operation, options) {
784
+ const features = options.frFeatures || {};
785
+ const access = options.frAccess || "sequence";
786
+ const rule = FR800_RULES[model];
787
+ const fail = (code, message, extra = {}) => invalid(value, series, model, mode, code, message, { normalized, parsed, ...extra });
788
+
789
+ if (parsed.kind !== "direct") {
790
+ return fail("DEVICE_NOT_SUPPORTED", `${parsed.kind} device notation is not listed for the inverter.`);
791
+ }
792
+ if (parsed.local || parsed.indirect || parsed.bit !== null || parsed.index !== null) {
793
+ return fail("MODIFIER_NOT_SUPPORTED", "The inverter manuals do not document bit selection, indirect specification, index modification, or local devices.");
794
+ }
795
+ if (prefixIsLatchRelay(parsed)) {
796
+ return fail("DEVICE_NOT_SUPPORTED", "The latch relay L has no points on the inverter; the sequence parameters accept a latch range but nothing is latched.");
797
+ }
798
+
799
+ if (access === "slmp") return analyzeFrSlmp(value, parsed, series, model, mode, normalized, operation, options, rule, fail);
800
+
801
+ if (parsed.timerPart) {
802
+ return fail("DEVICE_NOT_SUPPORTED", `${parsed.notationPrefix} contact/coil/current notation belongs to the SLMP device table, not to the sequence program; use ${parsed.prefix}${parsed.addressText}, or pass frAccess: "slmp".`);
803
+ }
804
+ const prefix = parsed.prefix;
805
+ if (parsed.digit !== null && !FR_DIGIT_PREFIXES.has(prefix)) {
806
+ return fail("DIGIT_MODIFIER_NOT_SUPPORTED", "K1-K8 digit designation is documented only for the bit devices X, Y, and M.");
807
+ }
808
+
809
+ const range = frSequenceRange(prefix, model, mode, options, features);
810
+ if (!range) {
811
+ return fail("DEVICE_NOT_SUPPORTED", `${prefix} is not listed as an inverter sequence function device for ${model}.`);
812
+ }
813
+ if (range.missingConfiguration) {
814
+ return fail("MISSING_CONFIGURATION", "configuredPoints.ST is required in configured mode; the accumulating timer defaults to 0 points.");
815
+ }
816
+ if (range.invalidConfiguration) {
817
+ return fail("INVALID_CONFIGURATION", `configuredPoints.ST exceeds the ${range.ceiling}-point maximum documented for ${model}.`);
818
+ }
819
+
820
+ return frResult(value, parsed, series, model, mode, normalized, operation, range, {
821
+ label: "inverter sequence function",
822
+ allocationHint: 'The accumulating timer ST defaults to 0 points; allocate it in the sequence parameters, then use mode "maximum" or configuredPoints.ST.',
823
+ checkDigit: true,
824
+ ioAreas: true
825
+ });
826
+ }
827
+
828
+ function prefixIsLatchRelay(parsed) {
829
+ return parsed.prefix === "L" && !parsed.timerPart;
830
+ }
831
+
832
+ function analyzeFrSlmp(value, parsed, series, model, mode, normalized, operation, options, rule, fail) {
833
+ if (!rule.slmp) {
834
+ return fail("MODEL_NOT_SUPPORTED", `SLMP device access is documented for the Ethernet inverters (FR-A800-E, FR-F800-E, FR-E800-E, FR-E800-SCE, FR-E806), not for ${model}.`);
835
+ }
836
+ if (parsed.digit !== null) {
837
+ return fail("DIGIT_MODIFIER_NOT_SUPPORTED", "SLMP addresses a head device and a point count; K1-K8 digit designation is not part of the device specification.");
838
+ }
839
+
840
+ const key = parsed.timerPart ? `${parsed.prefix}${{ contact: "S", coil: "C", current: "N" }[parsed.timerPart]}` : parsed.prefix;
841
+ const entry = FR_SLMP_DEVICES[key];
842
+ if (!entry) {
843
+ const parts = { T: ["TS", "TC", "TN"], ST: rule.stSpelling === "S" ? ["SS", "SC", "SN"] : ["STS", "STC", "STN"], C: ["CS", "CC", "CN"] }[parsed.prefix];
844
+ const hint = parts
845
+ ? ` The SLMP table addresses the contact, coil, and current value separately, so use ${parts.map(part => `${part}${parsed.addressText}`).join(", ")}.`
846
+ : "";
847
+ return fail("DEVICE_NOT_SUPPORTED", `${parsed.notationPrefix || parsed.prefix} is not listed in the SLMP device table.${hint}`);
848
+ }
849
+ if (entry.requires && !rule[entry.requires]) {
850
+ return fail("DEVICE_NOT_SUPPORTED", `The link register W is only listed in the FR-E800 SLMP device table, not for ${model}.`);
851
+ }
852
+
853
+ const range = frSlmpRange(entry, model, mode, options);
854
+ if (range.missingConfiguration) {
855
+ return fail("MISSING_CONFIGURATION", "configuredPoints.ST is required in configured mode; the accumulating timer defaults to 0 points.");
856
+ }
857
+ if (range.invalidConfiguration) {
858
+ return fail("INVALID_CONFIGURATION", `configuredPoints.ST exceeds the ${range.ceiling}-point maximum documented in the SLMP device table.`);
859
+ }
860
+
861
+ const spellingWarning = [];
862
+ const canonical = FR_SLMP_SPELLING[key];
863
+ if (canonical) {
864
+ const expected = rule.stSpelling === "S" ? canonical : key;
865
+ if ((parsed.notationPrefix || "") !== expected) {
866
+ spellingWarning.push(`The ${model} manual spells this accumulating-timer device ${expected}${parsed.addressText}; ${parsed.notationPrefix}${parsed.addressText} is accepted as the equivalent from the other inverter manual.`);
867
+ }
868
+ }
869
+ if (parsed.prefix === "SM" && entry.unit === "bit") {
870
+ spellingWarning.push("For word-unit SLMP access to SM, the head device number must be one listed in the special relay table, otherwise the read or write is not performed correctly.");
871
+ }
872
+
873
+ return frResult(value, parsed, series, model, mode, normalized, operation, range, {
874
+ label: "SLMP device table",
875
+ allocationHint: 'The accumulating timer defaults to 0 points; allocate it in the sequence parameters, then use mode "maximum" or configuredPoints.ST.',
876
+ checkDigit: false,
877
+ ioAreas: true,
878
+ deviceCode: entry.code,
879
+ deviceUnit: entry.unit,
880
+ extraWarnings: spellingWarning
881
+ });
882
+ }
883
+
884
+ function frResult(value, parsed, series, model, mode, normalized, operation, range, context) {
885
+ const prefix = parsed.prefix;
886
+ const fail = (code, message, extra = {}) => invalid(value, series, model, mode, code, message, { normalized, parsed, ...extra });
887
+ const withinRange = address => range.ranges.some(([start, end]) => address >= start && address <= end);
888
+ const warnings = [...(context.extraWarnings || [])];
889
+ let configurationDependent = range.dependent;
890
+
891
+ if (mode !== "syntax") {
892
+ if (!withinRange(parsed.address)) {
893
+ if (range.allocatableTo != null && parsed.address <= range.allocatableTo) {
894
+ return fail("REQUIRES_CONFIGURATION", `${parsed.notationPrefix || prefix}${parsed.addressText} needs sequence-parameter allocation. ${context.allocationHint}`,
895
+ { configurationDependent: true, suggestedMode: "maximum" });
896
+ }
897
+ if (range.serialExtendedTo != null && parsed.address <= range.serialExtendedTo) {
898
+ return fail("REQUIRES_SERIAL_SUPPORT",
899
+ `${prefix}${parsed.addressText} needs the 32-point T/C extension, which ${model} only provides from the January 2021 production month onward. Check the SERIAL plate, then use mode "maximum" or frFeatures.extendedTimerPoints.`,
900
+ { configurationDependent: true, suggestedMode: "maximum" });
901
+ }
902
+ return fail("ADDRESS_OUT_OF_RANGE", `${parsed.notationPrefix || prefix}${parsed.addressText} is outside the ${model} range in the ${context.label}.`);
903
+ }
904
+ if (context.checkDigit && parsed.digit !== null) {
905
+ const last = parsed.address + parsed.digit * 4 - 1;
906
+ if (!withinRange(last)) {
907
+ return fail("DIGIT_RANGE_OVERFLOW", `K${parsed.digit}${prefix}${parsed.addressText} covers ${parsed.digit * 4} points and runs past the end of the ${prefix} range.`);
908
+ }
909
+ }
910
+ if (range.dependent && (prefix === "T" || prefix === "C")) {
911
+ warnings.push("The 32-point T/C extension depends on the SERIAL production month; pass frFeatures.extendedTimerPoints once it is known.");
912
+ }
913
+ if (range.allocated) {
914
+ warnings.push("The accumulating timer is only usable once the sequence parameters allocate its points; pass configuredPoints.ST for an exact result.");
915
+ }
916
+ if (range.dependent && prefix === "P") {
917
+ warnings.push("The 256-point P pointer device depends on the SERIAL production month; pass frFeatures.pointerDevice once it is known.");
918
+ }
919
+ if (context.ioAreas && (prefix === "X" || prefix === "Y")) {
920
+ const area = frIoArea(parsed.address);
921
+ if (area && area[3]) {
922
+ configurationDependent = true;
923
+ warnings.push(`${prefix}${parsed.addressText} belongs to the ${area[2]} area, which only exists while that network is configured.`);
924
+ }
925
+ }
926
+ }
927
+
928
+ const readOnly = mode !== "syntax" && prefix === "X";
929
+ if (readOnly) {
930
+ warnings.push("Input device X is refreshed from the terminal or network every scan, so turning it on does not drive the corresponding signal. Use SM1200/SM1255 and SD1148/SD1149 for inverter operation control instead.");
931
+ }
932
+ const operationAllowed = operation === "write" ? !readOnly : operation === "read" ? true : null;
933
+
934
+ const common = {
935
+ valid: true, input: value, normalized, series, model, mode, operation, code: "VALID",
936
+ parsed, source: SOURCES[series]
937
+ };
938
+ if (context.deviceCode) {
939
+ common.deviceCode = context.deviceCode;
940
+ common.deviceUnit = context.deviceUnit;
941
+ }
942
+
943
+ if (mode === "syntax") {
944
+ return {
945
+ ...common,
946
+ message: `Valid ${context.label} device syntax; range and access were not evaluated in syntax mode.`,
947
+ configurationDependent: false, access: "not-evaluated", operationAllowed: null, warnings: []
948
+ };
949
+ }
950
+
951
+ return {
952
+ ...common,
953
+ message: operation === "write" && readOnly
954
+ ? "Valid device notation and range, but X is an input image that cannot be driven."
955
+ : configurationDependent
956
+ ? `Valid within the ${context.label} range; confirm the inverter SERIAL, sequence parameters, and network configuration.`
957
+ : `Valid within the ${context.label} range.`,
958
+ configurationDependent,
959
+ access: readOnly ? "read-only" : "read-write",
960
+ operationAllowed,
961
+ warnings
962
+ };
963
+ }
964
+
563
965
  function analyzeDevice(value, options = {}) {
564
966
  const mode = options.mode || "default";
565
967
  const inputMode = options.inputMode || "exact";
@@ -575,7 +977,7 @@ function analyzeDevice(value, options = {}) {
575
977
  }
576
978
  const series = canonicalSeries(options.series);
577
979
  if (!series) return invalid(value, null, null, mode, "UNSUPPORTED_SERIES", `Supported series: ${SERIES.join(", ")}.`);
578
- const seriesModel = cr800ModelFromSeries(options.series);
980
+ const seriesModel = modelFromSeries(options.series);
579
981
  const model = normalizeModel(options.series, options.model ?? seriesModel);
580
982
  if (!model) return invalid(value, series, null, mode, "UNSUPPORTED_MODEL", `Unsupported ${series} model: ${String(options.model)}.`);
581
983
  if (seriesModel && model !== seriesModel) return invalid(value, series, model, mode, "MODEL_SERIES_MISMATCH", `${String(options.series)} conflicts with model ${model}.`);
@@ -583,11 +985,19 @@ function analyzeDevice(value, options = {}) {
583
985
  const featureError = validateCr800Features(options.cr800Features);
584
986
  if (featureError) return invalid(value, series, model, mode, "INVALID_CR800_FEATURES", featureError);
585
987
  }
988
+ if (series === "FR800") {
989
+ const featureError = validateFrFeatures(options.frFeatures, model);
990
+ if (featureError) return invalid(value, series, model, mode, "INVALID_FR_FEATURES", featureError);
991
+ if (options.frAccess !== undefined && !["sequence", "slmp"].includes(options.frAccess)) {
992
+ return invalid(value, series, model, mode, "INVALID_FR_ACCESS", "frAccess must be sequence or slmp.");
993
+ }
994
+ }
586
995
  const parsed = parseDevice(value, { series, inputMode });
587
996
  const normalized = inputMode === "friendly" ? normalizeDevice(value) : (typeof value === "string" ? value : null);
588
997
  if (!parsed) return invalid(value, series, model, mode, "INVALID_SYNTAX", "The value is not a supported MELSEC/MELFA device notation.", { normalized });
589
998
 
590
999
  if (series === "CR800") return analyzeCr800(value, parsed, series, model, mode, normalized, operation, options.cr800Features);
1000
+ if (series === "FR800") return analyzeFr(value, parsed, series, model, mode, normalized, operation, options);
591
1001
 
592
1002
  if (series === "iQ-F" && ["V", "LT", "LST", "ZR", "RD", "FX", "FY", "FD"].includes(parsed.prefix)) {
593
1003
  return invalid(value, series, model, mode, "DEVICE_NOT_SUPPORTED", `${parsed.prefix} is not listed as an iQ-F device in the attached manual.`, { normalized, parsed });
@@ -615,12 +1025,32 @@ function analyzeDevice(value, options = {}) {
615
1025
  return { valid: true, input: value, normalized, series, model, mode, code: "VALID", message: "Valid link-direct device notation; the actual range depends on the configured network.", configurationDependent: true, parsed, source: SOURCES[series] };
616
1026
  }
617
1027
 
618
- if (parsed.kind === "unit" || parsed.kind === "cpu-buffer") {
619
- const maxAddress = parsed.kind === "cpu-buffer"
620
- ? series === "MX-R" ? 524287 : series === "MX-F" ? 16809983 : 268435455
621
- : 268435455;
622
- if (parsed.address > maxAddress) return invalid(value, series, model, mode, "ADDRESS_OUT_OF_RANGE", `Buffer-memory address must be 0-${maxAddress}.`, { normalized, parsed });
623
- return { valid: true, input: value, normalized, series, model, mode, code: "VALID", message: "Valid access-device notation; existence and actual range depend on the installed unit or CPU buffer.", configurationDependent: true, parsed, source: SOURCES[series] };
1028
+ if (parsed.kind === "cpu-buffer") {
1029
+ const rule = CPU_BUFFER_RULES[series];
1030
+ const maxAddress = rule[parsed.prefix];
1031
+ if (maxAddress === undefined) {
1032
+ return invalid(value, series, model, mode, "DEVICE_NOT_SUPPORTED", `U3En\\${parsed.prefix} is not listed as a ${series} device in the attached manual.`, { normalized, parsed });
1033
+ }
1034
+ if (!rule.units.includes(parsed.cpu)) {
1035
+ return invalid(value, series, model, mode, "CPU_NUMBER_OUT_OF_RANGE", `U3E${parsed.cpu.toString(16).toUpperCase()} is not a documented CPU selector for ${series}.`, { normalized, parsed });
1036
+ }
1037
+ if (parsed.address > maxAddress) {
1038
+ return invalid(value, series, model, mode, "ADDRESS_OUT_OF_RANGE", `U3En\\${parsed.prefix} address must be 0-${maxAddress}.`, { normalized, parsed });
1039
+ }
1040
+ return { valid: true, input: value, normalized, series, model, mode, code: "VALID", message: "Valid CPU-buffer access notation; the actual usable range depends on the refresh and multiple-CPU configuration.", configurationDependent: true, parsed, source: SOURCES[series] };
1041
+ }
1042
+
1043
+ if (parsed.kind === "unit") {
1044
+ const rule = UNIT_ACCESS_RULES[series];
1045
+ if (rule.unitMin !== null && (parsed.unit < rule.unitMin || parsed.unit > rule.unitMax)) {
1046
+ return invalid(value, series, model, mode, "UNIT_NUMBER_OUT_OF_RANGE",
1047
+ `U${parsed.unit.toString(16).toUpperCase()} is outside the documented ${series} head I/O number range ${rule.unitMin.toString(16).toUpperCase().padStart(2, "0")}H-${rule.unitMax.toString(16).toUpperCase()}H.`,
1048
+ { normalized, parsed });
1049
+ }
1050
+ if (parsed.address > rule.address) {
1051
+ return invalid(value, series, model, mode, "ADDRESS_OUT_OF_RANGE", `Buffer-memory address must be 0-${rule.address}.`, { normalized, parsed });
1052
+ }
1053
+ return { valid: true, input: value, normalized, series, model, mode, code: "VALID", message: "Valid access-device notation; existence and actual range depend on the installed unit.", configurationDependent: true, parsed, source: SOURCES[series] };
624
1054
  }
625
1055
 
626
1056
  if (mode === "syntax") {
package/dist/index.d.ts CHANGED
@@ -1,8 +1,16 @@
1
- export type MelSeries = "iQ-R" | "iQ-F" | "MX-R" | "MX-F" | "CR800" | "CR800-R" | "CR800-D" | "CR800-Q";
1
+ export type MelSeries =
2
+ | "iQ-R" | "iQ-F" | "MX-R" | "MX-F"
3
+ | "CR800" | "CR800-R" | "CR800-D" | "CR800-Q"
4
+ | "FR800"
5
+ | "FR-A800" | "FR-A800-E" | "FR-A800-P" | "FR-A800-CRN" | "FR-A800-LC" | "FR-A800-Plus"
6
+ | "FR-F800" | "FR-F800-E"
7
+ | "FR-E800" | "FR-E800-E" | "FR-E800-SCE" | "FR-E800-NC" | "FR-E806";
2
8
  export type ValidationMode = "syntax" | "default" | "maximum" | "configured";
3
9
  export type InputMode = "exact" | "friendly";
4
10
  export type DeviceOperation = "read" | "write" | "either";
5
11
  export type DeviceAccess = "read-write" | "read-only" | "configuration-dependent" | "not-evaluated";
12
+ /** "sequence" validates the sequence-program device table, "slmp" the SLMP device table of the Ethernet inverters. */
13
+ export type FrAccess = "sequence" | "slmp";
6
14
 
7
15
  export interface Cr800Features {
8
16
  /** CR800-R/Q sequencer I/O unit direct control (parameter QXYREAD). */
@@ -25,6 +33,22 @@ export interface Cr800Features {
25
33
  ccLinkIef?: boolean;
26
34
  }
27
35
 
36
+ export interface FrFeatures {
37
+ /**
38
+ * 32-point T/ST/C extension. Listed only for FR-A800/FR-A800-E (excluding
39
+ * FR-A800-P), FR-A800 Plus (FR-A800-CRN/LC) and FR-F800/FR-F800-E, from the
40
+ * January 2021 production month onward. Omit when the SERIAL plate has not
41
+ * been checked. Ignored for `frAccess: "slmp"`, whose table caps T/C/ST at
42
+ * 16 points.
43
+ */
44
+ extendedTimerPoints?: boolean;
45
+ /**
46
+ * 256-point P pointer device. Listed only for FR-E800, from the January 2021
47
+ * production month onward.
48
+ */
49
+ pointerDevice?: boolean;
50
+ }
51
+
28
52
  export interface DeviceOptions {
29
53
  series: MelSeries | string;
30
54
  model?: string;
@@ -34,6 +58,10 @@ export interface DeviceOptions {
34
58
  configuredPoints?: Partial<Record<string, number>>;
35
59
  /** Known CR800 feature states. Omitted properties are treated as unknown. */
36
60
  cr800Features?: Cr800Features;
61
+ /** Known inverter feature states. Omitted properties are treated as unknown. */
62
+ frFeatures?: FrFeatures;
63
+ /** Which inverter device table to validate against. Defaults to "sequence". */
64
+ frAccess?: FrAccess;
37
65
  }
38
66
 
39
67
  export interface ParsedDevice {
@@ -73,6 +101,10 @@ export interface DeviceAnalysis {
73
101
  operationAllowed?: boolean | null;
74
102
  activeAllocations?: string[];
75
103
  possibleAllocations?: string[];
104
+ /** SLMP device code, such as "H9C". Present for `frAccess: "slmp"` results. */
105
+ deviceCode?: string;
106
+ /** SLMP access unit for the device code. */
107
+ deviceUnit?: "bit" | "word";
76
108
  warnings?: string[];
77
109
  parsed?: ParsedDevice;
78
110
  suggestedMode?: ValidationMode;
@@ -6,11 +6,14 @@
6
6
 
7
7
  | シリーズ | マニュアル | 文書番号・版 | 主な参照ページ |
8
8
  | --- | --- | --- | --- |
9
- | iQ-R | MELSEC iQ-R シーケンサCPUユニットユーザーズマニュアル | SH-082487-J | 65(機種別デフォルト点数)、408(デバイス一覧)、409-410(点数設定・最大使用範囲)、435-436(インデックス修飾) |
10
- | iQ-F | MELSEC iQ-F FX5ユーザーズマニュアル(応用編) | JY997D54301AF | 57(デバイス一覧)、69-70(インデックス)、76(割込みポインタ)、89(機種別使用範囲) |
11
- | MX-R | MELSEC MXコントローラ MX-Rモデルユーザーズマニュアル | SH-082640-D | 350(デバイス一覧・デフォルト点数)、351-352(点数設定・最大使用範囲)、380-381(インデックス修飾) |
12
- | MX-F | MELSEC MXコントローラ MX-Fモデルユーザーズマニュアル | SH-082633-E | 277(性能・デフォルト点数)、416(デバイス一覧)、417-418(点数設定・最大使用範囲) |
9
+ | iQ-R | MELSEC iQ-R シーケンサCPUユニットユーザーズマニュアル | SH-082487-J | 65(機種別デフォルト点数)、408(デバイス一覧)、409-410(点数設定・最大使用範囲)、432-433(ユニット/CPUバッファアクセス指定方法)、435-436(インデックス修飾) |
10
+ | iQ-F | MELSEC iQ-F FX5ユーザーズマニュアル(応用編) | JY997D54301AF | 57(デバイス一覧)、68(ユニットアクセス)、69-70(インデックス)、76(割込みポインタ)、89(機種別使用範囲) |
11
+ | MX-R | MELSEC MXコントローラ MX-Rモデルユーザーズマニュアル | SH-082640-D | 221(性能仕様)、350(デバイス一覧・デフォルト点数)、351-352(点数設定・最大使用範囲)、376-378(ユニット/CPUバッファアクセス指定方法)、380-381(インデックス修飾) |
12
+ | MX-F | MELSEC MXコントローラ MX-Fモデルユーザーズマニュアル | SH-082633-E | 277(性能・デフォルト点数)、416(デバイス一覧)、417-418(点数設定・最大使用範囲)、441-443(ユニット/CPUバッファアクセス指定方法) |
13
13
  | CR800 | CR800シリーズコントローラ 機能と操作の詳細解説 | BFP-A3477-AB | 5-489~5-491(CPUバッファ対応)、6-618~6-619(シーケンサリンク)、6-654(対応デバイス一覧)、6-655~6-657(デバイス割付) |
14
+ | FR800 | FR-A800/F800/E800 シーケンス機能プログラミングマニュアル | IB-0600491-Q | 8(SERIAL別対応可否)、11-12(シーケンス機能仕様・注記*8)、22-28(デバイスマップ)、107(デバイス一覧)、125-126(桁指定)、157(ネスティング) |
15
+ | FR800(SLMP) | A800-E/F800-E Ethernet機能説明書 | IB-0600627-D | 37(SLMPデバイスコードと範囲)、38(デバイス番号指定)、94(インバータ間リンクデバイスマップ) |
16
+ | FR800(SLMP) | FR-E800取扱説明書(通信編) | IB-0600870-S | 12(Ethernet対応仕様品)、132-133(SLMPデバイスコードと範囲・リンクレジスタ)、136(デバイス番号指定)、222(インバータ間リンクデバイスマップ) |
14
17
 
15
18
  ## 基数
16
19
 
@@ -18,6 +21,7 @@
18
21
  | --- | --- | --- | --- |
19
22
  | iQ-F | X, Y | B, SB, W, SW | その他の通常デバイス |
20
23
  | iQ-R / MX-R / MX-F | - | X, Y, B, SB, W, SW, FX, FY | その他の通常デバイス |
24
+ | FR800 | - | X, Y | その他の通常デバイス |
21
25
 
22
26
  この差により、iQ-Fの `X1777` は有効候補ですが `X1780` は構文エラーです。一方、iQ-R/MXのX/Yは16進です。
23
27
 
@@ -80,6 +84,25 @@ B=8K、F=2K、SB=2K、V=2K、W=8K、SW=2K、L=8Kは共通です。S/ST/LST/R/ZR/
80
84
 
81
85
  両マニュアルとも、実際の最大値はグローバルラベル使用量で変化すると注記しています。
82
86
 
87
+ ## ユニットアクセス/CPUバッファメモリアクセス
88
+
89
+ 先頭入出力番号(`Un`)、CPU指定(`U3En`)、アドレスの根拠は下記のとおりです。
90
+
91
+ | シリーズ | `Un` の範囲 | `Un¥G` アドレス | `U3En` の範囲 | `U3En¥G` アドレス | `U3En¥HG` アドレス | 参照 |
92
+ | --- | --- | ---: | --- | ---: | ---: | --- |
93
+ | iQ-R | 00H~FFH | 0-268435455 | 3E0~3E3 | 0-268435455 | 0-12287 | 本文432・433ページ、408ページ |
94
+ | iQ-F | 記載なし | 0-268435455 | 非対応 | - | - | 本文68ページ、57ページ注記 |
95
+ | MX-R | 00H~FFH | 0-268435455 | 3E0のみ | 0-524287 | 記載なし | 本文376・377・378ページ、221ページ |
96
+ | MX-F | 01H~FEH | 0-268435455 | 3E0のみ | 0-16809983 | 記載なし | 本文441・442・443ページ、277ページ |
97
+
98
+ iQ-R本文433ページの表は`U3En`のnを1号機=3E0、2号機=3E1、3号機=3E2、4号機=3E3と明記し、CPUバッファメモリ(`G`)と定周期通信エリア(`HG`)を別領域として区別しています。`G`は408ページで最大268435456点(変更不可)、`HG`は同ページで最大12288点です。
99
+
100
+ MX-R本文377ページ、MX-F本文442ページのバッファメモリ指定例では、CPU機能部の先頭入出力番号として3E0のみが示されます。MX-F本文443ページは「U3E0¥Gnで指定します」と明記しています。また両マニュアルとも`HG`表記は一度も現れず、MX-Rは本文221ページ、MX-Fは本文277ページの性能仕様でCPUバッファメモリアクセスデバイス点数を`U3E¥G`としてのみ規定しています。このため本パッケージは、MX-R/MX-Fの`U3E1`以降と`HG`表記を無効として扱います。
101
+
102
+ iQ-F本文68ページはユニット番号の範囲を明示していないため、`Un`のnは判定しません(`U¥G`自体がFX5S非対応である点のみ判定します)。
103
+
104
+ MX-F本文441ページのユニットアクセスデバイスは`Gn`を0~4294967295としていますが、同277ページの性能仕様は最大268435456点としています。本パッケージは厳しいほうの性能仕様値を採用しています。
105
+
83
106
  ## 修飾表記
84
107
 
85
108
  iQ-R本文436ページ、MX-R本文381ページ、MX-Fの同等節では、次の例が明示されています。
@@ -134,3 +157,96 @@ iQ-F本文69ページではZによる全デバイスのインデックス修飾
134
157
  - 6.2.2/表6-4: `U3En\G10000-G10511`をロボットCPU No.nの領域として使用
135
158
 
136
159
  このため`U3En\G512-G1023`は有効範囲へ追加しません。該当入力には`MANUAL_RANGE_CONFLICT`を返します。IQMEMの意図された読み出し側アドレスと考えられる`U3En\G10512-G11023`は固定範囲としては有効ですが、表記不整合が解消できないため、IQMEM有効時でもR属性を断定せず`configuration-dependent`とします。これはマニュアル間の矛盾を隠して推測値を確定しないための意図的な保守性です。
160
+
161
+ ## FRインバータ シーケンス機能(FR-A800/A800 Plus/F800/E800)
162
+
163
+ 本文107ページ「3.9.1 デバイス一覧」が全デバイスの点数と使用範囲を確定しています。点数はシーケンスパラメータで変更できないため、`configuredPoints`は参照しません。
164
+
165
+ | 記号 | FR-A800 / FR-A800 Plus / FR-F800 | FR-E800 | 基数 |
166
+ | --- | --- | --- | --- |
167
+ | X | `X0-X8F`(144点) | 同左 | 16進 |
168
+ | Y | `Y0-Y8F`(144点) | 同左 | 16進 |
169
+ | M | `M0-M127`(128点) | 同左 | 10進 |
170
+ | L | ―(シーケンスパラメータ設定可能だがラッチしない) | 同左 | ― |
171
+ | T | `T0-T15` / `T0-T31` | `T0-T15` | 10進 |
172
+ | ST | 初期値0点(割付けにより `ST0-ST15` / `ST0-ST31`) | 初期値0点(割付けにより `ST0-ST15`) | 10進 |
173
+ | C | `C0-C15` / `C0-C31` | `C0-C15` | 10進 |
174
+ | D | `D0-D255`(256点) | 同左 | 10進 |
175
+ | P | 記載なし | `P0-P127`、`P2048-P2175`(256点、すべて共通ポインタ) | 10進 |
176
+ | SM | `SM0-SM2047`(機能制限あり) | 同左 | 10進 |
177
+ | SD | `SD0-SD2047`(機能制限あり) | 同左 | 10進 |
178
+
179
+ X/Yの144点は、本文22-28ページのデバイスマップが `X0-X1F`(外部入出力・内蔵オプション)、`X20-X2F`(システム入出力)、`X30-X3F`(CC-Linkリモート入出力)、`X40-X8F`(Ethernetインバータ間リンク、マスタ局は5スレーブ分)と連続的に割り付けていることと一致します。Y側も同一構成です。`X30`以降は該当ネットワークの構成時のみ存在するため、成功時も設定依存として警告を付けます。
180
+
181
+ ネスティング(N)はデバイス一覧には含まれませんが、本文157ページに「入れ子は15個(N0~14)まで」と明記されているため、`N0-N14`のみ有効としています。
182
+
183
+ ### SERIAL(製造年月)依存
184
+
185
+ 本文8ページ「1.1.1 SERIAL(製造番号)の確認」の表は、次の3機能を2021年1月以降の製造品のみ対応としています。
186
+
187
+ | 機能 | FR-A800(FR-A800-Pは除く)/FR-A800 Plus(FR-A800-CRN/LC) | FR-A800 Plus(FR-A800-CRN/LC以外) | FR-F800 | FR-E800 |
188
+ | --- | :-: | :-: | :-: | :-: |
189
+ | プログラム言語(ST)対応 | − | − | − | ○ |
190
+ | T/ST/Cのデバイス32点対応 | ○ | − | ○ | − |
191
+ | ポインタ(P)デバイス256点対応 | − | − | − | ○ |
192
+
193
+ ST言語対応は表記判定に影響しないため実装対象外です。残る2件は型名だけでは確定できないため、次のように分離しています。
194
+
195
+ - 既定(`default`)はSERIALを未確認とみなし、拡張前の点数で判定します。範囲外だが拡張範囲内の場合は`ADDRESS_OUT_OF_RANGE`ではなく`REQUIRES_SERIAL_SUPPORT`を返し、`suggestedMode: "maximum"`を添えます。
196
+ - `maximum`は拡張後の点数を許可しますが、成功時も`configurationDependent: true`とします。
197
+ - `frFeatures.extendedTimerPoints` / `frFeatures.pointerDevice` に真偽値を渡した場合のみ、設定依存を解除して確定判定します。マニュアルが非対応としている型名に`true`を渡した場合は`INVALID_FR_FEATURES`を返します。
198
+
199
+ FR-A800-Pは本文8ページの対応列から明示的に除外されているため、32点拡張の対象外として扱います。FR-A800 PlusのうちFR-A800-CRN/LC以外も同様に対象外です。
200
+
201
+ ### 桁指定
202
+
203
+ 本文125-126ページは、桁指定の対象をビットデバイス(X、Y、M)と明記し、16ビット命令でK1~K4、32ビット命令でK1~K8としています。本パッケージはK1~K8を受け付け、先頭デバイス+指定点数がデバイス範囲を超える場合に`DIGIT_RANGE_OVERFLOW`を返します。本文127ページの例(`K5M0`、`K5M10`)のとおり先頭番号のアライメント制約はないため、そこは判定しません。
204
+
205
+ ### 入力デバイスXの属性
206
+
207
+ 本文22・25ページの注記は「入力デバイスXは、外部端子の入力状態をモニタします。例えば、X00をシーケンスプログラム上でONしてもSTF信号をONすることはできません」とし、信号入力にはSM1200、SM1255、SD1148、SD1149を使うよう指示しています。Xはネットワーク側の領域(`X30`以降)も含めて毎スキャンリフレッシュされる入力イメージであるため、`access: "read-only"`として扱い、`operation: "write"`では`operationAllowed: false`を返します。
208
+
209
+ ### 意図的な保守性
210
+
211
+ - ビット指定、間接指定(`@`)、インデックス修飾(`Z`/`LZ`)、ローカルデバイス(`#`)、ユニットアクセス(`Un\G`)、CPUバッファアクセス(`U3En\G`)、リンクダイレクト(`Jn\`)は本マニュアルに記載がないため、すべて無効とします。
212
+ - `TS`/`TC`/`TN`のような接点・コイル・現在値の分離表記も記載がないため無効とし、`T0`形式を案内します。
213
+ - SM/SDは「機能制限あり」と注記されているとおり、全番号に機能が割り当てられているわけではありません。本パッケージは領域の範囲のみを判定します。
214
+
215
+ ### 積算タイマ(ST)の初期値0点
216
+
217
+ 本文11ページの積算タイマ行には注記*8が付き、12ページで「*8 初期値は0です。」と定義されています。IB-0600627-D 37ページおよびIB-0600870-S 132ページのSLMPデバイス表も、積算タイマの範囲を「0点(初期値。PCパラメータ割付けにより16点まで使用可能。)」としており、両者は一致します。
218
+
219
+ このためSTのみ、他シリーズの点数変更可能デバイスと同じ扱いにしています。
220
+
221
+ | モード | STの判定 |
222
+ | --- | --- |
223
+ | `default` | 有効点数0点。任意のST表記に対して`REQUIRES_CONFIGURATION`と`suggestedMode: "maximum"`を返します。 |
224
+ | `maximum` | 割付け上限(16点、32点拡張対応機種では32点)まで許可し、`configurationDependent: true`とします。 |
225
+ | `configured` | `configuredPoints.ST`の点数で判定します。未指定なら`MISSING_CONFIGURATION`、上限超過なら`INVALID_CONFIGURATION`です。 |
226
+
227
+ T・Cには同様の注記がないため、これらは従来どおり固定範囲(16点/SERIAL依存で32点)として扱います。
228
+
229
+ ## FRインバータ SLMPデバイステーブル
230
+
231
+ IB-0600627-D 37ページ(FR-A800-E/FR-F800-E)およびIB-0600870-S 132-133ページ(FR-E800)の「デバイス」表は、外部機器がSLMPで指定できるデバイスコードと範囲を規定しています。シーケンス機能のデバイス一覧とは範囲も表記も異なるため、`frAccess: "slmp"`で別テーブルとして判定します。
232
+
233
+ | デバイス | コード | 種別 | 範囲 | 備考 |
234
+ | --- | --- | --- | --- | --- |
235
+ | SM | `H91` | ビット | `0-2047` | ワード単位指定時は特殊リレー一覧のデバイスNo.を先頭に指定する必要あり |
236
+ | SD | `HA9` | ワード | `0-2047` | |
237
+ | X | `H9C` | ビット | `H0-H7F` | シーケンス機能の`H0-H8F`より狭い |
238
+ | Y | `H9D` | ビット | `H0-H7F` | 同上 |
239
+ | M | `H90` | ビット | `0-127` | |
240
+ | D | `HA8` | ワード | `0-255` | |
241
+ | W | `HB4` | ワード | `0-8191`(8192点) | IB-0600870-S 132ページ。FR-E800側の表にのみ記載 |
242
+ | TS / TC / TN | `HC1` / `HC0` / `HC2` | ビット / ビット / ワード | `0-15` | |
243
+ | STS / STC / STN(SS / SC / SN) | `HC7` / `HC6` / `HC8` | ビット / ビット / ワード | 0点(初期値。割付けにより16点まで) | |
244
+ | CS / CC / CN | `HC4` / `HC3` / `HC5` | ビット / ビット / ワード | `0-15` | |
245
+
246
+ - IB-0600870-S 12ページは「Ethernet通信は、Ethernet仕様品、安全通信仕様品、IP67仕様品で使用可能です」と規定しています。SLMPを許可する型名は、これとIB-0600627-Dの適用範囲から FR-A800-E、FR-F800-E、FR-E800-E、FR-E800-SCE、FR-E806 に限定し、その他は`MODEL_NOT_SUPPORTED`とします。
247
+ - 積算タイマの各部の表記は、IB-0600627-D 37ページが`STS`/`STC`/`STN`、IB-0600870-S 132ページが`SS`/`SC`/`SN`です。デバイスコードは同一(HC7/HC6/HC8)のため両表記を受け付け、選択した型名のマニュアル表記と異なる場合に警告を返します。
248
+ - リンクレジスタWは、IB-0600870-S 133ページ以降でPr.番号(`W0-W999`)、モニタ、アラーム履歴、シリアルナンバー(`W8101-W8108`)等に対応づけられており、番号は10進です。PLCシリーズのWが16進であるのと異なるため、FR800では基数を分けています(FRで16進なのはX/Yのみ)。割付けは疎ですが、本パッケージは表が定める8192点の範囲のみを判定します。
249
+ - 両マニュアルのSLMP表はT/CおよびSTの上限を16点としており、シーケンス機能側の32点拡張には言及していません。したがって`frAccess: "slmp"`では`frFeatures.extendedTimerPoints`で範囲を広げません。
250
+ - SLMPは先頭デバイス番号と点数を別フィールドで送るため、桁指定は表記の一部ではありません。P・N・Lも表に存在しないため無効とします。
251
+
252
+ インバータ間リンクの入出力割付け(`X40-X8F` / `Y40-Y8F`)は、IB-0600627-D 94ページとIB-0600870-S 222ページがシーケンス機能プログラミングマニュアルと同一の内容を示しており、判定値の変更はありません。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mokouliszt/mel-device",
3
- "version": "0.3.0",
4
- "description": "Model-aware Mitsubishi MELSEC and MELFA device notation validator.",
3
+ "version": "0.4.0",
4
+ "description": "Model-aware Mitsubishi MELSEC, MELFA and FR inverter device notation validator.",
5
5
  "license": "MIT",
6
6
  "author": "mokouliszt",
7
7
  "type": "module",
@@ -37,7 +37,11 @@
37
37
  "mx-r",
38
38
  "mx-f",
39
39
  "cr800",
40
- "melfa"
40
+ "melfa",
41
+ "inverter",
42
+ "fr-a800",
43
+ "fr-f800",
44
+ "fr-e800"
41
45
  ],
42
46
  "engines": {
43
47
  "node": ">=18"