#pragma once #include "ademco_packet.h" #include <assert.h> #include <stdint.h> #include <string.h> #include <vector> #include <string> #include <type_traits> #include <iterator> namespace hb { typedef uint8_t Char; typedef std::vector<Char> CharVector; // checksum //! only allow len<=255 inline Char sum(Char* arr, Char len) { auto& sum_ = arr[len - 1]; sum_ = 0; for (Char i = 0; i < len - 1; i++) { sum_ = static_cast<Char>((sum_ + arr[i]) & 0xFFU); } return sum_; } inline Char sum(CharVector& vec) { return sum(vec.data(), static_cast<Char>(vec.size() & 0xFF)); } template <typename T, typename = int> struct T_has_data_and_len : std::false_type {}; template <typename T> struct T_has_data_and_len<T, decltype((void)T::data, (void)T::len, 0)> : std::true_type {}; template <class T> inline typename std::enable_if<T_has_data_and_len<T>::value, Char>::type sum(T& t) { return sum(t.data, t.len); } template <class T> inline typename std::enable_if<T_has_data_and_len<T>::value, Char>::type sum(T* t) { return sum<T>(*t); } //! now most machine use this proto namespace common { //! 主机状态码 enum class MachineStatus : Char { //! 布防 Arm = 0, //! 半布防 HalfArm = 1, //! 撤防 Disarm = 2, //! 设置 Setting = 3, MachineStatusCount, InvalidMachineStatus = 0xFF, }; static MachineStatus machineStatusFromChar(Char s) { if (static_cast<Char>(MachineStatus::Arm) <= s && s < static_cast<Char>(MachineStatus::MachineStatusCount)) { return static_cast<MachineStatus>(s); } return MachineStatus::InvalidMachineStatus; } #ifdef ENABLE_COMMON_MACHINE_STATUS_TO_STRING static const char* machineStatusToString(MachineStatus status) { switch (status) { case MachineStatus::Arm: return "Arm"; case MachineStatus::HalfArm:return "HalfArm"; case MachineStatus::Disarm: return "Disarm"; case MachineStatus::Setting:return "Setting"; default: return "Unknown MachineStatus"; } } static const wchar_t* machineStatusToWString(MachineStatus status) { switch (status) { case MachineStatus::Arm: return L"布防"; case MachineStatus::HalfArm:return L"半布防"; case MachineStatus::Disarm: return L"撤防"; case MachineStatus::Setting:return L"设置"; default: return L"未知主机状态"; } } #endif // ENABLE_COMMON_MACHINE_STATUS_TO_STRING //! 主机类型 enum MachineType : Char { //! WiFi主机 (已停用) WiFi = 0, //! 网络摄像机 (未启用) Camera = 1, //! 物联卡主机 [HB-5050G HB-5050-4G] Gprs_IoT = 2, //! 网络模块主机 [ HB-G250 ] NetMod = 3, //! 改进型卧式主机 [ HB-4040G HB-5050G HB-5050-4G ] Gprs = 4, //! 液晶主机 [ HB-BJQ-560B ] Lcd = 5, //! 网线主机 [ HB-4040R HB-5050R ] Wired = 6, //! 真彩主机 [ HTZ-G1000-2G HTZ-G1000-4G ] TrueColor = 7, //! 三区段主机 [ HTZ-G1000-2G HTZ-G1000-4G ] ThreeSection = 8, //! 物联卡主机 [ HB-5050-4GW ] IoT = 9, //! GPRS主机可以打电话 [ HB-2050-2G ] 防区 [1-60] Gprs_Phone = 10, MachineTypeCount, InvalidMachineType = 0xFF, }; static MachineType machineTypeFromChar(Char t) { if (static_cast<Char>(MachineType::WiFi) <= t && t < static_cast<Char>(MachineType::MachineTypeCount)) { return static_cast<MachineType>(t); } return MachineType::InvalidMachineType; } static constexpr MachineType AllMachineTypes[MachineTypeCount] = { WiFi, Camera, Gprs_IoT, NetMod, Gprs, Lcd, Wired, TrueColor, ThreeSection, IoT, Gprs_Phone, }; //! 最大防区号根据型号不同而不同 static uint16_t zoneMax(MachineType type) { switch (type) { case MachineType::Gprs_IoT: case MachineType::Gprs: case MachineType::Wired: return 99; case MachineType::NetMod: return 999; case MachineType::Lcd: return 249; case MachineType::TrueColor: return 68; case MachineType::ThreeSection: return 191; case MachineType::IoT: case MachineType::Gprs_Phone: return 60; case MachineType::WiFi: case MachineType::Camera: default: return 0; break; } } //! 防区号是否合法(可以包含0防区) static bool isValidZone(MachineType type, uint16_t zone) { return ademco::Zone4MachineSelf <= zone && zone <= zoneMax(type); } //! 防区号是否合法(不可以可以包含0防区) static bool isValidZoneStrict(MachineType type, uint16_t zone) { return ademco::ZoneMin <= zone && zone <= zoneMax(type); } //! 主机是否具有半布防功能 static bool machineCanHalfArm(MachineType type) { return type == MachineType::NetMod || type == MachineType::Lcd ; } //! 主机是否可以报告信号强度 static bool machineCanReportSignalStrength(MachineType type) { return type == MachineType::Gprs || type == MachineType::Gprs_IoT || type == MachineType::IoT || type == MachineType::Lcd || type == MachineType::TrueColor || type == MachineType::ThreeSection || type == MachineType::Gprs_Phone ; } //! 主机本身是否可以短信报警(不算通过服务如阿里语音等) static bool machineCanReportBySMS(MachineType type) { return type == MachineType::Gprs || type == MachineType::Lcd || type == MachineType::TrueColor || type == MachineType::ThreeSection || type == MachineType::Gprs_Phone ; } //! 主机是否已投产使用 static bool machineIsSelling(MachineType type) { return type == MachineType::NetMod || type == MachineType::Gprs || type == MachineType::Gprs_IoT || type == MachineType::IoT || type == MachineType::Lcd || type == MachineType::Wired || type == MachineType::TrueColor || type == MachineType::ThreeSection || type == MachineType::Gprs_Phone ; } //! 主机是否支持有线防区 static bool machineHasWiredZone(MachineType type) { return type == MachineType::NetMod || type == MachineType::TrueColor || type == MachineType::ThreeSection; } //! 主机最小有线防区号 static ademco::AdemcoZone wiredZoneMin(MachineType type) { switch (type) { case hb::common::NetMod: return 1; case hb::common::Gprs: break; case hb::common::Lcd: break; case hb::common::Wired: break; case hb::common::TrueColor: return 1; break; case hb::common::ThreeSection: return 61; break; case hb::common::IoT: break; case hb::common::Gprs_Phone: break; case hb::common::MachineTypeCount: break; case hb::common::InvalidMachineType: break; default: break; } return 0; } //! 主机最大有线防区号 static ademco::AdemcoZone wiredZoneMax(MachineType type) { switch (type) { case hb::common::Gprs_IoT: break; case hb::common::NetMod: return 8; break; case hb::common::Gprs: break; case hb::common::Lcd: break; case hb::common::Wired: break; case hb::common::TrueColor:return 8; break; case hb::common::ThreeSection:return 68; break; case hb::common::IoT: break; case hb::common::Gprs_Phone: break; case hb::common::MachineTypeCount: break; case hb::common::InvalidMachineType: break; default: break; } return 0; } //! 主机是否可以直接写入防区数据(无需对码) static bool machineCanDirectlyWriteZone(MachineType type) { return type == MachineType::NetMod; } //! 主机是否可以挂载分机 static bool machineCanLinkSubMachine(MachineType type) { return type == MachineType::NetMod; } #ifdef ENABLE_COMMON_MACHINE_TYPE_TO_STRING static const char* machineTypeToString(MachineType type) { switch (type) { case MachineType::WiFi: return "0 WiFi"; case MachineType::Camera: return "1 Camera"; case MachineType::Gprs_IoT: return "2 Gprs_IoT"; case MachineType::NetMod: return "3 NetMod"; case MachineType::Gprs: return "4 GPRS"; case MachineType::Lcd: return "5 LCD"; case MachineType::Wired: return "6 Wired"; case MachineType::TrueColor: return "7 TrueColor"; case MachineType::ThreeSection: return "8 ThreeSection"; case MachineType::IoT: return "9 IoT"; case MachineType::Gprs_Phone: return "10 Gprs_Phone"; default: return "Unknown MachineType"; } } static const wchar_t* machineTypeToWString(MachineType type) { switch (type) { case MachineType::WiFi: return L"0 WiFi主机"; case MachineType::Camera: return L"1 摄像头主机"; case MachineType::Gprs_IoT: return L"2 简化的物联卡主机"; case MachineType::NetMod: return L"3 网络模块+工程主机"; case MachineType::Gprs: return L"4 GPRS主机"; case MachineType::Lcd: return L"5 液晶主机"; case MachineType::Wired: return L"6 网线主机"; case MachineType::TrueColor: return L"7 真彩主机"; case MachineType::ThreeSection: return L"8 三区段主机"; case MachineType::IoT: return L"9 物联卡主机"; case MachineType::Gprs_Phone: return L"10 GPRS主机能打电话"; default: return L"未知主机"; } } #endif // ENABLE_COMMON_MACHINE_TYPE_TO_STRING //! 防区属性 enum ZoneProperty : Char { //! 匪警全局 Buglar = 0x00, //! 匪警紧急 Emergency = 0x01, //! 火警防区 Fire = 0x02, //! 胁迫防区 Duress = 0x03, //! 燃气防区 Gas = 0x04, //! 淹水防区 Water = 0x05, //! 分机 SubMachine = 0x06, //! 遥控器 RemoteControl = 0x07, //! 匪警半局 BuglarHalf = 0x08, //! 屏蔽防区 Shield = 0x09, //! 门铃防区 DoorRing = 0x0A, //! 保留 Reserved0B = 0x0B, //! 保留 Reserved0C = 0x0C, //! 保留 Reserved0D = 0x0D, //! 保留 Reserved0E = 0x0E, //! 旁路防区 //! 2019年9月2日16:01:58 适配金建峰真彩主机 Bypass = 0x0F, ZonePropertyCount, InvalidZoneProperty = 0xFF, }; static ZoneProperty zonePropertyFromChar(Char zp) { if (ZoneProperty::Buglar <= zp && zp < ZoneProperty::ZonePropertyCount) { return static_cast<ZoneProperty>(zp); } return ZoneProperty::InvalidZoneProperty; } static std::vector<ZoneProperty> getAvailableZoneProperties(MachineType type) { switch (type) { case NetMod: return { Buglar, Emergency, Fire, Duress, Gas, Water, SubMachine, RemoteControl, BuglarHalf, Shield, DoorRing }; case Gprs: return { Buglar, Emergency, Fire, Duress, Gas, Water, }; case Lcd: return { Buglar, Emergency, Fire, Duress, Gas, Water, SubMachine, RemoteControl, BuglarHalf, Shield, DoorRing }; case Wired: return { Buglar, Emergency, Fire, Duress, Gas, Water, }; case TrueColor: return { Buglar, Emergency, Fire, Duress, Gas, Water, RemoteControl, Shield, DoorRing, Bypass }; case ThreeSection:return { Buglar, Emergency, Fire, Duress, Gas, Water, RemoteControl, Shield, DoorRing, Bypass }; default: return {}; } } #ifdef ENABLE_COMMON_ZONE_PROPERTY_TO_STRING static const wchar_t* zonePropertyToStringChinese(ZoneProperty zp) { switch (zp) { case ZoneProperty::Buglar: return L"匪警全局"; case ZoneProperty::Emergency: return L"匪警紧急"; case ZoneProperty::Fire: return L"火警防区"; case ZoneProperty::Duress: return L"胁迫防区"; case ZoneProperty::Gas: return L"燃气防区"; case ZoneProperty::Water: return L"淹水防区"; case ZoneProperty::SubMachine: return L"分机"; case ZoneProperty::RemoteControl: return L"遥控器"; case ZoneProperty::BuglarHalf: return L"匪警半局"; case ZoneProperty::Shield: return L"屏蔽防区"; case ZoneProperty::DoorRing: return L"门铃防区"; case ZoneProperty::Reserved0B: return L"保留"; case ZoneProperty::Reserved0C: return L"保留"; case ZoneProperty::Reserved0D: return L"保留"; case ZoneProperty::Reserved0E: return L"保留"; case ZoneProperty::Bypass: return L"旁路防区"; case ZoneProperty::InvalidZoneProperty: default: return L"无效属性"; } } static const char* zonePropertyToStringEn(ZoneProperty zp) { switch (zp) { case ZoneProperty::Buglar: return "Buglar"; case ZoneProperty::Emergency: return "Emergency"; case ZoneProperty::Fire: return "Fire"; case ZoneProperty::Duress: return "Duress"; case ZoneProperty::Gas: return "Gas"; case ZoneProperty::Water: return "Water"; case ZoneProperty::SubMachine: return "SubMachine"; case ZoneProperty::RemoteControl: return "RemoteControl"; case ZoneProperty::BuglarHalf: return "BuglarHalf"; case ZoneProperty::Shield: return "Shield"; case ZoneProperty::DoorRing: return "DoorRing"; case ZoneProperty::Reserved0B: return "Reserved0B"; case ZoneProperty::Reserved0C: return "Reserved0C"; case ZoneProperty::Reserved0D: return "Reserved0D"; case ZoneProperty::Reserved0E: return "Reserved0E"; case ZoneProperty::Bypass: return "Bypass"; case ZoneProperty::InvalidZoneProperty: default: return "InvalidZoneProperty"; } } #endif // ENABLE_COMMON_ZONE_PROPERTY_TO_STRING /** * @brief 串口透传协议 * @note 网络传输模块与主机模块通信协议 * @note 客户端(手机APP、接警中心等)会用到一些透传数据 */ namespace com { //! 读取主机账号 struct ReadMachineAcctRequest { static constexpr Char len = 7; static constexpr Char data[len] = { 0xEB, 0xBA, 0x3F, 0x07, 0x00, 0x4C, 0x37 }; }; //! 读取主机账号回应 struct ReadMachineAcctResponse { static constexpr Char len = 15; static constexpr Char fixHeader[5] = { 0xEB, 0xCB, 0x3F, 0x0F, 0x4C }; Char data[len] = { 0 }; bool parseAcct(std::string& acct) { if (memcmp(data, fixHeader, sizeof(fixHeader))) { return false; } ReadMachineAcctResponse tmp; memcpy(tmp.data, data, len - 1); sum(tmp); if (tmp.data[len - 1] != data[len - 1]) { return false; } char buff[32] = { 0 }; acct = ademco::detail::HexCharArrayToStr(buff, (const char*)data + 5, 9); return true; } }; //! 写入主机账号 struct WriteMachineAcctRequest { static constexpr Char len = 15; Char data[len] = { 0xEB, 0xCB, 0x3F, 0x0F, 0x4D }; //! maker, max acct len is 18 void make(const std::string& acct) { ademco::detail::NumStr2HexCharArray_N(acct.data(), (char*)data + 5, 9); sum(this); } }; //! 写入主机账号回应(与读取主机账号命令相同) typedef ReadMachineAcctRequest WriteMachineAcctResponse; //! 三区段主机索要主机状态 EB CB 3F 06 B0 AB struct MachineStatusRequest3Section { static constexpr const Char len = 6; static constexpr const Char data[len] = { 0xEB, 0xCB, 0x3F, 0x06, 0xB0, 0xAB }; }; typedef MachineStatusRequest3Section Req_B0; //! 三区段主机状态回应 EB BA 3F 08 P0 B1 P1 SUM struct MachineStatusResponse3Section { static constexpr Char len = 8; union { struct Cmd { Char head[6]; // 0 撤防,1 布防 Char status3 : 2; Char status2 : 2; Char status1 : 2; Char status : 2; Char sum; }; Char data[len]; Cmd cmd; }data = {}; bool check() { Char head[4] = { 0xEB, 0xBA, 0x3F, 0x08 }; Char s = data.data[len - 1]; return memcmp(data.cmd.head, head, 4) == 0 && data.cmd.head[5] == 0xB1 && sum(data.data, len) == s; } void make(MachineStatus status1, MachineStatus status2, MachineStatus status3, MachineStatus status = MachineStatus::Arm) { Char head[6] = { 0xEB, 0xBA, 0x3F, 0x08, 0xCC, 0xB1 }; memcpy(data.cmd.head, head, 6); data.cmd.status = (Char)status & 0x3; data.cmd.status1 = (Char)status1 & 0x3; data.cmd.status2 = (Char)status2 & 0x3; data.cmd.status3 = (Char)status3 & 0x3; sum(data.data, len); } }; static_assert(sizeof(MachineStatusResponse3Section) == 8, "sizeof(MachineStatusResponse3Section) must be 8"); typedef MachineStatusResponse3Section Resp_B1; //! 索要主机状态 EB AB 3F A0 75 struct MachineStatusRequest { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xA0, 0x75 }; }; typedef MachineStatusRequest Req_A0; //! 回应主机状态 EB BA 3F 07 P0 A0 P1 P2 P3 SUM struct MachineStatusResponse { static constexpr Char len = 9; // 0 1 2 3 4 P0 5 6.status 7.type 8.sum Char data[len] = { 0xEB, 0xBA, 0x3F, 0x07, 0xCC, 0xA0, 0x00, 0x00, 0x00 }; MachineStatus status = MachineStatus::Arm; // status; MachineType type = MachineType::WiFi; // type MachineStatusResponse& make(MachineStatus status_, MachineType type_) { status = status_; type = type_; return make(); } MachineStatusResponse& make() { data[6] = (Char)status; data[7] = type; sum(this); return *this; } }; typedef MachineStatusResponse Resp_A0; //! 索要主机防区 EB AB 3F A1 76 struct ZoneRequest { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xA1, 0x76 }; }; typedef ZoneRequest Req_A1; //! 回应主机防区 EB BA 3F PN P0 A2 [Z, P]xN P1 SUM struct ZoneResponse { static constexpr Char min_len = 8; // 无防区数据时长度最小为8 static constexpr Char max_zone = 20; // 最多可以包含 20 个防区 static constexpr Char max_len = min_len + max_zone * 2; // 一包数据最多有8+20*2=48个字节 /* * when param is not 0xFF, means there's more zone coming; vice versa * zone&prop can be placed as much as 20 times */ Char data[max_len] = {}; Char len = min_len; // init as minimum len // default ctor, no zone exists ZoneResponse() { Char nodata[min_len] = { 0xEB, 0xBA, 0x3F, 0x08, 0xCC, 0xA2, 0xFF, 0x00 }; sum(nodata, min_len); memcpy(data, nodata, min_len); len = min_len; } /* * @brief maker, add zone to data and (if this packet isOver) calc sum * @note first time zone&prop will be placed in 6&7, then second zone&prop will be in 8&9, and so on * @note but the max number of zone&prop you can add is 20 * @note len is automatically added (with 2) everytime except for first time * @note if hasMoreZone, param will be set to 0, else 0xFF */ ZoneResponse& addZone(Char zone, ZoneProperty prop, bool isOver = true, bool hasMoreZone = false) { if (len == max_len) { return *this; } Char pos = static_cast<Char>(len - 2); data[pos] = zone; data[pos + 1] = prop; len = static_cast<Char>(len + 2); data[3] = len; data[len - 2] = hasMoreZone ? 0x00 : 0xFF; if (isOver) { sum(this); } return *this; } struct ZoneAndProperty { Char zone = 0; ZoneProperty prop = ZoneProperty::InvalidZoneProperty; }; typedef std::vector<ZoneAndProperty> ZoneAndProperties; bool assign(const char* in_data, Char in_len) { if (in_len > max_len) { return false; } memcpy(data, in_data, in_len); len = in_len; return true; } //! parser, make sure to call ResponseParser::parse first and return is A2_response to check data valid, then call assign. bool parse(ZoneAndProperties& zps, bool& hasMore) { if (len < min_len || data[3] != len) { return false; } // check valid again Char sum_ = data[len - 1]; sum(data, len); if (sum_ != data[len - 1]) { return false; } // check sum again Char count = Char((len - min_len) >> 1); // zone/prop pairs if (count == 0) { zps.clear(); hasMore = false; return true; } for (Char i = 0; i < count; i++) { ZoneAndProperty zp; zp.zone = data[6 + i * 2]; zp.prop = zonePropertyFromChar(data[7 + i * 2]); if (zp.prop != ZoneProperty::InvalidZoneProperty) { zps.emplace_back(zp); } } hasMore = data[len - 2] != 0xFF; return true; } }; typedef ZoneResponse Resp_A2; //! 索要更多主机防区 EB AB 3F A2 77 //! 仅应在收到ZoneResponse的param非0xFF时发送,以索要全部防区 struct ZoneRequestMore { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xA2, 0x77 }; }; typedef ZoneRequestMore Req_A2; //! 修改主机防区 EB CB 3F 09 A3 P1 P2 P3 SUM struct ZoneOpRequest { static constexpr Char len = 9; //! 修改主机的操作码 enum ZoneOpCode : Char { //! 删除防区 Delete = 0x00, //! 学码 Learn = 0x01, //! 停止学码 Stop = 0x03, //! 修改属性 Change = 0x04, ZoneOpCodeCount, }; // 0 1 2 3 4 5.zone 6.prop 7.op 8.sum Char data[len] = { 0xEB, 0xCB, 0x3F, 0x09, 0xA3, 0x00, 0x00, 0x00, 0x00 }; ZoneOpRequest& make(Char zone, ZoneProperty prop, ZoneOpCode op) { data[5] = zone; data[6] = prop; data[7] = op; sum(this); return *this; } }; typedef ZoneOpRequest Req_A3; //! 学码开始回应 EB BA 3F 07 P0 A3 5A //! 因为学码时主机要等待外部无线信号(用户触发探测器),因此先回应A3表示已经开始学码,学码成功时回 ZoneOpResponse A4 struct ZoneOpLearningResponse { static constexpr Char len = 7; Char data[len] = { 0xEB, 0xBA, 0x3F, 0x07, 0xCC, 0xA3, 0x5A }; }; typedef ZoneOpLearningResponse Resp_A3; //! 修改防区回应 EB BA 3F 0A P0 A4 P1 P2 P3 SUM struct ZoneOpResponse { static constexpr Char len = 10; //! 防区操作回应码 enum ZoneOpRespCode : Char { //! 失败 Failed = 0x00, //! 成功 Succeed = 0x01, //! 失败--重码 Duplicate = 0x02, //! 失败--空防区 NoSuchZone = 0x03, ZoneOpRespCodeCount, }; // 0 1 2 3 4 5 zone prop code sum Char data[len] = { 0xEB, 0xBA, 0x3F, 0x0A, 0xCC, 0xA4, 0x00, 0x00, 0x00, 0x00 }; ZoneOpResponse& make(Char zone, ZoneProperty prop, ZoneOpRespCode code) { data[6] = zone; data[7] = prop; data[8] = code; sum(this); return *this; } }; typedef ZoneOpResponse Resp_A4; struct Timepoint { Char hour = 0xFF, minute = 0xFF; }; struct Timer { Timepoint armAt = {}, disarmAt = {}; }; //! 主机定时器,一共2组 union MachineTimer { Timer timer[2]; // 8byte Char data[8]; MachineTimer() { memset(data, 0xFF, 8); } }; static_assert(sizeof(MachineTimer) == 8, "sizeof(MachineTimer) must be 8"); static inline bool isValidMachineTimer(const Timer& t) { return 0 <= t.armAt.hour && t.armAt.hour < 24 && 0 <= t.armAt.minute && t.armAt.minute < 60 && 0 <= t.disarmAt.hour && t.disarmAt.hour < 24 && 0 <= t.disarmAt.minute && t.disarmAt.minute < 60; } //! 获取主机定时器 EB AB 3F A5 7A struct MachineTimerRequest { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xA5, 0x7A }; }; typedef MachineTimerRequest Req_A5; //! 主机定时器回应 EB BA 3F 0F P0 A6 H1 M1 H2 M2 H3 M3 H4 M4 SUM struct MachineTimerResponse { static constexpr Char len = 7 + sizeof(MachineTimer); // 15 // 0 1 2 3 4 5 a1ah a1am a1dh a1dm a2ah a2am a2dh a2dm sum Char data[len] = { 0xEB, 0xBA, 0x3F, 0x0F, 0xCC, 0xA6, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00 }; MachineTimer getTimer() const { MachineTimer t; memcpy(&t.data, &data[6], sizeof(MachineTimer)); return t; } void setTimer(const MachineTimer& t) { memcpy(&data[6], &t.data, sizeof(MachineTimer)); sum(this); } }; typedef MachineTimerResponse Resp_A6; //! 设置主机定时器 EB CB 3F 0E A7 H1 M1 H2 M2 H3 M3 H4 M4 SUM struct SetMachineTimerRequest { static constexpr Char len = 6 + sizeof(MachineTimer); // 14 // 0 1 2 3 4 a1ah a1am a1dh a1dm a2ah a2am a2dh a2dm sum Char data[len] = { 0xEB, 0xCB, 0x3F, 0x0E, 0xA7, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00 }; MachineTimer getTimer() const { MachineTimer t; memcpy(&t.data, &data[5], sizeof(MachineTimer)); return t; } void setTimer(const MachineTimer& t) { memcpy(&data[5], &t, sizeof(MachineTimer)); sum(this); } }; typedef SetMachineTimerRequest Req_A7; //! 设置主机定时器回应 EB BA 3F 07 P0 A7 SUM struct SetMachineTimerResponse { static constexpr Char len = 7; Char data[len] = { 0xEB, 0xBA, 0x3F, 0x07, 0xCC, 0xA7, 0x5E }; }; typedef SetMachineTimerResponse Resp_A7; //! 拒绝设置回应 EB BA 3F 07 P0 A8 SUM //! 任何操作,主机如果认为非法,都可以用A8直接回复 struct RejectResponse { static constexpr Char len = 7; Char data[len] = { 0xEB, 0xBA, 0x3F, 0x07, 0xCC, 0xA8, 0x5F }; }; typedef RejectResponse Resp_A8; //! 测试地址 // TODO //! 索要防区探头遗失/失联 第一次索要 EB AB 3F AC 81 struct QuerySensorLostSettingsRequest { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xAC, 0x81 }; }; typedef QuerySensorLostSettingsRequest Req_AC; //! 索要防区探头遗失/失联 索要更多 EB AB 3F AD 82 struct QueryMoreSensorLostSettingsRequest { static constexpr Char len = 5; static constexpr Char data[len] = { 0xEB, 0xAB, 0x3F, 0xAD, 0x82 }; }; typedef QueryMoreSensorLostSettingsRequest Req_AD; //! 索要防区探头遗失/失联回应 EB BA 3F PN P0 AD P1 DATA P2 SUM struct QuerySensorLostSettingsResponse { static constexpr Char P1FlagZoneAs1Char = 0xF0; static constexpr Char P1FlagZoneAs2Chars = 0xF1; static constexpr Char min_len = 9; // 无防区防拆数据时长度最小为9 static constexpr Char max_zone = 20; // 最多包含20个防区 static constexpr Char max_len = min_len + max_zone * 2; // 最多有 9 + 20 *2 = 49 个字节 Char data[max_len] = {}; Char len = 9; // init as minimum len const Char p1 = P1FlagZoneAs1Char; // F0 防区号单字节表示,F1防区号双字节表示 // default ctor, no zone exists QuerySensorLostSettingsResponse(Char p1 = P1FlagZoneAs1Char) :p1(p1) { Char nodata[min_len] = { 0xEB, 0xBA, 0x3F, 0x09, 0x00, 0xAD, p1, 0xFF, 0x00 }; sum(nodata, min_len); memcpy(data, nodata, min_len); len = min_len; } Char maxZone() const { Char res = (max_len - min_len); if (p1 == P1FlagZoneAs2Chars) { res /= 2; } return res; } QuerySensorLostSettingsResponse& addZone(size_t zone, bool isOver = true, bool hasMore = false) { if (len == max_len) { return *this; } Char pos = static_cast<Char>(len - 2); if (p1 == P1FlagZoneAs1Char) { data[pos] = zone & 0xFF; len = static_cast<Char>(len + 1); data[3] = len; } else { data[pos] = (zone >> 8) & 0xFF; data[pos + 1] = zone & 0xFF; len = static_cast<Char>(len + 2); data[3] = len; } data[len - 2] = hasMore ? 0x00 : 0xFF; if (isOver) { sum(this); } return *this; } //! parser, make sure to call ResponseParser::parse first and return is ADR_response to check data valid, then copy data/len to member. bool parse(std::vector<size_t>& zones, bool& hasMore) { if (len < min_len || data[3] != len) { return false; } // check valid again Char sum_ = data[len - 1]; sum(data, len); if (sum_ != data[len - 1]) { return false; } // check sum again Char flag = data[6]; if (flag != P1FlagZoneAs1Char && flag != P1FlagZoneAs2Chars) { return false; } // check param Char count = Char(len - min_len); if (flag == P1FlagZoneAs2Chars) { count = Char(count >> 1); } if (count == 0) { zones.clear(); hasMore = false; return true; } for (Char i = 0; i < count; i++) { size_t zone = data[7 + i]; if (p1 == P1FlagZoneAs2Chars) { zone <<= 8; zone |= data[8 + i]; } zones.push_back(zone); } hasMore = data[len - 2] != 0xFF; return true; } }; typedef QuerySensorLostSettingsResponse Resp_AD; //! 修改防区探头遗失/失联 EB CB 3F 08 AA P1 P2 SUM struct ChangeSensorLostSettingsRequest { static constexpr Char len = 8; Char data[len] = { 0xEB, 0xAB, 0x3F, 0x08, 0xAA, 0x00, 0x00, 0x00 }; ChangeSensorLostSettingsRequest() = default; ChangeSensorLostSettingsRequest(Char zone, Char on) { data[5] = zone; data[6] = on; sum(data, len); } }; typedef ChangeSensorLostSettingsRequest Req_AA; //! 修改防区探头遗失/失联回应 EB BA 3F 09 P0 AB P1 P2 SUM struct ChangeSensorLostSettingsResponse { static constexpr Char len = 9; Char data[len] = { 0xEB, 0xAB, 0x3F, 0x09, 0xCC, 0xAB, 0x00, 0x00, 0x00 }; ChangeSensorLostSettingsResponse() = default; ChangeSensorLostSettingsResponse(Char zone, Char p2) { data[6] = zone; data[7] = p2; sum(data, len); } }; typedef ChangeSensorLostSettingsResponse Resp_AB; //! 解析 Request // 主要用来给模拟主机识别过来的串口命令类型 struct RequestParser { enum class RequestType { A0_request, // 索要主机状态 A1_request, // 索要防区 A2_request, // 索要更多防区 A3_request, // 修改防区 A5_request, // 获取定时器 A7_request, // 设置定时器 AA_request, // 修改防区探头遗失/失联 AC_request, // 索要防区探头遗失/失联--第一次索要 AD_request, // 索要防区探头遗失/失联--继续索要 B0_request, // 索要三区段主机状态 RD_acct_request, // 读取主机账号 WR_acct_request, // 写入主机账号 Invalid_request = -1, }; static RequestType parse(const Char* data, Char len) { do { if (len < 5) { break; } if (data[0] != 0xEB) { break; } switch (data[1]) { case 0xAB: { if (data[2] != 0x3F) { break; } switch (data[3]) { case 0xA0: // EB AB 3F A0 75 { if (len != Req_A0::len) { break; } if (memcmp(Req_A0::data, data, len) != 0) { break; } return RequestType::A0_request; } case 0xA1: // EB AB 3F A1 76 { if (len != Req_A1::len) { break; } if (memcmp(Req_A1::data, data, len) != 0) { break; } return RequestType::A1_request; } case 0xA2: // EB AB 3F A2 77 { if (len != Req_A2::len) { break; } if (memcmp(Req_A2::data, data, len) != 0) { break; } return RequestType::A2_request; } case 0xA5: // EB AB 3F A5 7A { if (len != Req_A5::len) { break; } if (memcmp(Req_A5::data, data, len) != 0) { break; } return RequestType::A5_request; } case 0xAC: // EB AB 3F AC 81 { if (len != Req_AC::len) { break; } if (memcmp(Req_AC::data, data, len) != 0) { break; } return RequestType::AC_request; } case 0xAD: // EB AB 3F AD 82 { if (len != Req_AD::len) { break; } if (memcmp(Req_AD::data, data, len) != 0) { break; } return RequestType::AD_request; } default: break; } break; } case 0xBA: { if (data[2] != 0x3F) { break; } if (len == ReadMachineAcctRequest::len && memcmp(data, ReadMachineAcctRequest::data, len) == 0) { return RequestType::RD_acct_request; } break; } case 0xCB: { if (data[2] != 0x3F) { break; } // EB CB 3F 09 A3 P1 P2 P3 SUM if (data[3] == 0x09 && data[4] == 0xA3 && len == Req_A3::len) { Req_A3 req; memcpy(req.data, data, len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::A3_request; } } if (data[3] == 0x0F && data[4] == 0x4D && len == WriteMachineAcctRequest::len) { WriteMachineAcctRequest req; memcpy(req.data, data, req.len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::WR_acct_request; } } // EB CB 3F 0E A7 H1 M1 H2 M2 H3 M3 H4 M4 SUM if (data[3] == 0x0E && data[4] == 0xA7 && len == Req_A7::len) { Req_A7 req; memcpy(req.data, data, req.len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::A7_request; } } /*if (data[3] == 0x08 && data[4] == 0xA9 && len == A9::len) { A7 req; memcpy(req.data, data, req.len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::A7_request; } }*/ // EB CB 3F 08 AA P1 P2 SUM if (data[3] == 0x08 && data[4] == 0xAA && len == Req_AA::len) { Req_AA req; memcpy(req.data, data, req.len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::AA_request; } } /*if (data[3] == 0x08 && data[4] == 0xAE && len == AE::len) { AA req; memcpy(req.data, data, req.len); sum(req); if (data[len - 1] == req.data[len - 1]) { return RequestType::AA_request; } }*/ // EB CB 3F 06 B0 AB if (data[3] == 0x06 && data[4] == 0xB0 && len == Req_B0::len && memcmp(Req_B0::data, data, len) == 0) { return RequestType::B0_request; } } } } while (0); return RequestType::Invalid_request; } }; //! 解析 Response 类型 // 用来解析主机发来的指令 struct ResponseParser { enum class ResponseType { A0_response, A2_response, A3_response, A4_response, A6_response, A7_response, A8_response, A9_response, AB_response, AD_response, AE_response, B1_response, Invalid_response = -1, }; static ResponseType parse(const Char* data, Char len) { do { if (len < 7) { break; } // 所有的 response ,长度最小为 7 if (data[0] != 0xEB || data[1] != 0xBA || data[2] != 0x3F) { break; } switch (data[5]) { case 0xA0: // EB BA 3F 07 P0 A0 P1 P2 P3 SUM { if (len != Resp_A0::len) { break; } Resp_A0 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A0_response; } case 0xA2: // EB BA 3F PN P0 A2 [Z, P]xN P1 SUM { if (len != data[3]) { break; } Resp_A2 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A2_response; } case 0xA3: { if (len != Resp_A3::len) { break; } Resp_A3 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A3_response; } case 0xA4: { if (len != Resp_A4::len) { break; } Resp_A4 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A4_response; } case 0xA6: { if (len != Resp_A6::len) { break; } Resp_A6 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A6_response; } case 0xA7: { if (len != Resp_A7::len) { break; } Resp_A7 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A7_response; } case 0xA8: { if (len != Resp_A8::len) { break; } Resp_A8 resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::A8_response; } case 0xA9: { // TODO } case 0xAB: { if (len != Resp_AB::len) { break; } Resp_AB resp; memcpy(resp.data, data, len); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::AB_response; } case 0xAD: // EB BA 3F PN P0 AD P1 DATA P2 SUM { if (len != data[3]) { break; } Resp_AD resp; memcpy(resp.data, data, len); sum(resp); sum(resp); if (resp.data[len - 1] != data[len - 1]) { break; } return ResponseType::AD_response; } case 0xAF: // TODO { break; } case 0xB1: // EB BA 3F 08 P0 B1 P1 SUM { if (len != Resp_B1::len) { break; } Resp_B1 resp; memcpy(resp.data.data, data, len); sum(resp.data.data, resp.len); if (resp.data.data[len - 1] != data[len - 1]) { break; } return ResponseType::B1_response; } } } while (0); return ResponseType::Invalid_response; } }; // fix gcc cry #ifdef __GNUG__ #define DECLARE_HB_COMMON_COM_CONSTEXPR_MEMBERS \ constexpr hb::Char hb::common::com::Req_A1::data[]; \ constexpr hb::Char hb::common::com::Req_A2::data[]; \ constexpr hb::Char hb::common::com::Req_AC::data[]; \ constexpr hb::Char hb::common::com::Req_AD::data[]; \ constexpr hb::Char hb::common::com::Req_A5::data[]; \ constexpr hb::Char hb::common::com::Req_B0::data[]; #else #define DECLARE_HB_COMMON_COM_CONSTEXPR_MEMBERS #endif } // namespace com } // namespace common //! barely used, everybody loves common machine namespace old { //! 键盘按键码 enum Key : Char { //! 00没有按键动作 Key_NULL, //! 01键盘上 1 键按下 Key_1, //! 02键盘上 2 键按下 Key_2, //! 03键盘上 3 键按下 Key_3, //! 04键盘上 4 键按下 Key_4, //! 05键盘上 5 键按下 Key_5, //! 06键盘上 6 键按下 Key_6, //! 07键盘上 7 键按下 Key_7, //! 08键盘上 8 键按下 Key_8, //! 09键盘上 9 键按下 Key_9, //! 0a键盘上 0 键按下 Key_0, //! 0b键盘上 * 键按下 Key_ASTERISK, //! 0c键盘上 # 键按下 Key_SHARP, //! 0d键盘上 全布防 键按下 Key_ARM, //! 0e键盘上 半布防 键按下 Key_HALF_ARM, //! 0f键盘上 撤防 键按下 Key_DISARM, //! 10键盘上 报警 键按下 Key_EMERGENCY, //! 11键盘上 录音 键按下 Key_RECORD_SOUND, //! 12键盘上 放音 键按下 Key_PLAY_SOUND, //! 13键盘上 停止 键按下 Key_STOP_SOUND, //! 0x16终止本次报警 Key_STOP_ALARM = 0x16, }; typedef std::vector<Key> Keys; //! 用于将Key的值Char转为Key,不特殊处理Key_0 static Key keyFromChar(Char c) { if ((Key_1 <= c && c <= Key_STOP_SOUND) || (c == Key_STOP_ALARM)) { return static_cast<Key>(c); }return Key_NULL; } //! 将 0~9转换为Key,特殊处理Key_0 static Key keyFromNumber(int i) { if (i == 0) { return Key_0; } else if (1 <= i && i <= 9) { return static_cast<Key>(i); } else { return Key_NULL; } } static char keyToPrintableChar(Key key) { switch (key) { case Key_1: case Key_2: case Key_3: case Key_4: case Key_5: case Key_6: case Key_7: case Key_8: case Key_9: return static_cast<char>(key + '0'); case Key_0: return '0'; case Key_ASTERISK: return '*'; case Key_SHARP: return '#'; case Key_ARM: return 'A'; case Key_HALF_ARM: return 'H'; case Key_DISARM: return 'D'; case Key_EMERGENCY: return 'E'; case Key_RECORD_SOUND: return 'R'; case Key_PLAY_SOUND: return 'P'; case Key_STOP_SOUND: return 'S'; case Key_STOP_ALARM: return 'M'; // m for mute case Key_NULL: default: return ' '; } } //! PC的模拟键盘序号为3 static constexpr Char PC_KEYBOARD_IDX = 3; #ifdef ENABLE_SEND_MULTI_KEY_IN_ONE_COMMAND //! 模拟键盘按下命令 struct KeyInput { std::vector<Char> data = {}; KeyInput() { reset(); } KeyInput(Key key){ data = { 0xEB, 0xAB, PC_KEYBOARD_IDX, key, 0x00 }; sum(data); } void reset(){ data = { 0xEB, 0xAB, PC_KEYBOARD_IDX, Key_NULL, 0x00 }; sum(data); } void setKey(Key key) { data[3] = key; sum(data); } void appendKey(Key key, bool end = true) { data.insert(data.end() - 1, key); data[2] = size(); if (end) { sum(data); } } const char* buff() const { return reinterpret_cast<const char*>(data.data()); } Char size() const { return static_cast<Char>(data.size() & 0xFF); } static KeyInput null() { return KeyInput(); } static KeyInput back() { KeyInput ki; ki.setKey(Key_0); ki.appendKey(Key_SHARP); return ki; } static KeyInput quitConfig() { KeyInput ki(Key_0); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_0, false); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_0, false); ki.appendKey(Key_SHARP); return ki; } // use default config password 112233 static KeyInput enterConfig() { KeyInput ki(Key_SHARP); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_1, false); ki.appendKey(Key_1, false); ki.appendKey(Key_2, false); ki.appendKey(Key_2, false); ki.appendKey(Key_3, false); ki.appendKey(Key_3, false); ki.appendKey(Key_SHARP); return ki; } // use config password, password must be numeric like 112233, not "112233" static KeyInput enterConfig(const char pwd[6]) { KeyInput ki(Key_SHARP); ki.appendKey(Key_SHARP); ki.appendKey(Key_SHARP); for (char i = 0; i < 6; i++) { ki.appendKey(keyFromChar(pwd[i]), false); } ki.appendKey(Key_SHARP); return ki; } // use default config password 112233 and code 12*48# static KeyInput enterDuimaMode() { KeyInput ki(Key_SHARP); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_1, false); ki.appendKey(Key_1, false); ki.appendKey(Key_2, false); ki.appendKey(Key_2, false); ki.appendKey(Key_3, false); ki.appendKey(Key_3, false); ki.appendKey(Key_SHARP, false); ki.appendKey(Key_1, false); ki.appendKey(Key_2, false); ki.appendKey(Key_ASTERISK, false); ki.appendKey(Key_4, false); ki.appendKey(Key_8, false); ki.appendKey(Key_SHARP); return ki; } std::string toString() const { std::string str; for (auto c : data) { str.push_back(ademco::detail::Dec2Hex((c >> 4) & 0x0F)); str.push_back(ademco::detail::Dec2Hex(c & 0x0F)); str.push_back(' '); } return str; } }; #else // ENABLE_SEND_MULTI_KEY_IN_ONE_COMMAND //! 模拟键盘按下命令 struct KeyInput { static constexpr Char OneKeyLen = 5; struct OneKey { std::vector<Char> data = {}; OneKey(Key key = Key_NULL) { data = { 0xEB, 0xAB, PC_KEYBOARD_IDX, key, 0x00 }; sum(data); } }; std::vector<char> buff_ = {}; KeyInput() { reset(); } KeyInput(Key key) { buff_.resize(OneKeyLen); OneKey data(key); memcpy(buff_.data() + buff_.size() - OneKeyLen, data.data.data(), data.data.size()); } KeyInput(Keys keys) { buff_.clear(); for (auto key : keys) { OneKey k(key); std::copy(k.data.begin(), k.data.end(), std::back_inserter(buff_)); } } void reset() { buff_.resize(OneKeyLen); OneKey data; memcpy(buff_.data(), data.data.data(), data.data.size()); } void appendKey(Key key) { buff_.resize(buff_.size() + OneKeyLen); OneKey data(key); memcpy(buff_.data() + buff_.size() - OneKeyLen, data.data.data(), data.data.size()); } const char* buff() const { return buff_.data(); } Char size() const { return static_cast<Char>(buff_.size() & 0xFF); } //////////////////////// helpers /////////////////////////////////////////////////// static KeyInput null() { return KeyInput(); } static KeyInput back() { KeyInput ki(Key_0); ki.appendKey(Key_SHARP); return ki; } static KeyInput quitConfig() { KeyInput ki(Key_0); ki.appendKey(Key_SHARP); ki.appendKey(Key_0); ki.appendKey(Key_SHARP); return ki; } // use default config password 112233 static KeyInput enterConfig() { KeyInput ki(Key_SHARP); ki.appendKey(Key_SHARP); ki.appendKey(Key_SHARP); ki.appendKey(Key_1); ki.appendKey(Key_1); ki.appendKey(Key_2); ki.appendKey(Key_2); ki.appendKey(Key_3); ki.appendKey(Key_3); ki.appendKey(Key_SHARP); return ki; } // use config password, password must be numeric like 112233, not "112233" static KeyInput enterConfig(const char pwd[6]) { KeyInput ki(Key_SHARP); ki.appendKey(Key_SHARP); ki.appendKey(Key_SHARP); for (size_t i = 0; i < 6; i++) { ki.appendKey(keyFromNumber(pwd[i])); } ki.appendKey(Key_SHARP); return ki; } // machine must be under config mode static KeyInput enterDuimaMode() { KeyInput ki(Key_1); ki.appendKey(Key_2); ki.appendKey(Key_ASTERISK); ki.appendKey(Key_4); ki.appendKey(Key_8); ki.appendKey(Key_SHARP); return ki; } // like ###112233# ... std::string toString() const { std::string str; for (size_t i = 0; i < buff_.size() / OneKeyLen; i++) { auto key = buff_[i * OneKeyLen + 3]; str.push_back(keyToPrintableChar(keyFromChar(key))); } return str; } }; #endif //! 声音 enum Sound : Char { //! 按键音 0 SOUND_BUTTON_PRESSED = 0, //! 成功 SOUND_SUCCESS, //! 叮咚 SOUND_DINGDONG, //! 非法操作 SOUND_ILLEGAL_OPERATION, //! 读秒 SOUND_SECOND, //! 异常 SOUND_EXCEPTION, //! 退出设置 SOUND_QUIT_CONFIG, //! 输入设置密码 SOUND_INPUT_CONFIG_PSW, //! 输入新密码 SOUND_INPUT_NEW_PSW, //! 输入用户密码 SOUND_INPUT_USER_PSW, //! 再次输入 10 SOUND_INPUT_AGAIN, //! "电话报警"; SOUND_ALARM_PHONE, //! "现场报警"; SOUND_ALARM_SOUNDTE, //! "半布防"; SOUND_SECURE_HALF, //! "全局布防"; SOUND_SECURE_ALL, //! "撤防"; SOUND_SECURE_CANCEL, //! "请输入报警电话"; SOUND_INPUT_PHONE_ALARM, //! "删除所有电话"; SOUND_DEL_ALL_PHONE, //! "输入报警声响时间"; SOUND_INPUT_ALARM_TIME, //! "输入离开时间"; SOUND_INPUT_LEAVE_TIME, //! "请输入进入时间"; 20 SOUND_INPUT_ENTER_TIME, //! "请输入振铃次数"; SOUND_INPUT_IN_DIAL_TIMES, //! "请输入拨号循环次数"; SOUND_INPUT_OUT_DIAL_TIMES, //! "主机进入对码状态"; SOUND_HOST_DUIMA, //! "请按遥控器布防键"; SOUND_PRESS_RC_SECURE_ALL, //! "请触发探头"; SOUND_TRIGGER_DETECTOR, //! "删除探头"; SOUND_DEL_DETECTOR, //! "删除所有探头"; SOUND_DEL_ALL_DETECTOR, //! "全局防区"; SOUND_AREA_GLOBAL, //! "半局防区"; SOUND_AREA_HALF, //! "紧急报警防区"; 30 SOUND_AREA_EMERGENCY, //! "屏蔽防区"; SOUND_AREA_SHIELD, //! "门铃防区"; SOUND_AREA_DOORRING, //! "下一个"; SOUND_NEXT, //! "请注意,有非法进入"; SOUND_SYSTEM_MALFUNTION, //! "主机进入调整状态"; SOUND_HOST_MODULATION, //! "退回"; SOUND_BACK, //! "错误请重新输入"; SOUND_ERROR_INPUT_AGAIN, //! "请输入防区属性"; SOUND_INPUT_AREA_PROPERTY, //! "该防区已存在"; SOUND_AREA_ALREADY_EXISTS, //! "该遥控器已存在"; 40 SOUND_RC_ALREADY_EXISTS, //! "欢迎使用"; SOUND_WELCOM, //! "请先删除该防区"; SOUND_DEL_AREA_FIRST, //! "请注意有探头无效"; SOUND_DETECTOR_INVALID, //! "电话线断了"; SOUND_PHONE_LINE_DISCONNECT, //! "主机远程控制"; SOUND_HOST_REMOTE_CONTROL, //! "开始监听"; SOUND_START_LISTEN, //! "现场静音"; SOUND_SOUNDTE_MUTE, //! "即将断线"; SOUND_ON_THE_WAY_DISCONNECT, //! "警笛报警声"; SOUND_ALARM_WISTLE, //! "录音报警声"; 50 SOUND_ALARM_RECORD, //! "清异常指示"; SOUND_CLEAR_EXCEPTION, //! "报警"; SOUND_ALARM, //! "输入接警中心电话"; SOUND_INPUT_PHONE_CENTER, //! "请输入用户号"; SOUND_INPUT_ADEMCO_ID, //! "请注意,你已非法进入" SOUND_VIALATION_ENTER, //! "请注意,报警了" SOUND_ATTENTION_ALARMING, //! "删除此电话" SOUND_DELETE_PHONE, //! "删除此遥控" SOUND_DELETE_REMOTE_RC, //! "删除所有遥控" 59 SOUND_DELETE_ALL_RC, //! "提示" 60 SOUND_WARNING, //! "火警防区" SOUND_AREA_FIRE, //! "胁迫防区" SOUND_AREA_DURESS, //! "煤气防区" SOUND_AREA_GAS, //! "水警防区" SOUND_AREA_WATER, //! "防区" SOUND_AREA, //! ”煤气泄漏" SOUND_GAS_LEAKAGE, //! "请救助" SOUND_PLEASE_HELP, //! "水泄漏" SOUND_WATER_LEAKAGE, //! "有火情" SOUND_FIRE, //! 0 ~ 9 70 SOUND_0, SOUND_1, SOUND_2, SOUND_3, SOUND_4, SOUND_5, SOUND_6, SOUND_7, SOUND_8, SOUND_9, //! "消防车音效" 80 SOUND_FIRE_TRUCK_SOUND, //! "煤气音效" SOUND_GAS_SOUND, //! "水警音效" SOUND_WATER_SOUND, //! "全部报警" SOUND_ALL_ALARM, //! "部分报警" SOUND_PART_ALARM, //! "接警中心报警选择" 85 SOUND_SEL_ALARM_CENTER, //! "网络主机" 86 SOUND_NET_MACHINE, //! 已断开连接 87 SOUND_DISCONNECT, //! 88 SOUND_COUNT, }; //! 主机到键盘显示 9字节 struct MachineInfoToKeyboard { static constexpr Char len = 9; // idx LED1 LED2 data3 data4 data5 sum Char data[len] = { 0xEB, 0xAE, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0xFF }; union LED1 { struct LED { char setting : 1; char halfarm : 1; char arm : 1; char invalid : 1; char solar_disturb : 1; char low_battery : 1; char shield : 1; char alarming : 1; } led; Char data; LED1() :data(0) {} }; union LED2 { struct LED { char battery_broke : 1; char door_ring : 1; char emergency : 1; char phone_disconn : 1; char phone_alarm : 1; char record : 1; char signal : 1; char run : 1; } led; Char data; LED2() :data(0) {} }; //! 数码管显示 struct LCD { //! 百位 Char h; //! 十位 Char d; //! 个位 Char s; char data[4] = { 0 }; wchar_t wdata[4] = { 0 }; Char toChar(Char c) const { if (0 <= c && c <= 9) { return static_cast<Char>('0' + c); } else if (0x0A <= c && c <= 0x0D) { return static_cast<Char>('A' + c); } else if (c == 0x0E) { return '-'; } else { return ' '; } } wchar_t toWChar(Char c) const { if (0 <= c && c <= 9) { return L'0' + c; } else if (0x0A <= c && c <= 0x0D) { return L'A' + c; } else if (c == 0x0E) { return L'-'; } else { return L' '; } } const char* toString() { data[0] = toChar(h); data[1] = toChar(d); data[2] = toChar(s); data[3] = '\0'; return data; } const wchar_t* toWString() { wdata[0] = toWChar(h); wdata[1] = toWChar(d); wdata[2] = toWChar(s); wdata[3] = L'\0'; return wdata; } }; // 1~4 Char getKeyboardAddr() const { return data[2]; } LED1 getLED1() const { LED1 led1; led1.data = data[3]; return led1; } void setLED1(LED1 led1) { data[3] = led1.data; } LED2 getLED2() const { LED2 led2; led2.data = data[4]; return led2; } void setLED2(LED2 led2) { data[4] = led2.data; } LCD getLCD() const { LCD lcd; lcd.h = data[5] & 0x0F; lcd.d = data[6] & 0x0F; lcd.s = data[7] & 0x0F; return lcd; } Sound getSound() const { Char s = static_cast<Char>((data[5] & 0xF0) | ((data[6] >> 4) & 0x0F)); if (s < SOUND_COUNT) { return static_cast<Sound>(s); } return SOUND_COUNT; } // for windows message #if defined(WIN32) && defined(_MINWINDEF_) inline void toWLParam(WPARAM& wParam, LPARAM& lParam) const { wParam = 0; wParam |= (data[2]) & 0xFF0000; wParam |= (data[3] << 8) & 0xFF00; wParam |= data[4] & 0xFF; lParam = 0; lParam |= (data[5] << 16) & 0xFF0000; lParam |= (data[6] << 8) & 0xFF00; lParam |= data[7] & 0xFF; } static MachineInfoToKeyboard fromWLParam(WPARAM wParam, LPARAM lParam) { MachineInfoToKeyboard mik; mik.data[2] = ((wParam >> 16) & 0xFF); mik.data[3] = (wParam >> 8) & 0xFF; mik.data[4] = wParam & 0xFF; mik.data[5] = (lParam >> 16) & 0xFF; mik.data[6] = (lParam >> 8) & 0xFF; mik.data[7] = lParam & 0xFF; return mik; } #endif // WIN32 }; //! 主机模式 enum MahcineMode : Char { //! 普通模式 ModeNormal, //! 设置模式 ModeConfig, //! 对码模式 ModeDuima, }; //! 主机(或分主机)状态码 enum MachineStatus : Char { Arm = 0x01, Disarm = 0x02, HalfArm = 0X04, MachineStatusInvalid = 0xFF, }; //! 报警码 enum AlarmCode : Char { //! 主机紧急报警 MACHINE_EMERGENCY = 0x08, //! 匪警 ALARM_BURGLAR = 0x00, //! 火警 ALARM_FIRE = 0x20, //! 呼救/挟持 ALARM_DURESS = 0x30, //! 煤气 ALARM_GAS = 0x40, //! 水警 ALARM_WATER = 0x50, //! 开机 ALARM_STARTUP = 0xCA, //! 关机 ALARM_SHUTDOWN = 0xED, //! 防拆 ALARM_TEMPER = 0xBA, //! 发低电 ALARM_S_BATTERY_LOW = 0xAB, //! 收低电 ALARM_R_BATTERY_LOW = 0xAC, //! 发电池损坏 ALARM_S_BATTERY_BROKE = 0xAE, //! 收电池损坏 ALARM_R_BATTERY_BROKE = 0xA5, //! 复电 ALARM_BETTERY_RECOVER = 0xAD, //! 强光干扰 ALARM_SOLAR_DISTURB = 0xBD, //! 强光恢复 ALARM_SOLAR_RECOVER = 0xCD, //! 发送端重启 ALARM_SENDER_REBOOT = 0x69, //! 长时间没有对上 ALARM_LONGTIME_DISCONN = 0xBC, //! 长时间已经对上 ALARM_LONGTIME_RECOVER = 0xCB, //! 门铃响了 2014年12月30日 14:39:43 ALARM_DOOR_RING = 0xDD, //! SM for Sub Machine //! 分机产生异常信息 ALARM_SM_EXCEPTION = 0xCE, //! 分机恢复异常信息 ALARM_SM_EXCEPT_RESUME = 0xCF, //! 分机产生电源异常 ALARM_SM_POWER_EXCEPT = 0xBE, //! 分机恢复电影异常 ALARM_SM_POWER_RESUME = 0xBF, AlarmCodeInvalid = 0xFF, }; //! 防区类型 enum ZoneType : Char { //! 直属防区 ZT_ZONE = 0x00, //! 分主机防区 ZT_SUBMACHINE = 0xEE, //! 未对码防区 ZT_NULL = 0xCC, ZT_INVALID = 0xFF, }; static ZoneType zoneTypeFromChar(Char c) { switch (c) { case ZT_ZONE: return ZT_ZONE; case ZT_SUBMACHINE: return ZT_SUBMACHINE; case ZT_NULL: return ZT_NULL; case ZT_INVALID: default: return ZT_INVALID; } } //! 防区属性 enum ZoneProperty : Char { //! 全局防区 ZP_GLOBAL = 0xD0, //! 半局防区 ZP_HALF = 0xD1, //! 紧急防区 ZP_EMERGENCY = 0xD2, //! 屏蔽防区 ZP_SHIELD = 0xD3, //! 门铃防区 ZP_DOORRING = 0xD4, //! 火警防区 ZP_FIRE = 0xD5, //! 胁迫防区 ZP_DURESS = 0xD6, //! 煤气防区 ZP_GAS = 0xD7, //! 水警防区 ZP_WATER = 0xD8, ZP_COUNT = 0x09, ZP_INVALID = 0xFF, }; static ZoneProperty zonePropertyFromChar(Char c) { switch (c) { case ZP_GLOBAL: return ZP_GLOBAL; case ZP_HALF: return ZP_HALF; case ZP_EMERGENCY: return ZP_EMERGENCY; case ZP_SHIELD: return ZP_SHIELD; case ZP_DOORRING: return ZP_DOORRING; case ZP_FIRE: return ZP_FIRE; case ZP_DURESS: return ZP_DURESS; case ZP_GAS: return ZP_GAS; case ZP_WATER: return ZP_WATER; case ZP_INVALID: default: return ZP_INVALID; } } #ifdef ENABLE_OLD_ZONE_PROPERTY_TO_STRING static const wchar_t* zonePropertyToString(ZoneProperty zp) { switch (zp) { case ZP_GLOBAL: return L"全局盗警"; case ZP_HALF: return L"半局盗警"; case ZP_EMERGENCY: return L"紧急防区"; case ZP_SHIELD: return L"屏蔽防区"; case ZP_DOORRING: return L"门铃防区"; case ZP_FIRE: return L"火警防区"; case ZP_DURESS: return L"胁迫防区"; case ZP_GAS: return L"煤气防区"; case ZP_WATER: return L"水警防区"; case ZP_INVALID: default: return L"未知属性"; } } #endif //! 防区无线地址 struct WirelessAddress { static constexpr uint16_t mask = 0xFFFF; Char hi = 0; Char lo = 0; WirelessAddress(uint16_t addr = mask) { fromUInt16(addr); } WirelessAddress(const WirelessAddress&) = default; WirelessAddress& operator=(const WirelessAddress&) = default; WirelessAddress& operator=(uint16_t addr) { fromUInt16(addr); return *this; } bool operator==(const WirelessAddress& rhs) const { return hi == rhs.hi && lo == rhs.lo; } bool operator==(uint16_t addr) const { return toUInt16() == addr; } void fromUInt16(uint16_t addr){ hi = static_cast<Char>((addr >> 8) & 0xFF); lo = static_cast<Char>(addr & 0xFF); } uint16_t toUInt16() const { return static_cast<Char>(((hi << 8) | lo) & mask); } bool valid() const { return toUInt16() != mask; } void reset() { hi = lo = 0xFF; } static WirelessAddress randomAddress() { WirelessAddress addr = static_cast<uint16_t>(rand() % mask); return addr; } }; //! 主机有线防区范围1~7 constexpr ademco::AdemcoZone ZoneMinWired = 1; constexpr ademco::AdemcoZone ZoneMaxWired = 8; //! 主机无线防区最小值 constexpr ademco::AdemcoZone ZoneMinWireless = 9; constexpr ademco::AdemcoZone ZoneMaxWireless = 999; //! 分主机防区号范围1~99 constexpr ademco::AdemcoZone ZoneMinSubMachine = 1; constexpr ademco::AdemcoZone ZoneMaxSubMachine = 99; //! 以2个字节表示的防区号 struct ZoneAsTwoChar { Char hi = 0; Char lo = 0; ZoneAsTwoChar(uint16_t zone = 0) { fromUInt16(zone); } ZoneAsTwoChar(const ZoneAsTwoChar&) = default; ZoneAsTwoChar& operator=(const ZoneAsTwoChar&) = default; ZoneAsTwoChar(Char hi, Char lo) : hi(hi), lo(lo) {} uint16_t toUInt16() const { return static_cast<Char>(((hi << 8) & 0xFF00) | lo); } void fromUInt16(uint16_t zone) { hi = static_cast<Char>((zone >> 8) & 0xFF); lo = static_cast<Char>(zone & 0xFF); } ZoneAsTwoChar& operator=(uint16_t zone) { fromUInt16(zone); return *this; } ZoneAsTwoChar& operator++() { if (++lo == 0xFF) { lo = 0; if (++hi == 0xFF) { assert(0); } } return *this; } bool operator==(const ZoneAsTwoChar& rhs) const { return hi == rhs.hi && lo == rhs.lo; } bool operator==(uint16_t zone) const { return toUInt16() == zone; } }; //! 防区信息 struct ZoneInfo { union MachineStatusOrZoneProperty { MachineStatus status; ZoneProperty prop; }; uint16_t zone = {}; ZoneType type = {}; MachineStatusOrZoneProperty prop; WirelessAddress addr = {}; //! 是否有效的无线防区 bool valid() const { return ZoneMinWireless <= zone && zone <= ZoneMaxWireless && type != ZT_INVALID && prop.prop != ZP_INVALID && addr.valid(); } void reset() { zone = UINT16_MAX; type = ZT_INVALID; prop.prop = ZP_INVALID; addr.reset(); } }; //! 所有防区信息 struct AllZoneInfo { //! use extra 1 space, for convenience to use zone as index ZoneInfo zones[ademco::ZoneSentinel] = {}; AllZoneInfo() { for (uint16_t i = 0; i < ademco::ZoneSentinel; i++) { zones[i].zone = i; } } bool isUniqueAddr(WirelessAddress addr) const { for (const auto& z : zones) { if (z.addr == addr)return false; } return true; } WirelessAddress randomAddress() { auto addr = WirelessAddress::randomAddress(); while (!isUniqueAddr(addr)) { addr = WirelessAddress::randomAddress(); } return addr; } }; /** * @brief 串口透传协议 * @note 网络传输模块与主机模块通信协议 * @note 客户端(手机APP、接警中心等)会用到一些透传数据 */ namespace com { //! PC到主机,查询主机(自身或防区)或分机(自身或防区)状态 struct Query { static constexpr Char len = 8; // 0 1 2 z-hi z-lo p1 p2 sum Char data[len] = { 0xEB, 0xB5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }; //! 参数,可用来查询或设置 union Param { //! 查询参数 struct Get { enum P1 : Char { //! 查询分机(自身或防区)状态 QuerySubMachine = 0xDD, //! 查询主机(自身或防区)状态 QueryMachine = 0xDE, }; P1 p1 = P1::QueryMachine; Char p2 = 0xEE; }; //! 设置参数 struct Set { enum P1 : Char { SetArm = 0x01, SetDisArm = 0x02, SetHalfArm = 0x04, SetEmergency = 0x08, }; P1 p1 = P1::SetArm; Char p2 = 0xEE; }; Get get; Set set; }; ZoneAsTwoChar getZone() const { return ZoneAsTwoChar(data[3], data[4]); } void setZone(ZoneAsTwoChar zone) { data[3] = zone.hi; data[4] = zone.lo; } static Query queryMachineStatus() { return queryZoneStatus(ademco::Zone4MachineSelf); } static Query queryZoneStatus(uint16_t zone) { Query query; query.setZone(zone); query.data[5] = Param::Get::P1::QueryMachine; query.data[6] = 0xEE; sum(query); return query; } static Query querySubMachineStatus() { return queryZoneStatusOfSubMachine(ademco::Zone4MachineSelf); } static Query queryZoneStatusOfSubMachine(Char zone) { Query query; query.setZone(zone); query.data[5] = Param::Get::P1::QuerySubMachine; query.data[6] = 0xEE; sum(query); return query; } }; //! 查询结果 struct Result { static constexpr Char len = 10; // 0 1 2 hi lo type zprop ad-hi ad-lo sum Char data[len] = { 0xEB, 0xB1, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ZoneAsTwoChar getZone() const { return ZoneAsTwoChar(data[3], data[4]); } ZoneType getZoneType() const { return zoneTypeFromChar(data[5]); } ZoneProperty getZoneProperty() const { return zonePropertyFromChar(data[6]); } WirelessAddress getWirelessAddress() const { WirelessAddress addr; addr.hi = data[7]; addr.lo = data[8]; return addr; } }; //! 写入防区无线地址到主机 //! 用户恢复主机数据或已知探头(分主机)无线地址时直接写入进行对码,无需触发探头(分主机) struct WriteToMachineRequest { static constexpr Char len = 10; // 0 1 2 z-hi z-lo type ad-hi ad-lo sum Char data[len] = { 0xEB, 0xB5, 0x0A, 0x00, 0x00, 0xDC, 0x00, 0x00, 0x00, 0x00 }; WriteToMachineRequest(ZoneAsTwoChar zone, ZoneType type, WirelessAddress addr) { data[3] = zone.hi; data[4] = zone.lo; data[6] = type; data[7] = addr.hi; data[8] = addr.lo; sum(this); } }; } // namespace com } // namespace old } // namespace hb