3. OTA升级通信协议选型与设计

OTA升级通信协议选型与设计 学习卡片

任务说明

在OTA升级流程中,版本号管理是防止回滚和重复安装的第一道防线。本案例实现一个轻量级版本号模块,支持:

  • 版本号解析与存储(格式:主版本.次版本.修订号,如 2.1.0)
  • 版本号比较逻辑,判定候选升级包是否高于当前已安装版本
  • 仅当候选版本严格高于当前版本时,返回允许升级

核心实现

# version_manager.py
from dataclasses import dataclass

@dataclass(frozen=True)
class Version:
    major: int
    minor: int
    patch: int

    @classmethod
    def from_string(cls, s: str) -> "Version":
        parts = s.strip().split(".")
        if len(parts) != 3:
            raise ValueError(f"版本号格式必须为 x.y.z,收到: {s}")
        try:
            major, minor, patch = (int(p) for p in parts)
        except ValueError:
            raise ValueError(f"版本号必须包含整数,收到: {s}")
        if major < 0 or minor < 0 or patch < 0:
            raise ValueError(f"版本号不允许负数: {s}")
        return cls(major=major, minor=minor, patch=patch)

    def __str__(self) -> str:
        return f"{self.major}.{self.minor}.{self.patch}"

    def __lt__(self, other: "Version") -> bool:
        return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

def is_upgrade_allowed(current: str, candidate: str) -> tuple[bool, str]:
    """
    判断候选升级包是否允许安装。
    返回 (是否允许, 原因说明)。
    """
    try:
        cur = Version.from_string(current)
        cand = Version.from_string(candidate)
    except ValueError as e:
        return False, str(e)

    if cand > cur:
        return True, f"允许升级: {candidate} > {current}"
    elif cand == cur:
        return False, f"拒绝: 候选版本 {candidate} 与当前版本相同,无需重复安装"
    else:
        return False, f"拒绝: 候选版本 {candidate} 低于当前版本 {current},禁止回滚"

# 最小调用入口
if __name__ == "__main__":
    test_cases = [
        ("1.0.0", "1.0.1"),   # 修订号升级,允许
        ("1.0.0", "1.1.0"),   # 次版本升级,允许
        ("1.0.0", "2.0.0"),   # 主版本升级,允许
        ("1.2.3", "1.2.3"),   # 相同版本,拒绝
        ("1.2.3", "1.2.2"),   # 降级,拒绝
        ("1.0.0", "1.0.0.1"), # 格式错误,拒绝
    ]
    for cur, cand in test_cases:
        allowed, reason = is_upgrade_allowed(cur, cand)
        print(f"当前={cur:<8} 候选={cand:<8} -> {'允许' if allowed else '拒绝'} | {reason}")

运行与验证

  1. 将上述代码保存为 version_manager.py,无需安装第三方依赖(仅使用Python标准库)。
  2. 在终端执行:python version_manager.py
  3. 预期输出(由代码逻辑直接推出):
当前=1.0.0    候选=1.0.1    -> 允许 | 允许升级: 1.0.1 > 1.0.0
当前=1.0.0    候选=1.1.0    -> 允许 | 允许升级: 1.1.0 > 1.0.0
当前=1.0.0    候选=2.0.0    -> 允许 | 允许升级: 2.0.0 > 1.0.0
当前=1.2.3    候选=1.2.3    -> 拒绝 | 拒绝: 候选版本 1.2.3 与当前版本相同,无需重复安装
当前=1.2.3    候选=1.2.2    -> 拒绝 | 拒绝: 候选版本 1.2.2 低于当前版本 1.2.3,禁止回滚
当前=1.0.0    候选=1.0.0.1  -> 拒绝 | 版本号格式必须为 x.y.z,收到: 1.0.0.1

通过判定标准:前3条输出为“允许”,后3条为“拒绝”,且拒绝原因分别对应“相同版本”“降级”“格式错误”三类场景,即视为验证通过。

集成说明

在实际OTA升级服务中,将当前已安装版本号从持久化存储(如配置文件或数据库)读出,候选版本号由升级包元数据解析获得,调用 is_upgrade_allowed() 后根据返回的布尔值决定是否继续后续的包校验与安装流程。本模块只负责版本判定,不涉及包体下载与刷写逻辑。