/*
 * Ademco library
 * captainwong
 * 2022-6-13 rewrited this C version
 */

#include "ademco.h"

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if ADEMCO_USE_UNIQUE_BUF
static char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif

#define ADEMCO_STRINGIFY(v) ADEMCO_STRINGIFY_HELPER(v)
#define ADEMCO_STRINGIFY_HELPER(v) #v

#define ADEMCO_VERSION_STRING_BASE ADEMCO_STRINGIFY(ADEMCO_VERSION_MAJOR) "." ADEMCO_STRINGIFY(ADEMCO_VERSION_MINOR) "." ADEMCO_STRINGIFY(ADEMCO_VERSION_PATCH)

#if ADEMCO_VERSION_IS_RELEASE
#define ADEMCO_VERSION_STRING ADEMCO_VERSION_STRING_BASE
#else
#define ADEMCO_VERSION_STRING ADEMCO_VERSION_STRING_BASE "-" ADEMCO_VERSION_SUFFIX
#endif

uint32_t ademco_version(void) {
    return ADEMCO_VERSION_HEX;
}

#if ADEMCO_ENABLE_TO_STRING
const char* ademco_version_string(void) {
    return ADEMCO_VERSION_STRING;
}
#endif

uint8_t ademco_hex2char(uint8_t h) {
    h &= 0x0F;
    if (h > 9)
        return 'A' + h - 10;
    else
        return '0' + h;
}

uint8_t ademco_char2hex(uint8_t c) {
    if ('0' <= c && c <= '9')
        return c - '0';
    else if ('A' <= c && c <= 'F')
        return c - 'A' + 10;
    else if ('a' <= c && c <= 'f')
        return c - 'a' + 10;
    else
        return 0xFF;
}

void ademco_print(const ademco_char_t* p, size_t len) {
    while (len--) {
        if (isprint(*(const uint8_t*)p))
            putchar(*p);
        else
            printf("\\x%02X", *(const uint8_t*)p);
        p++;
    }
    printf("\n");
}

uint8_t ademco_encode_signal_strength(uint8_t strength) {
    if (strength > ADEMCO_SIGNAL_STRENGTH_MAX)
        strength = ADEMCO_SIGNAL_STRENGTH_MAX;
    return bcd_encode(strength);
}

uint8_t ademco_decode_signal_strength(uint8_t bcd_strength) {
    uint8_t strength = bcd_decode(bcd_strength);
    if (strength > ADEMCO_SIGNAL_STRENGTH_MAX)
        strength = ADEMCO_SIGNAL_STRENGTH_MAX;
    return strength;
}

bool ademco_is_valid_account(const char* acct) {
    size_t ADEMCO_BUF_MEMORY_MODIFIER len = 0;
    int ADEMCO_BUF_MEMORY_MODIFIER ishex = 0;
    while (*acct && len < ADEMCO_PACKET_ACCT_MAX_LEN) {
        if (isdigit(*acct)) {
            len++;
        } else if (isxdigit(*acct) && len < ADEMCO_PACKET_ACCT_MAC_LEN) {
            len++;
            ishex = 1;
        } else {
            return 0;
        }
        acct++;
    }
    return *acct == '\0' &&
           (ishex ? len == ADEMCO_PACKET_ACCT_MAC_LEN : (len >= ADEMCO_PACKET_ACCT_MIN_LEN));
}

bool ademco_is_valid_password(const char* pwd) {
    size_t ADEMCO_BUF_MEMORY_MODIFIER len = 0;
    while (*pwd && len < ADEMCO_PACKET_PWD_MAX_LEN) {
        if (isdigit(*pwd)) {
            len++;
        } else {
            return false;
        }
        pwd++;
    }
    return *pwd == '\0' && len >= ADEMCO_PACKET_PWD_MIN_LEN;
}

bool ademco_is_machine_status_event(ademco_event_t ademco_event) {
    return ademco_event == EVENT_ARM_AWAY ||
           ademco_event == EVENT_ARM_STAY ||
           ademco_event == EVENT_ARM_STAY_1456 ||
           ademco_event == EVENT_DISARM;
}

bool ademco_is_machine_type_event(ademco_event_t ademco_event) {
    return ademco_event == EVENT_I_AM_NET_MODULE ||
           ademco_event == EVENT_I_AM_GPRS ||
           ademco_event == EVENT_I_AM_LCD ||
           ademco_event == EVENT_I_AM_WIRE ||
           ademco_event == EVENT_I_AM_WIFI ||
           ademco_event == EVENT_I_AM_3_SECTION ||
           ademco_event == EVENT_I_AM_IOT ||
           ademco_event == EVENT_I_AM_TRUE_COLOR ||
           ademco_event == EVENT_I_AM_GPRS_IOT ||
           ademco_event == EVENT_I_AM_GPRS_PHONE ||
           ademco_event == EVENT_I_AM_NB ||
           ademco_event == EVENT_I_AM_WIFI2;
}

bool ademco_is_event_need_control_source(ademco_event_t ademco_event) {
    return ademco_event == EVENT_ARM_AWAY ||
           ademco_event == EVENT_ARM_STAY ||
           ademco_event == EVENT_ARM_STAY_1456 ||
           ademco_event == EVENT_DISARM ||
           ademco_event == EVENT_EMERGENCY ||
           ademco_event == EVENT_DURESS ||
           ademco_event == EVENT_PHONE_USER_CANCLE_ALARM;
}

ademco_event_t ademco_get_exception_event_by_recover_event(ademco_event_t recover_event) {
    switch (recover_event) {
        case EVENT_DISCONNECT_RECOVER:
            return EVENT_DISCONNECT;
        case EVENT_LOST_RECOVER:
            return EVENT_LOST;
        case EVENT_SERIAL_485_RECOVER:
            return EVENT_SERIAL_485_DIS;
        case EVENT_SUB_MACHINE_SENSOR_RECOVER:
            return EVENT_SUB_MACHINE_SENSOR_EXCEPTION;
        case EVENT_SUB_MACHINE_POWER_RECOVER:
            return EVENT_SUB_MACHINE_POWER_EXCEPTION;
        case EVENT_LOW_BATTERY_RECOVER:
            return EVENT_LOW_BATTERY;
        case EVENT_POWER_EXCEPTION_RECOVER:
            return EVENT_POWER_EXCEPTION;
        case EVENT_OTHER_EXCEPTION_RECOVER:
            return EVENT_OTHER_EXCEPTION;
        case EVENT_AC_RECOVER:
            return EVENT_AC_BROKEN;
        case EVENT_SOLAR_DISTURB_RECOVER:
            return EVENT_SOLAR_DISTURB;
        case EVENT_BAD_BATTERY_RECOVER:
            return EVENT_BAD_BATTERY;
        default:
            return EVENT_INVALID_EVENT;
    }
}

ademco_event_level_t ademco_get_event_level(ademco_event_t ademco_event) {
    switch (ademco_event) {
        case EVENT_ARM_AWAY:
        case EVENT_DISARM:
        case EVENT_ARM_STAY:
        case EVENT_ARM_STAY_1456:
            return EVENT_LEVEL_STATUS;

        case EVENT_BAD_BATTERY_RECOVER:
        case EVENT_AC_RECOVER:
        case EVENT_DISCONNECT_RECOVER:
        case EVENT_LOST_RECOVER:
        case EVENT_SERIAL_485_RECOVER:
        case EVENT_SUB_MACHINE_SENSOR_RECOVER:
        case EVENT_SUB_MACHINE_POWER_RECOVER:
        case EVENT_LOW_BATTERY_RECOVER:
        case EVENT_POWER_EXCEPTION_RECOVER:
        case EVENT_OTHER_EXCEPTION_RECOVER:
        case EVENT_SOLAR_DISTURB_RECOVER:
            return EVENT_LEVEL_EXCEPTION_RECOVER;

        case EVENT_AC_BROKEN:
        case EVENT_LOW_BATTERY:
        case EVENT_BAD_BATTERY:
        case EVENT_SOLAR_DISTURB:
        case EVENT_DISCONNECT:
        case EVENT_LOST:
        case EVENT_SUB_MACHINE_SENSOR_EXCEPTION:
        case EVENT_SUB_MACHINE_POWER_EXCEPTION:
        case EVENT_POWER_EXCEPTION:
        case EVENT_OTHER_EXCEPTION:
            return EVENT_LEVEL_EXCEPTION;

        case EVENT_SERIAL_485_DIS:
        case EVENT_BURGLAR:
        case EVENT_DURESS:
        case EVENT_EMERGENCY:
        case EVENT_FIRE:
        case EVENT_GAS:
        case EVENT_TAMPER:
        case EVENT_ZONE_TAMPER:
        case EVENT_WATER:
            return EVENT_LEVEL_ALARM;

        default:
            return EVENT_LEVEL_NULL;
    }
}

#if ADEMCO_ENABLE_TO_STRING
const char* ademco_event_to_string(ademco_event_t ademco_event) {
    switch (ademco_event) {
#define XX(code, name, zh) \
    case EVENT_##name:     \
        return #name;

        // ----标准安定宝协议事件----
        ADEMCO_STATUS_EVENTS_MAP(XX)
        ADEMCO_ALARM_EVENTS_MAP(XX)
        ADEMCO_ALARM_RECOVER_EVENTS_MAP(XX)
        ADEMCO_EXEPTION_EVENTS_MAP(XX)
        ADEMCO_EXEPTION_RECOVER_EVENTS_MAP(XX)

        // ---私有事件---------
        ADEMCO_HB_EVENTS_MAP(XX)
#undef XX
        default:
            return "Undefined ademco_event_t";
    }
}
#endif

#if ADEMCO_ENABLE_CHINESE
const char* ademco_event_to_string_chinese(ademco_event_t ademco_event) {
    switch (ademco_event) {
#define XX(code, name, zh) \
    case EVENT_##name:     \
        return zh;

        // ----标准安定宝协议事件----
        ADEMCO_STATUS_EVENTS_MAP(XX)
        ADEMCO_ALARM_EVENTS_MAP(XX)
        ADEMCO_ALARM_RECOVER_EVENTS_MAP(XX)
        ADEMCO_EXEPTION_EVENTS_MAP(XX)
        ADEMCO_EXEPTION_RECOVER_EVENTS_MAP(XX)

        // ---私有事件---------
        ADEMCO_HB_EVENTS_MAP(XX)
#undef XX
        default:
            return "未定义的安定宝事件";
    }
}
#endif

#ifndef ADEMCO_USE_EXTERNAL_CRC16
static const uint16_t ADEMCO_CONST_MODIFIER crc16Table[256] = {
    /* DEFINE THE FIRST ORDER POLYINOMIAL TABLE */
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,  // 0x00
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,  // 0x08
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,  // 0x10
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,  // 0x18
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,  // 0x20
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,  // 0x28
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,  // 0x30
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,  // 0x38
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,  // 0x40
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,  // 0x48
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,  // 0x50
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,  // 0x58
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,  // 0x60
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,  // 0x68
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,  // 0x70
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,  // 0x78
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,  // 0x80
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,  // 0x88
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,  // 0x90
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,  // 0x98
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,  // 0xA0
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,  // 0xA8
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,  // 0xB0
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,  // 0xB8
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,  // 0xC0
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,  // 0xC8
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,  // 0xD0
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,  // 0xD8
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,  // 0xE0
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,  // 0xE8
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,  // 0xF0
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,  // 0xF8
};

uint16_t ademco_crc16(const ademco_char_t ADEMCO_BUF_MEMORY_MODIFIER* buf, size_t len) {
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc = 0;
    while (len--)
        crc = (crc >> 8) ^ crc16Table[(crc ^ *buf++) & 0xFF];
    return crc;
}
#endif  // ADEMCO_USE_EXTERNAL_CRC16

size_t ademco_append_data(ademco_char_t* buf, const char* acct, ademco_id_t ademco_id,
                          ademco_event_t ademco_event, ademco_gg_t gg, ademco_zone_t zone) {
    char* p = (char*)buf;
    uint8_t i;
    if (ademco_event == EVENT_INVALID_EVENT) {
        *p++ = '[';
        *p++ = ']';
        return ADEMCO_PACKET_DATA_SEGMENT_EMPTY_LEN;
    } else {
        *p++ = '[';

        *p++ = '#';
        ademco_id %= ADEMCO_ID_SENTINEL;
        if (acct && acct[0] && ademco_id == 0) {
            for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *acct; i++) {
                *p++ = *acct++;
            }
        } else {
            p += ademco_dec_to_hex_str(p, ademco_id, 6);
        }

        *p++ = '|';

        *p++ = (uint8_t)(ademco_event / 1000) + '0';
        *p++ = (uint8_t)((ademco_event % 1000) / 100) + '0';
        *p++ = (uint8_t)((ademco_event % 100) / 10) + '0';
        *p++ = (uint8_t)(ademco_event % 10) + '0';

        *p++ = ' ';

        if (gg == 0xEE) {
            *p++ = 'E';
            *p++ = 'E';
        } else if (gg == 0xCC) {
            *p++ = 'C';
            *p++ = 'C';
        } else {
            *p++ = ((gg / 10) & 0x0F) + '0';
            *p++ = ((gg % 10) & 0x0F) + '0';
        }

        *p++ = ' ';

        zone %= ADEMCO_ZONE_SENTINEL;
        *p++ = (uint8_t)(zone / 100) + '0';
        *p++ = (uint8_t)((zone % 100) / 10) + '0';
        *p++ = (uint8_t)(zone % 10) + '0';

        *p++ = ']';

        // assert(p - (char*)buf == ADEMCO_PACKET_DATA_SEGMENT_FULL_LEN);

        *p = '\0';  // for debug convenience

        return p - (char*)buf;
    }
}

size_t ademco_append_data2(ademco_data_t* ademco_data, const char* acct,
                           ademco_id_t ademco_id, ademco_event_t ademco_event,
                           ademco_gg_t gg, ademco_zone_t zone) {
    ademco_data->ademco_id = ademco_id;
    ademco_data->ademco_event = ademco_event;
    ademco_data->gg = gg;
    ademco_data->zone = zone;
    return ademco_data->raw_len = ademco_append_data(ademco_data->raw,
                                                     acct, ademco_id,
                                                     ademco_event, gg, zone);
}

size_t ademco_append_data3(ademco_data_t* ademco_data, const char* acct) {
    char* p = (char*)ademco_data->raw;
    uint8_t i;
    if (ademco_data->ademco_event == EVENT_INVALID_EVENT) {
        *p++ = '[';
        *p++ = ']';
        return ademco_data->raw_len = ADEMCO_PACKET_DATA_SEGMENT_EMPTY_LEN;
    } else {
        *p++ = '[';

        *p++ = '#';
        ademco_data->ademco_id %= ADEMCO_ID_SENTINEL;
        if (acct && acct[0] && ademco_data->ademco_id == 0) {
            for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *acct; i++) {
                *p++ = *acct++;
            }
        } else {
            p += ademco_dec_to_hex_str(p, ademco_data->ademco_id, 6);
        }

        *p++ = '|';

        *p++ = (uint8_t)(ademco_data->ademco_event / 1000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_event % 1000) / 100) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_event % 100) / 10) + '0';
        *p++ = (uint8_t)(ademco_data->ademco_event % 10) + '0';

        *p++ = ' ';

        if (ademco_data->gg == 0xEE) {
            *p++ = 'E';
            *p++ = 'E';
        } else if (ademco_data->gg == 0xCC) {
            *p++ = 'C';
            *p++ = 'C';
        } else {
            *p++ = ((ademco_data->gg / 10) & 0x0F) + '0';
            *p++ = ((ademco_data->gg % 10) & 0x0F) + '0';
        }

        *p++ = ' ';

        ademco_data->zone %= ADEMCO_ZONE_SENTINEL;
        *p++ = (uint8_t)(ademco_data->zone / 100) + '0';
        *p++ = (uint8_t)((ademco_data->zone % 100) / 10) + '0';
        *p++ = (uint8_t)(ademco_data->zone % 10) + '0';

        *p++ = ']';

        // assert(p - (char*)buf == ADEMCO_PACKET_DATA_SEGMENT_FULL_LEN);

        *p = '\0';  // for debug convenience

        return ademco_data->raw_len = p - (char*)ademco_data->raw;
    }
}

ademco_parse_result_t ademco_parse_data(const ademco_char_t* packet, size_t packet_len,
                                        ademco_data_t* ademco_data,
                                        ademco_parse_error_t* err) {
    if (packet_len == ADEMCO_PACKET_DATA_SEGMENT_EMPTY_LEN &&
        packet[0] == '[' && packet[1] == ']') {
        memset(ademco_data, 0, sizeof(*ademco_data));
        strcpy((char*)ademco_data->raw, "[]");
        ademco_data->ademco_event = EVENT_INVALID_EVENT;
        ademco_data->ademco_id = 0;
        ademco_data->gg = 0;
        ademco_data->zone = 0;
        ademco_data->raw_len = 2;
        return ADEMCO_PARSE_RESULT_OK;
    } else if (packet_len >= ADEMCO_PACKET_DATA_SEGMENT_FULL_LEN &&
               packet[0] == '[' &&
               packet[packet_len - 1] == ']') {  // [#000000|1400 00 000]
        const char* p = packet + 2;
        size_t ADEMCO_BUF_MEMORY_MODIFIER acct_len = packet_len - 15;
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
        static
#endif
            char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_DATA_SEGMENT_FULL_LEN_MAX] = {0};
#endif

        if (acct_len < 6) {
            ADEMCO_FILL_PARSE_ERROR(err, p - packet, "acct_len < 6");
            return ADEMCO_PARSE_RESULT_ERROR;
        } else if (acct_len > ADEMCO_PACKET_ACCT_MAX_LEN) {
            ADEMCO_FILL_PARSE_ERROR(err, p - packet, "acct_len > ADEMCO_PACKET_ACCT_MAX_LEN");
            return ADEMCO_PARSE_RESULT_ERROR;
        }

        strncpy(buf, (const char*)p, acct_len);
        buf[acct_len] = '\0';
        ademco_data->ademco_id = (ademco_id_t)strtoul(buf, NULL, 16);
        if (ademco_data->ademco_id > ADEMCO_ID_SENTINEL) {
            // ademco_id maybe acct like "861234567890"
            ademco_data->ademco_id = ADEMCO_ID_INVALID;
		}
        p += acct_len;

        if (*p++ != '|') {
            ADEMCO_FILL_PARSE_ERROR(err, p - packet, "*p++ != '|'");
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        strncpy(buf, (const char*)p, 4);
        buf[4] = '\0';
        ademco_data->ademco_event = (ademco_event_t)(atoi(buf));
        p += 4;

        if (*p++ != ' ') {
            ADEMCO_FILL_PARSE_ERROR(err, p - packet, "*p++ != ' '");
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        if (*p == 'E' && *(p + 1) == 'E') {
            ademco_data->gg = 0xEE;
        } else if (*p == 'C' && *(p + 1) == 'C') {
            ademco_data->gg = 0xCC;
        } else {
            ademco_data->gg = (ademco_gg_t)((*p - '0') * 10 + (*(p + 1) - '0'));
        }
        p += 2;

        if (*p++ != ' ') {
            ADEMCO_FILL_PARSE_ERROR(err, p - packet, "*p++ != ' '");
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        strncpy(buf, (const char*)p, 3);
        buf[3] = '\0';
        ademco_data->zone = (ademco_zone_t)atoi(buf);

        if (ademco_data->raw != packet) {
            memcpy(ademco_data->raw, packet, packet_len);
        }
        ademco_data->raw_len = packet_len;
        return ADEMCO_PARSE_RESULT_OK;
    }

    ADEMCO_FILL_PARSE_ERROR(err, 0, "dig in source code for more info");
    return ADEMCO_PARSE_RESULT_ERROR;
}

size_t ademco_data_to_congwin_fe100(ademco_char_t* fe100, size_t fe100_len,
                                    const ademco_data_t* ademco_data) {
    if (ademco_data->raw_len == ADEMCO_PACKET_DATA_SEGMENT_EMPTY_LEN) {
        if (fe100_len < 2) return 0;
        fe100[0] = '\n';
        fe100[1] = '\r';
        return 2;
    } else if (ademco_data->raw_len >= ADEMCO_PACKET_DATA_SEGMENT_FULL_LEN) {
        char* p;
        if (fe100_len < CONGWIN_FE100_PACKET_LEN) return 0;

        p = fe100;
        *p++ = '\n';
        *p++ = ' ';

        *p++ = '0';  // RG
        *p++ = '0';
        *p++ = ' ';

        *p++ = (uint8_t)(ademco_data->ademco_id / 10000000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 10000000) / 1000000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 1000000) / 100000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 100000) / 10000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 10000) / 1000) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 1000) / 100) + '0';
        *p++ = (uint8_t)((ademco_data->ademco_id % 100) / 10) + '0';
        *p++ = (uint8_t)(ademco_data->ademco_id % 10) + '0';

        *p++ = ' ';  // 18
        *p++ = '1';
        *p++ = '8';
        *p++ = ' ';

        // E for open, R for close
        if (ademco_data->ademco_event / 1000 == 1 ||
            ademco_data->ademco_event == EVENT_ARM_STAY)
            *p++ = 'E';
        else
            *p++ = 'R';

        if (ademco_data->ademco_event == EVENT_OFFLINE) {
            *p++ = '3';
            *p++ = 'B';
            *p++ = '2';
        } else if (ademco_data->ademco_event == EVENT_ONLINE) {
            *p++ = '3';
            *p++ = 'B';
            *p++ = '0';
        } else {
            *p++ = '0' + (uint8_t)((ademco_data->ademco_event % 1000) / 100);
            *p++ = '0' + ((ademco_data->ademco_event % 100) / 10);
            *p++ = '0' + (ademco_data->ademco_event % 10);
        }

        *p++ = ' ';  // FE100, gg === 0
        *p++ = '0';
        *p++ = '0';
        *p++ = ' ';

        if (ademco_is_machine_status_event(ademco_data->ademco_event))
            *p++ = 'U';  // user event
        else
            *p++ = 'C';  // zone event

        *p++ = '0' + (uint8_t)((ademco_data->zone % 1000) / 100);
        *p++ = '0' + ((ademco_data->zone % 100) / 10);
        *p++ = '0' + (ademco_data->zone % 10);

        *p++ = ' ';
        *p++ = '\r';
        return p - fe100;
    }
    return 0;
}

void ademco_xdata_init(ademco_xdata_t* xdat) {
    memset(xdat, 0, sizeof *xdat);
}

bool ademco_xdata_convert(ademco_xdata_t* xdat,
                          ademco_xdata_length_format_t xlf) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_XDATA_MAX_LEN];
#endif
    size_t ADEMCO_BUF_MEMORY_MODIFIER len;

    if (!xdat) {
        return false;
    }
    if (xdat->lenghFormat == xlf) {
        return false;
    }
    len = ademco_xdata_get_valid_content_len(xdat);

    if (xlf == ADEMCO_XDATA_LENGTH_FMT_TWO_HEX) {
        if (len + 6 > ADEMCO_PACKET_XDATA_MAX_LEN)
            return false;
        xdat->raw[5 + ademco_xdata_get_valid_content_len(xdat)] = '\0';
        len = ademco_hex_str_to_array((uint8_t*)buf,
                                      ademco_xdata_get_valid_content_addr(xdat),
                                      0x0F);
        ademco_make_xdata(xdat,
                          ADEMCO_XDATA_LENGTH_FMT_TWO_HEX,
                          ADEMCO_XDATA_TRANSFORM_AS_IS,
                          buf, len);
    } else {
        memcpy(buf, ademco_xdata_get_valid_content_addr(xdat), len);
        ademco_make_xdata(xdat,
                          ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL,
                          ADEMCO_XDATA_TRANSFORM_TO_ASCII,
                          buf, len);
    }
    return true;
}

#ifdef SWIG
static
#endif
const char* ademco_xdata_get_valid_content_addr(const ademco_xdata_t* xdat) {
    if (xdat->lenghFormat == ADEMCO_XDATA_LENGTH_FMT_TWO_HEX &&
        xdat->raw_len > 4)
        return xdat->raw + 3;
    else if (xdat->lenghFormat == ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL &&
             xdat->raw_len > 6)
        return xdat->raw + 5;
    else
        return NULL;
}

bool ademco_xdata_memeq(const ademco_xdata_t* xdat,
                        const void* buf, size_t buf_len) {
    const void* p;
    if (ademco_xdata_get_valid_content_len(xdat) != buf_len) return false;
    p = ademco_xdata_get_valid_content_addr(xdat);
    if (!p) return 0;
    return memcmp(p, buf, buf_len) == 0;
}

size_t ademco_xdata_get_valid_content_len(const ademco_xdata_t* xdat) {
    if (!xdat) return 0;
    if (xdat->lenghFormat == ADEMCO_XDATA_LENGTH_FMT_TWO_HEX &&
        xdat->raw_len > 4)
        return xdat->raw_len - 4;
    else if (xdat->lenghFormat == ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL &&
             xdat->raw_len > 6)
        return xdat->raw_len - 6;
    else
        return 0;
}

size_t ademco_xdata_copy(ademco_xdata_t* dst, const ademco_xdata_t* src) {
    if (dst && src && src->raw_len > 0) {
        memcpy(dst, src, sizeof(ademco_xdata_t));
        return src->raw_len;
    }
    return 0;
}

bool ademco_make_xdata(ademco_xdata_t* xdat,
                       ademco_xdata_length_format_t xlf,
                       ademco_xdata_transform_t xtr,
                       const ademco_char_t* content, size_t len) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_XDATA_MAX_LEN];
#endif
    uint32_t ADEMCO_BUF_MEMORY_MODIFIER translen;
    len &= 0xFFFF;
    translen = len & 0xFFFF;

    if (xtr == ADEMCO_XDATA_TRANSFORM_AS_IS)
        memcpy(buf, content, len);
    else if (len * 2 < ADEMCO_PACKET_XDATA_MAX_LEN)
        translen = ademco_hex_array_to_str(buf,
                                           (const uint8_t*)content, len) &
                   0xFFFF;
    else
        return false;

    xdat->lenghFormat = xlf;
    if (xlf == ADEMCO_XDATA_LENGTH_FMT_TWO_HEX &&
        translen + 4 < ADEMCO_PACKET_XDATA_MAX_LEN) {
        ademco_char_t* p = xdat->raw;
        *p++ = '[';
        *p++ = (translen & 0xFFFF) >> 8;
        *p++ = (translen & 0xFFFF) & 0xFF;
        memcpy(p, buf, translen);
        p += translen;
        *p++ = ']';
        xdat->raw_len = p - xdat->raw;
        return true;
    } else if (xlf == ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL &&
               translen + 6 < ADEMCO_PACKET_XDATA_MAX_LEN) {
        ademco_char_t* p = xdat->raw;
        *p++ = '[';
        p += ademco_dec_to_hex_str(p, translen, 4);
        memcpy(p, buf, translen);
        p += translen;
        *p++ = ']';
        xdat->raw_len = p - xdat->raw;
        return true;
    } else
        return false;
}

bool ademco_is_valid_packet_id(const char* standard, const char* id, size_t len) {
    while (*standard && len--) {
        if (*standard++ != *id++)
            return false;
    }
    return true;
}

ademco_packet_id_t ademco_packet_id_from_string(const char* id, size_t len) {
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_NULL, id, len))
        return AID_NULL;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_ACK, id, len))
        return AID_ACK;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_HB, id, len))
        return AID_HB;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_ADM_CID, id, len))
        return AID_ADM_CID;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_NAK, id, len))
        return AID_NAK;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_DUH, id, len))
        return AID_DUH;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_MOD_REG, id, len))
        return AID_MOD_REG;
    if (ademco_is_valid_packet_id(ADEMCO_PACKET_ID_REG_RSP, id, len))
        return AID_REG_RSP;

    return AID_INVALID;
}

const char* ademco_packet_id_to_string(ademco_packet_id_t id) {
    switch (id) {
        case AID_NULL:
            return ADEMCO_PACKET_ID_NULL;
        case AID_ACK:
            return ADEMCO_PACKET_ID_ACK;
        case AID_NAK:
            return ADEMCO_PACKET_ID_NAK;
        case AID_DUH:
            return ADEMCO_PACKET_ID_DUH;
        case AID_HB:
            return ADEMCO_PACKET_ID_HB;
        case AID_ADM_CID:
            return ADEMCO_PACKET_ID_ADM_CID;
        case AID_MOD_REG:
            return ADEMCO_PACKET_ID_MOD_REG;
        case AID_REG_RSP:
            return ADEMCO_PACKET_ID_REG_RSP;
        default:
            return NULL;
    }
}

static void getNowTimestamp(char* buf) {
    time_t ADEMCO_BUF_MEMORY_MODIFIER now = time(NULL);

#if ADEMCO_DISABLE_GMTIME
    struct tm ADEMCO_BUF_MEMORY_MODIFIER tm = {0};
    _localtime_r(&now, &tm);
#else
    struct tm ADEMCO_BUF_MEMORY_MODIFIER tm = *localtime(&now);
#endif  // ADEMCO_DISABLE_GMTIME
    tm.tm_mon += 1;
    tm.tm_year += 1900;
    *buf++ = '_';
    *buf++ = (uint8_t)(tm.tm_hour / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_hour % 10) + '0';
    *buf++ = ':';
    *buf++ = (uint8_t)(tm.tm_min / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_min % 10) + '0';
    *buf++ = ':';
    *buf++ = (uint8_t)(tm.tm_sec / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_sec % 10) + '0';
    *buf++ = ',';
    *buf++ = (uint8_t)(tm.tm_mon / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_mon % 10) + '0';
    *buf++ = '-';
    *buf++ = (uint8_t)(tm.tm_mday / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_mday % 10) + '0';
    *buf++ = '-';
    *buf++ = (uint8_t)(tm.tm_year / 1000) + '0';
    *buf++ = (uint8_t)((tm.tm_year % 1000) / 100) + '0';
    *buf++ = (uint8_t)((tm.tm_year % 100) / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_year % 10) + '0';
    *buf = '\0';
}

static void getGmtTimestamp(char* buf) {
#ifdef ADEMCO_DISABLE_GMTIME
    getNowTimestamp(buf);
#else
    time_t ADEMCO_BUF_MEMORY_MODIFIER now = time(NULL);
    struct tm ADEMCO_BUF_MEMORY_MODIFIER tm = *gmtime(&now);
    tm.tm_mon += 1;
    tm.tm_year += 1900;
    *buf++ = '_';
    *buf++ = (uint8_t)(tm.tm_hour / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_hour % 10) + '0';
    *buf++ = ':';
    *buf++ = (uint8_t)(tm.tm_min / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_min % 10) + '0';
    *buf++ = ':';
    *buf++ = (uint8_t)(tm.tm_sec / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_sec % 10) + '0';
    *buf++ = ',';
    *buf++ = (uint8_t)(tm.tm_mon / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_mon % 10) + '0';
    *buf++ = '-';
    *buf++ = (uint8_t)(tm.tm_mday / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_mday % 10) + '0';
    *buf++ = '-';
    *buf++ = (uint8_t)(tm.tm_year / 1000) + '0';
    *buf++ = (uint8_t)((tm.tm_year % 1000) / 100) + '0';
    *buf++ = (uint8_t)((tm.tm_year % 100) / 10) + '0';
    *buf++ = (uint8_t)(tm.tm_year % 10) + '0';
    *buf = '\0';
#endif
}

size_t ademco_make_empty_data_packet(ademco_char_t* dst_buff, size_t len,
                                     const char* id, uint16_t seq,
                                     const char* acct, ademco_id_t ademco_id) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif
    char ADEMCO_BUF_MEMORY_MODIFIER* p = buf;
    char ADEMCO_BUF_MEMORY_MODIFIER* pcrc = buf + 1;
    char ADEMCO_BUF_MEMORY_MODIFIER* plen = buf + 5;
    char ADEMCO_BUF_MEMORY_MODIFIER* pid = buf + 9;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER packet_len, ademco_len;
    uint8_t i;

    buf[0] = ADEMCO_PACKET_PREFIX;
    memcpy(pid, id, strlen(id));
    p = pid + strlen(id);
    seq %= 10000;
    *p++ = (uint8_t)(seq / 1000) + '0';
    *p++ = (uint8_t)((seq % 1000) / 100) + '0';
    *p++ = (uint8_t)((seq % 100) / 10) + '0';
    *p++ = (uint8_t)(seq % 10) + '0';
    memcpy(p, ADEMCO_RRCVR_DEFAULT, strlen(ADEMCO_RRCVR_DEFAULT));
    p += strlen(ADEMCO_RRCVR_DEFAULT);
    memcpy(p, ADEMCO_LPREF_DEFAULT, strlen(ADEMCO_LPREF_DEFAULT));
    p += strlen(ADEMCO_LPREF_DEFAULT);
    *p++ = '#';
    if (acct && acct[0]) {
        for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *acct; i++) {
            *p++ = *acct++;
        }
    } else {
        p += ademco_dec_to_hex_str(p, ademco_id, 6);
    }
    *p++ = '[';
    *p++ = ']';
    getNowTimestamp((char*)p);
    p += ADEMCO_PACKET_TIMESTAMP_LEN;
    *p++ = ADEMCO_PACKET_SUFIX;

    packet_len = (p - buf) & 0xFFFF;
    //                       \n  crc len  \r
    ademco_len = packet_len - 1 - 4 - 4 - 1;
    ademco_dec_to_hex_str(plen, ademco_len, 4);
    crc = ademco_crc16(pid, ademco_len);
    ademco_dec_to_hex_str(pcrc, crc, 4);

    if (dst_buff != NULL && len > packet_len) {
        memcpy(dst_buff, buf, packet_len);
        dst_buff[packet_len] = '\0';  // for debug convenience
        return packet_len;
    } else
        return 0;
}

size_t ademco_make_adm_empty_data_packet(ademco_char_t* dst_buff, size_t len,
                                         const char* id, uint16_t seq,
                                         const char* acct, ademco_id_t ademco_id) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif
    char ADEMCO_BUF_MEMORY_MODIFIER* p = buf;
    char ADEMCO_BUF_MEMORY_MODIFIER* pcrc = buf + 1;
    char ADEMCO_BUF_MEMORY_MODIFIER* plen = buf + 5;
    char ADEMCO_BUF_MEMORY_MODIFIER* pid = buf + 9;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER packet_len, ademco_len;
    uint8_t i;

    buf[0] = ADEMCO_PACKET_PREFIX;
    memcpy(pid, id, strlen(id));
    p = pid + strlen(id);
    seq %= 10000;
    *p++ = (uint8_t)(seq / 1000) + '0';
    *p++ = (uint8_t)((seq % 1000) / 100) + '0';
    *p++ = (uint8_t)((seq % 100) / 10) + '0';
    *p++ = (uint8_t)(seq % 10) + '0';
    p += 4;
    memcpy(p, ADEMCO_RRCVR_DEFAULT, strlen(ADEMCO_RRCVR_DEFAULT));
    p += strlen(ADEMCO_RRCVR_DEFAULT);
    memcpy(p, ADEMCO_LPREF_DEFAULT, strlen(ADEMCO_LPREF_DEFAULT));
    p += strlen(ADEMCO_LPREF_DEFAULT);
    *p++ = '#';
    if (acct && acct[0]) {
        for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *acct; i++) {
            *p++ = *acct++;
        }
    } else {
        p += ademco_dec_to_hex_str(p, ademco_id, 6);
    }
    *p++ = '[';
    *p++ = ']';
    getGmtTimestamp((char*)p);
    p += ADEMCO_PACKET_TIMESTAMP_LEN;
    *p++ = ADEMCO_PACKET_SUFIX;

    packet_len = (p - buf) & 0xFFFF;
    //                       \n  crc len  \r
    ademco_len = packet_len - 1 - 4 - 4 - 1;
    ademco_dec_to_hex_str(plen, ademco_len, 4);
    crc = ademco_crc16(pid, ademco_len);
    ademco_dec_to_hex_str(pcrc, crc, 4);

    if (dst_buff != NULL && len > packet_len) {
        memcpy(dst_buff, buf, packet_len);
        dst_buff[packet_len] = '\0';  // for debug convenience
        return packet_len;
    } else
        return 0;
}

size_t ademco_make_null_packet(ademco_char_t* buf, size_t len,
                               uint16_t seq, const char* acct,
                               ademco_id_t ademco_id) {
    return ademco_make_empty_data_packet(buf, len,
                                         ADEMCO_PACKET_ID_NULL,
                                         seq, acct, ademco_id);
}

size_t ademco_make_ack_packet(ademco_char_t* buf, size_t len,
                              uint16_t seq, const char* acct,
                              ademco_id_t ademco_id) {
    return ademco_make_empty_data_packet(buf, len,
                                         ADEMCO_PACKET_ID_ACK,
                                         seq, acct, ademco_id);
}

size_t ademco_make_nak_packet(ademco_char_t* buf, size_t len,
                              uint16_t seq, const char* acct,
                              ademco_id_t ademco_id) {
    return ademco_make_empty_data_packet(buf, len,
                                         ADEMCO_PACKET_ID_NAK,
                                         seq, acct, ademco_id);
}

size_t ademco_make_hb_packet(ademco_char_t* dst_buff, size_t len,
                             uint16_t seq, const char* acct,
                             ademco_id_t ademco_id, ademco_event_t ademco_event,
                             ademco_gg_t gg, ademco_zone_t zone,
                             const ademco_xdata_t* xdat) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif
    char ADEMCO_BUF_MEMORY_MODIFIER* p = buf;
    char ADEMCO_BUF_MEMORY_MODIFIER* pcrc = buf + 1;
    char ADEMCO_BUF_MEMORY_MODIFIER* plen = buf + 5;
    char ADEMCO_BUF_MEMORY_MODIFIER* pid = buf + 9;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER packet_len, ademco_len;
    uint8_t i;

    buf[0] = ADEMCO_PACKET_PREFIX;
    memcpy(pid, ADEMCO_PACKET_ID_HB, strlen(ADEMCO_PACKET_ID_HB));
    p = pid + strlen(ADEMCO_PACKET_ID_HB);
    seq %= 10000;
    *p++ = (uint8_t)(seq / 1000) + '0';
    *p++ = (uint8_t)((seq % 1000) / 100) + '0';
    *p++ = (uint8_t)((seq % 100) / 10) + '0';
    *p++ = (uint8_t)(seq % 10) + '0';
    memcpy(p, ADEMCO_RRCVR_DEFAULT, strlen(ADEMCO_RRCVR_DEFAULT));
    p += strlen(ADEMCO_RRCVR_DEFAULT);
    memcpy(p, ADEMCO_LPREF_DEFAULT, strlen(ADEMCO_LPREF_DEFAULT));
    p += strlen(ADEMCO_LPREF_DEFAULT);
    *p++ = '#';
    if (acct && acct[0]) {
        char ADEMCO_BUF_MEMORY_MODIFIER* src = (char*)acct;
        for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *src; i++) {
            *p++ = *src++;
        }
    } else {
        p += ademco_dec_to_hex_str(p, ademco_id, 6);
    }
    p += ademco_append_data(p, acct, ademco_id, ademco_event, gg, zone);
    if (xdat && xdat->raw_len > 0) {
        memcpy(p, xdat->raw, xdat->raw_len);
        p += xdat->raw_len;
    }
    getNowTimestamp(p);
    p += ADEMCO_PACKET_TIMESTAMP_LEN;
    *p++ = ADEMCO_PACKET_SUFIX;

    packet_len = (p - buf) & 0xFFFF;
    //                       \n  crc len  \r
    ademco_len = packet_len - 1 - 4 - 4 - 1;
    ademco_dec_to_hex_str(plen, ademco_len, 4);
    crc = ademco_crc16(pid, ademco_len);
    ademco_dec_to_hex_str(pcrc, crc, 4);

    if (dst_buff != NULL && len > packet_len) {
        memcpy(dst_buff, buf, packet_len);
        dst_buff[packet_len] = '\0';  // for debug convenience
        return packet_len;
    }
    return 0;
}

size_t ademco_make_adm_packet(ademco_char_t* dst_buff, size_t len,
                              uint16_t seq, const char* acct,
                              ademco_id_t ademco_id,
                              ademco_event_t ademco_event,
                              ademco_gg_t gg, ademco_zone_t zone,
                              const ademco_xdata_t* xdat) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif
    char ADEMCO_BUF_MEMORY_MODIFIER* p = buf;
    char ADEMCO_BUF_MEMORY_MODIFIER* pcrc = buf + 1;
    char ADEMCO_BUF_MEMORY_MODIFIER* plen = buf + 5;
    char ADEMCO_BUF_MEMORY_MODIFIER* pid = buf + 9;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER packet_len, ademco_len;
    uint8_t i;

    buf[0] = ADEMCO_PACKET_PREFIX;
    memcpy(pid, ADEMCO_PACKET_ID_ADM_CID, strlen(ADEMCO_PACKET_ID_ADM_CID));
    p = pid + strlen(ADEMCO_PACKET_ID_ADM_CID);
    seq %= 10000;
    *p++ = (uint8_t)(seq / 1000) + '0';
    *p++ = (uint8_t)((seq % 1000) / 100) + '0';
    *p++ = (uint8_t)((seq % 100) / 10) + '0';
    *p++ = (uint8_t)(seq % 10) + '0';
    memcpy(p, ADEMCO_RRCVR_DEFAULT, strlen(ADEMCO_RRCVR_DEFAULT));
    p += strlen(ADEMCO_RRCVR_DEFAULT);
    memcpy(p, ADEMCO_LPREF_DEFAULT, strlen(ADEMCO_LPREF_DEFAULT));
    p += strlen(ADEMCO_LPREF_DEFAULT);
    *p++ = '#';
    if (acct && acct[0]) {
        char ADEMCO_BUF_MEMORY_MODIFIER* src = (char*)acct;
        for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && *src; i++) {
            *p++ = *src++;
        }
    } else {
        p += ademco_dec_to_hex_str(p, ademco_id, 6);
    }
    p += ademco_append_data(p, acct, ademco_id, ademco_event, gg, zone);
    if (xdat && xdat->raw_len > 0) {
        memcpy(p, xdat->raw, xdat->raw_len);
        p += xdat->raw_len;
    }
    getGmtTimestamp(p);
    p += ADEMCO_PACKET_TIMESTAMP_LEN;
    *p++ = ADEMCO_PACKET_SUFIX;

    packet_len = (p - buf) & 0xFFFF;
    //                       \n  crc len  \r
    ademco_len = packet_len - 1 - 4 - 4 - 1;
    ademco_dec_to_hex_str(plen, ademco_len, 4);
    crc = ademco_crc16(pid, ademco_len);
    ademco_dec_to_hex_str(pcrc, crc, 4);

    if (dst_buff != NULL && len > packet_len) {
        memcpy(dst_buff, buf, packet_len);
        dst_buff[packet_len] = '\0';  // for debug convenience
        return packet_len;
    }
    return 0;
}

static void copyAcct2AdemcoPacket(ademco_packet_t* pkt, const char* acct) {
    if (acct) {
        size_t ADEMCO_BUF_MEMORY_MODIFIER len = strlen(acct);
        if (len > ADEMCO_PACKET_ACCT_MAX_LEN)
            len = ADEMCO_PACKET_ACCT_MAX_LEN;
        memcpy(pkt->acct, acct, len);
        pkt->acct[len] = '\0';
    } else
        pkt->acct[0] = '\0';
}

size_t ademco_make_null_packet2(ademco_packet_t* pkt, uint16_t seq,
                                const char* acct, ademco_id_t ademco_id) {
    pkt->seq = seq;
    copyAcct2AdemcoPacket(pkt, acct);
    pkt->dat.ademco_id = ademco_id;
    pkt->id = AID_NULL;
    return pkt->raw_len = ademco_make_null_packet(pkt->raw, sizeof(pkt->raw),
                                                  seq, acct, ademco_id);
}

size_t ademco_make_ack_packet2(ademco_packet_t* pkt, uint16_t seq,
                               const char* acct, ademco_id_t ademco_id) {
    pkt->seq = seq;
    copyAcct2AdemcoPacket(pkt, acct);
    pkt->dat.ademco_id = ademco_id;
    pkt->id = AID_ACK;
    return pkt->raw_len = ademco_make_ack_packet(pkt->raw, sizeof(pkt->raw),
                                                 seq, acct, ademco_id);
}

size_t ademco_make_nak_packet2(ademco_packet_t* pkt, uint16_t seq,
                               const char* acct, ademco_id_t ademco_id) {
    pkt->seq = seq;
    copyAcct2AdemcoPacket(pkt, acct);
    pkt->dat.ademco_id = ademco_id;
    pkt->id = AID_NAK;
    return pkt->raw_len = ademco_make_nak_packet(pkt->raw, sizeof(pkt->raw),
                                                 seq, acct, ademco_id);
}

size_t ademco_make_hb_packet2(ademco_packet_t* pkt, uint16_t seq,
                              const char* acct, ademco_id_t ademco_id,
                              ademco_event_t ademco_event, ademco_gg_t gg,
                              ademco_zone_t zone,
                              const ademco_xdata_t* xdat) {
    pkt->seq = seq;
    copyAcct2AdemcoPacket(pkt, acct);
    ademco_append_data2(&pkt->dat, acct, ademco_id, ademco_event, gg, zone);
    if (xdat && xdat->raw_len > 0) {
        pkt->xdat.lenghFormat = xdat->lenghFormat;
        memcpy(pkt->xdat.raw, xdat->raw, xdat->raw_len);
        pkt->xdat.raw_len = xdat->raw_len;
    } else
        memset(&pkt->xdat, 0, sizeof(*xdat));
    pkt->id = AID_HB;
    return pkt->raw_len = ademco_make_hb_packet(pkt->raw, sizeof(pkt->raw),
                                                seq, acct, ademco_id,
                                                ademco_event,
                                                gg, zone, xdat);
}

size_t ademco_make_hb_packet3(ademco_packet_t* pkt) {
#if !ADEMCO_USE_UNIQUE_BUF
#if ADEMCO_USE_STATIC_BUF
    static
#endif
        char ADEMCO_BUF_MEMORY_MODIFIER buf[ADEMCO_PACKET_MAX_LEN];
#endif
    char ADEMCO_BUF_MEMORY_MODIFIER* p = buf;
    char ADEMCO_BUF_MEMORY_MODIFIER* pcrc = buf + 1;
    char ADEMCO_BUF_MEMORY_MODIFIER* plen = buf + 5;
    char ADEMCO_BUF_MEMORY_MODIFIER* pid = buf + 9;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER packet_len, ademco_len;
    uint8_t i;

    buf[0] = ADEMCO_PACKET_PREFIX;
    memcpy(pid, ADEMCO_PACKET_ID_HB, strlen(ADEMCO_PACKET_ID_HB));
    p = pid + strlen(ADEMCO_PACKET_ID_HB);
    *p++ = (uint8_t)(pkt->seq / 1000) + '0';
    *p++ = (uint8_t)((pkt->seq % 1000) / 100) + '0';
    *p++ = (uint8_t)((pkt->seq % 100) / 10) + '0';
    *p++ = (uint8_t)(pkt->seq % 10) + '0';
    memcpy(p, ADEMCO_RRCVR_DEFAULT, strlen(ADEMCO_RRCVR_DEFAULT));
    p += strlen(ADEMCO_RRCVR_DEFAULT);
    memcpy(p, ADEMCO_LPREF_DEFAULT, strlen(ADEMCO_LPREF_DEFAULT));
    p += strlen(ADEMCO_LPREF_DEFAULT);
    *p++ = '#';
    if (pkt->acct[0] != '\0') {
        for (i = 0; i < ADEMCO_PACKET_ACCT_MAX_LEN && pkt->acct[i]; i++) {
            *p++ = pkt->acct[i];
        }
    } else {
        p += ademco_dec_to_hex_str(p, pkt->dat.ademco_id, 6);
    }
    ademco_append_data3(&pkt->dat, pkt->acct);
    memcpy(p, pkt->dat.raw, pkt->dat.raw_len);
    p += pkt->dat.raw_len;
    if (pkt->xdat.raw_len > 0) {
        memcpy(p, pkt->xdat.raw, pkt->xdat.raw_len);
        p += pkt->xdat.raw_len;
    }
    getNowTimestamp(p);
    p += ADEMCO_PACKET_TIMESTAMP_LEN;
    *p++ = ADEMCO_PACKET_SUFIX;

    packet_len = (p - buf) & 0xFFFF;
    //                       \n  crc len  \r
    ademco_len = packet_len - 1 - 4 - 4 - 1;
    ademco_dec_to_hex_str(plen, ademco_len, 4);
    crc = ademco_crc16(pid, ademco_len);
    ademco_dec_to_hex_str(pcrc, crc, 4);

    memcpy(pkt->raw, buf, packet_len);
    pkt->raw[packet_len] = '\0';  // for debug convenience
    return pkt->raw_len = packet_len;
}

size_t ademco_make_adm_packet2(ademco_packet_t* pkt, uint16_t seq,
                               const char* acct, ademco_id_t ademco_id,
                               ademco_event_t ademco_event, ademco_gg_t gg,
                               ademco_zone_t zone,
                               const ademco_xdata_t* xdat) {
    pkt->seq = seq;
    copyAcct2AdemcoPacket(pkt, acct);
    ademco_append_data2(&pkt->dat, acct, ademco_id, ademco_event, gg, zone);
    if (xdat && xdat->raw_len > 0) {
        pkt->xdat.lenghFormat = xdat->lenghFormat;
        memcpy(pkt->xdat.raw, xdat->raw, xdat->raw_len);
        pkt->xdat.raw_len = xdat->raw_len;
    } else
        memset(&pkt->xdat, 0, sizeof(*xdat));
    pkt->id = AID_ADM_CID;
    return pkt->raw_len = ademco_make_adm_packet(pkt->raw, sizeof(pkt->raw),
                                                 seq, acct, ademco_id,
                                                 ademco_event,
                                                 gg, zone, xdat);
}

ademco_parse_result_t ademco_parse_packet(const ademco_char_t* buf,
                                          size_t len,
                                          ademco_packet_t* pkt,
                                          size_t* cb_commited,
                                          ademco_parse_error_t* err) {
    const char *p, *q, *pid, *pend, *pacct, *pdat;
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER h;
    uint16_t ADEMCO_BUF_MEMORY_MODIFIER crc;
    size_t ADEMCO_BUF_MEMORY_MODIFIER len_needed;

    if (len < 9) {
        ADEMCO_FILL_PARSE_ERROR(err, 0, "ADEMCO_PARSE_RESULT_NOT_ENOUGH");
        return ADEMCO_PARSE_RESULT_NOT_ENOUGH;
    }

    *cb_commited = 0;
    while (*buf != ADEMCO_PACKET_PREFIX && len > 0) {
        buf++;
        len--;
        (*cb_commited)++;
    }

    if (*cb_commited > 0) {
        ADEMCO_FILL_PARSE_ERROR(err, 0, "fixed prefix not found");
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    p = buf;
    if (*p++ != ADEMCO_PACKET_PREFIX) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "fixed prefix not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    // crc
    crc = 0;
    for (q = p; p - q < 4; p++) {
        h = ademco_char2hex(*p);
        if (h == 0xFF) {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "crc contains non-hex characters");
            *cb_commited += p - buf;
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        crc = (crc << 4) | h;
    }

    // len
    pkt->len = 0;
    for (q = p; p - q < 4; p++) {
        h = ademco_char2hex(*p);
        if (h == 0xFF) {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "len contains non-hex characters");
            *cb_commited += p - buf;
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        pkt->len = (pkt->len << 4) | h;
    }
    len_needed = 9 + pkt->len + 1;
    if (len < len_needed) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "ADEMCO_PARSE_RESULT_NOT_ENOUGH");
        return ADEMCO_PARSE_RESULT_NOT_ENOUGH;
    } else if (len_needed >= ADEMCO_PACKET_MAX_LEN) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "len_needed >= ADEMCO_PACKET_MAX_LEN");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    pid = p;
    pend = pid + pkt->len;

    if (pend > buf + len) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "suffix out of range");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    if (*pend != ADEMCO_PACKET_SUFIX) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "suffix not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    pkt->crc = ademco_crc16(pid, pkt->len);
    if (pkt->crc != crc) {
        ADEMCO_FILL_PARSE_ERROR(err, 1, "crc error");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    // id
    if (*pid != '\"') {
        ADEMCO_FILL_PARSE_ERROR(err, pid - buf, "left \" of id not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    p = pid + 1;
    while (p < pend && *p != '\"') {
        p++;
    }
    if (p >= pend || *p != '\"') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "right \" of id not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    pkt->id = ademco_packet_id_from_string((const char*)pid, ++p - pid);
    if (pkt->id == AID_INVALID) {
        ADEMCO_FILL_PARSE_ERROR(err, pid - buf, "unknown ademco id");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    // seq
    pkt->seq = 0;
    for (q = p; p - q < 4; p++) {
        if ('0' <= *p && *p <= '9') {
            pkt->seq = (pkt->seq * 10) + (*p - '0');
        } else {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "seq contains non-digit characters");
            // some apps use FFFF, wtf
            // return ADEMCO_PARSE_RESULT_ERROR;
        }
    }
    if (pkt->seq >= 10000) {
        pkt->seq = 0;
    }

    // *rrcvr
    if (*p == 'R') {  // rrcvr exists
        // const char* prcvr = p;
        while (p < pend && *p != 'L' && *p != '#') {
            p++;
        }
        if (p >= pend || (*p != 'L' && *p != '#')) {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "p >= pend || (*p != 'L' && *p != '#')");
            *cb_commited += p - buf;
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        // only check if format is correct, ignore it's content
    } else if (*p == 'L') {  // rrcvr not exits
                             // pass
    } else {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "both LPREF and RRCVR not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    // lpref
    if (*p != 'L') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "LPREF not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    while (p < pend && *p != '#') {
        p++;
    }
    // only check if format is correct, ignore it's content
    if (p >= pend || *p != '#') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "# not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    // lpref passed

    // #acct
    pacct = ++p;
    while (p - pacct < ADEMCO_PACKET_ACCT_MAX_LEN && *p != '[') {
        if (!isxdigit(*p)) {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "acct contains non-hex characters");
            *cb_commited += p - buf;
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        p++;
    }
    if (*p != '[') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "acct too long");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    strncpy(pkt->acct, (const char*)pacct, p - pacct);
    pkt->acct[p - pacct] = '\0';

    // dat
    pdat = p;
    if (*p != '[') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "dat's [ not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    while (p < pend && *p != ']') {
        p++;
    }
    if (p >= pend || *p != ']') {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "dat's ] not found");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    if (ademco_parse_data(pdat, ++p - pdat, &pkt->dat, err) != ADEMCO_PARSE_RESULT_OK) {
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    // *xdat
    if (*p == '[') {  // xdat exists
        const char* pxdata = p++;
        ademco_xdata_length_format_t ADEMCO_BUF_MEMORY_MODIFIER xlf = ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL;
        size_t ADEMCO_BUF_MEMORY_MODIFIER valid_len = 0;
        uint8_t ADEMCO_BUF_MEMORY_MODIFIER i;
        for (i = 0; i < 4; i++)
            if (!isxdigit(*(uint8_t*)(p + i)))
                xlf = ADEMCO_XDATA_LENGTH_FMT_TWO_HEX;

        if (xlf == ADEMCO_XDATA_LENGTH_FMT_FOUR_DECIMAL) {
            valid_len = (ademco_char2hex(*(pxdata + 1)) << 12) |
                        (ademco_char2hex(*(pxdata + 2)) << 8) |
                        (ademco_char2hex(*(pxdata + 3)) << 4) |
                        (ademco_char2hex(*(pxdata + 4)));
            p += 4 + valid_len;
        } else {
            valid_len = (pxdata[1] << 8) | pxdata[2];
            p += 2 + valid_len;
        }

        if (p >= pend || *p != ']' || *(p + 1) != '_') {
            ADEMCO_FILL_PARSE_ERROR(err, p - buf, "xdat's ] not found or next char is not _");
            *cb_commited += p - buf;
            return ADEMCO_PARSE_RESULT_ERROR;
        }
        pkt->xdat.lenghFormat = xlf;
        pkt->xdat.raw_len = ++p - pxdata;
        memcpy(pkt->xdat.raw, pxdata, pkt->xdat.raw_len);
    } else {
        pkt->xdat.raw_len = 0;
    }

    // timestamp, _%02d:%02d:%02d,%02d-%02d-%04d
    // only check lengh, if format is incorrect, use local time instead
    if (pend - p == ADEMCO_PACKET_TIMESTAMP_LEN) {
        struct tm ADEMCO_BUF_MEMORY_MODIFIER tm = {0};
        pkt->timestamp = 0;
        do {
            if (*p++ != '_') {
                break;
            }
            tm.tm_hour = ademco_char2hex(*p++);
            tm.tm_hour = tm.tm_hour * 10 + ademco_char2hex(*p++);
            if (*p++ != ':') {
                break;
            }

            tm.tm_min = ademco_char2hex(*p++);
            tm.tm_min = tm.tm_min * 10 + ademco_char2hex(*p++);
            if (*p++ != ':') {
                break;
            }

            tm.tm_sec = ademco_char2hex(*p++);
            tm.tm_sec = tm.tm_sec * 10 + ademco_char2hex(*p++);
            if (*p++ != ',') {
                break;
            }

            tm.tm_mon = ademco_char2hex(*p++);
            tm.tm_mon = tm.tm_mon * 10 + ademco_char2hex(*p++);
            if (*p++ != '-') {
                break;
            }

            tm.tm_mday = ademco_char2hex(*p++);
            tm.tm_mday = tm.tm_mday * 10 + ademco_char2hex(*p++);
            if (*p++ != '-') {
                break;
            }

            tm.tm_year = ademco_char2hex(*p++);
            tm.tm_year = tm.tm_year * 10 + ademco_char2hex(*p++);
            tm.tm_year = tm.tm_year * 10 + ademco_char2hex(*p++);
            tm.tm_year = tm.tm_year * 10 + ademco_char2hex(*p++);

            tm.tm_year -= 1900;
            tm.tm_mon--;
            tm.tm_isdst = -1;
            pkt->timestamp = mktime(&tm);
        } while (0);

        if (pkt->timestamp <= 0) {  // use local time instead
            pkt->timestamp = time(NULL);
            p = pend;
        }
    } else {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "timestamp length not correct");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }

    if (p++ != pend) {
        ADEMCO_FILL_PARSE_ERROR(err, p - buf, "packet length not correct");
        *cb_commited += p - buf;
        return ADEMCO_PARSE_RESULT_ERROR;
    }
    pkt->raw_len = *cb_commited = p - buf;
    if (pkt->raw != buf)
        memcpy(pkt->raw, buf, pkt->raw_len);
    return ADEMCO_PARSE_RESULT_OK;
}

size_t ademco_dec_to_hex_str(char* str, uint32_t dec, uint8_t hex_len) {
    char* p = str;
    uint8_t i;
    for (i = 0; i < hex_len; i++) {
        *p++ = ademco_hex2char((dec >> (4 * (hex_len - i - 1))) & 0x0F);
    }
    return p - str;
}

size_t ademco_hilo_array_to_dec_str(ademco_char_t ADEMCO_BUF_MEMORY_MODIFIER* str,
                                    const uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                    size_t len) {
    char ADEMCO_BUF_MEMORY_MODIFIER* p = str;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    char ADEMCO_BUF_MEMORY_MODIFIER c;
    for (i = 0; i < len; i++) {
        c = (arr[i] >> 4) & 0x0F;
        if (c > 9) {
            return p - str;
        }
        *p++ = c + '0';
        c = (arr[i] & 0x0F);
        if (c > 9) {
            return p - str;
        }
        *p++ = c + '0';
    }
    return p - str;
}

size_t ademco_hilo_array_to_hex_str(ademco_char_t ADEMCO_BUF_MEMORY_MODIFIER* str,
                                    const uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                    size_t len) {
    char ADEMCO_BUF_MEMORY_MODIFIER* p = str;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    for (i = 0; i < len; i++) {
        char ADEMCO_BUF_MEMORY_MODIFIER c = (arr[i] >> 4) & 0x0F;
        if (c > 9) {
            if (i < 6) {
                *p++ = c - 10 + 'A';
            } else {
                return p - str;
            }
        } else {
            *p++ = c + '0';
        }

        c = (arr[i] & 0x0F);
        if (c > 9) {
            if (i < 6) {
                *p++ = c - 10 + 'A';
            } else {
                return p - str;
            }
        } else {
            *p++ = c + '0';
        }
    }
    return p - str;
}

size_t ademco_dec_str_to_hilo_array(uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                    size_t len,
                                    const char ADEMCO_BUF_MEMORY_MODIFIER* str) {
    char ADEMCO_BUF_MEMORY_MODIFIER* p = (char ADEMCO_BUF_MEMORY_MODIFIER*)arr;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    size_t ADEMCO_BUF_MEMORY_MODIFIER slen = str ? strlen(str) : 0;
    char ADEMCO_BUF_MEMORY_MODIFIER hi, lo;
    if (slen > len * 2)
        slen = len * 2;
    for (i = 0; i < slen; i += 2) {
        hi = str[i];
        if ('0' <= hi && hi <= '9') {
            if (i + 1 < slen) {
                lo = str[i + 1];
                if ('0' <= lo && lo <= '9')
                    *p++ = (hi << 4) | (lo & 0x0F);
                else {
                    *p++ = (hi << 4) | 0x0F;
                    break;
                }
            } else {
                *p++ = (hi << 4) | 0x0F;
                break;
            }
        } else
            break;
    }
    while ((char ADEMCO_BUF_MEMORY_MODIFIER*)arr + len > p)
        *p++ = 0xFF;
    return len;
}

size_t ademco_hex_str_to_hilo_array(uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                    size_t len,
                                    const char ADEMCO_BUF_MEMORY_MODIFIER* str) {
    char ADEMCO_BUF_MEMORY_MODIFIER* p = (char ADEMCO_BUF_MEMORY_MODIFIER*)arr;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    size_t ADEMCO_BUF_MEMORY_MODIFIER slen = str ? strlen(str) : 0;
    char ADEMCO_BUF_MEMORY_MODIFIER hi, lo;
    if (slen > len * 2)
        slen = len * 2;
    for (i = 0; i < slen; i += 2) {
        hi = str[i];
        if (isxdigit(hi)) {
            if (i + 1 < slen) {
                lo = str[i + 1];
                if (isxdigit(lo))
                    *p++ = (ademco_char2hex(hi) << 4) | (ademco_char2hex(lo) & 0x0F);
                else {
                    *p++ = (ademco_char2hex(hi) << 4) | 0x0F;
                    break;
                }
            } else {
                *p++ = (ademco_char2hex(hi) << 4) | 0x0F;
                break;
            }
        } else {
            break;
        }
    }
    while ((char ADEMCO_BUF_MEMORY_MODIFIER*)arr + len > p)
        *p++ = 0xFF;
    return len;
}

size_t ademco_hex_array_to_str(char ADEMCO_BUF_MEMORY_MODIFIER* str, const uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr, size_t len) {
    char ADEMCO_BUF_MEMORY_MODIFIER* p = str;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    for (i = 0; i < len; i++) {
        *p++ = ademco_hex2char((arr[i] >> 4) & 0x0F);
        *p++ = ademco_hex2char(arr[i] & 0x0F);
    }
    return p - str;
}

size_t ademco_hex_str_to_array(uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr, const char ADEMCO_BUF_MEMORY_MODIFIER* str, uint8_t padding) {
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER* p = arr;
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER hi = 0, lo = 0;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    size_t ADEMCO_BUF_MEMORY_MODIFIER slen = str ? strlen(str) : 0;
    char ADEMCO_BUF_MEMORY_MODIFIER c;
    padding &= 0x0F;
    for (i = 0; i < slen / 2; i++) {
        c = str[i * 2];
        if ((hi = ademco_char2hex(c)) == 0xFF)
            return 0;
        if (i * 2 + 1 < slen) {
            c = str[i * 2 + 1];
            if ((lo = ademco_char2hex(c)) == 0xFF)
                return 0;
        } else
            lo = padding;
        *p++ = ((hi << 4) & 0xF0) | (lo & 0x0F);
    }
    return p - arr;
}

size_t ademco_hex_str_to_array_n(uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                 const char ADEMCO_BUF_MEMORY_MODIFIER* str,
                                 size_t len, uint8_t padding) {
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER* p = arr;
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER hi = 0, lo = 0;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    char ADEMCO_BUF_MEMORY_MODIFIER c;
    padding &= 0x0F;
    for (i = 0; i < len / 2; i++) {
        c = str[i * 2];
        if ((hi = ademco_char2hex(c)) == 0xFF)
            return 0;
        if (i * 2 + 1 < len) {
            c = str[i * 2 + 1];
            if ((lo = ademco_char2hex(c)) == 0xFF)
                return 0;
        } else
            lo = padding;
        *p++ = ((hi << 4) & 0xF0) | (lo & 0x0F);
    }
    return p - arr;
}

size_t ademco_hex_str_to_array_n_allow_non_hex_str(uint8_t ADEMCO_BUF_MEMORY_MODIFIER* arr,
                                                   const char ADEMCO_BUF_MEMORY_MODIFIER* str,
                                                   size_t len,
                                                   uint8_t padding) {
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER* p = arr;
    uint8_t ADEMCO_BUF_MEMORY_MODIFIER hi = 0, lo = 0;
    size_t ADEMCO_BUF_MEMORY_MODIFIER i;
    char ADEMCO_BUF_MEMORY_MODIFIER c;
    padding &= 0x0F;
    for (i = 0; i < len / 2; i++) {
        c = str[i * 2];
        if ((hi = ademco_char2hex(c)) == 0xFF)
            hi = padding;
        if (i * 2 + 1 < len) {
            c = str[i * 2 + 1];
            if ((lo = ademco_char2hex(c)) == 0xFF)
                lo = padding;
        } else
            lo = padding;
        *p++ = ((hi << 4) & 0xF0) | (lo & 0x0F);
    }
    return p - arr;
}