Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wrong device]: [SPM02-D2TZ] power meter won't report negative values #20631

Closed
elektrinis opened this issue Jan 7, 2024 · 13 comments
Closed
Labels
stale Stale issues

Comments

@elektrinis
Copy link

Link

https://zigbee.blakadder.com/Zemismart_SPM02-D2TZ.html

Model

_TZE200_ves1ycwx

Description

3-phase power meter

Vendor

TuYa

Picture (link)

https://zigbee.blakadder.com/assets/images/devices/Zemismart_SPM02-D2TZ.webp

Database entry

{}

Notes

Device is working well, however it is reporting negative current/power as positive. Positive also as positive, so I'm sure there is no issue with wiring. Not sure if this is integration, or issue with device itself.
Newbie here, please explain in detail if I need to submit any extra info.

@HobboRobin
Copy link

I have the same Problem with the Same Device. I have a 1Phase Solar inverter on phase 3. when i have a power load of 50W and then inject 600W it goes to 550W. So there is something wrong. I got the from the manufacture a converter File. But i dont get it working correctly. Or i dont even know it is correct.

@HobboRobin
Copy link

/**********************************************************************
Version 01.00.02

Date 2023/10/26

update 支持读取总有功功率,三相无功功率,总无功功率,
三相势在功率,总势在功率,系统频率
**********************************************************************/

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;
const { Numeric } = require('zigbee-herdsman-converters/lib/exposes');
const {
precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
batteryVoltageToPercentage,
} = require('zigbee-herdsman-converters/lib/utils');
const utils = require('zigbee-herdsman-converters/lib/utils');

let preEnergy = 0;
let preProduced_energy = 0;

const converters = {
seMetering: {

    cluster: 'seMetering',
    type: ['attributeReport', 'readResponse'],
    options: (definition) => {
        const result = [];
        if (definition.exposes.find((e) => e.name === 'power')) {
            result.push(exposes.options.precision('power'), exposes.options.calibration('power', 'percentual'));
        }
        if (definition.exposes.find((e) => e.name === 'energy')) {
            result.push(exposes.options.precision('energy'), exposes.options.calibration('energy', 'percentual'));
        }
        if (definition.exposes.find((e) => e.name === 'produced_energy')) {
            result.push(exposes.options.precision('produced_energy'), exposes.options.calibration('energy', 'percentual'));
        }
        return result;

    },

    convert: (model, msg, publish, options, meta) => {
        if (utils.hasAlreadyProcessedMessage(msg, model)) return;
        const payload = {};
        const multiplier = msg.endpoint.getClusterAttributeValue('seMetering', 'multiplier');
        const divisor = msg.endpoint.getClusterAttributeValue('seMetering', 'divisor');
        const factor = multiplier && divisor ? multiplier / divisor : null;


        if (factor != null && (msg.data.hasOwnProperty('currentSummDelivered') ||
            msg.data.hasOwnProperty('currentSummReceived'))) {
            let energy  = preEnergy;
            let produced_energy  = preProduced_energy;
            if (msg.data.hasOwnProperty('currentSummDelivered')) {
                const data = msg.data['currentSummDelivered'];
                const value = (parseInt(data[0]) << 32) + parseInt(data[1]);
                energy = value * factor;
                preEnergy = energy;
                // produced_energy = preProduced_energy;
            }
            if (msg.data.hasOwnProperty('currentSummReceived'))  {
                const data = msg.data['currentSummReceived'];
                const value = (parseInt(data[0]) << 32) + parseInt(data[1]);
                produced_energy = value * factor;
                preProduced_energy = produced_energy;
                // energy = preEnergy;
            }
            payload.energy = calibrateAndPrecisionRoundOptions(energy, options, 'energy');
            payload.produced_energy = calibrateAndPrecisionRoundOptions(produced_energy, options, 'energy');
            // payload.produced_energy = produced_energy;
        }
        return payload;
    },
},
electrical_measurement_bituo: {
    //bituo-SPM02
    cluster: 'haElectricalMeasurement',
    type: ['attributeReport', 'readResponse'],
    options: [
        exposes.options.precision('ac_frequency'), exposes.options.precision('total_active_power'),
        exposes.options.calibration('active_power', 'percentual'), exposes.options.precision('active_power'),
        exposes.options.precision('active_power_phase_b'), exposes.options.precision('active_power_phase_c'),
        exposes.options.precision('total_power_apparent'), exposes.options.precision('power_apparent'),
        exposes.options.precision('power_apparent_phase_b'), exposes.options.precision('power_apparent_phase_c'),
        exposes.options.precision('total_power_reactive'), exposes.options.precision('power_reactive'),
        exposes.options.precision('power_reactive_phase_b'), exposes.options.precision('power_reactive_phase_c'),
        exposes.options.calibration('current', 'percentual'), exposes.options.precision('current'),
        exposes.options.calibration('voltage', 'percentual'), exposes.options.precision('voltage'),
    ],
    convert: (model, msg, publish, options, meta) => {
        if (utils.hasAlreadyProcessedMessage(msg, model)) return;
        const getFactor = (key) => {
            const multiplier = msg.endpoint.getClusterAttributeValue('haElectricalMeasurement', `${key}Multiplier`);
            const divisor = msg.endpoint.getClusterAttributeValue('haElectricalMeasurement', `${key}Divisor`);
            const factor = multiplier && divisor ? multiplier / divisor : 1;
            return factor;
        };

        const lookup = [
            {key: 'activePower', name: 'active_power', factor: 'acPower'},
            {key: 'activePowerPhB', name: 'active_power_phase_b', factor: 'acPower'},
            {key: 'activePowerPhC', name: 'active_power_phase_c', factor: 'acPower'},
            {key: 'totalActivePower', name: 'total_active_power', factor: 'acPower'},
            {key: 'apparentPower', name: 'power_apparent', factor: 'acPower'},
            {key: 'apparentPowerPhB', name: 'power_apparent_phase_b', factor: 'acPower'},
            {key: 'apparentPowerPhC', name: 'power_apparent_phase_c', factor: 'acPower'},
            {key: 'totalAppaarentPower', name: 'total_power_apparent', factor: 'acPower'},
            {key: 'reactivePower', name: 'power_reactive', factor: 'acPower'},
            {key: 'reactivePowerPhB', name: 'power_reactive_phase_b', factor: 'acPower'},
            {key: 'reactivePowerPhC', name: 'power_reactive_phase_c', factor: 'acPower'},
            {key: 'totalReactivePower', name: 'total_power_reactive', factor: 'acPower'},
            {key: 'rmsCurrent', name: 'current', factor: 'acCurrent'},
            {key: 'rmsCurrentPhB', name: 'current_phase_b', factor: 'acCurrent'},
            {key: 'rmsCurrentPhC', name: 'current_phase_c', factor: 'acCurrent'},
            {key: 'rmsVoltage', name: 'voltage', factor: 'acVoltage'},
            {key: 'rmsVoltagePhB', name: 'voltage_phase_b', factor: 'acVoltage'},
            {key: 'rmsVoltagePhC', name: 'voltage_phase_c', factor: 'acVoltage'},
            {key: 'acFrequency', name: 'ac_frequency', factor: 'acFrequency'},
        ];

        const payload = {};
        for (const entry of lookup) {
            if (msg.data.hasOwnProperty(entry.key)) {
                const factor = getFactor(entry.factor);
                const property = postfixWithEndpointName(entry.name, msg, model, meta);
                const value = msg.data[entry.key] * factor;
                payload[property] = calibrateAndPrecisionRoundOptions(value, options, entry.name);
            }
        }

        // alarm mask
        if(msg.data.hasOwnProperty('ACAlarmsMask')){
            payload.Alarm = msg.data['ACAlarmsMask'].toString(2);
        }


        if (msg.data.hasOwnProperty('powerFactor')) {
            payload.power_factor = precisionRound(msg.data['powerFactor'] , 2);
        }
        if (msg.data.hasOwnProperty('powerFactorPhB')) {
            payload.power_factor_phase_b = precisionRound(msg.data['powerFactorPhB'] , 2);
        }
        if (msg.data.hasOwnProperty('powerFactorPhC')) {
            payload.power_factor_phase_c = precisionRound(msg.data['powerFactorPhC'] , 2);
        }
        return payload;
    },
},
hw_version: {
    cluster: 'genBasic',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.hasOwnProperty('hwVersion')) result['hw_version'] = msg.data.hwVersion;
        return result;
    },
},
locationDesc: {
    cluster: 'genBasic',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.hasOwnProperty('locationDesc')) result['locationDesc'] = msg.data.locationDesc;
        return result;
    },
},

}
const power_kW = () => new Numeric('active_power', ea.STATE).withUnit('kW').withDescription('Instantaneous measured active power');
const power_kW_phase_b = () => new Numeric('active_power_phase_b', ea.STATE).withUnit('kW').withDescription('Instantaneous measured active power on phase B');
const power_kW_phase_c = () => new Numeric('active_power_phase_c', ea.STATE).withUnit('kW').withDescription('Instantaneous measured active power on phase C');
const total_power_kW = () => new Numeric('total_active_power', ea.STATE).withUnit('kW').withDescription('Instantaneous measured total active power');
const power_factor_phase_b = () => new Numeric('power_factor_phase_b', ea.STATE).withUnit('%').withDescription('Instantaneous measured power factor on phase B');
const power_factor_phase_c = () => new Numeric('power_factor_phase_c', ea.STATE).withUnit('%').withDescription('Instantaneous measured power factor on phase C');
const power_reactive = () => new Numeric('power_reactive', ea.STATE).withUnit('kVAR').withDescription('Instantaneous measured reactive power');
const power_reactive_phase_b = () => new Numeric('power_reactive_phase_b', ea.STATE).withUnit('kVAR').withDescription('Instantaneous measured reactive power on phase B');
const power_reactive_phase_c = () => new Numeric('power_reactive_phase_c', ea.STATE).withUnit('kVAR').withDescription('Instantaneous measured reactive power on phase C');
const total_power_reactive = () => new Numeric('total_power_reactive', ea.STATE).withUnit('kVAR').withDescription('Instantaneous measured total reactive power');
const power_apparent = () => new Numeric('power_apparent', ea.STATE).withUnit('kVA').withDescription('Instantaneous measured apparent power');
const power_apparent_phase_b = () => new Numeric('power_apparent_phase_b', ea.STATE).withUnit('kVA').withDescription('Instantaneous measured apparent power on phase B');
const power_apparent_phase_c = () => new Numeric('power_apparent_phase_c', ea.STATE).withUnit('kVA').withDescription('Instantaneous measured apparent power on phase C');
const total_power_apparent = () => new Numeric('total_power_apparent', ea.STATE).withUnit('kVA').withDescription('Instantaneous measured total apparent power');
const hw_version = () => new Numeric('hw_version', ea.STATE).withUnit(' ').withDescription('Hardware Version');
const locationDesc = () => new Numeric('locationDesc', ea.STATE).withUnit(' ').withDescription('Zigbee Version');

const definition = {
zigbeeModel: ['SPM02X001'],// The model ID from: Device with modelID 'lumi.sens' is not supported.
model: 'SPM02X001', // Vendor model number, look on the device for a model number
vendor: 'BITUO TECHNIK', // Vendor of the device (only used for documentation and startup logging)
description: 'Smart energy monitor for 3P+N system',
fromZigbee: [converters.electrical_measurement_bituo, converters.seMetering, converters.hw_version, converters.locationDesc],
toZigbee: [],
//configure: tuya.configureMagicPacket,
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);// 选取1为服务端点,spm0x只有1
await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering', 'genBasic']);// 将端点1与haElectricalMeasurement绑定
await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);// 读取电力测量的乘法因子和除法因子
await reporting.readMeteringMultiplierDivisor(endpoint);
await reporting.activePower(endpoint);// 有功功率
await reporting.rmsCurrent(endpoint);// 电流
await reporting.rmsVoltage(endpoint);// 电压
await reporting.powerFactor(endpoint);
await reporting.apparentPower(endpoint);
await reporting.reactivePower(endpoint);
await reporting.currentSummDelivered(endpoint);
await reporting.currentSummReceived(endpoint);
device.save();
},
exposes: [e.ac_frequency(), e.voltage(), e.voltage_phase_b(), e.voltage_phase_c(),
power_kW(), power_kW_phase_b(), power_kW_phase_c(), total_power_kW(),
e.current(), e.current_phase_b(), e.current_phase_c(),
e.power_factor(), power_factor_phase_b(), power_factor_phase_c(),
power_reactive(), power_reactive_phase_b(), power_reactive_phase_c(), total_power_reactive(),
power_apparent(), power_apparent_phase_b(), power_apparent_phase_c(), total_power_apparent(),
// Change the description according to the specifications of the device
e.energy().withDescription('Total forward active energy'),
e.produced_energy().withDescription('Total reverse active energy'),
hw_version(), locationDesc(),
//e.paySwitch().withDescription('预付费开关'),
],
};

module.exports = definition;

@Le-Syl21
Copy link

Le-Syl21 commented Mar 14, 2024

How can I use your code ???
https://github.com/script0803/SPM0X-Z2M-Configuration/blob/main/SPM02_Z3_Local_02.js

@Le-Syl21
Copy link

I've update to Z2M EDGE. It's work but I can't show energy and produced energy:
Capture d’écran du 2024-03-18 11-59-07
Can someone help me ?

@sesame1215
Copy link

I have also purchased the same product from them. They have told me that there are two versions: V1 (older) and V2 (newer). The V2 versions can show the negative values and have more DP (more user friendly in my view). Too bad that V1 cannot OTA to V2.

@maciey
Copy link

maciey commented Apr 3, 2024

I've update to Z2M EDGE

How did you manage to update this device? Did you resolve missign readings issues?

@Le-Syl21
Copy link

Le-Syl21 commented Apr 4, 2024

I have found a solution:
use this file: SPM02_Z3_Local_02.j
then replace line 15:
BEFORE = const extend = require('zigbee-herdsman-converters/lib/extend');
AFTER = const extend = require('zigbee-herdsman-converters/lib/modernExtend');

It's work but need rework like Hz. Look at this:
image
image
image
Bye

@maciey
Copy link

maciey commented Apr 4, 2024

I have found a solution:

Thanks a lot.
Can you plz advise where should I put this file and how to configure HA to read it?

@dfrepos
Copy link

dfrepos commented Sep 28, 2024

Can you please share where exacly do we have to implement this? Is it a OTA firmware update or some other config in HA?

@maciey
Copy link

maciey commented Oct 2, 2024

@Le-Syl21 - same question here... 🙏

@script0803
Copy link

https://github.com/script0803/SPM0X-Z2M-Configuration/blob/main/SPM02_Z3_Local_02.js
This file is the external converter of zigbee2mqtt, it is suitable for the zigbee3.0 version of SPM02, it has the suffix of U01.

So it is not the same product as the SPM02-D2TZ mentioned at the beginning.

I think the U01 version of the device does not need this file now. I have added its configuration to the zigbee2mqtt support library.

@akira215
Copy link

akira215 commented Oct 24, 2024

I'm not using your device but I'm acutally witing a converter for another device. But I note that
const value = (parseInt(data[0]) << 32) + parseInt(data[1]); is failing from my side:

It fail sto left shift 32 bits due to js number limitation. Basically, (parseInt(data[0]) << 32) is equal to parseInt(data[0])

The workaround is to use BigInt : (BigInt(parseInt(data[0])) << BigInt(32) + BigInt(parseInt(data[1]));, after what you will have to always add BigInt in your computations.

Then I received EventBus error 'Receive/deviceMessage': Do not know how to serialize a BigInt , so I convert the result to a string result["Water Consumption"] = (currentSummDelivered * (BigInt(factor) ?? 1n)).toString();

I also note that this could affect more than your converter as I saw this in many other convert code ?

Does anyone experienced the same ? Obviously, it will not affect the result until you reach a value higher than 32bits length int.

Copy link
Contributor

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 30 days

@github-actions github-actions bot added the stale Stale issues label Dec 30, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale Stale issues
Projects
None yet
Development

No branches or pull requests

8 participants