Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/device/zigbee/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import deviceSensorOccupancy from './sensor-occupancy.js'
import deviceSensorSmoke from './sensor-smoke.js'
import deviceSwitchStateless from './switch-stateless.js'
import deviceThermostat from './thermostat.js'
import deviceZigbeeWaterValve from './zigbee-water-valve.js';

export default {
deviceLightCCT,
Expand All @@ -24,4 +25,5 @@ export default {
deviceSensorSmoke,
deviceSwitchStateless,
deviceThermostat,
deviceZigbeeWaterValve,
}
197 changes: 197 additions & 0 deletions lib/device/zigbee/zigbee-water-valve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import platformConsts from '../../utils/constants.js'
import { hasProperty } from '../../utils/functions.js'

export default class {
constructor(platform, accessory) {
this.hapChar = platform.api.hap.Characteristic;
this.hapServ = platform.api.hap.Service;
this.hapErr = platform.api.hap.HapStatusError;
this.log = platform.log;
this.platform = platform;
this.lang = platform.lang;
this.name = accessory.displayName;
this.accessory = accessory;

// Set up custom variables for this device type
const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {};
this.enableLogging = !platform.config.disableDeviceLogging;

// Initialize cache state
this.cacheState = false;

// Initialize timer references
this.timer = null;
this.updateInterval = null;

// Set up the Valve Service for HomeKit
this.service = this.accessory.getService(this.hapServ.Valve)
if (!this.service) {
this.service = this.accessory.addService(this.hapServ.Valve);
this.service.updateCharacteristic(this.hapChar.Active, 0);
this.service.updateCharacteristic(this.hapChar.InUse, 0);
this.service.updateCharacteristic(this.hapChar.ValveType, 1); // 1 = Irrigation
this.service.updateCharacteristic(this.hapChar.SetDuration, 30); // Default timer duration
this.service.addCharacteristic(this.hapChar.RemainingDuration);
}

// Restore timer state if it exists
if (this.accessory.context.timerEndTime) {
const now = Date.now();
if (now < this.accessory.context.timerEndTime) {
// Timer should still be running
const remainingTime = Math.round((this.accessory.context.timerEndTime - now) / 1000);
this.service.updateCharacteristic(this.hapChar.RemainingDuration, remainingTime);
this.startTimer(remainingTime);
} else {
// Timer has expired, clean up
this.service.updateCharacteristic(this.hapChar.Active, 0);
this.service.updateCharacteristic(this.hapChar.InUse, 0);
this.service.updateCharacteristic(this.hapChar.RemainingDuration, 0);
delete this.accessory.context.timerEndTime;
}
}

// Set up event handlers for Active characteristic
this.service
.getCharacteristic(this.hapChar.Active)
.onSet(async value => {
try {
const newState = value === 1;
if (this.enableLogging) {
this.log('[%s] Setting valve to [%s]', this.name, newState ? 'On' : 'Off');
}

// Send command to the device
await this.platform.sendDeviceUpdate(this.accessory, {
switch: newState
});

// Update InUse characteristic to match Active
this.service.updateCharacteristic(this.hapChar.InUse, value);

if (value === 1) {
// If turning on, start the timer
const duration = this.service.getCharacteristic(this.hapChar.SetDuration).value;
this.startTimer(duration);
} else {
// If turning off, clear the timer and reset remaining duration
this.clearTimer();
}

// Update cache after successful command
this.cacheState = newState;
} catch (err) {
// Revert the characteristic state on error
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState);
this.service.updateCharacteristic(this.hapChar.InUse, this.cacheState);
this.platform.deviceUpdateError(this.accessory, err, false);
throw new this.hapErr(-70402);
}
});

// Set up handler for SetDuration characteristic
this.service.getCharacteristic(this.hapChar.SetDuration)
.onSet(value => {
if (this.service.getCharacteristic(this.hapChar.InUse).value === 1) {
// Update timer with new duration if valve is currently active
this.startTimer(value);
}
});

this.log('[%s] initialized as a Zigbee Smart Water Valve.', this.name);
}

startTimer(duration) {
// Clear any existing timer
this.clearTimer();

// Set timer end time and store it in context
this.accessory.context.timerEndTime = Date.now() + (duration * 1000);

// Update remaining duration
this.service.updateCharacteristic(this.hapChar.RemainingDuration, duration);

// Set new timer
this.timer = setTimeout(async () => {
try {
// Send explicit command to turn off the valve
await this.platform.sendDeviceUpdate(this.accessory, {
switch: false
});

// Update HomeKit characteristics
this.service.updateCharacteristic(this.hapChar.Active, 0);
this.service.updateCharacteristic(this.hapChar.InUse, 0);
this.service.updateCharacteristic(this.hapChar.RemainingDuration, 0);
delete this.accessory.context.timerEndTime;

// Update cache state
this.cacheState = false;

if (this.enableLogging) {
this.log('[%s] Timer finished - turning valve [OFF]', this.name);
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, false);
}
}, duration * 1000);

// Start a periodic update of remaining duration
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
this.updateInterval = setInterval(() => {
const remaining = Math.round((this.accessory.context.timerEndTime - Date.now()) / 1000);
if (remaining > 0) {
this.service.updateCharacteristic(this.hapChar.RemainingDuration, remaining);
} else {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}, 1000);
}

clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
delete this.accessory.context.timerEndTime;
this.service.updateCharacteristic(this.hapChar.RemainingDuration, 0);
}

async externalUpdate(params) {
try {
// Handle On/Off state
if (hasProperty(params, 'switch')) {
const newState = params.switch ? true : false;
if (newState !== this.cacheState) {
this.cacheState = newState;
const value = newState ? 1 : 0;
this.service.updateCharacteristic(this.hapChar.Active, value);
this.service.updateCharacteristic(this.hapChar.InUse, value);

if (!newState) {
// If turned off externally, clear timer
this.clearTimer();
}

if (params.updateSource && this.enableLogging) {
this.log('[%s] Valve state updated to [%s]', this.name, newState ? 'On' : 'Off');
}
}
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, false);
}
}

markStatus(isOnline) {
this.isOnline = isOnline;
}
}
10 changes: 10 additions & 0 deletions lib/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,16 @@ export default class {
this.applyAccessoryLogging(accessory)
accessory.control = new deviceTypes.zb.deviceThermostat(this, accessory)
/** */
} else if (platformConsts.devices.zbWaterValve.includes(uiid)) {
/**
*******************
ZB WATER VALVE
********************
*/
accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
this.applyAccessoryLogging(accessory)
accessory.control = new deviceTypes.zb.deviceZigbeeWaterValve(this, accessory)
/** */
} else if (platformConsts.devices.group.includes(uiid)) {
/**
***********
Expand Down
8 changes: 5 additions & 3 deletions lib/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ export default {
zbSensorWater: [4026],
zbSensorSmoke: [5026],
zbThermostat: [7017],
zbWaterValve: [7027],
group: [5000],
template: [130], // SPM sub unit
cannotSupport: [0, 65, 118, 119, 120, 121],
Expand Down Expand Up @@ -513,7 +514,7 @@ export default {
1258: 1,
1514: 1, // Graywind Zigbee Shades
1770: 1, // "ZIGBEE_TEMPERATURE_SENSOR"
1771: 1, // "" some duplicate of the above https://github.com/homebridge-plugins/homebridge-ewelink/issues/494
1771: 1, // "" some duplicate of the above https://github.com/bwp91/homebridge-ewelink/issues/494
2026: 1, // "ZIGBEE_MOBILE_SENSOR"
2256: 2, // "ZIGBEE_SWITCH_2"
3026: 1, // "ZIGBEE_DOOR_AND_WINDOW_SENSOR"
Expand All @@ -529,9 +530,10 @@ export default {
7004: 1, // Zigbee Single-Channel Switch ­_Support OTA
7005: 1, // Some switch, not entirely sure
7006: 1, // Zigbee Curtain_Support OTA
7014: 1, // some sensor https://github.com/homebridge-plugins/homebridge-ewelink/issues/494
7014: 1, // some sensor https://github.com/bwp91/homebridge-ewelink/issues/494
7016: 1, // https://sonoff.tech/product/gateway-and-sensors/snzb-06p/
7017: 1, // https://github.com/homebridge-plugins/homebridge-ewelink/issues/518
7017: 1, // https://github.com/bwp91/homebridge-ewelink/issues/518
7027: 1, // Zigbee Smart Water Valve
},

cannotSupportDevices: {
Expand Down
Loading