Skip to content

门卡

卡片数据结构

扇区8,头部信息(2+4+4+4+4+4+16=38字节):
+-----------+--------------+---------------+--------------+----------------+----------------+--------------------+
| 魔数(2字节)| 随机数(4字节) | 酒店ID(4字节) | 用户ID(4字节) | 开始时间(4字节) | 结束时间(4字节) | 门卡鉴权摘要(16字节) |
+-----------+--------------+---------------+--------------+----------------+----------------+--------------------+

扇区9-15,长度+钥匙串(每6字节一组)读到0为止:
+------------------[+-----------+--------------+-----------+]
| 长度uint16(2字节)|[类型(1字节),钥匙选项(1字节),权限ID(4字节)]
+------------------[+-----------+--------------+-----------+]
  1. 魔数固定为0xFC,代表酒店门卡结构,也就是本协议。
  2. 扇区号8、9-15,使用酒店写卡密钥写入,使用酒店读卡密钥读取,扇区写满继续写入下一个扇区。
  3. 钥匙串每6字节一组,每组包含类型、钥匙选项、权限ID。
  4. 时间有效期为0,代表永久有效。

门卡鉴权摘要计算

  1. 将卡片UID的7字节,连接头部22字节,再连接钥匙串数据,再加上酒店数据密码
  2. 对拼接后的数据进行MD5加密
  3. MD5加密结果的16字节即为门卡鉴权摘要

写卡规则

  1. 如果要写一张套房子间门卡,那么他需要写入6组钥匙【酒店钥匙,分区钥匙,楼栋钥匙,楼层钥匙,房间钥匙,子间钥匙】
  2. 如果要写一张房间房门卡,那么他需要写入5组钥匙【酒店钥匙,分区钥匙,楼栋钥匙,楼层钥匙,房间钥匙】
  3. 如果要写一张楼层电梯门卡,那么他需要写入4组钥匙【酒店钥匙,分区钥匙,楼栋钥匙,楼层钥匙】
  4. 如果要写一张楼栋单元门卡,那么他需要写入3组钥匙【酒店钥匙,分区钥匙,楼栋钥匙】
  5. 如果要写一张分区单元门卡,那么他需要写入2组钥匙【酒店钥匙,分区钥匙】
  6. 如果要写一张酒店大门卡,那么他需要写入1组钥匙【酒店钥匙】

注意上级的钥匙不要重复写入:

  1. 例如一张楼层卡,需要4组钥匙。如果同一个楼栋两个楼层的楼层卡,那么他只需要写入5组钥匙,而不是8组。
  2. 例如一张卡有同意房间的两个子间,那么他只需要写入的是7组钥匙,而不是12组。

开门规则

  • 可以这样去理解,一张ic卡就相当于一个钥匙串,一个钥匙串中有多把不同的钥匙,每一把钥匙去尝试能否打开这把锁。

读取到一组开门钥匙后:

  1. 如果是一组酒店钥匙,并且本锁是酒店锁,并且酒店id等于本锁的酒店id,开锁成功。
  2. 如果是一组分区钥匙,并且本锁是分区锁,并且分区id等于本锁的分区id,开锁成功。
  3. 如果是一组楼栋钥匙,并且本锁是楼栋锁,并且楼栋id等于本锁的楼栋id,开锁成功。
  4. 如果是一组楼层钥匙,并且本锁是楼层锁,并且楼层id等于本锁的楼层id,开锁成功。
  5. 如果是一组房间钥匙,并且本锁是房间锁,并且房间id等于本锁的房间id,开锁成功。
  6. 如果是一组子间钥匙,并且本锁是子间锁,并且子间id等于本锁的子间id,开锁成功。
  7. 如果是一组清空钥匙,恢复出厂设置。

伪代码:

python
头部数据 = 读取(扇区8, 038)
if 头部数据.魔数 != 0xFC:
    log("不是门卡")
    return
if 头部数据.酒店ID != 本锁.酒店ID:
    log("不是本酒店的门卡")
    return

// 计算摘要
鉴权摘要 = 头部数据[22:38]
钥匙串长度 = 读取(扇区9, 02)
钥匙串数据 = 读取(扇区9-15, 2, 钥匙串长度)
鉴权计算数据 = 卡片UID+头部数据[0:22]+钥匙串数据+酒店数据密码

if MD5(鉴权计算数据) != 鉴权摘要:
    log("鉴权失败,篡改卡或复制卡")
    return

// 判断有效期
if 头部数据.开始时间 != 0 and 头部数据.结束时间 != 0:
    if 头部数据.开始时间 > 当前时间:
        log("开始时间未到,不符合开锁条件")
        return
    if 头部数据.结束时间 < 当前时间:
        log("结束时间已过,不符合开锁条件")
        return

// 遍历钥匙串
for 钥匙 in 钥匙串:
   if 钥匙.类型 == 本锁.类型 and 钥匙.权限ID == 本锁.权限ID:
      // 精准开锁
      开锁()
      break
   if 钥匙.权限.递归下一级 and 钥匙.类型 == 本锁.类型.上一级类型 and 钥匙.权限ID != 本锁.权限ID.上一级权限:
      // 递归钥匙开锁:命中下一级
      开锁()
      break
   if 钥匙.权限.递归下二级 and 钥匙.类型 == 本锁.类型.上二级类型 and 钥匙.权限ID != 本锁.权限ID.上二级权限:
      // 递归钥匙开锁:命中下二级
      开锁()
      break
   if 钥匙.权限.递归下三级 and 钥匙.类型 == 本锁.类型.上三级类型 and 钥匙.权限ID != 本锁.权限ID.上三级权限:
      // 递归钥匙开锁:命中下三级
      开锁()
      break
   if 钥匙.权限.递归下四级 and 钥匙.类型 == 本锁.类型.上四级类型 and 钥匙.权限ID != 本锁.权限ID.上四级权限:
      // 递归钥匙开锁:命中下四级
      开锁()
      break
   if 钥匙.权限.递归下五级 and 钥匙.类型 == 本锁.类型.上五级类型 and 钥匙.权限ID != 本锁.权限ID.上五级权限:
      // 递归钥匙开锁:命中下五级
      // 例如本锁是子间,上1级是房间,上2级是楼层,上3级是楼栋,上4级是分区,上5级是酒店
      // 说明是酒店总裁卡开子间锁。
      开锁()
      break

开门日志

  • 日志一并存入蓝牙协议规定的门锁操作记录
  • 事件类型为酒店IC卡开锁:HOTEL_IC_UNLOCK
  • 事件的UserNo为0,Password是IC卡的用户ID的string形式

物联网设备通信协议文档