macless-haystack icon indicating copy to clipboard operation
macless-haystack copied to clipboard

Add nrf51xx battery status

Open dkgs2000 opened this issue 1 year ago • 9 comments

Support for reporting battery information on nrf51xx:

Relevant issue #100

NOTE: this is only the hardware part. Since i have some design questions i wanted to do this in parts.

Technical details:

I use the "status" byte (index 6) in the advertisement data to report the status. Currently only the first 2 highest bits. I update the battery information every key exchange wake cycle. All the code for the battery service is copied from here

Questions:

  1. When using a single key - there is no wake cycles... so the battery information will never be updated.
  • We can add another wake timer to handle the battery information. But the we have to make uint8_t *raw_data; accessible (global?)
  • We can apply the wake timer also for single key. Maybe change some parameters and make the setAndAdvertiseNextKey function more efficient.
  1. What should we do if the board doesn't support it? Since I dont have a physical nrf52xx board to test so i made sure my changed are for the nrf51xx only. What should i report when the board doesnt support battery information?

  2. Should i really use only 2 bit? The original project uses the rest of the 6 bits for a counter (irrelevant to us). We can use more data to get the exact % of the battery. Do we want to reserve the bits to something else?

  • Maybe for a "capabilities" bitmap? to report if the board support or doesn't support battery information?

I am very willing to dev this feature so plz tell me how should i progress from here.

dkgs2000 avatar Aug 24 '24 11:08 dkgs2000

Hello @dkgs2000 ,

thanks for your work. Without changes to the remote station (front end), your change unfortunately offers no added value. This should be added before I can merge it. Have you actually checked whether the information can be retrieved from Apple at all? Simply transmitting via Bluetooth is useless if the information cannot be transmitted to and retrieved from the Apple servers. If so, where can this information be found in the report?

Kind Regards, Danny

dchristl avatar Sep 06 '24 11:09 dchristl

Hey Danny! Thanks for replying :)

We already fetch the relevant data. I have check it on my local setup and also found a relevant code here

The status byte is the last (10th) byte of the decrypted & decoded payload array. I have also added a commit as an example to how it can be read and added to the flutter frontend. Sadly, I have 0 experience with flutter / dart :(

As I said, this PR still requires work on the 3 questions I have added in the first comment. Once they are answered and I fixed the firmware code to act accordingly - I think the GUI part is easy. either I'll learn flutter, or someone else could complete the job with my guidance.

Note that I added my own recommended solutions to each question.

Let me know how would you like this to progress :)

dkgs2000 avatar Sep 07 '24 15:09 dkgs2000

I first wanted to know whether the entire route generally works before I got stuck into the questions and answered them ;)

When using a single key - there is no wake cycles... so the battery information will never be updated. We can add another wake timer to handle the battery information. But the we have to make uint8_t *raw_data; accessible (global?) We can apply the wake timer also for single key. Maybe change some parameters and make the setAndAdvertiseNextKey function more efficient.

Here we have a somewhat bigger problem. Rotating the keys (i.e. the additional wake cycle) consumes an insane amount of power, which is why I don't use it myself (any more). An nrf51 with one key lasts about a year, with several not even a month. Unfortunately, even with the help of the Nordic forum, I haven't managed to find a good and energy-saving solution. But I have to admit that I'm not a firmware programming pro. If you add a timer now, then a device with only one key will probably only last 1 month. Maybe you know your way around better and can make it more energy-efficient, otherwise I don't see much success for the feature, as CR2477 batteries are usually not very cheap.

What should we do if the board doesn't support it? Since I dont have a physical nrf52xx board to test so i made sure my changed are for the nrf51xx only. What should i report when the board doesnt support battery information?

Almost all compatible boards (except the NRF5X) do not support this ;) Any distinct information (i.e. 000000 ) should be fine. They can be filtered out in the UI.

Should i really use only 2 bit? The original project uses the rest of the 6 bits for a counter (irrelevant to us). We can use more data to get the exact % of the battery. Do we want to reserve the bits to something else? Maybe for a "capabilities" bitmap? to report if the board support or doesn't support battery information?

I don't quite understand that? If you don't have any more information, then just leave it out. If you have the exact battery level, then add it (possibly in steps of ten or 5 to accommodate them in 6 bits). As I said, this feature is only supported by a fraction of the boards. Can you give me a few examples of what is now being sent and received? i.e. Battery low , 15 % -> 110011 (11 = battery low, 10 = 3 -> 3 x 5 ) Battery medium , 30 % -> 010110 (01 = battery medium, 100 = 6 -> 6 x 5 ) Battery high, 100% -> 1010100 (10 = battery high, 10100 = 20 -> 20 x 5 )

These are just examples and I don't know how what is possible in detail.

dchristl avatar Sep 09 '24 09:09 dchristl

FYI: Have you seen #120? This seems to me to be a good and significantly improved firmware alternative. When I have time, I'll try it out and take a look at the energy consumption

dchristl avatar Sep 14 '24 06:09 dchristl

Cool! look promising. It looks like it doesn't support battery status update out-of-the-box all though there are many references in the code. Like this one. But the update_battery_level() function isn't actually fully implemented.

The SDK upgrade to version 12 is actually very important because in version 12.2 there is an SDK implementation of the es_battery_voltage_get(). In this PR i implement it on my own.

About the power consumption problem - as you said, whats taking power is waking the board. When switching keys we wake up every 30 minutes. If We'll create a new timer for the battery level update we can wake the board only once a day, week or even month. So I don't think it will have a huge effect on the power consumption.

To answer your question about the status byte - We use the 2 highest bits of the status byte to represents 4 level of battery status (level high, medium, low and critical). We don't need more than 4 value. I will also implement using another bit to represent boards the actually support this feature so we can use it in the GUI.

Note that thanks to @PuNS3c we now have full GUI support with icons to show the latest battery level of the tag.

What is missing in this PR currently (I'll be working on that):

  • Add another timer that wakes the board every X (I suggest 2 weeks) to update the battery level.
  • Add a bit to say whether the board supports battery status or not.

If you decide to use the firmware from #120 - I'll make sure to implement the battery update in the new firmware. The flutter code for battery icons will be the same :)

dkgs2000 avatar Sep 14 '24 15:09 dkgs2000

Done! I flashed the firmware on my tag and I'm getting good reports. I think its safe to merge it do dev branch now. I'll let you know if any problems occur or i experience something weird.

dkgs2000 avatar Sep 14 '24 17:09 dkgs2000

If you decide to use the firmware from #120 - I'll make sure to implement the battery update in the new firmware. The flutter code for battery icons will be the same :)

That sounds great! I'd be happy to chat if you're interested in implementing it. I'd like to make it optional, though, so that byte can be used for something else as well (hence the current implemented stubs)

es_battery_voltage_get() is nice indeed.

pix avatar Sep 17 '24 20:09 pix

Maybe it's off-topic, but can somebody add Ota to the fw? It will be really useful to use it to flash the new version of the fw without uart. Thanks in advance.

T-REX-XP avatar Sep 30 '24 13:09 T-REX-XP

That sounds great! I'd be happy to chat if you're interested in implementing it. I'd like to make it optional, though, so that byte can be used for something else as well (hence the current implemented stubs)

es_battery_voltage_get() is nice indeed.

I pushed preliminary support using es_battery_voltage_get (reused key timer, updating ~ once a day +/- the key rotation interval)

Compile and seems to work on both nrf51 & nrf52.

pix avatar Oct 04 '24 13:10 pix

Today i got the first Non-full battery status! Screenshot_20250107-084011.png

So i can fully promise this pull request works and does not break any existing logic.

Would be nice to merge it if possible.

Thanks!

dkgs2000 avatar Jan 07 '25 06:01 dkgs2000

Thank you for your work. I've merged and changed the code a little bit.

  • condition changed when to show the icon, because some of my devices shows unknown battery status (this is currently all users with the firmware of this project)
  • Improved layout (smaller icon and text removed)

dchristl avatar Feb 06 '25 07:02 dchristl

Is batteryStatus the same logic with firmware pix/heystack-nrf5x? https://github.com/pix/heystack-nrf5x?tab=readme-ov-file#makefile-variables-summary

This firmware support makefile variable HAS_BATTERY

lovelyelfpop avatar Feb 13 '25 10:02 lovelyelfpop

Hey @dkgs2000 I've seen this PR and looks great! I have few nrf52832 laying around. I would like to add battery reporting to those, I can try to do it myself with some guidance and of course open a PR. Hope you can help me out. From where should I start? May I ask where did you found nrf51_battery implementation? I see a Nordic header

umbynos avatar Feb 15 '25 20:02 umbynos

Hello @umbynos ,

this PR is already merged and release. You can just use the latest firmware to use this feature.

dchristl avatar Feb 16 '25 09:02 dchristl

Hey! I tried yesterday (version 2.4.0) but unfortunately this firmware seems to implement battery monitoring for nrf51x only

umbynos avatar Feb 16 '25 09:02 umbynos

OK, I see. Then we have to wait for @dkgs2000

dchristl avatar Feb 16 '25 09:02 dchristl

Hey @dkgs2000 I've seen this PR and looks great! I have few nrf52832 laying around. I would like to add battery reporting to those, I can try to do it myself with some guidance and of course open a PR. Hope you can help me out. From where should I start? May I ask where did you found nrf51_battery implementation? I see a Nordic header

Hey :) sorry for taking so long to reply. Here is the story:

The SDK contains the function for getting the the voltage. Then, we use some very unaccreted math to get the battery percentage (good enough tho).

Sadly, this function only exists in SDK version 12. The nrf firmware in this project uses the SDK version 11: https://github.com/dchristl/macless-haystack/blob/4265346955cedabbd8f8176511ed787e4c0fab12/firmware/nrf5x/Makefile#L46

I tried to upgrade this project to SDK version 12 but it had compilation error and i was too lazy to look into it.

If you want to implement generic battery support for all Nordic board this is definitely the way. Upgrade the SDK and the functions will work.

Luckily, before SDK version 12 was released. Someone ( I don't remember where) implemented the relevant parts specifically for nrf51 and I copied it.


What might work as a quick win - is to copy the relevant code from the SDK version 12 like this file to our project and hope that the implementation is compatible with the old SDK version. no promises :)

Whatever you decide I'll be happy to help :) lemme know what you decide to do!


P.S. during the development someone shared heystack-nrf5x - which uses the newest SDK. There is a claim that it has better power consumption also (idk if it's confirmed). If you choose to upgrade the SDK, maybe we should just use this project's firmware instead.

dkgs2000 avatar Feb 16 '25 13:02 dkgs2000

I got it, remove this line in lib\findMy\decrypt_reports.dart. Now the app support battery status for firmware pix/heystack-nrf5x.

// if (status & 0x20 != 0) // Check if battery status updates are supported

The battery level shown on this app is the same as that shown on the nrfConnect app 图片

lovelyelfpop avatar Feb 17 '25 00:02 lovelyelfpop

I got it, remove this line in lib\findMy\decrypt_reports.dart. Now the app support battery status for firmware pix/heystack-nrf5x.

// if (status & 0x20 != 0) // Check if battery status updates are supported

The battery level shown on this app is the same as that shown on the nrfConnect app 图片

LOL! That's very funny :) The thing is, most of the firmware's cant report their battery status (old versions, different hardware) Since we read 2 bits of the status byes to get the battery status, we need a way to tell the app if the bits has a meaning at all.

What would happend without this line is that EVERY board would start to "report" full battery regardless of its state. This is what the "unknown" state is for. Because the board doesnt report, we dont know :).

This line (and some firmware modifications i did) help to tell the app if the board "knows" how to update the bits. Thats why we hide the battery icon on the app if it doesn't.

If you want to add support for battery reporting all you need to do is to "flip" the right bit in the heystack firmware.

Don't remove the line, its misleading.

This project is more complicated then the firmware one because the app has to support everything.

dkgs2000 avatar Feb 17 '25 08:02 dkgs2000

this is how we tell the app that the board reports battery status.

this is where we need to add it in the heystack firmware. It would look like this:

status_flag |= STATUS_FLAG_BATTERY_UPDATES_SUPPORT;

after copying the STATUS_FLAG_BATTERY_UPDATES_SUPPORT header.

dkgs2000 avatar Feb 17 '25 08:02 dkgs2000

I got it, remove this line in lib\findMy\decrypt_reports.dart. Now the app support battery status for firmware pix/heystack-nrf5x.

// if (status & 0x20 != 0) // Check if battery status updates are supported

I have removed the line in the dev branch. You can try this with the other firmware if you use the web version or the Android app from here (you must be logged in)

I would suggest supporting only one firmware version afterwards, namely the heystack-nrf5x version. More devices and settings/flags are supported here.

I can then deprecate the firmware in my project and link to the other one. I am currently looking at the code and instructions and hope to get data from the battery status.

Compiling and flashing went without problems, but unfortunately I'm not getting any reports at the moment.

dchristl avatar Feb 17 '25 12:02 dchristl

Short update: I have added the removed line in the code again. The problem is that the pix-firmware only uses 2 bits (4 states) for the status and therefore there is no differentiation whether the feature is supported at all or not. A full battery (both bits 0) is the same as if the firmware does not support the battery status at all (like most firmwares). With the check removed, a full battery is then displayed for every device without battery status-supported firmware. The implementation of @dkgs2000 is better and has an additional bit in it, which indicates whether the feature is enabled or not. The rest is identical for both firmwares (same status), which then also leads to the correct display. I have now adjusted this in the UI so that the icon for the battery is displayed if the status bit is set or if the status is not 0. This now theoretically works for both firmwares, except that full batteries are not displayed in the pix firmware. I now have a tag with pix-firmware that also transfers locations and see if that works when the battery drops below 80%

The web version is already up to date, for the Android version the build artefact can be used again.

But I still have one question that I couldn't answer myself: Why aren't the percentage values simply transferred here instead of transformed values? You have a total of 8 bits. For the numbers from 0 -100 you need 7 bits and 1 bit to indicate whether the feature is activated or not. This could be used to create detailed displays. Is something being swallowed by Apple or did they just want to save bits?

dchristl avatar Feb 17 '25 15:02 dchristl

@dchristl thanks for the update! As i said:

this is how we tell the app that the board reports battery status.

this is where we need to add it in the heystack firmware. It would look like this:

status_flag |= STATUS_FLAG_BATTERY_UPDATES_SUPPORT;

after copying the STATUS_FLAG_BATTERY_UPDATES_SUPPORT header.

This is the changes for the pix-firmware to add the special bit for the battery reports. The app doesn't need changes. Our new firmware should follow our rules of marking battery reports capability bit.

As for your question: We can't actually read the battery "percentage". We read the voltage. In battery, the voltage is not constant and many parameters effect it - temperature, age and wear, stress\load and also how charged it is.

Even without the other parameters, the graph for capacity\voltage is not linear and varies from battery to battery. Modern phones uses very complex algorithms to estimate battery capacity and calibrate themselves regularly.

We don't know any of those parameters and cant calibrate because the batteries mostly not chargeable.

All of the above causes our estimation of battery % to be very very very inaccurate. So we just give 4 state.... The original Haystack protocol state the other bits as "reserved", probably for the same reasons...

As long as we keep the special bit I did to tell if the board support battery reports or not, and show the correct information in the app - I'm happy :)

dkgs2000 avatar Feb 17 '25 15:02 dkgs2000

Thanks for the clarification, @dkgs2000 . That explains a lot. For example, I have a tag that always shows medium with a new battery, although it is completely new and shows full in another tag (from another manufacturer). Could it also be that with your firmware, the battery information is sometimes not sent/received? I have the behaviour that sometimes the battery info is there and sometimes not. This means that sometimes the battery is displayed and sometimes not. Do you have an explanation for this?

dchristl avatar Feb 17 '25 16:02 dchristl

Thanks for the clarification, @dkgs2000 . That explains a lot. For example, I have a tag that always shows medium with a new battery, although it is completely new and shows full in another tag (from another manufacturer). Could it also be that with your firmware, the battery information is sometimes not sent/received? I have the behaviour that sometimes the battery info is there and sometimes not. This means that sometimes the battery is displayed and sometimes not. Do you have an explanation for this?

The battery thing make since as I said. If you see this in many boards, maybe we can tweak some constants to make the estimation better.

About the battery reports - this seems weird. I'm getting the battery icon all the time. Are you using a single key or rotating keys? In the app - how many days back do you read the reports?

I'm not sure the problem has anything to do with these question but I need a place to start.

If you really want to get into it - download the nRF Connect app and check if the tags send the battery info in the BLE packet. If they do - the problem is not in the firmware.

dkgs2000 avatar Feb 17 '25 22:02 dkgs2000

About the battery reports - this seems weird. I'm getting the battery icon all the time.

I should have mentioned that I modified the code locally (accessory_registry.dart) so that the battery status is always updated. I removed the condition here, because it doesn't really make sense.:

    `//If battery status exists, update last battery status

    if (lastReport.batteryStatus != null) {

      accessory.lastBatteryStatus = lastReport.batteryStatus;

    }`

This causes problems when you install a different firmware, as the icon will always remain in the last status. That's why I wanted to remove it. I noticed this issue, but you probably won't experience it because if the information doesn't come through, nothing gets updated.

Are you using a single key or rotating keys? In the app - how many days back do you read the reports?

I use rotating keys and fetch the maximum (7 days). Unfortunately, I don't get any results with the nrf app, even though 3 tags are nearby and data is being transmitted. I have tried with different phones, is there a special setting I need to use?

dchristl avatar Feb 18 '25 10:02 dchristl

Another small update: I have changed the pix-firmware to support the STATUS_FLAG_BATTERY_UPDATES_SUPPORT and it works so far. I will test a little bit (I have 2 identical tags, one with modified pix-firmware and one with macless-firmware) and check which one will last longer. If the pix firmware works like expected and lasts longer than I will add a link to the project in the README. Of course, I will add a pull request to support the battery flag.

dchristl avatar Feb 18 '25 16:02 dchristl

Can we please continue the discussion here? The closed pull request is difficult to find and not really transparent for other users.

dchristl avatar Feb 18 '25 16:02 dchristl