Skip to content

esim: implement profile switching and deletion#37779

Merged
adeebshihadeh merged 12 commits intocommaai:masterfrom
greatgitsby:lpa-switch-delete
Apr 11, 2026
Merged

esim: implement profile switching and deletion#37779
adeebshihadeh merged 12 commits intocommaai:masterfrom
greatgitsby:lpa-switch-delete

Conversation

@greatgitsby
Copy link
Copy Markdown
Contributor

@greatgitsby greatgitsby commented Apr 8, 2026

Summary

  • switch_profile(): EnableProfile TLV command with refreshFlag support (tizi uses SIM toolkit refresh, mici uses CFUN cycle)
  • delete_profile(): DeleteProfile TLV command with comma profile guard (is_comma_profile check)
  • catBusy (0x05) retry: lte.sh modem reset and re-enable on CAT busy state
  • PROFILE_ERROR_CODES, TAG_ENABLE_PROFILE, TAG_DELETE_PROFILE constants

Stress test results (15 switches each, 10s between)

tizi (EG25) — 15/15 passed

 1: -> mm2    0.062s OK       9: -> mm2    0.052s OK
 2: -> mm1    0.047s OK      10: -> mm1    0.051s OK
 3: -> mm2    0.051s OK      11: -> mm2    0.045s OK
 4: -> mm1    0.049s OK      12: -> mm1    0.053s OK
 5: -> mm2    0.049s OK      13: -> mm2    0.045s OK
 6: -> mm1    0.053s OK      14: -> mm1    0.052s OK
 7: -> mm2    0.050s OK      15: -> mm2    0.047s OK
 8: -> mm1    0.054s OK

avg=0.051s, min=0.045s, max=0.062s

mici (EG916Q) — 15/15 passed

 1: -> mobi1  0.057s OK       9: -> mobi1  0.095s OK
 2: -> mobi2  0.063s OK      10: -> mobi2  0.051s OK
 3: -> mobi1  0.055s OK      11: -> mobi1  0.049s OK
 4: -> mobi2  0.167s OK      12: -> mobi2  0.118s OK
 5: -> mobi1  0.061s OK      13: -> mobi1  0.063s OK
 6: -> mobi2  0.054s OK      14: -> mobi2  0.077s OK
 7: -> mobi1  0.057s OK      15: -> mobi1  0.054s OK
 8: -> mobi2  0.049s OK

avg=0.071s, min=0.049s, max=0.167s

Sample test code

import time
from openpilot.system.hardware.tici.lpa import TiciLPA

lpa = TiciLPA()
profiles = lpa.list_profiles()
target = [p for p in profiles if not p.enabled and "mobi" in p.nickname][0]

t0 = time.monotonic()
lpa.switch_profile(target.iccid)
print(f"switched to {target.nickname} in {time.monotonic() - t0:.3f}s")

# modem resettles in background (AT+CFUN=1,1 on mici, SIM REFRESH on tizi)
# next list_profiles will show updated state once modem is back

@greatgitsby greatgitsby marked this pull request as draft April 8, 2026 02:36
greatgitsby and others added 2 commits April 7, 2026 19:37
lte.sh already blocks until the modem is online, so no post-reset sleep needed.
open_isdr and send_apdu retry loops recover without delays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add TLV encoding utilities: encode_tlv, string_to_tbcd, require_tag,
  int_bytes for constructing ES10x commands
- Generalize _decode_profile_fields into decode_struct with FieldMap type
  for reuse across profile, notification, and other TLV structures
- Add set_profile_nickname function (ES10c SetNickname command)
- TiciLPA: remove singleton pattern, add _acquire_channel context manager
  with filesystem lock and ModemManager inhibit for exclusive modem access
- Wrap list_profiles and nickname_profile with _acquire_channel
- Add base64_trim, b64d helpers for downstream ES9P/download use

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greatgitsby greatgitsby force-pushed the lpa-switch-delete branch 6 times, most recently from f26dfd1 to 6fc4cb0 Compare April 8, 2026 03:03
- TiciLPA.switch_profile: enable profile with device-specific refresh flag
  (tizi uses SIM presence refresh, mici uses CFUN cycle), catBusy retry
  with lte.sh modem reset, post-switch modem recovery
- TiciLPA.delete_profile: encode DeleteProfile TLV command with comma
  profile guard (refuses to delete Webbing/comma profiles)
- TiciLPA._enable_profile: send ES10c EnableProfile command
- TiciLPA._has_sim_presence: detect tizi vs mici for refresh strategy
- hardware.py: use AT+CFUN=1,1 for modem reboot (enables network
  registration after power-on)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greatgitsby greatgitsby force-pushed the lpa-switch-delete branch 3 times, most recently from b3ce36d to 3415e3e Compare April 9, 2026 15:30
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

Process replay diff report

Replays driving segments through this PR and compares the behavior to master.
Please review any changes carefully to ensure they are expected.

✅ 0 changed, 66 passed, 0 errors

@greatgitsby greatgitsby force-pushed the lpa-switch-delete branch 6 times, most recently from 3fb484c to 4004370 Compare April 9, 2026 16:06
- Consolidate modem reset into AtClient._reset_modem(), used by both reset() and open_isdr()
- Add AtClient.send_raw() and flush_input() to avoid private _serial access
- Use HARDWARE.get_device_type() instead of importing tici module directly
- Extract PROFILE_OK, PROFILE_NOT_IN_DISABLED_STATE, PROFILE_CAT_BUSY constants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
greatgitsby and others added 6 commits April 9, 2026 09:50
Both mici and tizi now do a CFUN reset after EnableProfile to force
a SIM re-read, ensuring list_profiles returns fresh data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The EG25 (tizi) doesn't recover from CFUN alone after a profile
switch — the SIM never comes back. Setting refreshFlag=1 tells the
eUICC to issue a proactive SIM REFRESH, which the modem handles
natively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mici (EG916Q) doesn't update ModemManager after refreshFlag=1 —
mmcli shows stale ICCID. Add a CFUN cycle (using proper query()
instead of send_raw) for mici only. tizi (EG25) handles refreshFlag
natively via SIM REFRESH.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AT+CFUN=0 drops the serial connection before OK is sent, so query()
times out. Use send_raw like before, with a 0.5s settle and flush.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sending both CFUN commands in one send_raw was unreliable — modem
sometimes processes them too fast without a full radio cycle. Split
into two sends with 0.5s gap so CFUN=0 completes before CFUN=1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tizi (EG25): refreshFlag=1 triggers native SIM REFRESH, 0.2s settle.
mici (EG916Q): refreshFlag=0 + CFUN cycle via send_raw, 0.5s settle.
This matches the configuration that was previously tested reliable on
both devices.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greatgitsby greatgitsby force-pushed the lpa-switch-delete branch 2 times, most recently from 6578bb6 to 79b2050 Compare April 11, 2026 02:40
@greatgitsby greatgitsby force-pushed the lpa-switch-delete branch 16 times, most recently from 4664366 to 39097fc Compare April 11, 2026 03:44
Instead of device-specific sleep values, use refreshFlag=0 + CFUN
on all devices and poll list_profiles until the target profile shows
as enabled. Timeout after 15s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greatgitsby greatgitsby marked this pull request as ready for review April 11, 2026 04:06
@adeebshihadeh adeebshihadeh merged commit b930f5c into commaai:master Apr 11, 2026
10 checks passed
@greatgitsby greatgitsby deleted the lpa-switch-delete branch April 11, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants