// If data should just be polled, set packet_send to NULL. // // If no packet is received from slave the length in packet_recv will be set to 0, // the exact reason for that is encoded in the return value. // // For the return value see RED_STACK_TRANSCEIVE_RESULT_* at the top of this file. static int red_stack_spi_transceive_message(REDStackPacket *packet_send, Packet *packet_recv, REDStackSlave *slave) { int retval = 0; uint8_t length, length_send; uint8_t checksum; int rc; uint8_t sequence_number_master = 0xFF; uint8_t sequence_number_slave = 0xFF; uint8_t tx[RED_STACK_SPI_PACKET_SIZE] = {0}; uint8_t rx[RED_STACK_SPI_PACKET_SIZE] = {0}; // We assume that we don't receive anything. If we receive a packet the // length will be overwritten again packet_recv->header.length = 0; // Preamble is always the same tx[RED_STACK_SPI_PREAMBLE] = RED_STACK_SPI_PREAMBLE_VALUE; if (packet_send == NULL) { // If packet_send is NULL // we send a message with empty payload (4 byte) tx[RED_STACK_SPI_LENGTH] = RED_STACK_SPI_PACKET_EMPTY_SIZE; retval = RED_STACK_TRANSCEIVE_RESULT_SEND_NONE; } else if (slave->status == RED_STACK_SLAVE_STATUS_AVAILABLE) { length = packet_send->packet.header.length; if (length > sizeof(Packet)) { retval |= RED_STACK_TRANSCEIVE_RESULT_SEND_ERROR; log_error("Send length is greater then allowed (actual: %d > maximum: %d)", length, (int)sizeof(Packet)); goto ret; } retval = RED_STACK_TRANSCEIVE_DATA_SEND; tx[RED_STACK_SPI_LENGTH] = length + RED_STACK_SPI_PACKET_EMPTY_SIZE; memcpy(tx+2, &packet_send->packet, length); } else { retval = RED_STACK_TRANSCEIVE_RESULT_SEND_ERROR; log_error("Slave with stack address %d is not present in stack", slave->stack_address); goto ret; } length = tx[RED_STACK_SPI_LENGTH]; // Set master and slave sequence number tx[RED_STACK_SPI_INFO(length)] = slave->sequence_number_master | slave->sequence_number_slave; // Calculate checksum tx[RED_STACK_SPI_CHECKSUM(length)] = red_stack_spi_calculate_pearson_hash(tx, length-1); struct spi_ioc_transfer spi_transfer = { .tx_buf = (unsigned long)&tx, .rx_buf = (unsigned long)&rx, .len = RED_STACK_SPI_PACKET_SIZE, }; red_stack_spi_select(slave); rc = ioctl(_red_stack_spi_fd, SPI_IOC_MESSAGE(1), &spi_transfer); red_stack_spi_deselect(slave); if (rc < 0) { // Overwrite current return status with error, // it seems ioctl itself didn't work. retval = RED_STACK_TRANSCEIVE_RESULT_SEND_ERROR | RED_STACK_TRANSCEIVE_RESULT_READ_ERROR; if(packet_send == NULL) { slave->next_packet_empty = true; } log_error("ioctl failed: %s (%d)", get_errno_name(errno), errno); goto ret; } length_send = rc; if (length_send != RED_STACK_SPI_PACKET_SIZE) { // Overwrite current return status with error, // it seems ioctl itself didn't work. retval = RED_STACK_TRANSCEIVE_RESULT_SEND_ERROR | RED_STACK_TRANSCEIVE_RESULT_READ_ERROR; if(packet_send == NULL) { slave->next_packet_empty = true; } log_error("ioctl has unexpected result (actual: %d != expected: %d)", length_send, RED_STACK_SPI_PACKET_SIZE); goto ret; } if (rx[RED_STACK_SPI_PREAMBLE] != RED_STACK_SPI_PREAMBLE_VALUE) { // Do not log by default, an "unproper preamble" is part of the protocol // if the slave is too busy to fill the DMA buffers fast enough // log_error("Received packet without proper preamble (actual: %d != expected: %d)", // rx[RED_STACK_SPI_PREAMBLE], RED_STACK_SPI_PREAMBLE_VALUE); retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_ERROR; if(packet_send == NULL) { slave->next_packet_empty = true; } goto ret; } // Check length length = rx[RED_STACK_SPI_LENGTH]; if ((length != RED_STACK_SPI_PACKET_EMPTY_SIZE) && ((length < (RED_STACK_SPI_PACKET_EMPTY_SIZE + sizeof(PacketHeader))) || (length > RED_STACK_SPI_PACKET_SIZE))) { log_error("Received packet with malformed length: %d", length); retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_ERROR; if(packet_send == NULL) { slave->next_packet_empty = true; } goto ret; } // Calculate and check checksum checksum = red_stack_spi_calculate_pearson_hash(rx, length-1); if (checksum != rx[RED_STACK_SPI_CHECKSUM(length)]) { log_error("Received packet with wrong checksum (actual: %x != expected: %x)", checksum, rx[RED_STACK_SPI_CHECKSUM(length)]); retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_ERROR; if(packet_send == NULL) { slave->next_packet_empty = true; } goto ret; } // If we send data and the master sequence number matches to the one // set in the packet we know that the slave received the packet! if ((packet_send != NULL) /*&& (packet_send->status == RED_STACK_PACKET_STATUS_SEQUENCE_NUMBER_SET)*/) { sequence_number_master = rx[RED_STACK_SPI_INFO(length)] & RED_STACK_SPI_INFO_SEQUENCE_MASTER_MASK; if (sequence_number_master == slave->sequence_number_master) { retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_SEND)) | RED_STACK_TRANSCEIVE_RESULT_SEND_OK; // Increase sequence number for next packet red_stack_increase_master_sequence_number(slave); } } else { // If we didn't send anything we can always increase the sequence number, // it doesn't matter if the slave actually received it. red_stack_increase_master_sequence_number(slave); } // If the slave sequence number matches we already processed this packet sequence_number_slave = rx[RED_STACK_SPI_INFO(length)] & RED_STACK_SPI_INFO_SEQUENCE_SLAVE_MASK; if (sequence_number_slave == slave->sequence_number_slave) { retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_NONE; } else { // Otherwise we save the new sequence number slave->sequence_number_slave = sequence_number_slave; if (length == RED_STACK_SPI_PACKET_EMPTY_SIZE) { // Do not log by default, will produce 2000 log entries per second // log_packet_debug("Received empty packet over SPI (w/ header)"); retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_NONE; } else { // Everything seems OK, we can copy to buffer memcpy(packet_recv, rx+2, length - RED_STACK_SPI_PACKET_EMPTY_SIZE); log_packet_debug("Received packet over SPI (%s)", packet_get_response_signature(packet_signature, packet_recv)); retval = (retval & (~RED_STACK_TRANSCEIVE_RESULT_MASK_READ)) | RED_STACK_TRANSCEIVE_RESULT_READ_OK; retval |= RED_STACK_TRANSCEIVE_DATA_RECEIVED; } } ret: return retval; } // Creates the "routing table", which is just the // array of REDStackSlave structures. static void red_stack_spi_create_routing_table(void) { char base58[BASE58_MAX_LENGTH]; int tries, ret, i; uint8_t stack_address = 0; uint8_t uid_counter = 0; log_debug("Starting to discover SPI stack slaves"); while (stack_address < RED_STACK_SPI_MAX_SLAVES) { REDStackSlave *slave = &_red_stack.slaves[stack_address]; Packet packet; StackEnumerateResponse *response; REDStackPacket red_stack_packet = { slave, {{ 0, // UID 0 sizeof(StackEnumerateRequest), FUNCTION_STACK_ENUMERATE, 0x08, // Return expected 0 }, {0}, {0}}, RED_STACK_PACKET_STATUS_ADDED, }; // We have to assume that the slave is available slave->status = RED_STACK_SLAVE_STATUS_AVAILABLE; // Send stack enumerate request for (tries = 0; tries < RED_STACK_SPI_ROUTING_TRIES; tries++) { ret = red_stack_spi_transceive_message(&red_stack_packet, &packet, slave); if ((ret & RED_STACK_TRANSCEIVE_RESULT_MASK_SEND) == RED_STACK_TRANSCEIVE_RESULT_SEND_OK) { break; } SLEEP_NS(0, RED_STACK_SPI_ROUTING_WAIT); // Give slave some more time } if (tries == RED_STACK_SPI_ROUTING_TRIES) { // Slave does not seem to be available, slave->status = RED_STACK_SLAVE_STATUS_ABSENT; // This means that there can't be any more slaves above // and we are actually done here already! break; } // Receive stack enumerate response for (tries = 0; tries < RED_STACK_SPI_ROUTING_TRIES; tries++) { // We first check if we already received an answer before we try again if ((ret & RED_STACK_TRANSCEIVE_RESULT_MASK_READ) == RED_STACK_TRANSCEIVE_RESULT_READ_OK) { break; } // Here we sleep before transceive so that there is some time // between the sending of stack enumerate and the receiving // of the answer SLEEP_NS(0, RED_STACK_SPI_ROUTING_WAIT); // Give slave some more time ret = red_stack_spi_transceive_message(NULL, &packet, slave); } if (tries == RED_STACK_SPI_ROUTING_TRIES) { // Slave does not seem to be available, slave->status = RED_STACK_SLAVE_STATUS_ABSENT; // This means that there can't be any more slaves above // and we are actually done here already! break; } response = (StackEnumerateResponse *)&packet; for (i = 0; i < PACKET_MAX_STACK_ENUMERATE_UIDS; i++) { if (response->uids[i] != 0) { uid_counter++; stack_add_recipient(&_red_stack.base, response->uids[i], stack_address); log_debug("Found UID number %d of slave %d with UID %s", i, stack_address, base58_encode(base58, uint32_from_le(response->uids[i]))); } else { break; } } stack_address++; } _red_stack.slave_num = stack_address; log_info("SPI stack slave discovery done. Found %d slave(s) with %d UID(s) in total", stack_address, uid_counter); }
// New data available event handler void rs485_serial_data_available_handler(void* opaque) { (void)opaque; if(!send_verify_flag) { disable_all_timers(); } int bytes_received = 0; uint32_t uid_from_packet = 0; int add_recipient_opaque; Packet empty_packet; Packet data_packet; uint64_t dummy_read_buffer; // Merge or simply save the received bytes if(partial_receive_flag) { bytes_received = read(_rs485_serial_fd, &receive_buffer[partial_receive_merge_index], (RECEIVE_BUFFER_SIZE - partial_receive_merge_index)); printf("bytes_received %d\n", bytes_received); partial_receive_merge_index = partial_receive_merge_index + bytes_received; } else { partial_receive_merge_index = read(_rs485_serial_fd, receive_buffer, RECEIVE_BUFFER_SIZE); if (partial_receive_merge_index != 13) printf("partial_receive_merge_index %d\n", partial_receive_merge_index); } //log_info("PARTIAL_RECEIVE_MERGE_INDEX = %d", partial_receive_merge_index); // Checks at least 13 bytes is available in the receive buffer if(partial_receive_merge_index >= 13) { int packet_validation_code = is_valid_packet(receive_buffer, partial_receive_merge_index); switch(packet_validation_code) { case PACKET_SEND_VERIFY_OK: // Stop send verify timer if (read(_send_verify_event, &dummy_read_buffer, sizeof(uint64_t))) {} setup_timer(&send_verify_timer, TIME_UNIT_NSEC, 0); timerfd_settime(_send_verify_event, 0, &send_verify_timer, NULL); // Disabling TX gpio_output_clear(_tx_pin); end = microseconds(); // Clearing send verify flag send_verify_flag = 0; // Clearing partial receive flag partial_receive_flag = 0; if(sent_ack_of_data_packet) { sent_ack_of_data_packet = 0; master_current_request_processed = 1; master_poll_slave_timeout_handler(NULL); } log_debug("RS485: Send verified"); return; case PACKET_EMPTY_OK: // Proper empty packet log_debug("RS485: Empty packet received"); if(sent_current_request_from_queue){ queue_pop(&_rs485_extension.packet_to_modbus_queue, NULL); sent_current_request_from_queue = 0; } // Updating recipient in the routing table memcpy(&uid_from_packet, &receive_buffer[3], sizeof(uint32_t)); stack_add_recipient(&_rs485_extension.base, uid_from_packet, receive_buffer[0]); partial_receive_flag = 0; master_current_request_processed = 1; if(sent_ack_of_data_packet) { sent_ack_of_data_packet = 0; } master_poll_slave_timeout_handler(NULL); return; case PACKET_DATA_OK: // Proper data packet log_info("RS485: Data packet received"); // Send message into brickd dispatcher memset(&data_packet, 0, sizeof(Packet)); memcpy(&data_packet, &receive_buffer[3], partial_receive_merge_index - MODBUS_PACKET_OVERHEAD); network_dispatch_response(&data_packet); log_debug("RS485: Dispatched packet to network subsystem"); if(sent_current_request_from_queue){ queue_pop(&_rs485_extension.packet_to_modbus_queue, NULL); sent_current_request_from_queue = 0; } // Updating recipient in the routing table memcpy(&uid_from_packet, &receive_buffer[3], sizeof(uint32_t)); add_recipient_opaque = receive_buffer[0]; stack_add_recipient(&_rs485_extension.base, uid_from_packet, add_recipient_opaque); // Send ACK to the slave memset(&empty_packet, 0, sizeof(PacketHeader)); empty_packet.header.length = 8; send_modbus_packet(_rs485_extension.slaves[master_current_slave_to_process], current_sequence_number, &empty_packet); sent_ack_of_data_packet = 1; return; case PACKET_ERROR_SEND_VERIFY: // Retry the current request log_error("RS485: Send verify failed"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; case PACKET_ERROR_ADDRESS: // Retry the current request log_error("RS485: Wrong address in packet"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; case PACKET_ERROR_FUNCTION_CODE: log_error("RS485: Wrong function code in packet"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; case PACKET_ERROR_SEQUENCE_NUMBER: // Retry the current request log_info("RS485: Wrong sequence number in packet"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; case PACKET_ERROR_LENGTH: // Retry the current request log_error("RS485: Wrong length in packet"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; case PACKET_ERROR_LENGTH_PARTIAL: log_debug("RS485: Partial data packet recieved"); handle_partial_receive(); return; case PACKET_ERROR_CRC16: // Retry the current request log_error("RS485: Wrong CRC16 in packet"); partial_receive_flag = 0; master_current_retry = MASTER_RETRIES; master_retry_timeout_handler(NULL); return; default: return; } } else { log_debug("RS485: Partial packet recieved"); handle_partial_receive(); return; } if(send_verify_flag) { // Disabling TX gpio_output_clear(_tx_pin); end = microseconds(); // Stop send verify timer if (read(_send_verify_event, &dummy_read_buffer, sizeof(uint64_t))) {} setup_timer(&send_verify_timer, TIME_UNIT_NSEC, 0); timerfd_settime(_send_verify_event, 0, &send_verify_timer, NULL); // Clearing send verify flag send_verify_flag = 0; } abort_current_request(); return; }
// New packet from BrickletStack is send into brickd event loop static void bricklet_stack_dispatch_from_spi(void *opaque) { BrickletStack *bricklet_stack = (BrickletStack*)opaque; int i; eventfd_t ev; Packet *packet; (void)opaque; // handle at most 5 queued responses at once to avoid blocking the event // loop for too long for (i = 0; i < 5; ++i) { if (eventfd_read(bricklet_stack->notification_event, &ev) < 0) { if (errno_would_block()) { return; // no queue responses left } log_error("Could not read from SPI notification event: %s (%d)", get_errno_name(errno), errno); return; } mutex_lock(&bricklet_stack->response_queue_mutex); packet = queue_peek(&bricklet_stack->response_queue); mutex_unlock(&bricklet_stack->response_queue_mutex); if (packet == NULL) { // eventfd indicates a response but queue is empty log_error("Response queue and notification event are out-of-sync"); return; } // Update routing table (this is necessary for Co-MCU Bricklets) if (packet->header.function_id == CALLBACK_ENUMERATE) { stack_add_recipient(&bricklet_stack->base, packet->header.uid, 0); } if ((packet->header.function_id == CALLBACK_ENUMERATE) || (packet->header.function_id == FUNCTION_GET_IDENTITY)) { EnumerateCallback *ec = (EnumerateCallback*)packet; // If the Bricklet is a HAT Brick (ID 111) or HAT Zero Brick (ID 112) // we update the connected_uid. if((ec->device_identifier == 111) || (ec->device_identifier == 112)) { *bricklet_stack->config.connected_uid = ec->header.uid; } // If the Bricklet is connected to an isolator we don't have to // update the position and the connected UID. this is already // done by the isolator itself. if(ec->position != 'Z' || ec->connected_uid[0] == '\0') { memcpy(ec->connected_uid, PACKET_NO_CONNECTED_UID_STR, PACKET_NO_CONNECTED_UID_STR_LENGTH); if((*bricklet_stack->config.connected_uid != 0) && (ec->device_identifier != 111) && (ec->device_identifier != 112)) { char base58[BASE58_MAX_LENGTH]; base58_encode(base58, uint32_from_le(*bricklet_stack->config.connected_uid)); strncpy(ec->connected_uid, base58, BASE58_MAX_LENGTH); } ec->position = 'a' + bricklet_stack->config.num; } } // Send message into brickd dispatcher network_dispatch_response(packet); bricklet_stack->data_seen = true; mutex_lock(&bricklet_stack->response_queue_mutex); queue_pop(&bricklet_stack->response_queue, NULL); mutex_unlock(&bricklet_stack->response_queue_mutex); } }