@aryacode/dsh-engineering 0.8.4 → 0.9.1
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/CHANGELOG.md +41 -0
- package/README.md +7 -1
- package/cordis.patch.yml +7 -0
- package/entries/resources.js +23 -0
- package/package.json +6 -3
- package/vendor/ftview/lib/edit/tools.d.ts +1 -1
- package/vendor/ftview/lib/edit/tools.js +26 -6
- package/vendor/ftview/lib/edit.js +2 -1
- package/vendor/ftview/lib/session.d.ts +9 -0
- package/vendor/ftview/lib/session.js +11 -0
- package/vendor/ftview/lib/style.d.ts +60 -0
- package/vendor/ftview/lib/style.js +109 -0
- package/vendor/l5x/lib/engine/style.d.ts +67 -0
- package/vendor/l5x/lib/engine/style.js +119 -0
- package/vendor/l5x/lib/session.d.ts +9 -0
- package/vendor/l5x/lib/session.js +11 -0
- package/vendor/resources/LICENSE-CONTENT.md +17 -0
- package/vendor/resources/SKILL.md +27 -0
- package/vendor/resources/SOURCE.json +6 -0
- package/vendor/resources/corpus/craft/craft-comments-and-intent.md +29 -0
- package/vendor/resources/corpus/craft/craft-debounce-and-filtering.md +29 -0
- package/vendor/resources/corpus/craft/craft-fault-handling.md +32 -0
- package/vendor/resources/corpus/craft/craft-naming.md +30 -0
- package/vendor/resources/corpus/craft/craft-review-checklist.md +29 -0
- package/vendor/resources/corpus/craft/craft-units-and-ranges.md +32 -0
- package/vendor/resources/corpus/iec/iec-ld-fbd-equivalence.md +31 -0
- package/vendor/resources/corpus/iec/iec-sequences-and-sfc.md +32 -0
- package/vendor/resources/corpus/iec/iec-st-structure.md +33 -0
- package/vendor/resources/corpus/iec/iec-state-machines.md +33 -0
- package/vendor/resources/corpus/iec/iec-task-model.md +31 -0
- package/vendor/resources/corpus/iec/iec-types-and-scaling.md +32 -0
- package/vendor/resources/corpus/optix/optix-alarms-and-trends.md +30 -0
- package/vendor/resources/corpus/optix/optix-bindings-and-expressions.md +30 -0
- package/vendor/resources/corpus/optix/optix-netlogic-usage.md +29 -0
- package/vendor/resources/corpus/optix/optix-ui-structure.md +29 -0
- package/vendor/resources/corpus/rockwell/rockwell-aoi-and-udt.md +32 -0
- package/vendor/resources/corpus/rockwell/rockwell-fault-and-alarm-words.md +31 -0
- package/vendor/resources/corpus/rockwell/rockwell-hmi-handshake.md +33 -0
- package/vendor/resources/corpus/rockwell/rockwell-program-structure.md +32 -0
- package/vendor/resources/corpus/rockwell/rockwell-safety-boundaries.md +31 -0
- package/vendor/resources/corpus/rockwell/rockwell-tag-naming.md +33 -0
- package/vendor/resources/corpus/view/view-alarm-presentation.md +32 -0
- package/vendor/resources/corpus/view/view-colour-and-contrast.md +31 -0
- package/vendor/resources/corpus/view/view-faceplates.md +32 -0
- package/vendor/resources/corpus/view/view-hpm-01.md +33 -0
- package/vendor/resources/corpus/view/view-navigation.md +31 -0
- package/vendor/resources/corpus/view/view-popup-patterns.md +31 -0
- package/vendor/resources/corpus/view/view-screen-hierarchy.md +31 -0
- package/vendor/resources/corpus/yaskawa/yaskawa-cam-and-gear.md +31 -0
- package/vendor/resources/corpus/yaskawa/yaskawa-error-handling.md +31 -0
- package/vendor/resources/corpus/yaskawa/yaskawa-motion-function-blocks.md +32 -0
- package/vendor/resources/corpus/yaskawa/yaskawa-pou-organisation.md +29 -0
- package/vendor/resources/corpus/yaskawa/yaskawa-task-rates.md +30 -0
- package/vendor/resources/index.json +873 -0
- package/vendor/resources/lib/corpus.d.ts +80 -0
- package/vendor/resources/lib/corpus.js +126 -0
- package/vendor/resources/lib/index.d.ts +55 -0
- package/vendor/resources/lib/index.js +88 -0
- package/vendor/resources/lib/profile.d.ts +113 -0
- package/vendor/resources/lib/profile.js +52 -0
- package/vendor/resources/lib/store.d.ts +22 -0
- package/vendor/resources/lib/store.js +35 -0
- package/vendor/resources/lib/tools.d.ts +138 -0
- package/vendor/resources/lib/tools.js +190 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infer the house style of one L5X project.
|
|
3
|
+
*
|
|
4
|
+
* Every number here is counted from the export, and the shape is deliberately
|
|
5
|
+
* the bundle-wide profile schema (`packages/resources/src/profile.ts`) rather
|
|
6
|
+
* than an L5X-specific one, so the resources row can validate and store it.
|
|
7
|
+
* Nothing is inferred from fewer than a handful of samples, and the confidence
|
|
8
|
+
* field says how much of the project the inference actually saw.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-engineering-l5x/engine/style
|
|
11
|
+
*/
|
|
12
|
+
/** Classify one identifier's case convention. */
|
|
13
|
+
function caseOf(identifier) {
|
|
14
|
+
if (/^[A-Z][A-Za-z0-9]*$/u.test(identifier))
|
|
15
|
+
return 'PascalCase';
|
|
16
|
+
if (/^[a-z][A-Za-z0-9]*$/u.test(identifier))
|
|
17
|
+
return 'camelCase';
|
|
18
|
+
if (/^[A-Za-z0-9]+(?:_[A-Za-z0-9]+)+$/u.test(identifier))
|
|
19
|
+
return 'snake_case';
|
|
20
|
+
return 'other';
|
|
21
|
+
}
|
|
22
|
+
/** The most common value in a counter, with its count. */
|
|
23
|
+
function top(counts) {
|
|
24
|
+
let best;
|
|
25
|
+
for (const [value, count] of counts) {
|
|
26
|
+
if (best === undefined || count > best.count)
|
|
27
|
+
best = { value, count };
|
|
28
|
+
}
|
|
29
|
+
return best;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read the style of one project.
|
|
33
|
+
*
|
|
34
|
+
* @param project - Snapshot the session opened.
|
|
35
|
+
* @returns The profile.
|
|
36
|
+
*/
|
|
37
|
+
export function styleProfileOf(project) {
|
|
38
|
+
const document = project.document;
|
|
39
|
+
const rungs = document.routines
|
|
40
|
+
.filter(routine => routine.body.type === 'RLL')
|
|
41
|
+
.flatMap(routine => (routine.body.type === 'RLL' ? routine.body.rungs.map(rung => ({ routine: routine.name, rung })) : []));
|
|
42
|
+
const identifiers = new Map();
|
|
43
|
+
const prefixes = new Map();
|
|
44
|
+
const cases = new Map();
|
|
45
|
+
let sealIn = 0;
|
|
46
|
+
let latchInstructions = 0;
|
|
47
|
+
let commented = 0;
|
|
48
|
+
const evidence = [];
|
|
49
|
+
const aoiNames = new Set(document.aois.map(aoi => aoi.name.toUpperCase()));
|
|
50
|
+
let firstSealInRung;
|
|
51
|
+
for (const { routine, rung } of rungs) {
|
|
52
|
+
if (rung.comment !== undefined)
|
|
53
|
+
commented += 1;
|
|
54
|
+
const text = rung.text;
|
|
55
|
+
for (const match of text.matchAll(/[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)*/gu)) {
|
|
56
|
+
const whole = match[0];
|
|
57
|
+
if (aoiNames.has(whole.toUpperCase()))
|
|
58
|
+
continue;
|
|
59
|
+
const base = whole.split('.')[0] ?? whole;
|
|
60
|
+
if (base.length < 5)
|
|
61
|
+
continue;
|
|
62
|
+
identifiers.set(base, (identifiers.get(base) ?? 0) + 1);
|
|
63
|
+
const parts = base.split('_');
|
|
64
|
+
if (parts.length > 1) {
|
|
65
|
+
const prefix = parts[0];
|
|
66
|
+
if (/^[A-Z][A-Za-z]{1,6}$/u.test(prefix) && base.length > prefix.length + 3) {
|
|
67
|
+
prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const kind = caseOf(base);
|
|
71
|
+
if (kind !== 'other')
|
|
72
|
+
cases.set(kind, (cases.get(kind) ?? 0) + 1);
|
|
73
|
+
}
|
|
74
|
+
for (const coil of text.matchAll(/OTE\(([^),]+)\)/gu)) {
|
|
75
|
+
const target = (coil[1] ?? '').trim();
|
|
76
|
+
const contact = `XIC(${target})`;
|
|
77
|
+
const coilIndex = coil.index ?? 0;
|
|
78
|
+
const contactIndex = text.indexOf(contact);
|
|
79
|
+
if (contactIndex >= 0 && contactIndex < coilIndex) {
|
|
80
|
+
sealIn += 1;
|
|
81
|
+
if (firstSealInRung === undefined)
|
|
82
|
+
firstSealInRung = rung.number;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
latchInstructions += (text.match(/OT[LU]\(/gu) ?? []).length;
|
|
86
|
+
}
|
|
87
|
+
const total = rungs.length;
|
|
88
|
+
const dominant = top(prefixes);
|
|
89
|
+
const dominantCase = top(cases);
|
|
90
|
+
const prefixShare = dominant === undefined ? 0 : dominant.count / Math.max(1, [...prefixes.values()].reduce((sum, count) => sum + count, 0));
|
|
91
|
+
const routineCases = new Map();
|
|
92
|
+
for (const routine of document.routines) {
|
|
93
|
+
const kind = caseOf(routine.name);
|
|
94
|
+
if (kind !== 'other')
|
|
95
|
+
routineCases.set(kind, (routineCases.get(kind) ?? 0) + 1);
|
|
96
|
+
}
|
|
97
|
+
const routineCase = top(routineCases);
|
|
98
|
+
if (dominant !== undefined)
|
|
99
|
+
evidence.push({ count: dominant.count, note: `tag prefix "${dominant.value}" appears ${dominant.count} time(s)` });
|
|
100
|
+
if (firstSealInRung !== undefined)
|
|
101
|
+
evidence.push({ line: firstSealInRung, count: sealIn, note: `seal-in rungs detected from rung ${firstSealInRung}` });
|
|
102
|
+
evidence.push({ count: total, note: `${total} ladder rung(s) read, ${commented} with a comment` });
|
|
103
|
+
const confidence = total >= 50 && prefixShare >= 0.5 ? 'high' : total >= 10 ? 'medium' : 'low';
|
|
104
|
+
return {
|
|
105
|
+
schema: 1,
|
|
106
|
+
source: { kind: 'l5x', path: project.path, sha256: project.sha256 },
|
|
107
|
+
naming: {
|
|
108
|
+
tags: { prefixes: [...prefixes].sort((left, right) => right[1] - left[1]).slice(0, 6).map(([value, count]) => ({ value, count })), case: dominantCase?.value ?? 'PascalCase' },
|
|
109
|
+
routines: { case: routineCase?.value ?? 'PascalCase', examples: document.routines.slice(0, 5).map(routine => routine.name) },
|
|
110
|
+
},
|
|
111
|
+
idioms: {
|
|
112
|
+
sealIn: { count: sealIn, form: sealIn > 0 ? 'XIC(seal) in parallel with the start contact, in series with the stop contact' : 'not observed' },
|
|
113
|
+
latch: { count: latchInstructions },
|
|
114
|
+
},
|
|
115
|
+
comments: { total, commented, ratio: total === 0 ? 0 : Number((commented / total).toFixed(2)) },
|
|
116
|
+
evidence,
|
|
117
|
+
confidence,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @module dsh-engineering-l5x/session
|
|
13
13
|
*/
|
|
14
14
|
import { type L5xDocument } from './model/index.ts';
|
|
15
|
+
import { type L5xStyleProfile } from './engine/style.ts';
|
|
15
16
|
import type { ExecContext } from './core/tool.ts';
|
|
16
17
|
import { L5xError } from './core/errors.ts';
|
|
17
18
|
import type { FsService, FsTarget } from './core/tool.ts';
|
|
@@ -93,6 +94,14 @@ export declare class ProjectSession {
|
|
|
93
94
|
* @returns The open project.
|
|
94
95
|
*/
|
|
95
96
|
require(projectId: string | undefined, exec?: ExecContext): Promise<ProjectSnapshot>;
|
|
97
|
+
/**
|
|
98
|
+
* Read the house style of one open project.
|
|
99
|
+
*
|
|
100
|
+
* @param projectId - Explicit handle, or undefined for the chat's only project.
|
|
101
|
+
* @param exec - Calling context.
|
|
102
|
+
* @returns The profile in the shared resources schema.
|
|
103
|
+
*/
|
|
104
|
+
styleProfile(projectId: string | undefined, exec?: ExecContext): Promise<L5xStyleProfile>;
|
|
96
105
|
/**
|
|
97
106
|
* Resolve one path through the filesystem provider without reading it.
|
|
98
107
|
*
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { createHash, randomUUID } from 'node:crypto';
|
|
15
15
|
import { L5xParseError, loadL5x } from "./model/index.js";
|
|
16
|
+
import { styleProfileOf } from "./engine/style.js";
|
|
16
17
|
import { L5xError } from "./core/errors.js";
|
|
17
18
|
import { decodeExport } from "./fs.js";
|
|
18
19
|
/** A failure raised by an analysis engine. */
|
|
@@ -130,6 +131,16 @@ export class ProjectSession {
|
|
|
130
131
|
}
|
|
131
132
|
return project;
|
|
132
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Read the house style of one open project.
|
|
136
|
+
*
|
|
137
|
+
* @param projectId - Explicit handle, or undefined for the chat's only project.
|
|
138
|
+
* @param exec - Calling context.
|
|
139
|
+
* @returns The profile in the shared resources schema.
|
|
140
|
+
*/
|
|
141
|
+
async styleProfile(projectId, exec) {
|
|
142
|
+
return styleProfileOf(await this.require(projectId, exec));
|
|
143
|
+
}
|
|
133
144
|
/**
|
|
134
145
|
* Resolve one path through the filesystem provider without reading it.
|
|
135
146
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Content licence
|
|
2
|
+
|
|
3
|
+
The pages under `corpus/` are written for this bundle. They summarise engineering practice and
|
|
4
|
+
implementation rules; they do **not** reproduce the text of any standard or vendor manual.
|
|
5
|
+
|
|
6
|
+
Each page names its basis in front matter (`basis:`). Those names are attribution, not endorsement:
|
|
7
|
+
|
|
8
|
+
- **IEC 61131-3** — the programmable-controller languages the pages describe (ST, LD, FBD, SFC).
|
|
9
|
+
- **ISA-101** — human-machine interface design, especially the high-performance HMI model.
|
|
10
|
+
- **ISA-18.2** — alarm management lifecycle and presentation.
|
|
11
|
+
- **Rockwell / Yaskawa / FactoryTalk** — vendor documentation and field practice for those products.
|
|
12
|
+
|
|
13
|
+
Rule IDs (`CRAFT-NAME-01`, `VIEW-HPM-02`, …) identify this bundle's own rules. They are not the
|
|
14
|
+
standards' clause numbers, and a finding that cites one is this bundle's opinion about your code, not a
|
|
15
|
+
certification.
|
|
16
|
+
|
|
17
|
+
Standards documents are copyrighted. If you need the normative text, obtain it from the issuing body.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Engineering standards
|
|
2
|
+
|
|
3
|
+
Use this skill whenever you write, change or review control code or HMI screens — before you author, not
|
|
4
|
+
after.
|
|
5
|
+
|
|
6
|
+
## Read first
|
|
7
|
+
|
|
8
|
+
- `engineering_reference` with no arguments lists the corpus. Search it for the language and the domain
|
|
9
|
+
you are about to touch (`iec state machine`, `rockwell naming`, `view alarm`, `craft fault`), then read
|
|
10
|
+
the page. Each page carries rule IDs; cite the ID when you report a finding.
|
|
11
|
+
- When a project or export is open, call `engineering_profile` **before** writing. It reports the naming,
|
|
12
|
+
idioms and visual conventions the plant already uses, with evidence and a confidence level. Follow the
|
|
13
|
+
profile where it is confident; where it is low, say so and ask.
|
|
14
|
+
|
|
15
|
+
## Order of work
|
|
16
|
+
|
|
17
|
+
1. Read the profile (existing project) or the standard pages (new work).
|
|
18
|
+
2. Author the change with the project's own names and visual language.
|
|
19
|
+
3. `engineering_review` on the draft. Fix findings you agree with; explain the ones you do not.
|
|
20
|
+
4. Then run the product's own validation (`draft_validate`, `ftview_validate`, `mw_compile`).
|
|
21
|
+
|
|
22
|
+
## Honesty rules
|
|
23
|
+
|
|
24
|
+
- A profile is evidence, not authority: quote the counts and the source path when you rely on it.
|
|
25
|
+
- A rule ID is this bundle's opinion. Never present it as a standards clause or a certification.
|
|
26
|
+
- Do not reproduce standards text; link the page instead.
|
|
27
|
+
- If the corpus or a profile is unavailable, say which one and continue with the product's own checks.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-comments-and-intent
|
|
3
|
+
title: Comments that explain why
|
|
4
|
+
domain: craft
|
|
5
|
+
level: intro
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [comments, intent]
|
|
8
|
+
applies_to: [l5x, ftview, mwi, optix]
|
|
9
|
+
rules: [CRAFT-COMMENT-01, CRAFT-COMMENT-02, CRAFT-COMMENT-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A comment that repeats the code is noise; a comment that explains the decision is maintenance.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **CRAFT-COMMENT-01** Comment the intent and the constraint (`// 250 ms: debounce for a bouncing
|
|
18
|
+
proximity switch`), not the instruction.
|
|
19
|
+
- **CRAFT-COMMENT-02** Every non-obvious rung gets a rung comment; every routine gets one line of purpose.
|
|
20
|
+
- **CRAFT-COMMENT-03** Record the change reason and date where the project does that, and never leave
|
|
21
|
+
commented-out logic as the only record.
|
|
22
|
+
|
|
23
|
+
## Apply
|
|
24
|
+
|
|
25
|
+
Write the comment as if the reader has the drawing but not the meeting.
|
|
26
|
+
|
|
27
|
+
## Anti-patterns
|
|
28
|
+
|
|
29
|
+
`// set bit`; a wall of comments at the top that no one updates; TODOs without an owner.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-debounce-and-filtering
|
|
3
|
+
title: Debounce, hysteresis and filtering
|
|
4
|
+
domain: craft
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [filter, debounce, hysteresis]
|
|
8
|
+
applies_to: [l5x, mwi]
|
|
9
|
+
rules: [CRAFT-FILTER-01, CRAFT-FILTER-02]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Real sensors are noisy and real processes overshoot. Filter in the PLC with a stated time, not in the
|
|
14
|
+
operator's head.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **CRAFT-FILTER-01** Debounce discrete inputs with a timer sized from the sensor's bounce, and document
|
|
19
|
+
the time next to the timer.
|
|
20
|
+
- **CRAFT-FILTER-02** Use hysteresis for thresholds that would otherwise chatter, and keep the band in one
|
|
21
|
+
place per signal.
|
|
22
|
+
|
|
23
|
+
## Apply
|
|
24
|
+
|
|
25
|
+
When a value "flickers" on the HMI, find the filter time before adding another one downstream.
|
|
26
|
+
|
|
27
|
+
## Anti-patterns
|
|
28
|
+
|
|
29
|
+
Filters stacked three deep with no owner; a debounce so long it hides a real trip.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-fault-handling
|
|
3
|
+
title: Faults: detect, latch, annunciate, reset
|
|
4
|
+
domain: craft
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [faults, reset, diagnostics]
|
|
8
|
+
applies_to: [l5x, mwi, optix]
|
|
9
|
+
rules: [CRAFT-FAULT-01, CRAFT-FAULT-02, CRAFT-FAULT-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A fault is a fact about the machine, not a message. The logic must detect it, remember it, tell someone,
|
|
14
|
+
and define recovery.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **CRAFT-FAULT-01** Detect at the source and set one bit for one cause; do not infer a fault from a
|
|
19
|
+
downstream symptom.
|
|
20
|
+
- **CRAFT-FAULT-02** Latch with a reset that only succeeds when the cause is gone; a reset that always
|
|
21
|
+
clears is a way to run a broken machine.
|
|
22
|
+
- **CRAFT-FAULT-03** Record first-out and the time, and surface it where the operator acts.
|
|
23
|
+
|
|
24
|
+
## Apply
|
|
25
|
+
|
|
26
|
+
For each fault, write the four sentences: what detects it, what it stops, how it is shown, how it is
|
|
27
|
+
cleared.
|
|
28
|
+
|
|
29
|
+
## Anti-patterns
|
|
30
|
+
|
|
31
|
+
Faults that clear themselves on the next scan; one reset for unrelated faults; a fault with no operator
|
|
32
|
+
message.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-naming
|
|
3
|
+
title: Naming that survives the project
|
|
4
|
+
domain: craft
|
|
5
|
+
level: intro
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [naming, readability]
|
|
8
|
+
applies_to: [l5x, ftview, mwi, optix]
|
|
9
|
+
rules: [CRAFT-NAME-01, CRAFT-NAME-02, CRAFT-NAME-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Names are the interface between the code and everyone who will read it after you.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **CRAFT-NAME-01** One case convention per project (PascalCase for tags is common; follow the project).
|
|
18
|
+
Consistency matters more than the choice.
|
|
19
|
+
- **CRAFT-NAME-02** Booleans read as assertions (`MotorRunning`, `DoorClosed`), not as questions or
|
|
20
|
+
negatives (`NotStopped`).
|
|
21
|
+
- **CRAFT-NAME-03** Put units and ranges in the description, not hidden in the name (`Temp_C`, `Speed_mps`).
|
|
22
|
+
|
|
23
|
+
## Apply
|
|
24
|
+
|
|
25
|
+
When a name does not fit the project's scheme, prefer the project's scheme; when adding a new concept, add
|
|
26
|
+
it to the scheme and document the prefix.
|
|
27
|
+
|
|
28
|
+
## Anti-patterns
|
|
29
|
+
|
|
30
|
+
`tmp`, `data`, `flag`; names that encode the wiring (`DI_3`); renaming a published tag without a migration.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-review-checklist
|
|
3
|
+
title: Pre-handover review checklist
|
|
4
|
+
domain: craft
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [review, handover, checklist]
|
|
8
|
+
applies_to: [l5x, ftview, mwi, optix]
|
|
9
|
+
rules: [CRAFT-REVIEW-01, CRAFT-REVIEW-02]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Handover is a review, not a save. Walk the same list every time, and record what was not checked.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **CRAFT-REVIEW-01** Before handover: every fault has a message and a reset, every screen has a way back,
|
|
18
|
+
every changed tag is documented, and the backup is taken and named.
|
|
19
|
+
- **CRAFT-REVIEW-02** Distinguish "checked" from "not checked" in the handover note; an unchecked item is
|
|
20
|
+
not a pass.
|
|
21
|
+
|
|
22
|
+
## Apply
|
|
23
|
+
|
|
24
|
+
Run the product's own checks first, then `engineering_review`, then the human walkthrough with the
|
|
25
|
+
operator.
|
|
26
|
+
|
|
27
|
+
## Anti-patterns
|
|
28
|
+
|
|
29
|
+
A handover that lists only what changed; signing off a screen nobody navigated.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: craft-units-and-ranges
|
|
3
|
+
title: Units, ranges and engineering values
|
|
4
|
+
domain: craft
|
|
5
|
+
level: intro
|
|
6
|
+
basis: [field practice]
|
|
7
|
+
tags: [units, range, scaling]
|
|
8
|
+
applies_to: [l5x, mwi, optix]
|
|
9
|
+
rules: [CRAFT-UNIT-01, CRAFT-UNIT-02, CRAFT-UNIT-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Every number the operator sees should have a unit, a range and a resolution. A number without those is a
|
|
14
|
+
guess.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **CRAFT-UNIT-01** Carry raw counts and engineering units as separate values with one documented
|
|
19
|
+
conversion.
|
|
20
|
+
- **CRAFT-UNIT-02** State the valid range and what happens outside it (clamp, fault, ignore) at the
|
|
21
|
+
conversion point.
|
|
22
|
+
- **CRAFT-UNIT-03** Display the unit next to the value and keep the decimal places consistent with the
|
|
23
|
+
resolution.
|
|
24
|
+
|
|
25
|
+
## Apply
|
|
26
|
+
|
|
27
|
+
When a value moves to a new screen, move its unit and range with it.
|
|
28
|
+
|
|
29
|
+
## Anti-patterns
|
|
30
|
+
|
|
31
|
+
Two conversions for one signal; a percentage displayed as a fraction; more decimal places than the sensor
|
|
32
|
+
has.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-ld-fbd-equivalence
|
|
3
|
+
title: Ladder, FBD and ST: same logic, different reading
|
|
4
|
+
domain: iec
|
|
5
|
+
level: intro
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [ladder, fbd, st, translation]
|
|
8
|
+
applies_to: [l5x, mwi]
|
|
9
|
+
rules: [IEC-LANG-01, IEC-LANG-02]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
The languages are notations, not designs. Choose the one that makes the intent obvious and keep one
|
|
14
|
+
implementation of each behaviour.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **IEC-LANG-01** Ladder for permissives, interlocks and outputs an electrician will trace; ST for
|
|
19
|
+
arithmetic, loops and state machines; FBD for signal flow. Do not translate a whole routine because you
|
|
20
|
+
prefer another language.
|
|
21
|
+
- **IEC-LANG-02** One behaviour, one implementation. A seal-in duplicated in LD and ST will diverge; if a
|
|
22
|
+
block is reused, make it an FB/AOI and call it.
|
|
23
|
+
|
|
24
|
+
## Apply
|
|
25
|
+
|
|
26
|
+
When reviewing inherited code, read it in its own language before proposing a rewrite. A "modernisation"
|
|
27
|
+
that changes scan-order semantics is a behaviour change.
|
|
28
|
+
|
|
29
|
+
## Anti-patterns
|
|
30
|
+
|
|
31
|
+
ST one-liners that hide three outputs; ladder with arithmetic chains no one can trace.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-sequences-and-sfc
|
|
3
|
+
title: Sequences, steps and timeouts
|
|
4
|
+
domain: iec
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [sequence, sfc, step]
|
|
8
|
+
applies_to: [mwi, l5x]
|
|
9
|
+
rules: [IEC-SEQ-01, IEC-SEQ-02, IEC-SEQ-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A sequence is a state machine whose states are process steps. The difference is that each step has a
|
|
14
|
+
completion condition and a timeout.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **IEC-SEQ-01** Give every step a timeout and a defined action on expiry. A sequence that can wait forever
|
|
19
|
+
is a fault waiting for an operator.
|
|
20
|
+
- **IEC-SEQ-02** Separate the step advance from the step action: advance on `done OR timeout`, act on the
|
|
21
|
+
current step.
|
|
22
|
+
- **IEC-SEQ-03** Make hold, abort and reset explicit transitions to named states, not flags that freeze
|
|
23
|
+
the sequence wherever it was.
|
|
24
|
+
|
|
25
|
+
## Apply
|
|
26
|
+
|
|
27
|
+
Draw the sequence (states and transitions) before coding it, and keep the drawing with the project. SFC is
|
|
28
|
+
appropriate when the drawing is the specification; a `CASE` machine is simpler when steps share actions.
|
|
29
|
+
|
|
30
|
+
## Anti-patterns
|
|
31
|
+
|
|
32
|
+
Advancing on a timer alone; a reset that clears outputs without leaving the step.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-st-structure
|
|
3
|
+
title: Structured Text that reads like a specification
|
|
4
|
+
domain: iec
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [st, structure, declarations]
|
|
8
|
+
applies_to: [mwi, optix, l5x]
|
|
9
|
+
rules: [IEC-ST-01, IEC-ST-02, IEC-ST-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A POU should be readable top to bottom without scrolling sideways: declarations grouped, one intent per
|
|
14
|
+
block, no hidden state. The reader is a maintenance technician at 2 a.m., not the author.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **IEC-ST-01** Group declarations by role (`VAR_INPUT`, `VAR_OUTPUT`, `VAR`), then by purpose, with a
|
|
19
|
+
comment above each group. Do not interleave.
|
|
20
|
+
- **IEC-ST-02** One POU, one responsibility. A POU that sequences, scales, filters and writes HMI data has
|
|
21
|
+
four reasons to change; split it.
|
|
22
|
+
- **IEC-ST-03** No side effects in conditions. A call inside an `IF` that changes state makes the scan
|
|
23
|
+
order part of the logic; call it explicitly, one statement per line.
|
|
24
|
+
|
|
25
|
+
## Apply
|
|
26
|
+
|
|
27
|
+
Order the body: input conditioning, permissives, sequence or control, outputs, diagnostics. A reader can
|
|
28
|
+
then stop at the block they need.
|
|
29
|
+
|
|
30
|
+
## Anti-patterns
|
|
31
|
+
|
|
32
|
+
Deeply nested `IF` (three levels or more) hiding permissives; magic numbers instead of named constants;
|
|
33
|
+
declarations added wherever there was space.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-state-machines
|
|
3
|
+
title: State machines that fail predictably
|
|
4
|
+
domain: iec
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [state-machine, case, sequence]
|
|
8
|
+
applies_to: [mwi, l5x]
|
|
9
|
+
rules: [IEC-SM-01, IEC-SM-02, IEC-SM-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A state machine is a contract: exactly one state is active, every transition has a cause, and an
|
|
14
|
+
unexpected state is handled rather than ignored.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **IEC-SM-01** Use one integer or enumeration `step` for the active state and a `CASE` (ST) or
|
|
19
|
+
integer-compare chain (LD) to act on it. Do not scatter `IF step =` tests through the program.
|
|
20
|
+
- **IEC-SM-02** Every `CASE` has an `ELSE` (or a default branch) that forces a known safe state and raises
|
|
21
|
+
a diagnostic. An out-of-range step must never leave outputs energised.
|
|
22
|
+
- **IEC-SM-03** Entry actions run once on the transition; continuous actions live in the state body. Mixing
|
|
23
|
+
them makes a re-entry repeat motion.
|
|
24
|
+
|
|
25
|
+
## Apply
|
|
26
|
+
|
|
27
|
+
Name states after the machine condition (`Idle`, `Homing`, `Ready`, `Cutting`, `Faulted`), not after
|
|
28
|
+
numbers in the HMI. Keep a parallel `stepTime` for timeouts.
|
|
29
|
+
|
|
30
|
+
## Anti-patterns
|
|
31
|
+
|
|
32
|
+
Two variables claiming to be the state; timers inside the transition condition; a `GOTO` around the
|
|
33
|
+
`CASE`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-task-model
|
|
3
|
+
title: Tasks, scan time and determinism
|
|
4
|
+
domain: iec
|
|
5
|
+
level: specialist
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [task, scan, watchdog]
|
|
8
|
+
applies_to: [mwi, l5x]
|
|
9
|
+
rules: [IEC-TASK-01, IEC-TASK-02, IEC-TASK-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
The task schedule is part of the program. A control loop that moved to a slower task is a behaviour
|
|
14
|
+
change even when the code is untouched.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **IEC-TASK-01** Put motion and fast interlocks in a fixed-rate task; put HMI handshakes and diagnostics in
|
|
19
|
+
a slower one. Document the rate next to the POU.
|
|
20
|
+
- **IEC-TASK-02** No unbounded work in a cyclic task: no file I/O, no waiting loops, no long string
|
|
21
|
+
handling. Bound every loop.
|
|
22
|
+
- **IEC-TASK-03** Size the watchdog from the measured worst case, not the average. A watchdog that fires
|
|
23
|
+
only under load is doing its job.
|
|
24
|
+
|
|
25
|
+
## Apply
|
|
26
|
+
|
|
27
|
+
Record for each task: period, priority, watchdog, worst measured scan, and what happens when it overruns.
|
|
28
|
+
|
|
29
|
+
## Anti-patterns
|
|
30
|
+
|
|
31
|
+
Copying a fast task into a slow one and accepting the jitter; hiding a blocking call behind a one-shot.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iec-types-and-scaling
|
|
3
|
+
title: Types, ranges and scaling without surprises
|
|
4
|
+
domain: iec
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [IEC 61131-3]
|
|
7
|
+
tags: [types, scaling, real]
|
|
8
|
+
applies_to: [mwi, l5x, optix]
|
|
9
|
+
rules: [IEC-TYPE-01, IEC-TYPE-02, IEC-TYPE-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Most field faults blamed on "the PLC" are a divide by zero, an overflow, or a REAL compared with `=`.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **IEC-TYPE-01** Declare the narrowest type that holds the range, and say the range in the comment
|
|
18
|
+
(`INT 0..1000 mm`). Never rely on the default type.
|
|
19
|
+
- **IEC-TYPE-02** Guard every division and scale: check the divisor, clamp the result, and handle the
|
|
20
|
+
out-of-range case explicitly.
|
|
21
|
+
- **IEC-TYPE-03** Compare REALs with a tolerance, never with equality. For counters and positions, use
|
|
22
|
+
integers where the resolution allows.
|
|
23
|
+
|
|
24
|
+
## Apply
|
|
25
|
+
|
|
26
|
+
Keep one scale/offset pair per signal, in one place. If two blocks scale the same input, they will
|
|
27
|
+
disagree.
|
|
28
|
+
|
|
29
|
+
## Anti-patterns
|
|
30
|
+
|
|
31
|
+
`INT` for a millisecond timer that can exceed 32 s; scaling in the HMI and again in the PLC; a REAL used
|
|
32
|
+
as a state number.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: optix-alarms-and-trends
|
|
3
|
+
title: Alarms and trends in Optix
|
|
4
|
+
domain: optix
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [FactoryTalk Optix, ISA-18.2]
|
|
7
|
+
tags: [alarms, trends]
|
|
8
|
+
applies_to: [optix]
|
|
9
|
+
rules: [OPTIX-ALM-01, OPTIX-ALM-02]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
Alarms and trends are process evidence. Their configuration is part of the design, not a runtime
|
|
14
|
+
afterthought.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **OPTIX-ALM-01** Configure alarm limits and priorities in the model (type or instance), so every
|
|
19
|
+
instance inherits a reviewed definition.
|
|
20
|
+
- **OPTIX-ALM-02** Choose trend sampling from the process dynamics, and keep the same time base across
|
|
21
|
+
pens that will be compared.
|
|
22
|
+
|
|
23
|
+
## Apply
|
|
24
|
+
|
|
25
|
+
Review the alarm list as a document: condition, limit, priority, message, action. Trends should answer a
|
|
26
|
+
specific question the operator or engineer has.
|
|
27
|
+
|
|
28
|
+
## Anti-patterns
|
|
29
|
+
|
|
30
|
+
Alarms enabled per instance with different limits; trend pens on unrelated time bases.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: optix-bindings-and-expressions
|
|
3
|
+
title: Bindings and expressions
|
|
4
|
+
domain: optix
|
|
5
|
+
level: practitioner
|
|
6
|
+
basis: [FactoryTalk Optix]
|
|
7
|
+
tags: [bindings, expressions, converters]
|
|
8
|
+
applies_to: [optix]
|
|
9
|
+
rules: [OPTIX-BIND-01, OPTIX-BIND-02, OPTIX-BIND-03]
|
|
10
|
+
---
|
|
11
|
+
## Principle
|
|
12
|
+
|
|
13
|
+
A binding moves a value; an expression computes one. Mixing them hides where a number comes from.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **OPTIX-BIND-01** Bind raw values point to point; put formatting and unit conversion in a formatter or a
|
|
18
|
+
converter with a name, not inline in the binding.
|
|
19
|
+
- **OPTIX-BIND-02** Validate expressions with the expression tool before attaching them, and handle the
|
|
20
|
+
null/empty case explicitly.
|
|
21
|
+
- **OPTIX-BIND-03** Keep one source of truth per value: do not bind and also write the same property from
|
|
22
|
+
NetLogic.
|
|
23
|
+
|
|
24
|
+
## Apply
|
|
25
|
+
|
|
26
|
+
When a value looks wrong, trace binding → converter → source before editing any of them.
|
|
27
|
+
|
|
28
|
+
## Anti-patterns
|
|
29
|
+
|
|
30
|
+
Long inline expressions in a property; two writers for one property; conversions that silently round.
|