m2m-python/m2m/utils.py
2026-02-19 08:14:53 +01:00

94 lines
3.3 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Utilities for data encoding/decoding (PSM timers, SIM BCD, PLMN lists).
"""
from typing import List, Union, Optional
def encode_psm_timer(seconds: int, is_t3324: bool = False) -> str:
"""Encodes seconds into 3GPP binary string."""
if is_t3324:
if seconds <= 2 * 31: return f"000{seconds // 2:05b}"
if seconds <= 60 * 31: return f"001{seconds // 60:05b}"
if seconds <= 360 * 31: return f"010{seconds // 360:05b}"
return "11100000"
else:
if seconds <= 30 * 31: return f"100{seconds // 30:05b}"
if seconds <= 60 * 31: return f"101{seconds // 60:05b}"
if seconds <= 3600 * 31: return f"001{seconds // 3600:05b}"
return f"010{min(31, seconds // 36000):05b}"
def decode_plmn(bcd_data: bytes) -> Optional[str]:
"""Decodes a 3-byte BCD PLMN (MCC/MNC) from SIM storage."""
if len(bcd_data) < 3 or bcd_data == b'\xff\xff\xff':
return None
mcc = f"{(bcd_data[0] & 0x0F)}{(bcd_data[0] >> 4)}{(bcd_data[1] & 0x0F)}"
mnc_digit3 = (bcd_data[1] >> 4)
mnc = f"{(bcd_data[2] & 0x0F)}{(bcd_data[2] >> 4)}"
if mnc_digit3 != 0xF:
mnc = f"{(bcd_data[2] & 0x0F)}{(bcd_data[2] >> 4)}{mnc_digit3}"
return f"{mcc}-{mnc}"
def decode_act(act_bytes: bytes) -> str:
"""Decodes Access Technology bitmask (2 bytes)."""
if len(act_bytes) < 2: return "Unknown"
techs = []
b1, b2 = act_bytes[0], act_bytes[1]
if b1 & 0x80: techs.append("UTRAN")
if b1 & 0x40: techs.append("E-UTRAN")
if b1 & 0x20: techs.append("NG-RAN")
if b2 & 0x80: techs.append("GSM")
if b2 & 0x40: techs.append("cdma2000 HRPD")
if b2 & 0x20: techs.append("cdma2000 1xRTT")
return "/".join(techs) if techs else "None"
def decode_plmn_list(data: bytes, has_act: bool = False) -> List[str]:
"""Decodes a list of PLMNs, optionally with Access Technology."""
stride = 5 if has_act else 3
results = []
for i in range(0, len(data), stride):
chunk = data[i:i+stride]
plmn = decode_plmn(chunk[:3])
if plmn:
if has_act:
act = decode_act(chunk[3:5])
results.append(f"{plmn} [{act}]")
else:
results.append(plmn)
return results
def decode_iccid(bcd_data: bytes) -> str:
"""Decodes raw ICCID bytes (swapped nibbles)."""
return "".join([f"{(b & 0x0F)}{(b >> 4):X}" for b in bcd_data]).replace("F", "")
def bands_to_hex_mask(bands: List[int]) -> str:
"""Converts a list of band numbers into a hex bitmask string."""
mask = 0
for b in bands:
if b > 0:
mask |= (1 << (b - 1))
return f"{mask:X}"
def hex_mask_to_bands(hex_mask: str) -> List[int]:
"""Converts a hex bitmask string back into a list of band numbers."""
try:
mask = int(hex_mask, 16)
except (ValueError, TypeError):
return []
bands = []
for i in range(128): # Support up to 128 bands
if (mask >> i) & 1:
bands.append(i + 1)
return bands
def fmt_val(val: Union[str, List, None]) -> str:
"""Helper to format values for display in reports and shells."""
if val is None or val == "" or val == []:
return "None (Not set on SIM)"
if isinstance(val, list):
return ", ".join(val)
return str(val)