homebridge-sensus 1.1.1 → 1.1.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.
@@ -49,6 +49,26 @@
49
49
  "type": "number",
50
50
  "default": 30,
51
51
  "description": "How often to fetch new data from Sensus Analytics, in minutes. Default: 30."
52
+ },
53
+ "displayUnit": {
54
+ "title": "HomeKit Display Unit",
55
+ "type": "string",
56
+ "default": "gal",
57
+ "oneOf": [
58
+ {
59
+ "title": "Gallons (gal)",
60
+ "enum": [
61
+ "gal"
62
+ ]
63
+ },
64
+ {
65
+ "title": "Liters (l)",
66
+ "enum": [
67
+ "l"
68
+ ]
69
+ }
70
+ ],
71
+ "description": "The unit of measurement to display in the HomeKit/Eve app. Default: Gallons."
52
72
  }
53
73
  }
54
74
  },
@@ -63,7 +83,11 @@
63
83
  "type": "fieldset",
64
84
  "title": "Advanced Options",
65
85
  "expandable": true,
66
- "items": ["leakThreshold", "pollInterval"]
86
+ "items": [
87
+ "leakThreshold",
88
+ "pollInterval",
89
+ "displayUnit"
90
+ ]
67
91
  }
68
92
  ]
69
93
  }
@@ -8,10 +8,19 @@ export declare class SensusWaterMeterAccessory {
8
8
  private readonly leakService;
9
9
  private readonly leakThreshold;
10
10
  private readonly pollIntervalMs;
11
+ private readonly displayUnit;
11
12
  private readonly eveConsumptionChar;
12
13
  private readonly eveTotalChar;
13
14
  private lastData;
14
15
  constructor(platform: SensusAnalyticsPlatform, accessory: PlatformAccessory, apiClient: SensusAnalyticsApi);
16
+ /**
17
+ * Helper to get the conversion factor from the Sensus API unit to Gallons.
18
+ */
19
+ private getSensusUnitMultiplier;
20
+ /**
21
+ * Converts a value in Gallons to the user's configured HomeKit display unit (gal or l).
22
+ */
23
+ private convertGallonsToDisplayUnit;
15
24
  /**
16
25
  * Creates and registers a custom Characteristic on the LeakSensor service.
17
26
  * If the characteristic already exists (restored from cache), it is returned as-is.
package/dist/accessory.js CHANGED
@@ -18,6 +18,7 @@ class SensusWaterMeterAccessory {
18
18
  (platform.config.pollInterval ?? DEFAULT_POLL_INTERVAL_MINUTES) *
19
19
  60 *
20
20
  1000;
21
+ this.displayUnit = platform.config.displayUnit ?? 'gal';
21
22
  // ── Accessory Information ─────────────────────────────────────────────
22
23
  this.accessory
23
24
  .getService(Svc.AccessoryInformation)
@@ -44,6 +45,31 @@ class SensusWaterMeterAccessory {
44
45
  this.poll();
45
46
  setInterval(() => this.poll(), this.pollIntervalMs);
46
47
  }
48
+ /**
49
+ * Helper to get the conversion factor from the Sensus API unit to Gallons.
50
+ */
51
+ getSensusUnitMultiplier(sensusUnit) {
52
+ const unit = sensusUnit.toUpperCase().trim();
53
+ if (unit === 'CCF' || unit === 'HCF') {
54
+ return 748.052; // 1 CCF = 748.052 Gallons
55
+ }
56
+ if (unit === 'CF' || unit === 'CUBIC_FOOT' || unit === 'CUBIC_FEET' || unit === 'CUBIC FEET') {
57
+ return 7.48052; // 1 Cubic Foot = 7.48052 Gallons
58
+ }
59
+ if (unit === 'L' || unit === 'LITER' || unit === 'LITERS' || unit === 'LITRES') {
60
+ return 0.264172; // 1 Liter = 0.264172 Gallons
61
+ }
62
+ return 1.0; // Default to 1 (already gallons or unknown)
63
+ }
64
+ /**
65
+ * Converts a value in Gallons to the user's configured HomeKit display unit (gal or l).
66
+ */
67
+ convertGallonsToDisplayUnit(gallons) {
68
+ if (this.displayUnit === 'l') {
69
+ return gallons * 3.78541; // 1 Gallon = 3.78541 Liters
70
+ }
71
+ return gallons; // Default to Gallons
72
+ }
47
73
  /**
48
74
  * Creates and registers a custom Characteristic on the LeakSensor service.
49
75
  * If the characteristic already exists (restored from cache), it is returned as-is.
@@ -55,7 +81,7 @@ class SensusWaterMeterAccessory {
55
81
  return existing;
56
82
  }
57
83
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- const props = { format, unit: 'gal', minValue, maxValue, minStep, perms };
84
+ const props = { format, unit: this.displayUnit, minValue, maxValue, minStep, perms };
59
85
  const char = new hap.Characteristic(displayName, uuid, props);
60
86
  char.setValue(0);
61
87
  this.leakService.addCharacteristic(char);
@@ -67,7 +93,9 @@ class SensusWaterMeterAccessory {
67
93
  if (!this.lastData) {
68
94
  return LEAK_NOT_DETECTED;
69
95
  }
70
- return this.lastData.daily.dailyUsage > this.leakThreshold
96
+ const multiplier = this.getSensusUnitMultiplier(this.lastData.daily.usageUnit);
97
+ const dailyUsageGallons = this.lastData.daily.dailyUsage * multiplier;
98
+ return dailyUsageGallons > this.leakThreshold
71
99
  ? LEAK_DETECTED
72
100
  : LEAK_NOT_DETECTED;
73
101
  }
@@ -82,18 +110,30 @@ class SensusWaterMeterAccessory {
82
110
  this.lastData = data;
83
111
  const { daily, hourly } = data;
84
112
  const { LEAK_DETECTED, LEAK_NOT_DETECTED } = this.platform.Characteristic.LeakDetected;
85
- const isLeaking = daily.dailyUsage > this.leakThreshold;
86
- // Push updates to HomeKit
113
+ // Convert raw API values (which can be in CCF, CF, liters, etc.) to Gallons
114
+ const multiplier = this.getSensusUnitMultiplier(daily.usageUnit);
115
+ const dailyUsageGallons = daily.dailyUsage * multiplier;
116
+ const odometerGallons = daily.odometer * multiplier;
117
+ const billingUsageGallons = daily.billingUsage * multiplier;
118
+ // Perform leak check in Gallons
119
+ const isLeaking = dailyUsageGallons > this.leakThreshold;
120
+ // Push leak status to HomeKit
87
121
  this.leakService.updateCharacteristic(this.platform.Characteristic.LeakDetected, isLeaking ? LEAK_DETECTED : LEAK_NOT_DETECTED);
88
- // Eve consumption characteristics
89
- this.eveConsumptionChar.updateValue(daily.dailyUsage);
90
- this.eveTotalChar.updateValue(daily.odometer);
91
- // Log a summary
122
+ // Convert from Gallons to configured display unit (gal or l)
123
+ const dailyUsageDisplay = this.convertGallonsToDisplayUnit(dailyUsageGallons);
124
+ const odometerDisplay = this.convertGallonsToDisplayUnit(odometerGallons);
125
+ // Update custom Eve characteristics
126
+ // - eveConsumptionChar (E863F10D, Current Power / Consumption in Watts) displays daily value
127
+ this.eveConsumptionChar.updateValue(dailyUsageDisplay);
128
+ // - eveTotalChar (E863F10C, Total Consumption in kWh) displays cumulative odometer
129
+ // Multiply by 1000 because Eve app divides E863F10C by 1000 to show kWh
130
+ this.eveTotalChar.updateValue(odometerDisplay * 1000);
131
+ // Log a summary showing both raw units and HomeKit display units
92
132
  const lastHour = hourly.at(-1);
93
133
  this.platform.log.info(`[${this.accessory.displayName}] ` +
94
- `daily=${daily.dailyUsage} ${daily.usageUnit} | ` +
95
- `odometer=${daily.odometer} ${daily.usageUnit} | ` +
96
- `billing=${daily.billingUsage} ${daily.usageUnit} | ` +
134
+ `daily=${dailyUsageDisplay.toFixed(2)} ${this.displayUnit} (raw=${daily.dailyUsage} ${daily.usageUnit}) | ` +
135
+ `odometer=${odometerDisplay.toFixed(2)} ${this.displayUnit} (raw=${daily.odometer} ${daily.usageUnit}) | ` +
136
+ `billing=${billingUsageGallons.toFixed(2)} gal (raw=${daily.billingUsage} ${daily.usageUnit}) | ` +
97
137
  `leak=${isLeaking}` +
98
138
  (lastHour ? ` | lastHourUsage=${lastHour.usage} | temp=${lastHour.temp}°F` : ''));
99
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-sensus",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Homebridge plugin for Sensus Analytics water meters",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
package/src/accessory.ts CHANGED
@@ -12,6 +12,7 @@ export class SensusWaterMeterAccessory {
12
12
  private readonly leakService: Service;
13
13
  private readonly leakThreshold: number;
14
14
  private readonly pollIntervalMs: number;
15
+ private readonly displayUnit: string;
15
16
 
16
17
  // Direct references to Eve custom characteristics to avoid UUID lookups on update
17
18
  private readonly eveConsumptionChar: Characteristic;
@@ -32,6 +33,7 @@ export class SensusWaterMeterAccessory {
32
33
  ((platform.config.pollInterval as number | undefined) ?? DEFAULT_POLL_INTERVAL_MINUTES) *
33
34
  60 *
34
35
  1000;
36
+ this.displayUnit = (platform.config.displayUnit as string | undefined) ?? 'gal';
35
37
 
36
38
  // ── Accessory Information ─────────────────────────────────────────────
37
39
  this.accessory
@@ -83,6 +85,33 @@ export class SensusWaterMeterAccessory {
83
85
  setInterval(() => this.poll(), this.pollIntervalMs);
84
86
  }
85
87
 
88
+ /**
89
+ * Helper to get the conversion factor from the Sensus API unit to Gallons.
90
+ */
91
+ private getSensusUnitMultiplier(sensusUnit: string): number {
92
+ const unit = sensusUnit.toUpperCase().trim();
93
+ if (unit === 'CCF' || unit === 'HCF') {
94
+ return 748.052; // 1 CCF = 748.052 Gallons
95
+ }
96
+ if (unit === 'CF' || unit === 'CUBIC_FOOT' || unit === 'CUBIC_FEET' || unit === 'CUBIC FEET') {
97
+ return 7.48052; // 1 Cubic Foot = 7.48052 Gallons
98
+ }
99
+ if (unit === 'L' || unit === 'LITER' || unit === 'LITERS' || unit === 'LITRES') {
100
+ return 0.264172; // 1 Liter = 0.264172 Gallons
101
+ }
102
+ return 1.0; // Default to 1 (already gallons or unknown)
103
+ }
104
+
105
+ /**
106
+ * Converts a value in Gallons to the user's configured HomeKit display unit (gal or l).
107
+ */
108
+ private convertGallonsToDisplayUnit(gallons: number): number {
109
+ if (this.displayUnit === 'l') {
110
+ return gallons * 3.78541; // 1 Gallon = 3.78541 Liters
111
+ }
112
+ return gallons; // Default to Gallons
113
+ }
114
+
86
115
  /**
87
116
  * Creates and registers a custom Characteristic on the LeakSensor service.
88
117
  * If the characteristic already exists (restored from cache), it is returned as-is.
@@ -104,7 +133,7 @@ export class SensusWaterMeterAccessory {
104
133
  }
105
134
 
106
135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
- const props: any = { format, unit: 'gal', minValue, maxValue, minStep, perms };
136
+ const props: any = { format, unit: this.displayUnit, minValue, maxValue, minStep, perms };
108
137
  const char = new hap.Characteristic(displayName, uuid, props);
109
138
  char.setValue(0);
110
139
  this.leakService.addCharacteristic(char);
@@ -119,7 +148,10 @@ export class SensusWaterMeterAccessory {
119
148
  return LEAK_NOT_DETECTED;
120
149
  }
121
150
 
122
- return this.lastData.daily.dailyUsage > this.leakThreshold
151
+ const multiplier = this.getSensusUnitMultiplier(this.lastData.daily.usageUnit);
152
+ const dailyUsageGallons = this.lastData.daily.dailyUsage * multiplier;
153
+
154
+ return dailyUsageGallons > this.leakThreshold
123
155
  ? LEAK_DETECTED
124
156
  : LEAK_NOT_DETECTED;
125
157
  }
@@ -138,25 +170,39 @@ export class SensusWaterMeterAccessory {
138
170
  const { daily, hourly } = data;
139
171
  const { LEAK_DETECTED, LEAK_NOT_DETECTED } = this.platform.Characteristic.LeakDetected;
140
172
 
141
- const isLeaking = daily.dailyUsage > this.leakThreshold;
173
+ // Convert raw API values (which can be in CCF, CF, liters, etc.) to Gallons
174
+ const multiplier = this.getSensusUnitMultiplier(daily.usageUnit);
175
+ const dailyUsageGallons = daily.dailyUsage * multiplier;
176
+ const odometerGallons = daily.odometer * multiplier;
177
+ const billingUsageGallons = daily.billingUsage * multiplier;
178
+
179
+ // Perform leak check in Gallons
180
+ const isLeaking = dailyUsageGallons > this.leakThreshold;
142
181
 
143
- // Push updates to HomeKit
182
+ // Push leak status to HomeKit
144
183
  this.leakService.updateCharacteristic(
145
184
  this.platform.Characteristic.LeakDetected,
146
185
  isLeaking ? LEAK_DETECTED : LEAK_NOT_DETECTED,
147
186
  );
148
187
 
149
- // Eve consumption characteristics
150
- this.eveConsumptionChar.updateValue(daily.dailyUsage);
151
- this.eveTotalChar.updateValue(daily.odometer);
188
+ // Convert from Gallons to configured display unit (gal or l)
189
+ const dailyUsageDisplay = this.convertGallonsToDisplayUnit(dailyUsageGallons);
190
+ const odometerDisplay = this.convertGallonsToDisplayUnit(odometerGallons);
191
+
192
+ // Update custom Eve characteristics
193
+ // - eveConsumptionChar (E863F10D, Current Power / Consumption in Watts) displays daily value
194
+ this.eveConsumptionChar.updateValue(dailyUsageDisplay);
195
+ // - eveTotalChar (E863F10C, Total Consumption in kWh) displays cumulative odometer
196
+ // Multiply by 1000 because Eve app divides E863F10C by 1000 to show kWh
197
+ this.eveTotalChar.updateValue(odometerDisplay * 1000);
152
198
 
153
- // Log a summary
199
+ // Log a summary showing both raw units and HomeKit display units
154
200
  const lastHour = hourly.at(-1);
155
201
  this.platform.log.info(
156
202
  `[${this.accessory.displayName}] ` +
157
- `daily=${daily.dailyUsage} ${daily.usageUnit} | ` +
158
- `odometer=${daily.odometer} ${daily.usageUnit} | ` +
159
- `billing=${daily.billingUsage} ${daily.usageUnit} | ` +
203
+ `daily=${dailyUsageDisplay.toFixed(2)} ${this.displayUnit} (raw=${daily.dailyUsage} ${daily.usageUnit}) | ` +
204
+ `odometer=${odometerDisplay.toFixed(2)} ${this.displayUnit} (raw=${daily.odometer} ${daily.usageUnit}) | ` +
205
+ `billing=${billingUsageGallons.toFixed(2)} gal (raw=${daily.billingUsage} ${daily.usageUnit}) | ` +
160
206
  `leak=${isLeaking}` +
161
207
  (lastHour ? ` | lastHourUsage=${lastHour.usage} | temp=${lastHour.temp}°F` : ''),
162
208
  );