EZSP milestone 2

This commit is contained in:
Stephan Hadinger 2020-06-19 20:54:37 +02:00
parent 39ee974f72
commit ca38d81b22
6 changed files with 1399 additions and 516 deletions

View File

@ -506,13 +506,16 @@
#define D_JSON_ZIGBEE_CC2530 "CC2530"
#define D_CMND_ZIGBEEZNPRECEIVE "ZNPReceive" // only for debug
#define D_CMND_ZIGBEE_EZSP_RECEIVE "EZSPReceive" // only for debug
#define D_CMND_ZIGBEE_EZSP_RECEIVE_RAW "EZSPReceiveRaw" // only for debug
#define D_CMND_ZIGBEEZNPSEND "ZNPSend"
#define D_CMND_ZIGBEE_EZSP_SEND "EZSPSend"
#define D_CMND_ZIGBEE_EZSP_SEND_RAW "EZSPSendRaw"
#define D_JSON_ZIGBEE_STATE "ZbState"
#define D_JSON_ZIGBEEZNPRECEIVED "ZbZNPReceived"
#define D_JSON_ZIGBEE_EZSP_RECEIVED "ZbEZSPReceived"
#define D_JSON_ZIGBEEZNPSENT "ZbZNPSent"
#define D_JSON_ZIGBEE_EZSP_SENT "ZbEZSPSent"
#define D_JSON_ZIGBEE_EZSP_SENT_RAW "ZbEZSPSentRaw"
#define D_JSON_ZIGBEEZCL_RECEIVED "ZbZCLReceived"
#define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZbZCLRawReceived"
#define D_JSON_ZIGBEE_DEVICE "Device"

View File

@ -55,6 +55,509 @@ enum ZnpSubsystem {
};
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
enum EZSPCondigId {
EZSP_CONFIG_PACKET_BUFFER_COUNT = 0x01,
EZSP_CONFIG_NEIGHBOR_TABLE_SIZE = 0x02,
EZSP_CONFIG_APS_UNICAST_MESSAGE_COUNT = 0x03,
EZSP_CONFIG_BINDING_TABLE_SIZE = 0x04,
EZSP_CONFIG_ADDRESS_TABLE_SIZE = 0x05,
EZSP_CONFIG_MULTICAST_TABLE_SIZE = 0x06,
EZSP_CONFIG_ROUTE_TABLE_SIZE = 0x07,
EZSP_CONFIG_DISCOVERY_TABLE_SIZE = 0x08,
EZSP_CONFIG_STACK_PROFILE = 0x0C,
EZSP_CONFIG_SECURITY_LEVEL = 0x0D,
EZSP_CONFIG_MAX_HOPS = 0x10,
EZSP_CONFIG_MAX_END_DEVICE_CHILDREN = 0x11,
EZSP_CONFIG_INDIRECT_TRANSMISSION_TIMEOUT = 0x12,
EZSP_CONFIG_END_DEVICE_POLL_TIMEOUT = 0x13,
EZSP_CONFIG_TX_POWER_MODE = 0x17,
EZSP_CONFIG_DISABLE_RELAY = 0x18,
EZSP_CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE = 0x19,
EZSP_CONFIG_SOURCE_ROUTE_TABLE_SIZE = 0x1A,
EZSP_CONFIG_FRAGMENT_WINDOW_SIZE = 0x1C,
EZSP_CONFIG_FRAGMENT_DELAY_MS = 0x1D,
EZSP_CONFIG_KEY_TABLE_SIZE = 0x1E,
EZSP_CONFIG_APS_ACK_TIMEOUT = 0x1F,
EZSP_CONFIG_BEACON_JITTER_DURATION = 0x20,
EZSP_CONFIG_END_DEVICE_BIND_TIMEOUT = 0x21,
EZSP_CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD = 0x22,
EZSP_CONFIG_REQUEST_KEY_TIMEOUT = 0x24,
EZSP_CONFIG_CERTIFICATE_TABLE_SIZE = 0x29,
EZSP_CONFIG_APPLICATION_ZDO_FLAGS = 0x2A,
EZSP_CONFIG_BROADCAST_TABLE_SIZE = 0x2B,
EZSP_CONFIG_MAC_FILTER_TABLE_SIZE = 0x2C,
EZSP_CONFIG_SUPPORTED_NETWORKS = 0x2D,
EZSP_CONFIG_SEND_MULTICASTS_TO_SLEEPY_ADDRESS = 0x2E,
EZSP_CONFIG_ZLL_GROUP_ADDRESSES = 0x2F,
EZSP_CONFIG_ZLL_RSSI_THRESHOLD = 0x30,
EZSP_CONFIG_MTORR_FLOW_CONTROL = 0x33,
EZSP_CONFIG_RETRY_QUEUE_SIZE = 0x34,
EZSP_CONFIG_NEW_BROADCAST_ENTRY_THRESHOLD = 0x35,
EZSP_CONFIG_BROADCAST_MIN_ACKS_NEEDED = 0x37,
EZSP_CONFIG_TC_REJOINS_USING_WELL_KNOWN_KEY_TIMEOUT_S = 0x38,
EZSP_CONFIG_CTUNE_VALUE = 0x39
};
enum EZSPValueId {
EZSP_VALUE_TOKEN_STACK_NODE_DATA = 0x00,
EZSP_VALUE_MAC_PASSTHROUGH_FLAGS = 0x01,
EZSP_VALUE_EMBERNET_PASSTHROUGH_SOURCE_ADDRESS = 0x02,
EZSP_VALUE_FREE_BUFFERS = 0x03,
EZSP_VALUE_UART_SYNCH_CALLBACKS = 0x04,
EZSP_VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE = 0x05,
EZSP_VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE = 0x06,
EZSP_VALUE_STACK_TOKEN_WRITING = 0x07,
EZSP_VALUE_STACK_IS_PERFORMING_REJOIN = 0x08,
EZSP_VALUE_MAC_FILTER_LIST = 0x09,
EZSP_VALUE_EXTENDED_SECURITY_BITMASK = 0x0A,
EZSP_VALUE_NODE_SHORT_ID = 0x0B,
EZSP_VALUE_DESCRIPTOR_CAPABILITY = 0x0C,
EZSP_VALUE_STACK_DEVICE_REQUEST_SEQUENCE_NUMBER = 0x0D,
EZSP_VALUE_RADIO_HOLD_OFF = 0x0E,
EZSP_VALUE_ENDPOINT_FLAGS = 0x0F,
EZSP_VALUE_MFG_SECURITY_CONFIG = 0x10,
EZSP_VALUE_VERSION_INFO = 0x11,
EZSP_VALUE_NEXT_HOST_REJOIN_REASON = 0x12,
EZSP_VALUE_LAST_REJOIN_REASON = 0x13,
EZSP_VALUE_NEXT_ZIGBEE_SEQUENCE_NUMBER = 0x14,
EZSP_VALUE_CCA_THRESHOLD = 0x15,
EZSP_VALUE_SET_COUNTER_THRESHOLD = 0x17,
EZSP_VALUE_RESET_COUNTER_THRESHOLDS = 0x18,
EZSP_VALUE_CLEAR_COUNTERS = 0x19,
EZSP_VALUE_CERTIFICATE_283K1 = 0x1A,
EZSP_VALUE_PUBLIC_KEY_283K1 = 0x1B,
EZSP_VALUE_PRIVATE_KEY_283K1 = 0x1C,
EZSP_VALUE_NWK_FRAME_COUNTER = 0x23,
EZSP_VALUE_APS_FRAME_COUNTER = 0x24,
EZSP_VALUE_RETRY_DEVICE_TYPE = 0x25,
EZSP_VALUE_ENABLE_R21_BEHAVIOR = 0x29,
EZSP_VALUE_ANTENNA_MODE = 0x30,
EZSP_VALUE_ENABLE_PTA = 0x31,
EZSP_VALUE_PTA_OPTIONS = 0x32,
EZSP_VALUE_MFGLIB_OPTIONS = 0x33,
EZSP_VALUE_USE_NEGOTIATED_POWER_BY_LPD = 0x34,
EZSP_VALUE_PTA_PWM_OPTIONS = 0x35,
EZSP_VALUE_PTA_DIRECTIONAL_PRIORITY_PULSE_WIDTH = 0x36,
EZSP_VALUE_PTA_PHY_SELECT_TIMEOUT = 0x37,
EZSP_VALUE_ANTENNA_RX_MODE = 0x38,
EZSP_VALUE_NWK_KEY_TIMEOUT = 0x39,
EZSP_VALUE_FORCE_TX_AFTER_FAILED_CCA_ATTEMPTS = 0x3A,
EZSP_VALUE_TRANSIENT_KEY_TIMEOUT_S = 0x3B,
ZSP_VALUE_COULOMB_COUNTER_USAGE = 0x3C,
EZSP_VALUE_MAX_BEACONS_TO_STORE = 0x3D,
EZSP_VALUE_END_DEVICE_TIMEOUT_OPTIONS_MASK = 0x3E,
EZSP_VALUE_END_DEVICE_KEEP_ALIVE_SUPPORT_MODE = 0x3F,
EZSP_VALUE_GPIO_RADIO_POWER_MASK = 0x40,
EZSP_VALUE_ACTIVE_RADIO_CONFIG = 0x41
};
enum EZSPEmberStatusId {
EMBER_SUCCESS = 0x00,
EMBER_ERR_FATAL = 0x01,
EMBER_BAD_ARGUMENT = 0x02,
EMBER_EEPROM_MFG_STACK_VERSION_MISMATCH = 0x04,
EMBER_INCOMPATIBLE_STATIC_MEMORY_DEFINITIONS = 0x05,
EMBER_EEPROM_MFG_VERSION_MISMATCH = 0x06,
EMBER_EEPROM_STACK_VERSION_MISMATCH = 0x07,
EMBER_NO_BUFFERS = 0x18,
EMBER_SERIAL_INVALID_BAUD_RATE = 0x20,
EMBER_SERIAL_INVALID_PORT = 0x21,
EMBER_SERIAL_TX_OVERFLOW = 0x22,
EMBER_SERIAL_RX_OVERFLOW = 0x23,
EMBER_SERIAL_RX_FRAME_ERROR = 0x24,
EMBER_SERIAL_RX_PARITY_ERROR = 0x25,
EMBER_SERIAL_RX_EMPTY = 0x26,
EMBER_SERIAL_RX_OVERRUN_ERROR = 0x27,
EMBER_MAC_TRANSMIT_QUEUE_FULL = 0x39,
EMBER_MAC_UNKNOWN_HEADER_TYPE = 0x3A,
EMBER_MAC_SCANNING = 0x3D,
EMBER_MAC_NO_DATA = 0x31,
EMBER_MAC_JOINED_NETWORK = 0x32,
EMBER_MAC_BAD_SCAN_DURATION = 0x33,
EMBER_MAC_INCORRECT_SCAN_TYPE = 0x34,
EMBER_MAC_INVALID_CHANNEL_MASK = 0x35,
EMBER_MAC_COMMAND_TRANSMIT_FAILURE = 0x36,
EMBER_MAC_NO_ACK_RECEIVED = 0x40,
EMBER_MAC_INDIRECT_TIMEOUT = 0x42,
EMBER_SIM_EEPROM_ERASE_PAGE_GREEN = 0x43,
EMBER_SIM_EEPROM_ERASE_PAGE_RED = 0x44,
EMBER_SIM_EEPROM_FULL = 0x45,
EMBER_ERR_FLASH_WRITE_INHIBITED = 0x46,
EMBER_ERR_FLASH_VERIFY_FAILED = 0x47,
EMBER_SIM_EEPROM_INIT_1_FAILED = 0x48,
EMBER_SIM_EEPROM_INIT_2_FAILED = 0x49,
EMBER_SIM_EEPROM_INIT_3_FAILED = 0x4A,
EMBER_ERR_FLASH_PROG_FAIL = 0x4B,
EMBER_ERR_FLASH_ERASE_FAIL = 0x4C,
EMBER_ERR_BOOTLOADER_TRAP_TABLE_BAD = 0x58,
EMBER_ERR_BOOTLOADER_TRAP_UNKNOWN = 0x59,
EMBER_ERR_BOOTLOADER_NO_IMAGE = 0x5A,
EMBER_DELIVERY_FAILED = 0x66,
EMBER_BINDING_INDEX_OUT_OF_RANGE = 0x69,
EMBER_ADDRESS_TABLE_INDEX_OUT_OF_RANGE = 0x6A,
EMBER_INVALID_BINDING_INDEX = 0x6C,
EMBER_INVALID_CALL = 0x70,
EMBER_COST_NOT_KNOWN = 0x71,
EMBER_MAX_MESSAGE_LIMIT_REACHED = 0x72,
EMBER_MESSAGE_TOO_LONG = 0x74,
EMBER_BINDING_IS_ACTIVE = 0x75,
EMBER_ADDRESS_TABLE_ENTRY_IS_ACTIVE = 0x76,
EMBER_ADC_CONVERSION_DONE = 0x80,
EMBER_ADC_CONVERSION_BUSY = 0x81,
EMBER_ADC_CONVERSION_DEFERRED = 0x82,
EMBER_ADC_NO_CONVERSION_PENDING = 0x84,
EMBER_SLEEP_INTERRUPTED = 0x85,
EMBER_PHY_TX_UNDERFLOW = 0x88,
EMBER_PHY_TX_INCOMPLETE = 0x89,
EMBER_PHY_INVALID_CHANNEL = 0x8A,
EMBER_PHY_INVALID_POWER = 0x8B,
EMBER_PHY_TX_BUSY = 0x8C,
EMBER_PHY_TX_CCA_FAIL = 0x8D,
EMBER_PHY_OSCILLATOR_CHECK_FAILED = 0x8E,
EMBER_PHY_ACK_RECEIVED = 0x8F,
EMBER_NETWORK_UP = 0x90,
EMBER_NETWORK_DOWN = 0x91,
EMBER_JOIN_FAILED = 0x94,
EMBER_MOVE_FAILED = 0x96,
EMBER_CANNOT_JOIN_AS_ROUTER = 0x98,
EMBER_NODE_ID_CHANGED = 0x99,
EMBER_PAN_ID_CHANGED = 0x9A,
EMBER_NO_BEACONS = 0xAB,
EMBER_RECEIVED_KEY_IN_THE_CLEAR = 0xAC,
EMBER_NO_NETWORK_KEY_RECEIVED = 0xAD,
EMBER_NO_LINK_KEY_RECEIVED = 0xAE,
EMBER_PRECONFIGURED_KEY_REQUIRED = 0xAF,
EMBER_NOT_JOINED = 0x93,
EMBER_INVALID_SECURITY_LEVEL = 0x95,
EMBER_NETWORK_BUSY = 0xA1,
EMBER_INVALID_ENDPOINT = 0xA3,
EMBER_BINDING_HAS_CHANGED = 0xA4,
EMBER_INSUFFICIENT_RANDOM_DATA = 0xA5,
EMBER_APS_ENCRYPTION_ERROR = 0xA6,
EMBER_SECURITY_STATE_NOT_SET = 0xA8,
EMBER_KEY_TABLE_INVALID_ADDRESS = 0xB3,
EMBER_SECURITY_CONFIGURATION_INVALID = 0xB7,
EMBER_TOO_SOON_FOR_SWITCH_KEY = 0xB8,
EMBER_KEY_NOT_AUTHORIZED = 0xBB,
EMBER_SECURITY_DATA_INVALID = 0xBD,
EMBER_SOURCE_ROUTE_FAILURE = 0xA9,
EMBER_MANY_TO_ONE_ROUTE_FAILURE = 0xAA,
EMBER_STACK_AND_HARDWARE_MISMATCH = 0xB0,
EMBER_INDEX_OUT_OF_RANGE = 0xB1,
EMBER_TABLE_FULL = 0xB4,
EMBER_TABLE_ENTRY_ERASED = 0xB6,
EMBER_LIBRARY_NOT_PRESENT = 0xB5,
EMBER_OPERATION_IN_PROGRESS = 0xBA,
};
enum EZSPStatusId {
EZSP_SUCCESS = 0x00,
EZSP_SPI_ERR_FATAL = 0x10,
EZSP_SPI_ERR_NCP_RESET = 0x11,
EZSP_SPI_ERR_OVERSIZED_EZSP_FRAME = 0x12,
EZSP_SPI_ERR_ABORTED_TRANSACTION = 0x13,
EZSP_SPI_ERR_MISSING_FRAME_TERMINATOR = 0x14,
EZSP_SPI_ERR_WAIT_SECTION_TIMEOUT = 0x15,
EZSP_SPI_ERR_NO_FRAME_TERMINATOR = 0x16,
EZSP_SPI_ERR_EZSP_COMMAND_OVERSIZED = 0x17,
EZSP_SPI_ERR_EZSP_RESPONSE_OVERSIZED = 0x18,
EZSP_SPI_WAITING_FOR_RESPONSE = 0x19,
EZSP_SPI_ERR_HANDSHAKE_TIMEOUT = 0x1A,
EZSP_SPI_ERR_STARTUP_TIMEOUT = 0x1B,
EZSP_SPI_ERR_STARTUP_FAIL = 0x1C,
EZSP_SPI_ERR_UNSUPPORTED_SPI_COMMAND = 0x1D,
EZSP_ASH_IN_PROGRESS = 0x20,
EZSP_HOST_FATAL_ERROR = 0x21,
EZSP_ASH_NCP_FATAL_ERROR = 0x22,
EZSP_DATA_FRAME_TOO_LONG = 0x23,
EZSP_DATA_FRAME_TOO_SHORT = 0x24,
EZSP_NO_TX_SPACE = 0x25,
EZSP_NO_RX_SPACE = 0x26,
EZSP_NO_RX_DATA = 0x27,
EZSP_NOT_CONNECTED = 0x28,
EZSP_ERROR_VERSION_NOT_SET = 0x30,
EZSP_ERROR_INVALID_FRAME_ID = 0x31,
EZSP_ERROR_WRONG_DIRECTION = 0x32,
EZSP_ERROR_TRUNCATED = 0x33,
EZSP_ERROR_OVERFLOW = 0x34,
EZSP_ERROR_OUT_OF_MEMORY = 0x35,
EZSP_ERROR_INVALID_VALUE = 0x36,
EZSP_ERROR_INVALID_ID = 0x37,
EZSP_ERROR_INVALID_CALL = 0x38,
EZSP_ERROR_NO_RESPONSE = 0x39,
EZSP_ERROR_COMMAND_TOO_LONG = 0x40,
EZSP_ERROR_QUEUE_FULL = 0x41,
EZSP_ERROR_COMMAND_FILTERED = 0x42,
EZSP_ERROR_SECURITY_KEY_ALREADY_SET = 0x43,
EZSP_ERROR_SECURITY_TYPE_INVALID = 0x44,
EZSP_ERROR_SECURITY_PARAMETERS_INVALID = 0x45,
EZSP_ERROR_SECURITY_PARAMETERS_ALREADY_SET = 0x46,
EZSP_ERROR_SECURITY_KEY_NOT_SET = 0x47,
EZSP_ERROR_SECURITY_PARAMETERS_NOT_SET = 0x48,
EZSP_ERROR_UNSUPPORTED_CONTROL = 0x49,
EZSP_ERROR_UNSECURE_FRAME = 0x4A,
EZSP_NO_ERROR = 0xFF
};
enum EZSP_Commands {
EZSP_version = 0x0000,
EZSP_getLibraryStatus = 0x0001,
EZSP_addEndpoint = 0x0002,
EZSP_getExtendedValue = 0x0003,
EZSP_getNextBeacon = 0x0004,
EZSP_nop = 0x0005,
EZSP_callback = 0x0006,
EZSP_noCallbacks = 0x0007,
EZSP_getNumStoredBeacons = 0x0008,
EZSP_setToken = 0x0009,
EZSP_getToken = 0x000A,
EZSP_getMfgToken = 0x000B,
EZSP_setMfgToken = 0x000C,
EZSP_stackTokenChangedHandler = 0x000D,
EZSP_setTimer = 0x000E,
EZSP_timerHandler = 0x000F,
EZSP_setConcentrator = 0x0010,
EZSP_setBrokenRouteErrorCode = 0x0011,
EZSP_debugWrite = 0x0012,
EZSP_getXncpInfo = 0x0013,
EZSP_requestLinkKey = 0x0014,
EZSP_setManufacturerCode = 0x0015,
EZSP_setPowerDescriptor = 0x0016,
EZSP_networkInit = 0x0017,
EZSP_networkState = 0x0018,
EZSP_stackStatusHandler = 0x0019,
EZSP_startScan = 0x001A,
EZSP_networkFoundHandler = 0x001B,
EZSP_scanCompleteHandler = 0x001C,
EZSP_stopScan = 0x001D,
EZSP_formNetwork = 0x001E,
EZSP_joinNetwork = 0x001F,
EZSP_leaveNetwork = 0x0020,
EZSP_findAndRejoinNetwork = 0x0021,
EZSP_permitJoining = 0x0022,
EZSP_childJoinHandler = 0x0023,
EZSP_trustCenterJoinHandler = 0x0024,
EZSP_zllClearTokens = 0x0025,
EZSP_getEui64 = 0x0026,
EZSP_getNodeId = 0x0027,
EZSP_getNetworkParameters = 0x0028,
EZSP_getParentChildParameters = 0x0029,
EZSP_clearBindingTable = 0x002A,
EZSP_setBinding = 0x002B,
EZSP_getBinding = 0x002C,
EZSP_deleteBinding = 0x002D,
EZSP_bindingIsActive = 0x002E,
EZSP_getBindingRemoteNodeId = 0x002F,
EZSP_setBindingRemoteNodeId = 0x0030,
EZSP_remoteSetBindingHandler = 0x0031,
EZSP_remoteDeleteBindingHandler = 0x0032,
EZSP_maximumPayloadLength = 0x0033,
EZSP_sendUnicast = 0x0034,
EZSP_getDutyCycleState = 0x0035,
EZSP_sendBroadcast = 0x0036,
EZSP_proxyBroadcast = 0x0037,
EZSP_sendMulticast = 0x0038,
EZSP_sendReply = 0x0039,
EZSP_sendMulticastWithAlias = 0x003A,
EZSP_joinNetworkDirectly = 0x003B,
EZSP_clearStoredBeacons = 0x003C,
EZSP_getFirstBeacon = 0x003D,
EZSP_getNeighborFrameCounter = 0x003E,
EZSP_messageSentHandler = 0x003F,
EZSP_setDutyCycleLimitsInStack = 0x0040,
EZSP_sendManyToOneRouteRequest = 0x0041,
EZSP_pollForData = 0x0042,
EZSP_pollCompleteHandler = 0x0043,
EZSP_pollHandler = 0x0044,
EZSP_incomingMessageHandler = 0x0045,
EZSP_macFilterMatchMessageHandler = 0x0046,
EZSP_customFrame = 0x0047,
EZSP_energyScanResultHandler = 0x0048,
EZSP_getRandomNumber = 0x0049,
EZSP_getChildData = 0x004A,
EZSP_getDutyCycleLimits = 0x004B,
EZSP_getCurrentDutyCycle = 0x004C,
EZSP_dutyCycleHandler = 0x004D,
EZSP_getTimer = 0x004E,
EZSP_getTrueRandomEntropySource = 0x004F,
EZSP_unicastCurrentNetworkKey = 0x0050,
EZSP_sendRawMessageExtended = 0x0051,
EZSP_getConfigurationValue = 0x0052,
EZSP_setConfigurationValue = 0x0053,
EZSP_customFrameHandler = 0x0054,
EZSP_setPolicy = 0x0055,
EZSP_getPolicy = 0x0056,
EZSP_invalidCommand = 0x0058,
EZSP_setSourceRouteDiscoveryMode = 0x005A,
EZSP_addressTableEntryIsActive = 0x005B,
EZSP_setAddressTableRemoteEui64 = 0x005C,
EZSP_setAddressTableRemoteNodeId = 0x005D,
EZSP_getAddressTableRemoteEui64 = 0x005E,
EZSP_getAddressTableRemoteNodeId = 0x005F,
EZSP_lookupNodeIdByEui64 = 0x0060,
EZSP_lookupEui64ByNodeId = 0x0061,
EZSP_incomingSenderEui64Handler = 0x0062,
EZSP_getMulticastTableEntry = 0x0063,
EZSP_setMulticastTableEntry = 0x0064,
EZSP_readAndClearCounters = 0x0065,
EZSP_addOrUpdateKeyTableEntry = 0x0066,
EZSP_sendTrustCenterLinkKey = 0x0067,
EZSP_setInitialSecurityState = 0x0068,
EZSP_getCurrentSecurityState = 0x0069,
EZSP_getKey = 0x006A,
EZSP_clearTransientLinkKeys = 0x006B,
EZSP_updateTcLinkKey = 0x006C,
EZSP_getTransientKeyTableEntry = 0x006D,
EZSP_switchNetworkKeyHandler = 0x006E,
EZSP_aesMmoHash = 0x006F,
EZSP_gpSinkTableInit = 0x0070,
EZSP_getKeyTableEntry = 0x0071,
EZSP_setKeyTableEntry = 0x0072,
EZSP_broadcastNextNetworkKey = 0x0073,
EZSP_broadcastNetworkKeySwitch = 0x0074,
EZSP_findKeyTableEntry = 0x0075,
EZSP_eraseKeyTableEntry = 0x0076,
EZSP_becomeTrustCenter = 0x0077,
EZSP_dsaVerifyHandler = 0x0078,
EZSP_getNeighbor = 0x0079,
EZSP_neighborCount = 0x007A,
EZSP_getRouteTableEntry = 0x007B,
EZSP_idConflictHandler = 0x007C,
EZSP_incomingManyToOneRouteRequestHandler = 0x007D,
EZSP_setExtendedTimeout = 0x007E,
EZSP_getExtendedTimeout = 0x007F,
EZSP_incomingRouteErrorHandler = 0x0080,
EZSP_echo = 0x0081,
EZSP_replaceAddressTableEntry = 0x0082,
EZSP_mfglibStart = 0x0083,
EZSP_mfglibEnd = 0x0084,
EZSP_mfglibStartTone = 0x0085,
EZSP_mfglibStopTone = 0x0086,
EZSP_mfglibStartStream = 0x0087,
EZSP_mfglibStopStream = 0x0088,
EZSP_mfglibSendPacket = 0x0089,
EZSP_mfglibSetChannel = 0x008A,
EZSP_mfglibGetChannel = 0x008B,
EZSP_mfglibSetPower = 0x008C,
EZSP_mfglibGetPower = 0x008D,
EZSP_mfglibRxHandler = 0x008E,
EZSP_launchStandaloneBootloader = 0x008F,
EZSP_sendBootloadMessage = 0x0090,
EZSP_getStandaloneBootloaderVersionPlatMicroPhy = 0x0091,
EZSP_incomingBootloadMessageHandler = 0x0092,
EZSP_bootloadTransmitCompleteHandler = 0x0093,
EZSP_aesEncrypt = 0x0094,
EZSP_overrideCurrentChannel = 0x0095,
EZSP_sendRawMessage = 0x0096,
EZSP_macPassthroughMessageHandler = 0x0097,
EZSP_rawTransmitCompleteHandler = 0x0098,
EZSP_setRadioPower = 0x0099,
EZSP_setRadioChannel = 0x009A,
EZSP_zigbeeKeyEstablishmentHandler = 0x009B,
EZSP_energyScanRequest = 0x009C,
EZSP_delayTest = 0x009D,
EZSP_generateCbkeKeysHandler = 0x009E,
EZSP_calculateSmacs = 0x009F,
EZSP_calculateSmacsHandler = 0x00A0,
EZSP_clearTemporaryDataMaybeStoreLinkKey = 0x00A1,
EZSP_setPreinstalledCbkeData = 0x00A2,
EZSP_dsaVerify = 0x00A3,
EZSP_generateCbkeKeys = 0x00A4,
EZSP_getCertificate = 0x00A5,
EZSP_dsaSign = 0x00A6,
EZSP_dsaSignHandler = 0x00A7,
EZSP_removeDevice = 0x00A8,
EZSP_unicastNwkKeyUpdate = 0x00A9,
EZSP_getValue = 0x00AA,
EZSP_setValue = 0x00AB,
EZSP_setGpioCurrentConfiguration = 0x00AC,
EZSP_setGpioPowerUpDownConfiguration = 0x00AD,
EZSP_setGpioRadioPowerMask = 0x00AE,
EZSP_addTransientLinkKey = 0x00AF,
EZSP_dsaVerify283k1 = 0x00B0,
EZSP_clearKeyTable = 0x00B1,
EZSP_zllNetworkOps = 0x00B2,
EZSP_zllSetInitialSecurityState = 0x00B3,
EZSP_zllStartScan = 0x00B4,
EZSP_zllSetRxOnWhenIdle = 0x00B5,
EZSP_zllNetworkFoundHandler = 0x00B6,
EZSP_zllScanCompleteHandler = 0x00B7,
EZSP_zllAddressAssignmentHandler = 0x00B8,
EZSP_setLogicalAndRadioChannel = 0x00B9,
EZSP_getLogicalChannel = 0x00BA,
EZSP_zllTouchLinkTargetHandler = 0x00BB,
EZSP_zllGetTokens = 0x00BC,
EZSP_zllSetDataToken = 0x00BD,
EZSP_isZllNetwork = 0x00BE,
EZSP_zllSetNonZllNetwork = 0x00BF,
EZSP_gpProxyTableLookup = 0x00C0,
EZSP_getSourceRouteTableEntry = 0x00C1,
EZSP_getSourceRouteTableFilledSize = 0x00C2,
EZSP_getSourceRouteTableTotalSize = 0x00C3,
EZSP_gpepIncomingMessageHandler = 0x00C5,
EZSP_dGpSend = 0x00C6,
EZSP_dGpSentHandler = 0x00C7,
EZSP_gpProxyTableGetEntry = 0x00C8,
EZSP_gpProxyTableProcessGpPairing = 0x00C9,
EZSP_setSecurityKey = 0x00CA,
EZSP_setSecurityParameters = 0x00CB,
EZSP_resetToFactoryDefaults = 0x00CC,
EZSP_getSecurityKeyStatus = 0x00CD,
EZSP_getTransientLinkKey = 0x00CE,
EZSP_zllSetSecurityStateWithoutKey = 0x00CF,
EZSP_setRoutingShortcutThreshold = 0x00D0,
EZSP_getRoutingShortcutThreshold = 0x00D1,
EZSP_unusedPanIdFoundHandler = 0x00D2,
EZSP_findUnusedPanId = 0x00D3,
EZSP_zllSetRadioIdleMode = 0x00D4,
EZSP_setZllNodeType = 0x00D5,
EZSP_setZllAdditionalState = 0x00D6,
EZSP_zllOperationInProgress = 0x00D7,
EZSP_zllRxOnWhenIdleGetActive = 0x00D8,
EZSP_getZllPrimaryChannelMask = 0x00D9,
EZSP_getZllSecondaryChannelMask = 0x00DA,
EZSP_setZllPrimaryChannelMask = 0x00DB,
EZSP_setZllSecondaryChannelMask = 0x00DC,
EZSP_gpSinkTableGetEntry = 0x00DD,
EZSP_gpSinkTableLookup = 0x00DE,
EZSP_gpSinkTableSetEntry = 0x00DF,
EZSP_gpSinkTableRemoveEntry = 0x00E0,
EZSP_gpSinkTableFindOrAllocateEntry = 0x00E1,
EZSP_gpSinkTableClearAll = 0x00E2,
EZSP_setLongUpTime = 0x00E3,
EZSP_setHubConnectivity = 0x00E4,
EZSP_isUpTimeLong = 0x00E5,
EZSP_isHubConnected = 0x00E6,
EZSP_setParentClassificationEnabled = 0x00E7,
EZSP_generateCbkeKeys283k1 = 0x00E8,
EZSP_generateCbkeKeysHandler283k1 = 0x00E9,
EZSP_calculateSmacs283k1 = 0x00EA,
EZSP_calculateSmacsHandler283k1 = 0x00EB,
EZSP_getCertificate283k1 = 0x00EC,
EZSP_savePreinstalledCbkeData283k1 = 0x00ED,
EZSP_clearTemporaryDataMaybeStoreLinkKey283k1 = 0x00EE,
EZSP_setBeaconClassificationParams = 0x00EF,
EZSP_getParentClassificationEnabled = 0x00F0,
EZSP_readCounters = 0x00F1,
EZSP_counterRolloverHandler = 0x00F2,
EZSP_getBeaconClassificationParams = 0x00F3,
EZSP_setMacPollFailureWaitTime = 0x00F4,
EZSP_sendLinkPowerDeltaRequest = 0x00F7,
EZSP_multiPhyStart = 0x00F8,
EZSP_multiPhyStop = 0x00F9,
EZSP_multiPhySetRadioPower = 0x00FA,
EZSP_multiPhySetRadioChannel = 0x00FB,
EZSP_getPhyInterfaceCount = 0x00FC,
EZSP_getRadioParameters = 0x00FD,
EZSP_writeNodeData = 0x00FE,
// Tasmota specifics
EZSP_rstAck = 0xFFFE,
};
#endif // USE_ZIGBEE_EZSP
// Commands in the SYS subsystem
enum SysCommand {
SYS_RESET = 0x00,

View File

@ -622,17 +622,124 @@ void Z_UpdateConfig(uint8_t zb_channel, uint16_t zb_pan_id, uint64_t zb_ext_pani
}
// patterns for EZSP
// wait for RSTACK, meaning the device booted
ZBM(ZBR_RSTACK, Z_B0(EZSP_rstAck), Z_B1(EZSP_rstAck)) // FEFF - internal code for RSTACK
// call version() and ask for EZSP v8
ZBM(ZBS_VERSION, EZSP_version, 0x00, 0x08) // 000008
ZBM(ZBR_VERSION, EZSP_version, 0x00, 0x08, 0x02) // 00000802 - expect v8, proto v2
// general configuration
// inspired from bellows: https://github.com/zigpy/bellows/blob/dev/bellows/config/ezsp.py
ZBM(ZBS_SET_ADDR_TABLE, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_KEY_TABLE_SIZE, 0x04, 0x00) // 53001E0400
ZBM(ZBS_SET_MCAST_TABLE, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_MULTICAST_TABLE_SIZE, 0x10, 0x00) // 5300061000
ZBM(ZBS_SET_STK_PROF, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_STACK_PROFILE, 0x02, 0x00) // 53000C0200
ZBM(ZBS_SET_SEC_LEVEL, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_SECURITY_LEVEL, 0x05, 0x00) // 53000D0500
ZBM(ZBS_SET_MAX_DEVICES, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_MAX_END_DEVICE_CHILDREN, 0x18, 0x00) // 5300111800
ZBM(ZBS_SET_INDIRECT_TMO, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_INDIRECT_TRANSMISSION_TIMEOUT, 0x00, 0x1E) // 530012001E
ZBM(ZBS_SET_TC_CACHE, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE, 0x02, 0x00) // 5300190200
ZBM(ZBS_SET_ROUTE_TBL, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_SOURCE_ROUTE_TABLE_SIZE, 0x10, 0x00) // 53001A1000
ZBM(ZBS_SET_KEY_TBL, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_KEY_TABLE_SIZE, 0x04, 0x00) // 53001E0400
ZBM(ZBS_SET_PANID_CNFLCT, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD, 0x02, 0x00)// 5300220200
// TODO APP_RECEIVES_SUPPORTED_ZDO_REQUESTS
ZBM(ZBS_SET_ZDO_REQ, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_APPLICATION_ZDO_FLAGS, 0x03, 0x00) // 53002A0300
ZBM(ZBS_SET_NETWORKS, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_SUPPORTED_NETWORKS, 0x01, 0x00) // 53002D0100
ZBM(ZBS_SET_PACKET_BUF, EZSP_setConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_PACKET_BUFFER_COUNT, 0xFF, 0x00) // 530001FF00
ZBM(ZBR_SET_OK, EZSP_setConfigurationValue, 0x00 /*high*/, 0x00 /*ok*/) // 530000
ZBM(ZBR_SET_OK2, 0x00, 0x00 /*high*/, 0x00 /*ok*/) // 000000 - TODO why does setting EZSP_CONFIG_PACKET_BUFFER_COUNT has a different response?
// Read some configuration values
ZBM(ZBS_GET_APS_UNI, EZSP_getConfigurationValue, 0x00 /*high*/, EZSP_CONFIG_APS_UNICAST_MESSAGE_COUNT) // 520003
ZBM(ZBR_GET_OK, EZSP_getConfigurationValue, 0x00 /*high*/, 0x00 /*ok*/) // 5200 - followed by the value
// Add Endpoints
// ZBM(ZBS_ADD_ENDPOINT1, EZSP_addEndpoint, 0x00 /*high*/, 0x01 /*ep*/, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
// 0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */,
// 0x0E /* inputClusterCount */, // actually all clusters will be received
// 0X00 /* outputClusterCount */,
// 0x00,0x00, 0x04,0x00, 0x05,0x00, 0x06,0x00, // 0x0000, 0x0004, 0x0005, 0x0006
// 0x07,0x00, 0x08,0x00, 0x0A,0x00, 0x02,0x01, // 0x0007, 0x0008, 0x000A, 0X0102
// 0x00,0x03, 0x00,0x04, 0x02,0x04, 0x03,0x04, // 0x0300, 0x0400, 0x0402, 0x0403
// 0x05,0x04, 0x06,0x04, // 0x0405, 0x0406
// )
ZBM(ZBS_ADD_ENDPOINT1, EZSP_addEndpoint, 0x00 /*high*/, 0x01 /*ep*/, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */,
0x00 /* inputClusterCount */, // actually all clusters will be received
0X00 /* outputClusterCount */ ) // 02000104010500000000
ZBM(ZBS_ADD_ENDPOINTB, EZSP_addEndpoint, 0x00 /*high*/, 0x0B /*ep*/, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */,
0x00 /* inputClusterCount */, // actually all clusters will be received
0X00 /* outputClusterCount */ ) // 02000B04010500000000
ZBM(ZBR_ADD_ENDPOINT, EZSP_addEndpoint, 0x00 /*high*/, 0x00 /*ok*/) // 020000
// set concentrator false
ZBM(ZBS_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*false*/, 0xF9,0xFF /*HIGH_RAM_CONCENTRATOR*/,
0x58,0x02 /*minTime*/, 0x08,0x07 /*maxTime*/, 0x02 /*errThr*/, 0x05 /*failThr*/, 0x00 /*maxHops*/) // 100000F9FF58020807020500
ZBM(ZBR_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*ok*/) // 100000
//False, <EmberConcentratorType.HIGH_RAM_CONCENTRATOR: 65529>, 600, 1800, 2, 5, 0)
const char kResetingDevice[] PROGMEM = D_LOG_ZIGBEE "resetting EZSP device";
const char kAbort[] PROGMEM = "Abort";
const char kZigbeeAbort[] PROGMEM = D_LOG_ZIGBEE "Abort";
static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_LABEL(0)
ZI_NOOP()
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
// ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
// ZI_WAIT(10500) // wait for 10 seconds for Tasmota to stabilize
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
ZI_WAIT(10500) // wait for 10 seconds for Tasmota to stabilize
// Hardware reset
ZI_LOG(LOG_LEVEL_INFO, kResetingDevice) // Log Debug: resetting EZSP device
ZI_CALL(&Z_Reset_Device, 0) // LOW = reset
ZI_WAIT(100) // wait for .1 second
ZI_CALL(&Z_Reset_Device, 1) // HIGH = release reset
// wait for device to start
ZI_WAIT_UNTIL(5000, ZBR_RSTACK) // wait for RSTACK message
// Init device and probe version
ZI_SEND(ZBS_VERSION) ZI_WAIT_RECV(1000, ZBR_VERSION) // check EXT PAN ID
// configure EFR32
ZI_SEND(ZBS_SET_ADDR_TABLE) ZI_WAIT_RECV(500, ZBR_SET_OK) // Address table size
ZI_SEND(ZBS_SET_MCAST_TABLE) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_STK_PROF) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_SEC_LEVEL) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_MAX_DEVICES) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_INDIRECT_TMO) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_TC_CACHE) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_ROUTE_TBL) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_KEY_TBL) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_PANID_CNFLCT) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_ZDO_REQ) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_NETWORKS) ZI_WAIT_RECV(500, ZBR_SET_OK)
ZI_SEND(ZBS_SET_PACKET_BUF) ZI_WAIT_RECV(500, ZBR_SET_OK2)
// read configuration
ZI_SEND(ZBS_GET_APS_UNI) ZI_WAIT_RECV_FUNC(500, ZBR_GET_OK, &Z_ReadAPSUnicastMessage)
// add endpoint 0x01 and 0x0B
ZI_SEND(ZBS_ADD_ENDPOINT1) ZI_WAIT_RECV(500, ZBR_ADD_ENDPOINT)
ZI_SEND(ZBS_ADD_ENDPOINTB) ZI_WAIT_RECV(500, ZBR_ADD_ENDPOINT)
// set Concentrator
ZI_SEND(ZBS_SET_CONCENTRATOR) ZI_WAIT_RECV(500, ZBR_SET_CONCENTRATOR)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
ZI_WAIT_FOREVER()
ZI_GOTO(ZIGBEE_LABEL_READY)
// Abort state machine, general error
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
ZI_MQTT_STATE(ZIGBEE_STATUS_ABORT, kAbort)
ZI_LOG(LOG_LEVEL_ERROR, kZigbeeAbort)
ZI_STOP(ZIGBEE_LABEL_ABORT)
};
#endif // USE_ZIGBEE_EZSP
@ -799,6 +906,9 @@ void ZigbeeStateMachine_Run(void) {
#ifdef USE_ZIGBEE_ZNP
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
ZigbeeEZSPSendCmd((uint8_t*) cur_ptr1, cur_d8 /* len */, true); // send cancel byte
#endif // USE_ZIGBEE_EZSP
break;
case ZGB_INSTR_WAIT_UNTIL:
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV

View File

@ -19,6 +19,81 @@
#ifdef USE_ZIGBEE
#ifdef USE_ZIGBEE_EZSP
/*********************************************************************************************\
* Parsers for incoming EZSP messages
\*********************************************************************************************/
// EZSP: received ASH RSTACK frame, indicating that the MCU finished boot
int32_t Z_EZSP_RSTACK(uint8_t reset_code) {
const char *reason_str;
switch (reset_code) {
case 0x01: reason_str = PSTR("External"); break;
case 0x02: reason_str = PSTR("Power-on"); break;
case 0x03: reason_str = PSTR("Watchdog"); break;
case 0x06: reason_str = PSTR("Assert"); break;
case 0x09: reason_str = PSTR("Bootloader"); break;
case 0x0B: reason_str = PSTR("Software"); break;
case 0x00:
default: reason_str = PSTR("Unknown"); break;
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Message\":\"EFR32 booted\",\"RestartReason\":\"%s\""
",\"Code\":%d}}"),
ZIGBEE_STATUS_BOOT, reason_str, reset_code);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
XdrvRulesProcess();
}
// EZSP: received ASH ERROR frame, indicating that the MCU finished boot
int32_t Z_EZSP_ERROR(uint8_t error_code) {
const char *reason_str;
switch (error_code) {
case 0x51: reason_str = PSTR("ACK timeout"); break;
default: reason_str = PSTR("Unknown"); break;
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Message\":\"Failed state\",\"Error\":\"%s\""
",\"Code\":%d}}"),
ZIGBEE_STATUS_ABORT, reason_str, error_code);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
XdrvRulesProcess();
}
/*********************************************************************************************\
* Default resolver
\*********************************************************************************************/
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
// Default message handler for new messages
if (zigbee.init_phase) {
// if still during initialization phase, ignore any unexpected message
return -1; // ignore message
} else {
// TODO
// for (uint32_t i = 0; i < sizeof(Z_DispatchTable)/sizeof(Z_Dispatcher); i++) {
// if (Z_ReceiveMatchPrefix(buf, Z_DispatchTable[i].match)) {
// (*Z_DispatchTable[i].func)(res, buf);
// }
// }
return -1;
}
}
int32_t Z_ReadAPSUnicastMessage(int32_t res, class SBuffer &buf) {
// Called when receiving a response from getConfigurationValue
// Value is in bytes 2+3
uint16_t value = buf.get16(2);
return res;
}
#endif // USE_ZIGBEE_EZSP
/*********************************************************************************************\
* Parsers for incoming ZNP messages
\*********************************************************************************************/
@ -597,6 +672,15 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) {
* Send specific EZS¨ messages
\*********************************************************************************************/
//
// Callback for loading Zigbee configuration from Flash, called by the state machine
//
int32_t Z_Reset_Device(uint8_t value) {
// TODO - GPIO is hardwired to GPIO4
digitalWrite(4, value ? HIGH : LOW);
return 0; // continue
}
//
// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address
//

View File

@ -0,0 +1,696 @@
/*
xdrv_23_zigbee_9_serial.ino - zigbee: serial communication with MCU
Copyright (C) 2020 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
#ifdef USE_ZIGBEE_ZNP
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
const uint8_t ZIGBEE_SOF = 0xFE;
const uint8_t ZIGBEE_SOF_ALT = 0xFF;
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
const uint32_t ZIGBEE_BUFFER_SIZE = 256;
const uint8_t ZIGBEE_EZSP_CANCEL = 0x1A; // cancel byte
const uint8_t ZIGBEE_EZSP_EOF = 0x7E; // end of frame
const uint8_t ZIGBEE_EZSP_ESCAPE = 0x7D; // escape byte
class EZSP_Serial_t {
public:
uint8_t to_ack = 0; // 0..7, frame number of next id to send
uint8_t from_ack = 0; // 0..7, frame to ack
uint8_t ezsp_seq = 0; // 0..255, EZSP sequence number
};
EZSP_Serial_t EZSP_Serial;
#endif // USE_ZIGBEE_EZSP
#include <TasmotaSerial.h>
TasmotaSerial *ZigbeeSerial = nullptr;
/********************************************************************************************/
//
// Called at event loop, checks for incoming data from the CC2530
//
void ZigbeeInputLoop(void) {
#ifdef USE_ZIGBEE_ZNP
static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte
static uint8_t fcs = ZIGBEE_SOF;
static uint32_t zigbee_frame_len = 5; // minimal zigbee frame length, will be updated when buf[1] is read
// Receive only valid ZNP frames:
// 00 - SOF = 0xFE
// 01 - Length of Data Field - 0..250
// 02 - CMD1 - first byte of command
// 03 - CMD2 - second byte of command
// 04..FD - Data Field
// FE (or last) - FCS Checksum
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZbInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len());
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
zigbee_frame_len = 5;
fcs = ZIGBEE_SOF;
// there is a rare race condition when an interrupt occurs when receiving the first byte
// in this case the first bit (lsb) is missed and Tasmota receives 0xFF instead of 0xFE
// We forgive this mistake, and next bytes are automatically resynchronized
if (ZIGBEE_SOF_ALT == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte);
zigbee_in_byte = ZIGBEE_SOF;
}
}
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
// waiting for SOF (Start Of Frame) byte, discard anything else
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte);
continue; // discard
}
if (zigbee_buffer->len() < zigbee_frame_len) {
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
fcs ^= zigbee_in_byte;
}
if (zigbee_buffer->len() >= zigbee_frame_len) {
zigbee_polling_window = 0; // Publish now
break;
}
// recalculate frame length
if (02 == zigbee_buffer->len()) {
// We just received the Lenght byte
uint8_t len_byte = zigbee_buffer->get8(1);
if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max
zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead
}
}
if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) {
char hex_char[(zigbee_buffer->len() * 2) + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
// buffer received, now check integrity
if (zigbee_buffer->len() != zigbee_frame_len) {
// Len is not correct, log and reject frame
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len);
} else if (0x00 != fcs) {
// FCS is wrong, packet is corrupt, log and reject frame
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs);
} else {
// frame is correct
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char);
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data);
}
// now process the message
ZigbeeProcessInput(znp_buffer);
}
zigbee_buffer->setLen(0); // empty buffer
}
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte
static bool escape = false; // was the previous byte an escape?
bool frame_complete = false; // frame is ready and complete
// Receive only valid EZSP frames:
// 1A - Cancel - cancel all previous bytes
// 7D - Escape byte - following byte is escaped
// 7E - end of frame
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x%02X len=%d"), zigbee_in_byte, zigbee_buffer->len());
// if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
// escape = false;
// frame_complete = false;
// }
if ((0x11 == zigbee_in_byte) || (0x13 == zigbee_in_byte)) {
continue; // ignore reserved bytes XON/XOFF
}
if (ZIGBEE_EZSP_ESCAPE == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Escape byte received"));
escape = true;
continue;
}
if (ZIGBEE_EZSP_CANCEL == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x1A, cancel byte received, discarding %d bytes"), zigbee_buffer->len());
zigbee_buffer->setLen(0); // empty buffer
escape = false;
frame_complete = false;
continue; // re-loop
}
if (ZIGBEE_EZSP_EOF == zigbee_in_byte) {
// end of frame
frame_complete = true;
break;
}
if (zigbee_buffer->len() < ZIGBEE_BUFFER_SIZE) {
if (escape) {
// invert bit 5
zigbee_in_byte ^= 0x20;
escape = false;
}
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
} // adding bytes
} // while (ZigbeeSerial->available())
uint32_t frame_len = zigbee_buffer->len();
if (frame_complete || (frame_len && (millis() > (zigbee_polling_window + ZIGBEE_POLLING)))) {
char hex_char[frame_len * 2 + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
if ((frame_complete) && (frame_len >= 3)) {
// frame received and has at least 3 bytes (without EOF), checking CRC
// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": received raw frame %s"), hex_char);
uint16_t crc = 0xFFFF; // frame CRC
// compute CRC
for (uint32_t i=0; i<frame_len-2; i++) {
crc = crc ^ ((uint16_t)zigbee_buffer->get8(i) << 8);
for (uint32_t i=0; i<8; i++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard
} else {
crc <<= 1;
}
}
}
uint16_t crc_received = zigbee_buffer->get8(frame_len - 2) << 8 | zigbee_buffer->get8(frame_len - 1);
// remove 2 last bytes
if (crc_received != crc) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %s"), crc_received, crc, hex_char);
} else {
// copy buffer
SBuffer ezsp_buffer = zigbee_buffer->subBuffer(0, frame_len - 2); // CRC
// CRC is correct, apply de-stuffing if DATA frame
if (0 == (ezsp_buffer.get8(0) & 0x80)) {
// DATA frame
uint8_t rand = 0x42;
for (uint32_t i=1; i<ezsp_buffer.len(); i++) {
ezsp_buffer.set8(i, ezsp_buffer.get8(i) ^ rand);
if (rand & 1) { rand = (rand >> 1) ^ 0xB8; }
else { rand = (rand >> 1); }
}
}
ToHex_P((unsigned char*)ezsp_buffer.getBuffer(), ezsp_buffer.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%s\"}"), hex_char);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); // TODO move to LOG_LEVEL_DEBUG when stable
}
// now process the message
ZigbeeProcessInputRaw(ezsp_buffer);
}
} else {
// the buffer timed-out, print error and discard
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %d"), hex_char);
}
zigbee_buffer->setLen(0); // empty buffer
escape = false;
frame_complete = false;
}
#endif // USE_ZIGBEE_EZSP
}
/********************************************************************************************/
// Initialize internal structures
void ZigbeeInitSerial(void)
{
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem1 = %d"), ESP_getFreeHeap());
zigbee.active = false;
if (PinUsed(GPIO_ZIGBEE_RX) && PinUsed(GPIO_ZIGBEE_TX)) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX));
// if seriallog_level is 0, we allow GPIO 13/15 to switch to Hardware Serial
ZigbeeSerial = new TasmotaSerial(Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX), seriallog_level ? 1 : 2, 0, 256); // set a receive buffer of 256 bytes
ZigbeeSerial->begin(115200);
if (ZigbeeSerial->hardwareSerial()) {
ClaimSerial();
uint32_t aligned_buffer = ((uint32_t)serial_in_buffer + 3) & ~3;
zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer) - 3, (char*) aligned_buffer);
} else {
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem2 = %d"), ESP_getFreeHeap());
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem3 = %d"), ESP_getFreeHeap());
}
zigbee.active = true;
zigbee.init_phase = true; // start the state machine
zigbee.state_machine = true; // start the state machine
ZigbeeSerial->flush();
}
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem9 = %d"), ESP_getFreeHeap());
}
#ifdef USE_ZIGBEE_ZNP
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
if ((len < 2) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len);
return;
}
uint8_t data_len = len - 2; // removing CMD1 and CMD2
if (ZigbeeSerial) {
uint8_t fcs = data_len;
ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF);
ZigbeeSerial->write(data_len);
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len);
for (uint32_t i = 0; i < len; i++) {
uint8_t b = pgm_read_byte(msg + i);
ZigbeeSerial->write(b);
fcs ^= b;
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b);
}
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
}
//
// Same code for `ZbZNPSend` and `ZbZNPReceive`
// building the complete message (intro, length)
//
void CmndZbZNPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
char *codes = RemoveSpace(XdrvMailbox.data);
int32_t size = strlen(XdrvMailbox.data);
SBuffer buf((size+1)/2);
while (size > 1) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
if (send) {
// Command was `ZbZNPSend`
ZigbeeZNPSend(buf.getBuffer(), buf.len());
} else {
// Command was `ZbZNPReceive`
ZigbeeProcessInput(buf);
}
}
ResponseCmndDone();
}
// For debug purposes only, simulates a message received
void CmndZbZNPReceive(void)
{
CmndZbZNPSendOrReceive(false);
}
void CmndZbZNPSend(void)
{
CmndZbZNPSendOrReceive(true);
}
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
// internal function to output a byte, and escape it (stuffing) if needed
void ZigbeeEZSPSend_Out(uint8_t out_byte) {
switch (out_byte) {
case 0x7E: // Flag byte
case 0x11: // XON
case 0x13: // XOFF
case 0x18: // Substitute byte
case 0x1A: // Cancel byte
case 0x7D: // Escape byte
// case 0xFF: // special wake-up
ZigbeeSerial->write(ZIGBEE_EZSP_ESCAPE); // send Escape byte 0x7D
ZigbeeSerial->write(out_byte ^ 0x20); // send with bit 5 inverted
break;
default:
ZigbeeSerial->write(out_byte); // send unchanged
break;
}
}
// Send low-level EZSP frames
//
// The frame should contain the Control Byte and Data Field
// The frame shouldn't be escaped, nor randomized
//
// Before sending:
// - send Cancel byte (0x1A) if requested
// - randomize Data Field if DATA Frame
// - compute CRC16
// - escape (stuff) reserved bytes
// - add EOF (0x7E)
// - send frame
// send_cancel: should we first send a EZSP_CANCEL (0x1A) before the message to clear any leftover
void ZigbeeEZSPSendRaw(const uint8_t *msg, size_t len, bool send_cancel) {
if ((len < 1) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEE_EZSP_SENT ": bad message len %d"), len);
return;
}
uint8_t data_len = len - 2; // removing CMD1 and CMD2
if (ZigbeeSerial) {
if (send_cancel) {
ZigbeeSerial->write(ZIGBEE_EZSP_CANCEL); // 0x1A
}
bool data_frame = (0 == (msg[0] & 0x80));
uint8_t rand = 0x42; // pseudo-randomizer initial value
uint16_t crc = 0xFFFF; // CRC16 CCITT initialization
for (uint32_t i=0; i<len; i++) {
uint8_t out_byte = msg[i];
// apply randomization if DATA field
if (data_frame && (i > 0)) {
out_byte ^= rand;
if (rand & 1) { rand = (rand >> 1) ^ 0xB8; }
else { rand = (rand >> 1); }
}
// compute CRC
crc = crc ^ ((uint16_t)out_byte << 8);
for (uint32_t i=0; i<8; i++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard
} else {
crc <<= 1;
}
}
// output byte
ZigbeeEZSPSend_Out(out_byte);
}
// send CRC16 in big-endian
ZigbeeEZSPSend_Out(crc >> 8);
ZigbeeEZSPSend_Out(crc & 0xFF);
// finally send End of Frame
ZigbeeSerial->write(ZIGBEE_EZSP_EOF); // 0x1A
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
}
// Send an EZSP command and data
// Ex: Version with min v8 = 000008
void ZigbeeEZSPSendCmd(const uint8_t *msg, size_t len, bool send_cancel) {
char hex_char[len*2 + 2];
ToHex_P(msg, len, hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %s"), hex_char);
SBuffer cmd(len+3); // prefix with seq number (1 byte) and frame control bytes (2 bytes)
cmd.add8(EZSP_Serial.ezsp_seq++);
cmd.add8(0x00); // Low byte of Frame Control
cmd.add8(0x01); // High byte of Frame Control, frameFormatVersion = 1
cmd.addBuffer(msg, len);
// send
ZigbeeEZSPSendDATA(cmd.getBuffer(), cmd.len(), send_cancel);
}
// Send an EZSP DATA frame, automatically calculating the correct frame numbers
void ZigbeeEZSPSendDATA(const uint8_t *msg, size_t len, bool send_cancel) {
uint8_t control_byte = ((EZSP_Serial.to_ack & 0x07) << 4) + (EZSP_Serial.from_ack & 0x07);
// increment to_ack
EZSP_Serial.to_ack = (EZSP_Serial.to_ack + 1) & 0x07;
// build complete frame
SBuffer buf(len+1);
buf.add8(control_byte);
buf.addBuffer(msg, len);
// send
ZigbeeEZSPSendRaw(buf.getBuffer(), buf.len(), send_cancel);
}
// Receive a high-level EZSP command/response, starting with 16-bits frame ID
int32_t ZigbeeProcessInputEZSP(class SBuffer &buf) {
// verify errors in first 2 bytes.
// TODO
// uint8_t sequence_num = buf.get8(0);
uint16_t frame_control = buf.get16(1);
bool truncated = frame_control & 0x02;
bool overflow = frame_control & 0x01;
bool callbackPending = frame_control & 0x04;
bool security_enabled = frame_control & 0x8000;
if (frame_control != 0x0180) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: specific frame_control 0x%04X"), frame_control);
}
// remove first 2 bytes, be
for (uint32_t i=0; i<buf.len()-3; i++) {
buf.set8(i, buf.get8(i+3));
}
buf.setLen(buf.len() - 3);
char hex_char[buf.len()*2 + 2];
// log message
ToHex_P((unsigned char*)buf.getBuffer(), buf.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%s\"}"), hex_char);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); // TODO move to LOG_LEVEL_DEBUG when stable
}
// Pass message to state machine
ZigbeeProcessInput(buf);
}
// Receive raw ASH frame (CRC was removed, data unstuffed) but still contains frame numbers
int32_t ZigbeeProcessInputRaw(class SBuffer &buf) {
uint8_t control_byte = buf.get8(0);
uint8_t ack_num = control_byte & 0x07; // keep 3 LSB
if (control_byte & 0x80) {
// non DATA frame
uint8_t frame_type = control_byte & 0xE0; // keep 3 MSB
if (frame_type == 0x80) {
// ACK
EZSP_Serial.from_ack = ack_num; // update ack num
} else if (frame_type == 0xA0) {
// NAK
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Received NAK %d, resending not implemented"), ack_num);
} else if (control_byte == 0xC1) {
// RSTACK
// received just after boot, either because of Power up, hardware reset or RST
Z_EZSP_RSTACK(buf.get8(2));
EZSP_Serial.from_ack = 0;
EZSP_Serial.to_ack = 0;
// pass it to state machine with a special 0xFFFE frame code (EZSP_RSTACK_ID)
buf.set8(0, Z_B0(EZSP_rstAck));
buf.set8(1, Z_B1(EZSP_rstAck));
// keep byte #2 with code
buf.setLen(3);
ZigbeeProcessInput(buf);
} else if (control_byte == 0xC2) {
// ERROR
Z_EZSP_ERROR(buf.get8(2));
zigbee.active = false; // stop all zigbee activities
} else {
// Unknown
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Received unknown control byte 0x%02X"), control_byte);
}
} else {
// DATA Frame
// check the frame number, and send ACK or NAK
if ((control_byte & 0x07) != EZSP_Serial.to_ack) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: wrong ack, received %d, expected %d"), control_byte & 0x07, EZSP_Serial.to_ack);
//EZSP_Serial.to_ack = control_byte & 0x07;
}
// MCU acknowledged the correct frame
// we acknowledge the frame too
EZSP_Serial.from_ack = ((control_byte >> 4) + 1) & 0x07;
uint8_t ack_byte = 0x80 | EZSP_Serial.from_ack;
ZigbeeEZSPSendRaw(&ack_byte, 1, false); // send a 1-byte ACK
// build the EZSP frame
// remove first byte
for (uint8_t i=0; i<buf.len()-1; i++) {
buf.set8(i, buf.get8(i+1));
}
buf.setLen(buf.len()-1);
// pass to next level
ZigbeeProcessInputEZSP(buf);
}
}
//
// Same code for `ZbEZSPSend` and `ZbEZSPReceive`
// building the complete message (intro, length)
//
// ZbEZSPSend1 = high level EZSP command
// ZbEZSPSend2 = low level EZSP DATA frame (with sequence numbers)
// ZbEZSPSend3 = low level ASH frame
//
void CmndZbEZSPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
char *codes = RemoveSpace(XdrvMailbox.data);
int32_t size = strlen(XdrvMailbox.data);
SBuffer buf((size+1)/2);
while (size > 1) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
if (send) {
// Command was `ZbEZSPSend`
if (2 == XdrvMailbox.index) { ZigbeeEZSPSendDATA(buf.getBuffer(), buf.len(), true); }
else if (3 == XdrvMailbox.index) { ZigbeeEZSPSendRaw(buf.getBuffer(), buf.len(), true); }
else { ZigbeeEZSPSendCmd(buf.getBuffer(), buf.len(), true); }
} else {
// Command was `ZbEZSPReceive`
if (2 == XdrvMailbox.index) { ZigbeeProcessInput(buf); }
else if (3 == XdrvMailbox.index) { ZigbeeProcessInputRaw(buf); }
else { ZigbeeProcessInputEZSP(buf); } // TODO
}
}
ResponseCmndDone();
}
// Variants with managed ASH frame numbers
// For debug purposes only, simulates a message received
void CmndZbEZSPReceive(void)
{
CmndZbEZSPSendOrReceive(false);
}
void CmndZbEZSPSend(void)
{
CmndZbEZSPSendOrReceive(true);
}
#endif // USE_ZIGBEE_EZSP
//
// Internal function, send the low-level frame
// Input:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - clusterIf: 16-bits cluster number
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
// - cmdId: 8-bits ZCL command number
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
// - len: length of the 'msg' payload
// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond
// - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number
// Returns: None
//
void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(32+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
} else {
buf.add8(Z_Addr_ShortAddress); // 02
buf.add64(shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded
buf.add8(endpoint); // dest endpoint
}
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
buf.add8(0x01); // source endpoint
buf.add16(clusterId);
buf.add8(transacId); // transacId
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add16(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
}
#endif // USE_ZIGBEE

View File

@ -21,23 +21,6 @@
#define XDRV_23 23
#ifdef USE_ZIGBEE_ZNP
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
const uint8_t ZIGBEE_SOF = 0xFE;
const uint8_t ZIGBEE_SOF_ALT = 0xFF;
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
const uint32_t ZIGBEE_BUFFER_SIZE = 256;
const uint8_t ZIGBEE_EZSP_CANCEL = 0x1A; // cancel byte
const uint8_t ZIGBEE_EZSP_EOF = 0x7E; // end of frame
const uint8_t ZIGBEE_EZSP_ESCAPE = 0x7D; // escape byte
#endif // USE_ZIGBEE_EZSP
#include <TasmotaSerial.h>
TasmotaSerial *ZigbeeSerial = nullptr;
const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
#ifdef USE_ZIGBEE_ZNP
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|"
@ -68,221 +51,6 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbConfig,
};
//
// Called at event loop, checks for incoming data from the CC2530
//
void ZigbeeInputLoop(void) {
#ifdef USE_ZIGBEE_ZNP
static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte
static uint8_t fcs = ZIGBEE_SOF;
static uint32_t zigbee_frame_len = 5; // minimal zigbee frame length, will be updated when buf[1] is read
// Receive only valid ZNP frames:
// 00 - SOF = 0xFE
// 01 - Length of Data Field - 0..250
// 02 - CMD1 - first byte of command
// 03 - CMD2 - second byte of command
// 04..FD - Data Field
// FE (or last) - FCS Checksum
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZbInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len());
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
zigbee_frame_len = 5;
fcs = ZIGBEE_SOF;
// there is a rare race condition when an interrupt occurs when receiving the first byte
// in this case the first bit (lsb) is missed and Tasmota receives 0xFF instead of 0xFE
// We forgive this mistake, and next bytes are automatically resynchronized
if (ZIGBEE_SOF_ALT == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte);
zigbee_in_byte = ZIGBEE_SOF;
}
}
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
// waiting for SOF (Start Of Frame) byte, discard anything else
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte);
continue; // discard
}
if (zigbee_buffer->len() < zigbee_frame_len) {
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
fcs ^= zigbee_in_byte;
}
if (zigbee_buffer->len() >= zigbee_frame_len) {
zigbee_polling_window = 0; // Publish now
break;
}
// recalculate frame length
if (02 == zigbee_buffer->len()) {
// We just received the Lenght byte
uint8_t len_byte = zigbee_buffer->get8(1);
if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max
zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead
}
}
if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) {
char hex_char[(zigbee_buffer->len() * 2) + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
// buffer received, now check integrity
if (zigbee_buffer->len() != zigbee_frame_len) {
// Len is not correct, log and reject frame
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len);
} else if (0x00 != fcs) {
// FCS is wrong, packet is corrupt, log and reject frame
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs);
} else {
// frame is correct
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char);
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data);
}
// now process the message
ZigbeeProcessInput(znp_buffer);
}
zigbee_buffer->setLen(0); // empty buffer
}
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte
bool escape = false; // was the previous byte an escape?
bool frame_complete = false; // frame is ready and complete
// Receive only valid EZSP frames:
// 1A - Cancel - cancel all previous bytes
// 7D - Escape byte - following byte is escaped
// 7E - end of frame
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x%02X len=%d"), zigbee_in_byte, zigbee_buffer->len());
// if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
// escape = false;
// frame_complete = false;
// }
if ((0x11 == zigbee_in_byte) || (0x13 == zigbee_in_byte)) {
continue; // ignore reserved bytes XON/XOFF
}
if (ZIGBEE_EZSP_ESCAPE == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Escape byte received"));
escape = true;
continue;
}
if (ZIGBEE_EZSP_CANCEL == zigbee_in_byte) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x1A, cancel byte received, discarding %d bytes"), zigbee_buffer->len());
zigbee_buffer->setLen(0); // empty buffer
escape = false;
frame_complete = false;
continue; // re-loop
}
if (ZIGBEE_EZSP_EOF == zigbee_in_byte) {
// end of frame
frame_complete = true;
break;
}
if (zigbee_buffer->len() < ZIGBEE_BUFFER_SIZE) {
if (escape) {
// invert bit 5
zigbee_in_byte ^= 0x20;
escape = false;
}
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
} // adding bytes
} // while (ZigbeeSerial->available())
uint32_t frame_len = zigbee_buffer->len();
if (frame_complete || (frame_len && (millis() > (zigbee_polling_window + ZIGBEE_POLLING)))) {
char hex_char[frame_len * 2 + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
if ((frame_complete) && (frame_len >= 3)) {
// frame received and has at least 3 bytes (without EOF), checking CRC
// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": received raw frame %s"), hex_char);
uint16_t crc = 0xFFFF; // frame CRC
// compute CRC
for (uint32_t i=0; i<frame_len-2; i++) {
crc = crc ^ ((uint16_t)zigbee_buffer->get8(i) << 8);
for (uint32_t i=0; i<8; i++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard
} else {
crc <<= 1;
}
}
}
uint16_t crc_received = zigbee_buffer->get8(frame_len - 2) << 8 | zigbee_buffer->get8(frame_len - 1);
// remove 2 last bytes
if (crc_received != crc) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %s"), crc_received, crc, hex_char);
} else {
// copy buffer
SBuffer ezsp_buffer = zigbee_buffer->subBuffer(0, frame_len - 2); // CRC
// CRC is correct, apply de-stuffing if DATA frame
if (0 == (ezsp_buffer.get8(0) & 0x80)) {
// DATA frame
uint8_t rand = 0x42;
for (uint32_t i=1; i<ezsp_buffer.len(); i++) {
ezsp_buffer.set8(i, ezsp_buffer.get8(i) ^ rand);
if (rand & 1) { rand = (rand >> 1) ^ 0xB8; }
else { rand = (rand >> 1); }
}
}
ToHex_P((unsigned char*)ezsp_buffer.getBuffer(), ezsp_buffer.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%s\"}"), hex_char);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); // TODO move to LOG_LEVEL_DEBUG when stable
}
// now process the message
ZigbeeProcessInput(ezsp_buffer);
}
} else {
// the buffer timed-out, print error and discard
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %d"), hex_char);
}
zigbee_buffer->setLen(0); // empty buffer
escape = false;
frame_complete = false;
}
#endif // USE_ZIGBEE_EZSP
}
/********************************************************************************************/
// Initialize internal structures
@ -301,28 +69,7 @@ void ZigbeeInit(void)
// update commands with the current settings
Z_UpdateConfig(Settings.zb_channel, Settings.zb_pan_id, Settings.zb_ext_panid, Settings.zb_precfgkey_l, Settings.zb_precfgkey_h);
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem1 = %d"), ESP_getFreeHeap());
zigbee.active = false;
if (PinUsed(GPIO_ZIGBEE_RX) && PinUsed(GPIO_ZIGBEE_TX)) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX));
// if seriallog_level is 0, we allow GPIO 13/15 to switch to Hardware Serial
ZigbeeSerial = new TasmotaSerial(Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX), seriallog_level ? 1 : 2, 0, 256); // set a receive buffer of 256 bytes
ZigbeeSerial->begin(115200);
if (ZigbeeSerial->hardwareSerial()) {
ClaimSerial();
uint32_t aligned_buffer = ((uint32_t)serial_in_buffer + 3) & ~3;
zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer) - 3, (char*) aligned_buffer);
} else {
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem2 = %d"), ESP_getFreeHeap());
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem3 = %d"), ESP_getFreeHeap());
}
zigbee.active = true;
zigbee.init_phase = true; // start the state machine
zigbee.state_machine = true; // start the state machine
ZigbeeSerial->flush();
}
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem9 = %d"), ESP_getFreeHeap());
ZigbeeInitSerial();
}
/*********************************************************************************************\
@ -366,266 +113,6 @@ void CmndZbReset(void) {
}
}
#ifdef USE_ZIGBEE_ZNP
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
if ((len < 2) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len);
return;
}
uint8_t data_len = len - 2; // removing CMD1 and CMD2
if (ZigbeeSerial) {
uint8_t fcs = data_len;
ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF);
ZigbeeSerial->write(data_len);
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len);
for (uint32_t i = 0; i < len; i++) {
uint8_t b = pgm_read_byte(msg + i);
ZigbeeSerial->write(b);
fcs ^= b;
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b);
}
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
}
//
// Same code for `ZbZNPSend` and `ZbZNPReceive`
// building the complete message (intro, length)
//
void CmndZbZNPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
char *codes = RemoveSpace(XdrvMailbox.data);
int32_t size = strlen(XdrvMailbox.data);
SBuffer buf((size+1)/2);
while (size > 1) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
if (send) {
// Command was `ZbZNPSend`
ZigbeeZNPSend(buf.getBuffer(), buf.len());
} else {
// Command was `ZbZNPReceive`
ZigbeeProcessInput(buf);
}
}
ResponseCmndDone();
}
// For debug purposes only, simulates a message received
void CmndZbZNPReceive(void)
{
CmndZbZNPSendOrReceive(false);
}
void CmndZbZNPSend(void)
{
CmndZbZNPSendOrReceive(true);
}
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
// internal function to output a byte, and escape it (stuffing) if needed
void ZigbeeEZSPSend_Out(uint8_t out_byte) {
switch (out_byte) {
case 0x7E: // Flag byte
case 0x11: // XON
case 0x13: // XOFF
case 0x18: // Substitute byte
case 0x1A: // Cancel byte
case 0x7D: // Escape byte
ZigbeeSerial->write(ZIGBEE_EZSP_ESCAPE); // send Escape byte 0x7D
ZigbeeSerial->write(out_byte ^ 0x20); // send with bit 5 inverted
break;
default:
ZigbeeSerial->write(out_byte); // send unchanged
break;
}
}
// Send low-level EZSP frames
//
// The frame should contain the Control Byte and Data Field
// The frame shouldn't be escaped, nor randomized
//
// Before sending:
// - send Cancel byte (0x1A) if requested
// - randomize Data Field if DATA Frame
// - compute CRC16
// - escape (stuff) reserved bytes
// - add EOF (0x7E)
// - send frame
// send_cancel: should we first send a EZSP_CANCEL (0x1A) before the message to clear any leftover
void ZigbeeEZSPSend(const uint8_t *msg, size_t len, bool send_cancel = false) {
if ((len < 1) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEE_EZSP_SENT ": bad message len %d"), len);
return;
}
uint8_t data_len = len - 2; // removing CMD1 and CMD2
if (ZigbeeSerial) {
if (send_cancel) {
ZigbeeSerial->write(ZIGBEE_EZSP_CANCEL); // 0x1A
}
bool data_frame = (0 == (msg[0] & 0x80));
uint8_t rand = 0x42; // pseudo-randomizer initial value
uint16_t crc = 0xFFFF; // CRC16 CCITT initialization
for (uint32_t i=0; i<len; i++) {
uint8_t out_byte = msg[i];
// apply randomization if DATA field
if (data_frame && (i > 0)) {
out_byte ^= rand;
if (rand & 1) { rand = (rand >> 1) ^ 0xB8; }
else { rand = (rand >> 1); }
}
// compute CRC
crc = crc ^ ((uint16_t)out_byte << 8);
for (uint32_t i=0; i<8; i++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard
} else {
crc <<= 1;
}
}
// output byte
ZigbeeEZSPSend_Out(out_byte);
}
// send CRC16 in big-endian
ZigbeeEZSPSend_Out(crc >> 8);
ZigbeeEZSPSend_Out(crc & 0xFF);
// finally send End of Frame
ZigbeeSerial->write(ZIGBEE_EZSP_EOF); // 0x1A
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
}
//
// Same code for `ZbZNPSend` and `ZbZNPReceive`
// building the complete message (intro, length)
//
void CmndZbEZSPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
char *codes = RemoveSpace(XdrvMailbox.data);
int32_t size = strlen(XdrvMailbox.data);
SBuffer buf((size+1)/2);
while (size > 1) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
if (send) {
// Command was `ZbEZSPSend`
ZigbeeEZSPSend(buf.getBuffer(), buf.len());
} else {
// Command was `ZbEZSPReceive`
ZigbeeProcessInput(buf);
}
}
ResponseCmndDone();
}
// For debug purposes only, simulates a message received
void CmndZbEZSPReceive(void)
{
CmndZbEZSPSendOrReceive(false);
}
void CmndZbEZSPSend(void)
{
CmndZbEZSPSendOrReceive(true);
}
#endif // USE_ZIGBEE_EZSP
//
// Internal function, send the low-level frame
// Input:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - clusterIf: 16-bits cluster number
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
// - cmdId: 8-bits ZCL command number
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
// - len: length of the 'msg' payload
// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond
// - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number
// Returns: None
//
void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(32+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
} else {
buf.add8(Z_Addr_ShortAddress); // 02
buf.add64(shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded
buf.add8(endpoint); // dest endpoint
}
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
buf.add8(0x01); // source endpoint
buf.add16(clusterId);
buf.add8(transacId); // transacId
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add16(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
}
/********************************************************************************************/
//
// High-level function