Support for low-power hardware (ESP32, etc.) to extend battery life in low-refresh use cases
Great work on this project! Very exciting platform!
FrameOS currently integrates with Raspberry Pi boards, but even the Zero W 2 consumes more power than necessary for certain scenarios. For example:
- Photo of the day (updating once every 24 hours)
- Daily calendar view (refreshing only once or a few times per day)
In these cases, the baseline power consumption of a Pi makes battery operation impractical. Low-power hardware such as the ESP32 is much better suited:
- Significantly lower idle power draw
- Deep sleep capabilities for long battery life
- Sufficient processing power for infrequent image/data updates
Adding support for ESP32 (or similar microcontrollers) would make FrameOS practical for wireless, battery-powered deployments where the frame only needs occasional refreshes.
Would it be possible to expand hardware support in this direction?
Hey @armond-avanes, that's a very good question!
Yes, this is technically possible to some degree, though a lot of work. One of the reasons that tilted me towards building this in the nim programming language in the first place was that I saw it was being used for ESP32 programming.
In practice this will be a lot of work to implement however. Months of work for me at my current pace, so I don't plan on taking it on. The ESP32 doesn't really give a lot of room to play with, and a lot of code (and library code) will need to be rewritten for it. It'll also support a limited subset of apps/scenes in the best of cases.
And there's a chance it might not be worth it in the end. I've heard from people working on ESP32s that space is really limited, and you'll end up offloading more and more to a server anyway, and just rendering whatever it sends you. If so, there are already companies like TRMNL doing that.
I also find that I always either leave my battery powered devices permanently plugged in, or they're dead. So I'd probably just leave all my frames permanently wired after the first recharge... so I'm not really after this feature myself unfortunately.
I'd be willing to entertain contributions here, however it's hard to measure the cost of maintaining two systems going forward as well. So I still don't know if it's a good idea or not, however we won't know until someone tries it... 🙃.
This has come up a few times before in various channels, so let's leave this issue open to track any progress or ideas on this.
@mariusandra I quite understand the time constraints and the technical limitations. But at least you have the foundation in place (nim language) just in case you or someone else decide to spend some time on it. That's promising.
Thanks a lot for taking the time to respond to my ticket.
I bought a 13.3 Spectra6 display with a dev board (wifi) - aka neoframe from AliExpress. I've been hacking on the default software to get a good display image w/ rotation and scaled to 8x10 (Ikea frame I have it in is ~8x10)...
I've been considering how to get it connected up to FrameOS, given it basically a dumb post endpoint. With some help from Jules (I'm on the go today) for reverse engineering, I think I have an approach for how to support it.
https://github.com/deftdawg/frameos/blob/neoframe-research-1/ARCHITECTURE.md#2-proposed-architecture-for-neoframe-support-virtual-edge-node-approach
TLDR:
- the edge-node needs to support "virtual" display devices that do http-posts instead pushing to physical hardware
- ~control sever need to allow non-standard ssh ports for edge-nodes~ EDIT: This is available under the settings tab
- control server needs to run an edge-node that posts to the virtual display
- bonus: make the edge-node support multiple devices, so a single edge-node can do multiple wifi frames.
This approach is pretty limited to the neoframe dev board, since that's what I have and there' other stuff I want to do like use home assistant power the display on only long enough to refresh and then power it off (using a smart-plug routine) but I think this approach could be a good start.
If you just want FrameOS to output an image over HTTP, then set up a random Linux box you can SSH into. Deploy FrameOS there with the "web only" driver. When done, click these links under the frame's settings:
I'll add "Image URL" there in an upcoming update, however for now click "Control URL" and change /c to /image in the URL. You can then use that URL to fetch the latest image rendered on the display. I don't know how this neoframe works, but if you can get it to periodically fetch a URL, we're half done.
What's missing is some way to send a webhook when FrameOS detects the image has changed...
Sadly, there's no docs for the neoframe dev-board. The demo page has 3 endpoints (/upload,/switchToRealTime,/switchToSlideShow) in it. I tried GET and POST to /download, but no love. I'll send an email to their support to ask if there's swagger, api docs or source code for the ESP binary, before I spend time any time looking at if the firmware is dumpable. Given how bare-bones this device seems, I wouldn't be terribly surprised if there was no way to download the contents currently displayed via http on the current firmware.
I gave a full-deploy a shot to see how far I could get anyway; provided a non-sudo account on my NixOS desktop, it was trying to do something but eventually failed:
$ nix path-info --json /nix/store/am5kblzv2azykf5d3a6gc11whprsvfmh-xgcc-14.2.1.20250322-libgcc ... [snipped]... 7p0vapcpk95ldzz9ky0vv8ai6giqh4p9-nixos-system-frame1-25.05.20250718.f01fe91
2025-10-04 21:33:34
Separator is not found, and chunk exceed the limit
2025-10-04 21:34:04
SSH connection closed (30s idle timeout)
Control server is the latest home-assistant addon.
EDIT: Second attempt from latest home-assistant addon to a Docker ubuntu.
Doesn't seem to init properly, build first failing looking for sudo (provided a root account), installed sudo, then make in /srv/frameos/build/build_fjfydyvygngc fails with these errors
root@8e864db663e5:/srv/frameos/build/build_fjfydyvygngc# make
Compiling on device, largest files first. This might take minutes on the first run.
In file included from /usr/lib/gcc/x86_64-linux-gnu/14/include/immintrin.h:43,
from @m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:5:
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h: In function 'toPremultipliedAlphaAvx2__pkgZpixieZsimdZavx2_u236':
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:1342:1: error: inlining failed in call to 'always_inline' '_mm256_set1_epi16': target specific option mismatch
1342 | _mm256_set1_epi16 (short __A)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:170:20: note: called from here
170 | hiMask_1 = _mm256_set1_epi16(((NI)65280));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:1342:1: error: inlining failed in call to 'always_inline' '_mm256_set1_epi16': target specific option mismatch
1342 | _mm256_set1_epi16 (short __A)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:169:20: note: called from here
169 | vec128_1 = _mm256_set1_epi16(((NI)128));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:1342:1: error: inlining failed in call to 'always_inline' '_mm256_set1_epi16': target specific option mismatch
1342 | _mm256_set1_epi16 (short __A)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:168:21: note: called from here
168 | oddMask_1 = _mm256_set1_epi16(((NI)65280));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=3.
cp: cannot stat '@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]': No such file or directory
[373/189] pixie-5.0.7-64999611cd1093e28b62ed80e695783839441538/pixie/simd/avx2.nim.c
In file included from @m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.c:7:
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h: In function 'crc32_sse41_pcmul__pkgZzippyZcrc3295simd_u39':
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.c:193:16: note: called from here
193 | x1_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)17));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.c:192:16: note: called from here
192 | x5_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)0));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.c:189:16: note: called from here
189 | x1_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)17));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=3.
cp: cannot stat '@m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.o': No such file or directory
[373/189] zippy-0.10.11-12e761e18ccc0a1b0e931c74dd02bb4b74544d56/zippy/crc32_simd.nim.c
In file included from /usr/lib/gcc/x86_64-linux-gnu/14/include/immintrin.h:43,
from @m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:5:
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h: In function 'fillUnsafeAvx__pkgZpixieZsimdZavx2_u401':
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:1335:1: error: inlining failed in call to 'always_inline' '_mm256_set1_epi32': target specific option mismatch
1335 | _mm256_set1_epi32 (int __A)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:95:22: note: called from here
95 | colorVec_1 = _mm256_set1_epi32(LOC5.dest);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:921:1: error: inlining failed in call to 'always_inline' '_mm256_store_si256': target specific option mismatch
921 | _mm256_store_si256 (__m256i *__P, __m256i __A)
| ^~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:106:33: note: called from here
106 | _mm256_store_si256(((void*) (p_1)), colorVec_1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/avxintrin.h:1335:1: error: inlining failed in call to 'always_inline' '_mm256_set1_epi32': target specific option mismatch
1335 | _mm256_set1_epi32 (int __A)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]:95:22: note: called from here
95 | colorVec_1 = _mm256_set1_epi32(LOC5.dest);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=3.
cp: cannot stat '@m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]': No such file or directory
[373/189] pixie-5.0.7-64999611cd1093e28b62ed80e695783839441538/pixie/simd/avx.nim.c
In file included from @m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.c:6:
/usr/lib/gcc/x86_64-linux-gnu/14/include/tmmintrin.h: In function 'adler32_ssse3__pkgZzippyZadler3295simd_u84':
/usr/lib/gcc/x86_64-linux-gnu/14/include/tmmintrin.h:112:1: error: inlining failed in call to 'always_inline' '_mm_maddubs_epi16': target specific option mismatch
112 | _mm_maddubs_epi16 (__m128i __X, __m128i __Y)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.c:187:50: note: called from here
187 | mad2_1 = _mm_maddubs_epi16(bytes2_1, tap2_1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/tmmintrin.h:112:1: error: inlining failed in call to 'always_inline' '_mm_maddubs_epi16': target specific option mismatch
112 | _mm_maddubs_epi16 (__m128i __X, __m128i __Y)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.c:182:50: note: called from here
182 | mad1_1 = _mm_maddubs_epi16(bytes1_1, tap1_1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/tmmintrin.h:112:1: error: inlining failed in call to 'always_inline' '_mm_maddubs_epi16': target specific option mismatch
112 | _mm_maddubs_epi16 (__m128i __X, __m128i __Y)
| ^~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.c:182:50: note: called from here
182 | mad1_1 = _mm_maddubs_epi16(bytes1_1, tap1_1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=3.
cp: cannot stat '@m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.o': No such file or directory
[373/189] zippy-0.10.11-12e761e18ccc0a1b0e931c74dd02bb4b74544d56/zippy/adler32_simd.nim.c
In file included from @m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.c:7:
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h: In function 'crc32_sse41_pcmul__pkgZcrunchyZcrc3295simd_u3':
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.c:156:16: note: called from here
156 | x1_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)17));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.c:155:16: note: called from here
155 | x5_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)0));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/14/include/wmmintrin.h:103:1: error: inlining failed in call to 'always_inline' '_mm_clmulepi64_si128': target specific option mismatch
103 | _mm_clmulepi64_si128 (__m128i __X, __m128i __Y, const int __I)
| ^~~~~~~~~~~~~~~~~~~~
@m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.c:152:16: note: called from here
152 | x1_1 = _mm_clmulepi64_si128(x1_1, x0_1, ((NI)17));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=3.
cp: cannot stat '@m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.o': No such file or directory
[373/189] crunchy-0.1.9-3afda170fbe40158cd78e00375e34690f1fae263/crunchy/crc32_simd.nim.c
Linking frameos
/usr/bin/ld: cannot find @m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]: No such file or directory
/usr/bin/ld: cannot find @m..@s..@s..@s..@[email protected]@[email protected]@szippy@scrc32_simd.nim.o: No such file or directory
/usr/bin/ld: cannot find @m..@s..@s..@s..@[email protected]@[email protected]@spixie@[email protected]: No such file or directory
/usr/bin/ld: cannot find @m..@s..@s..@s..@[email protected]@[email protected]@szippy@sadler32_simd.nim.o: No such file or directory
/usr/bin/ld: cannot find @m..@s..@s..@s..@[email protected]@[email protected]@scrunchy@scrc32_simd.nim.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:15: frameos] Error 1
There is a compile_frameos.sh script that does produce a 5MB frameos binary, that requires a frame.json file not present in the build directory...
I'm not sure if the idle ssh connections dropping after 30s is having an effect on deployment; I guess should setup an RPi and try with RPiOS.
We now have a HTTP upload driver: https://github.com/FrameOS/frameos/pull/161