mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
#
|
||||
# LuCI status & settings app for the wwand modem daemon: the modem status page,
|
||||
# the Modems editor, live settings, and the shared resources (wwand.bands /
|
||||
# wwand.modemopts / wwand.simlist) reused by the proto handler.
|
||||
#
|
||||
# Sources live in https://github.com/ddimension/luci-app-wwand — bump
|
||||
# PKG_SOURCE_VERSION (and PKG_SOURCE_DATE) to pick up new source commits.
|
||||
#
|
||||
# NOTE: built from a git PKG_SOURCE, so it does NOT use luci.mk — luci.mk's
|
||||
# htdocs/root install is gated on those dirs living next to the Makefile
|
||||
# (in-tree), which yields an EMPTY package for a git-fetched source. We install
|
||||
# explicitly from $(PKG_BUILD_DIR), like the wwand package in this feed.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-wwand
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
||||
PKG_SOURCE_VERSION:=1c03a023715367e8ce36f9c50ac6ebdaa1e49459
|
||||
PKG_SOURCE_DATE:=2026-08-13
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
PKG_MAINTAINER:=
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/luci-app-wwand
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=wwand cellular modem status & settings
|
||||
DEPENDS:=+luci-base +wwand
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/luci-app-wwand/description
|
||||
LuCI status and configuration app for the wwand cellular modem daemon.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/luci-app-wwand/install
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources
|
||||
$(CP) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/* \
|
||||
$(1)/www/luci-static/resources/
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/luci/menu.d/*.json \
|
||||
$(1)/usr/share/luci/menu.d/
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/rpcd/acl.d/*.json \
|
||||
$(1)/usr/share/rpcd/acl.d/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-wwand))
|
||||
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# LuCI network-manager protocol handler for the wwand modem daemon.
|
||||
# Registers only the 'wwand' protocol UI (protocol/wwand.js). It deliberately
|
||||
# does NOT ship a 'qmi' alias: `proto qmi` stays with luci-proto-qmi/uqmi (no
|
||||
# file collision, good-citizen coexistence), and interfaces are moved to
|
||||
# `proto wwand` via the migration list in the LuCI modem page.
|
||||
#
|
||||
# Sources live in https://github.com/ddimension/luci-proto-wwand — bump
|
||||
# PKG_SOURCE_VERSION (and PKG_SOURCE_DATE) to pick up new source commits.
|
||||
#
|
||||
# NOTE: this is built from a git PKG_SOURCE, so it does NOT use luci.mk. luci.mk
|
||||
# gates its htdocs/root install on those directories living NEXT TO the Makefile
|
||||
# (in-tree); for a git-fetched source they only exist under $(PKG_BUILD_DIR), so
|
||||
# luci.mk produces an EMPTY package. We install explicitly from $(PKG_BUILD_DIR),
|
||||
# exactly like the wwand package in this feed.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-proto-wwand
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-proto-wwand.git
|
||||
PKG_SOURCE_VERSION:=7d9ac0fe15257ac6469d9e2c0228506789200229
|
||||
PKG_SOURCE_DATE:=2026-08-11
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
PKG_MAINTAINER:=
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/luci-proto-wwand
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=4. Protocols
|
||||
TITLE:=Support for QMI/5G cellular modems (wwand)
|
||||
# luci-app-wwand provides the shared wwand.bands resource (band/frequency
|
||||
# helpers) used by this handler; depending on it keeps a single source of truth.
|
||||
DEPENDS:=+luci-base +wwand +luci-app-wwand
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/luci-proto-wwand/description
|
||||
LuCI protocol handler for the wwand cellular modem daemon: the network-native
|
||||
'wwand' proto plus the legacy 'qmi' alias.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/luci-proto-wwand/install
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/protocol
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/protocol/*.js \
|
||||
$(1)/www/luci-static/resources/protocol/
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/rpcd/acl.d/*.json \
|
||||
$(1)/usr/share/rpcd/acl.d/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-proto-wwand))
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=24
|
||||
PKG_RELEASE:=25
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
@@ -154,6 +154,7 @@ const GEOM_DFLT = { contentMin: 500, sidebarW: 224, railW: 68, contentPad: 56 };
|
||||
let _geom = null, _geomDensity = null, _geomWarned = false;
|
||||
function shellGeometry() {
|
||||
const density = document.documentElement.getAttribute('data-density') || '';
|
||||
/* the gutter is re-asked even on the memo hit: it moves with the WIDTH, not with the density */
|
||||
if (_geom && _geomDensity === density) return _geom;
|
||||
_geomDensity = density;
|
||||
const px = (name, dflt) => resolveLen(name, dflt);
|
||||
@@ -161,7 +162,9 @@ function shellGeometry() {
|
||||
contentMin: px('--fs-content-min', GEOM_DFLT.contentMin),
|
||||
sidebarW: px('--fs-sidebar-w', GEOM_DFLT.sidebarW),
|
||||
railW: px('--fs-rail-w', GEOM_DFLT.railW),
|
||||
/* the token is ONE side's padding; the column loses it twice */
|
||||
/* the token is ONE side's padding; the column loses it twice. Kept as `tokenPad` because it
|
||||
* is only the FALLBACK — the gutter that counts is measured, see measureShell() */
|
||||
tokenPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2,
|
||||
contentPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2
|
||||
};
|
||||
/* Plausibility, and it costs one comparison: the rail IS the sidebar collapsed, so
|
||||
@@ -183,21 +186,67 @@ function shellGeometry() {
|
||||
+ 'renamed, or a foreign sheet is reaching the measurement probe.');
|
||||
}
|
||||
_geom = sane ? g : Object.assign({}, GEOM_DFLT);
|
||||
if (_geom.tokenPad === undefined) _geom.tokenPad = _geom.contentPad;
|
||||
/* the token is only the fallback; the gutter that counts is the one measureShell() read */
|
||||
if (_shellPad != null) _geom.contentPad = _shellPad;
|
||||
return _geom;
|
||||
}
|
||||
|
||||
/* THE GUTTER IS MEASURED WHERE IT IS APPLIED, not read off the token that usually supplies it, and
|
||||
* SO IS THE WINDOW — but both are read HERE, from a fitter, and nowhere else.
|
||||
*
|
||||
* `--fs-content-pad` is what `.fs-content` uses at desktop widths, and `theme/20-shell.css` gives
|
||||
* the same element `padding: var(--fs-space-4)` below 767px: the token says 28px a side while the
|
||||
* column's gutter is 16px there. That is a 24px error in a model this file exists to keep honest,
|
||||
* and `live-audit` found it on every page at 320, 390 and 568 — the same trap the alert-message rule
|
||||
* under that media query carries a paragraph about, repeated in the JS. The breakpoint itself may
|
||||
* NOT come here: a width literal copied into JS is exactly what these reads exist to avoid, and a
|
||||
* media query has no `data-*` to key on either. Asking the element what it actually got is the only
|
||||
* form that cannot drift.
|
||||
*
|
||||
* WHY IT IS A SEPARATE FUNCTION AND WHY ONLY A FITTER CALLS IT. `clientWidth` is a LAYOUT read and
|
||||
* `getComputedStyle` resolves style; `contentWidth()` below is called mid-scroll, by the one pass in
|
||||
* this theme that must answer without reading either (fs-select's, for a table the poll brought in
|
||||
* under the reader's thumb). So the numbers are taken when a fitter runs — which is on every resize
|
||||
* and every content mutation, and never during a flick, because `fitChrome()` defers exactly like
|
||||
* every other measuring pass — and everything after that reads what was stored. Nothing they
|
||||
* describe can change without a resize, and a resize schedules a fit.
|
||||
*
|
||||
* Unlike the token probe above, a hostile app's declaration is not a threat here: `.fs-content`
|
||||
* carries no chrome mark, so an app CAN restyle it, and if it did then that padding IS the column's
|
||||
* real gutter. What the probe must not report is a foreign answer for OUR layout; this reports the
|
||||
* layout as it stands. Before any fitter has run — the login page has no `.fs-content` at all — the
|
||||
* token stands in. */
|
||||
let _shellOuter = 0, _shellPad = null;
|
||||
function measureShell() {
|
||||
_shellOuter = document.documentElement.clientWidth;
|
||||
const host = document.querySelector('.fs-content');
|
||||
const cs = host ? getComputedStyle(host) : null;
|
||||
const v = cs ? parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) : NaN;
|
||||
if (Number.isFinite(v) && v >= 0) {
|
||||
_shellPad = v;
|
||||
if (_geom) _geom.contentPad = v;
|
||||
}
|
||||
}
|
||||
|
||||
function columnWidth(g, state) {
|
||||
/* narrow OR top: in both the chrome is above the content, not beside it */
|
||||
const cut = (state.narrow || state.top) ? 0 : (state.rail ? g.railW : g.sidebarW);
|
||||
return Math.max(0, state.outerW - cut - g.contentPad);
|
||||
}
|
||||
|
||||
function fitShell() {
|
||||
const root = document.documentElement;
|
||||
/* the one place the window and the gutter are read — see measureShell(). It runs in BOTH
|
||||
* branches: the bar layout decides nothing here, but `contentWidth()` still answers in it. */
|
||||
measureShell();
|
||||
if (prefs.currentLayout() === 'top') { /* no sidebar, no cut, nothing to decide */
|
||||
root.removeAttribute('data-narrow');
|
||||
return;
|
||||
}
|
||||
const g = shellGeometry();
|
||||
const cut = prefs.currentRail() ? g.railW : g.sidebarW;
|
||||
/* clientWidth, not innerWidth: the column the content actually gets excludes a classic
|
||||
* scrollbar, and innerWidth includes it — 15-17px of phantom room on Linux/Windows, i.e. the
|
||||
* 500px floor really fired at ~484. */
|
||||
const content = document.documentElement.clientWidth - cut - g.contentPad;
|
||||
/* asked UNCOLLAPSED — this is the measurement that decides `data-narrow`, so it may not read it */
|
||||
const content = columnWidth(g, { outerW: _shellOuter, rail: prefs.currentRail() });
|
||||
/* toggleAttribute, NOT setAttribute: a same-value setAttribute still QUEUES a mutation record
|
||||
* (measured in Chromium: 5 identical setAttribute('data-narrow','') -> 5 records; toggleAttribute
|
||||
* on an already-present attribute -> 0). fitShell runs from fitChrome, which fs-fit calls on every
|
||||
@@ -476,15 +525,30 @@ return baseclass.extend({
|
||||
* a known amount of the window and the shell adds a known padding, and all three are already
|
||||
* memoised against the density attribute for `fitShell()`. It is exported because a pass that
|
||||
* must answer mid-scroll — fs-select's, for a table the poll has just brought in — otherwise has
|
||||
* only `window.innerWidth`, which in the sidebar layout is the wrong number by exactly the
|
||||
* only the window's own width, which in the sidebar layout is the wrong number by exactly the
|
||||
* sidebar: at an 800px window the column is 520px, so the cheap judgement said "plenty of room"
|
||||
* for a table that then overflowed and was clipped. */
|
||||
* for a table that then overflowed and was clipped.
|
||||
*
|
||||
* The arithmetic itself is columnWidth()'s, shared with fitShell — see there. All this adds is
|
||||
* the page's current state, which fitShell is in the middle of deciding and this one reads. */
|
||||
contentWidth() {
|
||||
const g = shellGeometry();
|
||||
const narrow = document.documentElement.hasAttribute('data-narrow');
|
||||
/* in the bar layout the chrome is above the content, not beside it */
|
||||
const cut = narrow ? 0 : (prefs.currentRail() ? g.railW : g.sidebarW);
|
||||
return Math.max(0, window.innerWidth - cut - (g.contentPad * 2));
|
||||
/* NO LAYOUT READ, and that is the whole point of this export: the window's width and the
|
||||
* column's gutter are whatever the last fitter measured (measureShell), and the three
|
||||
* attributes below are style, not layout. The bootstrap read is for a caller that arrives
|
||||
* before any fitter has run, which cannot happen mid-scroll. */
|
||||
if (!_shellOuter) measureShell();
|
||||
const root = document.documentElement;
|
||||
return columnWidth(shellGeometry(), {
|
||||
outerW: _shellOuter,
|
||||
narrow: root.hasAttribute('data-narrow'),
|
||||
top: prefs.isTopLayout(),
|
||||
rail: prefs.currentRail()
|
||||
});
|
||||
},
|
||||
|
||||
/* exported for the unit suite (tests/chrome-geometry.test.mjs): the cut is pure arithmetic over
|
||||
* four measured numbers, and driving it directly is the only way to hold every combination of
|
||||
* layout, rail and width without a browser */
|
||||
columnWidth,
|
||||
wireRail
|
||||
});
|
||||
|
||||
@@ -37,25 +37,38 @@ const _viewIntervals = (window.__fsViewIntervals || (window.__fsViewIntervals =
|
||||
if (window.__fsIntervalsHooked) return;
|
||||
window.__fsIntervalsHooked = true;
|
||||
const _si = window.setInterval, _ci = window.clearInterval;
|
||||
/* THE ID THE CALLER GOT IS THE ID IT KEEPS, whatever the pause below does underneath. The map is
|
||||
* keyed by that first id and the entry carries `live`, the id the platform has armed RIGHT NOW
|
||||
* (null while paused) — so a view holding its handle can still stop its own poller after a trip
|
||||
* through a hidden tab, which is exactly what re-arming under a fresh id took away from it.
|
||||
* The arguments are kept for the same reason: a `setInterval` id carries none of them back. */
|
||||
window.setInterval = function (fn, ms) {
|
||||
const id = _si.apply(window, arguments);
|
||||
/* the arguments are kept, not just the id: a paused timer has to be re-armed with what it
|
||||
* was armed with, and a `setInterval` id carries none of that back */
|
||||
_viewIntervals.set(id, { fn, ms, rest: Array.prototype.slice.call(arguments, 2) });
|
||||
_viewIntervals.set(id, { fn, ms, rest: Array.prototype.slice.call(arguments, 2), live: id });
|
||||
return id;
|
||||
};
|
||||
window.clearInterval = function (id) {
|
||||
const spec = _viewIntervals.get(id);
|
||||
_viewIntervals.delete(id);
|
||||
/* A PAUSED TIMER IS ALREADY DISARMED, and its number is the platform's to hand out again —
|
||||
* clearing it here would stop whatever timer holds it now. Untracked ids fall through
|
||||
* unchanged: the hook must stay a pass-through for everything it did not arm. */
|
||||
if (spec) return (spec.live == null) ? undefined : _ci.call(window, spec.live);
|
||||
return _ci.apply(window, arguments);
|
||||
};
|
||||
/* A HIDDEN TAB MUST NOT KEEP CALLING THE ROUTER. `wireVisibility()` below stops LuCI's own poll
|
||||
* when the tab goes away, which is most of the traffic — but a view is free to run a plain
|
||||
* `setInterval` of its own (luci-app-podkop's log tailer does), and those kept hammering ubus in
|
||||
* a background tab for as long as it stayed open. The registry that navigation already uses to
|
||||
* clear them is enough to pause them too: cleared on hide, re-armed on show with the same
|
||||
* clear them is enough to pause them too: disarmed on hide, re-armed on show with the same
|
||||
* callback and period, so a view that was polling every 3 s is polling every 3 s again and one
|
||||
* that was cleared meanwhile stays cleared. */
|
||||
let _paused = [];
|
||||
* that was cleared meanwhile stays cleared.
|
||||
*
|
||||
* A PAUSED TIMER STAYS IN THE REGISTRY, armed on nothing. It was carried in a private list
|
||||
* beside it, and a list is not what the navigation sweep reads: hide the tab while a navigation
|
||||
* is in flight (a click, then straight to another tab), and coming back re-armed the timers of
|
||||
* the page that navigation had already replaced — the sweep had run while they were somewhere
|
||||
* it could not see. In the map, `clearViewIntervals()` takes them like any other. */
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
/* LuCI'S OWN TICK IS NOT OURS TO PAUSE, and taking it made the page poll FASTER the
|
||||
@@ -75,18 +88,22 @@ const _viewIntervals = (window.__fsViewIntervals || (window.__fsViewIntervals =
|
||||
* costs a doubling that never stops. Same judgement as clearViewIntervals(). */
|
||||
const keep = pollTickId();
|
||||
if (keep === false) return;
|
||||
_paused = [];
|
||||
for (const [ id, spec ] of _viewIntervals) {
|
||||
if (id === keep) continue;
|
||||
_paused.push(spec);
|
||||
_ci.call(window, id);
|
||||
_viewIntervals.delete(id);
|
||||
if (id === keep || spec.live == null) continue;
|
||||
_ci.call(window, spec.live);
|
||||
spec.live = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const back = _paused;
|
||||
_paused = [];
|
||||
for (const spec of back) window.setInterval(spec.fn, spec.ms, ...spec.rest);
|
||||
for (const spec of _viewIntervals.values()) {
|
||||
if (spec.live != null) continue;
|
||||
/* `_si`, not the hook: this timer is already in the registry under the id its
|
||||
* caller is holding, and re-registering it would key a second entry to a number
|
||||
* nobody has. The fresh id lives in `spec.live` alone and is never a KEY, so it
|
||||
* cannot collide with an entry — and the platform hands ids out in sequence, so a
|
||||
* re-arm cannot be given a number some earlier caller is still holding either. */
|
||||
spec.live = _si.call(window, spec.fn, spec.ms, ...spec.rest);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -306,6 +323,22 @@ function markExpired() {
|
||||
* ended, and the very next click sends the user to a login form. */
|
||||
console.warn('footstrap: the LuCI session is gone — every navigation from here is a full load.');
|
||||
}
|
||||
/* AND THE VERDICT IS NOT A LATCH. It was, and the shape was wrong for a signal read off somebody
|
||||
* else's reply: an interceptor sees `msg` only once the transport succeeded and the body parsed
|
||||
* (rpc.js rejects before that), so a missing frame is not a network flap — but it IS a captive
|
||||
* portal's page, a proxy's error body, one truncated reply. Any of those took the client router off
|
||||
* for the rest of the document while the session was alive throughout, explained by one console
|
||||
* line nobody reads until later.
|
||||
*
|
||||
* A clean `session.access` is the same call the failing one was, so it is evidence in the other
|
||||
* direction and it is taken as such. If the session really has ended no clean one arrives, because
|
||||
* every ubus call carries the same dead sid — the router stays off exactly as long as it should. */
|
||||
function markAlive() {
|
||||
if (!_expired) return;
|
||||
_expired = false;
|
||||
console.warn('footstrap: the LuCI session answers again — client navigation is back on.');
|
||||
}
|
||||
function sessionExpired() { return _expired; }
|
||||
function watchSession() {
|
||||
if (_sessionWired) return;
|
||||
_sessionWired = true;
|
||||
@@ -325,9 +358,22 @@ function watchSession() {
|
||||
rpc.addInterceptor((msg, r) => {
|
||||
try {
|
||||
if (!r || r.object !== 'session' || r.method !== 'access') return;
|
||||
if (!msg || msg.jsonrpc !== '2.0' ||
|
||||
(msg.error && msg.error.code && msg.error.message))
|
||||
markExpired();
|
||||
if (!msg || msg.jsonrpc !== '2.0') return;
|
||||
/* an `error` carrying both a code and a message is what handleCallReply() rejects
|
||||
* on, and a rejected session probe is the signal. A frame that is not JSON-RPC 2.0
|
||||
* is rejected there too, but it says nothing about the SESSION. */
|
||||
if (msg.error && msg.error.code && msg.error.message) { markExpired(); return; }
|
||||
/* AND ONLY `access: true` SAYS THE SESSION IS THERE — measured on the stands, because
|
||||
* this is the one place where guessing costs the whole fix. A dead sid does not make
|
||||
* this call fail: `session.access` answers `[0, {access:false}]` with HTTP 200 and no
|
||||
* error frame at all (the `-32002` arrives on the ORDINARY call, which is what makes
|
||||
* luci-base fire this probe in the first place). So "the reply parsed" would have
|
||||
* been read as "the session is back", and the verdict a 403 had just reached would
|
||||
* be cleared by the very probe that confirms it. `access:false` stays out of BOTH
|
||||
* answers: an ACL denial for a restricted user looks exactly the same, which is why
|
||||
* it may not expire a session either. */
|
||||
if (Array.isArray(msg.result) && msg.result[1] && msg.result[1].access === true)
|
||||
markAlive();
|
||||
}
|
||||
catch (e) { /* ditto */ }
|
||||
});
|
||||
@@ -1503,6 +1549,11 @@ return baseclass.extend({
|
||||
/* exported for the unit suite (tests/router-contract.test.mjs), which drives it against a
|
||||
* hand-broken `L` — the one way to see the OFF branch without a router that ships one */
|
||||
contractBreaks,
|
||||
/* likewise: tests/interval-pause.test.mjs drives the navigation sweep around a visibilitychange,
|
||||
* and tests/session-expiry.test.mjs reads the verdict the interceptors reached. navigate() is
|
||||
* the real caller of the first and `_expired` gates the second — nothing else may call either. */
|
||||
clearViewIntervals,
|
||||
sessionExpired,
|
||||
/* fs-search warms the pages this admin actually uses (its recents) and the arrow-key-highlighted
|
||||
* result, both of which the pointer/focus triggers above cannot see. The edge points that way
|
||||
* round — search → router — because the router must keep no dependency on the palette. */
|
||||
|
||||
@@ -651,6 +651,34 @@ function fitTables() {
|
||||
return;
|
||||
}
|
||||
|
||||
/* AND THE TABLE ITSELF HAS TO BE MEASURABLE, WHICH ON ITS FIRST PASS IT IS NOT — because
|
||||
* THIS FILE'S OWN GATE is what hides it. `theme/30-tables.css` holds a `.fs-dt` out of the
|
||||
* layout until something marks it `.fs-fitted`, so the very first pass over a fresh table
|
||||
* reads `scrollWidth: 0` — a `display: none` box has no content width — and 0 overflows
|
||||
* nothing. The pass therefore concluded "it fits", wrote `.fs-fitted`, and CACHED that
|
||||
* answer against the slot; the table then appeared at its natural width, 777px inside a
|
||||
* 712px column, columns cut off by `.fs-main { overflow-x: clip }` with no scrollbar and
|
||||
* nothing to say so.
|
||||
*
|
||||
* It usually corrected itself, which is what made it a report rather than a gate failure: a
|
||||
* second pass ~60 ms later measured the now-visible table and broke its widest column. But
|
||||
* that pass only exists if something mutates `#view` again, and Status → Processes renders
|
||||
* once and then stands still. Measured on the stand at 768px, entering the page directly:
|
||||
* 2 of 8 arrivals on 0.13.1 and 3 of 8 here left the table past the column, and one
|
||||
* mutation of any kind fixed it instantly.
|
||||
*
|
||||
* So a zero measurement is not an answer to anything: lift the gate so the next frame can
|
||||
* see the table, ask for that frame, and write NOTHING to the slot. The schedule is asked
|
||||
* for once — on the pass that lifts the gate — so a table hidden for somebody else's reason
|
||||
* (a closed section, a tab pane) cannot turn this into a frame loop. */
|
||||
if (t.scrollWidth === 0) {
|
||||
if (!t.classList.contains('fs-fitted')) {
|
||||
t.classList.add('fs-fitted');
|
||||
fit.schedule();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* the answer this slot already has, for a table of the same shape in the same room — UNLESS the
|
||||
* table has since outgrown it. Room and column count are not the whole input: a poll tick can
|
||||
* put longer values into the same columns, and then a table that fitted a second ago does not.
|
||||
|
||||
@@ -9,7 +9,13 @@
|
||||
-#}
|
||||
{% if (dispatcher.lookup("admin/logout")): %}
|
||||
<a class="fs-logout" href="{{ dispatcher.build_url('admin/logout') }}" title="{{ _('Log out') }}" aria-label="{{ _('Log out') }}">
|
||||
{% include('themes/footstrap/partials/icon', { body: '<path d="M14 4h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4"/><path d="M10 12H3m0 0 3-3m-3 3 3 3"/>' }) %}
|
||||
{#
|
||||
MIRRORED ABOUT x=12, i.e. the glyph reads LEAVING rather than entering: the doorway sits on
|
||||
the left and the arrow points out of it, to the right. Every path is the same path reflected
|
||||
(each x becomes 24-x, and each arc's sweep flag flips with it), so the stroke widths, the
|
||||
corner radii and the 24x24 box are untouched — nothing here is a redrawn icon.
|
||||
-#}
|
||||
{% include('themes/footstrap/partials/icon', { body: '<path d="M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4"/><path d="M14 12h7m0 0-3-3m3 3-3 3"/>' }) %}
|
||||
<span class="fs-label">{{ _('Log out') }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# Self-contained static build of lpac for wwand-esim.
|
||||
#
|
||||
# lpac's ES9+ HTTPS leg needs a TLS stack with the full GSMA curve/algorithm
|
||||
# set. OpenWrt's mbedtls is feature-stripped (fails the SM-DP+ TLS) and its
|
||||
# libcurl+OpenSSL drags in ~6.4 MB of libcrypto/libssl. Instead we bundle a
|
||||
# minimal static wolfSSL + a minimal static libcurl straight into the lpac
|
||||
# binary: one ~1 MB self-contained executable, no libcurl/libssl .so at all.
|
||||
#
|
||||
# Proven on the RG650E: full SGP.22 RSP download over the wwand stdio bridge.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand-lpac
|
||||
PKG_VERSION:=2.3.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=AGPL-3.0-only GPL-3.0-or-later curl
|
||||
PKG_MAINTAINER:=
|
||||
|
||||
# lpac (primary source)
|
||||
PKG_SOURCE:=lpac-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/estkme-group/lpac/tar.gz/refs/tags/v$(PKG_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
# unique build dir (the lpac tarball's top dir is lpac-2.3.0, same as the stock
|
||||
# lpac package — extract it ourselves with --strip-components into our own dir)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/wwand-lpac-$(PKG_VERSION)
|
||||
|
||||
# bundled, statically-linked dependencies
|
||||
WOLFSSL_VERSION:=5.9.1-stable
|
||||
CURL_VERSION:=8.21.0
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
# wolfSSL's SP math (sp_int.c, WOLFSSL_SP_MIPS) carries inline assembly that
|
||||
# MIPS16 cannot encode ("operand 3 must be an immediate expression `addu ...'")
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Download/wolfssl
|
||||
FILE:=wolfssl-$(WOLFSSL_VERSION).tar.gz
|
||||
URL:=https://github.com/wolfSSL/wolfssl/archive/v$(WOLFSSL_VERSION)
|
||||
HASH:=skip
|
||||
endef
|
||||
$(eval $(call Download,wolfssl))
|
||||
|
||||
define Download/curl
|
||||
FILE:=curl-$(CURL_VERSION).tar.xz
|
||||
URL:=https://github.com/curl/curl/releases/download/curl-8_21_0
|
||||
HASH:=skip
|
||||
endef
|
||||
$(eval $(call Download,curl))
|
||||
|
||||
define Package/wwand-lpac
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=lpac eSIM LPA — self-contained static build (wolfSSL)
|
||||
URL:=https://github.com/estkme-group/lpac
|
||||
# PROVIDES:=lpac so wwand-esim's `+lpac` dependency resolves to this
|
||||
# self-contained build. Do NOT also CONFLICTS:=lpac — when the real `lpac`
|
||||
# package is present in the feed set (CI), CONFLICTS emits a
|
||||
# `depends on PACKAGE_lpac` (scripts/package-metadata.pl mconf_conflicts)
|
||||
# which, together with the PROVIDES `select`, is a Kconfig recursive
|
||||
# dependency that breaks `make defconfig` for every feed consumer. The
|
||||
# vpackage provider model keeps the two `lpac` providers mutually exclusive.
|
||||
PROVIDES:=lpac
|
||||
endef
|
||||
|
||||
define Package/wwand-lpac/description
|
||||
A single self-contained lpac binary for wwand-esim: minimal static wolfSSL
|
||||
(TLS 1.2/1.3, ECDHE, ECDSA+RSA, NIST + Brainpool curves, x25519, AES-GCM,
|
||||
SHA-2; built with WOLFSSL_NO_ASN_STRICT so the GSMA id-rspRole critical
|
||||
extension is accepted) and a minimal static libcurl (HTTPS only) are linked
|
||||
straight into lpac. No libcurl/libssl/libcrypto .so — ~1 MB total instead of
|
||||
the ~6.4 MB that libcurl+OpenSSL pulls in. Only the stdio + curl drivers are
|
||||
built (the wwand stdio bridge relays the eUICC APDUs through wwand's own
|
||||
modem_apdu channel). Drop-in replacement for the stock lpac package.
|
||||
|
||||
Includes patch 110 (backport of upstream #399, the v2.3.0 stdio-driver fix).
|
||||
endef
|
||||
|
||||
PREFIX:=$(PKG_BUILD_DIR)/.bundle
|
||||
WOLFSSL_DIR:=$(PKG_BUILD_DIR)/wolfssl-$(WOLFSSL_VERSION)
|
||||
CURL_DIR:=$(PKG_BUILD_DIR)/curl-$(CURL_VERSION)
|
||||
LPAC_BUILD:=$(PKG_BUILD_DIR)/.lpac-build
|
||||
|
||||
# LTO across the whole bundle (wolfSSL + libcurl + lpac). Fat LTO objects keep
|
||||
# real machine code alongside the IR, so the sub-libs' configure link-tests
|
||||
# (plain, no -flto) still resolve symbols while the final lpac link (-flto) still
|
||||
# optimises across all three. gcc-ar/gcc-ranlib are the LTO-aware archivers.
|
||||
LTO_CFLAGS:=-flto=auto -ffat-lto-objects
|
||||
BUNDLE_AR:=AR="$(TARGET_CROSS)gcc-ar" RANLIB="$(TARGET_CROSS)gcc-ranlib"
|
||||
|
||||
# extract lpac (strip its top dir into our unique build dir), patch it, then
|
||||
# unpack the bundled wolfSSL + curl sources alongside it
|
||||
define Build/Prepare
|
||||
$(INSTALL_DIR) $(PKG_BUILD_DIR)
|
||||
$(TAR) -C $(PKG_BUILD_DIR) --strip-components=1 -xzf $(DL_DIR)/$(PKG_SOURCE)
|
||||
$(Build/Patch)
|
||||
$(TAR) -C $(PKG_BUILD_DIR) -xzf $(DL_DIR)/wolfssl-$(WOLFSSL_VERSION).tar.gz
|
||||
$(TAR) -C $(PKG_BUILD_DIR) -xJf $(DL_DIR)/curl-$(CURL_VERSION).tar.xz
|
||||
endef
|
||||
|
||||
# nothing to autoconf for the top-level (lpac is CMake); the sub-libs are
|
||||
# configured inside Build/Compile
|
||||
Build/Configure:=
|
||||
|
||||
define Build/Compile
|
||||
# 1) minimal static wolfSSL (GSMA-capable)
|
||||
( cd $(WOLFSSL_DIR) && ./autogen.sh )
|
||||
( cd $(WOLFSSL_DIR) && ./configure --host=$(GNU_TARGET_NAME) --prefix=$(PREFIX) \
|
||||
--enable-static --disable-shared \
|
||||
--enable-curl --enable-tls13 --enable-brainpool --enable-ecccustcurves \
|
||||
--enable-curve25519 --disable-dtls --disable-oldtls \
|
||||
--disable-examples --disable-crypttests \
|
||||
CC="$(TARGET_CC)" $(BUNDLE_AR) \
|
||||
CFLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -DWOLFSSL_NO_ASN_STRICT" )
|
||||
+$(MAKE) -C $(WOLFSSL_DIR)
|
||||
$(MAKE) -C $(WOLFSSL_DIR) install
|
||||
|
||||
# 2) minimal static libcurl (HTTPS only) against the bundled wolfSSL
|
||||
# -DSIZEOF_LONG_LONG=8: on 32-bit targets curl_config.h defines
|
||||
# SIZEOF_LONG=4 and nothing else; wolfSSL's CTC_SETTINGS enum (types.h)
|
||||
# then hits "#error bad math long / long long settings". 64-bit targets
|
||||
# take the SIZEOF_LONG==8 branch and never look at it.
|
||||
( cd $(CURL_DIR) && \
|
||||
PKG_CONFIG_PATH="$(PREFIX)/lib/pkgconfig" PKG_CONFIG_LIBDIR="$(PREFIX)/lib/pkgconfig" \
|
||||
./configure --host=$(GNU_TARGET_NAME) --prefix=$(PREFIX) --with-wolfssl=$(PREFIX) \
|
||||
--enable-static --disable-shared \
|
||||
--disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp \
|
||||
--disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap \
|
||||
--disable-smtp --disable-gopher --disable-mqtt --disable-smb --disable-ntlm \
|
||||
--disable-manual --disable-unix-sockets --disable-progress-meter \
|
||||
--without-libpsl --without-zlib --without-nghttp2 --without-brotli \
|
||||
--without-zstd --without-libidn2 --without-librtmp --without-ca-path \
|
||||
--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \
|
||||
CC="$(TARGET_CC)" $(BUNDLE_AR) \
|
||||
CFLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -DSIZEOF_LONG_LONG=8" \
|
||||
CPPFLAGS="-I$(PREFIX)/include" LDFLAGS="-L$(PREFIX)/lib" LIBS="-lm" )
|
||||
+$(MAKE) -C $(CURL_DIR)
|
||||
$(MAKE) -C $(CURL_DIR) install
|
||||
|
||||
# 3) lpac, statically linked against the two bundled .a
|
||||
rm -rf $(LPAC_BUILD)
|
||||
mkdir -p $(LPAC_BUILD)
|
||||
( cd $(LPAC_BUILD) && PKG_CONFIG_PATH="$(PREFIX)/lib/pkgconfig" cmake $(PKG_BUILD_DIR) \
|
||||
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=$(ARCH) \
|
||||
-DCMAKE_C_COMPILER="$(TARGET_CROSS)gcc" \
|
||||
-DCMAKE_AR="$(TARGET_CROSS)gcc-ar" -DCMAKE_RANLIB="$(TARGET_CROSS)gcc-ranlib" \
|
||||
-DCMAKE_FIND_ROOT_PATH="$(PREFIX)" -DCMAKE_PREFIX_PATH="$(PREFIX)" \
|
||||
-DCMAKE_C_FLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -I$(PREFIX)/include" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="$(TARGET_LDFLAGS) $(LTO_CFLAGS) -Wl,--gc-sections -L$(PREFIX)/lib" \
|
||||
-DCMAKE_C_STANDARD_LIBRARIES="-L$(PREFIX)/lib -lwolfssl -lm" \
|
||||
-DLPAC_DYNAMIC_LIBEUICC=OFF \
|
||||
-DLPAC_WITH_APDU_PCSC=OFF -DLPAC_WITH_APDU_AT=OFF \
|
||||
-DLPAC_WITH_APDU_QMI=OFF -DLPAC_WITH_APDU_MBIM=OFF \
|
||||
-DLPAC_WITH_HTTP_CURL=ON \
|
||||
-DCURL_INCLUDE_DIR="$(PREFIX)/include" -DCURL_LIBRARY="$(PREFIX)/lib/libcurl.a" )
|
||||
+$(MAKE) -C $(LPAC_BUILD)
|
||||
endef
|
||||
|
||||
define Package/wwand-lpac/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib
|
||||
$(INSTALL_BIN) $(LPAC_BUILD)/output/lpac $(1)/usr/lib/lpac
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) ./files/lpac.sh $(1)/usr/bin/lpac
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,wwand-lpac))
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# thin wrapper so `lpac` on PATH resolves to the self-contained static binary
|
||||
exec /usr/lib/lpac "$@"
|
||||
@@ -0,0 +1,36 @@
|
||||
Backport of upstream lpac PR #399 ("fix(driver): 'json_request' interface was
|
||||
changed in b23f0e4", commit 977c324, merged 2025-11-02) to the v2.3.0 release.
|
||||
json_print()/json_request() were changed to return true on success, but
|
||||
apdu/stdio.c's connect/logic_channel_open/transmit still checked
|
||||
`if (json_request(...)) return -1;` — bailing with -1 the moment a request is
|
||||
sent successfully, so euicc_init dies at connect before reading any reply.
|
||||
Drop this patch once lpac ships a release later than v2.3.0 that includes #399.
|
||||
|
||||
--- a/driver/apdu/stdio.c
|
||||
+++ b/driver/apdu/stdio.c
|
||||
@@ -160,7 +160,7 @@
|
||||
static int apdu_interface_connect(struct euicc_ctx *ctx) {
|
||||
int ecode;
|
||||
|
||||
- if (json_request("connect", NULL, 0)) {
|
||||
+ if (!json_request("connect", NULL, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
static int apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len) {
|
||||
int ecode;
|
||||
|
||||
- if (json_request("logic_channel_open", aid, aid_len)) {
|
||||
+ if (!json_request("logic_channel_open", aid, aid_len)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
uint32_t tx_len) {
|
||||
int ecode;
|
||||
|
||||
- if (json_request("transmit", tx, tx_len)) {
|
||||
+ if (!json_request("transmit", tx, tx_len)) {
|
||||
return -1;
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
# OpenWrt package definitions for the wwand connection manager.
|
||||
# One source package (git: ddimension/wwand); the ucode side is split into a
|
||||
# backend-neutral base plus per-backend packages so an install carries only the
|
||||
# control protocols it needs:
|
||||
# wwand - daemon + shared core + codec/client + native wwand_io.so
|
||||
# transport module (no backend on its own)
|
||||
# wwand-qmi - QMI backend (DEPENDS wwand)
|
||||
# wwand-mbim - MBIM backend (DEPENDS wwand-qmi: the MBIM
|
||||
# QMI-over-MBIM passthrough reuses
|
||||
# qmi_backend)
|
||||
# wwand-ncm - NCM/ECM backend (DEPENDS wwand)
|
||||
# wwand-mhi - PCIe/MHI transport + drivers (DEPENDS wwand; add a backend)
|
||||
# wwand-esim - eSIM management (DEPENDS wwand-qmi + wwand-lpac)
|
||||
# A typical QMI router installs `wwand-qmi` (which pulls in `wwand`); add
|
||||
# `wwand-mbim` / `wwand-ncm` for those modems, `wwand-mhi` for a PCIe/MHI modem.
|
||||
# The ucode tree ships precompiled to bytecode by default — built by the
|
||||
# repo-root CMakeLists.txt alongside wwand_io.so (dev opt-out:
|
||||
# CONFIG_WWAND_UCODE_SOURCE).
|
||||
#
|
||||
# NOTE: the split needs the ucode source that lazy-loads every backend
|
||||
# (qmi_lazy.uc + the backend-neutral daemon). Bump PKG_SOURCE_VERSION (and
|
||||
# PKG_SOURCE_DATE / PKG_MIRROR_HASH) to a wwand commit that includes them.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=61f4c2dc728d70e490c94b3e1d8cb2262c262ebb
|
||||
PKG_SOURCE_DATE:=2026-08-17
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
PKG_MAINTAINER:=
|
||||
|
||||
# host ucode is the bytecode compiler for the precompile step (below). It is
|
||||
# built from the same ucode revision as the on-device runtime, so the bytecode
|
||||
# it emits always matches the target interpreter.
|
||||
PKG_BUILD_DEPENDS:=ucode/host
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
|
||||
# One cmake build (repo-root CMakeLists.txt) produces both the native
|
||||
# wwand_io.so (io/ subdir) and — by default — the ucode tree compiled to
|
||||
# bytecode, one compiler invocation per file with explicit source lists (the
|
||||
# whole mechanism, incl. the invariants it enforces, is documented in the
|
||||
# wwand repo's CMakeLists.txt). The bytecode compiler is the SDK's host ucode
|
||||
# (PKG_BUILD_DEPENDS:=ucode/host) — same ucode revision as the target runtime,
|
||||
# which bytecode is locked to.
|
||||
CMAKE_BINARY_SUBDIR:=build
|
||||
|
||||
CMAKE_OPTIONS += \
|
||||
-DUCODE_COMPILER=$(STAGING_DIR_HOSTPKG)/bin/ucode \
|
||||
-DUCODE_PRECOMPILE=$(if $(CONFIG_WWAND_UCODE_SOURCE),OFF,ON)
|
||||
|
||||
# every install section takes the ucode tree from here: the bytecode output of
|
||||
# the cmake build, or the plain sources when CONFIG_WWAND_UCODE_SOURCE is set.
|
||||
WWAND_UCODE=$(if $(CONFIG_WWAND_UCODE_SOURCE),$(PKG_BUILD_DIR)/src-ucode,$(PKG_BUILD_DIR)/$(CMAKE_BINARY_SUBDIR)/ucode/wwand)
|
||||
|
||||
UCDIR:=/usr/share/ucode/wwand
|
||||
|
||||
# Base-package ucode set, listed explicitly per install dir. No glob-then-`rm`:
|
||||
# every module is owned by exactly one package, so a new backend file can never
|
||||
# silently ship in the base too. The per-backend install lists below carry the
|
||||
# QMI/MBIM/NCM/eSIM modules; main.uc/wwandctl.uc install as executables.
|
||||
WWAND_BASE_UC:=apndb.uc atcmd.uc atcmd_parse.uc atport.uc backend.uc board.uc \
|
||||
client.uc config.uc config_check.uc context_common.uc context_monitor_qmi.uc \
|
||||
ctx_settings.uc daemon.uc datapath_qmi.uc discovery.uc hwops.uc log.uc \
|
||||
modem_common.uc modem_init_qmi.uc modem_quirks.uc modeswitch.uc ncm_vendors.uc \
|
||||
netlink.uc netsel_ops.uc protocol_switch.uc reconnect.uc recovery.uc regdetail.uc \
|
||||
sim.uc sim_plmn.uc simops.uc sms.uc sms_pdu.uc telemetry_mbim.uc telemetry_ncm.uc \
|
||||
telemetry_qmi.uc transport.uc ubus.uc
|
||||
WWAND_BASE_CODEC:=arfcn_bands.uc hex.uc qmux.uc tlv.uc
|
||||
WWAND_BASE_SCHEMA:=ctl.uc dms.uc dsd.uc loc.uc loc_lazy.uc merge.uc nas.uc rat.uc \
|
||||
uim.uc wda.uc wds.uc wms.uc wms_lazy.uc
|
||||
|
||||
# DEVELOPERS: enable CONFIG_WWAND_UCODE_SOURCE (menuconfig, under this package)
|
||||
# to ship readable .uc source instead of bytecode — for editing modules live
|
||||
# under /usr/share/ucode/wwand and for source-line tracebacks; bytecode
|
||||
# tracebacks report offsets only.
|
||||
PKG_CONFIG_DEPENDS:=CONFIG_WWAND_UCODE_SOURCE
|
||||
|
||||
define Package/wwand/config
|
||||
config WWAND_UCODE_SOURCE
|
||||
bool "Ship ucode sources instead of precompiled bytecode"
|
||||
depends on PACKAGE_wwand
|
||||
default n
|
||||
help
|
||||
wwand normally ships its ucode tree precompiled to bytecode
|
||||
(faster daemon start, no parse step on the device). Enable this
|
||||
for development builds: the readable sources are installed
|
||||
instead, runtime tracebacks carry source line numbers and the
|
||||
modules can be edited in place on the device.
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# base: daemon + framework + codec/client + shared core. No backend on its own;
|
||||
# the daemon loads whichever wwand-qmi/-mbim/-ncm package is present.
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=Event-driven cellular connection manager (backend-neutral core)
|
||||
DEPENDS:=+ucode +ucode-mod-fs +ucode-mod-struct +ucode-mod-uloop \
|
||||
+ucode-mod-ubus +ucode-mod-uci +ucode-mod-rtnl
|
||||
# the native wwand_io transport module ships inside this package (it is
|
||||
# wwand-private, always version-locked to the ucode side). PROVIDES keeps
|
||||
# configs/upgrades from the era of the separate ucode-mod-wwand-io working.
|
||||
PROVIDES:=ucode-mod-wwand-io
|
||||
endef
|
||||
|
||||
define Package/wwand/description
|
||||
wwand is an event-driven connection manager daemon for cellular modems,
|
||||
supporting multiple modems and multiple parallel PDP contexts. This base
|
||||
package carries the daemon, the netifd proto shim, the QMI codec (qmux/tlv +
|
||||
service schemas), the generic service client, the SIM/APDU layer and the
|
||||
shared modem/context core. Install a backend package (wwand-qmi, wwand-mbim
|
||||
or wwand-ncm) for the control protocols your modems use.
|
||||
endef
|
||||
|
||||
define Package/wwand/install
|
||||
# native transport module (built by the cmake step from io/)
|
||||
$(INSTALL_DIR) $(1)/usr/lib/ucode
|
||||
$(INSTALL_BIN) $(CMAKE_BINARY_DIR)/io/wwand_io.so $(1)/usr/lib/ucode/
|
||||
# base ucode tree — explicit per-file lists (see WWAND_BASE_* above), so the
|
||||
# base package owns exactly its own modules and nothing a backend ships.
|
||||
$(INSTALL_DIR) $(1)$(UCDIR)/codec/schema
|
||||
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/,$(WWAND_BASE_UC)) $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/codec/,$(WWAND_BASE_CODEC)) $(1)$(UCDIR)/codec/
|
||||
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/codec/schema/,$(WWAND_BASE_SCHEMA)) $(1)$(UCDIR)/codec/schema/
|
||||
# daemon + CLI as executables (not under $(UCDIR))
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) $(WWAND_UCODE)/main.uc $(1)/usr/sbin/wwand
|
||||
# wwandctl: the human-friendly CLI front-end over the ubus API
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(WWAND_UCODE)/wwandctl.uc $(1)/usr/bin/wwandctl
|
||||
$(INSTALL_DIR) $(1)/lib/netifd/proto
|
||||
# the shim registers `wwand`; the legacy `qmi` alias only when the global
|
||||
# `option takeover` is set (default off, so uqmi keeps `proto qmi`). Install
|
||||
# under the current name — the historical qmi.sh belonged to uqmi
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand-proto.sh $(1)/lib/netifd/proto/wwand.sh
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/wwand
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand-migrate $(1)/usr/libexec/wwand/migrate
|
||||
# migration is user-triggered (LuCI modem list / the migrate CLI); the
|
||||
# uci-defaults hook auto-migrates on install/upgrade ONLY when the global
|
||||
# `option takeover` is set (default off — wwand does not touch existing configs)
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand.defaults $(1)/etc/uci-defaults/99-wwand-migrate
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand.init $(1)/etc/init.d/wwand
|
||||
# no /etc/config/wwand for new installs — all config lives in
|
||||
# /etc/config/network now (existing files survive upgrade and are still read).
|
||||
# files/wwand.config is kept in the source tree only as a documented example.
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/usbmisc
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug $(1)/etc/hotplug.d/usbmisc/20-wwand
|
||||
# net hotplug: NCM modems (no cdc-wdm) + re-enumeration after a mode switch
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/net
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.net $(1)/etc/hotplug.d/net/20-wwand
|
||||
# tty hotplug: AT ports appearing after the datapath netdev (vendor-serial
|
||||
# new_id bind / late kmodloader) re-kick a modem parked in no_at_port backoff
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/tty
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.tty $(1)/etc/hotplug.d/tty/20-wwand
|
||||
# NOTE: the kernel-wwan-subsystem hotplug (/etc/hotplug.d/wwan/20-wwand) is
|
||||
# NOT installed here — it belongs to wwand-mhi, which also pulls the MHI
|
||||
# drivers that create /sys/class/wwan in the first place (procd only arms a
|
||||
# subsystem whose hotplug dir exists). USB modems never need it.
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wwand-qmi: the QMI backend (native qmux over /dev/cdc-wdmX).
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand-qmi
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=QMI backend for wwand
|
||||
DEPENDS:=+wwand +kmod-usb-net-qmi-wwan +kmod-rmnet
|
||||
# coexists with the stock OpenWrt QMI stack (uqmi):
|
||||
# by default wwand claims only `proto wwand` interfaces, so both can be
|
||||
# installed. Set the global `option takeover '1'` (or migrate interfaces from
|
||||
# the LuCI modem list) to hand `proto qmi` interfaces to wwand.
|
||||
endef
|
||||
|
||||
define Package/wwand-qmi/description
|
||||
QMI control backend for wwand: talks QMI natively over /dev/cdc-wdmX (qmi_wwan
|
||||
driver) with QMAP multiplexing (rmnet), without spawning uqmi/qmicli. This is
|
||||
the common case for most cellular routers.
|
||||
endef
|
||||
|
||||
define Package/wwand-qmi/install
|
||||
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/modem.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/context.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_backend.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/callend.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_lazy.uc $(1)$(UCDIR)/
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wwand-mbim: the MBIM backend. Depends on wwand-qmi because the QMI-over-MBIM
|
||||
# passthrough runs the QMI stack (qmi_backend) over the open MBIM channel.
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand-mbim
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=MBIM backend for wwand
|
||||
DEPENDS:=+wwand-qmi +kmod-usb-net-cdc-mbim
|
||||
# coexists with the stock OpenWrt MBIM stack (netifd `mbim` proto, package
|
||||
# umbim): wwand manages a cdc_mbim modem only once its interface has been
|
||||
# migrated to `proto wwand` (LuCI modem list / migrate CLI), so both stacks can
|
||||
# be installed side by side.
|
||||
endef
|
||||
|
||||
define Package/wwand-mbim/description
|
||||
MBIM control backend for wwand (cdc_mbim driver): native MS Basic Connect (+
|
||||
Extensions v2/v3) plus a QMI-over-MBIM passthrough that tunnels the whole QMI
|
||||
stack over the open MBIM channel for full CDC-level telemetry/config. The
|
||||
passthrough reuses qmi_backend, hence the dependency on wwand-qmi.
|
||||
endef
|
||||
|
||||
define Package/wwand-mbim/install
|
||||
$(INSTALL_DIR) $(1)$(UCDIR)/codec/mbim_schema
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/modem_mbim.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/context_mbim.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_backend.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_client.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_over_mbim.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_lazy.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim.uc $(1)$(UCDIR)/codec/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim_schema/*.uc $(1)$(UCDIR)/codec/mbim_schema/
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wwand-ncm: the NCM/ECM backend (cdc_ncm / cdc_ether, AT-controlled).
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand-ncm
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=NCM/ECM backend for wwand
|
||||
DEPENDS:=+wwand +kmod-usb-net-cdc-ncm +kmod-usb-net-cdc-ether
|
||||
# coexists with the stock OpenWrt NCM stack (netifd `ncm` proto, package
|
||||
# comgt-ncm): wwand drives an AT/NCM modem only once its interface has been
|
||||
# migrated to `proto wwand` (LuCI modem list / migrate CLI), so both stacks can
|
||||
# be installed side by side.
|
||||
endef
|
||||
|
||||
define Package/wwand-ncm/description
|
||||
NCM/ECM control backend for wwand: an AT-controlled datapath over a plain
|
||||
cdc_ncm / cdc_ether netdev, for modems that expose no cdc-wdm control device.
|
||||
endef
|
||||
|
||||
define Package/wwand-ncm/install
|
||||
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/modem_ncm.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/context_ncm.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/ncm_lazy.uc $(1)$(UCDIR)/
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wwand-mhi: PCIe/MHI transport bundle. Modems on the kernel `wwan` subsystem
|
||||
# (Foxconn T99W175/SDX, Quectel RM5xx-AE, Telit FN990, …) expose their QMI/MBIM
|
||||
# control port under /sys/class/wwan — not usbmisc/cdc-wdm — and need the MHI
|
||||
# driver stack instead of the USB kmods. This package pulls those drivers and
|
||||
# ships the wwan-subsystem hotplug that binds such a modem on cold-plug. It is
|
||||
# transport-only and backend-neutral: install ALONGSIDE wwand-qmi (QMI over MHI,
|
||||
# rmnet data) or wwand-mbim (MBIM over MHI) for the control protocol.
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand-mhi
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=PCIe/MHI transport + drivers for wwand
|
||||
DEPENDS:=+wwand +kmod-mhi-pci-generic +kmod-mhi-wwan-ctrl \
|
||||
+kmod-mhi-wwan-mbim +kmod-mhi-net
|
||||
endef
|
||||
|
||||
define Package/wwand-mhi/description
|
||||
Support for cellular modems attached over PCIe/MHI (Foxconn T99W175 and
|
||||
similar), whose control ports appear on the kernel wwan subsystem. Pulls the
|
||||
MHI bus/PCI/control/data drivers and installs the wwan-subsystem hotplug so
|
||||
wwand binds these modems on boot. Install together with a control backend
|
||||
(wwand-qmi or wwand-mbim).
|
||||
endef
|
||||
|
||||
define Package/wwand-mhi/install
|
||||
# wwan-subsystem hotplug: MHI modems expose their control port under
|
||||
# /sys/class/wwan (/dev/wwanXqmiN|mbimN). procd only dispatches the `wwan`
|
||||
# subsystem once this directory exists — so it ships with the MHI drivers,
|
||||
# not with the base package.
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/wwan
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.wwan $(1)/etc/hotplug.d/wwan/20-wwand
|
||||
endef
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# wwand-esim: eSIM (eUICC) profile management. Needs a QMI UIM/APDU channel.
|
||||
# ---------------------------------------------------------------------------
|
||||
define Package/wwand-esim
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WWAN
|
||||
TITLE:=eSIM (eUICC) profile management for wwand
|
||||
# Needs an lpac binary at /usr/bin/lpac: either the generic openwrt-packages
|
||||
# lpac, or our self-contained static wwand-lpac (bundled wolfSSL+libcurl, no
|
||||
# OpenSSL/mbedtls .so — it PROVIDES lpac, so it satisfies +lpac too). lpac
|
||||
# drives the SM-DP+ download/notification (ES9+ HTTPS on the router); the
|
||||
# daemon bridges its stdio APDU protocol inline. APDU access uses the modem
|
||||
# UIM channel, so it also needs the QMI backend.
|
||||
DEPENDS:=+wwand-qmi +lpac
|
||||
endef
|
||||
|
||||
define Package/wwand-esim/description
|
||||
Optional ES10c (SGP.22) profile management for wwand: EID, profile list,
|
||||
enable/disable/delete over the modem UIM APDU channel. SM-DP+ profile
|
||||
downloads and notification acknowledgements are driven by lpac: its ES10
|
||||
APDUs are relayed through wwand's own APDU channel (ubus modem_apdu) by the
|
||||
stdio bridge, so wwand stays the sole owner of the modem — no separate AT
|
||||
port. lpac runs the ES9+ HTTPS on the router; no dedicated modem APN is
|
||||
required.
|
||||
|
||||
The download is handled by an lpac binary (the +lpac dependency): either the
|
||||
generic openwrt-packages lpac, or the self-contained wwand-lpac (which PROVIDES
|
||||
lpac) — a single ~0.9 MB binary with a minimal static wolfSSL + libcurl linked
|
||||
in, needing no libcurl/libssl/libcrypto .so (vs the ~6.4 MB stock libcurl+
|
||||
OpenSSL pulls in) and carrying the GSMA-capable TLS itself. Either works via
|
||||
/usr/bin/lpac; the stdio APDU bridge needs lpac >= 2.3.0 (upstream PR #399).
|
||||
See wwand-lpac/Makefile.
|
||||
endef
|
||||
|
||||
define Package/wwand-esim/install
|
||||
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/esim.uc $(1)$(UCDIR)/
|
||||
$(INSTALL_DATA) $(WWAND_UCODE)/esim_bridge.uc $(1)$(UCDIR)/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,wwand))
|
||||
$(eval $(call BuildPackage,wwand-qmi))
|
||||
$(eval $(call BuildPackage,wwand-mbim))
|
||||
$(eval $(call BuildPackage,wwand-ncm))
|
||||
$(eval $(call BuildPackage,wwand-mhi))
|
||||
$(eval $(call BuildPackage,wwand-esim))
|
||||
Reference in New Issue
Block a user