UNPKG

matterbridge-ecoflow-delta-pro-ultra

Version:
240 lines 15.6 kB
import { RestClient } from '@ecoflow-api/rest-client'; import { Matterbridge, MatterbridgeEndpoint, MatterbridgeDynamicPlatform, batteryStorage, bridgedNode, deviceEnergyManagement, electricalSensor, onOffSwitch, powerSource } from 'matterbridge'; import { DeviceEnergyManagement, ElectricalPowerMeasurement, OnOff, PowerSource } from 'matterbridge/matter/clusters'; import { AnsiLogger } from 'matterbridge/logger'; import { PowerSourceTag } from 'matterbridge/matter'; import mqtt from 'mqtt'; import { mqttResponseBaseSchema, mqttResponseCmdId1Params, mqttResponseCmdId2Params, mqttResponseCmdId3Params, restAllQuotaData } from './schemas.js'; export class EcoflowDeltaProUltraPlatform extends MatterbridgeDynamicPlatform { ecoflowRestClient; batteryStorageDevices = new Map(); refreshSensorsInterval; constructor(matterbridge, log, config) { super(matterbridge, log, config); const accessKey = config.accessKey ?? process.env.ECOFLOW_ACCESS_KEY; const secretKey = config.secretKey ?? process.env.ECOFLOW_SECRET_KEY; if (!accessKey || !secretKey) { this.log.error('Missing EcoFlow Developer Access and Secret Keys!'); this.log.error(' - Platform Config Props: accessKey & secretKey'); this.log.error(' - Environment Variables: ECOFLOW_ACCESS_KEY & ECOFLOW_SECRET_KEY'); } else { config.accessKey = accessKey; config.secretKey = secretKey; this.ecoflowRestClient = new RestClient({ accessKey: accessKey, secretKey: secretKey, host: 'https://api-a.ecoflow.com' }); } } async onStart(reason) { this.log.info('[onStart]', reason); await this.ready; await this.clearSelect(); if (!this.ecoflowRestClient) { return; } const devices = await this.ecoflowRestClient.getDevicesPlain(); for (const device of devices.data) { if (device.productName !== 'DELTA Pro Ultra') { continue; } const devicePropsResponse = await this.ecoflowRestClient.getDevicePropertiesPlain(device.sn); const deviceProps = restAllQuotaData.parse(devicePropsResponse.data); this.log.debug('Properties:', deviceProps); const batteryLevel = deviceProps['hs_yj751_pd_appshow_addr.soc']; const acInFreq = deviceProps['hs_yj751_pd_backend_addr.acInFreq'] ?? 0; const acOutFreq = deviceProps['hs_yj751_pd_backend_addr.acOutFreq'] ?? 0; const endpoint = new MatterbridgeEndpoint([batteryStorage, deviceEnergyManagement, bridgedNode], { id: 'EcoFlow-' + device.sn }, this.config.debug) .createDefaultBridgedDeviceBasicInformationClusterServer(device.deviceName ?? 'DELTA Pro Ultra', device.sn, 0xfff1, 'EcoFlow', device.productName, parseInt(this.version.replace(/\D/g, '')), this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion) .createDefaultDeviceEnergyManagementClusterServer(DeviceEnergyManagement.EsaType.BatteryStorage, true, DeviceEnergyManagement.EsaState.Online, -7200_000, 7200_000) .createDefaultDeviceEnergyManagementModeClusterServer() .addRequiredClusterServers(); endpoint.name = `EcoFlow ${device.productName}`; endpoint.addChildDeviceType('Battery', powerSource, { tagList: [PowerSourceTag.Battery] }) .createDefaultPowerSourceRechargeableBatteryClusterServer(batteryLevel, PowerSource.BatChargeLevel.Ok, 102.4 * 1000) .addRequiredClusterServers(); endpoint.addChildDeviceType('Grid', powerSource, { tagList: [PowerSourceTag.Grid] }) .createDefaultPowerSourceWiredClusterServer(PowerSource.WiredCurrentType.Ac) .addRequiredClusterServers(); endpoint.addChildDeviceType('ACInput', electricalSensor) .createDefaultElectricalEnergyMeasurementClusterServer() .createDefaultElectricalPowerMeasurementClusterServer(null, null, null, acInFreq * 1000) .createDefaultPowerTopologyClusterServer() .addRequiredClusterServers(); endpoint.addChildDeviceType('ACOutput', electricalSensor) .createDefaultElectricalEnergyMeasurementClusterServer() .createDefaultElectricalPowerMeasurementClusterServer(null, null, null, acOutFreq * 1000) .createDefaultPowerTopologyClusterServer() .addRequiredClusterServers(); endpoint.addChildDeviceType('ACSwitch', onOffSwitch) .createDefaultOnOffClusterServer() .addRequiredClusterServers(); // acSwitch.addCommandHandler('on', async () => { // this.ecoflowRestClient?.setCommandPlain({ // sn: device.sn, // cmdCode: 'YJ751_PD_AC_DSG_SET', // params: { enable: 1 } // }); // }); // acSwitch.addCommandHandler('off', async () => { // this.ecoflowRestClient?.setCommandPlain({ // sn: device.sn, // cmdCode: 'YJ751_PD_AC_DSG_SET', // params: { enable: 0 } // }); // }); const dcSwitch = endpoint.addChildDeviceType('DCSwitch', onOffSwitch) .createDefaultOnOffClusterServer() .addRequiredClusterServers(); dcSwitch.addCommandHandler('on', async () => { try { this.ecoflowRestClient?.setCommandPlain({ sn: device.sn, cmdCode: 'YJ751_PD_DC_SWITCH_SET', params: { enable: 1 } }); } catch (error) { this.log.error('Failed to execute DC switch ON command:', error); } }); dcSwitch.addCommandHandler('off', async () => { try { this.ecoflowRestClient?.setCommandPlain({ sn: device.sn, cmdCode: 'YJ751_PD_DC_SWITCH_SET', params: { enable: 0 } }); } catch (error) { this.log.error('Failed to execute DC switch OFF command:', error); } }); await this.registerDevice(endpoint); this.batteryStorageDevices.set(device.sn, endpoint); this.setSelectDevice(device.sn, device.deviceName ?? 'DELTA Pro Ultra', 'https://developer.ecoflow.com/', 'hub'); } } async onConfigure() { await super.onConfigure(); if (!this.ecoflowRestClient) { return; } const mqttCreds = await this.ecoflowRestClient.getMqttCredentials(); const mqttClient = await mqtt.connectAsync(`${mqttCreds.protocol}://${mqttCreds.url}:${mqttCreds.port}`, { username: `${mqttCreds.certificateAccount}`, password: `${mqttCreds.certificatePassword}`, protocolVersion: 5 }); for (const deviceSerialNumber of this.batteryStorageDevices.keys()) { await mqttClient.subscribeAsync(`/open/${mqttCreds.certificateAccount}/${deviceSerialNumber}/quota`); await mqttClient.subscribeAsync(`/open/${mqttCreds.certificateAccount}/${deviceSerialNumber}/status`); } mqttClient.on('message', async (topic, message) => { const serialNumber = topic.split('/')[3]; const endpoint = this.batteryStorageDevices.get(serialNumber); if (!endpoint) { return; } const batteryPowerSourceEndpoint = endpoint.getChildEndpointByName('Battery'); const gridPowerSourceEndpoint = endpoint.getChildEndpointByName('Grid'); const acInputElectricalSensorEndpoint = endpoint.getChildEndpointByName('ACInput'); const acOutputElectricalSensorEndpoint = endpoint.getChildEndpointByName('ACOutput'); const acSwitchOnOffSwitchEndpoint = endpoint.getChildEndpointByName('ACSwitch'); const dcSwitchOnOffSwitchEndpoint = endpoint.getChildEndpointByName('DCSwitch'); const messageWrapper = mqttResponseBaseSchema.parse(JSON.parse(message.toString())); switch (messageWrapper.cmdId) { case 1: { const params = mqttResponseCmdId1Params.parse(messageWrapper.param); this.log.debug('MQTT Message', topic, messageWrapper.cmdId, messageWrapper.cmdFunc, params); // Battery Power Source: Battery Percent Remaining & Charge Level if (batteryPowerSourceEndpoint && params.soc !== undefined) { const batChargeLevel = params.soc > 10 ? PowerSource.BatChargeLevel.Ok : PowerSource.BatChargeLevel.Warning; await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'batPercentRemaining', params.soc * 2, endpoint.log); await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'batChargeLevel', batChargeLevel, endpoint.log); } // Battery Power Source: Battery Time Remaining if (batteryPowerSourceEndpoint && params.remainTime !== undefined && params.wattsInSum !== undefined) { if (params.wattsInSum === 0) { await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'batTimeRemaining', params.remainTime * 60, endpoint.log); } else { await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'batTimeRemaining', null, endpoint.log); } } // Grid Power Source: Status if (gridPowerSourceEndpoint && params.inAc5p8Pwr !== undefined && params.inAcC20Pwr !== undefined) { const powerSourceStatus = params.inAc5p8Pwr > 0 || params.inAcC20Pwr > 0 ? PowerSource.PowerSourceStatus.Active : PowerSource.PowerSourceStatus.Standby; await gridPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'status', powerSourceStatus, endpoint.log); } // AC Input Electrical Sensor: Power/Watts if (acInputElectricalSensorEndpoint && (params.inAc5p8Pwr !== undefined || params.inAcC20Pwr !== undefined)) { const acInPwr_mW = Math.round(((params.inAc5p8Pwr ?? 0) + (params.inAcC20Pwr ?? 0)) * 1000); await acInputElectricalSensorEndpoint.setAttribute(ElectricalPowerMeasurement.Cluster.id, 'activePower', acInPwr_mW, endpoint.log); } // AC Output Electrical Sensor: Power/Watts if (acOutputElectricalSensorEndpoint && (params.outAc5p8Pwr !== undefined || params.outAcL11Pwr !== undefined || params.outAcL12Pwr !== undefined || params.outAcL14Pwr !== undefined || params.outAcL21Pwr !== undefined || params.outAcL22Pwr !== undefined || params.outAcTtPwr !== undefined)) { const acOutPwr_mW = Math.round(((params.outAc5p8Pwr ?? 0) + (params.outAcL11Pwr ?? 0) + (params.outAcL12Pwr ?? 0) + (params.outAcL14Pwr ?? 0) + (params.outAcL21Pwr ?? 0) + (params.outAcL22Pwr ?? 0) + (params.outAcTtPwr ?? 0)) * 1000); await acOutputElectricalSensorEndpoint.setAttribute(ElectricalPowerMeasurement.Cluster.id, 'activePower', acOutPwr_mW, endpoint.log); } // AC On Off Switch if (acSwitchOnOffSwitchEndpoint && params.showFlag !== undefined) { const isOn = (params.showFlag & 0x04) !== 0; await acSwitchOnOffSwitchEndpoint.setAttribute(OnOff.Cluster.id, 'onOff', isOn, endpoint.log); } // DC On Off Switch if (dcSwitchOnOffSwitchEndpoint && params.showFlag !== undefined) { const isOn = (params.showFlag & 0x02) !== 0; await dcSwitchOnOffSwitchEndpoint.setAttribute(OnOff.Cluster.id, 'onOff', isOn, endpoint.log); } break; } case 2: { const params = mqttResponseCmdId2Params.parse(messageWrapper.param); this.log.debug('MQTT Message', topic, messageWrapper.cmdId, messageWrapper.cmdFunc, params); // Battery Power Source: Battery Charging State if (batteryPowerSourceEndpoint && params.bmsInputWatts !== undefined) { const batChargeState = params.bmsInputWatts > 0 ? PowerSource.BatChargeState.IsCharging : PowerSource.BatChargeState.IsNotCharging; await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'batChargeState', batChargeState, endpoint.log); } // Battery Power Source: Status if (batteryPowerSourceEndpoint && params.bmsOutputWatts !== undefined) { const powerSourceStatus = params.bmsOutputWatts > 0 ? PowerSource.PowerSourceStatus.Active : PowerSource.PowerSourceStatus.Standby; await batteryPowerSourceEndpoint.setAttribute(PowerSource.Cluster.id, 'status', powerSourceStatus, endpoint.log); } // AC Input Electrical Sensor: Current/Amps if (acInputElectricalSensorEndpoint && params.inAc5p8Amp !== undefined && params.inAcC20Amp !== undefined) { const acInAmp_mA = Math.round((params.inAc5p8Amp + params.inAcC20Amp) * 1000); await acInputElectricalSensorEndpoint.setAttribute(ElectricalPowerMeasurement.Cluster.id, 'activeCurrent', acInAmp_mA, endpoint.log); } // AC Input Electrical Sensor: Voltage/Volts if (acInputElectricalSensorEndpoint && params.inAc5p8Vol !== undefined && params.inAcC20Vol !== undefined) { const acInVol = params.inAc5p8Vol > 0 && params.inAcC20Vol > 0 ? (params.inAc5p8Vol + params.inAcC20Vol) / 2 : (params.inAc5p8Vol > 0 ? params.inAc5p8Vol : params.inAcC20Vol); const acInVol_mV = Math.round(acInVol * 1000); await acInputElectricalSensorEndpoint.setAttribute(ElectricalPowerMeasurement.Cluster.id, 'voltage', acInVol_mV, endpoint.log); } break; } case 3: { const params = mqttResponseCmdId3Params.parse(messageWrapper.param); this.log.debug('MQTT Message', topic, messageWrapper.cmdId, messageWrapper.cmdFunc, params); break; } } }); } async onShutdown(reason) { await super.onShutdown(reason); this.log.info('[onShutdown]', reason); if (this.config.unregisterOnShutdown === true) { await this.unregisterAllDevices(500); } } } //# sourceMappingURL=platform.js.map