jeatheak / Mitsubishi-WF-RAC-Integration

WF-RAC homeassistant integration
MIT License
101 stars 19 forks source link

Add energy sensor #6

Closed jeatheak closed 1 year ago

jeatheak commented 1 year ago

Some Airco models have built in energy monitoring. It is possible to read this and add is as an energy senor

jeatheak commented 1 year ago

It is implemented with release v0.1.0 and PR #11 But there can be some issues so I will not close this one yet

virtualdj commented 1 year ago

How do you interpret the energy monitoring sensor? These days I had the Airco always turned off and the app reports 13.5 kWh (as it actually counts the total kWh of the month), while HA has a value variable in time but negative (-35.25 kWh usually).

Today:

immagine

Previous days including today (it has always been off, so I would expect a straight line):

immagine

Is it working correctly in your case? Or maybe you don't have a model with the energy sensor?

jeatheak commented 1 year ago

Hey,

At the moment I don't calculate the energy usage. The value that is returned from te parsed airco payload is converted into a sensor.

I did add the sensor to the energy dasboard and on my side it looks like it is working.

But till now I did not find the correct way to implement it from the app code. I will try to investigate what is happing here.

virtualdj commented 1 year ago

So you don't get negative values? I mean on the sensor directly, just like the screenshots above, and not in the energy dashboard.

KixAss commented 1 year ago

It looks like it’s working on my end. I don’t get negative values.

84A12F2C-B7AD-424B-91FD-C05608CF997B

What you could do to add a power-usage sensor is to substract the previous value from the current value (and devide by 1000 to get Watt). You’ll need to check if the value is higher, else it could be reset and you’ll need to return 0.

virtualdj commented 1 year ago

I will try to investigate what is happing here.

With some help I was able to debug the application and I managed to capture these strings:

AACnmbL/AAAAAAASCgAAAAAAAf////8RCYAEABE0qQAAiAAAAgAAAAAAABWAIKn/gBC//4EgbP+BEP7/ghBG/4cQbf+FEA7/sRAA/5AQAP+UEBcAHyD//x8Q//8TEPoAERAA/w0A//8hEP//MhoE/zQRAQQ1EAP/HhAA/x4RAf9fIA==

AACnmbL/AAAAAAASCgAAAAAAAf////8RCYAEABE0qQAAiAAAAgAAAAAAABWAIKn/gBDA/4EgbP+BEP7/ghBG/4cQbf+FEA7/sRAA/5AQAP+UEBcAHyD//x8Q//8TEPoAERAA/w0A//8hEP//MhoE/zQRAQQ1EAP/HhAA/x4RAf8GPw==

all of them should result in a ac.Eletric = 5.75 according to the app debugging; the app itself then shows 13.5 kWh because it probably sums this value with previous from the cloud? Anyway, no negative values here.

So, I took your code from commit 7adae44, because I don't know how to run it "manually" on Python with the recent commits that have the Home Assistant integration (is it possible, and if so, how?).

I changed your main.py like this:

import json
from src.parser import Parser
from src.models.aircon import Aircon
from src.repository.repository import Repository
from src.utils import Utils
from base64 import b64decode

# My decoding test (first of the base64 strings above)
statusString = 'AACnmbL/AAAAAAASCgAAAAAAAf////8RCYAEABE0qQAAiAAAAgAAAAAAABWAIKn/gBC//4EgbP+BEP7/ghBG/4cQbf+FEA7/sRAA/5AQAP+UEBcAHyD//x8Q//8TEPoAERAA/w0A//8hEP//MhoE/zQRAQQ1EAP/HhAA/x4RAf9fIA=='
ac = Parser.translateBytes(statusString)
currentStatus = json.dumps(ac.__dict__, sort_keys=False, indent=4)
print(currentStatus)

Running this "static decoding" results in:

{
    "Operation": false,
    "PresetTemp": 26.0,
    "OperationMode": 0,
    "AirFlow": 2,
    "WindDirectionUD": 2,
    "WindDirectionLR": 3,
    "Entrust": false,
    "CoolHotJudge": false,
    "ModelNr": 0,
    "Vacant": 0,
    "ErrorCode": "00",
    "IndoorTemp": 27.5,
    "OutdoorTemp": 25.0,
    "Electric": -37.0
}

As you can see on the last line, there's the negative value. I also placed a breakpoint here and compared the values of all the 130 bytes of the content variable with the ones captured from the debugging: they're exactly the same.

So probably there's something wrong in the decoding function or the previous byte packing? Unfortunately I'm not able to debug the single steps of the calculation in the app... I only know the starting point (the base64 string) and the final result (i.e. the value of the Electric field). Could this be of any help?

KixAss commented 1 year ago

One other weird thing: the bottom airco hasn’t been on, but has the same usage as the airco above that one.

86BE3B0C-760C-473C-B6E4-77815070C0ED

virtualdj commented 1 year ago

To get the 5.75 these two lines: https://github.com/jeatheak/Mitsubishi-WF-RAC-Integration/blob/4454fdd94918e1b745e0af601f6145fcd5e2f8b2/custom_components/mitsubishi-wf-rac/wfrac/rac_parser.py#L269-L270 must be changed to:

 if len(value_segment) >= 39: 
     ac_device.Electric = ((value_segment[39] << 8) + value_segment[38]) * 0.25 

immagine

@KixAss Can you try the modification and see how it changes your values? Just out of curiosity...

virtualdj commented 1 year ago

@jeatheak I would also check this: https://github.com/jeatheak/Mitsubishi-WF-RAC-Integration/blob/4454fdd94918e1b745e0af601f6145fcd5e2f8b2/custom_components/mitsubishi-wf-rac/wfrac/rac_parser.py#L266-L268 value_segment[n] should be a byte; when it is negative all is working well, but what happens when it goes positive? I think a IndexError: list index out of range error.

Probably it's better to replace the whole block like this (avoiding checking the length, too?):

ac.Electric = None
for i in range(0, len(valueSegment), 4):
    if (valueSegment[i] == -128 and valueSegment[i+1] == 32):
    ac.IndoorTemp = (Constants.indoorTemp[valueSegment[i+2] & 0xFF])
    if (valueSegment[i] == -128 and valueSegment[i+1] == 16):
    ac.OutdoorTemp = (Constants.outdoorTemp[valueSegment[i+2] & 0xFF])
    if (valueSegment[i] == -108 and valueSegment[i+1] == 16):
    ac.Electric = (((valueSegment[i+3] << 8) + valueSegment[i+2]) * 0.25)
mcheijink commented 1 year ago

I think that this check is done because not all units report back their energy usage. And it can probably be optimized a bit, since the current implementation is logically a copy of the official android app’s implementation. And the official implementation contains some weird stuff… After my holidays (in 1.5 week) I can have a thorough look at it. EDIT: I did have a chance to run the airco a couple of hours yesterday, to compare the increase in electricity usage. The airco reported a number of 2.25 kWh, with the house reporting 2.75 kWh. I do believe that the house is correct, since it’s reported power corresponds to the rated power. Today, the airco reported 0 kWh. So it seems to be resetting to zero every day.

jeatheak commented 1 year ago

Hi Guys,

Thank you for all the investigations I did not have much time on hand. But I did push a new fix on the main branch. I did find that the parsed values for energy can be different per airco.

So I added a while loop that loops trough the last segment. and checks if a combination of bytes are pressent.

If you guys could test it for me that would be awesome. If all is correct I will make a new Release.

EDIT: I did not read the Code change of @virtualdj very good. But the proposed change is better than the code I did write so I adjusted it and commited that one. Thanks @virtualdj .

EDIT2: put a second fix because I did switch the indoor and outdoor temp

virtualdj commented 1 year ago

I did find that the parsed values for energy can be different per airco.

Thanks. As you can see, now the energy value is stable and doesn't change like on my chart on the previous posts. It seems that we're OK now (though it's still different from what the app reports)!

immagine

Thanks @virtualdj

We're here to help each other, that's the good thing of the open source and Github!

Today, the airco reported 0 kWh. So it seems to be resetting to zero every day.

In my case, it doesn't reset every day. In fact it's still 5.75 now, and the airco has not been switched on in the meanwhile.

KixAss commented 1 year ago

When I look at my stats, it's like it is being reset every time i turn the airco off and on:

image

(i'm still using the old v0.1.0)

jeatheak commented 1 year ago

Ok good to hear.

In my case, it doesn't reset every day. In fact it's still 5.75 now, and the airco has not been switched on in the meanwhile.

The reset only happens when you switch on the airco. So if you don't use the airco it will keep those values. (but I'm not entirely sure, I did see that happening once) @KixAss I see you where faster at replying. but glad to see the same is happening at yours.

(though it's still different from what the app reports)!

The app has more data. it concats the data from the entire month

image

KixAss commented 1 year ago

Maybe it would be nice to add a sensor where you calculate the power-usage (in Watt instead of kWh). Then you'll have the current usage and can calulate anything you want (for example with the Utility-integration).

If i'm correct you can calculate kWh to Watt when you get the Current kWh - Previous kWh (delta_kWh). Then you calculate the Current timestamp - Previous timestamp (in seconds) (delta_s).

Then you can calculate Watt by: (delta_kWh / delta_s) 1000 3600

mcheijink commented 1 year ago

Maybe it would be nice to add a sensor where you calculate the power-usage (in Watt instead of kWh). Then you'll have the current usage and can calulate anything you want (for example with the Utility-integration).

If i'm correct you can calculate kWh to Watt when you get the Current kWh - Previous kWh (delta_kWh). Then you calculate the Current timestamp - Previous timestamp (in seconds) (delta_s).

Then you can calculate Watt by: (delta_kWh / delta_s) 1000 3600

I would not be in favor of this, since we are doing work on the data reported by the airco. And this is easily implemented by creating a “derivative” sensor of the energy usage.

jeatheak commented 1 year ago

I will leave this open for a few days to check if everything is going ok. Please let me know if it working correctly.

mcheijink commented 1 year ago

image It seems to roll back when it hits 32 kWh, which is a quarter of 128. The step size is 0.25 kWh. This would indicate that we are using 8 bit to display the energy usage, and that Python perhaps needs to interpret the data as an unsigned integer instead of a signed integer. 8 bit still seems low for this type of data. Especially since we are reading the data from two bytes. So perhaps it would be beter to interpret it as a uint16 (or int16)?

virtualdj commented 1 year ago

The step size is 0.25 kWh. This would indicate that we are using 8 bit to display the energy usage, and that Python perhaps needs to interpret the data as an unsigned integer instead of a signed integer. 8 bit still seems low for this type of data. Especially since we are reading the data from two bytes. So perhaps it would be beter to interpret it as a uint16 (or int16)?

I don't know how Python works, but in the the app, that uses Java, the calculation is done like so:

int high_part;
byte low_part;
double result = ((double)(((high_part & 0xFF) << 8) + (low_part & 0xFF))) * 0.25;

AFAIK both int and byte in Java should be signed, but the high_part is in a bitwise AND that should remove the sign as it's dropping the most significant byte. The low_part could be signed, though.

So considering the maximum high_part and low_part we have: (255 256 [left shift of 8 bits] + 127) 0.25 = (65280 + 127) * 0.25 = 1635175 kWh which should be more than enough... Provided that the bytes are coming in correctly, of course.

This is the current Python code: https://github.com/jeatheak/Mitsubishi-WF-RAC-Integration/blob/10cb10434ca36c27d6ab4b596b474be964c17f87/custom_components/mitsubishi-wf-rac/wfrac/rac_parser.py#L269-L270

I think you're right, because as bytes in Java are always signed, the first part of the Python:

(vals[i + 3] << 8)       # TODO: vals[i + 3] must always be positive!

is not a correct translation. Previously, there's a function that converts the bytes to signed (to look like Java I presume): https://github.com/jeatheak/Mitsubishi-WF-RAC-Integration/blob/10cb10434ca36c27d6ab4b596b474be964c17f87/custom_components/mitsubishi-wf-rac/wfrac/rac_parser.py#L222-L225 but this is not correct for the part above and should be changed to return an always positive number (0..255). How to do that in Python is out of my knowledge :-(