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

Decoding sensor data #1

Open
krconv opened this issue Nov 22, 2021 · 112 comments
Open

Decoding sensor data #1

krconv opened this issue Nov 22, 2021 · 112 comments

Comments

@krconv
Copy link
Collaborator

krconv commented Nov 22, 2021

Repurposing this issue for the discussion that it started. Was originally a question about connecting, and turned into a discussion about deciding the sensor data.


In the blog post, you mentioned that one of your USB-Serial adaptors wasn't working when you tried to connect to the ESP32. I'm wondering if I'm hitting the same issue; I only have one serial adaptor to test with at the moment though.

I'm using the FT232RL FTDI Mini USB to TTL Serial Converter, and getting nothing while trying to connect:

> esptool.py --port /dev/cu.usbserial-A50285BI read_flash 0 0x400000 flash_contents.bin
esptool.py v2.8
Serial port /dev/cu.usbserial-A50285BI
Connecting........_____....._____....._____....._____....._____....._____....._____

A fatal error occurred: Failed to connect to Espressif device: Timed out waiting for packet header

Process I'm taking to enter flashing mode:

  1. Power with 3.3v
  2. Pull IO0 pin to GND
  3. Pull EN pin to GND, and release
  4. Release IO0 pin

But that's not working; my only thought is that maybe there is something else connected with TX/RX that is interfering? And a different serial converter could sort through the noise?

Any guidance would be appreciated!


Edit by @flaviut for visibility:

There is now a ESPHome component. You can follow development at

@krconv
Copy link
Collaborator Author

krconv commented Nov 22, 2021

Actually, I had TX and RX backwards; TX on the debug header goes to RX on the ESP32. My bad (you mentioned it in the blog post and I missed it).
Just successfully downloaded the flash and got debug logs

@krconv krconv closed this as completed Nov 22, 2021
@flaviut
Copy link
Collaborator

flaviut commented Nov 22, 2021

Ha! This stuff happens, glad you got it figured out! Please feel free to ask anything else, and let me know if you end up building something on top of all this!

@krconv
Copy link
Collaborator Author

krconv commented Nov 22, 2021

My main goal is to build an esphome integration. I've never had to fully reverse a chip before, but it feel like a fun challenge that I might be able to help with, and I like the small form-factor and low price of the Vue. I'd appreciate any pointers if you have any; currently waiting for a USB oscilloscope to help with identifying what each of the pins are doing.

@flaviut
Copy link
Collaborator

flaviut commented Nov 22, 2021

Sure! First off, oscilloscopes are very helpful for identifying the function of things, but when it comes to actually reading digital data you want a logic analyzer.

Anyway, please do poke at it, it's a lot of fun. If you'd like though, I briefly described the physical communication on the board here: https://flaviutamas.com/2021/reversing-emporia-vue-2#on-board-communications

There's another chip on the board that communicates with the ESP32 over I2C. The raw I2C message gets dumped over MQTT as hex here: https://github.com/flaviut/emporia-vue2-reversing/blob/master/example_messages.txt#L2-L19. Note that right below that hex data are the output results: the I2C data is combined with the calibration data stored in NVS to produce the final output: The calibration data in NVS is nulled and not used at all.

diagram showing data flow

@flaviut
Copy link
Collaborator

flaviut commented Nov 23, 2021

By the way, I've given you write access to this repo! Please feel free to do whatever you wish with that access, but let's keep Emporia-copyrighted stuff off of here to avoid causing them problems :)

@Maelstrom96
Copy link
Collaborator

Maelstrom96 commented Nov 23, 2021

Hey - I'm actually working on the exact same goal as @krconv. I'm mostly trying to make it so I can add a separate ESP32 running ESPHome that would read the messages on the i2c bus and publish that data to MQTT or through the native ESPHome API, without the need to flashing the onboard module. The reason being that I like having the data in their UI, but I also want to be able to do second triggers on the energy consumption. At first I was thinking about making a MQTT bridge to their AWS IOT mqtt endpoint, but I wasn't able to reverse engineer the certificates/auth method.

Because of this, I've been working on reverse engineering the i2c message data and I think I mostly have it figured out at this point. Here is my data spreadsheet I used to figure out / validate the parsing logic: https://docs.google.com/spreadsheets/d/1Yu98tDRhFJqqMntNak6X297SKyzQtf0_Cvv_g7qKewk/edit?usp=sharing.

The only data missing from the message is the calibration - which is only on the esp32 - and the voltage frequency.

Edit: Forgot to say, thanks @flaviut for the post and the data in the repo, it was immensely useful. Also, I updated my Vue2 firmware to the last version (Vue2-1612298550, Tue Feb 2 20:42:33 UTC 2021) and it's still outputting logs and the TEST_MODE is still there and it looks like it's still working.

@flaviut
Copy link
Collaborator

flaviut commented Nov 23, 2021

WOW! This is incredible! I'm curious about how you went about doing this, I did spend a while looking at things in a hex editor, but didn't really get anywhere.

@flaviut
Copy link
Collaborator

flaviut commented Nov 23, 2021

It took me a bit to understand the spreadsheet, but now that I do, I've written up a C declaration for that data:

// all data is little-endian
struct __attribute__((__packed__)) ch_power {
    int32_t ph1, ph2, ph3;
}

struct __attribute__((__packed__)) vue2_i2c_data {
    uint32_t unknown1;
    ch_power power[19];
    int16_t ph_voltage[3];
    int16_t frequency;
    int16_t ph2_offset_deg;
    int16_t ph3_offset_deg;
    int16_t current[19];
    uint16_t unknown2;
}

One thing that I suspect is going on here (from my use of the Vue 2 locally) is a non-1:1 mapping of channel numbers to data indexes. I think, but am not sure, that some of the indexes here do not match up with the numbers printed on the box.

@Maelstrom96
Copy link
Collaborator

Maelstrom96 commented Nov 23, 2021

I'll start off by saying that I'm mostly out of my league when it comes down to reverse engineering things, but I wanted to give it a shot anyway.

Input P[V] values

The first thing I did was to manually check the debug/v2 message that you had in your post to see if I could find any pattern. An obvious one was the amount of FFFF value bytes separated by 2 bytes of data. I also noticed that all the P[V] values were small-ish negative ones. Doing a find and count for FFFF in Notepad++ returned 57 match. Dividing that value with the 3 P[V] per input results in 19 - the same amount of input we have in the logs. That block was then clearly the data for all the input power values.

I copied the first 12 bytes (which should correspond to the first 3 P[V] of I01) in this amazing hex tool and started to check for other patterns in the data. I also copied another 12 bytes from another message with positive values, and the values seemed to correlate when parsed as INT32 (DCBA).

I wasn't sure what to do then, since the decimal values weren't the same as the expected values. I decided to plot values from multiple inputs on a graph to see if there was a linear equation hidden somewhere.

Here's what it gave me :
2021-11-22_23h44_31

As you can see, it does look like it's linear, but calculating the actual expression gave me a pretty significant offset (which can also be seen on the graph since the line is not going through 0). A small offset is expected since the values are most likely rounded, but the result is too much. Also, by looking at the graph, we can see that there are 2 general groups. I was able to see that the slope value was different between I01-I03 and I04+.

After thinking about it, and checking the points (expected value and RAW decimal) on the graph, I noticed that not all the points were exactly on the trend line, so I started to wonder if the calibration numbers could be at play, since it would explain the deviation. I decided to multiply the values by their respective calibration and plot those values.

Here's the graph after that :
2021-11-22_23h48_04

As you can see, the deviation became negligible, and the offset was almost 0 (still some but very small, > 0.03). This told me I should now be able to calculate the slope and then use that to get the P[V] value. The slope 5.5% for the first 3 inputs, and 22% for the rest.

Voltage ADC

After that, I was wondering what other values I could find, and decided to enter the next couple of bytes into the HEX tool, and got three values that I could spot easily in the debug output:

pos Raw INT16
0 00 0F 15
2 00 0C 12
4 00 0B 11
V1:   0.3,  0.4 Hz, *15*, 0.0225336
V2:   0.3, 0 degrees, *12*, 0.0220000
V3:   0.2, 0 degrees, *11*, 0.0220000

I wasn't sure what those values where, but I decided to see if multiplying this with the calibration data would actually give the voltage, and it actually did. So I figure this is a voltage ADC value.

Input Current?

At this point, the amount of bytes of data was pretty low, not enough for 19 x int32, I also noticed that the voltage ADC was an int16, so I started to see if I could notice any pattern with 19 x int16 in a row. It was pretty easy to spot since all the values were pretty much the same, see for yourself :

0050 0050 0050 FD4F FD4F FD4F FD4F FD4F 0350 0350 0350 FD4F FD4F FD4F FD4F FD4F 0350 0350 0350

I01:  372.4, P[V1]:   -80.0, P[V2]:   -21.9, P[V3]:    -4.9
I02:  372.4, P[V1]:   -80.0, P[V2]:   -21.9, P[V3]:    -4.9
I03:  372.4, P[V1]:   -80.0, P[V2]:   -21.9, P[V3]:    -4.9
I04:   93.1, P[V1]:   -19.4, P[V2]:    -5.2, P[V3]:    -0.9
I05:   93.1, P[V1]:   -20.3, P[V2]:    -5.6, P[V3]:    -1.7
I06:   93.1, P[V1]:   -20.5, P[V2]:    -5.8, P[V3]:    -1.3
I07:   93.1, P[V1]:   -19.2, P[V2]:    -4.4, P[V3]:    -2.4
I08:   93.1, P[V1]:   -20.0, P[V2]:    -5.7, P[V3]:    -0.7
I09:   93.1, P[V1]:   -20.9, P[V2]:    -4.8, P[V3]:    -1.0
I10:   93.1, P[V1]:   -19.4, P[V2]:    -6.2, P[V3]:    -1.0
I11:   93.1, P[V1]:   -20.1, P[V2]:    -6.1, P[V3]:    -0.8
I12:   93.1, P[V1]:   -19.4, P[V2]:    -5.2, P[V3]:    -0.9
I13:   93.1, P[V1]:   -20.3, P[V2]:    -5.6, P[V3]:    -1.7
I14:   93.1, P[V1]:   -20.5, P[V2]:    -5.8, P[V3]:    -1.3
I15:   93.1, P[V1]:   -19.2, P[V2]:    -4.4, P[V3]:    -2.4
I16:   93.1, P[V1]:   -20.0, P[V2]:    -5.7, P[V3]:    -0.7
I17:   93.1, P[V1]:   -20.9, P[V2]:    -4.8, P[V3]:    -1.0
I18:   93.1, P[V1]:   -19.4, P[V2]:    -6.2, P[V3]:    -1.0
I19:   93.1, P[V1]:   -20.1, P[V2]:    -6.1, P[V3]:    -0.8

I wasn't sure why there were 2 different values FD4F & 0350 for what looked like 93.1, but I was still pretty sure that they were the right bytes. I tried to multiply one of the values with it's input offset annnnnnnnnnnd:

0x0050 = 20480

20480 x 5.5 = 112,640

Well, that doesn't look anything close to 372.4...
How about a division?

20480 / 5.5 = 3,723.63636363636[...]

That was mostly pure luck to find that, but it looks like it is indeed how it's calculated. Just need to shift the period and we're golden!

Voltage degrees

I know it's pretty useless and not needed, but at this point I feel like superman, so I wanted to see If I could crack the code.

The amount of bytes left is pretty limited : 03EC 52EA [...] A601 8E00 0000 [...] 0000

There's only a single block with 3 int16, so it's only logical that A601 8E00 0000 are the right ones, but converting them didn't really give useful numbers :

V1: 120.2, 61.6 Hz, 5241 ADC, 0.0229308 Calibration
V2: 121.3, 121 degrees, 5574 ADC, 0.0217630 Calibration
V3:   8.1, 0 degrees, 369 ADC, 0.0220000 Calibration
pos Raw INT16
0 01 A6 422
2 00 8E 142
4 00 00 0

The 0 degree made sense, but how about the 142? How does that become 121 degrees? I wondered if the values were a ratio of 360°. Calculating it actually gives the right value.

142       x
---  =  ----
422     360

x = 360 x 142 / 422
x = 121.13[...]

I haven't figured out if the voltage frequency is in the data, but I don't think it is, since the first 4 bytes seems to be some kind of counter, and the last 2 bytes are always 0x0000. It could actually be the 422 value, but I would need to have more data with variations to confirm it.

@Maelstrom96
Copy link
Collaborator

One thing that I suspect is going on here (from my use of the Vue 2 locally) is a non-1:1 mapping of channel numbers to data indexes. I think, but am not sure, that some of the indexes here do not match up with the numbers printed on the box.

If you have a message that you think doesn't match the mapping, let me know. I don't believe that it will deviate from this structure but we never know.

@Maelstrom96
Copy link
Collaborator

@flaviut I wanted to know - do you happen to know the i2c frequency? I'm trying to connect to it but I'm not having much luck at 50kHz or 100kHz.

@flaviut
Copy link
Collaborator

flaviut commented Nov 24, 2021

@Maelstrom96 I believe it's 100kHz. I uploaded a dump from sigrok pulseview to https://github.com/flaviut/emporia-vue2-reversing/blob/master/i2c%20dump.bin, and unless the time base is off in that, it shows 100kHz.

2021-11-23-221659_950x909_scrot

That hopefully the screenshot will also explain how it expects to be asked for data.

@krconv
Copy link
Collaborator Author

krconv commented Nov 24, 2021

This is awesome! I just had fun decoding the message too, and it's cool to see that I landed in the same place. I used Python to help me walk through the message, activating each of the inputs and finding the bytes that changed. In case your curious, this is my helper script. I was just about do try to figure out the calibration as well, and ended up with a linear relationship (just eye-balled it, didn't verify it was exactly linear) between points on I04[V2], similar to you.

From my testing, I'm pretty sure the uint16 with offset 0x00EE is the frequency; it was sandwiched between the AC voltages and degrees, and it was consistently changing with the frequency output in MQTT. The conversion for my device is 0.0014268004584473604 * <sensor_value> + 0.4 = frequency; tried it on the example messages @flaviut and it was a little off, but I think that is due to a different calibration.

For calibration data; I think we could do the same thing that the existing CT clamp sensor in ESPhome does, where whoever is flashing the device would need to manually calibrate each of the sensors. Maybe we could reverse engineer the calibration data from nvs, and make that easier? I think it's worth skipping that for now.

@Maelstrom96 Would you want to work together on the integration? I'm not familiar with building an ESPHome integration, are you? I am familiar with C++ and think I could make faster progress on the integration though if you want to keep looking into the connections on the board because I'm not as familiar with the signal processing side of things.

@krconv
Copy link
Collaborator Author

krconv commented Nov 24, 2021

And I'm pretty excited...I feel like this will be a huge improvement for cost effective energy monitoring if we can get it to work

@krconv krconv changed the title Serial adaptor for flashing Decoding sensor data Nov 24, 2021
@krconv
Copy link
Collaborator Author

krconv commented Nov 24, 2021

Reopening for visibility, to make it easier to find for others interested

@Maelstrom96
Copy link
Collaborator

@krconv I'm not really familiar with ESPHome components. I tried to see how ESPHome handles I2C devices and I'm not sure if we're going to have an issue with the TwoWire lib that they're using. The reason being that is looks like requests read buffer is limited to 32 bytes with TwoWire, and we need to query the full 284 bytes all at once.

@krconv
Copy link
Collaborator Author

krconv commented Nov 27, 2021

I've been trying to learn how various ESPHome components work. I think under the hoods, i2c will use the TwoWire library by default, but we can configure it to talk to the onboard i2c controller instead and I'm guessing that's how the the original software does it.

I'm not sure what other implications this has, but this is an example from here to tell ESPHome to use the native ESP API:

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf
    version: recommended

flaviut added a commit to flaviut/esp8266-Arduino that referenced this issue Nov 27, 2021
This is needed since in some applications, we need much much larger buffers.

See emporia-vue-local/emporia-vue2-reversing#1 (comment)
@Maelstrom96
Copy link
Collaborator

We'll most likely have an issue with https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.h too. And if you look at Wire.cpp, the requestFrom() implementation cast down the "len" value to an uint_8, which doesn't go high enough for our 284 bytes.

@krconv
Copy link
Collaborator Author

krconv commented Nov 27, 2021

I tried out that approach, and agreed I don't think it's much of an option. I think setting the framework type to esp-idf will cause it to load this driver (instead of any of the Arduino ones), and from what I can tell it doesn't have any internal buffers, which I think is good. But, setting the framework type also causes everything to attempt to compile using the native framework, and there isn't one for MQTT according to esphome, so validation throws this error:
image

From what I understand, Arduino is a generic framework, and arduino-esp32 are the bindings written to port Arduino to ESP32. ESPHome just barely added support for using the native esp-idf framework in release 2021.10. It seems like if we could get @flaviut's PR merged to the esp8266 and esp32 Arduino libraries, we could keep doing I2C through Arduino, or if we have enough support on esp-idf, we could go in that direction (because this component would only need to be used on esp32 anyways).

Also, I've been thinking about a potential Config format; have either of you tried that out yet? Any thoughts on this idea?

Config Idea
sensor:
  - platform: emporia_vue
    update_interval: 2s
    phases:
      - id: phase_a
        phase_input: BLACK # i.e. the color of the wire that is connected to the phase
      - id: phase_b
        phase_input: RED
    power:
      - id: total_phase_a
        phase_id: phase_a # not sure how the factory software figures out which phase to use; this makes it explicit
        ct_input: A # uses A-C, 1-16 as labeled on the outside of the Vue
        filters: # and each one would have a separate calibration
          - calibrate_linear:
            - 0 -> 0
            - 2393 -> 2.0
      - id: kitchen_outlets_power
        phase_id: phase_a
        ct_input: "1"
        filters:
          - calibrate_linear:
            - 0 -> 0
            - 2393 -> 2.0
      - id: stove_power
        name: Stove Power
        phase_id: phase_b
        ct_input: "2"
        filters:
          - calibrate_linear:
            - 0 -> 0
            - 3438 -> 2.0
          - lambda: return x * 2;
 

I've been testing out config validation on my fork using this external component:

external_components:
  - source: github://krconv/esphome@add-emporia-vue
    components: [ emporia_vue ]
    refresh: 1min

I haven't tested anything on hardware yet, but my next step might be to connect a spare ESP32's i2c port to the Vue and see if it can talk to the sensor at all with the esp-idf library

@flaviut
Copy link
Collaborator

flaviut commented Nov 27, 2021

cal_data is a false lead--the calibration data is internal to the ESP32 for their wifi hardware.

The real calibration constants are cReal and cApparent.

However, the values here are surprising. The interesting data is marked as erased, while the uninteresting version is not:

      {
        "entry_state": "Erased",
        "entry_key": "cReal",
        "entry_data": "AAAgKJkOI0EAAOC+axEjQQAA8OEE2SFBAACAbhjnAkEAAHCC3dECQQAAhAty2wJBAADg+pYAA0EAAKhAYtoCQQAA2O8s5wJBAAAkLELwAkEAADA5JMoBQQAAtLRw6wJBAAC4VIYKA0EAACjF59kCQQAAYHSiBwNBAAAwksz1AkEAALj8Wt8CQQAA6OxK6AJBAAAQYf0SA0E="
      },
      {
        "entry_state": "Erased",
        "entry_key": "cApparent",
        "entry_data": "AACA/dMMwEAAAACbBxDAQAAAgB+DMcFAAAAAb/v/n0AAAACo+dufQAAAABPd7p9AAACAwU0VoEAAAACnb+ifQAAAAAaV/Z9AAACAftMIoEAAAIAUqTShQAAAAJyuA6BAAAAAsfAboEAAAACm7+ifQAAAgOacGaBAAAAAIswKoEAAAAB68PKfQAAAAEllAaBAAACAiX0koEA="
      },
      {
        "entry_state": "Erased",
        "entry_key": "cReal",
        "entry_data": "AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEA="
      },
      {
        "entry_state": "Written",
        "entry_key": "cApparent",
        "entry_data": "AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEA="
      },

Decoding these gets you basically what you expect: 19 entries, the first 3 of which are different than the rest.

$ base64 -d | xxd
AAAgKJkOI0EAAOC+axEjQQAA8OEE2SFBAACAbhjnAkEAAHCC3dECQQAAhAty2wJBAADg+pYAA0EAAKhAYtoCQQAA2O8s5wJBAAAkLELwAkEAADA5JMoBQQAAtLRw6wJBAAC4VIYKA0EAACjF59kCQQAAYHSiBwNBAAAwksz1AkEAALj8Wt8CQQAA6OxK6AJBAAAQYf0SA0E=
00000000: 0000 2028 990e 2341 0000 e0be 6b11 2341  .. (..#A....k.#A
00000010: 0000 f0e1 04d9 2141 0000 806e 18e7 0241  ......!A...n...A
00000020: 0000 7082 ddd1 0241 0000 840b 72db 0241  ..p....A....r..A
00000030: 0000 e0fa 9600 0341 0000 a840 62da 0241  .......A...@b..A
00000040: 0000 d8ef 2ce7 0241 0000 242c 42f0 0241  ....,..A..$,B..A
00000050: 0000 3039 24ca 0141 0000 b4b4 70eb 0241  ..09$..A....p..A
00000060: 0000 b854 860a 0341 0000 28c5 e7d9 0241  ...T...A..(....A
00000070: 0000 6074 a207 0341 0000 3092 ccf5 0241  ..`t...A..0....A
00000080: 0000 b8fc 5adf 0241 0000 e8ec 4ae8 0241  ....Z..A....J..A
00000090: 0000 1061 fd12 0341                      ...a...A
$ base64 -d | xxd
AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEA=
00000000: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000010: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000020: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000030: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000040: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000050: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000060: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000070: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000080: 0000 0000 0000 2440 0000 0000 0000 2440  ......$@......$@
00000090: 0000 0000 0000 2440                      ......$@

I bet that these can be decoded as:

struct __attribute__((__packed__)) calib_entry {
    int32_t offset, slope;
}
typedef calib_entry[19] calibration;

@flaviut
Copy link
Collaborator

flaviut commented Nov 27, 2021

@Maelstrom96 You're right--increasing the number there won't work unless we adjust the function signatures too. We already have https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.h#L98, and it's a real shame that that function is written in terms of the other functions rather than having all the other functions call it. However, it seems like a simple-enough refactoring, and the ESP32-arduino project seems responsive to contributions.

@krconv

framework type to esp-idf will cause it to load this driver (instead of any of the Arduino ones), and from what I can tell it doesn't have any internal buffers, which I think is good. But, setting the framework type also causes everything to attempt to compile using the native framework

:(

I guess you could try adding MQTT support to the esp-idf version of the esphome project, that'd probably be something useful for everyone. But I think the fastest & easiest way would be to do PRs to the wire library, since they're pretty simple. I can do that over the next day or two!

Also, I've been thinking about a potential Config format; have either of you tried that out yet? Any thoughts on this idea?

Huh. Neat! I had no idea ESPHome made things that easy, that's really nice!

@krconv
Copy link
Collaborator Author

krconv commented Nov 28, 2021

The calibration data I think will be helpful, the more I think about it. Calibrating 19 CT clamps manually might be a real pain; maybe we could provide a script that helps parse the calibration data later on.

That sounds great; I agree getting MQTT on esp-idf would probably be helpful for everyone, but ya it might slow things down. I also checked the api component, and that relies on Arduino too; so there would be no way to get the sensor data to home assistant without some more development. I did find a native MQTT library; if we do go that route, we might be the first integration that requires esp-idf, which might have some other implications. I'll take a look at the work involved to support MQTT on esp-idf and see if the i2c using the native library even works for this scenario.

@blackburn42330
Copy link

Ok that makes sense. I haven’t purchased a usb to serial adapter yet. I wanted to make sure I could understand it all before spending money to dive into it.

@kgeange
Copy link

kgeange commented Jan 13, 2022

Exciting little project... I received my Vue last week and followed flaviut's "Reverse-engineering" guide to get it talking to mosquitto / node-red running on a Raspberry Pi via MQTT. The parse_mqtt_dbg.py is pretty straight forward and outlines how to process the MQTT messages into an InfluxDB, which I am looking to reimplement in node-red. I notice that it's over 4 months old. Is there any newer code / documentation available which outlines the processing of the MQTT messages? Or is that pretty much all there is to it???

The circuit breaker on my hot tub trips a couple times each winter and I almost lost the hot tub last year due to the breaker tripping (it was well below freezing and the power was off for a few days). By monitoring the power usage I can get an alert as soon as the breaker trips again... Thanks for your hard work and dedication to this project. It really opens up this sweet little device as a powerful home automation tool.

@flaviut
Copy link
Collaborator

flaviut commented Jan 13, 2022

The parse_mqtt_dbg.py is pretty straight forward and outlines how to process the MQTT messages into an InfluxDB, which I am looking to reimplement in node-red. I notice that it's over 4 months old. Is there any newer code / documentation available which outlines the processing of the MQTT messages?

That's all there is to it. process_message() returns a Vue2Reading, which you can use however you want. Only a few of the lines are MQTT-specific.

The reason there hasn't been any more work on that is that running fully open-source firmware is less janky (doesn't require specific IP addresses, working with copywrited firmware, or any limits on the wifi password). So all effort has gone into the FOSS firmware, through ESPHome.

The circuit breaker on my hot tub trips a couple times each winter and I almost lost the hot tub last year due to the breaker tripping (it was well below freezing and the power was off for a few days).

Wow! That's an awesome use, and I'm glad your hot tub survived!

Maybe you could use another esp32, duct tape, and one of these to make yourself a self resetting breaker 😁

@kgeange
Copy link

kgeange commented Jan 13, 2022

lol... I'll see what I can do about the automated breaker resetter...

Thanks again... You guys rock!

@PeterHaban
Copy link

I've finally taken the plunge and ordered mine yesterday after finding this thread. Just wanted to say thanks for all the hard work, brilliant guide and give a quick bit of feedback on how things went for me.
The unit and PCB I got look exactly like the pictures in the guide, I had the extra flux on the solder pads which was fairly easy to remove. My solder pads were full so I drilled them out with a tiny Dremel drill and soldered in headers. Everything from thereon was surprisingly straightforward. I've connected the cables from the unit's supply tail into a 13amp plug (black - Live, all other = Neutral), given that my consumer unit doesn't have enough spare room or a free breaker I'm going to mount the Emporia next to the consumer unit.

  1. Backup from Ubuntu with a basic USB adapter:
    esptool.py version
    esptool.py v2.8
    esptool.py -b 921600 read_flash 0 0x800000 flash_contents.bin
    esptool.py v2.8
    Found 1 serial ports
    Serial port /dev/ttyUSB0
    Connecting......
    Detecting chip type... ESP32
    Chip is ESP32D0WDQ5 (revision 1)
    Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
    Crystal is 40MHz
    MAC: 40:f5:20:88:03:d8
    Uploading stub...
    Running stub...
    Stub running...
    Changing baud rate to 921600
    Changed.
    8388608 (100 %)
    Read 8388608 bytes at 0x0 in 153.4 seconds (437.5 kbit/s)...
    Hard resetting via RTS pin...

  2. Over to HA, create a new ESP device, copy over the example code from the "Setting up" guide and compile to download.

  3. Attach USB adapter under MacOS, connect device and flash the .bin with ESPHome Flasher.

  4. Quick look into ESPHome on HA and the device has connected ok, follow the usual "new device found" dialog and all the sensors come through.

I've hooked up one of the big CT clamps (that's all we need here in the UK for a normal domestic supply) and one of the little CT clamps on channel one. Readings come through ok and look correct, so far everything seems stable and I'm going to watch it for a bit before I take a deep breath over the weekend and try to squeeze all those 16 CT clamps into my consumer unit.

@binfill3
Copy link

It looks me a while to get mine installed, mostly because I was daunted by the task of opening the load center. Once I started, everything went fine. Thank you for the work you guys have done. I directly uploaded the yaml with @krconv's branch, and it's working smoothly.

One thing I noticed which might help someone else to troubleshoot: If your power values see-saw between positive and negative, you have mapped the circuit to the wrong phase. I've attached a screenshot of my graph, showing before and after I fixed it to illustrate.
Screen Shot 2022-01-16 at 3 40 08 PM

@flaviut's instructions are very clear on how to avoid this in the first place. My excuse is that I have a cluttered load center and somewhere along the way, I started counting wrong :)

@flaviut
Copy link
Collaborator

flaviut commented Jan 16, 2022

Glad you got things working :)

I want to point out one thing: it's not unusual to get ±1W of noise in the readings, that's normal and expected. The problem your image shows is -0.25 watts (on average). Unless you have a solar system or something, that's a problem:

  • you may have put the clamp on the wire backwards
  • you may have selected the wrong phase (as you found & fixed)

@brianhealey
Copy link

Just stopped by to say I have converted all four of mine and they are running stable.
1 in the main panel monitoring my mains , my solar, and my dual phase circuits.
2 in the inside panel monitoring the line from the main panel, and 28 different single phase breakers
1 in my pool subpanel monitoring the pumps, controller, lighting, and UV lights.

These CT's can be finicky and I recommend calibrating them properly. The default settings were not accurate for me and depending how how close the CT is to other wires and the panel, I see a lot of variances that need to be filtered out. I chose a median filter, sliding window of 5, with a report rate of 1. I also added a lambda filter as it bounces below 0 quite a bit (a sign that you don't have it 100% calibrated) and I just want to filter those out.

The funny part is I am using emulated kasa to feed some of these circuits to my sense, just because I like their UI better.

@flaviut
Copy link
Collaborator

flaviut commented Jan 17, 2022

These CT's can be finicky and I recommend calibrating them properly. The default settings were not accurate for me

About how wrong was the data for you? I'd expect up to about 5% difference because we don't set a certain calibration constant dynamically at this time. I'm curious if this was the problem.

I chose a median filter, sliding window of 5, with a report rate of 1.

Do you find this works better than a sliding average like in the example? I've ended up with this in my personal config, but if there's something better, I'd be happy to change:

  - &df1
    sliding_window_moving_average:
      window_size: 12
      send_every: 6
  - &df2
    lambda: 'return max(x, 0.0f);'

and

accuracy_decimals: 0

(a sign that you don't have it 100% calibrated)

I disagree. Every system has some degree of noise to it, nothing that exists is perfect. On a circuit that consumes 0W, I'd expect to see 50% of the readings below 0W, and 50% above. Are you seeing more than ±1W of noise around 0W?

For reference, here's what I see with the filter above, and I think it's reasonable:


The funny part is I am using emulated kasa to feed some of these circuits to my sense, just because I like their UI better.

That's really cool.

That's the kind of stuff I love about hacking home automation stuff. You don't need to deal with every vendor's walled garden :)

@brianhealey
Copy link

brianhealey commented Jan 19, 2022

These CT's can be finicky and I recommend calibrating them properly. The default settings were not accurate for me

About how wrong was the data for you? I'd expect up to about 5% difference because we don't set a certain calibration constant dynamically at this time. I'm curious if this was the problem.

I am seeing about 5% variance now, but originally all of my numbers were trending low when compared to other manufacturer devices monitoring the same line.

When I converted these units they had been running for a while, and the calibration numbers I was seeing in the logs/NVS were higher than the defaults. I believe in both cases I was seeing different values from defaults in the source ( 0.023109 for phase A and a different number for phase B that I forgot to write down). I am assuming this was fine tuned by emporia.. I also have other CT's monitoring these same lines (The Sense, CT's in my powerwalls, my EV wall controller is also capturing them). I slowly adjusted the values until the vue started reporting similar numbers for voltage and wattage.

I chose a median filter, sliding window of 5, with a report rate of 1.

Do you find this works better than a sliding average like in the example? I've ended up with this in my personal config, but if there's something better, I'd be happy to change:

I think it comes out to a wash in the end. I am running with your filters right now (changed the windows to still report once per second) and I don't know if I would be able to tell the difference. /shrug

  - &df1
    sliding_window_moving_average:
      window_size: 12
      send_every: 6
  - &df2
    lambda: 'return max(x, 0.0f);'

and

accuracy_decimals: 0

(a sign that you don't have it 100% calibrated)

I disagree. Every system has some degree of noise to it, nothing that exists is perfect. On a circuit that consumes 0W, I'd expect to see 50% of the readings below 0W, and 50% above. Are you seeing more than ±1W of noise around 0W?

In some of my lines I see it jump ±20W when at 0. When I first installed my sense, their support contacted me and let me know that if the CT was pressed against the side of my panel (rigid wiring) I would see behavior like this. I am assuming my issue is similar there for these. I am going to have to open up my panels and see if I can find a more convenient spot.

The stock settings gave me low numbers originally. I think it was showing my voltage around 10V lower than it actually was, which I think was driving all of the power numbers down. Once I had that calibrated, I am seeing something similar to you in that a 0W reading bounces around ±1W.

For reference, here's what I see with the filter above, and I think it's reasonable:

The funny part is I am using emulated kasa to feed some of these circuits to my sense, just because I like their UI better.

That's really cool.

That's the kind of stuff I love about hacking home automation stuff. You don't need to deal with every vendor's walled garden :)

I am loving this as well! Once we have arduino framework support, I am looking forward to having the device perform the kasa emulation itself. :)

@binfill3
Copy link

One more data point about the inaccuracy of the readings - one of my circuits was showing abnormal fluctuations (+/- 30W with no load) after I bottled everything up. I went back and opened the panel and I saw that the 2.5mm jack had slightly dislodged from the port because of all the wires that are crammed in there. I reseated the connection, and everything went back to normal.

Obviously, this isn't a code issue, but for someone troubleshooting inaccurate readings, this is another troubleshooting step to consider.

@flaviut
Copy link
Collaborator

flaviut commented Jan 21, 2022

One more data point about the inaccuracy of the readings - one of my circuits was showing abnormal fluctuations (+/- 30W with no load) after I bottled everything up. I went back and opened the panel and I saw that the 2.5mm jack had slightly dislodged from the port because of all the wires that are crammed in there. I reseated the connection, and everything went back to normal.

That's right, I remember now! I had this exact same issue, and actually put off putting the panel cover back on (since it would move the stuff inside around) because of it.

Thanks for bringing this up, I'll add it right to the FAQ.

@odewdney
Copy link

Thanks for all the hard work here. I flashed stock Micropython to the esp32 and can read the CTs over i2c. Here in the UK, typically we have only a single phase. I was wondering if I can read the power factor of the circuits, and report kWh as well as kVA(h).

@flaviut
Copy link
Collaborator

flaviut commented Jan 30, 2022

I was wondering if I can read the power factor of the circuits, and report kWh as well as kVA(h).

As far as I'm aware, no. There may be some configuration options for the other MCU on the board, but I've never seen them so I don't know if they exist.

@binfill3
Copy link

Is anyone having any long term stability issues? I am using github://krconv/esphome@remove-freertos-task for a while now and I had been very happy until earlier this week when I saw that all my circuits were reading NaN.

Looking at the logs, I see this a lot:
[E][emporia_vue:055]: Failed to read from sensor due to I2C error 3

I've tried rebooting a few times and have already reflashed the firmware. I don't see how running esphome can cause this since we are only reading data from the bus, but I am curious to see if anyone else has also seen this error.

@brianhealey
Copy link

I see that same error some of the time and it usually doesn't go away without actually disconnecting the power for about 5 seconds and plugging it back in. Since mine is two phase, both circuit breakers that are providing power need to be off at the same time.

Also, you might have more luck with github://flaviut/esphome@emporia-vue. This fork appears to be more stable for me.

@flaviut
Copy link
Collaborator

flaviut commented Feb 15, 2022

I've generally been supporting this here, if you'd like to move the conversation over.

@nightweyr I'm really not sure what could be going on here--the error is ERROR_TIMEOUT; the secondary processor is not responding to the request for data in a reasonable amount of time. The actual issue could be anything:

  • loose electrical connection
  • bug in the software, either of the ESP32 or the data processing MCU
  • some kind of unlucky timing
  • ???

I'd try a few things, one at a time and then maybe together:

  • power it off entirely for a few seconds (what @brianhealey said, in case the other MCU is in a bad state)
  • decrease the frequency of the i2c component (in case one of the connections is not great)
  • try the github://flaviut/esphome@emporia-vue (maybe the timing is bad and we'll get lucky here?)

@binfill3
Copy link

Thanks for the suggestions! I was putting off manually powercycling the device since I'll have to open up the panel. I'll try it this evening and post my findings in the comments of the gist page. I was able to remotely modify my esphome to use @flaviut's fork, but that didn't help.

@faelenor
Copy link

faelenor commented Apr 4, 2022

Hi! There are 3 power values for each clamp but only one seems to be useful. Do you know why all 3 are provided and what they could represent? I mean, I'm really curious to understand how the Atmel IC is computing them. For instance, the power value for the opposite phase is often close to the negative value of the power, but not always.

Somewhere, either in the ESP32 firmware or the cloud, Emporia automatically figure out which of the 3 value they should use, but why aren't they doing this directly in the Atmel firmware to avoid sending useless data? It's not super useful to understand all this as you have a working solution, but for some reason, I can't bear not fully understand how something works!

@flaviut
Copy link
Collaborator

flaviut commented Apr 4, 2022

Do you know why all 3 are provided and what they could represent?

Power is current * voltage. Each of the power values is for a different voltage. Keep in mind that AC is system where current and voltage continuously change--it's not enough to simply multiply, you need to multiply over a sufficiently small time interval.

For instance, the power value for the opposite phase is often close to the negative value of the power, but not always.

You are likely on a north american split-phase system. Your phases are opposite, ignoring tolerances, resistance losses, etc.

Somewhere, either in the ESP32 firmware or the cloud, Emporia automatically figure out which of the 3 value they should use, but why aren't they doing this directly in the Atmel firmware to avoid sending useless data?

Same reason the ESPHome plugin doesn't do this, and you need to set this up in the config. You need historical data, because the direction of flow can change, for example, in solar systems. It's also hard to do for circuits that stay around zero load.

@faelenor
Copy link

faelenor commented Apr 5, 2022

Power is current * voltage. Each of the power values is for a different voltage. Keep in mind that AC is system where current and voltage continuously change--it's not enough to simply multiply, you need to multiply over a sufficiently small time interval.

Yes, I know and that's why I found the values in the example_messages.txt confusing. If the two phases are exactly in sync but with opposite voltage (as they should be), the power value for both phases should be the same, but one value positive and one negative (assuming that the sampling rate of the ADC is high enough). That's not what I was seeing in your document. For example, the 3rd and 13th row are:
P[V1]: 771.5, P[V2]: -185.5, P[V3]: -82.1
P[V1]: -56.8, P[V2]: 194.1, P[V3]: -1.0

Anyway, I tried to dump the raw power values in my setup and they are exactly as I would expect them. The power value for phase A is always very close to the negative value of phase B for all clamps and that makes sense. I'm not sure why you got weird numbers in the example_messages_txt file, maybe you have a different setup (but the voltage and frequency points to a North American setup too).

Same reason the ESPHome plugin doesn't do this, and you need to set this up in the config. You need historical data, because the direction of flow can change, for example, in solar systems. It's also hard to do for circuits that stay around zero load.

That makes sense, thanks!

@flaviut
Copy link
Collaborator

flaviut commented Apr 5, 2022

maybe you have a different setup (but the voltage and frequency points to a North American setup too).

I don't know if that's the case for me. I live in a condo, so the whole electrical thing gets a lot more complicated. I might just have 2 phases out of 3.

Actually, now that I look at it, I'm sure I have two out of three phases. My V2 is offset from V1 by 120 degrees according to that same file. It'd be 180 for split phase.

@faelenor
Copy link

faelenor commented Apr 5, 2022

Actually, now that I look at it, I'm sure I have two out of three phases. My V2 is offset from V1 by 120 degrees according to that same file. It'd be 180 for split phase.

That is very strange, I didn't know it was possible! Split-phase systems are supposed to be 2 lines that are out of phase by 180 degrees (that's what the wikipedia article you sent explains and that's what I've always seen). If you really have 2 out of 3 phases with an offset of 120 degrees, that could be a problem. It means that all your 240v equipment is working at only 75% of their rated power! Do you have a well defined load on one circuit, like a 1000w heater or something like that? You could look at its power usage reported by the Vue and see if it really is 1000w or closer to 750w.

@flaviut
Copy link
Collaborator

flaviut commented Apr 5, 2022

If you really have 2 out of 3 phases with an offset of 120 degrees, that could be a problem. It means that all your 240v equipment is working at only 75% of their rated power!

Yup. I guess you're right. I never thought about that.

Regardless, all the appliances are rated to work at both 240V & 208V. That's what I see when I check random manuals for various appliances.

@faelenor
Copy link

faelenor commented Apr 5, 2022

Yeah, they will work but may have lower performance (oven/water heater will take longer to heat for instance). I found a thread explaining that this is not common, but it can sometimes be seen in condos or in some areas. That's interesting, I didn't know about this!

@blackburn42330
Copy link

blackburn42330 commented Apr 5, 2022 via email

@BlagovestKambarev
Copy link

BlagovestKambarev commented Jun 1, 2022

Hello
I'm writing a driver for Berry's Emporia32. I read everything about the topic. I dumped the i2c bus. I reviewed cpp
and h files for ESPHome. Things are working out for me. But I have a few questions:

  1. What are the indications for "Power data" used for in the accounts?
  2. When I take the bytes of the data for "Current data" in the file "emporia_vue.cpp" it is commented that they are not multiplied by a factor, but the data I receive are: 69, 77, 82 ..... The reports are constantly floating up and down . The load I measure is 44W. In order to get the above statements true, I have to multiply by a coefficient: 0.0022. How do you consider the consumption if you do not apply a coefficient for the indication? If all goes well, I'll draw a Tasmota "C" driver.
    Greetings Blagovest.

@flaviut flaviut mentioned this issue Jun 1, 2022
@flaviut
Copy link
Collaborator

flaviut commented Jun 1, 2022

Hey @BlagovestKambarev, let's discuss this in #8.


For anyone else, locking since this particular issue is resolved, but still very interesting, so I'd rather not close it.

If you think I should unlock it or you have something else to discuss, please open a new issue!

@emporia-vue-local emporia-vue-local locked as resolved and limited conversation to collaborators Jun 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests