logo elektroda
logo elektroda
X
logo elektroda

How I reduced my IoT config page size 11 times by using Javascript instead of static HTML

p.kaczmarek2 2766 30
ADVERTISEMENT
  • JavaScript code snippet generating dropdown elements.
    Here I will show you how I reduced my IoT device pins configuration HTML page from about 90kB down to only 8kB. I will use my OpenBeken project as an example, but the generic idea shown here can easily apply to any other environment creating HTML pages from C (or similiar) code. This topic will assume that user knows the basic of C, HTML and Javascript...

    So, in this topic I am referring to OBK configuration page that allows you to set GPIO roles in dropdown lists:
    IoT device pin configuration in OpenBK7231
    Screenshot of the GPIO configuration page in the OpenBeken project
    Before, size of this page was above 90 000 bytes:
    Screenshot of a text editor displaying a fragment of HTML code, indicating a file length of 92,698 bytes and 2 lines of code.
    It was causing slow loading on multiple platforms.
    After the change, size is now:
    Screenshot displaying a code snippet in a text editor.

    Let's start by checking why the page size is so big in case of this config. We can just view the source of the page in the browser:
    HTML code snippet for IoT device pin configuration
    Now you can clearly see, that for each of the dropdowns, the list of options is repeated. This is very simple, but also not optimal. This repeats for every GPIO, so if there is 30 available IO pins on a sample platform, then it's repeated 30 times!
    A brief look at OBK code confirms that's what is happening:
    Code: C / C++
    Log in, to see the code

    The following is done for every possible pin! We need to optimize that...

    In order to optimize it, we will use Javascript. In JS, we will create a single, global pin roles array and then for each dropdown we will insert the items into it with a script. There are, however, two issues to solve:
    - we need to also set the active selected element, so people don't lose their settings with each refresh and save
    - we need to exclude PWM roles for non-PWM pins.
    Let's ignore the PWM issue for now. Let's start with the following draft:
    Code: Javascript
    Log in, to see the code

    Array r is a list of item role names. In my function f, I iterate the pin role names and add them to the dropdown d. There I also check for the selected role, which index must be given when calling f function.
    The selected role index can be easily taken from OBK code, but for the reference to the select DOM object I had to modify the code futher. I settled with retrieving it by ID:

    Code: Javascript
    Log in, to see the code

    Now that we have basics done, let's also consider how we can skip PWM roles for non-PWM pins. OBK already knows which pins support PWM, so we can just pass extra argument and then check whether pin role starts with PWM, altough in the future I may just hardcode the index of role (it will be few characters shorter). Note that the 'b' variable is the extra argument I added to the function:
    Code: Javascript
    Log in, to see the code

    So finally I have modified my C code to just print the following Javascript when generating OBK page, of course also replacing the dummy data with the real OBK pin roles:
    Screenshot of C code generating a Javascript script for GPIO configuration in the OpenBeken project.
    This works and page size is now reduced 9 times, but is it all that we can do?

    A brief glance at HTML code shows that there is still room for the improvement:
    Screenshot of the HTML code for an IoT device configuration page created with C code.
    The channel index div generation can be also procedural. Here is a beautified version of the new code that also generates div:
    Code: Javascript
    Log in, to see the code

    The div creation could be actually separated into another function, as it's done two times, but I don't think it would give me a substantial numbers of characters.
    So, now the page size is down to 8kB:
    Screenshot of a text editor showing a snippet of JavaScript code.

    So, to sum up - generating HTML code from C is very easy, but also not very optimal. In case of the repeating data, like the dropdowns with the same options, it's more efficient to generate a Javascript that will later generate HTML elements. For me, it gave me over one order of magnitude improvement in terms of space used. It also fixed OBK issues, hopefully...
    The same principle can be applied also to any other IoT projects, the same problems could occur on ESP and could be also solved in ESP by generating HTML+JS instead of pure HTML, so I hope that everybody learned something.
    My sample could be of course improved futher, also with Javascript-compression scripts, but I think the optimization I have managed to get so far is enough for our project.

    By the way, if any OBK user is reading it, please check it and let me know. Can you check how your pins page works (especially on Firefox) and then update to test build:
    https://github.com/openshwprojects/OpenBK7231T_App/pull/1215
    does the page loads faster now? Or better? @divadiow @max4elektroda @DeDaMrAz @miegapele ? Can you test it on multiple OBK platforms?
    NOTE: Obk also provides pins setting in the web app, so the following page is not crucial, but it's still good to have it running well.

    Cool? Ranking DIY
    Helpful post? Buy me a coffee.
    About Author
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    p.kaczmarek2 wrote 11822 posts with rating 9927, helped 564 times. Been with us since 2014 year.
  • ADVERTISEMENT
  • #2 21078389
    DeDaMrAz
    Level 19  
    I had one particular N module that will completely freeze if I went to config pins, this resolved that issue for me!

    BEFORE: Device pin configuration page with dropdown list and channel values.

    AFTER: Screenshot of a module configuration panel with various pin settings.
  • #3 21078490
    max4elektroda
    Level 20  
    Works here on my LN882H, page loads fast (but to be honest, I can't remember how it was before)

    Having a quick look, you might even save some more bytes.
    I see some whitespaces and (my thought when I saw that page, I was having a likewise idea, that this one needs some optimization):
    What about all these "_n" values? Maybe two arrays, one with "sole" values, one with values, which will be used twice (val and val+"_n")...
  • ADVERTISEMENT
  • #4 21078504
    p.kaczmarek2
    Moderator Smart Home
    Actually I have LN882H running at the moment with an older build. And for that it seems that old version runs very good as well. There is no noticeable slowdown in the older build. I've tested both Chrome and Firefox. Btw, here is my "LN882H dev board" I used for testing:
    LN882H development board on a wooden table with an open case lid and a cable. Dobo brand STB decoder displaying the time 12:34.
    Yes, LN882H is inside.

    Interestingly enough, I also had reports saying that OBK on BK7231 fails on pins, but only on Firefox...

    Anyway, this size reduction should fix it.

    Regarding the potential improvement - of course there is much more things that could be done, but none of them seems as efficient as what I've shown already. Here is a couple of ideas:
    - whitespaces (obviously)
    - split double text field creation into one short function used twice
    - instead of 'null' string, use -1 (under assumption we won't need channel -1 as some special syntax for something)
    - maybe we could shorten some of variable names or class names
    - it seems that some kind of @iprak 's auto refresh code still gets attached to this page, even while it's not our index ...
    The "_n" suffix maybe also could be improved, but I am not sure if non-n version always come before "_n" and I am not sure how much would it save.
    Helpful post? Buy me a coffee.
  • #5 21078516
    max4elektroda
    Level 20  
    A really good idea and a huge improvement!
    Out of curiosity: I expect not too much improvement on the image size? I mean, it was much HTML code, but most was created in loops, IIRC.
  • ADVERTISEMENT
  • #6 21078604
    p.kaczmarek2
    Moderator Smart Home
    I didn't check the flash size before change (the main releases system seems to have some issue at the moment) but I would say that change in flash size is very, very small. Keep in mind that the mentioned Javascript snipped is now in the firmware flash as well (as C string) and that is taking some space as well.
    Helpful post? Buy me a coffee.
  • #7 21078652
    max4elektroda
    Level 20  
    May I suggest a small "improvement" (it's a question of "style" vs "size"):

    If you change

    function f(alias, id, c, b, ch1, ch2) {
        let f = document.getElementById("x");
        let d = document.createElement("div");
        d.className = "hdiv";
        d.innerText = alias;
        f.appendChild(d);
        let s = document.createElement("select");
        s.className = "hele";
    [...]
    


    the part with the "alias", which has different sizes for the labels, to

    function f(alias, id, c, b, ch1, ch2) {
        let f = document.getElementById("x");
        let d = document.createElement("div");
        d.className = "hdiv";
        d.innerHTML = "<span style='display:inline-block; width: 4ch'>"+ alias+"</span>";
        f.appendChild(d);
        let s = document.createElement("select");
        s.className = "hele";
    [...]
    


    you will have a much "prettier" page:
    Comparison of two OpenLN882H_C25E1088 configuration interfaces

    (or you can add a css format in any other way, it's much leaner than a table here, which would need more JS code, too) .

    Added after 8 [minutes]:

    Sorry, I didn't realize, this code already made it to the release ...
  • #8 21078693
    p.kaczmarek2
    Moderator Smart Home
    That's a good change, we can add it as well, no problem. The only question would be, can't we just use one of existing styles? Or is the proposed solution the shortest one?
    Helpful post? Buy me a coffee.
  • #9 21078735
    max4elektroda
    Level 20  
    To be honest I was too lazy to try to dive into the styles, it might be possible to reuse another, but since we would need a class, it would probably not be much shorter...

    Added after 8 [minutes]:

    Just recognized: If I see the screenshot from DeDaMrAz, my proposal would not be sufficient, for it's a static width.
    A proper style would be better. So just leave it for the moment, it will need some more thinking...
  • #10 21078783
    p.kaczmarek2
    Moderator Smart Home
    The second channel field is currently showed only when pin role supports it. For example, button can have two linked channel - one for single click, second for dual click. Futher clicks (like triple click) are also possible, but only via scripting.
    Helpful post? Buy me a coffee.
  • #11 21078786
    divadiow
    Level 34  
    Would it be possible to have the second channel box reveal as soon as supported device is selected rather than having to save to reveal second box then save again after entering required channel number?
  • ADVERTISEMENT
  • #12 21078886
    p.kaczmarek2
    Moderator Smart Home
    Of course, but it would require keeping a second array with a number of channels that given pin role requires, and then catching the onChange event of the dropdown, so the visibility of the channel fields can be changed there. I don't think I will be able to do that tomorrow, but if anyone wants to try, feel free to do so.

    My plan for the upcoming days is 1. publishFile command (as a way for custom hass discovery) with presentation 2. finalize, test and relase new ws2812b animations 3. a WS2812b-like device from user 4. finish my ln882h dev board and post it 5.more selfs tests for obk source code
    Helpful post? Buy me a coffee.
  • #13 21079472
    divadiow
    Level 34  
    p.kaczmarek2 wrote:
    Can you test it on multiple OBK platforms?
    NOTE: Obk also provides pins setting in the web app, so the following page is not crucial, but it's still good to have it running well.


    can't say I really noticed a slowness but they're all fast and the page loads fine.

    chrome.

    User interface showing a dropdown list for assigning roles to pins in the OpenBK system. OpenBK configuration screen for assigning pin roles and channels. OpenW600 configuration interface with dropdown lists for assigning roles to GPIO pins. OpenBK_BL602 settings interface with GPIO pin configuration. OpenBK configuration interface for BK7231T device with a list of available pins. GPIO configuration interface for OpenW800_CDD5556C device.

    Added after 7 [minutes]:

    p.kaczmarek2 wrote:
    NOTE: Obk also provides pins setting in the web app

    would be cool if the GPIO doctor labels were relevant to the chip in use instead of being N/T (?) labels for all.

    Screenshot showing GPIO pin settings interface with various configuration options.
  • #14 21079624
    max4elektroda
    Level 20  
    divadiow wrote:
    Would it be possible to have the second channel box reveal as soon as supported device is selected rather than having to save to reveal second box then save again after entering required channel number?

    I tried to implement this in PR #1228

    Just as a first idea I got during this work:
    At the moment we have the properties of an IO role in several places:
    - we have the "main" enum ioRole_e in new_pins.h with all the information
    - we have the according "nicer" names in htmlPinRoleNames[] in new_http.c
    - we have some information about the "function class" (like temperature or air sensor) in some #defines in new_pins.h
    - we have information about the number of channels per role in some "if statements" in http_fn_cfg_pins of http_fns.c (at least, we had - I made a function in new_pins for that)

    So my idea is to put this all together in one place - maybe an array of a struct containing all information in one place.
    That will be quite some work to implement and change all functions using this.
    But it would mean there is only one place to maintain.

    As I said, just an idea, and I might give it a try.
    But since this will be quite some work to do, I would only start, if there are no objections or someone with more knowledge of the code points out that I just missed one fact which will make this impossible.

    Added after 31 [minutes]:

    This is how it looks


  • #16 21080723
    p.kaczmarek2
    Moderator Smart Home
    max4elektroda wrote:
    I tried to implement this in PR #1228


    Very good, I will merge it in a moment. Some of things could be improved there (code could be shorter), but it's not crucial. The change from 90kB to 8kB was more important.


    max4elektroda wrote:
    So my idea is to put this all together in one place - maybe an array of a struct containing all information in one place.
    That will be quite some work to implement and change all functions using this.
    But it would mean there is only one place to maintain.

    As I said, just an idea, and I might give it a try.
    But since this will be quite some work to do, I would only start, if there are no objections or someone with more knowledge of the code points out that I just missed one fact which will make this impossible.

    This is a good idea and we may do it together in the future but I think we may need more automatic tests first to make sure that everything works as expected. See sample test here:
    https://github.com/openshwprojects/OpenBK7231...lob/main/src/selftest/selftest_buttonEvents.c
    Are you able to run self tests on Windows?
    Helpful post? Buy me a coffee.
  • #17 21080761
    max4elektroda
    Level 20  
    p.kaczmarek2 wrote:
    Some of things could be improved there (code could be shorter), but it's not crucial. The change from 90kB to 8kB was more important.

    Yes, I had my focus on the function, not the size here, but will take another look to shorten the code if the function is proofed ;-).

    I'm usually using Linux, but if it's not too complicated, I would surely be able to do some tests of the functions in Windows to find possible issues in advance to avoid problems are found by users...
    Especially when working on something so crucial to the basic functions like the management of pins/roles/channels this would help...
  • #18 21080765
    p.kaczmarek2
    Moderator Smart Home
    If you are using Linux, then maybe you'd be able to help us with porting:
    a) just self tests to Linux (should be very easy)
    b) whole simulator to linux (should be relatively easy, as it's SDL, but I think the menubar is using windows API)
    Helpful post? Buy me a coffee.
  • #19 21080786
    max4elektroda
    Level 20  
    I'm willing to help, but be warned, I'm just a "hobby coder", not a serious "developer" ;-)
  • #20 21080907
    p.kaczmarek2
    Moderator Smart Home
    Well, the first thing would be to compile the simulator (or at least the self tests part, without SDL window) on Linux. How would we go on about that? The win_main.c could be actually compiled on Linux I think.
    Helpful post? Buy me a coffee.
  • #21 21081216
    max4elektroda
    Level 20  
    I'll take a first look tomorrow, but don't know how much time I can afford ...
  • #22 21081965
    max4elektroda
    Level 20  
    O.k., I did have a first look and I'm afraid, that's too much for me.

    The showstopper for me were the numerous #defines that need to be investigated.
    Don't get me wrong, no offense, but I am often unable to decide, why they are there.
    If my command was correct, there are over 180 lines with a reference to a (un)defined "WINDOWS".
    At least I would identify this possible "reasons"
    - needed to compile code for windows (e.g. to know I need #include <windows.h>)
    - used to distinguish between hardware and simulation (e.g. I won't need to worry about connecting to WiFi on windows)
    - used to simulate things (e.g. "manipulate" the time)
    - used to distinguish between platforms (but "misused" e.g. if a feature is only built on some Platforms - I remember this with LittleFS and scripting)

    So you can't just define WINDOWS and compile for linux, but need to decide on every occurrence, if you need to add something like a "#if(n)def LINUX" there.

    Just to repeat myself: No offense, it's just beyond my possibilities and knowledge.
    And maybe I'm mistaken, and there is an easy way I wasn't able to see ;-).
  • #23 21081968
    p.kaczmarek2
    Moderator Smart Home
    I think I can introduce a new #define, namely OBK_SIMULATOR, for things that I think are multiplatform. I will split the code into SIMULATOR and WINDOWS myself. Will that help?
    Helpful post? Buy me a coffee.
  • #24 21082221
    max4elektroda
    Level 20  
    That will surely help. We'll see, if it's enough for me...

    Added after 1 [hours] 55 [minutes]:

    @p.kaczmarek2 Could you please check PR #1238?
    I did't take into account, that index can differ from value in the JS options, if there is a pin not suitable for PWM.
    The original code would reffer to the wrong index in the array providing the channels for the roles.

    I hope you can merge it, I made the change in the "old" branch and did't make a pull in advance ...
  • #25 21082457
    p.kaczmarek2
    Moderator Smart Home
    There seem to be a merge conflict, can you resolve it?

    Added after 1 [minutes]:

    I think i got it
    Helpful post? Buy me a coffee.
  • #26 21082520
    max4elektroda
    Level 20  
    Thanks and sorry for the hassle...
  • #27 21085175
    divadiow
    Level 34  
    @ElektrodaBot what are the downsides, if any, to adding optimisation code like the following to a makefile?

    CFLAGS += -ffunction-sections -fdata-sections
    LDFLAGS += -Wl,--gc-sections
    CFLAGS += -flto
    LDFLAGS += -flto
  • #28 21085177
    ElektrodaBot
    AI OpenAI ChatGPT4
    divadiow wrote:
    what are the downsides, if any, to adding optimisation code like the following to a makefile?

    CFLAGS += -ffunction-sections -fdata-sections
    LDFLAGS += -Wl,--gc-sections
    CFLAGS += -flto
    LDFLAGS += -flto
    Great question! Adding optimization flags to a Makefile can indeed improve the performance and memory footprint of your program. However, there are some potential downsides. Let's go through each flag step by step.

    1. `-ffunction-sections` and `-fdata-sections`
    These flags instruct the compiler to place each function or data item into its own section in the output file. This can lead to better optimization during the linking stage, as unused sections can be discarded.

    Pros:
    - Can reduce the size of the final binary by eliminating unused functions and data items.

    Cons:
    - Can increase the size of the object files, as each function/data item now has its own section.
    - Might slightly increase compile time due to additional bookkeeping required for the sections.
    - Could potentially make debugging more challenging, as the debugger has to handle many small sections.

    2. `-Wl,--gc-sections`
    This linker flag tells the linker to perform garbage collection of unused sections.

    Pros:
    - Works in tandem with `-ffunction-sections` and `-fdata-sections` to reduce the size of the final binary by removing unused sections.

    Cons:
    - If the linker incorrectly discards a section that is actually needed, it can lead to runtime errors or missing functionality. This is rare but can happen in complex builds.

    3. `-flto` (Link Time Optimization)
    This flag enables link-time optimization, where the compiler can perform optimizations across translation units (source files).

    Pros:
    - Can result in significant performance improvements and reduced binary size, as the compiler has a global view of the entire program.

    Cons:
    - Can significantly increase compile and link times, as the optimization work is deferred to the linking stage.
    - May introduce additional complexities in debugging, as the optimized code may not correspond directly to the original source code structure.
    - Could introduce subtle bugs if the LTO implementation has issues, although this is relatively uncommon with mature compilers like GCC and Clang.

    Summary
    In general, these optimization flags can provide substantial benefits in terms of performance and binary size. However, they come with trade-offs in terms of increased compile/link times and potential challenges in debugging. It’s important to thoroughly test your application after enabling these optimizations to ensure that no new issues are introduced.

    Follow-up Questions:
    1. What kind of project are you working on (e.g., embedded systems, desktop application, etc.)?
    2. How critical is the binary size and performance for your application?
    3. Are you experiencing any specific problems after enabling these optimizations?

    Understanding more about your specific context can help tailor the advice to better suit your needs.
    You can call me in any topic by writing @ElektrodaBot. Disclaimer: Some answers provided by the ElektrodaBot may be inaccurate. Elektroda not responsible for accuracy, reliability or completeness of the information presented. All ElektrodaBot responses should be verified by the user. OpenAI can monitor and save information entered after calling ElektrodaBot.
  • #29 21085184
    divadiow
    Level 34  
    @ElektrodaBot cool thanks. those changes shaved ~19k off a final binary so was curious about the trade-offs

    Screenshot showing a list of FLS files with dates and file sizes.
  • #30 21085185
    ElektrodaBot
    AI OpenAI ChatGPT4
    divadiow wrote:
    cool thanks. those changes shaved ~19k off a final binary so was curious about the trade-offs

    https://obrazki.elektroda.pl/1946300900_1715891397.png
    Great to hear that you achieved a significant reduction in the binary size! Given the differences in the file sizes in your screenshot, it looks like your optimizations are already paying off.

    Additional Considerations

    Since you've seen tangible benefits, here are a few more considerations and recommendations to balance the trade-offs:

    1. Thorough Testing: Ensure that you run extensive tests on your application to catch any potential issues introduced by these optimizations. Pay special attention to the functionality and scenarios where the application might behave differently due to the optimized code.

    2. Debugging:
    - Use Debug Symbols: While debugging, compile with `-g` in addition to your optimization flags. This will include debug symbols in your binary, making it easier to debug.
    - Separate Debug Info: You can also separate debug information from the binary using `objcopy --only-keep-debug`. This keeps the binary size small while preserving debug information in a separate file.

    3. Profiling: Use profiling tools (e.g., `gprof`, `perf`) to understand how the optimizations affect the performance. This can help identify any performance bottlenecks that might have been introduced or improved.

    4. Analyze LTO Impact: Keep an eye on the compile and link times. If they become too burdensome, you might consider using `-flto=jobserver` or `-flto=4` (replace 4 with the number of parallel jobs) to limit the number of parallel jobs used during linking.

    5. Check Compatibility: Ensure that your build environment and toolchain fully support these optimization features. Sometimes, older versions of compilers and linkers might not handle LTO or section optimization well.

    Additional Optimization Flags

    If you are willing to explore more, you can look into additional optimization flags:

    - `-Os`: Optimize for size. This can be used instead of `-O2` or `-O3` if you are focusing on reducing binary size.
    - `-march=native`: Generate code optimized for the host machine. This can give performance boosts but might reduce portability.

    Follow-up Questions

    1. Have you run into any specific issues or unexpected behavior after applying these optimizations?
    2. What kind of testing framework or methodology are you using to validate the changes?
    3. Are there specific parts of the code where size or performance is more critical?

    Your approach and insights can help further refine the optimization strategy to better fit your project's needs.
    You can call me in any topic by writing @ElektrodaBot. Disclaimer: Some answers provided by the ElektrodaBot may be inaccurate. Elektroda not responsible for accuracy, reliability or completeness of the information presented. All ElektrodaBot responses should be verified by the user. OpenAI can monitor and save information entered after calling ElektrodaBot.

Topic summary

The discussion centers on optimizing the configuration page of an IoT device, specifically reducing its size from approximately 90kB to 8kB by utilizing JavaScript instead of static HTML. The OpenBeken project serves as a primary example, demonstrating how dynamic content generation can enhance loading speeds across various platforms. Participants share their experiences with different modules, such as the LN882H and issues encountered with the BK7231, particularly in Firefox. Suggestions for further optimization include reducing whitespace, reusing code for creating elements, and improving the visibility of channel fields based on user selections. The conversation also touches on the implications of optimization flags in Makefiles and the potential for future enhancements in the code structure.
Summary generated by the language model.
ADVERTISEMENT