/* * Copyright (C) 2017-2025 3devo (http://www.3devo.eu) * * 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, version 3. * * 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 . */ #include #include "Bus.h" #include "Crc.h" #include "BaseProtocol.h" static int configuredAddress = 0; static int handleGeneralCall(uint8_t *data, uint8_t len, uint8_t /* maxLen */) { if (len == 1 && data[0] == GeneralCallCommands::RESET) { resetSystem(); } else if (len == 1 && data[0] == GeneralCallCommands::RESET_ADDRESS) { BusResetDeviceAddress(); configuredAddress = 0; } return 0; } cmd_result handleCommand(uint8_t cmd, uint8_t *datain, uint8_t len, uint8_t *dataout, uint8_t maxLen) { if (maxLen < 5) return cmd_result(Status::NO_REPLY); switch (cmd) { case ProtocolCommands::GET_PROTOCOL_VERSION: dataout[0] = PROTOCOL_VERSION >> 8; dataout[1] = PROTOCOL_VERSION & 0xFF; return cmd_ok(2); case ProtocolCommands::SET_ADDRESS: if (len != 2) return cmd_result(Status::INVALID_ARGUMENTS); // Only respond if the hw type in the request is // the wildcard or matches ours. if (datain[1] != 0 && datain[1] != INFO_HW_TYPE) return cmd_result(Status::NO_REPLY); BusSetDeviceAddress(datain[0]); configuredAddress = datain[0]; return cmd_ok(); case ProtocolCommands::GET_MAX_PACKET_LENGTH: dataout[0] = MAX_PACKET_LENGTH >> 8; dataout[1] = MAX_PACKET_LENGTH & 0xFF; return cmd_ok(2); default: return processCommand(cmd, datain, len, dataout, maxLen); } } // The bus implementation will already have checked whether the request // is addressed to us, this just checks whether child select maybe // prevents a response. bool shouldRespondToAddress(uint8_t address) { #if defined(USE_CHILD_SELECT) // Child select does not apply to general call or the configured // address, and the pin is active low. return address == 0 || address == configuredAddress || !CHILD_SELECT_PIN.read(); #else (void)address; // unused return true; #endif } #if defined(USE_I2C) int BusCallback(uint8_t address, uint8_t *data, uint8_t len, uint8_t maxLen) { if (!shouldRespondToAddress(address)) return 0; if (address == 0) return handleGeneralCall(data, len, maxLen); // Check that there is at least room for a status, length and a CRC if (maxLen < 3) return 0; cmd_result res(0); // Check we received at least command and crc if (len < 2) { res = cmd_result(Status::INVALID_TRANSFER); } else { uint8_t crc = Crc8Ccitt().update(data, len).get(); if (crc != 0) { res = cmd_result(Status::INVALID_CRC); } else { // CRC checks out, process a command res = handleCommand(data[0], data + 1, len - 2, data + 2, maxLen - 3); if (res.status == Status::NO_REPLY) return 0; } } data[0] = res.status; data[1] = res.len; len = res.len + 2; uint8_t crc = Crc8Ccitt().update(data, len).get(); data[len++] = crc; return len; } #elif defined(USE_RS485) int BusCallback(uint8_t address, uint8_t *data, uint8_t len, uint8_t maxLen) { if (!shouldRespondToAddress(address)) return 0; // Check that there is at least room for an address, status, length and CRC if (maxLen < 5) return 0; cmd_result res(0); // Check we received at least command and crc if (len < 3) { res = cmd_result(Status::INVALID_TRANSFER); } else { uint16_t crc = Crc16Ibm().update(address).update(data, len - 2).get(); if (crc != (data[len - 2] | data[len - 1] << 8)) { // Invalid CRC, so no reply (we cannot // be sure that the message was really // for us, some someone else might also // reply). return 0; } else if (address == 0) { return handleGeneralCall(data, len - 2, maxLen); } else { // CRC checks out, process a command res = handleCommand(data[0], data + 1, len - 3, data + 3, maxLen - 5); if (res.status == Status::NO_REPLY) return 0; } } data[0] = address; data[1] = res.status; data[2] = res.len; len = res.len + 3; uint16_t crc = Crc16Ibm().update(data, len).get(); data[len++] = crc; data[len++] = crc >> 8; return len; } #endif