Пример #1
0
/** @brief Decode a packet and respond
 *
 * This is the core function that controls which functionality is run when a
 * packet is received in full by the ISR code and control is passed back to the
 * main loop code. The vast majority of communications action happens here.
 */
void decodePacketAndRespond(){
	/* Extract and build up the header fields */
	TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

	/* Initialised here such that override is possible */
	TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
	TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

	// How big was the packet that we got back
	unsigned short RXPacketLengthReceived = (unsigned short)RXBufferCurrentPosition - (unsigned short)&RXBuffer;

	/* Check that the packet is big enough for header,ID,checksum */
	if(RXPacketLengthReceived < 4){
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		FLAG_AND_INC_FLAGGABLE(FLAG_SERIAL_PACKETS_UNDER_LENGTH_OFFSET);
		KeyUserDebugs.serialAndCommsCodeErrors++;
		return;
	}

	/* Pull out the received checksum and calculate the real one, then check */
	unsigned char RXReceivedChecksum = (unsigned char)*(RXBufferCurrentPosition - 1);
	unsigned char RXCalculatedChecksum = checksum((unsigned char*)&RXBuffer, RXPacketLengthReceived - 1);
	if(RXCalculatedChecksum != RXReceivedChecksum){
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		FLAG_AND_INC_FLAGGABLE(FLAG_SERIAL_CHECKSUM_MISMATCHES_OFFSET);
		KeyUserDebugs.serialAndCommsCodeErrors++;
		return;
	}

	/* Start this off as full packet length and build down to the actual length */
	RXCalculatedPayloadLength = RXPacketLengthReceived;

	/* Grab the RX header flags out of the RX buffer */
	RXBufferCurrentPosition = (unsigned char*)&RXBuffer;
	RXHeaderFlags = *RXBufferCurrentPosition;
	RXBufferCurrentPosition++;
	RXCalculatedPayloadLength--;

	/* Flag that we are transmitting! */
	TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
	// SCI0 only for now...

	/* Load a blank header into the TX buffer ready for masking */
	TXHeaderFlags = TXBufferCurrentPositionHandler;
	*TXHeaderFlags = 0;
	TXBufferCurrentPositionHandler++;

	/* Grab the payload ID for processing and load the return ID */
	RXHeaderPayloadID = *((unsigned short*)RXBufferCurrentPosition);
	*((unsigned short*)TXBufferCurrentPositionHandler) = RXHeaderPayloadID + 1;
	RXBufferCurrentPosition += 2;
	TXBufferCurrentPositionHandler += 2;
	RXCalculatedPayloadLength -= 2;

	/* Check that the length is sufficient for the fields configured. Packets
	 * that are too long will be caught and rejected on an individual payload
	 * ID basis as the information required to handle that is not available at
	 * this point. Packets that are too short are rejected immediately!
	 */
	if(((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 7))
		|| ((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXPacketLengthReceived < 6))
		|| ((RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 5))){
		finaliseAndSend(packetTooShortForSpecifiedFields);
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		return;
	}

	/* Subtract checksum to get final length */
	RXCalculatedPayloadLength--;

	if(RXHeaderFlags & HEADER_HAS_SEQUENCE){
		*TXBufferCurrentPositionHandler = *RXBufferCurrentPosition;
		RXBufferCurrentPosition++;
		TXBufferCurrentPositionHandler++;
		RXCalculatedPayloadLength--;
		*TXHeaderFlags |= HEADER_HAS_SEQUENCE;
	}

	if(RXHeaderFlags & HEADER_HAS_LENGTH){
		RXHeaderPayloadLength = *((unsigned short*)RXBufferCurrentPosition);
		RXBufferCurrentPosition += 2;
		RXCalculatedPayloadLength -= 2;
		/* Already subtracted one for checksum */
		if(RXHeaderPayloadLength != RXCalculatedPayloadLength){
			finaliseAndSend(payloadLengthHeaderMismatch);
			resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
			return;
		}
	}

	/* Calculate the position of the end of the stored packet for later use as a buffer */
	void* leftOverBuffer = (void*)((unsigned short)&RXBuffer + RXPacketLengthReceived);

	unsigned short errorID = 0;
	/* This is where all the communication logic resides.
	 *
	 * Please Note: Length and its flag should be set by each return packet
	 * type handler if required or desired. If an ack has been requested,
	 * ensure the negative ack flag is set if the operation failed.
	 */
	switch (RXHeaderPayloadID){
	// FreeEMS Core Comms Interface cases
		case requestInterfaceVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(interfaceVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&interfaceVersion, sizeof(interfaceVersion));
			TXBufferCurrentPositionHandler += sizeof(interfaceVersion);
			break;
		}
		case requestFirmwareVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(firmwareVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&firmwareVersion, sizeof(firmwareVersion));
			TXBufferCurrentPositionHandler += sizeof(firmwareVersion);
			break;
		}
		case requestMaxPacketSize:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Load the size into place */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RX_BUFFER_SIZE;
			TXBufferCurrentPositionHandler += 2;
			break;
		}
		case requestEchoPacketReturn:
		{
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RXPacketLengthReceived;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&RXBuffer, RXPacketLengthReceived);
			/* Note, there is no overflow check here because the TX buffer is slightly       */
			/* bigger than the RX buffer and there is overflow checking for receives anyway. */
			TXBufferCurrentPositionHandler += RXPacketLengthReceived;
			break;
		}
		case requestSoftSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{ // Perform soft system reset
				_start();
			}
			break;
		}
		case requestHardSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{
				/* This is how the serial monitor does it. */
				COPCTL = 0x01; /* Arm with shortest time */
				ARMCOP = 0xFF; /* Write bad value, should cause immediate reset */
				/* Using _start() only resets the app ignoring the monitor switch. It does not work */
				/* properly because the location of _start is not the master reset vector location. */
			}
			break;
		}
		case requestReInitOfSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{
				init();
			}
			break;
		}
	// FreeEMS Vanilla Firmware Specific cases
		case clearCountersAndFlagsToZero:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short zeroCounter;
			unsigned char* counterPointer = (unsigned char*) &Counters;
			for(zeroCounter = 0;zeroCounter < sizeof(Counter);zeroCounter++){
				*counterPointer = 0;
				counterPointer++;
			}
			KeyUserDebugs.flaggableFlags = 0;
			unsigned char* flaggablePointer = (unsigned char*) &Flaggables;
			for(zeroCounter = 0;zeroCounter < sizeof(Flaggable);zeroCounter++){
				*flaggablePointer = 0;
				flaggablePointer++;
			}
			break;
		}
		case requestDecoderName:
		case requestFirmwareBuildDate:
		case requestCompilerVersion:
		case requestOperatingSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char* stringToSend = 0;
			switch (RXHeaderPayloadID) {
				case requestDecoderName:
					stringToSend = (unsigned char*)decoderName;
					break;
				case requestFirmwareBuildDate:
					stringToSend = (unsigned char*)buildTimeAndDate;
					break;
				case requestCompilerVersion:
					stringToSend = (unsigned char*)compilerVersion;
					break;
				case requestOperatingSystem:
					stringToSend = (unsigned char*)operatingSystem;
					break;
			}
			/* This type must have a length field, set that up and load the body into place at the same time */
			*((unsigned short*)TXBufferCurrentPositionHandler) = stringCopy((TXBufferCurrentPositionHandler + 2), stringToSend);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			// Update with length field and string length.
			TXBufferCurrentPositionHandler += 2 + *((unsigned short*)TXBufferCurrentPositionHandler);
			break;
		}
		case updateBlockInRAM:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Don't let anyone write to running variables unless we are running BenchTest firmware!
			if((details.flags & block_is_read_only) && compare((unsigned char*)&decoderName, (unsigned char*)BENCH_TEST_NAME, sizeof(BENCH_TEST_NAME))){
				errorID = attemptToWriteToReadOnlyBlock;
				break;
			}

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in RAM!
			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			// Save page values for restore
			unsigned char oldRamPage = RPAGE;
			// Set the viewable RAM page
			RPAGE = details.RAMPage;

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.RAMAddress, details.size);

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					RPAGE = oldRamPage; // Restore the original RAM page, even when getting an error condition.
					break;
				}
			}

			// Copy from the RX buffer to the block of RAM
			memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

			// Check that the write was successful
			unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

			// Restore the original RAM and flash pages
			RPAGE = oldRamPage;

			if(index != 0){
				errorID = MEMORY_WRITE_ERROR;
			}
			break;
		}
		case updateBlockInFlash:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in flash!
			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					/* Save page value for restore and set the visible page */
					unsigned char oldFlashPage = PPAGE;
					PPAGE = details.FlashPage;

					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.FlashAddress, details.size);

					/* Restore the original flash page */
					PPAGE = oldFlashPage;

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			/* Copy the flash details and populate the RAM details with the buffer location */
			blockDetails burnDetails;
			burnDetails.FlashPage = details.FlashPage;
			burnDetails.FlashAddress = details.FlashAddress + offset;
			burnDetails.RAMPage = RPAGE;
			burnDetails.RAMAddress = RXBufferCurrentPosition;
			burnDetails.size = size;

			/* Copy from the RX buffer to the block of flash */
			errorID = writeBlock(&burnDetails, leftOverBuffer);
			if(errorID != 0){
				break;
			}

			/* If present in RAM, update that too */
			if((details.RAMPage != 0) && (details.RAMAddress != 0)){
				/* Save page values for restore */
				unsigned char oldRamPage = RPAGE;
				/* Set the viewable RAM page */
				RPAGE = details.RAMPage;

				/* Copy from the RX buffer to the block of RAM */
				memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

				/* Check that the write was successful */
				unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

				/* Restore the original RAM and flash pages */
				RPAGE = oldRamPage;

				if(index != 0){
					errorID = MEMORY_WRITE_ERROR;
				}
			}

			break;
		}
		case retrieveBlockFromRAM:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = size;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Save page value for restore and set the visible page */
			unsigned char oldRamPage = RPAGE;
			RPAGE = details.RAMPage;

			/* Copy the block of RAM to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.RAMAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			RPAGE = oldRamPage;

			break;
		}
		case retrieveBlockFromFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = size;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Save page value for restore and set the visible page */
			unsigned char oldFlashPage = PPAGE;
			PPAGE = details.FlashPage;

			/* Copy the block of flash to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.FlashAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			PPAGE = oldFlashPage;

			break;
		}
		case burnBlockFromRamToFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check that all data we need is present */
			if((details.RAMPage == 0) || (details.RAMAddress == 0) || (details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which burns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}


			// adjust details block to feed to represent the subsection of ram and flash that we want to burn down.
			details.RAMAddress += offset;
			details.FlashAddress += offset;
			details.size = size;

			/* Write the block down from RAM to Flash */
			errorID = writeBlock(&details, leftOverBuffer);
			break;
		}
		case requestDatalogPacket: // Set type through standard configuration methods
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Set the length field up */
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			unsigned short* localLength = (unsigned short*)TXBufferCurrentPositionHandler;
			TXBufferCurrentPositionHandler += 2;

			/* Fill out the log and send */
			*localLength = populateBasicDatalog();
			break;
		}
		case setAsyncDatalogType:
		{
			if(RXCalculatedPayloadLength != 1){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char newDatalogType = *((unsigned char*)RXBufferCurrentPosition);
			if(newDatalogType > asyncDatalogLastType){
				errorID = noSuchAsyncDatalogType;
				break;
			}

			TablesB.SmallTablesB.loggingSettings.datalogStreamType = newDatalogType;
			break;
		}
		case retrieveArbitraryMemory:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short length = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Make sure the buffer can handle the block
			if(length > TX_MAX_PAYLOAD_SIZE){
				errorID = requestedLengthTooLarge;
				break;
			}

			void* address = (void*) *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Ensure we don't try to read past the end of the address space
			if(((unsigned short)address) <= ((0xFFFF - length) + 1)){
				// TODO Possibly check and limit ranges
				errorID = requestedAddressDisallowed;
				break;
			}

			unsigned char RAMPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure RAM page is valid. Being too high is not possible.
			if(RAMPage < RPAGE_MIN){
				errorID = requestedRAMPageInvalid;
				break;
			}

			unsigned char FlashPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure Flash page is valid. Being too high is not possible.
			if(FlashPage < PPAGE_MIN){
				errorID = requestedFlashPageInvalid;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = length + 6;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Put the request payload into the reply */
			*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short) address;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned short*)TXBufferCurrentPositionHandler) = length;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned char*)TXBufferCurrentPositionHandler) = RAMPage;
			TXBufferCurrentPositionHandler++;
			*((unsigned char*)TXBufferCurrentPositionHandler) = FlashPage;
			TXBufferCurrentPositionHandler++;

			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, address, length);
			TXBufferCurrentPositionHandler += length;

			break;
		}
		case retrieveListOfLocationIDs:
		{
			if(RXCalculatedPayloadLength != 3){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the type of list that we want
			unsigned char listType = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;

			// Extract the mask for the qualities that we want
			unsigned short blockDetailsMask = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			unsigned short * listLength = (unsigned short*)TXBufferCurrentPositionHandler;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Zero the counter before we start, woops!
			*listLength = 0;

			unsigned long locationID;
			blockDetails details;
			for(locationID = 0;locationID < 65536;locationID++){
				unsigned short locationIDDoesntExist;
				locationIDDoesntExist = lookupBlockDetails((unsigned short)locationID, &details);

				if(!locationIDDoesntExist){
					if((listType == 0x00) || // get all
							((listType == 0x01) && (details.flags & blockDetailsMask)) || // get OR of bits
							((listType == 0x02) && (!(~(details.flags) & blockDetailsMask)))){ // get AND of bits
						*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short)locationID;
						TXBufferCurrentPositionHandler += 2;
						*listLength += 2;
					}
				}
			}

			break;
		}
		case retrieveLocationIDDetails:
		{
			if(RXCalculatedPayloadLength != 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(blockDetails);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Write straight to output buffer to save time/code
			errorID = lookupBlockDetails(locationID, (blockDetails*)TXBufferCurrentPositionHandler);

			if(errorID != 0){
				break;
			}

			// Adjust TX buffer position if successful
			TXBufferCurrentPositionHandler += sizeof(blockDetails);

			break;
		}
		case requestUnitTestOverSerial:
		{
			/*
			 * The idea here is to call this function with arguments, and data
			 * and have the result sent back for comparison with an expected
			 * result that isn't divulged to the firmware.
			 *
			 * It is intended that all testable functions be callable through
			 * this mechanism and that any number of test executions can be
			 * performed by an external suite using different parameters and
			 * data sets and matching expected results.
			 *
			 * The usual error mechanism shall be used to indicate some sort of
			 * either internal or test failure and returned errors shall be
			 * suitably descriptive to allow diagnosis and fixing of issues.
			 */

			// Must at least have test ID
			if(RXCalculatedPayloadLength < 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// grab unit test ID from payload
			unsigned short unitTestID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			switch(unitTestID){
				case testEmptyTest:
				{
					// Must be only the ID
					if(RXCalculatedPayloadLength != 2){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					*((unsigned short*)TXBufferCurrentPositionHandler) = unitTestID;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				case testTwoDTableUSLookup:
				{
					// ID + Value + Table
					if(RXCalculatedPayloadLength != (2 + 2 + sizeof(twoDTableUS))){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					unsigned short Value = *((unsigned short*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += 2;

					twoDTableUS* Table = ((twoDTableUS*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += sizeof(twoDTableUS);

					unsigned short result = lookupTwoDTableUS(Table, Value);

					*((unsigned short*)TXBufferCurrentPositionHandler) = result;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				// http://issues.freeems.org/view.php?id=156
				//
				/// TODO @todo test all things listed below:
				// lookupPagedMainTableCellValue - pass this RPAGE so that it remains unchanged
				// validateMainTable
				// validateTwoDTable
				// set table values - leave this till last, currently unused by mtx, likely to be removed anyway
				// generateDerivedVars - convert to pointers, remove headers, privatise a lot of data!
				// calculateFuelAndIgnition - ditto
				// scheduling algorithm - ditto
				// safeAdd
				// safeTrim
				// safeScale
				// sleep (milliseconds)
				// sleepMicro (microseconds)
				// checksum
				// stringCopy
				// compare
				// utils that can't be checked: sampleLoopADC sampleBlockADC sampleEachADC - can check for how long each takes! adjustPWM (test only anyway), resetToNonRunningState and setupPagedRAM (would interfere with functioning of device)
				// init code may be able to be partially checked
				// most other code at this stage is ISR code, flash writing code, or could interfere with the running of the engine
				// more testable code will appear with time, such as the HAL layer, and most accessory functions.
				default:
				{
					errorID = noSuchUnitTestID;
				}

			// each case:
				// checks length, fails if wrong
				// parses data into args
				// calls function on data/args
				// assembles response OR sets error
				// breaks
			}
			break;
		}
		case startBenchTestSequence:
		{
			// see TODO on include at top and modify this line appropriately
			if(!(compare((unsigned char*)&decoderName, (unsigned char*)BENCH_TEST_NAME, sizeof(BENCH_TEST_NAME)))){
				if(RXCalculatedPayloadLength < 1){
					errorID = payloadLengthTypeMismatch;
					break;
				}

				unsigned char localTestMode = *((unsigned char*)RXBufferCurrentPosition); //1; // The only mode, for now.
				RXBufferCurrentPosition++;
				if(localTestMode > TEST_MODE_BUMP_UP_CYCLES){
					errorID = unimplementedTestMode;
					break;
				}else if((localTestMode == TEST_MODE_STOP) && (RXCalculatedPayloadLength == 1)){
					if(!(coreStatusA & BENCH_TEST_ON)){
						errorID = benchTestNotRunningToStop;
						break;
					}

					// Ensure we succeed at stopping it as quickly as possible.
					ATOMIC_START();
					KeyUserDebugs.currentEvent = testEventsPerCycle - 1; // Gets incremented then compared with testEventsPerCycle
					testNumberOfCycles = 1;                              // Gets decremented then compared with zero
					ATOMIC_END();

					// eventually save and return where it got to
					break;
				}else if((localTestMode == TEST_MODE_BUMP_UP_CYCLES) && (RXCalculatedPayloadLength == 2)){
					if(!(coreStatusA & BENCH_TEST_ON)){
						errorID = benchTestNotRunningToBump;
						break;
					}

					// Get bump value from payload
					unsigned char bumpCycles = *((unsigned char*)RXBufferCurrentPosition); //1; // The only mode, for now.
					RXBufferCurrentPosition++;

					if(bumpCycles == 0){
						errorID = bumpingByZeroMakesNoSense;
						break;
					}

					// Bump count by value from payload
					testNumberOfCycles += bumpCycles;
					// Given that this function is only for situations when A it's getting near to
					// zero and B the user is watching, not checking for overflow is reasonable.
					break;
				}else if((localTestMode == TEST_MODE_ITERATIONS) && (RXCalculatedPayloadLength == 24)){
					testMode = localTestMode;
					// do nothing to fall through, or move other code into here
				}else{
					errorID = packetSizeWrongForTestMode;
					break;
				}

				if(coreStatusA & BENCH_TEST_ON){
					errorID = benchTestAlreadyRunning;
					break;
				}

				testEventsPerCycle = *((unsigned char*)RXBufferCurrentPosition); //100;  // @ 10ms  =  1s
				RXBufferCurrentPosition++;
				if(testEventsPerCycle == 0){
					errorID = invalidEventsPerCycle;
					break;
				}

				testNumberOfCycles = *((unsigned short*)RXBufferCurrentPosition); //20;   // @ 1s    = 20s
				RXBufferCurrentPosition += 2;
				if(testNumberOfCycles == 0){
					errorID = invalidNumberOfCycles;
					break;
				}

				testTicksPerEvent = *((unsigned short*)RXBufferCurrentPosition); //12500; // @ 0.8us = 10ms
				RXBufferCurrentPosition += 2;
				if(testTicksPerEvent < decoderMaxCodeTime){
					errorID = tooShortOfAnEventPeriod;
					break;
				}

				// Pluck the arrays out of the packet for the loop below
				unsigned char* testEventNumbers = RXBufferCurrentPosition;
				RXBufferCurrentPosition += 6;
				unsigned short* testPulseWidths = (unsigned short*)RXBufferCurrentPosition;
				RXBufferCurrentPosition += 12;

				// Reset the clock for reading timeout
				Clocks.timeoutADCreadingClock = 0; // make this optional, such that we can use real inputs to determine pw and/or dwell.

				// Validate and transfer the per-channel data
				unsigned char channel;
				unsigned char configuredChannels = 6;
				for(channel = 0;channel < 6;channel++){
					if(testPulseWidths[channel] > injectorSwitchOnCodeTime){ // See next block for warning.
						// use as-is
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = testPulseWidths[channel];
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else if(testPulseWidths[channel] > 3){
						// less than the code time, and not special, error!
						errorID = tooShortOfAPulseWidthToTest;
						// Warning, PWs close to this could be slightly longer than requested, that will change in later revisions.
						break;
					}else if(testPulseWidths[channel] == 3){
						testMode++; // Dirty hack to avoid dealing with Dave for the time being.
						testNumberOfMissing = channel;
					}else if(testPulseWidths[channel] == 2){
						// use the dwell from the core maths and input vars.
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = DerivedVars->Dwell;
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else if(testPulseWidths[channel] == 1){
						// use the reference pulse width from the core maths and input vars.
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = DerivedVars->RefPW;
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else{ // is zero
						// Set this channel to zero for and therefore off, don't set this channel.
						outputEventInputEventNumbers[channel] = 0xFF; // Off.
						configuredChannels--;
					}
				}

				if(configuredChannels == 0){
					errorID = noChannelsConfiguredToTest;
					break;
				}

				if(errorID == 0){
					// Let the first iteration roll it over to zero.
					KeyUserDebugs.currentEvent = 0xFF; // Needs to be here in case of multiple runs, init is not sufficient

					if(testMode == TEST_MODE_DODGY_MISSING_TOOTH){
						if(testEventsPerCycle <= 127){
							testEventsPerCycle *= 2;
						}else{
							errorID = tooManyEventsPerCycleMissingTth;
							break;
						}

						// Store the time per event in RPM such that it can be updated dynamically
						CoreVars->RPM = testTicksPerEvent;

						// The channels to use rely on the defaults from initialisers! Custom builds can break BenchTest mode!

						// Un-schedule anything that got scheduled
						outputEventInputEventNumbers[2] = 0xFF;
						outputEventInputEventNumbers[3] = 0xFF;
						outputEventInputEventNumbers[4] = 0xFF;
						outputEventInputEventNumbers[5] = 0xFF;
					}else if(testMode > TEST_MODE_DODGY_MISSING_TOOTH){
						errorID = unimplementedTestMode;
						break;
					}

					// Trigger decoder interrupt to fire thus starting the loop!
					TIE = 0x01; // The ISR does the rest!

					// Nothing went wrong, now set flag.
					coreStatusA |= BENCH_TEST_ON;
				}else{
					break;
				}


/* http://issues.freeems.org/view.php?id=155
 *
 * The following block has been left in, as I still do not know why it won't work as intended:
 *
 * - It should fire all 6 output pins with a 52ms duration pulse, exactly once.
 * - The SAME code run from anywhere else (pre main loop, in main loop, in rtc, in decoder) works fine, just not here in commsCore.c
 * - The interrupts run, but the pin doesn't change state, despite the registers being configured correctly
 *
 * I've tried quite a bit:
 *
 * - Moving this code around
 * - Checking memory definitions
 * - Completely rewriting the output ISR
 * - Adding significant debug to output ISR
 * - Checking for register contents in output ISR
 * - Checking for key things modified in this file
 * - General head scratching and confused searching
 */

//				outputEventPinNumbers[0] = 0; // 1 ign
//				outputEventPinNumbers[1] = 1; // 2 ign
//				outputEventPinNumbers[2] = 2; // 3 ign/1 fuel
//				outputEventPinNumbers[3] = 3; // 4 ign/2 fuel
//				outputEventPinNumbers[4] = 4; // 3 fuel
//				outputEventPinNumbers[5] = 5; // 4 fuel
//				outputEventDelayFinalPeriod[0] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[1] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[2] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[3] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[4] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[5] = decoderMaxCodeTime;
//				outputEventPulseWidthsMath[0] = SHORTMAX;
//				outputEventPulseWidthsMath[1] = SHORTMAX;
//				outputEventPulseWidthsMath[2] = SHORTMAX;
//				outputEventPulseWidthsMath[3] = SHORTMAX;
//				outputEventPulseWidthsMath[4] = SHORTMAX;
//				outputEventPulseWidthsMath[5] = SHORTMAX;
//
//				unsigned short edgeTimeStamp = TCNT;
//				// call sched output with args
//				LongTime timeStamp;
//				/* Install the low word */
//				timeStamp.timeShorts[1] = edgeTimeStamp;
//				/* Find out what our timer value means and put it in the high word */
//				if(TFLGOF && !(edgeTimeStamp & 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
//					timeStamp.timeShorts[0] = timerExtensionClock + 1;
//				}else{
//					timeStamp.timeShorts[0] = timerExtensionClock;
//				}
//
//				schedulePortTPin(0, timeStamp);
//				schedulePortTPin(1, timeStamp);
//				schedulePortTPin(2, timeStamp);
//				schedulePortTPin(3, timeStamp);
//				schedulePortTPin(4, timeStamp);
//				schedulePortTPin(5, timeStamp);
//
//				sleep(1000);
			}else{
				errorID = thisIsNotTheBenchTestDecoder;
			}
			break;
		}
		default:
		{
			if((RXHeaderPayloadID % 2) == 1){
				errorID = invalidPayloadID;
			}else{
				errorID = unrecognisedPayloadID;
			}
			break;
		}
	}

	// Always reply, if errorID is zero it's just an ack.
	finaliseAndSend(errorID);

	/* Switch reception back on now that we are done with the received data */
	resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
}
Пример #2
0
/** @brief Decode a packet and respond
 *
 * This is the core function that controls which functionality is run when a
 * packet is received in full by the ISR code and control is passed back to the
 * main loop code. The vast majority of communications action happens here.
 *
 * @author Fred Cooke
 */
void decodePacketAndRespond(){
	/* Extract and build up the header fields */
	RXBufferCurrentPosition = (unsigned char*)&RXBuffer;
	TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

	/* Initialised here such that override is possible */
	TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
	TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

	/* Start this off as full packet length and build down to the actual length */
	RXCalculatedPayloadLength = RXPacketLengthReceived;

	/* Grab the RX header flags out of the RX buffer */
	RXHeaderFlags = *RXBufferCurrentPosition;
	RXBufferCurrentPosition++;
	RXCalculatedPayloadLength--;

	/* Flag that we are transmitting! */
	TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
	// SCI0 only for now...

	/* Load a blank header into the TX buffer ready for masking */
	unsigned char* TXHeaderFlags = TXBufferCurrentPositionHandler;
	*TXHeaderFlags = 0;
	TXBufferCurrentPositionHandler++;

	/* Grab the payload ID for processing and load the return ID */
	RXHeaderPayloadID = *((unsigned short*)RXBufferCurrentPosition);
	*((unsigned short*)TXBufferCurrentPositionHandler) = RXHeaderPayloadID + 1;
	RXBufferCurrentPosition += 2;
	TXBufferCurrentPositionHandler += 2;
	RXCalculatedPayloadLength -= 2;

	/* Check that the length is sufficient for the fields configured. Packets
	 * that are too long will be caught and rejected on an individual payload
	 * ID basis as the information required to handle that is not available at
	 * this point. Packets that are too short are rejected immediately!
	 */
	if(((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 7))
		|| ((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXPacketLengthReceived < 6))
		|| ((RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 5))){
		finaliseAndSend(packetTooShortForSpecifiedFields);
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		return;
	}

	/* Subtract checksum to get final length */
	RXCalculatedPayloadLength--;

	if(RXHeaderFlags & HEADER_HAS_SEQUENCE){
		*TXBufferCurrentPositionHandler = *RXBufferCurrentPosition;
		RXBufferCurrentPosition++;
		TXBufferCurrentPositionHandler++;
		RXCalculatedPayloadLength--;
		*TXHeaderFlags |= HEADER_HAS_SEQUENCE;
	}

	if(RXHeaderFlags & HEADER_HAS_LENGTH){
		RXHeaderPayloadLength = *((unsigned short*)RXBufferCurrentPosition);
		RXBufferCurrentPosition += 2;
		RXCalculatedPayloadLength -= 2;
		/* Already subtracted one for checksum */
		if(RXHeaderPayloadLength != RXCalculatedPayloadLength){
			finaliseAndSend(payloadLengthHeaderMismatch);
			resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
			return;
		}
	}

	/* Calculate the position of the end of the stored packet for later use as a buffer */
	void* leftOverBuffer = (void*)((unsigned short)&RXBuffer + RXPacketLengthReceived);

	unsigned short errorID = 0;
	/* This is where all the communication logic resides.
	 *
	 * Please Note: Length and it's flag should be set by each return packet
	 * type handler if required or desired.	If an ack has been requested,
	 * ensure the negative ack flag is set if the operation failed.
	 */
	switch (RXHeaderPayloadID){
		case requestInterfaceVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(interfaceVersionAndType);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&interfaceVersionAndType, sizeof(interfaceVersionAndType));
			TXBufferCurrentPositionHandler += sizeof(interfaceVersionAndType);
			break;
		}
		case requestFirmwareVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(firmwareVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&firmwareVersion, sizeof(firmwareVersion));
			TXBufferCurrentPositionHandler += sizeof(firmwareVersion);
			break;
		}
		case requestMaxPacketSize:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Load the size into place */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RX_BUFFER_SIZE;
			TXBufferCurrentPositionHandler += 2;
			break;
		}
		case requestEchoPacketReturn:
		{
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RXPacketLengthReceived;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&RXBuffer, RXPacketLengthReceived);
			/* Note, there is no overflow check here because the TX buffer is slightly       */
			/* bigger than the RX buffer and there is overflow checking for receives anyway. */
			TXBufferCurrentPositionHandler += RXPacketLengthReceived;
			break;
		}
		case requestSoftSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Perform soft system reset */
			_start();
		}
		case requestHardSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This is how the serial monitor does it. */
			COPCTL = 0x01; /* Arm with shortest time */
			ARMCOP = 0xFF; /* Write bad value, should cause immediate reset */
			/* Using _start() only resets the app ignoring the monitor switch. It does not work */
			/* properly because the location of _start is not the master reset vector location. */
		}
		case requestReInitOfSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			init();
			break;
		}
		case updateBlockInRAM:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in RAM!
			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			// Save page values for restore
			unsigned char oldRamPage = RPAGE;
			// Set the viewable RAM page
			RPAGE = details.RAMPage;

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.RAMAddress, details.size);

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			// Copy from the RX buffer to the block of RAM
			memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

			// Check that the write was successful
			unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

			// Restore the original RAM and flash pages
			RPAGE = oldRamPage;

			if(index != 0){
				errorID = MEMORY_WRITE_ERROR;
			}
			break;
		}
		case updateBlockInFlash:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in flash!
			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					/* Save page value for restore and set the visible page */
					unsigned char oldFlashPage = PPAGE;
					PPAGE = details.FlashPage;

					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.FlashAddress, details.size);

					/* Restore the original flash page */
					PPAGE = oldFlashPage;

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			/* Copy the flash details and populate the RAM details with the buffer location */
			blockDetails burnDetails;
			burnDetails.FlashPage = details.FlashPage;
			burnDetails.FlashAddress = details.FlashAddress + offset;
			burnDetails.RAMPage = RPAGE;
			burnDetails.RAMAddress = RXBufferCurrentPosition;
			burnDetails.size = size;

			/* Copy from the RX buffer to the block of flash */
			errorID = writeBlock(&burnDetails, leftOverBuffer);
			if(errorID != 0){
				break;
			}

			/* If present in RAM, update that too */
			if((details.RAMPage != 0) && (details.RAMAddress != 0)){
				/* Save page values for restore */
				unsigned char oldRamPage = RPAGE;
				/* Set the viewable RAM page */
				RPAGE = details.RAMPage;

				/* Copy from the RX buffer to the block of RAM */
				memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

				/* Check that the write was successful */
				unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

				/* Restore the original RAM and flash pages */
				RPAGE = oldRamPage;

				if(index != 0){
					errorID = MEMORY_WRITE_ERROR;
				}
			}

			break;
		}
		case retrieveBlockFromRAM:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			/* Save page value for restore and set the visible page */
			unsigned char oldRamPage = RPAGE;
			RPAGE = details.RAMPage;

			/* Copy the block of RAM to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.RAMAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			RPAGE = oldRamPage;

			break;
		}
		case retrieveBlockFromFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			/* Save page value for restore and set the visible page */
			unsigned char oldFlashPage = PPAGE;
			PPAGE = details.FlashPage;

			/* Copy the block of flash to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.FlashAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			PPAGE = oldFlashPage;

			break;
		}
		case burnBlockFromRamToFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check that all data we need is present */
			if((details.RAMPage == 0) || (details.RAMAddress == 0) || (details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}


			// adjust details block to feed to represent the subsection of ram and flash that we want to burn down.
			details.RAMAddress += offset;
			details.FlashAddress += offset;
			details.size = size;

			/* Write the block down from RAM to Flash */
			errorID = writeBlock(&details, leftOverBuffer);
			break;
		}
		case adjustMainTableCell:
		{
			if(RXCalculatedPayloadLength != 8){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short RPMIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			unsigned short LoadIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			unsigned short cellValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableCellValue(details.RAMPage, details.RAMAddress, RPMIndex, LoadIndex, cellValue);
			break;
		}
		case adjustMainTableRPMAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short RPMIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short RPMValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableRPMValue(details.RAMPage, details.RAMAddress, RPMIndex, RPMValue);
			break;
		}
		case adjustMainTableLoadAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short LoadIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short LoadValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableLoadValue(details.RAMPage, details.RAMAddress, LoadIndex, LoadValue);
			break;
		}
		case adjust2dTableAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a 2d table */
			if(!(details.flags & block_is_2dus_table)){
				errorID = invalidIDForTwoDTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short axisIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short axisValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedTwoDTableAxisValue(details.RAMPage, details.RAMAddress, axisIndex, axisValue);
			break;
		}
		case adjust2dTableCell:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a 2d table */
			if(!(details.flags & block_is_2dus_table)){
				errorID = invalidIDForTwoDTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short cellIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short cellValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedTwoDTableCellValue(details.RAMPage, details.RAMAddress, cellIndex, cellValue);
			break;
		}
		case requestBasicDatalog:
		{
			if((RXCalculatedPayloadLength > 2) || (RXCalculatedPayloadLength == 1)){
				errorID = payloadLengthTypeMismatch;
				break;
			}else if(RXCalculatedPayloadLength == 2){
				unsigned short newConfiguredLength = *((unsigned short*)RXBufferCurrentPosition);
				if(newConfiguredLength > maxBasicDatalogLength){
					errorID = datalogLengthExceedsMax;
					break;
				}else{
					configuredBasicDatalogLength = newConfiguredLength;
				}
			}// fall through to use existing configured length

			/* Set the length field up */
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			*(unsigned short*)TXBufferCurrentPositionHandler = configuredBasicDatalogLength;
			TXBufferCurrentPositionHandler += 2;

			/* Fill out the log and send */
			populateBasicDatalog();
			break;
		}
		case requestConfigurableDatalog:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case setAsyncDatalogType:
		{
			if(RXCalculatedPayloadLength != 1){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char newDatalogType = *((unsigned char*)RXBufferCurrentPosition);
			if(newDatalogType > 0x01){
				errorID = noSuchAsyncDatalogType;
				break;
			}

			TablesB.SmallTablesB.datalogStreamType = newDatalogType;
			break;
		}
		case forwardPacketOverCAN:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case forwardPacketOverOtherUART:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case retrieveArbitraryMemory:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short length = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Make sure the buffer can handle the block
			if(length > TX_MAX_PAYLOAD_SIZE){
				errorID = requestedLengthTooLarge;
				break;
			}

			void* address = (void*) *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Ensure we don't try to read past the end of the address space
			if(((unsigned short)address) <= ((0xFFFF - length) + 1)){
				// TODO Possibly check and limit ranges
				errorID = requestedAddressDisallowed;
				break;
			}

			unsigned char RAMPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure RAM page is valid. Being too high is not possible.
			if(RAMPage < RPAGE_MIN){
				errorID = requestedRAMPageInvalid;
				break;
			}

			unsigned char FlashPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure Flash page is valid. Being too high is not possible.
			if(FlashPage < PPAGE_MIN){
				errorID = requestedFlashPageInvalid;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = length + 6;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Put the request payload into the reply */
			*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short) address;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned short*)TXBufferCurrentPositionHandler) = length;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned char*)TXBufferCurrentPositionHandler) = RAMPage;
			TXBufferCurrentPositionHandler++;
			*((unsigned char*)TXBufferCurrentPositionHandler) = FlashPage;
			TXBufferCurrentPositionHandler++;

			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, address, length);
			TXBufferCurrentPositionHandler += length;

			break;
		}
		case retrieveListOfLocationIDs:
		{
			if(RXCalculatedPayloadLength != 3){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the type of list that we want
			unsigned char listType = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;

			// Extract the mask for the qualities that we want
			unsigned short blockDetailsMask = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			unsigned short * listLength = (unsigned short*)TXBufferCurrentPositionHandler;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Zero the counter before we start, woops!
			*listLength = 0;

			unsigned long locationID;
			blockDetails details;
			for(locationID = 0;locationID < 65536;locationID++){
				unsigned short locationIDDoesntExist;
				locationIDDoesntExist = lookupBlockDetails((unsigned short)locationID, &details);

				if(!locationIDDoesntExist){
					if((listType == 0x00) || // get all
							((listType == 0x01) && (details.flags & blockDetailsMask)) || // get OR of bits
							((listType == 0x02) && (!(~(details.flags) & blockDetailsMask)))){ // get AND of bits
						*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short)locationID;
						TXBufferCurrentPositionHandler += 2;
						*listLength += 2;
					}
				}
			}

			break;
		}
		case retrieveLocationIDDetails:
		{
			if(RXCalculatedPayloadLength != 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Write straight to output buffer to save time/code
			errorID = lookupBlockDetails(locationID, (blockDetails*)TXBufferCurrentPositionHandler);

			if(errorID != 0){
				break;
			}

			// Adjust TX buffer position if successful
			TXBufferCurrentPositionHandler += sizeof(blockDetails);

			break;
		}
		case requestUnitTestOverSerial:
		{
			/*
			 * The idea here is to call this function with arguments, and data
			 * and have the result sent back for comparison with an expected
			 * result that isn't divulged to the firmware.
			 *
			 * It is intended that all testable functions be callable through
			 * this mechanism and that any number of test executions can be
			 * performed by an external suite using different parameters and
			 * data sets and matching expected results.
			 *
			 * The usual error mechanism shall be used to indicate some sort of
			 * either internal or test failure and returned errors shall be
			 * suitably descriptive to allow diagnosis and fixing of issues.
			 */

			// Must at least have test ID
			if(RXCalculatedPayloadLength < 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// grab unit test ID from payload
			unsigned short unitTestID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			switch(unitTestID){
				case emptyTest:
				{
					// Must be only the ID
					if(RXCalculatedPayloadLength != 2){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					*((unsigned short*)TXBufferCurrentPositionHandler) = unitTestID;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				case twoDTableUSLookup:
				{
					// ID + Value + Table
					if(RXCalculatedPayloadLength != (2 + 2 + sizeof(twoDTableUS))){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					unsigned short Value = *((unsigned short*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += 2;

					twoDTableUS* Table = ((twoDTableUS*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += sizeof(twoDTableUS);

					unsigned short result = lookupTwoDTableUS(Table, Value);

					*((unsigned short*)TXBufferCurrentPositionHandler) = result;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				default:
				{
					errorID = noSuchUnitTestID;
				}

			// each case:
				// checks length, fails if wrong
				// parses data into args
				// calls function on data/args
				// assembles response OR sets error
				// breaks
			}
			break;
		}
		default:
		{
			if((RXHeaderPayloadID % 2) == 1){
				errorID = invalidPayloadID;
			}else{
				errorID = unrecognisedPayloadID;
			}
		}
	}

	// Always reply, if errorID is zero it's just an ack.
	finaliseAndSend(errorID);

	/* Switch reception back on now that we are done with the received data */
	resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
	PORTK |= BIT0;
}