@mokouliszt/mel-device 0.3.1 → 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 +122 -4
- package/README.md +122 -4
- package/dist/index.cjs +400 -12
- package/dist/index.d.ts +33 -1
- package/docs/manual-evidence.md +97 -0
- package/package.json +7 -3
package/README.ja.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md)
|
|
4
4
|
|
|
5
|
-
三菱電機 MELSEC / MX
|
|
5
|
+
三菱電機 MELSEC / MXコントローラ、MELFAロボットコントローラ、およびFRインバータのシーケンス機能のデバイス表記を、シリーズとCPU/コントローラ/インバータ型名を含めて検証する、依存ライブラリなしのnpmパッケージです。
|
|
6
6
|
|
|
7
|
-
対応シリーズは iQ-R、iQ-F、MX-R、MX-F、CR800-R/D/Q
|
|
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
|
|
62
|
+
CR800-R/D/QおよびFRインバータのデバイス範囲はマニュアル上固定されているため、これらのシリーズでは`configuredPoints`を参照しません。ただしインバータでは、`maximum`が後述のSERIAL依存のT/C 32点拡張と未割付のST点数を許可します。インバータが受け付ける点数指定は`configuredPoints.ST`のみです。
|
|
63
63
|
|
|
64
64
|
```js
|
|
65
65
|
isValidDevice("D99", {
|
|
@@ -182,6 +182,123 @@ CR800-Qの表6-16にある`U3En\G512-G1023`は、表6-13の固定範囲`G10000-G
|
|
|
182
182
|
|
|
183
183
|
定数(`K100`, `HFF`, 実数・文字列)は「デバイス」ではないため判定対象外です。ラベル、構造体メンバ、SLMPのバイナリデバイスコードも対象外です。
|
|
184
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
|
+
|
|
185
302
|
## API
|
|
186
303
|
|
|
187
304
|
### `isValidDevice(value, options): boolean`
|
|
@@ -219,6 +336,7 @@ CR800-Qの表6-16にある`U3En\G512-G1023`は、表6-13の固定範囲`G10000-G
|
|
|
219
336
|
- MX-R: MXR300-16/-32/-64、MXR500-128/-256
|
|
220
337
|
- MX-F: MXF100系のマニュアル対象10機種(`MXF100` をファミリ別名として受付)
|
|
221
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
|
|
222
340
|
|
|
223
341
|
## 資料
|
|
224
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
|
|
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,
|
|
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
|
|
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 `
|
|
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", {
|
|
@@ -182,6 +182,123 @@ Bit specifications, digit specifications, indirect specifications, index modific
|
|
|
182
182
|
|
|
183
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.
|
|
184
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
|
+
|
|
185
302
|
## API
|
|
186
303
|
|
|
187
304
|
### `isValidDevice(value, options): boolean`
|
|
@@ -219,6 +336,7 @@ Returns the list of models covered by the manuals.
|
|
|
219
336
|
- MX-R: MXR300-16/-32/-64 and MXR500-128/-256
|
|
220
337
|
- MX-F: the 10 models covered by the MXF100-series manual (`MXF100` is accepted as a family alias)
|
|
221
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
|
|
222
340
|
|
|
223
341
|
## References
|
|
224
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,8 @@ 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] }
|
|
113
120
|
};
|
|
114
121
|
|
|
115
122
|
// CPU buffer memory access devices (U3En\G, U3En\HG).
|
|
@@ -166,6 +173,99 @@ const CR800_ALLOCATION_LABELS = {
|
|
|
166
173
|
ccLinkIef: "CC-Link IE Field"
|
|
167
174
|
};
|
|
168
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
|
+
|
|
169
269
|
class MelDeviceError extends Error {
|
|
170
270
|
constructor(message, result) {
|
|
171
271
|
super(message);
|
|
@@ -175,22 +275,35 @@ class MelDeviceError extends Error {
|
|
|
175
275
|
}
|
|
176
276
|
}
|
|
177
277
|
|
|
278
|
+
function compactName(value) {
|
|
279
|
+
return value.normalize("NFKC").trim().toUpperCase().replace(/[\s_-]/g, "");
|
|
280
|
+
}
|
|
281
|
+
|
|
178
282
|
function canonicalSeries(value) {
|
|
179
283
|
if (typeof value !== "string") return null;
|
|
180
|
-
const compact = value
|
|
284
|
+
const compact = compactName(value);
|
|
285
|
+
if (Object.prototype.hasOwnProperty.call(FR_SERIES_ALIASES, compact)) return "FR800";
|
|
181
286
|
return ({ IQR: "iQ-R", IQF: "iQ-F", MXR: "MX-R", MXF: "MX-F", CR800: "CR800", CR800R: "CR800", CR800D: "CR800", CR800Q: "CR800" })[compact] || null;
|
|
182
287
|
}
|
|
183
288
|
|
|
184
289
|
function cr800ModelFromSeries(value) {
|
|
185
290
|
if (typeof value !== "string") return null;
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
|
188
301
|
}
|
|
189
302
|
|
|
190
303
|
function normalizeModel(series, value) {
|
|
191
304
|
const s = canonicalSeries(series);
|
|
192
305
|
if (!s) return null;
|
|
193
|
-
if (s === "CR800" && value == null) return
|
|
306
|
+
if ((s === "CR800" || s === "FR800") && value == null) return modelFromSeries(series);
|
|
194
307
|
if (typeof value !== "string") return null;
|
|
195
308
|
const model = value.normalize("NFKC").trim().toUpperCase().replace(/\s+/g, "");
|
|
196
309
|
if (s === "iQ-R") {
|
|
@@ -210,16 +323,20 @@ function normalizeModel(series, value) {
|
|
|
210
323
|
return MX_R_MODELS.includes(model) ? model : null;
|
|
211
324
|
}
|
|
212
325
|
if (s === "CR800") {
|
|
213
|
-
const compact = String(value)
|
|
326
|
+
const compact = compactName(String(value));
|
|
214
327
|
return ({ R: "CR800-R", D: "CR800-D", Q: "CR800-Q", CR800R: "CR800-R", CR800D: "CR800-D", CR800Q: "CR800-Q" })[compact] || null;
|
|
215
328
|
}
|
|
329
|
+
if (s === "FR800") {
|
|
330
|
+
const compact = compactName(String(value));
|
|
331
|
+
return FR_SERIES_ALIASES[compact] || FR_MODEL_ALIASES[compact] || null;
|
|
332
|
+
}
|
|
216
333
|
if (model === "MXF100") return model;
|
|
217
334
|
return MX_F_MODELS.includes(model) ? model : null;
|
|
218
335
|
}
|
|
219
336
|
|
|
220
337
|
function getSupportedModels(series) {
|
|
221
338
|
const s = canonicalSeries(series);
|
|
222
|
-
const inferred =
|
|
339
|
+
const inferred = modelFromSeries(series);
|
|
223
340
|
if (inferred) return [inferred];
|
|
224
341
|
return s ? [...MODEL_LISTS[s]] : [];
|
|
225
342
|
}
|
|
@@ -246,6 +363,9 @@ function invalid(input, series, model, mode, code, message, extra = {}) {
|
|
|
246
363
|
}
|
|
247
364
|
|
|
248
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;
|
|
249
369
|
const radix = RADIX[prefix];
|
|
250
370
|
if (radix === "series-x") return series === "iQ-F" ? 8 : 16;
|
|
251
371
|
return radix || 10;
|
|
@@ -259,13 +379,14 @@ function parseNumber(text, radix) {
|
|
|
259
379
|
}
|
|
260
380
|
|
|
261
381
|
function parseDirect(text, series) {
|
|
262
|
-
const
|
|
382
|
+
const aliases = series === "FR800" ? FR_TIMER_ALIAS : TIMER_ALIAS;
|
|
383
|
+
const timerPrefixes = Object.keys(aliases).sort((a, b) => b.length - a.length);
|
|
263
384
|
for (const alias of timerPrefixes) {
|
|
264
385
|
if (!text.startsWith(alias)) continue;
|
|
265
386
|
const addressText = text.slice(alias.length);
|
|
266
387
|
const address = parseNumber(addressText, 10);
|
|
267
388
|
if (address !== null) {
|
|
268
|
-
const [prefix, timerPart] =
|
|
389
|
+
const [prefix, timerPart] = aliases[alias];
|
|
269
390
|
return { kind: "direct", prefix, notationPrefix: alias, address, addressText, radix: 10, timerPart };
|
|
270
391
|
}
|
|
271
392
|
}
|
|
@@ -582,6 +703,265 @@ function analyzeCr800(value, parsed, series, model, mode, normalized, operation,
|
|
|
582
703
|
};
|
|
583
704
|
}
|
|
584
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
|
+
|
|
585
965
|
function analyzeDevice(value, options = {}) {
|
|
586
966
|
const mode = options.mode || "default";
|
|
587
967
|
const inputMode = options.inputMode || "exact";
|
|
@@ -597,7 +977,7 @@ function analyzeDevice(value, options = {}) {
|
|
|
597
977
|
}
|
|
598
978
|
const series = canonicalSeries(options.series);
|
|
599
979
|
if (!series) return invalid(value, null, null, mode, "UNSUPPORTED_SERIES", `Supported series: ${SERIES.join(", ")}.`);
|
|
600
|
-
const seriesModel =
|
|
980
|
+
const seriesModel = modelFromSeries(options.series);
|
|
601
981
|
const model = normalizeModel(options.series, options.model ?? seriesModel);
|
|
602
982
|
if (!model) return invalid(value, series, null, mode, "UNSUPPORTED_MODEL", `Unsupported ${series} model: ${String(options.model)}.`);
|
|
603
983
|
if (seriesModel && model !== seriesModel) return invalid(value, series, model, mode, "MODEL_SERIES_MISMATCH", `${String(options.series)} conflicts with model ${model}.`);
|
|
@@ -605,11 +985,19 @@ function analyzeDevice(value, options = {}) {
|
|
|
605
985
|
const featureError = validateCr800Features(options.cr800Features);
|
|
606
986
|
if (featureError) return invalid(value, series, model, mode, "INVALID_CR800_FEATURES", featureError);
|
|
607
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
|
+
}
|
|
608
995
|
const parsed = parseDevice(value, { series, inputMode });
|
|
609
996
|
const normalized = inputMode === "friendly" ? normalizeDevice(value) : (typeof value === "string" ? value : null);
|
|
610
997
|
if (!parsed) return invalid(value, series, model, mode, "INVALID_SYNTAX", "The value is not a supported MELSEC/MELFA device notation.", { normalized });
|
|
611
998
|
|
|
612
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);
|
|
613
1001
|
|
|
614
1002
|
if (series === "iQ-F" && ["V", "LT", "LST", "ZR", "RD", "FX", "FY", "FD"].includes(parsed.prefix)) {
|
|
615
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 });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
export type MelSeries =
|
|
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;
|
package/docs/manual-evidence.md
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
| MX-R | MELSEC MXコントローラ MX-Rモデルユーザーズマニュアル | SH-082640-D | 221(性能仕様)、350(デバイス一覧・デフォルト点数)、351-352(点数設定・最大使用範囲)、376-378(ユニット/CPUバッファアクセス指定方法)、380-381(インデックス修飾) |
|
|
12
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
|
|
|
@@ -153,3 +157,96 @@ iQ-F本文69ページではZによる全デバイスのインデックス修飾
|
|
|
153
157
|
- 6.2.2/表6-4: `U3En\G10000-G10511`をロボットCPU No.nの領域として使用
|
|
154
158
|
|
|
155
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.
|
|
4
|
-
"description": "Model-aware Mitsubishi MELSEC and
|
|
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"
|