homebridge-securitysystem 0.0.1-matter
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/.gitattributes +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +46 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +17 -0
- package/.github/ISSUE_TEMPLATE/questions-answers.yml +40 -0
- package/.github/demo.jpg +0 -0
- package/.github/dependabot.yml +19 -0
- package/.github/release.yml +36 -0
- package/.github/workflows/build-package.yml +49 -0
- package/.github/workflows/bump-version.yml +59 -0
- package/.github/workflows/dependency-review.yml +26 -0
- package/.github/workflows/publish.yml +155 -0
- package/.github/workflows/stale-activity.yml +27 -0
- package/AGENTS.md +247 -0
- package/CHANGELOG.md +572 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +5 -0
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/SECURITY.md +12 -0
- package/config.schema.json +934 -0
- package/dist/conditions/already-triggered-condition.d.ts +10 -0
- package/dist/conditions/already-triggered-condition.js +22 -0
- package/dist/conditions/already-triggered-condition.js.map +1 -0
- package/dist/conditions/arming-in-progress-condition.d.ts +10 -0
- package/dist/conditions/arming-in-progress-condition.js +21 -0
- package/dist/conditions/arming-in-progress-condition.js.map +1 -0
- package/dist/conditions/arming-lock-condition.d.ts +10 -0
- package/dist/conditions/arming-lock-condition.js +43 -0
- package/dist/conditions/arming-lock-condition.js.map +1 -0
- package/dist/conditions/condition.d.ts +22 -0
- package/dist/conditions/condition.js +19 -0
- package/dist/conditions/condition.js.map +1 -0
- package/dist/conditions/double-knock-condition.d.ts +24 -0
- package/dist/conditions/double-knock-condition.js +73 -0
- package/dist/conditions/double-knock-condition.js.map +1 -0
- package/dist/conditions/not-armed-condition.d.ts +10 -0
- package/dist/conditions/not-armed-condition.js +26 -0
- package/dist/conditions/not-armed-condition.js.map +1 -0
- package/dist/conditions/trigger-already-running-condition.d.ts +10 -0
- package/dist/conditions/trigger-already-running-condition.js +21 -0
- package/dist/conditions/trigger-already-running-condition.js.map +1 -0
- package/dist/constants/default-constant.d.ts +24 -0
- package/dist/constants/default-constant.js +27 -0
- package/dist/constants/default-constant.js.map +1 -0
- package/dist/constants/homekit-constant.d.ts +2 -0
- package/dist/constants/homekit-constant.js +3 -0
- package/dist/constants/homekit-constant.js.map +1 -0
- package/dist/constants/matter-constant.d.ts +21 -0
- package/dist/constants/matter-constant.js +61 -0
- package/dist/constants/matter-constant.js.map +1 -0
- package/dist/handlers/sensor-handler.d.ts +24 -0
- package/dist/handlers/sensor-handler.js +63 -0
- package/dist/handlers/sensor-handler.js.map +1 -0
- package/dist/handlers/state-handler.d.ts +49 -0
- package/dist/handlers/state-handler.js +204 -0
- package/dist/handlers/state-handler.js.map +1 -0
- package/dist/handlers/switch-handler.d.ts +28 -0
- package/dist/handlers/switch-handler.js +95 -0
- package/dist/handlers/switch-handler.js.map +1 -0
- package/dist/handlers/trip-handler.d.ts +44 -0
- package/dist/handlers/trip-handler.js +156 -0
- package/dist/handlers/trip-handler.js.map +1 -0
- package/dist/homekit/homekit-registrar.d.ts +12 -0
- package/dist/homekit/homekit-registrar.js +23 -0
- package/dist/homekit/homekit-registrar.js.map +1 -0
- package/dist/homekit/service-factory.d.ts +12 -0
- package/dist/homekit/service-factory.js +24 -0
- package/dist/homekit/service-factory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/condition-context-interface.d.ts +14 -0
- package/dist/interfaces/condition-context-interface.js +2 -0
- package/dist/interfaces/condition-context-interface.js.map +1 -0
- package/dist/interfaces/hap-types-interface.d.ts +9 -0
- package/dist/interfaces/hap-types-interface.js +2 -0
- package/dist/interfaces/hap-types-interface.js.map +1 -0
- package/dist/interfaces/mqtt-status-payload-interface.d.ts +6 -0
- package/dist/interfaces/mqtt-status-payload-interface.js +2 -0
- package/dist/interfaces/mqtt-status-payload-interface.js.map +1 -0
- package/dist/interfaces/options-interface.d.ts +87 -0
- package/dist/interfaces/options-interface.js +2 -0
- package/dist/interfaces/options-interface.js.map +1 -0
- package/dist/interfaces/persisted-state-interface.d.ts +6 -0
- package/dist/interfaces/persisted-state-interface.js +2 -0
- package/dist/interfaces/persisted-state-interface.js.map +1 -0
- package/dist/interfaces/service-registry-interface.d.ts +10 -0
- package/dist/interfaces/service-registry-interface.js +2 -0
- package/dist/interfaces/service-registry-interface.js.map +1 -0
- package/dist/interfaces/system-state-interface.d.ts +22 -0
- package/dist/interfaces/system-state-interface.js +2 -0
- package/dist/interfaces/system-state-interface.js.map +1 -0
- package/dist/schemas/arming-lock-schema.d.ts +10 -0
- package/dist/schemas/arming-lock-schema.js +12 -0
- package/dist/schemas/arming-lock-schema.js.map +1 -0
- package/dist/schemas/error-schema.d.ts +4 -0
- package/dist/schemas/error-schema.js +7 -0
- package/dist/schemas/error-schema.js.map +1 -0
- package/dist/schemas/mode-request-schema.d.ts +10 -0
- package/dist/schemas/mode-request-schema.js +18 -0
- package/dist/schemas/mode-request-schema.js.map +1 -0
- package/dist/schemas/status-response-schema.d.ts +7 -0
- package/dist/schemas/status-response-schema.js +10 -0
- package/dist/schemas/status-response-schema.js.map +1 -0
- package/dist/schemas/trip-mode-request-schema.d.ts +9 -0
- package/dist/schemas/trip-mode-request-schema.js +24 -0
- package/dist/schemas/trip-mode-request-schema.js.map +1 -0
- package/dist/security-system-platform.d.ts +19 -0
- package/dist/security-system-platform.js +56 -0
- package/dist/security-system-platform.js.map +1 -0
- package/dist/security-system.d.ts +28 -0
- package/dist/security-system.js +169 -0
- package/dist/security-system.js.map +1 -0
- package/dist/services/command-service.d.ts +16 -0
- package/dist/services/command-service.js +65 -0
- package/dist/services/command-service.js.map +1 -0
- package/dist/services/configuration-service.d.ts +24 -0
- package/dist/services/configuration-service.js +189 -0
- package/dist/services/configuration-service.js.map +1 -0
- package/dist/services/event-bus-service.d.ts +12 -0
- package/dist/services/event-bus-service.js +17 -0
- package/dist/services/event-bus-service.js.map +1 -0
- package/dist/services/matter-service.d.ts +69 -0
- package/dist/services/matter-service.js +363 -0
- package/dist/services/matter-service.js.map +1 -0
- package/dist/services/mqtt-service.d.ts +16 -0
- package/dist/services/mqtt-service.js +97 -0
- package/dist/services/mqtt-service.js.map +1 -0
- package/dist/services/server-service.d.ts +28 -0
- package/dist/services/server-service.js +275 -0
- package/dist/services/server-service.js.map +1 -0
- package/dist/services/storage-service.d.ts +14 -0
- package/dist/services/storage-service.js +70 -0
- package/dist/services/storage-service.js.map +1 -0
- package/dist/services/webhook-service.d.ts +16 -0
- package/dist/services/webhook-service.js +68 -0
- package/dist/services/webhook-service.js.map +1 -0
- package/dist/tests/conditions.test.d.ts +1 -0
- package/dist/tests/conditions.test.js +175 -0
- package/dist/tests/conditions.test.js.map +1 -0
- package/dist/tests/matter-service.test.d.ts +1 -0
- package/dist/tests/matter-service.test.js +475 -0
- package/dist/tests/matter-service.test.js.map +1 -0
- package/dist/tests/mqtt-service.test.d.ts +1 -0
- package/dist/tests/mqtt-service.test.js +168 -0
- package/dist/tests/mqtt-service.test.js.map +1 -0
- package/dist/tests/sensor-handler.test.d.ts +1 -0
- package/dist/tests/sensor-handler.test.js +87 -0
- package/dist/tests/sensor-handler.test.js.map +1 -0
- package/dist/tests/server-service.test.d.ts +1 -0
- package/dist/tests/server-service.test.js +188 -0
- package/dist/tests/server-service.test.js.map +1 -0
- package/dist/tests/state-handler.test.d.ts +1 -0
- package/dist/tests/state-handler.test.js +276 -0
- package/dist/tests/state-handler.test.js.map +1 -0
- package/dist/tests/switch-handler.test.d.ts +1 -0
- package/dist/tests/switch-handler.test.js +186 -0
- package/dist/tests/switch-handler.test.js.map +1 -0
- package/dist/tests/trip-handler.test.d.ts +1 -0
- package/dist/tests/trip-handler.test.js +179 -0
- package/dist/tests/trip-handler.test.js.map +1 -0
- package/dist/timers/timer-manager.d.ts +31 -0
- package/dist/timers/timer-manager.js +118 -0
- package/dist/timers/timer-manager.js.map +1 -0
- package/dist/types/event-payload-map-type.d.ts +15 -0
- package/dist/types/event-payload-map-type.js +2 -0
- package/dist/types/event-payload-map-type.js.map +1 -0
- package/dist/types/event-type.d.ts +44 -0
- package/dist/types/event-type.js +20 -0
- package/dist/types/event-type.js.map +1 -0
- package/dist/types/matter-accessory-key-type.d.ts +7 -0
- package/dist/types/matter-accessory-key-type.js +2 -0
- package/dist/types/matter-accessory-key-type.js.map +1 -0
- package/dist/types/mode-type.d.ts +4 -0
- package/dist/types/mode-type.js +2 -0
- package/dist/types/mode-type.js.map +1 -0
- package/dist/types/origin-type.d.ts +9 -0
- package/dist/types/origin-type.js +11 -0
- package/dist/types/origin-type.js.map +1 -0
- package/dist/types/security-state-type.d.ts +11 -0
- package/dist/types/security-state-type.js +13 -0
- package/dist/types/security-state-type.js.map +1 -0
- package/dist/types/sensor-kind-type.d.ts +2 -0
- package/dist/types/sensor-kind-type.js +2 -0
- package/dist/types/sensor-kind-type.js.map +1 -0
- package/dist/types/service-result-type.d.ts +4 -0
- package/dist/types/service-result-type.js +2 -0
- package/dist/types/service-result-type.js.map +1 -0
- package/dist/utils/arming-util.d.ts +19 -0
- package/dist/utils/arming-util.js +24 -0
- package/dist/utils/arming-util.js.map +1 -0
- package/dist/utils/state-util.d.ts +8 -0
- package/dist/utils/state-util.js +29 -0
- package/dist/utils/state-util.js.map +1 -0
- package/docs/MQTT.md +50 -0
- package/package.json +64 -0
- package/src/@types/homebridge-lib.d.ts +11 -0
- package/src/conditions/already-triggered-condition.ts +24 -0
- package/src/conditions/arming-in-progress-condition.ts +23 -0
- package/src/conditions/arming-lock-condition.ts +50 -0
- package/src/conditions/condition.ts +31 -0
- package/src/conditions/double-knock-condition.ts +84 -0
- package/src/conditions/not-armed-condition.ts +30 -0
- package/src/conditions/trigger-already-running-condition.ts +23 -0
- package/src/constants/default-constant.ts +28 -0
- package/src/constants/homekit-constant.ts +2 -0
- package/src/constants/matter-constant.ts +69 -0
- package/src/handlers/sensor-handler.ts +78 -0
- package/src/handlers/state-handler.ts +257 -0
- package/src/handlers/switch-handler.ts +111 -0
- package/src/handlers/trip-handler.ts +193 -0
- package/src/homekit/homekit-registrar.ts +28 -0
- package/src/homekit/service-factory.ts +35 -0
- package/src/index.ts +9 -0
- package/src/interfaces/condition-context-interface.ts +15 -0
- package/src/interfaces/hap-types-interface.ts +11 -0
- package/src/interfaces/mqtt-status-payload-interface.ts +6 -0
- package/src/interfaces/options-interface.ts +116 -0
- package/src/interfaces/persisted-state-interface.ts +7 -0
- package/src/interfaces/service-registry-interface.ts +11 -0
- package/src/interfaces/system-state-interface.ts +25 -0
- package/src/schemas/arming-lock-schema.ts +12 -0
- package/src/schemas/error-schema.ts +7 -0
- package/src/schemas/mode-request-schema.ts +18 -0
- package/src/schemas/status-response-schema.ts +10 -0
- package/src/schemas/trip-mode-request-schema.ts +24 -0
- package/src/security-system-platform.ts +68 -0
- package/src/security-system.ts +199 -0
- package/src/services/command-service.ts +76 -0
- package/src/services/configuration-service.ts +224 -0
- package/src/services/event-bus-service.ts +21 -0
- package/src/services/matter-service.ts +446 -0
- package/src/services/mqtt-service.ts +123 -0
- package/src/services/server-service.ts +327 -0
- package/src/services/storage-service.ts +80 -0
- package/src/services/webhook-service.ts +77 -0
- package/src/tests/conditions.test.ts +212 -0
- package/src/tests/matter-service.test.ts +610 -0
- package/src/tests/mqtt-service.test.ts +231 -0
- package/src/tests/sensor-handler.test.ts +116 -0
- package/src/tests/server-service.test.ts +238 -0
- package/src/tests/state-handler.test.ts +371 -0
- package/src/tests/switch-handler.test.ts +234 -0
- package/src/tests/trip-handler.test.ts +244 -0
- package/src/timers/timer-manager.ts +141 -0
- package/src/types/event-payload-map-type.ts +25 -0
- package/src/types/event-type.ts +61 -0
- package/src/types/matter-accessory-key-type.ts +33 -0
- package/src/types/mode-type.ts +4 -0
- package/src/types/origin-type.ts +9 -0
- package/src/types/security-state-type.ts +11 -0
- package/src/types/sensor-kind-type.ts +2 -0
- package/src/types/service-result-type.ts +4 -0
- package/src/utils/arming-util.ts +44 -0
- package/src/utils/state-util.ts +32 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { SecurityState } from '../types/security-state-type.js';
|
|
3
|
+
import { OriginType } from '../types/origin-type.js';
|
|
4
|
+
import { EventType } from '../types/event-type.js';
|
|
5
|
+
import type { SystemState } from '../interfaces/system-state-interface.js';
|
|
6
|
+
import type { SecuritySystemOptions } from '../interfaces/options-interface.js';
|
|
7
|
+
|
|
8
|
+
function makeState(overrides: Partial<SystemState> = {}): SystemState {
|
|
9
|
+
return {
|
|
10
|
+
currentState: SecurityState.HOME,
|
|
11
|
+
targetState: SecurityState.HOME,
|
|
12
|
+
defaultState: SecurityState.OFF,
|
|
13
|
+
availableTargetStates: [SecurityState.HOME, SecurityState.AWAY, SecurityState.NIGHT, SecurityState.OFF],
|
|
14
|
+
isArming: false,
|
|
15
|
+
isTripping: false,
|
|
16
|
+
isKnocked: false,
|
|
17
|
+
serverAuthenticationAttempts: 0,
|
|
18
|
+
pausedCurrentState: null,
|
|
19
|
+
armingLocks: { global: false, home: false, away: false, night: false },
|
|
20
|
+
modeAwayExtended: false,
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeOptions(overrides: Partial<SecuritySystemOptions> = {}): SecuritySystemOptions {
|
|
26
|
+
return {
|
|
27
|
+
overrideOff: false,
|
|
28
|
+
doubleKnock: false,
|
|
29
|
+
doubleKnockSeconds: 90,
|
|
30
|
+
doubleKnockModes: [],
|
|
31
|
+
triggerSeconds: 0,
|
|
32
|
+
homeTriggerSeconds: null,
|
|
33
|
+
awayTriggerSeconds: null,
|
|
34
|
+
nightTriggerSeconds: null,
|
|
35
|
+
modeAwayExtendedSwitchTriggerSeconds: null,
|
|
36
|
+
trippedMotionSensor: false,
|
|
37
|
+
trippedMotionSensorSeconds: 5,
|
|
38
|
+
homeDoubleKnockSeconds: null,
|
|
39
|
+
awayDoubleKnockSeconds: null,
|
|
40
|
+
nightDoubleKnockSeconds: null,
|
|
41
|
+
...overrides,
|
|
42
|
+
} as unknown as SecuritySystemOptions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── TripHandler tests ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe('TripHandler', async () => {
|
|
48
|
+
const { TripHandler } = await import('../handlers/trip-handler.js');
|
|
49
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
50
|
+
|
|
51
|
+
let state: SystemState;
|
|
52
|
+
let bus: InstanceType<typeof EventBusService>;
|
|
53
|
+
let tripHandler: any;
|
|
54
|
+
const mockSensorHandler = {
|
|
55
|
+
pulseTrippedMotionSensor: vi.fn(),
|
|
56
|
+
setTrippedMotionSensor: vi.fn(),
|
|
57
|
+
resetTrippedMotionSensor: vi.fn(),
|
|
58
|
+
};
|
|
59
|
+
const mockTimers = {
|
|
60
|
+
setTriggerTimer: vi.fn(), clearTriggerTimer: vi.fn(), isTriggerRunning: vi.fn().mockReturnValue(false),
|
|
61
|
+
setTrippedInterval: vi.fn(), clearTrippedInterval: vi.fn(),
|
|
62
|
+
setDoubleKnockTimer: vi.fn(), clearDoubleKnockTimer: vi.fn(),
|
|
63
|
+
clearAll: vi.fn(),
|
|
64
|
+
} as any;
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
state = makeState({ currentState: SecurityState.HOME });
|
|
69
|
+
bus = new EventBusService();
|
|
70
|
+
const mockLog = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
71
|
+
tripHandler = new TripHandler(state, makeOptions(), mockLog as any, bus, mockSensorHandler as any, mockTimers);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('blocks trip when system is disarmed (not overriding)', () => {
|
|
75
|
+
state.currentState = SecurityState.OFF;
|
|
76
|
+
const result = tripHandler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
77
|
+
expect(result.success).toBe(false);
|
|
78
|
+
expect(result.reason).toBe('Trip Switch (Not armed): system is disarmed and override is not enabled');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('blocks trip when arming is in progress', () => {
|
|
82
|
+
state.isArming = true;
|
|
83
|
+
const result = tripHandler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
84
|
+
expect(result.success).toBe(false);
|
|
85
|
+
expect(result.reason).toBe('Trip Switch (Still arming): arm delay countdown is still in progress');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('blocks trip when already triggered', () => {
|
|
89
|
+
state.currentState = SecurityState.TRIGGERED;
|
|
90
|
+
const result = tripHandler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
91
|
+
expect(result.success).toBe(false);
|
|
92
|
+
expect(result.reason).toBe('Security System (Already triggered): alarm is already active');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('blocks trip when trigger timeout is already running', () => {
|
|
96
|
+
state.isTripping = true;
|
|
97
|
+
const result = tripHandler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
98
|
+
expect(result.success).toBe(false);
|
|
99
|
+
expect(result.reason).toBe('Security System (Already tripped): trigger delay countdown is already running');
|
|
100
|
+
state.isTripping = false;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('allows trip when system is armed (HOME mode)', () => {
|
|
104
|
+
state.currentState = SecurityState.HOME;
|
|
105
|
+
const result = tripHandler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
106
|
+
expect(result.success).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('tripped motion sensor', () => {
|
|
110
|
+
it('starts steady-on when trippedMotionSensorSeconds = 0', () => {
|
|
111
|
+
const handler = new TripHandler(
|
|
112
|
+
state,
|
|
113
|
+
makeOptions({ trippedMotionSensor: true, trippedMotionSensorSeconds: 0 }),
|
|
114
|
+
{ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
115
|
+
bus, mockSensorHandler as any, mockTimers,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
handler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
119
|
+
|
|
120
|
+
expect(mockSensorHandler.setTrippedMotionSensor).toHaveBeenCalledWith(true);
|
|
121
|
+
expect(mockSensorHandler.pulseTrippedMotionSensor).not.toHaveBeenCalled();
|
|
122
|
+
expect(mockTimers.setTrippedInterval).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('pulses with interval when trippedMotionSensorSeconds > 0', () => {
|
|
126
|
+
const handler = new TripHandler(
|
|
127
|
+
state,
|
|
128
|
+
makeOptions({ trippedMotionSensor: true, trippedMotionSensorSeconds: 10 }),
|
|
129
|
+
{ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
130
|
+
bus, mockSensorHandler as any, mockTimers,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
handler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
134
|
+
|
|
135
|
+
expect(mockSensorHandler.pulseTrippedMotionSensor).toHaveBeenCalled();
|
|
136
|
+
expect(mockTimers.setTrippedInterval).toHaveBeenCalledWith(10000, expect.any(Function));
|
|
137
|
+
expect(mockSensorHandler.setTrippedMotionSensor).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('resets tripped sensor on cancelTrip', () => {
|
|
141
|
+
const handler = new TripHandler(
|
|
142
|
+
state,
|
|
143
|
+
makeOptions({ trippedMotionSensor: true }),
|
|
144
|
+
{ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
145
|
+
bus, mockSensorHandler as any, mockTimers,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
handler.updateTripSwitch(false, OriginType.REGULAR_SWITCH, false);
|
|
149
|
+
|
|
150
|
+
expect(mockSensorHandler.resetTrippedMotionSensor).toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('cancels trip and emits TRIP_CANCELLED', () => {
|
|
155
|
+
const emitted: unknown[] = [];
|
|
156
|
+
bus.on(EventType.TRIP_CANCELLED, (payload) => emitted.push(payload));
|
|
157
|
+
|
|
158
|
+
tripHandler.updateTripSwitch(false, OriginType.REGULAR_SWITCH, false);
|
|
159
|
+
|
|
160
|
+
expect(emitted).toHaveLength(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('emits TRIP_CANCELLED with stateChanged=false when trip cancelled while triggered', () => {
|
|
164
|
+
state.currentState = SecurityState.TRIGGERED;
|
|
165
|
+
let payload: any;
|
|
166
|
+
bus.on(EventType.TRIP_CANCELLED, (p) => {
|
|
167
|
+
payload = p;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
tripHandler.updateTripSwitch(false, OriginType.REGULAR_SWITCH, false);
|
|
171
|
+
|
|
172
|
+
expect(payload).toBeDefined();
|
|
173
|
+
expect(payload.stateChanged).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('emits TRIP_CANCELLED with stateChanged=true when state has changed', () => {
|
|
177
|
+
state.currentState = SecurityState.TRIGGERED;
|
|
178
|
+
let payload: any;
|
|
179
|
+
bus.on(EventType.TRIP_CANCELLED, (p) => {
|
|
180
|
+
payload = p;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
tripHandler.updateTripSwitch(false, OriginType.INTERNAL, true);
|
|
184
|
+
|
|
185
|
+
expect(payload).toBeDefined();
|
|
186
|
+
expect(payload.stateChanged).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('uses the extended trigger seconds when armed away-extended', () => {
|
|
190
|
+
state.currentState = SecurityState.AWAY;
|
|
191
|
+
state.modeAwayExtended = true;
|
|
192
|
+
const handler = new TripHandler(
|
|
193
|
+
state,
|
|
194
|
+
makeOptions({ modeAwayExtendedSwitchTriggerSeconds: 45 }),
|
|
195
|
+
{ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
196
|
+
bus, mockSensorHandler as any, mockTimers,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
handler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
200
|
+
|
|
201
|
+
expect(mockTimers.setTriggerTimer).toHaveBeenCalledWith(45000, expect.any(Function));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('uses the regular away trigger seconds when not extended', () => {
|
|
205
|
+
state.currentState = SecurityState.AWAY;
|
|
206
|
+
const handler = new TripHandler(
|
|
207
|
+
state,
|
|
208
|
+
makeOptions({ modeAwayExtendedSwitchTriggerSeconds: 45, awayTriggerSeconds: 10 }),
|
|
209
|
+
{ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
210
|
+
bus, mockSensorHandler as any, mockTimers,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
handler.updateTripSwitch(true, OriginType.REGULAR_SWITCH, false);
|
|
214
|
+
|
|
215
|
+
expect(mockTimers.setTriggerTimer).toHaveBeenCalledWith(10000, expect.any(Function));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('triggerIfModeSet allows when current mode matches required', () => {
|
|
219
|
+
state.currentState = SecurityState.HOME;
|
|
220
|
+
const result = tripHandler.triggerIfModeSet(SecurityState.HOME, true);
|
|
221
|
+
expect(result.success).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('triggerIfModeSet blocks when current mode does not match', () => {
|
|
225
|
+
state.currentState = SecurityState.AWAY;
|
|
226
|
+
const result = tripHandler.triggerIfModeSet(SecurityState.HOME, true);
|
|
227
|
+
expect(result.success).toBe(false);
|
|
228
|
+
expect(result.reason).toBe('mode not set');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('triggerIfModeSet allows a trip while triggered when the target matches', () => {
|
|
232
|
+
state.currentState = SecurityState.TRIGGERED;
|
|
233
|
+
state.targetState = SecurityState.HOME;
|
|
234
|
+
const result = tripHandler.triggerIfModeSet(SecurityState.HOME, true);
|
|
235
|
+
expect(result.success).toBe(false);
|
|
236
|
+
expect(result.reason).toBe('Security System (Already triggered): alarm is already active');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('custom trip switch cancellation works with triggerIfModeSet', () => {
|
|
240
|
+
state.currentState = SecurityState.HOME;
|
|
241
|
+
const result = tripHandler.triggerIfModeSet(SecurityState.HOME, false);
|
|
242
|
+
expect(result.success).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Logging } from 'homebridge';
|
|
2
|
+
|
|
3
|
+
type TimerHandle = ReturnType<typeof setTimeout> | null;
|
|
4
|
+
type IntervalHandle = ReturnType<typeof setInterval> | null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Centralised owner of all timer and interval handles used by the security
|
|
8
|
+
* system. Handlers call set/clear methods here rather than mutating the shared
|
|
9
|
+
* SystemState directly, giving a single place where timer lifecycle is managed.
|
|
10
|
+
*/
|
|
11
|
+
export class TimerManager {
|
|
12
|
+
private armTimer: TimerHandle = null;
|
|
13
|
+
private pauseTimer: TimerHandle = null;
|
|
14
|
+
private triggerTimer: TimerHandle = null;
|
|
15
|
+
private doubleKnockTimer: TimerHandle = null;
|
|
16
|
+
private resetTimer: TimerHandle = null;
|
|
17
|
+
private trippedInterval: IntervalHandle = null;
|
|
18
|
+
|
|
19
|
+
constructor(private readonly log: Logging) {}
|
|
20
|
+
|
|
21
|
+
// ── Arm timer ──────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
setArmTimer(ms: number, cb: () => void): void {
|
|
24
|
+
this.clearArmTimer();
|
|
25
|
+
this.armTimer = setTimeout(() => {
|
|
26
|
+
this.armTimer = null;
|
|
27
|
+
cb();
|
|
28
|
+
}, ms);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clearArmTimer(): void {
|
|
32
|
+
if (this.armTimer) {
|
|
33
|
+
clearTimeout(this.armTimer);
|
|
34
|
+
this.armTimer = null;
|
|
35
|
+
this.log.debug('Arming timeout (Cleared)');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Trigger timer ──────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
setTriggerTimer(ms: number, cb: () => void): void {
|
|
42
|
+
this.clearTriggerTimer();
|
|
43
|
+
this.triggerTimer = setTimeout(() => {
|
|
44
|
+
this.triggerTimer = null;
|
|
45
|
+
cb();
|
|
46
|
+
}, ms);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
clearTriggerTimer(): void {
|
|
50
|
+
if (this.triggerTimer) {
|
|
51
|
+
clearTimeout(this.triggerTimer);
|
|
52
|
+
this.triggerTimer = null;
|
|
53
|
+
this.log.debug('Trigger timeout (Cleared)');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isTriggerRunning(): boolean {
|
|
58
|
+
return this.triggerTimer !== null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Pause timer ────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
setPauseTimer(ms: number, cb: () => void): void {
|
|
64
|
+
this.clearPauseTimer();
|
|
65
|
+
this.pauseTimer = setTimeout(() => {
|
|
66
|
+
this.pauseTimer = null;
|
|
67
|
+
cb();
|
|
68
|
+
}, ms);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
clearPauseTimer(): void {
|
|
72
|
+
if (this.pauseTimer) {
|
|
73
|
+
clearTimeout(this.pauseTimer);
|
|
74
|
+
this.pauseTimer = null;
|
|
75
|
+
this.log.debug('Pause timeout (Cleared)');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Double-knock timer ─────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
setDoubleKnockTimer(ms: number, cb: () => void): void {
|
|
82
|
+
this.clearDoubleKnockTimer();
|
|
83
|
+
this.doubleKnockTimer = setTimeout(() => {
|
|
84
|
+
this.doubleKnockTimer = null;
|
|
85
|
+
cb();
|
|
86
|
+
}, ms);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clearDoubleKnockTimer(): void {
|
|
90
|
+
if (this.doubleKnockTimer) {
|
|
91
|
+
clearTimeout(this.doubleKnockTimer);
|
|
92
|
+
this.doubleKnockTimer = null;
|
|
93
|
+
this.log.debug('Double-knock timeout (Cleared)');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Reset timer ────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
setResetTimer(ms: number, cb: () => void): void {
|
|
100
|
+
this.clearResetTimer();
|
|
101
|
+
this.resetTimer = setTimeout(() => {
|
|
102
|
+
this.resetTimer = null;
|
|
103
|
+
cb();
|
|
104
|
+
}, ms);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
clearResetTimer(): void {
|
|
108
|
+
if (this.resetTimer) {
|
|
109
|
+
clearTimeout(this.resetTimer);
|
|
110
|
+
this.resetTimer = null;
|
|
111
|
+
this.log.debug('Reset timeout (Cleared)');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Tripped motion sensor interval ─────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
setTrippedInterval(ms: number, cb: () => void): void {
|
|
118
|
+
this.clearTrippedInterval();
|
|
119
|
+
this.trippedInterval = setInterval(cb, ms);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
clearTrippedInterval(): void {
|
|
123
|
+
if (this.trippedInterval) {
|
|
124
|
+
clearInterval(this.trippedInterval);
|
|
125
|
+
this.trippedInterval = null;
|
|
126
|
+
this.log.debug('Tripped interval (Cleared)');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Bulk clear ─────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/** Clears every active timer and interval. */
|
|
133
|
+
clearAll(): void {
|
|
134
|
+
this.clearTriggerTimer();
|
|
135
|
+
this.clearArmTimer();
|
|
136
|
+
this.clearTrippedInterval();
|
|
137
|
+
this.clearDoubleKnockTimer();
|
|
138
|
+
this.clearPauseTimer();
|
|
139
|
+
this.clearResetTimer();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EventType } from './event-type.js';
|
|
2
|
+
import type {
|
|
3
|
+
TargetChangedPayload,
|
|
4
|
+
CurrentChangedPayload,
|
|
5
|
+
ArmingPayload,
|
|
6
|
+
WarningPayload,
|
|
7
|
+
TriggerFiredPayload,
|
|
8
|
+
TripCancelledPayload,
|
|
9
|
+
SensorStateChangedPayload,
|
|
10
|
+
EmptyPayload,
|
|
11
|
+
} from './event-type.js';
|
|
12
|
+
|
|
13
|
+
/** Maps each EventType to the payload type emitted with it. */
|
|
14
|
+
export type EventPayloadMap = {
|
|
15
|
+
[EventType.TARGET_CHANGED]: TargetChangedPayload;
|
|
16
|
+
[EventType.CURRENT_CHANGED]: CurrentChangedPayload;
|
|
17
|
+
[EventType.ARMING]: ArmingPayload;
|
|
18
|
+
[EventType.WARNING]: WarningPayload;
|
|
19
|
+
[EventType.RESET_TRIP_SWITCHES]: EmptyPayload;
|
|
20
|
+
[EventType.RESET_MODE_SWITCHES]: EmptyPayload;
|
|
21
|
+
[EventType.UPDATE_MODE_SWITCHES]: EmptyPayload;
|
|
22
|
+
[EventType.SENSOR_STATE_CHANGED]: SensorStateChangedPayload;
|
|
23
|
+
[EventType.TRIGGER_FIRED]: TriggerFiredPayload;
|
|
24
|
+
[EventType.TRIP_CANCELLED]: TripCancelledPayload;
|
|
25
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { SecurityState } from './security-state-type.js';
|
|
2
|
+
import type { OriginType } from './origin-type.js';
|
|
3
|
+
import type { SensorKind } from './sensor-kind-type.js';
|
|
4
|
+
|
|
5
|
+
/** All domain events emitted on the EventBusService. */
|
|
6
|
+
export enum EventType {
|
|
7
|
+
TARGET_CHANGED = 'target-changed',
|
|
8
|
+
CURRENT_CHANGED = 'current-changed',
|
|
9
|
+
ARMING = 'arming',
|
|
10
|
+
WARNING = 'warning',
|
|
11
|
+
|
|
12
|
+
// Emitted by StateHandler before TARGET_CHANGED/CURRENT_CHANGED;
|
|
13
|
+
// consumed by TripHandler and SwitchHandler to reset their own state.
|
|
14
|
+
RESET_TRIP_SWITCHES = 'reset-trip-switches',
|
|
15
|
+
RESET_MODE_SWITCHES = 'reset-mode-switches',
|
|
16
|
+
UPDATE_MODE_SWITCHES = 'update-mode-switches',
|
|
17
|
+
|
|
18
|
+
// Emitted by SensorHandler whenever a motion sensor value changes;
|
|
19
|
+
// consumed by MatterService to push occupancy state to Matter.
|
|
20
|
+
SENSOR_STATE_CHANGED = 'sensor-state-changed',
|
|
21
|
+
|
|
22
|
+
// Emitted by TripHandler; consumed by SecuritySystem which calls into StateHandler.
|
|
23
|
+
TRIGGER_FIRED = 'trigger-fired',
|
|
24
|
+
TRIP_CANCELLED = 'trip-cancelled',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TargetChangedPayload {
|
|
28
|
+
state: SecurityState;
|
|
29
|
+
origin: OriginType;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CurrentChangedPayload {
|
|
33
|
+
state: SecurityState;
|
|
34
|
+
origin: OriginType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ArmingPayload {
|
|
38
|
+
state: SecurityState;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WarningPayload {
|
|
42
|
+
origin: OriginType;
|
|
43
|
+
triggerSeconds: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TriggerFiredPayload {
|
|
47
|
+
origin: OriginType;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TripCancelledPayload {
|
|
51
|
+
origin: OriginType;
|
|
52
|
+
stateChanged: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SensorStateChangedPayload {
|
|
56
|
+
sensor: SensorKind;
|
|
57
|
+
value: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Empty payload for internal coordination events. */
|
|
61
|
+
export type EmptyPayload = Record<string, never>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable identifier for every Matter accessory the plugin can publish.
|
|
3
|
+
* Custom trip switches carry their mode and index, e.g. `trip-custom-away-0`.
|
|
4
|
+
*/
|
|
5
|
+
export type MatterAccessoryKey =
|
|
6
|
+
// Trip switches
|
|
7
|
+
| 'trip'
|
|
8
|
+
| 'trip-home'
|
|
9
|
+
| 'trip-away'
|
|
10
|
+
| 'trip-night'
|
|
11
|
+
| 'trip-override'
|
|
12
|
+
| `trip-custom-home-${number}`
|
|
13
|
+
| `trip-custom-away-${number}`
|
|
14
|
+
| `trip-custom-night-${number}`
|
|
15
|
+
// Mode switches
|
|
16
|
+
| 'mode-home'
|
|
17
|
+
| 'mode-away'
|
|
18
|
+
| 'mode-night'
|
|
19
|
+
| 'mode-off'
|
|
20
|
+
| 'mode-away-extended'
|
|
21
|
+
| 'mode-pause'
|
|
22
|
+
// Arming lock switches
|
|
23
|
+
| 'arming-lock'
|
|
24
|
+
| 'arming-lock-home'
|
|
25
|
+
| 'arming-lock-away'
|
|
26
|
+
| 'arming-lock-night'
|
|
27
|
+
// Motion sensors
|
|
28
|
+
| 'sensor-arming'
|
|
29
|
+
| 'sensor-tripped'
|
|
30
|
+
| 'sensor-reset';
|
|
31
|
+
|
|
32
|
+
/** All fixed switch keys — everything except the dynamic custom trip switches. */
|
|
33
|
+
export type FixedMatterSwitchKey = Exclude<MatterAccessoryKey, `trip-custom-${string}` | `sensor-${string}`>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numeric states matching HomeKit's SecuritySystemCurrentState &
|
|
3
|
+
* SecuritySystemTargetState characteristic values.
|
|
4
|
+
*/
|
|
5
|
+
export enum SecurityState {
|
|
6
|
+
HOME = 0, // STAY_ARM
|
|
7
|
+
AWAY = 1, // AWAY_ARM
|
|
8
|
+
NIGHT = 2, // NIGHT_ARM
|
|
9
|
+
OFF = 3, // DISARMED / DISARM
|
|
10
|
+
TRIGGERED = 4, // ALARM_TRIGGERED (current state only)
|
|
11
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SecurityState } from '../types/security-state-type.js';
|
|
2
|
+
|
|
3
|
+
/** Subset of SecuritySystemOptions needed for arming delay resolution. */
|
|
4
|
+
interface ArmingOptions {
|
|
5
|
+
armSeconds: number;
|
|
6
|
+
homeArmSeconds: number | null;
|
|
7
|
+
awayArmSeconds: number | null;
|
|
8
|
+
nightArmSeconds: number | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Subset of SystemState needed for arming delay resolution. */
|
|
12
|
+
interface ArmingState {
|
|
13
|
+
currentState: SecurityState;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the arming delay in seconds for the given target state.
|
|
18
|
+
* Returns 0 when disarming (OFF) or when currently triggered.
|
|
19
|
+
* Falls back to the global `armSeconds` when no mode-specific override is set.
|
|
20
|
+
*/
|
|
21
|
+
export function getArmingSeconds(
|
|
22
|
+
state: ArmingState,
|
|
23
|
+
options: ArmingOptions,
|
|
24
|
+
targetState: SecurityState,
|
|
25
|
+
): number {
|
|
26
|
+
const isTriggered = state.currentState === SecurityState.TRIGGERED;
|
|
27
|
+
const isOff = targetState === SecurityState.OFF;
|
|
28
|
+
|
|
29
|
+
if (isTriggered || isOff) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (targetState === SecurityState.HOME && options.homeArmSeconds !== null) {
|
|
34
|
+
return options.homeArmSeconds;
|
|
35
|
+
}
|
|
36
|
+
if (targetState === SecurityState.AWAY && options.awayArmSeconds !== null) {
|
|
37
|
+
return options.awayArmSeconds;
|
|
38
|
+
}
|
|
39
|
+
if (targetState === SecurityState.NIGHT && options.nightArmSeconds !== null) {
|
|
40
|
+
return options.nightArmSeconds;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return options.armSeconds;
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SecurityState } from '../types/security-state-type.js';
|
|
2
|
+
import type { Mode } from '../types/mode-type.js';
|
|
3
|
+
|
|
4
|
+
/** Converts a SecurityState value to its human-readable mode string. */
|
|
5
|
+
export function stateToMode(state: SecurityState | string): Mode {
|
|
6
|
+
switch (state) {
|
|
7
|
+
case SecurityState.TRIGGERED: return 'triggered';
|
|
8
|
+
case SecurityState.HOME: return 'home';
|
|
9
|
+
case SecurityState.AWAY: return 'away';
|
|
10
|
+
case SecurityState.NIGHT: return 'night';
|
|
11
|
+
case SecurityState.OFF: return 'off';
|
|
12
|
+
case 'lock': return 'lock';
|
|
13
|
+
case 'warning': return 'warning';
|
|
14
|
+
default: return 'unknown';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Converts a mode string to its corresponding SecurityState value (-1 if unknown). */
|
|
19
|
+
export function modeToState(mode: string): SecurityState {
|
|
20
|
+
switch (mode) {
|
|
21
|
+
case 'home': return SecurityState.HOME;
|
|
22
|
+
case 'away': return SecurityState.AWAY;
|
|
23
|
+
case 'night': return SecurityState.NIGHT;
|
|
24
|
+
case 'off': return SecurityState.OFF;
|
|
25
|
+
default: return -1 as SecurityState;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Capitalises the first letter of a string. */
|
|
30
|
+
export function capitalise(s: string): string {
|
|
31
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
32
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"DOM",
|
|
6
|
+
"ES2022"
|
|
7
|
+
],
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"module": "nodenext",
|
|
10
|
+
"moduleResolution": "nodenext",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noUnusedLocals": true,
|
|
13
|
+
"noUnusedParameters": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"outDir": "dist",
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"allowSyntheticDefaultImports": true,
|
|
18
|
+
"esModuleInterop": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"skipLibCheck": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"homebridge-ui",
|
|
24
|
+
"src"
|
|
25
|
+
]
|
|
26
|
+
}
|