homebridge-powerview-universal 4.3.2

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.
@@ -0,0 +1,102 @@
1
+ import type { PlatformConfig } from 'homebridge';
2
+ /**
3
+ * Platform name users put in config.json as `"platform": "PowerView"`.
4
+ */
5
+ export declare const PLATFORM_NAME = "PowerView";
6
+ /**
7
+ * Must match package.json `name` — used when registering platform accessories.
8
+ */
9
+ export declare const PLUGIN_NAME = "homebridge-powerview-universal";
10
+ export declare const SHADE_POLL_INTERVAL_MS = 30000;
11
+ /**
12
+ * Consecutive hub shade lists a shade must be absent from before its accessory
13
+ * is unregistered. Removal is irreversible for the user's rooms and automations,
14
+ * so a single short response must never trigger it.
15
+ */
16
+ export declare const SHADE_REMOVAL_THRESHOLD = 3;
17
+ /**
18
+ * How long a shade takes to travel its full range. Used to animate HomeKit's
19
+ * position while the motor runs, rather than reporting arrival on command.
20
+ */
21
+ export declare const SHADE_FULL_TRAVEL_MS = 20000;
22
+ /** Floor on an estimated travel, so a tiny nudge still reads as movement. */
23
+ export declare const SHADE_MIN_TRAVEL_MS = 1000;
24
+ /** Minimum gap between cheap background reads of the same shade. */
25
+ export declare const BACKGROUND_REFRESH_INTERVAL_MS = 10000;
26
+ /**
27
+ * Default local time for the optional daily battery poll (API: updateBatteryLevel).
28
+ *
29
+ * The poll is opt-in — see resolveBatteryPollSettings. It is an RF round-trip
30
+ * that wakes the shade motor: audible, and it nudges the shade.
31
+ */
32
+ export declare const BATTERY_POLL_DEFAULT_AT = "14:00";
33
+ /**
34
+ * Default quiet window (local time) for anything that actuates hardware.
35
+ * Homebridge can restart unattended, so a startup position sync must not be
36
+ * free to wake five motors at 03:00.
37
+ */
38
+ export declare const QUIET_START_HOUR = 21;
39
+ export declare const QUIET_END_HOUR = 8;
40
+ export declare enum ShadeKind {
41
+ ROLLER = 1,
42
+ TOP_BOTTOM = 2,
43
+ HORIZONTAL = 3,
44
+ VERTICAL = 4,
45
+ /** Capability 6. Primary rail runs reversed relative to bottom-up shades. */
46
+ TOP_DOWN = 5,
47
+ /** Capability 2. Tilt spans 180 degrees, not the 90 that HORIZONTAL assumes. */
48
+ HORIZONTAL_180 = 6,
49
+ /** Capability 5. Tilt only, no lift. */
50
+ TILT_ONLY = 7,
51
+ /** Capabilities 8 and 9. Duolite / overlapped panels. */
52
+ DUAL_OVERLAPPED = 8
53
+ }
54
+ /**
55
+ * Shade type -> ShadeCapabilities, from the PowerView Hub REST API v2 appendix
56
+ * (jlaur/hdpowerview-doc v1.0.4). The official specification leaves the
57
+ * ShadeType schema empty, so this table is the only published mapping.
58
+ */
59
+ export declare const SHADE_TYPE_CAPABILITY: Readonly<Record<number, number>>;
60
+ /** Kinds whose position and tilt maths this plugin actually implements. */
61
+ export declare const FULLY_SUPPORTED_KINDS: readonly ShadeKind[];
62
+ export declare const SUBTYPE: {
63
+ readonly BOTTOM: "bottom";
64
+ readonly TOP: "top";
65
+ };
66
+ /** Context stored on a scene accessory, to tell it from a shade on restore. */
67
+ export interface SceneContext {
68
+ sceneId: number;
69
+ }
70
+ export interface PowerViewPlatformConfig extends PlatformConfig {
71
+ host?: string;
72
+ refreshShades?: boolean;
73
+ pollShadesForUpdate?: boolean;
74
+ strictErrors?: boolean;
75
+ /** Spacing between serialised hub requests, ms. Raise it if your hub struggles. */
76
+ requestIntervalMs?: number;
77
+ /** Expose hub scenes as HomeKit switches. Nothing appears if none are defined. */
78
+ exposeScenes?: boolean;
79
+ /** Opt in to the daily battery poll. Off by default: it wakes the shade motor. */
80
+ batteryPolling?: boolean;
81
+ /** Local "HH:MM" time for the daily battery poll. Defaults to 14:00. */
82
+ batteryPollAt?: string;
83
+ /** Re-sync positions over RF once at startup. Defaults to true. */
84
+ syncPositionsOnStart?: boolean;
85
+ /** Local-time window in which nothing may actuate hardware. `false` disables it. */
86
+ quietHours?: {
87
+ start: number;
88
+ end: number;
89
+ } | false;
90
+ forceRollerShades?: number[];
91
+ forceTopBottomShades?: number[];
92
+ forceHorizontalShades?: number[];
93
+ forceVerticalShades?: number[];
94
+ }
95
+ export interface ShadeContext {
96
+ shadeId: number;
97
+ shadeType: ShadeKind;
98
+ /** False when the hub rejected motion jog for this shade. */
99
+ jogSupported?: boolean;
100
+ /** Last known positions, persisted so a restart does not report 0. */
101
+ lastPositions?: Partial<Record<number, number>>;
102
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Platform name users put in config.json as `"platform": "PowerView"`.
3
+ */
4
+ export const PLATFORM_NAME = 'PowerView';
5
+ /**
6
+ * Must match package.json `name` — used when registering platform accessories.
7
+ */
8
+ export const PLUGIN_NAME = 'homebridge-powerview-universal';
9
+ export const SHADE_POLL_INTERVAL_MS = 30_000;
10
+ /**
11
+ * Consecutive hub shade lists a shade must be absent from before its accessory
12
+ * is unregistered. Removal is irreversible for the user's rooms and automations,
13
+ * so a single short response must never trigger it.
14
+ */
15
+ export const SHADE_REMOVAL_THRESHOLD = 3;
16
+ /**
17
+ * How long a shade takes to travel its full range. Used to animate HomeKit's
18
+ * position while the motor runs, rather than reporting arrival on command.
19
+ */
20
+ export const SHADE_FULL_TRAVEL_MS = 20_000;
21
+ /** Floor on an estimated travel, so a tiny nudge still reads as movement. */
22
+ export const SHADE_MIN_TRAVEL_MS = 1_000;
23
+ /** Minimum gap between cheap background reads of the same shade. */
24
+ export const BACKGROUND_REFRESH_INTERVAL_MS = 10_000;
25
+ /**
26
+ * Default local time for the optional daily battery poll (API: updateBatteryLevel).
27
+ *
28
+ * The poll is opt-in — see resolveBatteryPollSettings. It is an RF round-trip
29
+ * that wakes the shade motor: audible, and it nudges the shade.
30
+ */
31
+ export const BATTERY_POLL_DEFAULT_AT = '14:00';
32
+ /**
33
+ * Default quiet window (local time) for anything that actuates hardware.
34
+ * Homebridge can restart unattended, so a startup position sync must not be
35
+ * free to wake five motors at 03:00.
36
+ */
37
+ export const QUIET_START_HOUR = 21;
38
+ export const QUIET_END_HOUR = 8;
39
+ export var ShadeKind;
40
+ (function (ShadeKind) {
41
+ ShadeKind[ShadeKind["ROLLER"] = 1] = "ROLLER";
42
+ ShadeKind[ShadeKind["TOP_BOTTOM"] = 2] = "TOP_BOTTOM";
43
+ ShadeKind[ShadeKind["HORIZONTAL"] = 3] = "HORIZONTAL";
44
+ ShadeKind[ShadeKind["VERTICAL"] = 4] = "VERTICAL";
45
+ /** Capability 6. Primary rail runs reversed relative to bottom-up shades. */
46
+ ShadeKind[ShadeKind["TOP_DOWN"] = 5] = "TOP_DOWN";
47
+ /** Capability 2. Tilt spans 180 degrees, not the 90 that HORIZONTAL assumes. */
48
+ ShadeKind[ShadeKind["HORIZONTAL_180"] = 6] = "HORIZONTAL_180";
49
+ /** Capability 5. Tilt only, no lift. */
50
+ ShadeKind[ShadeKind["TILT_ONLY"] = 7] = "TILT_ONLY";
51
+ /** Capabilities 8 and 9. Duolite / overlapped panels. */
52
+ ShadeKind[ShadeKind["DUAL_OVERLAPPED"] = 8] = "DUAL_OVERLAPPED";
53
+ })(ShadeKind || (ShadeKind = {}));
54
+ /**
55
+ * Shade type -> ShadeCapabilities, from the PowerView Hub REST API v2 appendix
56
+ * (jlaur/hdpowerview-doc v1.0.4). The official specification leaves the
57
+ * ShadeType schema empty, so this table is the only published mapping.
58
+ */
59
+ export const SHADE_TYPE_CAPABILITY = {
60
+ 1: 0, 4: 0, 5: 0, 6: 0, 7: 6, 8: 7, 9: 7, 18: 1, 23: 1, 38: 9,
61
+ 42: 0, 43: 1, 44: 1, 47: 7, 49: 0, 51: 2, 54: 3, 55: 3, 56: 3,
62
+ 62: 2, 65: 8, 66: 5, 69: 4, 70: 4, 71: 4, 79: 8,
63
+ };
64
+ /** Kinds whose position and tilt maths this plugin actually implements. */
65
+ export const FULLY_SUPPORTED_KINDS = [
66
+ ShadeKind.ROLLER,
67
+ ShadeKind.TOP_BOTTOM,
68
+ ShadeKind.HORIZONTAL,
69
+ ShadeKind.VERTICAL,
70
+ ];
71
+ export const SUBTYPE = {
72
+ BOTTOM: 'bottom',
73
+ TOP: 'top',
74
+ };
75
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AAEzC;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,gCAAgC,CAAC;AAE5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAC3C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAEzC,oEAAoE;AACpE,MAAM,CAAC,MAAM,8BAA8B,GAAG,MAAM,CAAC;AAErD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAE/C;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,CAAN,IAAY,SAaX;AAbD,WAAY,SAAS;IACnB,6CAAU,CAAA;IACV,qDAAc,CAAA;IACd,qDAAc,CAAA;IACd,iDAAY,CAAA;IACZ,6EAA6E;IAC7E,iDAAY,CAAA;IACZ,gFAAgF;IAChF,6DAAkB,CAAA;IAClB,wCAAwC;IACxC,mDAAa,CAAA;IACb,yDAAyD;IACzD,+DAAmB,CAAA;AACrB,CAAC,EAbW,SAAS,KAAT,SAAS,QAapB;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqC;IACrE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IAC7D,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IAC7D,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;CAChD,CAAC;AAEF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAyB;IACzD,SAAS,CAAC,MAAM;IAChB,SAAS,CAAC,UAAU;IACpB,SAAS,CAAC,UAAU;IACpB,SAAS,CAAC,QAAQ;CACnB,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;CACF,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { HubPosition, type ShadePositions } from './powerviewHub.js';
2
+ import { ShadeKind, type PowerViewPlatformConfig } from './settings.js';
3
+ export interface TimeOfDay {
4
+ hour: number;
5
+ minute: number;
6
+ }
7
+ export interface BatteryPollSettings {
8
+ enabled: boolean;
9
+ at: TimeOfDay;
10
+ }
11
+ /** Parses an "HH:MM" local time. Returns null on anything malformed. */
12
+ export declare function parseTimeOfDay(value: unknown): TimeOfDay | null;
13
+ /**
14
+ * Milliseconds from `now` until the next occurrence of `hour:minute` local time,
15
+ * rolling to tomorrow when today's slot has already passed.
16
+ *
17
+ * Callers must recompute this from a fresh Date after every run rather than
18
+ * adding 24h to the previous deadline: only recomputing keeps the poll pinned to
19
+ * the same wall-clock time across a DST change.
20
+ */
21
+ export declare function msUntilNextDailyRun(now: Date, hour: number, minute: number): number;
22
+ /**
23
+ * Resolves battery-poll timing from user config.
24
+ *
25
+ * Polling is opt-in: `updateBatteryLevel=true` is an RF round-trip that audibly
26
+ * wakes the shade motor and nudges the shade, so a default-on timer actuates
27
+ * hardware in someone's home that they never asked for. Battery state still
28
+ * reaches HomeKit from ordinary hub reads; this poll only forces a fresh
29
+ * measurement.
30
+ */
31
+ export declare function resolveBatteryPollSettings(config: Partial<PowerViewPlatformConfig>): BatteryPollSettings;
32
+ export declare function decodeBase64Name(encoded: string | undefined, fallback: string): string;
33
+ /** A shade's reported positions, keyed by position kind. */
34
+ export type PositionMap = Partial<Record<HubPosition, number>>;
35
+ /** API PositionKind value when the hub reports a position error. */
36
+ export declare const POSITION_KIND_ERROR = 4;
37
+ /**
38
+ * How many posKindN/positionN pairs a shade response may carry. The hub uses
39
+ * two in practice; the loop is bounded rather than stopping at the first gap.
40
+ */
41
+ export declare const MAX_POSITION_KINDS = 4;
42
+ export declare function isValidPositionKind(kind: number): boolean;
43
+ /**
44
+ * Builds a map of position kind → hub value, skipping invalid/error kinds.
45
+ */
46
+ export declare function parsePositionMap(positions: ShadePositions): Partial<Record<HubPosition, number>>;
47
+ /**
48
+ * Serializes a position map back to hub posKindN/positionN fields.
49
+ */
50
+ export declare function serializePositionMap(positions: Record<number, number>): ShadePositions;
51
+ /**
52
+ * Nominal pack voltage in tenths of a volt. Gen 1/2 hubs report `batteryStrength`
53
+ * as tenths of a volt (e.g. 146 = 14.6V) against an 18.0V nominal pack, NOT as a
54
+ * percentage. Matches the conversion used by aiopvapi / Home Assistant.
55
+ */
56
+ export declare const BATTERY_NOMINAL_TENTHS_VOLT = 180;
57
+ /** Percentage at or below which HomeKit should show a low-battery warning. */
58
+ export declare const LOW_BATTERY_PERCENT = 20;
59
+ /**
60
+ * Converts a hub `batteryStrength` (tenths of a volt) to a 0-100 percentage.
61
+ * Returns undefined when the value is absent or 0 — the hub reports 0 for shades
62
+ * it has not polled yet, which is "unknown", not "empty".
63
+ */
64
+ export declare function batteryStrengthToPercent(batteryStrength?: number): number | undefined;
65
+ /** BatteryStatus: 0 = none, 1 = low, 2 = medium, 3 = high, 4 = plugged in. */
66
+ export declare function isLowBattery(batteryStatus?: number, batteryStrength?: number): boolean;
67
+ export interface BatteryReading {
68
+ /** HomeKit BatteryLevel (0–100). */
69
+ level: number;
70
+ low: boolean;
71
+ /** Hub reports mains-powered (BatteryStatus plugged in). */
72
+ mainsPowered: boolean;
73
+ }
74
+ /**
75
+ * Maps PowerView shade battery fields to HomeKit Battery service values.
76
+ * Returns undefined when the hub provides no usable battery data.
77
+ */
78
+ export declare function resolveBatteryReading(batteryStatus?: number, batteryStrength?: number): BatteryReading | undefined;
79
+ export declare function formatShadeFirmware(firmware?: {
80
+ revision: number;
81
+ subRevision: number;
82
+ build: number;
83
+ }): string | undefined;
84
+ export type PositionLookup = {
85
+ kind: 'ok';
86
+ value: number;
87
+ } | {
88
+ kind: 'missing';
89
+ } | {
90
+ kind: 'invalid';
91
+ value: unknown;
92
+ };
93
+ /**
94
+ * Distinguishes "the hub did not report this position" from "the hub reported
95
+ * something unusable".
96
+ *
97
+ * The distinction is not academic: a PowerView Gen 2 hub returns no `positions`
98
+ * object at all on a cached read — positions exist only after a `refresh=true`
99
+ * read or a set — so `missing` is the normal path on every HomeKit read, while
100
+ * `invalid` means the hub really did send bad data. Collapsing the two logged a
101
+ * warning on the happy path 77 times a day and pointed diagnosis at the hub's
102
+ * values when the hub had sent no values at all.
103
+ */
104
+ export declare function lookupPosition(positions: PositionMap, position: HubPosition): PositionLookup;
105
+ /**
106
+ * Validates a position map restored from persisted accessory context, which is
107
+ * plain JSON written by an earlier run and must not be trusted structurally.
108
+ */
109
+ export declare function sanitizePositionMap(value: unknown): PositionMap;
110
+ /** Shallow equality for position maps, treating a missing map as empty. */
111
+ export declare function positionMapsEqual(a: PositionMap | undefined, b: PositionMap | undefined): boolean;
112
+ export interface QuietHours {
113
+ /** Start of the quiet window, inclusive. Equal to endHour means "no quiet window". */
114
+ startHour: number;
115
+ /** End of the quiet window, exclusive. */
116
+ endHour: number;
117
+ }
118
+ /**
119
+ * True when `date`'s local hour falls in [startHour, endHour), wrapping across
120
+ * midnight when startHour > endHour. An empty window is never within.
121
+ */
122
+ export declare function isWithinQuietHours(date: Date, startHour: number, endHour: number): boolean;
123
+ /** Resolves the window during which the plugin must not actuate hardware. */
124
+ export declare function resolveQuietHours(config: Partial<PowerViewPlatformConfig>): QuietHours;
125
+ /**
126
+ * Resolves a shade's ShadeCapabilities value: the hub's own `capabilities`
127
+ * field when it reports one, otherwise the documented type table.
128
+ *
129
+ * Capability is the right axis, not `type`: 26 published types collapse onto
130
+ * 10 capability classes, and behaviour (reversed rail, tilt range, dual
131
+ * panels) follows the capability. Older hubs omit the field entirely.
132
+ */
133
+ export declare function resolveShadeCapability(shade: {
134
+ type?: number;
135
+ capabilities?: number;
136
+ }): number | undefined;
137
+ /** Maps a ShadeCapabilities value onto the kind the plugin drives it as. */
138
+ export declare function shadeKindForCapability(capability: number): ShadeKind | undefined;
@@ -0,0 +1,292 @@
1
+ import { HubPosition } from './powerviewHub.js';
2
+ import { BATTERY_POLL_DEFAULT_AT, SHADE_TYPE_CAPABILITY, ShadeKind, QUIET_END_HOUR, QUIET_START_HOUR, } from './settings.js';
3
+ /** Parses an "HH:MM" local time. Returns null on anything malformed. */
4
+ export function parseTimeOfDay(value) {
5
+ if (typeof value !== 'string') {
6
+ return null;
7
+ }
8
+ const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
9
+ if (!match) {
10
+ return null;
11
+ }
12
+ const hour = Number(match[1]);
13
+ const minute = Number(match[2]);
14
+ return hour <= 23 && minute <= 59 ? { hour, minute } : null;
15
+ }
16
+ /**
17
+ * Milliseconds from `now` until the next occurrence of `hour:minute` local time,
18
+ * rolling to tomorrow when today's slot has already passed.
19
+ *
20
+ * Callers must recompute this from a fresh Date after every run rather than
21
+ * adding 24h to the previous deadline: only recomputing keeps the poll pinned to
22
+ * the same wall-clock time across a DST change.
23
+ */
24
+ export function msUntilNextDailyRun(now, hour, minute) {
25
+ const next = new Date(now);
26
+ next.setHours(hour, minute, 0, 0);
27
+ if (next.getTime() <= now.getTime()) {
28
+ next.setDate(next.getDate() + 1);
29
+ next.setHours(hour, minute, 0, 0);
30
+ }
31
+ return next.getTime() - now.getTime();
32
+ }
33
+ /**
34
+ * Resolves battery-poll timing from user config.
35
+ *
36
+ * Polling is opt-in: `updateBatteryLevel=true` is an RF round-trip that audibly
37
+ * wakes the shade motor and nudges the shade, so a default-on timer actuates
38
+ * hardware in someone's home that they never asked for. Battery state still
39
+ * reaches HomeKit from ordinary hub reads; this poll only forces a fresh
40
+ * measurement.
41
+ */
42
+ export function resolveBatteryPollSettings(config) {
43
+ return {
44
+ enabled: config.batteryPolling === true,
45
+ at: parseTimeOfDay(config.batteryPollAt) ?? parseTimeOfDay(BATTERY_POLL_DEFAULT_AT),
46
+ };
47
+ }
48
+ export function decodeBase64Name(encoded, fallback) {
49
+ if (!encoded) {
50
+ return fallback;
51
+ }
52
+ try {
53
+ return Buffer.from(encoded, 'base64').toString();
54
+ }
55
+ catch {
56
+ return encoded;
57
+ }
58
+ }
59
+ /** API PositionKind value when the hub reports a position error. */
60
+ export const POSITION_KIND_ERROR = 4;
61
+ /**
62
+ * How many posKindN/positionN pairs a shade response may carry. The hub uses
63
+ * two in practice; the loop is bounded rather than stopping at the first gap.
64
+ */
65
+ export const MAX_POSITION_KINDS = 4;
66
+ export function isValidPositionKind(kind) {
67
+ return kind >= HubPosition.BOTTOM && kind <= HubPosition.VANES;
68
+ }
69
+ /**
70
+ * Builds a map of position kind → hub value, skipping invalid/error kinds.
71
+ */
72
+ export function parsePositionMap(positions) {
73
+ const map = {};
74
+ for (let i = 1; positions[`posKind${i}`] != null; ++i) {
75
+ const kind = positions[`posKind${i}`];
76
+ if (kind === POSITION_KIND_ERROR || !isValidPositionKind(kind)) {
77
+ continue;
78
+ }
79
+ map[kind] = positions[`position${i}`];
80
+ }
81
+ return map;
82
+ }
83
+ /**
84
+ * Serializes a position map back to hub posKindN/positionN fields.
85
+ */
86
+ export function serializePositionMap(positions) {
87
+ const result = {};
88
+ let i = 1;
89
+ for (const key of Object.keys(positions).sort((a, b) => Number(a) - Number(b))) {
90
+ const kind = parseInt(key, 10);
91
+ if (kind === POSITION_KIND_ERROR || !isValidPositionKind(kind)) {
92
+ continue;
93
+ }
94
+ result[`posKind${i}`] = kind;
95
+ result[`position${i}`] = positions[kind];
96
+ ++i;
97
+ }
98
+ return result;
99
+ }
100
+ /**
101
+ * Nominal pack voltage in tenths of a volt. Gen 1/2 hubs report `batteryStrength`
102
+ * as tenths of a volt (e.g. 146 = 14.6V) against an 18.0V nominal pack, NOT as a
103
+ * percentage. Matches the conversion used by aiopvapi / Home Assistant.
104
+ */
105
+ export const BATTERY_NOMINAL_TENTHS_VOLT = 180;
106
+ /** Percentage at or below which HomeKit should show a low-battery warning. */
107
+ export const LOW_BATTERY_PERCENT = 20;
108
+ /**
109
+ * Converts a hub `batteryStrength` (tenths of a volt) to a 0-100 percentage.
110
+ * Returns undefined when the value is absent or 0 — the hub reports 0 for shades
111
+ * it has not polled yet, which is "unknown", not "empty".
112
+ */
113
+ export function batteryStrengthToPercent(batteryStrength) {
114
+ if (typeof batteryStrength !== 'number' || !Number.isFinite(batteryStrength)) {
115
+ return undefined;
116
+ }
117
+ if (batteryStrength <= 0) {
118
+ return undefined;
119
+ }
120
+ return Math.min(100, Math.round((batteryStrength / BATTERY_NOMINAL_TENTHS_VOLT) * 100));
121
+ }
122
+ /** BatteryStatus: 0 = none, 1 = low, 2 = medium, 3 = high, 4 = plugged in. */
123
+ export function isLowBattery(batteryStatus, batteryStrength) {
124
+ if (batteryStatus === 1) {
125
+ return true;
126
+ }
127
+ const percent = batteryStrengthToPercent(batteryStrength);
128
+ if (percent != null) {
129
+ return percent <= LOW_BATTERY_PERCENT;
130
+ }
131
+ return false;
132
+ }
133
+ /**
134
+ * Maps PowerView shade battery fields to HomeKit Battery service values.
135
+ * Returns undefined when the hub provides no usable battery data.
136
+ */
137
+ export function resolveBatteryReading(batteryStatus, batteryStrength) {
138
+ if (batteryStatus == null && batteryStrength == null) {
139
+ return undefined;
140
+ }
141
+ if (batteryStatus === 0) {
142
+ return undefined;
143
+ }
144
+ let level = batteryStrengthToPercent(batteryStrength);
145
+ if (level == null && batteryStatus != null) {
146
+ switch (batteryStatus) {
147
+ case 1:
148
+ level = 15;
149
+ break;
150
+ case 2:
151
+ level = 50;
152
+ break;
153
+ case 3:
154
+ level = 90;
155
+ break;
156
+ case 4:
157
+ level = 100;
158
+ break;
159
+ default:
160
+ return undefined;
161
+ }
162
+ }
163
+ if (level == null) {
164
+ return undefined;
165
+ }
166
+ return {
167
+ level,
168
+ low: isLowBattery(batteryStatus, batteryStrength),
169
+ mainsPowered: batteryStatus === 4,
170
+ };
171
+ }
172
+ export function formatShadeFirmware(firmware) {
173
+ if (!firmware) {
174
+ return undefined;
175
+ }
176
+ return `${firmware.revision}.${firmware.subRevision}.${firmware.build}`;
177
+ }
178
+ /**
179
+ * Distinguishes "the hub did not report this position" from "the hub reported
180
+ * something unusable".
181
+ *
182
+ * The distinction is not academic: a PowerView Gen 2 hub returns no `positions`
183
+ * object at all on a cached read — positions exist only after a `refresh=true`
184
+ * read or a set — so `missing` is the normal path on every HomeKit read, while
185
+ * `invalid` means the hub really did send bad data. Collapsing the two logged a
186
+ * warning on the happy path 77 times a day and pointed diagnosis at the hub's
187
+ * values when the hub had sent no values at all.
188
+ */
189
+ export function lookupPosition(positions, position) {
190
+ if (!Object.prototype.hasOwnProperty.call(positions, position)) {
191
+ return { kind: 'missing' };
192
+ }
193
+ const value = positions[position];
194
+ return typeof value === 'number' && Number.isFinite(value)
195
+ ? { kind: 'ok', value }
196
+ : { kind: 'invalid', value };
197
+ }
198
+ /**
199
+ * Validates a position map restored from persisted accessory context, which is
200
+ * plain JSON written by an earlier run and must not be trusted structurally.
201
+ */
202
+ export function sanitizePositionMap(value) {
203
+ const clean = {};
204
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
205
+ return clean;
206
+ }
207
+ for (const [key, entry] of Object.entries(value)) {
208
+ const kind = Number(key);
209
+ if (!Number.isInteger(kind) || !isValidPositionKind(kind)) {
210
+ continue;
211
+ }
212
+ if (typeof entry === 'number' && Number.isFinite(entry)) {
213
+ clean[kind] = entry;
214
+ }
215
+ }
216
+ return clean;
217
+ }
218
+ /** Shallow equality for position maps, treating a missing map as empty. */
219
+ export function positionMapsEqual(a, b) {
220
+ const left = a ?? {};
221
+ const right = b ?? {};
222
+ const leftKeys = Object.keys(left);
223
+ if (leftKeys.length !== Object.keys(right).length) {
224
+ return false;
225
+ }
226
+ return leftKeys.every((key) => {
227
+ const kind = Number(key);
228
+ return Object.prototype.hasOwnProperty.call(right, kind) && left[kind] === right[kind];
229
+ });
230
+ }
231
+ /**
232
+ * True when `date`'s local hour falls in [startHour, endHour), wrapping across
233
+ * midnight when startHour > endHour. An empty window is never within.
234
+ */
235
+ export function isWithinQuietHours(date, startHour, endHour) {
236
+ if (startHour === endHour) {
237
+ return false;
238
+ }
239
+ const hour = date.getHours();
240
+ return startHour < endHour
241
+ ? hour >= startHour && hour < endHour
242
+ : hour >= startHour || hour < endHour;
243
+ }
244
+ /** Resolves the window during which the plugin must not actuate hardware. */
245
+ export function resolveQuietHours(config) {
246
+ const quiet = config.quietHours;
247
+ if (quiet === false) {
248
+ return { startHour: 0, endHour: 0 };
249
+ }
250
+ if (quiet
251
+ && Number.isInteger(quiet.start) && quiet.start >= 0 && quiet.start <= 23
252
+ && Number.isInteger(quiet.end) && quiet.end >= 0 && quiet.end <= 23) {
253
+ return { startHour: quiet.start, endHour: quiet.end };
254
+ }
255
+ return { startHour: QUIET_START_HOUR, endHour: QUIET_END_HOUR };
256
+ }
257
+ /**
258
+ * Resolves a shade's ShadeCapabilities value: the hub's own `capabilities`
259
+ * field when it reports one, otherwise the documented type table.
260
+ *
261
+ * Capability is the right axis, not `type`: 26 published types collapse onto
262
+ * 10 capability classes, and behaviour (reversed rail, tilt range, dual
263
+ * panels) follows the capability. Older hubs omit the field entirely.
264
+ */
265
+ export function resolveShadeCapability(shade) {
266
+ const reported = shade.capabilities;
267
+ if (typeof reported === 'number' && Number.isInteger(reported) && reported >= 0 && reported <= 9) {
268
+ return reported;
269
+ }
270
+ const type = shade.type;
271
+ if (typeof type !== 'number') {
272
+ return undefined;
273
+ }
274
+ return SHADE_TYPE_CAPABILITY[type];
275
+ }
276
+ /** Maps a ShadeCapabilities value onto the kind the plugin drives it as. */
277
+ export function shadeKindForCapability(capability) {
278
+ switch (capability) {
279
+ case 0: return ShadeKind.ROLLER;
280
+ case 1: return ShadeKind.HORIZONTAL;
281
+ case 2: return ShadeKind.HORIZONTAL_180;
282
+ case 3:
283
+ case 4: return ShadeKind.VERTICAL;
284
+ case 5: return ShadeKind.TILT_ONLY;
285
+ case 6: return ShadeKind.TOP_DOWN;
286
+ case 7: return ShadeKind.TOP_BOTTOM;
287
+ case 8:
288
+ case 9: return ShadeKind.DUAL_OVERLAPPED;
289
+ default: return undefined;
290
+ }
291
+ }
292
+ //# sourceMappingURL=shadeUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadeUtils.js","sourceRoot":"","sources":["../src/shadeUtils.ts"],"names":[],"mappings":"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"}