Also making the first VLAN in the table load when we load the UI
Removing the tickbox for the PVID, I'll swap this out to ticks
or something later on, but the PVID will be automatically set
based on the untagged membership, otherwise it will be 1
Send the vlan with name command when attempting to add a new VLAN
Because of the structure of the vlan command, we need to send the name as well if its set
Set the PVID based on the untagged membership and reload the
table when applied. We need to block setting the PVID to the tagged by proxy
Blue button
Need to provide array of pvid commands not concat
Storing state of each vlan so we can refer later
Blue button
The simulator answered /status.json without the "macs" field the Ports page now reads. It counts the dynamic entries of its own L2 table for the port, so the column and /l2.json agree and the two static entries show the difference.
Needed to increase buffer size for this to send app.js
The logical port status is what is returned in the array, for
non-sequential systems we need to map through physToLog rather
than just subtracting 1
physToLog is 1 based! not 0; also fixing up typecasts
Reimplementing using an array of objects to represent ports.
The previous approach of a bitmask of logical ports, was just too
simple on devices such as the SWGT024_V2_0_MANAGED which has
gaps in the port assignments.
boot_to_html outputs 1 and 0 not true and false, making the sim match
the real implementation.
lldp.json has a hanging , in the array; so overwriting it
Moving the global on/off onto the LLDP page
Building a table of LLDP status from the S ports. Later on, if
we wish to implement an lldp receiver we can display the remote
details here
Adding support to the cmd parser for the lldp commands
The image rule no longer passes -p, so fileadder fell through to printing
html_data.h and html_data.c on stdout on every image build. The listing is
only useful when fileadder is run without an image, to see what it would
generate.
Two things kept the same source from producing the same image.
fileadder walked the input directory with readdir(), which returns entries
in whatever order the filesystem hands them over. The same tree therefore
embedded the files in a different order on a different filesystem, and the
image differed byte for byte. scandir() with alphasort() fixes the order.
version.h was listed in .PHONY, which makes it permanently out of date and
propagates that to everything including it, so a plain build recompiled the
four largest translation units even when nothing had changed. Dropping it
from .PHONY alone would go too far the other way: with no prerequisites the
recipe would never run again and the version string would freeze at its
first value. A FORCE prerequisite keeps the recipe running every time, and
the generated header is only moved into place when its content actually
differs, so the timestamp survives an unchanged rebuild.
Measured on this tree: with FORCE the version follows GIT_VERSION and an
unchanged rebuild leaves version.h untouched; without it the header keeps
the value from the first build.
Building with SDCC 4.6.0 emits tons of warnings e.g.:
cmd_parser.c:31: warning 356: object in read-only code space should be const
cmd_parser.c:36: warning 357: pointer to object in read-only code space should be pointer to const
These of course become fatal when building with --Werror.
Comparing resultant binaries, it seems that a single string is shifted
(deduplication?) but the numbers emitted at the end of the build are
otherwise identical.
The pages become one index.html holding every section, one app.js and a
self-contained login.html. The layout is a sidebar with light, dark and
Selenized themes (or follow the OS), a live dashboard with per-port
packet rates, and confirmation dialogs in front of the actions that can
lock the operator out.
Behaviour changes against the previous pages:
- every request goes through one queue that waits for the previous
response BODY rather than its headers: the firmware shares a single
output buffer across connections and delimits responses by closing,
so a second request in flight corrupts the first
- "Save to flash" merges the command log into the startup configuration
with the grammar the console accepts, shows the result before writing,
verifies the sector by reading it back and clears the command log only
after that; the header shows an "unsaved changes" badge while the
running configuration differs from flash
- the console section shows the output the firmware returns for a
command, including the parser's explanation on a rejected one
- the firmware update section checks size, bank header and CRC16 of the
image in the browser before uploading, shows upload progress and waits
for the switch to come back after the reset
- the ports table shows the learned MAC of a port when there is exactly
one and the count otherwise; the MAC table sorts per column, filters,
and names the LAG a learned address sits behind
- the spanning tree section sends only the settings that changed on
Apply instead of one command per control event
- link aggregation groups are numbered 1-4 like the command line, and
applying an empty group clears it
The translation table is inline in app.js with English, Japanese and
Chinese. The language selector sits in the page header and reloads the
page so that dynamically built tables are rebuilt in the new language.
Only login.html and the favicon are served before authentication; the
stylesheet, the port images and the old script bundle no longer exist.
The simulator's allowlist follows the firmware's.
replaceCalls() ended with strlen(&buffer[pos]), so the length recorded for a
file was the distance to its first 0x00 byte rather than the file size. The
byte just past the content happens to be zero today, which is the only reason
the numbers come out right.
Nothing in html/ contains a 0x00 today, so this is latent, but it is a trap
waiting for the first gzipped or binary asset: gzip puts a zero in the fourth
byte of its header. A gzipped style.css lands in the index as 3 bytes, and
since addr advances by that same value the next file is placed 3 bytes in and
overwrites what came before it. Two gzipped files in html/ are enough to see
it: both come out as 3 bytes and the second sits 1356 bytes inside the first.
Pass the length in and return the adjusted one. The substitution shortens
#{function} to #{NNN}, so the delta is known without measuring. The loops are
bounded by the length as well, which also covers -n, where nothing had put a
terminator in the buffer for strlen to find in the first place.
No output change: the image built from an unmodified html/ is byte for byte
the same before and after, and html_data.h is identical.
index.html now contains all pages as sections (overview, ports, stat,
vlan, l2, mirror, lag, eee, bandwidth, system, update); the sidebar
switches between them via the URL hash (#/ports, #/vlan, ...).
login.html stays separate as the authentication gate.
A section's initialisation runs when the section is shown, its polling
intervals run only while it is visible, and re-entering a section
refetches the data; the checkbox builders (lag/vlan/mirror) and the L2
scanner were made idempotent so re-initialising cannot duplicate rows.
tools/minify.py: recognise regex literals - an escaped slash before the
closing delimiter was previously stripped as a // comment.
Measured (make MACHINE=KP_9000_6XHML_X2): 8 embedded files, 109,814 ->
97,730 -> 25,302 bytes gzip (23.0 %); the data block ends at 0x462d6
(~86 KB freed). On a real PCB-K0402WS-V3: cold load ~450 ms, section
switches ~60-160 ms with no asset re-downloads, all sections render
without JavaScript errors.
All 17 page scripts (i18n, shared helpers, navigation and the per-page
scripts) are merged into one html/main.js that every page loads as its
only script, so the browser fetches it once and gzip can compress
across all of it.
The page-specific initialisation is guarded by the presence of the
matching page elements (it used to be selected implicitly by which
script a page loaded); the sidebar fill and the stat popup wiring moved
into DOMContentLoaded handlers because the bundle loads in <head>,
before the body exists.
tools/httpd_sim.h: BUFFER_SIZE 24000 -> 131072 so the simulator can
serve the larger bundle (the firmware is unaffected, it serves the
gzip'd bundle of ~17 KB).
Measured (make MACHINE=KP_9000_6XHML_X2): the JS drops from 23,302 to
16,948 bytes gzip; the whole Web UI from 34,784 to 28,200 bytes (25.3 %
of the raw 111,280); the data block ends at 0x46e28. Verified with a
headless browser against httpd_sim and against a real PCB-K0402WS-V3:
all pages render, the sidebar and the port strip are drawn, no
JavaScript errors.
Two build-time steps shrink the embedded Web UI (html/) from 113,015 to
34,784 bytes (30.8 %):
- tools/minify.py (new): string-literal-safe minifier that strips
comments and indentation; comments are pure overhead on the wire, so
the transfer size drops already before gzip (113,015 -> 101,498).
Binary files (.ico) are copied through unchanged. The output goes to
output/html_min/, the new fileadder input.
- fileadder -z: gzip-compresses every added file (zlib gzip format,
Z_BEST_COMPRESSION); the generated f_data entries carry a gzip flag.
tools/Makefile links the tool against -lz.
- httpd: adds "Content-Encoding: gzip" for flagged entries; the 8051
only copies compressed bytes, decompression happens in the browser.
The JSON API stays uncompressed.
Fixes required to make the sizes deterministic:
- fileadder terminated files at data_read + 1, so the strlen()-based
size computation depended on uninitialised buffer content; the NUL
now lands directly after the content.
- version.h was .PHONY, so every build rewrote it while parallel builds
could read a half-written copy.
- the file list was unscoped and missed .css/.ico; it is now scoped to
html/ and covers all embedded types.
Measured with make MACHINE=KP_9000_6XHML_X2: data block end moves from
0x5b977 to 0x487e0 (~78 KB freed on a 512 KB image). Verified with
MACHINE=PCB_K0402WS_V3 and on real hardware (PCB-K0402WS-V3).
Add a language selector to the System Settings page supporting English,
Japanese, and Chinese. The i18n dictionary already contained zh
translations but lacked the UI to switch languages.
Changes:
- html/i18n.js: add sys_language key to en/ja/zh
- html/system.html: add language selector dropdown
- html/system.js: add changeLang() and selector initialization
- tools/httpd_sim.c: fix cookie parsing for multi-cookie headers;
fix Set-Cookie response (missing Path=/, missing \r\n\r\n);
add SO_REUSEADDR for faster port reuse
The presence of the main function is a hardcoded trigger in sdcc that causes
the interrupt vectors and atomic rollback helpers to be generated as part of
compilation of the file containing the main function.
This makes sure the runtime library is correctly initialized and reduces
crtstart.asm to contain only the banked call helpers.
To ensure that the __interrupt_vect in area HOME is located at the beginning
of the firmware, the linker flags are updated to set the HOME start to
0x00000 and the linker object order is modified to have rtlplayground.rel
first.
Generated code in rtlplayground.asm:
```
;--------------------------------------------------------
; interrupt vector
;--------------------------------------------------------
.area HOME (CODE)
__interrupt_vect:
ljmp __sdcc_gsinit_startup
ljmp _isr_ext0
.ds 5
ljmp _isr_timer0
.ds 5
ljmp _isr_ext1
.ds 5
reti
.ds 7
ljmp _isr_serial
.ds 5
ljmp _isr_timer2
.ds 5
reti
.ds 7
reti
.ds 7
ljmp _isr_ext2
.ds 5
ljmp _isr_ext3
; restartable atomic support routines
.ds 2
sdcc_atomic_exchange_rollback_start::
nop
nop
sdcc_atomic_exchange_pdata_impl:
movx a, @r0
mov r3, a
mov a, r2
movx @r0, a
sjmp sdcc_atomic_exchange_exit
nop
nop
sdcc_atomic_exchange_xdata_impl:
movx a, @dptr
mov r3, a
mov a, r2
movx @dptr, a
sjmp sdcc_atomic_exchange_exit
sdcc_atomic_compare_exchange_idata_impl:
mov a, @r0
cjne a, ar2, .+#5
mov a, r3
mov @r0, a
ret
nop
sdcc_atomic_compare_exchange_pdata_impl:
movx a, @r0
cjne a, ar2, .+#5
mov a, r3
movx @r0, a
ret
nop
sdcc_atomic_compare_exchange_xdata_impl:
movx a, @dptr
cjne a, ar2, .+#5
mov a, r3
movx @dptr, a
ret
sdcc_atomic_exchange_rollback_end::
sdcc_atomic_exchange_gptr_impl::
jnb b.6, sdcc_atomic_exchange_xdata_impl
mov r0, dpl
jb b.5, sdcc_atomic_exchange_pdata_impl
sdcc_atomic_exchange_idata_impl:
mov a, r2
xch a, @r0
mov dpl, a
ret
sdcc_atomic_exchange_exit:
mov dpl, r3
ret
sdcc_atomic_compare_exchange_gptr_impl::
jnb b.6, sdcc_atomic_compare_exchange_xdata_impl
mov r0, dpl
jb b.5, sdcc_atomic_compare_exchange_pdata_impl
sjmp sdcc_atomic_compare_exchange_idata_impl
```
The Makefile has the version number and generates a version.h which
has VERSION_SW define that looks like "v<VERSION>-g<GIT_SHORT_HASH>".
Software version shows at boot in the serial console.
And shown with command: `version`.
It can also be requested via `information.json`.
```
{
"ip_address": "192.168.10.247",
"ip_gateway": "192.168.10.1",
"ip_netmask": "255.255.255.0",
"mac_address": "1c:2a:a3:23:00:02",
"sw_ver": "v0.1.0-gd48235f",
"hw_ver": "SWGT024-V2.0"
}
```