mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-20 14:53:42 +08:00
feat: implement device management and eSIM support services
This commit is contained in:
+16
-30
@@ -1071,15 +1071,14 @@ func (manager *Manager) renameCachedProfile(id, iccid, nickname string) {
|
||||
manager.esimCacheMu.Unlock()
|
||||
}
|
||||
|
||||
// recoverAfterProfileSwitch owns the post-commit reset independently of the
|
||||
// initiating HTTP request. EC20 commonly drops the AT port while processing
|
||||
// CFUN=1,1, so the reset error is intentionally followed by discovery retries.
|
||||
// recoverAfterProfileSwitch owns the post-commit SIM reset independently of the
|
||||
// initiating HTTP request.
|
||||
func (manager *Manager) recoverAfterProfileSwitch(id string) {
|
||||
resetContext, cancelReset := context.WithTimeout(context.Background(), manager.longTimeout)
|
||||
if native, err := manager.powerCycleNativeQMISIM(resetContext, id); native {
|
||||
cancelReset()
|
||||
if err == nil {
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
// Native WWAN identity and profile verification are both QMI-backed.
|
||||
// Do not enter the AT refresh path: OpenStick firmware can accept the
|
||||
@@ -1088,52 +1087,39 @@ func (manager *Manager) recoverAfterProfileSwitch(id string) {
|
||||
}
|
||||
cancelReset()
|
||||
if !manager.isPCSCDevice(id) {
|
||||
resetContext, cancelReset := context.WithTimeout(context.Background(), manager.longTimeout)
|
||||
_ = manager.rebootForProfileSwitch(resetContext, id)
|
||||
resetContext, cancelReset := context.WithTimeout(context.Background(), manager.commandTimeout*2)
|
||||
_ = manager.softResetForProfileSwitch(resetContext, id)
|
||||
cancelReset()
|
||||
}
|
||||
manager.refreshAfterProfileSwitch(id)
|
||||
}
|
||||
|
||||
// refreshAfterProfileSwitch repopulates the device snapshot in the background
|
||||
// after an eSIM profile switch + modem reboot. /overview only serves the cached
|
||||
// snapshot, and nothing else live-reads post-switch, so without this the card
|
||||
// stays on "--" forever. The EC20 takes ~10-15s to come back from AT+CFUN=1,1,
|
||||
// so we delay first, then retry with backoff. Transport errors during the
|
||||
// reboot window are fine — Fix 1 discards the poisoned client and reopens on
|
||||
// the next attempt. All errors are swallowed: this is best-effort self-healing
|
||||
// and setResult already records the last failure for the UI.
|
||||
// after an eSIM profile switch.
|
||||
func (manager *Manager) refreshAfterProfileSwitch(id string) {
|
||||
if manager.isPCSCDevice(id) {
|
||||
time.Sleep(750 * time.Millisecond)
|
||||
for attempt := 0; attempt < 10; attempt++ {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), manager.commandTimeout*4)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for attempt := 0; attempt < 5; attempt++ {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), manager.commandTimeout*2)
|
||||
_, _ = manager.Discover(ctx)
|
||||
_, err := manager.Refresh(ctx, id)
|
||||
cancel()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return
|
||||
}
|
||||
const (
|
||||
settle = 8 * time.Second
|
||||
interval = 4 * time.Second
|
||||
attempts = 6
|
||||
settle = 1 * time.Second
|
||||
interval = 1 * time.Second
|
||||
attempts = 5
|
||||
)
|
||||
time.Sleep(settle)
|
||||
for attempt := 0; attempt < attempts; attempt++ {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), manager.commandTimeout*4)
|
||||
_, _ = manager.Discover(ctx)
|
||||
_, flightErr := manager.SetFlight(ctx, id, true)
|
||||
var err error
|
||||
if flightErr == nil {
|
||||
_, err = manager.Refresh(ctx, id)
|
||||
} else {
|
||||
err = flightErr
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), manager.commandTimeout*2)
|
||||
_, err := manager.Refresh(ctx, id)
|
||||
cancel()
|
||||
if err == nil {
|
||||
return
|
||||
@@ -1233,7 +1219,7 @@ func (manager *Manager) canVerifyProfileSwitchWithoutRestart(id string) bool {
|
||||
// is finalized by REFRESH/reset. The UI must not report success until the modem
|
||||
// is actually exposing the requested ICCID.
|
||||
func (manager *Manager) verifySwitchedICCID(ctx context.Context, id, expected string) error {
|
||||
return manager.verifySwitchedICCIDAttempts(ctx, id, expected, 6, 2*time.Second)
|
||||
return manager.verifySwitchedICCIDAttempts(ctx, id, expected, 6, 1*time.Second)
|
||||
}
|
||||
|
||||
func (manager *Manager) verifySwitchedICCIDAttempts(
|
||||
|
||||
+22
-13
@@ -631,13 +631,11 @@ func (manager *Manager) Reboot(ctx context.Context, id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// rebootForProfileSwitch is the post-EnableProfile modem reset. After the eUICC
|
||||
// marks a new profile active, the modem keeps the old SIM cached and lands in
|
||||
// SIM failure (-CME 13) until it is bounced. ESIMSwitchProfile has already
|
||||
// released opMu by the time it calls this, so the reset is safe to take the
|
||||
// lock. This mirrors Reboot but is separate so the call site can't recurse into
|
||||
// a guarded-reset path.
|
||||
func (manager *Manager) rebootForProfileSwitch(ctx context.Context, id string) error {
|
||||
// softResetForProfileSwitch resets the baseband SIM stack using a soft CFUN sequence
|
||||
// (AT+CFUN=0 -> AT+CFUN=1/4) instead of rebooting the entire hardware module (AT+CFUN=1,1).
|
||||
// This causes the baseband to reload the new eSIM profile files within ~1-2 seconds
|
||||
// without disconnecting USB/PCIe or dropping serial communication ports.
|
||||
func (manager *Manager) softResetForProfileSwitch(ctx context.Context, id string) error {
|
||||
state, err := manager.lookup(id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -652,14 +650,25 @@ func (manager *Manager) rebootForProfileSwitch(ctx context.Context, id string) e
|
||||
manager.setResult(id, state, nil, err)
|
||||
return err
|
||||
}
|
||||
commandCtx, cancel := manager.withTimeout(ctx, manager.longTimeout)
|
||||
commandCtx, cancel := manager.withTimeout(ctx, manager.commandTimeout)
|
||||
defer cancel()
|
||||
_, err = client.Execute(commandCtx, "AT+CFUN=1,1")
|
||||
if closeErr := client.Close(); err == nil {
|
||||
err = closeErr
|
||||
|
||||
// 1. Cycle SIM interface to minimum functionality / clear cached SIM files
|
||||
_, _ = client.Execute(commandCtx, "AT+CFUN=0")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
state.client = nil
|
||||
state.preFlightMode = nil
|
||||
|
||||
// 2. Restore radio to trigger fresh USIM file reading
|
||||
targetCFUN := "AT+CFUN=1"
|
||||
if state.snapshot != nil && state.snapshot.FlightMode {
|
||||
targetCFUN = "AT+CFUN=4"
|
||||
}
|
||||
_, err = client.Execute(commandCtx, targetCFUN)
|
||||
|
||||
manager.clearSnapshot(id, state)
|
||||
manager.setResult(id, state, nil, err)
|
||||
return err
|
||||
|
||||
@@ -56,7 +56,6 @@ type ReceivedSMS struct {
|
||||
RawTPDU string
|
||||
DecodeError string
|
||||
}
|
||||
|
||||
// ReceivedSMSStatus is network delivery evidence for one submitted SMS part.
|
||||
type ReceivedSMSStatus struct {
|
||||
DeviceID string
|
||||
|
||||
Reference in New Issue
Block a user