跳转至

酒店外设驱动的一致性门禁:每个驱动自带 simulator 与 CTS

酒店 PMS 要直控的外设五花八门:身份证读卡器、发卡器、门锁网关、小票打印机、RF 灯光控制器、UPS。每家厂商的 SDK/协议都不一样,而研发团队手里往往只有一两台真机。如果驱动的合入质量靠"到店联调时再说",现场交付就会变成灾难。

我们的做法是把「每个驱动必须自带模拟器 + 一致性测试套件(CTS)」设为 CI 硬门禁。

驱动抽象

所有外设收敛到一个小接口:

type Driver interface {
    Open(cfg Config) error
    Close() error
    Health() Status
    Read(req Request) (Response, error)
    Write(req Request) (Response, error)
    Describe() string
}

type FaultInjector interface {
    SetFault(f Fault) // 无卡/超时/离线/写失败/忙
}

要点有三个:

  1. 用 Read/Write + Op 语义而不是每设备一套方法,上层业务模块才能对驱动做统一编排;
  2. FaultInjector 是一等公民:离线、超时、无卡、写失败都要能按需注入;
  3. Describe() 输出契约摘要,设备监控页直接渲染,现场排障不用翻文档。

CTS:同一套用例跑模拟器与真机

每个驱动交付时附带 CTS(Conformance Test Suite):开设备、健康检查、正常读写、各类故障注入下的错误语义、并发访问。CTS 先在模拟器上跑绿;到了有真机的环境(HIL 台位),同一套用例指向真实驱动再跑一遍,结果记入台账。

这带来两个直接收益:

  • CI 里永远有完整的外设测试,不依赖真机在不在工位上;
  • 真机替换模拟器时业务代码零改动——模拟器必须实现与真机相同的错误语义(ErrNoCard、ErrTimeout、ErrOffline……),这是抽象质量的试金石。

实战里最有价值的两类用例

错误语义归一。业务层不该理解每家 SDK 的错误码,驱动层把"设备未连接""无卡在位""读写超时"归一成统一错误,再由业务层转成前台话术。这类用例在模拟器上注入故障即可覆盖,把现场"设备连不上"类工单的定位时间从小时级压到分钟级。

操作留痕与幂等。发卡、销卡这类动作必须有操作历史(卡操作时间线),且重试不能重复写卡。用例会故意重复调用并断言记录数——这类"看起来不可能出错"的幂等细节,恰恰是最常被漏测的。

门禁的边界

门禁不能替代真机验证:协议细节(波特率、指令时序、固件差异)只有真机能暴露。所以流程是「模拟器 CTS 全绿才允许合入,真机 HIL 用例补台账后才算交付」。工具链管住可以管住的,剩下的用流程清单兜底。