/* * Copyright (c) 2013 by Felix Rusu * Library for facilitating wireless programming using an RFM12B radio (get library at: https://github.com/LowPowerLab/RFM12B) * and the SPI Flash memory library for arduino/moteino (get library at: TODO). * DEPENDS ON the two libraries mentioned above * Install all three of these libraries in your Arduino/libraries folder ([Arduino > Preferences] for location of Arduino folder) * * This file is free software; you can redistribute it and/or modify * it under the terms of either the GNU General Public License version 2 * or the GNU Lesser General Public License version 2.1, both as * published by the Free Software Foundation. */ #include #include /// Checks whether the last message received was a wireless programming request handshake /// If so it will start the handshake protocol, receive the new HEX image and /// store it on the external flash chip, then reboot /// Assumes radio has been initialized and has just received a message (is not in SLEEP mode, and you called CRCPass()) /// Assumes flash is an external SPI flash memory chip that has been initialized void CheckForWirelessHEX(RFM12B radio, SPIFlash flash, boolean DEBUG) { //special FLASH command, enter a FLASH image exchange sequence if (*radio.DataLen >= 4 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X' && radio.Data[3]=='?') { byte remoteID = radio.GetSender(); if (*radio.DataLen == 7 && radio.Data[4]=='E' && radio.Data[5]=='O' && radio.Data[6]=='F') { //sender must have not received EOF ACK so just resend radio.Send(remoteID, "FLX?OK",6); } else if (HandleWirelessHEXData(radio, remoteID, flash, DEBUG)) { if (DEBUG) Serial.print("FLASH IMG TRANSMISSION SUCCESS!\n"); resetUsingWatchdog(DEBUG); } else { if (DEBUG) Serial.print("Timeout, erasing written data ... "); flash.blockErase32K(0); //clear any written data if (DEBUG) Serial.println("DONE"); } } } boolean HandleWirelessHEXData(RFM12B radio, byte remoteID, SPIFlash flash, boolean DEBUG) { int seq=0; uint8_t c; long now=0; int tmp,len; char buffer[16]; int timeout = 3000; //3s for flash data uint16_t bytesFlashed=10; radio.SendACK("FLX?OK",6); //ACK the HANDSHAKE if (DEBUG) Serial.println("FLX?OK (ACK sent)"); //first clear the fist 32k block (dedicated to a new FLASH image) flash.blockErase32K(0); flash.writeBytes(0,"FLXIMG:", 7); flash.writeByte(9,':'); now=millis(); while(1) { if (radio.ReceiveComplete() && radio.CRCPass() && radio.GetSender() == remoteID) { byte dataLen = *radio.DataLen; if (dataLen >= 4 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X') { if (radio.Data[3]==':' && dataLen >= 7) //FLX:_:_ { byte index=3; int tmp = 0; //read packet SEQ for (byte i = 4; i<8; i++) //up to 4 characters for seq number { index++; if (radio.Data[i] >=48 && radio.Data[i]<=57) tmp = tmp*10+radio.Data[i]-48; else if (radio.Data[i]==':') { if (i==4) return false; else break; } } if (DEBUG) { Serial.print("radio ["); Serial.print(dataLen); Serial.print("] > "); PrintHex83((byte*)radio.Data, dataLen); } if (radio.Data[index++] != ':') return false; now = millis(); //got "good" packet if (tmp==seq) { for(byte i=index;i31744) { if (DEBUG) Serial.println("IMG exceeds 31k"); return false; //just return, let MAIN timeout } radio.SendACK("FLX?OK",6); if (DEBUG) Serial.println("FLX?OK"); //save # of bytes written flash.writeByte(7,(bytesFlashed-10)>>8); flash.writeByte(8,(bytesFlashed-10)); flash.writeByte(9,':'); return true; } } } #ifdef LED //blink! pinMode(LED,OUTPUT); digitalWrite(LED,HIGH); delay(1); digitalWrite(LED,LOW); #endif } //abort FLASH sequence if no valid packet received for a long time if (millis()-now > timeout) { return false; } } } //returns # of bytes read, up to 64 byte readSerialLine(void* input) { //char c; byte inputLen = 0; if (Serial.available()) { do { //c = Serial.read(); //if (c>=32 && c<=126) //only human readable ASCII ((byte*)input)[inputLen++]=Serial.read(); delay(2); //need it otherwise serial stream can get truncated } while(Serial.available()); ((byte*)input)[inputLen]=0; } return inputLen; } /// returns TRUE if a HEX file transmission was detected and it was actually transmitted successfully boolean CheckForSerialHEX(byte* input, byte inputLen, RFM12B radio, byte targetID, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG) { if (inputLen == 4 && input[0]=='F' && input[1]=='L' && input[2]=='X' && input[3]=='?') { if (HandleSerialHandshake(radio, targetID, false, TIMEOUT, ACKTIMEOUT, DEBUG)) { Serial.println("FLX?OK"); //signal serial handshake back to host script if (HandleSerialHEXData(radio, targetID, TIMEOUT, ACKTIMEOUT, DEBUG)) { Serial.println("FLX?OK"); //signal EOF serial handshake back to host script if (DEBUG) Serial.println("FLASH IMG TRANSMISSION SUCCESS"); return true; } if (DEBUG) Serial.println("FLASH IMG TRANSMISSION FAIL"); return false; } } return false; } boolean HandleSerialHandshake(RFM12B radio, byte targetID, boolean isEOF, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG) { //temp //return true; byte retries = 3; long now = millis(); while (millis()-now= 6) { //FLX:9: if (input[0]=='F' && input[1]=='L' && input[2]=='X') { if (input[3]==':') { byte index = 3; for (byte i = 4; i<8; i++) //up to 4 characters for seq number { index++; if (input[i] >=48 && input[i]<=57) tmp = tmp*10+input[i]-48; else if (input[i]==':') { if (i==4) return false; else break; } } //Serial.print("input[index] = ");Serial.print("[");Serial.print(index);Serial.print("]=");Serial.println(input[index]); if (input[index++] != ':') return false; now = millis(); //got good packet byte hexDataLen = validateHEXData(input+index, inputLen-index); if (hexDataLen>0) { if (tmp==seq) //only read data when packet number is the next expected SEQ number { byte sendBufLen = prepareSendBuffer(input+index+8, sendBuf, hexDataLen, seq); //extract HEX data from input to BYTE data into sendBuf (go from 2 HEX bytes to 1 byte), +8 will jump over the header directly to the HEX raw data //Serial.print("PREP ");Serial.print(sendBufLen); Serial.print(" > "); PrintHex83(sendBuf, sendBufLen); //SEND RADIO DATA if (sendHEXPacket(radio, remoteID, sendBuf, sendBufLen, seq, DEBUG)) { sprintf((char*)sendBuf, "FLX:%d:OK",seq); Serial.println((char*)sendBuf); //response to host (python?) seq++; } else return false; } } else Serial.println("FLX:INV"); } if (inputLen==7 && input[3]=='?' && input[4]=='E' && input[5]=='O' && input[6]=='F') { //SEND RADIO EOF return HandleSerialHandshake(radio, targetID, true, TIMEOUT, ACKTIMEOUT, DEBUG); } } } //abort FLASH sequence if no valid packet received for a long time timeoutcheck: if (millis()-now > TIMEOUT) { Serial.print("Timeout receiving FLASH image from SERIAL, aborting..."); //send abort msg or just let node timeout as well? return false; } } return true; } //returns length of HEX data bytes if everything is valid //returns 0 if any validation failed byte validateHEXData(void* data, byte length) { //assuming 1 byte record length, 2 bytes address, 1 byte record type, N data bytes, 1 CRC byte char* input = (char*)data; if (length <12 || length%2!=0) return 0; //shortest possible intel data HEX record is 12 bytes //Serial.print("VAL > "); Serial.println((char*)input); uint16_t checksum=0; //check valid HEX data and CRC for (byte i=0; i=48 && input[i]<=57) || (input[i] >=65 && input[i]<=70))) //0-9,A-F return 0; if (i%2 && i=65?MSB-55:MSB-48)*16 + (LSB>=65?LSB-55:LSB-48); } //return the SEQ of the ACK received, or -1 if invalid boolean sendHEXPacket(RFM12B radio, byte targetID, byte* sendBuf, byte hexDataLen, byte seq, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG) { long now = millis(); while(1){ if (DEBUG) { Serial.print("RFTX > "); PrintHex83(sendBuf, hexDataLen); } radio.Send(targetID, sendBuf, hexDataLen, true); if (waitForAck(radio, ACKTIMEOUT)) { byte ackLen = *radio.DataLen; if (DEBUG) { Serial.print("RFACK > "); Serial.print(ackLen); Serial.print(" > "); PrintHex83((byte*)radio.Data, ackLen); } if (ackLen >= 8 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X' && radio.Data[3]==':' && radio.Data[ackLen-3]==':' && radio.Data[ackLen-2]=='O' && radio.Data[ackLen-1]=='K') { int tmp=0; sscanf((const char*)radio.Data, "FLX:%d:OK", &tmp); return tmp == seq; } } if (millis()-now > TIMEOUT) { Serial.println("Timeout waiting for packet ACK, aborting FLASH operation ..."); break; //abort FLASH sequence if no valid ACK was received for a long time } } return false; } // wait a few milliseconds for proper ACK to me, return true if indeed received boolean waitForAck(RFM12B radio, uint16_t ACKTIMEOUT) { long now = millis(); while (millis() - now <= ACKTIMEOUT) { if (radio.ACKReceived(radio.GetSender())) return true; } return false; } void PrintHex83(uint8_t *data, uint8_t length) // prints 8-bit data in hex { char tmp[length*2+1]; byte first ; int j=0; for (uint8_t i=0; i> 4) | 48; if (first > 57) tmp[j] = first + (byte)39; else tmp[j] = first ; j++; first = (data[i] & 0x0F) | 48; if (first > 57) tmp[j] = first + (byte)39; else tmp[j] = first; j++; } tmp[length*2] = 0; Serial.println(tmp); } /// Use watchdog to reset void resetUsingWatchdog(boolean DEBUG) { //wdt_disable(); if (DEBUG) Serial.print("REBOOTING"); wdt_enable(WDTO_15MS); while(1) if (DEBUG) Serial.print('.'); }