theengs / decoder

Efficient, portable and lightweight library for Internet of Things payload decoding.
https://decoder.theengs.io
GNU General Public License v3.0
116 stars 38 forks source link

add support for e-volve/fitdays smart scale #503

Closed bbbart closed 2 months ago

bbbart commented 6 months ago

The past few days I have worked on reverse engineering a smart scale I happen to own. The brand is e-volve and the companion app is Fitdays.

I figured out that

I would like to process and store this information in Home Assistant, and I believe Theengs OpenMQTTGateway is a good use for an ESP32 achieving just that.

However, if I am not mistaken, I first need to get decoding support for this particular scale supported here before I program my board with Theengs. Correct?

I'd be more than happy to share my findings here and perhaps even prepare a pull request to get it sorted.

I didn't come across any pointers yet on how to get started however. Perhaps I can get some here? :-)

Thanks to @koenvervloesem for pointing me in this direction!

DigiH commented 6 months ago

Hi @bbbart

  • weight and impedance information is simply broadcasted in the advertisement-packets

The perfect situation for Decoder.

However, if I am not mistaken, I first need to get decoding support for this particular scale supported here before I program my board with Theengs. Correct?

Correct, Theengs Decoder is the BLE decoding library used for OpenMQTTGatewaym Theengs Gateway, the Theengs App for mobile phones and several other projects …

I didn't come across any pointers yet on how to get started however. Perhaps I can get some here? :-)

We have an Adding Decoders section for anyone wanting to add new decoders for devices. It can possibly be a bit overwhelming when going through it all for the first time, but we are happy to help with any queries or working on a new decoder together.

You might want to have a look at the existing Xiaomi Body Composition Scale decoder as a starting point, as you scale has much the same functionality.

https://github.com/theengs/decoder/blob/development/src/devices/XMTZC05HMLB_json.h

bbbart commented 6 months ago

Alright, let me look into it. Thanks for the pointers

I currently have it working with a completely custom written, Nimble and PubSubClient based approach, but I'd rather be standing on some giant's shoulders here than completely relying on myself. :-D

bbbart commented 6 months ago

Just a quick question: part of the decoding requires and XOR operation of selected bytes with a given, static value. All other operations and conditions required seem fine, but I couldn't seem to find how to get that XOR to happen. Is this a supported operation for a post_proc?

bbbart commented 6 months ago

(I know that A XOR B = (A NAND (A NAND B)) NAND (B NAND (A NAND B)) and that NOT and AND are available, but is that the best way forward? :-))

DigiH commented 6 months ago

Do you have your custom solution online as a repo?

Or could you post some undecoded raw data you a receiving with OpenMQTTGateway from the scale, when you have Advertisement and advanced data set to true? Also indicating which part of the advertising data you already know is the weight in kg, with any additionally required calculations?

Do you build OpenMQTTGateway for an ESP32 yourself with PlatformIO?

DigiH commented 6 months ago

(I know that A XOR B = (A NAND (A NAND B)) NAND (B NAND (A NAND B)) and that NOT and AND are available, but is that the best way forward? :-))

Could you possibly explain a bit further how this specifically applies to decoding the scale data?

I would assume that what you need to do might be to have alternative calculations for the same property, but with different conditions. Have a look at the APPLEDEVICE decoder, with its sole unlocked property having two conditional alternatives

https://github.com/theengs/decoder/blob/development/src/devices/APPLEDEVICE_json.h

So creating two conditions, one with true AND false, the other with false AND true, you should be able to define your XOR situation.

bbbart commented 6 months ago

This is what I figured out how to get weight data from the advertised manufacturer data:

if byte at position 8 is 0x20, then concatenate the bytes at positions 9, 10 and 11 xor that value with 0x2CA0A0 divide the resulting value by 1000

Here's a snippet from my current code:

if (cManufacturerData[8] == 0x20) {
  uint32_t rawWeight = 0;
  rawWeight |= ((uint32_t)cManufacturerData[9] << 16);
  rawWeight |= ((uint32_t)cManufacturerData[10] << 8);
  rawWeight |= ((uint32_t)cManufacturerData[11]);

  uint32_t decodedWeight = rawWeight ^ 0x2CA0A0;

  float weight = round(decodedWeight / 1000.0f * 10) / 10.0f;
}

I feel I know how to get the condition, the concatenation and the division implemented, but not the xorring.

DigiH commented 6 months ago

Could you give some example manufacturerdata along with its decoded weight?

bbbart commented 6 months ago

sure:

ACA0CE9C695F91A1202DB0520DB4 corresponds to 69.9 kg

-> byte at position 8 is 0x20, so you calculate (0x2DB052 ^ 0x2CA0A0) / 1000 = 69.874, which gives 69.9 when rounded.

DigiH commented 6 months ago

I suppose we'd have to add an XOR post_proc for this ;) quite an unusual weight calculation.

May I ask where you got this from, as for only by reverse engineering it is not something immediately obvious?

byte at position 8 is 0x20

Does this mean that with octet 8 being something other than 0x20 there is also a different XOR calculation?

bbbart commented 6 months ago

I suppose we'd have to add an XOR post_proc for this ;) quite an unusual weight calculation.

I reckon this is what they refer to as encryption on the packaging...

May I ask where you got this from, as for only by reverse engineering it is not something immediately obvious?

I put two and two together after stumbling upon https://stackoverflow.com/questions/63761622/get-weight-data-from-bluetooth-le-scale-by-disassemble-manucaftures-library and figuring out what the data was for 0.0kg.

byte at position 8 is 0x20

Does this mean that with octet 8 being something other than 0x20 there is also a different XOR calculation?

No. If there's no 0x20 there, it either means that:

DigiH commented 6 months ago

I reckon this is what they refer to as encryption on the packaging...

As long as this 'encryption' is generally applicable to all the e.volve scales and not some changing value set when first pairing the scale to the app, which I assume you have previously done. Would be also good to know if the scale always broadcasts BLE advertising data, or only if or if not provisioned with the app, as some devices do.

  • the scale is still calibrating (it sends out many weight values, but when position 8 is 0x20, it is a final measurement)

Good to know, as we also have a similar indicator with the Xiaomi scale, and only decode and publish the weight with the 'settled' indicator.

I suppose there is a similar indicator to show if the weighing has been done barefoot so that any impedance value broadcast is validated as well.

Let me have a look into adding an XOR post_proc the next days, unless you also want to have a go at adding this yourself 😉

https://github.com/theengs/decoder/blob/development/src/decoder.cpp#L670

DigiH commented 6 months ago

In started a bit if you want to have a peak ;)

https://github.com/DigiH/decoder/tree/evolve

bbbart commented 6 months ago

I reckon this is what they refer to as encryption on the packaging...

As long as this 'encryption' is generally applicable to all the e.volve scales and not some changing value set when first pairing the scale to the app, which I assume you have previously done. Would be also good to know if the scale always broadcasts BLE advertising data, or only if or if not provisioned with the app, as some devices do.

Ah, these are good questions. I indeed paired the app with the scale after unpacking it.

However, I suspect the scale being 'dumb' enough to not being able to have anything set or written to it. The same app supports 4G, WIFI and Bluetooth scales. I'm doubtful it is doing more than just looking for and registering data (I'm not sure, obviously).

I do know at least that even with the app not running, I can get the values. Also, with the app running, my ESP32 and a BLE sniffer on, all three devices get the advertising data simultaneously. There is absolutely no talk of connecting or pin codes or other security measures.

  • the scale is still calibrating (it sends out many weight values, but when position 8 is 0x20, it is a final measurement)

Good to know, as we also have a similar indicator with the Xiaomi scale, and only decode and publish the weight with the 'settled' indicator.

Ah. Then this 0x20 is the 'settled' indicator. FYI, the same decoding works for packages having a 0xA0 at position 8, but those aren't settled then.

I suppose there is a similar indicator to show if the weighing has been done barefoot so that any impedance value broadcast is validated as well.

yup. The impedance value is set with indicator 0x85, but I haven't figured out yet what the corresponding data bytes mean or how to interpret them. I do know that the packet is always there, even when the app says that no impedance data was received (when not weighing barefoot, for example).

DigiH commented 6 months ago

Ah, these are good questions. I indeed paired the app with the scale after unpacking it.

However, I suspect the scale being 'dumb' enough to not being able to have anything set or written to it. The same app supports 4G, WIFI and Bluetooth scales. I'm doubtful it is doing more than just looking for and registering data (I'm not sure, obviously).

I do know at least that even with the app not running, I can get the values. Also, with the app running, my ESP32 and a BLE sniffer on, all three devices get the advertising data simultaneously. There is absolutely no talk of connecting or pin codes or other security measures.

It's not so much the app running or not etc. but just having provisioned/paired the scale with the app once. Some devices, e.g. the Qingping Air Monitor Lite, only broadcast BLE advertising data after they have been provisioned/paired with their app. Then the app can not run at all or even be deleted, but once they get deleted from the app again they don't broadcast any more BLE advertising data - hence we have made a note of this in the relating doc

https://decoder.theengs.io/devices/CGDN1.html

It would be good if you could just test this with the scale being deleted from the app as well, just for verification. Hopefully though, when provisioning/pairing the scale with the app again it won't set a different encryption/XOR key to the current one ;)

Also it is very likely that the scale can have some small bits set/written to it, probably at least the unit, if the display on the scale shows kg or lbs for example. So an encryption/XOR key might also be an option - but let's hope not.

Ah. Then this 0x20 is the 'settled' indicator. FYI, the same decoding works for packages having a 0xA0 at position 8, but those aren't settled then.

I don't see much point in publishing the unsettled weight and only to publish the weight once it has settled down as indicated by the 0x20, that's why I put this verification into the actual model condition of the quick test decoder. We do the same for the Xiaomi scales.

The impedance value is set with indicator 0x85, but I haven't figured out yet what the corresponding data bytes mean or how to interpret them. I do know that the packet is always there, even when the app says that no impedance data was received (when not weighing barefoot, for example).

Again much the same as with the Xiamo scale, where impedance data is always being broadcast, but when wearing shoes or socks there is a 'not valid impedance' indicator which changes to a different 'valid' indicator when a proper impedance reading has been achieved standing barefoot on the scale. Putting this as a condition of the impedance property makes sure that only valid impedance readings are being published.

FYI - I merged the XOR post_proc

bbbart commented 6 months ago

Okay. I played a little more with the scale. Here are some findings:

    • there is a hardware button at the bottom with which I can change the units between kg, lb and st:lb
    • the companion app follows along with changing the units, even when I change it while standing on the scale
    • -> this must mean that the unit must be set in the advertisement data
    • however: the measurement itself is always correctly decoded by my ESP32 using the method described above, in kg
    • -> this must mean that the data is always sent in kg and the unit is just a flag so the companion app can do the conversion itself
    • after removing the scale from the app, everything still works
    • when using the scale with the companion app running (without the scale added to it) results in the scale automatically getting detected and added to it
    • -> this could be because it simply finds it by itself (which might explain why I don't actually remember registering or pairing the scale originally), or because it's not really being deleted, but merely 'hidden' or something

So, I still don't know if the 'encryption' is set at init, nor if the scale will work if never paired in the first place, but I still feel somehow that the device is very dumb and simply sends out advertisement packages whenever it's on (still just a feeling though).

FYI - I merged the XOR post_proc

Yay!

I have yet to install/use Theengs for the first time. Do I start from source, or is there a release out already with this change?

Thanks so much for your responsiveness and efforts!

DigiH commented 6 months ago
  1. We should find out which flag of the broadcast data indicates the different units, to be able to include a unit property in the decoder.
  2. Not uncommon that the actual value is always sent in the metric system, even if the preferred display unit might be imperial, e.g. with lots of thermometers the value is always being broadcast in Celsius, even though the unit flag might be set to Fahrenheit.
  3. Then this hopefully means that no provisioning/pairing is required and that the 'key' is always the same for this scale model.

I have yet to install/use Theengs for the first time. Do I start from source, or is there a release out already with this change?

The best would be to get the source for OpenMQTTGateway, you should want to take the latest development branch version there, open it in Visual Studio Code and make sure to have the PlatformIO extension installed for VSC, so you can then build and upload the esp32dev-ble environment for your ESP32. As the OpenMQTTGateway development branch currently also has the development version of Theengs Decoder linked to, the latest XOR post_proc commit will be included. You can then change this line to any test decoder branch of a fork of Decoder, with which you want to develop the decoder further for continued testing.

Or install the web upload of the development nightly build of OpenMQTTGateway from https://docs.openmqttgateway.com/dev/upload/web-install.html

Or if you want to use Theengs Gateway, you can do a pip install of the latest release version, which will automatically also install the letest release of Theengs Decoder as one of its dependencies. You can then get the latest Decoder source to build and install the latest dev with the XOR commit. which in turn is then also possible with any further decoder test branch you will create.

Let us know how you get on.

bbbart commented 6 months ago
  1. We should find out which flag of the broadcast data indicates the different units, to be able to include a unit property in the decoder.

I believe I just (kind of) figured this one out. I am getting 100% consistent results using the following rule regarding the byte at position 13. When expressed in hex, the first symbol (so, the first four bits) denote the unit. The rest (so, the last four bits) are a checksum.

Let's disregard the checksum.

When the first symbol of byte 13 is A or B, the unit is kg, when it is E or F, the unit it st:lb, and when it is 8 or 9, the unit is lb.

The same, but better structured:

First four bits of byte 13 unit
A kg
B kg
E st:lb
F st:lb
8 lb
9 lb

Do you have support for bits in the Theengs decoder already, or only for bytes? Or we could solve it with simple comparisons too, since we're talking about the most significant bits.

bbbart commented 6 months ago

Or install the web upload of the development nightly build of OpenMQTTGateway from https://docs.openmqttgateway.com/dev/upload/web-install.html

This is what I did. After, I updated to the latest nightly build. This is reported by Home Assistant for the OMG_ESP32_BLE SYS: Firmware Update control:

OpenMQTTGateway Nightly (TEST ONLY)
Installed version         ef07a3
Latest version            ef07a3

When using the scale, I can see in the esp32 console that it is being detected. The log contains the name and the MAC address and the claim it published something:

Send on /BTtoMQTT/A1915F699CCE msg {"id":"A1:91:5F:69:9C:CE","mac_type":0,"adv_type":3,"name":"AAA002","manufacturerdata":"aca0ce9c695f91a1ad07a0a00bbf","rssi":-60}

(I enabled publishing of manufacturerdata).

But... now what? I don't seem to be getting any of this information in Home Assistant. Sorry if I'm missing something obvious here.

DigiH commented 6 months ago

Do you have support for bits in the Theengs decoder already, or only for bytes?

From https://decoder.theengs.io/participate/adding-decoders.html#properties

If a direct binary bit evaluation encoded in a hex digit is desired the third parameter is "bit", the fourth parameter the bit position from 3-0 and the fifth parameter the bit state 0 or 1.

So with that we can implement the selected unit. I'm wondering if the changing bit[0] state with every unit alternative might indicate the different weighing with or without impedance or such, as again it is similar to the Xiaomi scales.

This is what I did. After, I updated to the latest nightly build.

Sorry, I wasn't too clear then ;) the development nightly build is linking the decoder library to the development branch of Decoder, which has the XOR implementation included after I merged it, but knows nothing of the e.volve scale test decoder I have started at my own fork, linked further above.

(I enabled publishing of manufacturerdata)

Perfect, that's the next thing I would have asked you to do :) so we can investigate the undecoded published data further. One thing I see with your above MQTT message is that the manufacturerdata includes the scale's Bluetooth MAC address at the beginning in reverse order, after what is usually the company ID of then device manufacturer, which I cannot find as being registered though, not unusual, but hopefully the same with every e.volve scale.

This is how I usually go about reverse engineering and separating unknown advertising data. With your sample above though, does the ad instead of a 20 mean the weight was not settled yet or could it be the impedance indicator followed by impedance data then, but both weighings were with the kg unit?

Screenshot 2024-01-29 at 10 04 08

But... now what? I don't seem to be getting any of this information in Home Assistant. Sorry if I'm missing something obvious here.

As stated above, the development test build you have installed only had the XOR post_proc commit cinluded, but knows nothing of the test decoder - maybe wasn't such a good idea of me to suggest that build for testing. I have started another test build form you to try, this time with an OpenMQTTGateway branch which links the decoder library to my started test decoder branch, as you can see as the only difference to the development branch of OMG

https://github.com/1technophile/OpenMQTTGateway/compare/development...decoder-test

This is also how I suggested above on how you would create and install your own OpenMQTTGateway builds, linkin g to your own or my test decoder. It would only required a few simple steps to install Visual Studio Code and the PlatformIO extension, as described here

https://platformio.org/install/ide?install=vscode

Then opening the downloaded OpenMQTTGateway source code in VSC and on the left side in the PlatformIO section under PROJECT TASKS you can see the esp32dev-ble environment, when expanded shows you the build and upload options.

bbbart commented 4 months ago

Hello again! Sorry for the long radio silence. Life got in the way, as it tends to do. :-)

Today, I took some time for some more structured measurements, but I'm not sure what we can learn from them.

Here's the raw data (grabbed with nRF Connect on my phone, with duplicate lines removed and in reverse chronological order).

shoes 24.4 kg

ACA0 CE9C695F91A1 A9 2CA0A0 0B A0 -> 0.0
ACA0 CE9C695F91A1 20 2CFFD8 0D B0 *
ACA0 CE9C695F91A1 A0 2CFF2C 0D A4
ACA0 CE9C695F91A1 A0 2CFF36 0D AE
ACA0 CE9C695F91A1 A0 2CC4FA 0D B7
ACA0 CE9C695F91A1 A0 2CC492 0D AF
ACA0 CE9C695F91A1 A0 2CEB50 0D B4
ACA0 CE9C695F91A1 A0 2CE23A 0D B5

---

bare feet 40.0 kg - 4.0%

ACA0 CE9C695F91A1 A5 4AA2B1 0B AD -> 6685.201
ACA0 CE9C695F91A1 20 2C9B84 0D B8 -> 15.14
ACA0 CE9C695F91A1 A0 2C9B84 0D B8
ACA0 CE9C695F91A1 A0 2CA0A0 0D B9
ACA0 CE9C695F91A1 AF 3BA2B0 0B A7
ACA0 CE9C695F91A1 20 2C3CAE 0D A3 *
ACA0 CE9C695F91A1 A0 2C3CAE 0D A3
ACA0 CE9C695F91A1 A0 2C3B68 0D BC
ACA0 CE9C695F91A1 A0 2C0588 0D A6
ACA0 CE9C695F91A1 A0 2C05B4 0D B2
ACA0 CE9C695F91A1 A0 2C0442 0D BF
ACA0 CE9C695F91A1 A0 2C2920 0D A2
ACA0 CE9C695F91A1 A0 2C22FA 0D B5

---

shoes 79.4 lb

ACA0 CE9C695F91A1 AE B0A0A0 0B 89 -> 10223.616
ACA0 CE9C695F91A1 20 2C2C00 0D 85 *
ACA0 CE9C695F91A1 A0 2C2C00 0D 85
ACA0 CE9C695F91A1 A0 2C2C36 0D 9B
ACA0 CE9C695F91A1 A0 2C2A48 0D 8B
ACA0 CE9C695F91A1 A0 2C2BB0 0D 94
ACA0 CE9C695F91A1 A0 2C2B42 0D 86

---

bare feet 87.6 lb - 4.0%

ACA0 CE9C695F91A1 AF 29A2B0 0B 95 -> 328.208
ACA0 CE9C695F91A1 20 2C3BFA 0D 8E *
ACA0 CE9C695F91A1 A0 2C3BFA 0D 8E
ACA0 CE9C695F91A1 A0 2C3BF0 0D 84
ACA0 CE9C695F91A1 A0 2C3A1A 0D 8D
ACA0 CE9C695F91A1 A0 2C3AEC 0D 9F
ACA0 CE9C695F91A1 A0 2C3AC0 0D 93
ACA0 CE9C695F91A1 A0 2C3A32 0D 85
ACA0 CE9C695F91A1 A0 2C3ED0 0D 87
ACA0 CE9C695F91A1 A0 2C3D12 0D 88
ACA0 CE9C695F91A1 A0 2C3CEA 0D 9F
ACA0 CE9C695F91A1 A0 2C3CF4 0D 89
ACA0 CE9C695F91A1 A0 2C0290 0D 8B
ACA0 CE9C695F91A1 A0 2C3A6E 0D 81
ACA0 CE9C695F91A1 A0 2C2ECC 0D 93

---

shoes 5:10 st:lb

ACA0 CE9C695F91A1 20 2C2D0E 0D F4 *
ACA0 CE9C695F91A1 A0 2C2D0E 0D F4
ACA0 CE9C695F91A1 A0 2C2B64 0D E8
ACA0 CE9C695F91A1 A0 2C2B42 0D E6
ACA0 CE9C695F91A1 A0 2CD3FA 0D E6

---

bare feet 14:13 st:lb - 28.2%

ACA0 CE9C695F91A1 85 A6A2B0 0B E8 -> 9044.496
ACA0 CE9C695F91A1 20 2DD29C 0D E8 *
ACA0 CE9C695F91A1 A0 2DD29C 0D E8
ACA0 CE9C695F91A1 A0 2DD20A 0D F6
ACA0 CE9C695F91A1 A0 2DD2D8 0D E4
ACA0 CE9C695F91A1 A0 2DD178 0D E3
ACA0 CE9C695F91A1 A0 2DD11A 0D E5
ACA0 CE9C695F91A1 A0 2DD106 0D F1
ACA0 CE9C695F91A1 A0 2DFA26 0D FA
ACA0 CE9C695F91A1 A0 2DF6D6 0D E6
ACA0 CE9C695F91A1 A0 2DD8EA 0D FC
ACA0 CE9C695F91A1 A0 2DD1F6 0D E1
ACA0 CE9C695F91A1 A0 2DE2E4 0D E0

What else would be useful at this time? Is it perhaps already a good thing to simply pull this into the codebase with only support for the weight data and ignoring impedance for now?

DigiH commented 4 months ago

Hi @bbbart

Yeah, life does that sometimes, doesn't it ;)

Today, I took some time for some more structured measurements, but I'm not sure what we can learn from them.

One thing we did learn is that the actual broadcast weight is always in kg, no matter what the unit on the scale is set to, but with the scale display unit also being broadcast.

So with the current decoder I am getting

shoes 24.4 kg ACA0 CE9C695F91A1 20 2CFFD8 0D B0 * \"weight\":24.44,\"unit\":\"kg\"

bare feet 40.0 kg - 4.0% ACA0 CE9C695F91A1 20 2C3CAE 0D A3 * \"weight\":39.95,\"unit\":\"kg\"

  • Although in the above I am confused about the second supposed final weight measurement, indicated by the 20 byte ACA0 CE9C695F91A1 20 2C9B84 0D B8 -> 15.14 ??

shoes 79.4 lb ACA0 CE9C695F91A1 20 2C2C00 0D 85 * \"weight\":36,\"unit\":\"lb\" 36 kg = 79.36641 lb

bare feet 87.6 lb - 4.0% ACA0 CE9C695F91A1 20 2C3BFA 0D 8E * \"weight\":39.77,\"unit\":\"lb\" 39.77 kg = 87.67784 lb

shoes 5:10 st:lb ACA0 CE9C695F91A1 20 2C2D0E 0D F4 * \"weight\":36.27,\"unit\":\"st:lb\" 36.27 kg = 79.96166 lb = 5.711547 st = 5 st 9.961658 lb

bare feet 14:13 st:lb - 28.2% ACA0 CE9C695F91A1 20 2DD29C 0D E8 * \"weight\":94.78,\"unit\":\"st:lb\" 94.78 kg = 208.9541 lb = 14.92529 st = 14 st 12.95406 lb

So apart from the confusing additional 20 final weight measurement in your second data set the weight and unit are recognised fine, although the keys might need to be renamed to weight_kg and display_unit for clarification.

Thinking about the impedance some more I am pretty certain that it would also need to be sent with the same broadcast, and not a different separate broadcast, as it would need to be available at the same time as the weight to be able to calculate all the other smart scale properties like body fat, bone mass etc. from these two values. The fact that the weight is encoded in the 3 octets, but with an XOR supports this theory, that there is more information contained in these three octets. I tried XORing with the binary inverse, but that also didn't produce any results which looked sensible to me. Maybe you have additional ideas of how the remaing data in these octets might be encoded.

You're right, we could initially release this decoder with the weight_kg and display_unit only, but it would really require for you to test the actual decoder with OpenMQTTGateway a bit more, especially with the rogue final weight measurement 15.14 kg vs. the actual 40.0 kg.

Did you get any further with setting up Visual Studio Code with PlatformIO?

There currently seem to be problems with the development test builds which I will need to look at more closely, so creating pre-built test binaries for easy ESP32 installation might be off the table until this is sorted out.

Actually, the development test builds do work fine, it's just a performance test which fails frequently with recent builds which makes them appear failed. So let us know when you have the time to install a pre-built test binary with the e.volve decoder included on your ESP32.

github-actions[bot] commented 2 months ago

This issue is stale because it has been open for 60 days with no activity.

github-actions[bot] commented 2 months ago

This issue was closed because it has been inactive for 14 days since being marked as stale.