Skip to content

Commit 73a0fe2

Browse files
authored
Decouple prompt parsing from DUT runner, some stability fixes (#5)
1 parent a14654f commit 73a0fe2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3365
-1390
lines changed

dut/zwave-js/handlers/behaviors/addMode.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,22 @@ import {
44
} from "alcalzone-shared/deferred-promise";
55
import { registerHandler } from "../../prompt-handlers.ts";
66
import { InclusionStrategy, type InclusionOptions } from "zwave-js";
7-
import { SecurityClass } from "@zwave-js/core";
87
import { wait } from "alcalzone-shared/async";
98

10-
export const FORCE_S0 = "force_s0";
119
const PIN_PROMISE = "pin promise";
1210

1311
registerHandler(/.*/, {
1412
onPrompt: async (ctx) => {
15-
if (/Include.+into the DUT network/i.test(ctx.promptText)) {
16-
// This is an empty prompt, just click Ok to proceed
17-
return "Ok";
18-
}
19-
20-
// Auto-click Ok for "observe the DUT" prompts
21-
if (ctx.promptText.toLowerCase().includes("activate the add mode")) {
22-
const { driver, state } = ctx;
13+
// Handle ACTIVATE_NETWORK_MODE for ADD mode
14+
if (
15+
ctx.message?.type === "ACTIVATE_NETWORK_MODE" &&
16+
ctx.message.mode === "ADD"
17+
) {
18+
const { driver, state, message } = ctx;
2319
state.set(PIN_PROMISE, createDeferredPromise<string>());
2420

25-
const forceS0 = state.get(FORCE_S0) === true;
2621
let inclusionOptions: InclusionOptions;
27-
if (forceS0) {
28-
state.delete(FORCE_S0);
22+
if (message.forceS0) {
2923
inclusionOptions = {
3024
strategy: InclusionStrategy.Security_S0,
3125
};
@@ -73,17 +67,14 @@ registerHandler(/.*/, {
7367
},
7468

7569
onLog: async (ctx) => {
76-
const pinPromise = ctx.state.get(PIN_PROMISE) as
77-
| DeferredPromise<string>
78-
| undefined;
79-
if (!pinPromise) return;
70+
if (ctx.message?.type === "S2_PIN_CODE") {
71+
const pinPromise = ctx.state.get(PIN_PROMISE) as
72+
| DeferredPromise<string>
73+
| undefined;
74+
if (!pinPromise) return;
8075

81-
const match = ctx.logText.match(/PIN( Code)?: (?<pin>\d{5})/i);
82-
const pin = match?.groups?.pin;
83-
if (pin) {
84-
console.log("Detected PIN code:", pin);
85-
pinPromise.resolve(pin);
86-
// handled
76+
console.log("Detected PIN code:", ctx.message.pin);
77+
pinPromise.resolve(ctx.message.pin);
8778
return true;
8879
}
8980
},

dut/zwave-js/handlers/behaviors/capabilities.ts

Lines changed: 91 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,135 +3,112 @@ import {
33
type PromptContext,
44
type PromptResponse,
55
} from "../../prompt-handlers.ts";
6+
import type {
7+
DUTCapabilityId,
8+
CCCapabilityQueryMessage,
9+
} from "../../../../src/ctt-message-types.ts";
610

7-
const questions: {
8-
pattern: RegExp;
9-
answer: PromptResponse | ((ctx: PromptContext) => PromptResponse);
10-
}[] = [
11-
{ pattern: /allows the end user to establish association/i, answer: "No" },
12-
{ pattern: /(capable|able) to display the last.+state/i, answer: "Yes" },
13-
{
14-
pattern: /(DUT's UI|current.+state|visuali[sz]ation).+is visible/i,
15-
answer: "Ok",
16-
},
17-
{ pattern: /icon type.+match the actual device/i, answer: "Yes" },
18-
{
19-
pattern: /Does the DUT use the identify command for any other purpose/i,
20-
answer: "No",
21-
},
22-
{ pattern: /able to send a Start\/Stop Level Change/i, answer: "Yes" },
23-
{
24-
pattern: /allow to set a dimming 'Duration' for 'Setting the Level'/i,
25-
answer: "Yes",
26-
},
27-
{
28-
pattern:
29-
/allow to set a ('Start Level'|dimming 'Duration') for '(Start|Stop) Level Change'/i,
30-
answer: "Yes",
31-
},
32-
{
33-
pattern:
34-
/activate and deactivate the '(audible|visual) notification' subsystem/i,
35-
answer: "Yes",
36-
},
37-
{
38-
pattern: /Does the DUT control.+COMMAND_CLASS_BASIC.+version 1/i,
39-
answer: "Yes",
40-
},
41-
{
42-
pattern: /Does the DUT control.+COMMAND_CLASS_INDICATOR.+version 3/i,
43-
answer: "Yes",
44-
},
45-
{
46-
pattern: /Does the DUT control.+COMMAND_CLASS_VERSION.+version 2/i,
47-
answer: "Yes",
48-
},
49-
{
50-
pattern: /Does the DUT control.+COMMAND_CLASS_WAKE_UP.+version 2/i,
51-
answer: "Yes",
11+
// DUT capability responses by capabilityId
12+
const dutCapabilityResponses: Record<
13+
DUTCapabilityId,
14+
PromptResponse | ((ctx: PromptContext) => PromptResponse)
15+
> = {
16+
ESTABLISH_ASSOCIATION: "No",
17+
DISPLAY_LAST_STATE: "Yes",
18+
QR_CODE: "Yes",
19+
LEARN_MODE: "No",
20+
LEARN_MODE_ACCESSIBLE: "No",
21+
FACTORY_RESET: "Yes",
22+
REMOVE_FAILED_NODE: "Yes",
23+
ICON_TYPE_MATCH: "Yes",
24+
IDENTIFY_OTHER_PURPOSE: "No",
25+
CONTROLS_UNLISTED_CCS: "No",
26+
ALL_DOCUMENTED_AS_CONTROLLED: "Yes",
27+
PARTIAL_CONTROL_DOCUMENTED: (ctx) => {
28+
// Entry Control CC and User Code CC is marked as partial control in the certification portal
29+
if (
30+
ctx.testName.includes("CCR_EntryControlCC") ||
31+
ctx.testName.includes("CCR_UserCodeCC")
32+
) {
33+
return "Yes";
34+
}
35+
return "No";
5236
},
53-
{ pattern: /provide a QR Code scanning capability/i, answer: "Yes" },
54-
{ pattern: /can be reset to factory settings/i, answer: "Yes" },
55-
{ pattern: /Does the DUT support Learn Mode/i, answer: "No" },
56-
{ pattern: /Is the Learn Mode accessible/i, answer: "No" },
57-
{ pattern: /lock or unlock the Anti-Theft feature/i, answer: "No" },
58-
{ pattern: /offering a possibility to remove the failed/i, answer: "Yes" },
37+
MAINS_POWERED: "Yes",
38+
};
5939

60-
// Command Class Control
61-
{
62-
pattern: /control any further Command Classes which are not listed/i,
63-
answer: "No",
64-
},
65-
{
66-
pattern: /Are all of them correctly documented as controlled/i,
67-
answer: "Yes",
68-
},
40+
// CC capability responses by commandClass and capabilityId
41+
type CCCapabilityKey = `${string}:${string}`;
42+
const ccCapabilityResponses: Record<
43+
CCCapabilityKey,
44+
PromptResponse | ((msg: CCCapabilityQueryMessage) => PromptResponse)
45+
> = {
46+
// Multilevel Switch capabilities
47+
"Multilevel Switch:START_STOP_LEVEL_CHANGE": "Yes",
48+
"Multilevel Switch:SET_DIMMING_DURATION": "Yes",
49+
"Multilevel Switch:SET_LEVEL_CHANGE_PARAMS": "Yes",
6950

70-
// Door Lock
71-
{
72-
pattern: /configure the door handles of a v[14] supporting end node/i,
73-
answer: "Yes",
74-
},
51+
// Barrier Operator capabilities
52+
"Barrier Operator:CONTROL_EVENT_SIGNALING": "Yes",
7553

76-
// Configuration CC
77-
{
78-
pattern: /allow to reset one particular configuration parameter/i,
79-
answer: "Yes",
80-
},
54+
// Anti-Theft capabilities
55+
"Anti-Theft:LOCK_UNLOCK": "No",
8156

82-
// Notification CC
83-
{
84-
pattern:
85-
/allow to create rules or commands based on received notifications/i,
86-
answer: "Yes",
87-
},
88-
{
89-
pattern: /capability to update its Notification list/i,
90-
answer: "Yes",
91-
},
57+
// Door Lock capabilities
58+
"Door Lock:CONFIGURE_DOOR_HANDLES": "Yes",
9259

93-
// Notification Report verification ready
94-
{
95-
pattern: /verifies if the DUT displays.+Notification Report.+click 'OK'/i,
96-
answer: "Ok",
97-
},
60+
// Configuration capabilities
61+
"Configuration:RESET_SINGLE_PARAM": "Yes",
9862

99-
// User Code CC
100-
{ pattern: /able to (modify|erase|add).+User Code/i, answer: "Yes" },
101-
{ pattern: /able to set the Keypad Mode/i, answer: "Yes" },
102-
{ pattern: /able to (set|disable).+Admin Code/i, answer: "Yes" },
63+
// Notification capabilities
64+
"Notification:CREATE_RULES_FROM_NOTIFICATIONS": "Yes",
65+
"Notification:UPDATE_NOTIFICATION_LIST": "Yes",
10366

104-
// Entry Control CC
105-
{ pattern: /able to configure the keypad/i, answer: "Yes" },
67+
// User Code capabilities
68+
"User Code:MODIFY_USER_CODE": "Yes",
69+
"User Code:SET_KEYPAD_MODE": "Yes",
70+
"User Code:SET_ADMIN_CODE": "Yes",
10671

107-
// Multilevel Sensor CC
108-
{
109-
pattern: /navigate to '[^']+' on DUT's UI and make '[^']+' scale visible/i,
110-
answer: "Ok",
111-
},
72+
// Entry Control capabilities
73+
"Entry Control:CONFIGURE_KEYPAD": "Yes",
74+
};
11275

113-
// Generic
114-
{ pattern: /Retry\?/i, answer: "No" },
115-
{
116-
pattern: /partial control behavior documented/i,
117-
answer: (ctx) => {
118-
if (ctx.testName.includes("CCR_EntryControlCC")) {
119-
return "Yes"; // Entry Control CC is marked as partial control in the certification portal
120-
}
121-
return "No";
122-
},
123-
},
124-
];
76+
// CC version control - which CC versions we control
77+
const controlledCCVersions: Record<string, number[]> = {
78+
Basic: [1, 2],
79+
Indicator: [1, 2, 3, 4],
80+
Version: [1, 2, 3],
81+
"Wake Up": [1, 2, 3],
82+
};
12583

12684
registerHandler(/.*/, {
12785
onPrompt: async (ctx) => {
128-
for (const q of questions) {
129-
if (q.pattern.test(ctx.promptText)) {
130-
if (typeof q.answer === "function") {
131-
return q.answer(ctx);
132-
} else {
133-
return q.answer;
86+
if (ctx.message?.type === "DUT_CAPABILITY_QUERY") {
87+
const response = dutCapabilityResponses[ctx.message.capabilityId];
88+
if (response !== undefined) {
89+
return typeof response === "function" ? response(ctx) : response;
90+
}
91+
}
92+
93+
if (ctx.message?.type === "CC_CAPABILITY_QUERY") {
94+
const { commandClass, capabilityId } = ctx.message;
95+
96+
// Special handling for CONTROLS_CC with version
97+
if (capabilityId === "CONTROLS_CC" && "version" in ctx.message) {
98+
const versions = controlledCCVersions[commandClass];
99+
if (versions?.includes(ctx.message.version)) {
100+
return "Yes";
134101
}
102+
return "No";
103+
}
104+
105+
// Look up standard capability response
106+
const key: CCCapabilityKey = `${commandClass}:${capabilityId}`;
107+
const response = ccCapabilityResponses[key];
108+
if (response !== undefined) {
109+
return typeof response === "function"
110+
? response(ctx.message)
111+
: response;
135112
}
136113
}
137114

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,20 @@
11
import {
22
registerHandler,
3-
type PromptContext,
43
type PromptResponse,
54
} from "../../prompt-handlers.ts";
65

7-
// State key for storing warning context
8-
const DISREGARD_RECOMMENDATION_CONTEXT = "disregardRecommendationContext";
9-
10-
// Enum for different recommendation reasons - extend as needed
11-
enum Recommendation {
12-
IndicatorReportInAGI = "IndicatorReportInAGI",
13-
}
14-
15-
// Context stored in state
16-
type DisregardRecommendationContext = {
17-
recommendation: Recommendation;
18-
};
19-
20-
// Rules for how to respond based on reason - static response or function
21-
type RuleResponse = PromptResponse | ((ctx: PromptContext) => PromptResponse);
22-
23-
const rules: Record<Recommendation, RuleResponse> = {
24-
[Recommendation.IndicatorReportInAGI]: "Yes",
6+
// Rules for how to respond based on recommendation type
7+
const rules: Record<string, PromptResponse> = {
8+
INDICATOR_REPORT_IN_AGI: "Yes",
259
};
2610

2711
registerHandler(/.*/, {
28-
onLog: async (ctx) => {
29-
let recContext: DisregardRecommendationContext | undefined;
30-
// Detect INDICATOR_REPORT not advertised in AGI Command List Report
31-
if (
32-
/INDICATOR_REPORT.+RECOMMENDED.+does not advertise.+AGI Command List Report/is.test(
33-
ctx.logText
34-
)
35-
) {
36-
recContext = {
37-
recommendation: Recommendation.IndicatorReportInAGI,
38-
};
39-
}
40-
41-
if (recContext) {
42-
ctx.state.set(DISREGARD_RECOMMENDATION_CONTEXT, recContext);
43-
}
44-
45-
return undefined;
46-
},
47-
4812
onPrompt: async (ctx) => {
49-
if (
50-
!/Is it intended to disregard the recommendation\?/i.test(ctx.promptText)
51-
) {
13+
if (ctx.message?.type !== "SHOULD_DISREGARD_RECOMMENDATION") {
5214
return undefined;
5315
}
5416

55-
const context = ctx.state.get(DISREGARD_RECOMMENDATION_CONTEXT) as
56-
| DisregardRecommendationContext
57-
| undefined;
58-
if (!context) return undefined;
59-
60-
ctx.state.delete(DISREGARD_RECOMMENDATION_CONTEXT);
61-
62-
const rule = rules[context.recommendation];
63-
return typeof rule === "function" ? rule(ctx) : rule;
17+
const rule = rules[ctx.message.recommendationType];
18+
return rule ?? undefined;
6419
},
6520
});

0 commit comments

Comments
 (0)