//T.A.Mitchell May 2018 //Modified for new hardware //Modbus commands have been commented out for now //LIBRARIES #include //EEPROM I/O #include //SPI Bus for SD Card #include //Simple SD card library #include //I2C Comms for RTC on A4 and A5 #include //OLED Display minimalist text library #include //OLED Display minimalist text library - extensions to support i2c #include //RTC #include //Software serial for comms on any digital pins //#include //Modbus RTU //DEFINITIONS to insert into code at compile time (so end up in flash memory not RAM) #define I2CDS1307Add 0x68 //I2C address for the RTC #define I2COLedAdd 0x3C //I2C address for OLED display //Indicator LED Pins #define PinLEDG 16 //Green LED cathode on pin 15 (= A1) - Flash on pulse on input channel 1 #define PinLEDR 15 //Red LED cathode on pin 17 (= A2) - Initalisation Error LED (ON), Set Clock (Flash) #define PinLEDAnode 14 //Anode pin, could just use permanent +5V //GSM Pins #define GsmRST 4 //GSM reset pin, pull low to reset the GSM #define GsmTx 2 //GSM Tx pin, receive pin on Arduino #define GsmRx 3 //GSM Rx pin, transmit pin on Arduino //SD Card Select (SPI bus) #define SdCS 10 //Set ChipSelect Pin - use 10 as SD.h reserves this pin anyhow //Pushbutton #define PinButton 5 //Digital pin connected to button //Modbus //#define Timeout 1000 //Maximum time for slave to respond (ms) //#define Polling 200 //Maximum scan rate of master to allow slave to return to idle (ms) //#define RetryCount 10 //Maximum retries if slave returns response Timeout or error //#define TxEnablePin 4 //Pin to set RS485 interface to transmit or receive (set pin high to Transmit, sets DE and RE on RS485 PCB high. DE must be high to transmit and RE low to receive) //Misc #define LongIntMax 4294967295 //Maximum value of an unsigned long integer, used in timer check in case value has reset to zero in the interval #define ClockInt 100 //Interval to check RTC (ms) to trigger next log to file. Should happen several times a second #define LcdUdInt 2000 //LCD Update Interval (ms). Should happen just over once a second so clock seconds increment nicely #define ButInt 500 //Maximum button press interval (ms), stops multiple triggering of button command actions due to fast running of code. 500ms is suitable, short enough not to be noticeable by user, but long enough to debounce #define MsgDelay 5000 //Delay (ms) after certain LCD messages before proceeding to next action that might write to the LCD, e.g. 5,000ms #define LEDPulseTime 200 //Time to light LED for to indicate a pulse (note: could light continuously if this is longer than debounce #define EEPROMCFGDataStart 16 //Address of start of sample config file data, first two bytes are length of remaining data (bytes 0-15 are 4 x 4 byte meter reads) //AT Commands //Read commands from eeprom and write straight to port, the only variables are the output filename and number of bytes to write. //Maximum number of setup commands, used to size arrays. The number of commands is less than or equal to this number and is read from the config file #define MaxGSMTestCmds 5 //Number of GSM Modem test commands #define MaxGSMInitCmds 10 //Number of GSM Modem initialisation commands #define MaxFTPInitCmds 10 //Number of FTP initialisation commands //GLOBAL VARIABLES char LoggerStatus = 'H'; //Logger status, initially H meaning wait to synch to full hour before writing to file (including after reboot), G = Go (logging) unsigned int LogInterval = 1; //Logging interval in seconds (overwritten with value from config file) //byte QtyRegs = 0; //Number of registers defined in config file to be read byte DispReg = 0; //Input to display on LCD (zero based) - changeable using button byte UploadRate = 1; //How often to upload data: 1 = Hourly, 2 = Daily byte NewSave = 1; //Whether an attempt has previoisly been made to FTP the data (used to only save readings to EEPROM on first attempt and set appropriate file for save when next new file available, and to trigger ftp retry of previous file) char MultiCol; //Multicolumn format for text files (Y/N) byte SetupError = 0; //1 if error occurred during setup, logging will not start File DataFile; //Reference to file char TempStr10[11]; //Temporary string read from file char OldFileName[13]; //Filename for previous log write to file char FileName[13]; //Name of current logging file char FTPFile[13]; //Filename for previous logging period: file to FTP unsigned int GsmSig = 0; //GSM Signal Strength % unsigned int GSMTimeout = 100; //Timeout for GSM response in ms (individual characters) unsigned int GSMLongTimeout = 5000; //Timeout for GSM response in ms (initial response, may be delayed by modem action) unsigned int GSMVeryLongTimeout = 65535; //Timeout for GSM response in ms (after write operation) (max value is 65,535ms) byte FirstModemCall = 1; //Ensures initialisation of modem and access point profile on first call to ftptest or if tests return an error //EEPROM Addresses unsigned int CFGFileLen; //Length of sample config file data in bytes byte SigStrCmdLen; //Length of signal Strength Command byte SigStrRespLen; //Length of signal strength response (exc. signal strength parameter) byte NGSMTestCmds; //Number of GSM test commands byte GSMTestCmdLen[MaxGSMTestCmds]; //Length of each GSM Test Command byte GSMTestRespLen[MaxGSMTestCmds]; //Length of each GSM Test Response byte NGSMInitCmds; //Number of GSM Initialisation commands byte GSMInitCmdLen[MaxGSMInitCmds]; //Length of each GSM Initialisation Command byte GSMInitRespLen[MaxGSMInitCmds]; //Length of each GSM Initialisation Response byte NFTPInitCmds; //Number of FTP Initialisation commands byte FTPInitCmdLen[MaxFTPInitCmds]; //Length of each FTP Initialisation Command byte FTPInitRespLen[MaxFTPInitCmds]; //Length of each FTP Initialisation Response unsigned int SigStrStartAddr; //Signal Strength command data start address unsigned int TestCmdStartAddr; //GSM test command data start address unsigned int GSMInitStartAddr; //GSM Initialisation command data start address unsigned int FTPInitStartAddr; //FTP Initialisation command data start address unsigned int FTPSendStartAddr; //FTP Send Commands start address byte GSMFileCmdLen; //Length of GSM Set Filename command byte GSMFileRespLen; //Length of GSM Set Filename command response byte GSMFTPStartCmdLen; //Length of GSM Start FTP command byte GSMFTPStartRespLen; //Length of GSM Start FTP command response byte GSMFTPReqCmdLen; //Length of GSM Request FTP Data send command byte GSMFTPReqRespLen; //Length of GSM Request FTP Data send command response byte GSMFTPEndCmdLen; // Length of GSM End FTP Data send command byte GSMFTPEndRespLen; // Length of GSM End FTP Data send command response byte GSMFTPEndSessLen; // Length of GSM End FTP Session command byte GSMFTPEndSessRespLen; // Length of GSM End FTP Session command response //Pulse Inputs byte PinIn[4] = {6, 7, 8, 9}; //Input pins for the pulse inputs byte OldInputState[4] = {0, 0, 0, 0}; //Old Input states when last read, so only triggers count on leading edge, 0 = high (open) byte NPulseInputs = 1; //Number of pulse inputs in use - overwritten from config file byte PulseDebounce = 50; //Pulse Input debounce - overwritten from config file. Applied to both contact closed time and contact open time: conctact must be closed for at least this time. At least this time must also elapse from contact opening to re-closing. unsigned int PulseWeight[4]; //Pulse weighting, e.g. 10 = 10 units per pulse, possibly not much point for electricity and gas, usually fraction of unit per pulse and the readings are integers unsigned long PulseReading[4] = {0, 0, 0, 0}; //Meter readings, defaults are used if readings file doesn't exist char PulseName[4][9]; //Pulse input names, first subscript is the number of array elements, the second is the size of each element (including null termination) unsigned long PrevPulseTime [4] = {0, 0, 0, 0}; //Timer of previous input state change used for debounce byte NewPulse [4] = {0, 0, 0, 0}; //Set 1 if new pulse detected and not yet reached debounce time //Timer Variables unsigned long PreviousMillis; //Old timer value used to trigger RTC check unsigned long PrevLcdUd = 0; //Timer at last LCD update unsigned int Secs = 65534; //Elapsed seconds (read from RTC) since values last logged, large value ensures logging starts immediately byte OldSecs; //Previous seconds value read from RTC - to detect change in RTC seconds to trigger logging on an interval defined in seconds byte OldMinute; //Previous minutess value read from RTC - to detect change in RTC minutes to keep seconds counter accurate during lengthy FTP byte OldHour; //Used for synch to midnight start - only start logging when hour increments and to trigger FTP upload when hour changes byte OldDay; //Used to trigger FTP upload when day changes //Previous button Press unsigned long OldButTime = 0; //Time of last button press //Modbus Error Flags //byte MErrorMin = 1; //MODBUS error condition, set to zero (no error) whenever modbus reading successful for all registers and reset to 1 (error) after each write to file //MODBUS data structures //#define TotalNoOfRegisters 10 //Max number of registers to log, used to allocate memory, same as total packets as all packets are sized for one register // Create an array of Packets to be configured //Packet Packets[TotalNoOfRegisters]; //Create an array to hold returned data in its raw form of unsigned integers //unsigned int HoldingRegs[TotalNoOfRegisters]; //byte RegOffset = 0; //Bugfix for values being returned in wrong array subscript in HoldingRegs. Determined by testing for each baud and parity setting. 0 if no adjustment, 1 adds one to subscript and highest number becomes zero //INITIALISE DEVICES through Function declarations HCRTC HCRTC; //Initialise RTC library first as it includes a Wire.begin(); statement SSD1306AsciiWire oled; //Initialise OLED display SoftwareSerial gsm(GsmTx, GsmRx); //Comms to GSM //__________ //Setup Function reads or creates a config file //Sets up the serial port as modbus slave //Updates the RTC if the config file requests this (then writes to config file to stop future RTC update on reset) void setup() { ////Local variables for config //unsigned long SerBaud; //Baud Rate //byte SerParity; //Parity N/O/E = None/Odd/Even //byte SerStop; //1 or 2 //byte SerConfig; //Value to set Data bits, parity and stop bits byte DataRead; //Raw data from file //unsigned int SlaveId, SlaveReg, LineStartPos, x = 0; //Parameters to read slave ID and registers from file //byte Func, InvalidEntry = 0, CommaCount = 0; //Various variables used to read register parameters from file byte RtcData[6]; //Store elements of date and time to write to RTC to set it byte i; //Counter to loop over characters //Initialise RTC //Note the RTC libraries for RTC and OLED issue wire.beginTransmission(Address) , wire.send , wire.endTransmission() as required HCRTC.RTCRead(I2CDS1307Add); //Initialise GSM serial comms gsm.begin(4800); //##Try faster baud rate, gsm should autodetect //Serial.begin(9600); //for testing, e.g. Serial.println(F("Test")); writes out to serial monitor //OLED Display oled.begin(&Adafruit128x64, I2COLedAdd); // set up the OLED's number of columns and rows oled.setFont(System5x7); //Standard font, at x2, 10 or 11 characters per line oled.clear(); oled.set2X(); //Double font size oled.setCursor(0,2); //column pixel, row in 8 pixel blocks. For larger font, there are 4 lines of text. 2 centres error messages vertically //Initialise LED Pins pinMode(PinLEDAnode, OUTPUT); pinMode(PinLEDG, OUTPUT); pinMode(PinLEDR, OUTPUT); digitalWrite(PinLEDAnode, HIGH); //+5V supply digitalWrite(PinLEDG, HIGH); //Cathode high = off digitalWrite(PinLEDR, HIGH); //Cathode high = off //Initialise Button Pin pinMode(PinButton, INPUT_PULLUP); //Initialise Pulse Input Pins //Wire 10k resistor between each input and ground //Wire the meter between +5V and input pinMode(PinIn[0], INPUT); pinMode(PinIn[1], INPUT); pinMode(PinIn[2], INPUT); pinMode(PinIn[3], INPUT); //Initialise RS485 Pin //pinMode(TxEnablePin, OUTPUT); //digitalWrite(TxEnablePin,LOW);//Receive 485 //Initialise SD card pin pinMode(SdCS, OUTPUT); digitalWrite(SdCS, HIGH); //Initialise GSM reset pin pinMode(GsmRST, OUTPUT); digitalWrite(GsmRST, HIGH); //Retrieve length of sample config file from EEPROM CFGFileLen = EEPROMReadUint16(EEPROMCFGDataStart); //Open the SD Card if (!SD.begin(SdCS)) //If fail to initialise SD comms trigger error condition and message { oled.println(F(" SD card")); oled.println(F(" error")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; //Exit setup return; } else //card is present { //Does the config file exist? if (!SD.exists(F("gsmlog.cfg"))) //If config file missing create an example and trigger error condition and message { //If no config file, create an example //from the data on the EEPROM SdFile::dateTimeCallback(DateTime); //Set datestamp for file DataFile = SD.open(F("gsmlog.cfg"), FILE_WRITE); //create example file if (DataFile) { //Create example //First part of file (above GSM commands) EEPROMToFile(EEPROMCFGDataStart + 2, CFGFileLen); //First two bytes are length of remaining data so not transferred //Second Part of file (GSM Commands) //Signal Strength Check unsigned int NextAddress = EEPROMCFGDataStart + 2 + CFGFileLen; //Next EEPROM Address = CFG File template start address + 2 bytes for length of that data plus the data itself SigStrStartAddr = NextAddress; DataFile.print(F("Sig Str L: ")); SigStrCmdLen = EEPROM.read(NextAddress); DataFile.println(SigStrCmdLen); NextAddress ++; DataFile.print(F("Sig Str C: ")); EEPROMToFile(NextAddress, SigStrCmdLen); DataFile.println(); //New line at end of data NextAddress += SigStrCmdLen; DataFile.print(F("Sig Str R D: ")); EEPROMToFile(NextAddress, 1); DataFile.println(); //New line at end of data NextAddress ++; DataFile.print(F("Sig Str R L: ")); SigStrRespLen = EEPROM.read(NextAddress); DataFile.println(SigStrRespLen); NextAddress ++; //Comms Tests TestCmdStartAddr = NextAddress; NGSMTestCmds = EEPROM.read(NextAddress); DataFile.print(F("N Comms Test Cmds (<7): ")); DataFile.println(NGSMTestCmds); NextAddress ++; for(byte GSMTestCmdNo = 1; GSMTestCmdNo <= NGSMTestCmds; GSMTestCmdNo++) { DataFile.print(F("L Comms Test C ")); DataFile.print(GSMTestCmdNo); DataFile.print(F(": ")); GSMTestCmdLen[GSMTestCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(GSMTestCmdLen[GSMTestCmdNo - 1]); NextAddress ++; DataFile.print(F("Comms Test C ")); DataFile.print(GSMTestCmdNo); DataFile.print(F(": ")); EEPROMToFile(NextAddress, GSMTestCmdLen[GSMTestCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += GSMTestCmdLen[GSMTestCmdNo - 1]; DataFile.print(F("L Comms Test C ")); DataFile.print(GSMTestCmdNo); DataFile.print(F(" Valid Resp: ")); GSMTestRespLen[GSMTestCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(GSMTestRespLen[GSMTestCmdNo - 1]); NextAddress ++; DataFile.print(F("Comms Test C ")); DataFile.print(GSMTestCmdNo); DataFile.print(F(" Valid Resp: ")); EEPROMToFile(NextAddress, GSMTestRespLen[GSMTestCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += GSMTestRespLen[GSMTestCmdNo - 1]; } //GSM Initialisation GSMInitStartAddr = NextAddress; NGSMInitCmds = EEPROM.read(NextAddress); DataFile.print(F("N GSM Init Cmds (<11): ")); DataFile.println(NGSMInitCmds); NextAddress ++; for(byte GSMInitCmdNo = 1; GSMInitCmdNo <= NGSMInitCmds; GSMInitCmdNo++) { DataFile.print(F("L GSM Init C ")); DataFile.print(GSMInitCmdNo); DataFile.print(F(": ")); GSMInitCmdLen[GSMInitCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(GSMInitCmdLen[GSMInitCmdNo - 1]); NextAddress ++; DataFile.print(F("GSM Init C ")); DataFile.print(GSMInitCmdNo); DataFile.print(F(": ")); EEPROMToFile(NextAddress, GSMInitCmdLen[GSMInitCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += GSMInitCmdLen[GSMInitCmdNo - 1]; DataFile.print(F("L GSM Init C ")); DataFile.print(GSMInitCmdNo); DataFile.print(F(" R: ")); GSMInitRespLen[GSMInitCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(GSMInitRespLen[GSMInitCmdNo - 1]); NextAddress ++; DataFile.print(F("GSM Init C ")); DataFile.print(GSMInitCmdNo); DataFile.print(F(" R: ")); EEPROMToFile(NextAddress, GSMInitRespLen[GSMInitCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += GSMInitRespLen[GSMInitCmdNo - 1]; } //FTP Initialisation FTPInitStartAddr = NextAddress; NFTPInitCmds = EEPROM.read(NextAddress); DataFile.print(F("N FTP Init Cmds (<11): ")); DataFile.println(NFTPInitCmds); NextAddress ++; for(byte FTPInitCmdNo = 1; FTPInitCmdNo <= NFTPInitCmds; FTPInitCmdNo++) { DataFile.print(F("L FTP Init C ")); DataFile.print(FTPInitCmdNo); DataFile.print(F(": ")); FTPInitCmdLen[FTPInitCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(FTPInitCmdLen[FTPInitCmdNo - 1]); NextAddress ++; DataFile.print(F("FTP Init C ")); DataFile.print(FTPInitCmdNo); DataFile.print(F(": ")); EEPROMToFile(NextAddress, FTPInitCmdLen[FTPInitCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += FTPInitCmdLen[FTPInitCmdNo - 1]; DataFile.print(F("L FTP Init C ")); DataFile.print(FTPInitCmdNo); DataFile.print(F(" R: ")); FTPInitRespLen[FTPInitCmdNo - 1] = EEPROM.read(NextAddress); DataFile.println(FTPInitRespLen[FTPInitCmdNo - 1]); NextAddress ++; DataFile.print(F("FTP Init C ")); DataFile.print(FTPInitCmdNo); DataFile.print(F(" R: ")); EEPROMToFile(NextAddress, FTPInitRespLen[FTPInitCmdNo - 1]); DataFile.println(); //New line at end of data NextAddress += FTPInitRespLen[FTPInitCmdNo - 1]; } //FTP Send Commands FTPSendStartAddr = NextAddress; DataFile.print(F("L GSM Set Filename C (to start of filename): ")); GSMFileCmdLen = EEPROM.read(NextAddress); DataFile.println(GSMFileCmdLen); NextAddress++; DataFile.print(F("GSM Set Filename C (to start of filename): ")); EEPROMToFile(NextAddress, GSMFileCmdLen); DataFile.println(); //New line at end of data NextAddress += GSMFileCmdLen; DataFile.print(F("L GSM Set Filename C R: ")); GSMFileRespLen = EEPROM.read(NextAddress); DataFile.println(GSMFileRespLen); NextAddress++; DataFile.print(F("GSM Set Filename CR: ")); EEPROMToFile(NextAddress, GSMFileRespLen); DataFile.println(); //New line at end of data NextAddress += GSMFileRespLen; DataFile.print(F("L GSM Start FTP C: ")); GSMFTPStartCmdLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPStartCmdLen); NextAddress++; DataFile.print(F("GSM Start FTP C: ")); EEPROMToFile(NextAddress, GSMFTPStartCmdLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPStartCmdLen; DataFile.print(F("L GSM Start FTP CR: ")); GSMFTPStartRespLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPStartRespLen); NextAddress++; DataFile.print(F("GSM Start FTP CR: ")); EEPROMToFile(NextAddress, GSMFTPStartRespLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPStartRespLen; DataFile.print(F("L GSM Req FTP Data send C: ")); GSMFTPReqCmdLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPReqCmdLen); NextAddress++; DataFile.print(F("GSM Req FTP Data send C (exc. Data L): ")); EEPROMToFile(NextAddress, GSMFTPReqCmdLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPReqCmdLen; DataFile.print(F("L GSM Req FTP Data Send CR: ")); GSMFTPReqRespLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPReqRespLen); NextAddress++; DataFile.print(F("GSM Req FTP Data Send CR: ")); EEPROMToFile(NextAddress, GSMFTPReqRespLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPReqRespLen; DataFile.print(F("L GSM End FTP Data send C: ")); GSMFTPEndCmdLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPEndCmdLen); NextAddress++; DataFile.print(F("GSM End FTP Data send C: ")); EEPROMToFile(NextAddress, GSMFTPEndCmdLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPEndCmdLen; DataFile.print(F("L GSM End FTP Data Send CR: ")); GSMFTPEndRespLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPEndRespLen); NextAddress++; DataFile.print(F("GSM End FTP Data Send CR: ")); EEPROMToFile(NextAddress, GSMFTPEndRespLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPEndRespLen; DataFile.print(F("L GSM End FTP Sess C: ")); GSMFTPEndSessLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPEndSessLen); NextAddress++; DataFile.print(F("GSM End FTP Sess C: ")); EEPROMToFile(NextAddress, GSMFTPEndSessLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPEndSessLen; DataFile.print(F("L GSM End FTP Sess CR: ")); GSMFTPEndSessRespLen = EEPROM.read(NextAddress); DataFile.println(GSMFTPEndSessRespLen); NextAddress++; DataFile.print(F("GSM End FTP Sess CR: ")); EEPROMToFile(NextAddress, GSMFTPEndSessRespLen); DataFile.println(); //New line at end of data NextAddress += GSMFTPEndSessRespLen; //Close file DataFile.close(); oled.println(F("Config file")); oled.println(F(" created")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; //Exit setup return; } else //Failed to create a config file { oled.println(F("Config file")); oled.println(F("write fail")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; //Exit setup return; } } else //Config file is present, read it { DataFile = SD.open(F("gsmlog.cfg"), FILE_WRITE); //open file, leave datestamp as is if (DataFile) { DataFile.seek(0);//go to start of file DataFile.seek(FindNext(DataFile,':') + 1);//Find the first parameter if (DataFile.peek() == 'Y') //Update RTC specified (Peek reads without changing current position in file) { DataFile.write('N'); //Change to N so clock not set again on next reset DataFile.seek(FindNext(DataFile,':') + 1); //Loop over Year (5), Month (4),Date (3), Hour (2), Minute (1), Second (0) for(i = 5; i < 255; i--) { memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string TempStr10[0] = DataFile.read(); TempStr10[1] = DataFile.read(); RtcData[i] = atoi(TempStr10); } //Set RTC //Display message to press button to set clock oled.setCursor(0,1); oled.println(F(" Press OK")); oled.println(F(" to set")); oled.println(F(" clock")); //Show a yellow LED digitalWrite(PinLEDR, LOW); digitalWrite(PinLEDG, LOW); //Wait for keypress do { } while (digitalRead(PinButton) == HIGH); //Keep looping until button pressed //Set clock HCRTC.RTCWrite(I2CDS1307Add, RtcData[5], RtcData[4], RtcData[3], RtcData[2], RtcData[1], RtcData[0], 1); //Set Clock, last parameter is weekday and not used delay(100); //Delay to let the clock be set //Turn Yellow LED off digitalWrite(PinLEDR, HIGH); digitalWrite(PinLEDG, HIGH); //Message displaying new time HCRTC.RTCRead(I2CDS1307Add); //Read RTC oled.clear(); oled.setCursor(0,1); //column pixel, row in 8 pixel blocks. oled.println(F("Clock set:")); oled.println(HCRTC.GetDateString()); //Display date oled.println(HCRTC.GetTimeString()); //Display time delay(MsgDelay); //Pause so can read LCD diagnostic message } else //don't need to update time { DataFile.seek(FindNext(DataFile,':') + 1); //Move to clock set value, but will not be used } //Wait for keypress to start (to allow user to synch reading with meter)? DataFile.seek(FindNext(DataFile,':') + 1); //Find parameter if (DataFile.peek() == 'Y') //Wait for keypress specified { DataFile.write('N'); //Change to N so no wait for keypress on next reboot //Display Message oled.clear(); oled.setCursor(0,2); oled.println(F(" Press OK")); oled.println(F(" to start")); //Show a yellow LED digitalWrite(PinLEDR, LOW); digitalWrite(PinLEDG, LOW); //Wait for keypress do { } while (digitalRead(PinButton) == HIGH); //Keep looping until button pressed //Turn Yellow LED off digitalWrite(PinLEDR, HIGH); digitalWrite(PinLEDG, HIGH); //Clear message oled.clear(); } //Number of pulse inputs ReadCharFromFile(); NPulseInputs = atoi(TempStr10); //Pulse Weights (only read for pulse inputs that exist) for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { ReadNumberFromFile(5); PulseWeight[InputNo] = atoi(TempStr10); } //Pulse Input Names (only read for pulse inputs that exist) for (byte InputNo=0; InputNo= 48) && (TempStr10[9] <= 57)) //10th character is a numeric digit { for (byte j = 0; j < 10; j++) { TempStr10[j] = TempStr10[j + 1]; } } PulseReading[InputNo] = atol(TempStr10); } //The reads are written to EEPROM at end of setup } else { //Skip lines in file for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { DataFile.seek(FindNext(DataFile,':') + 1); } //Read values from EEPROM for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { PulseReading[InputNo] = EEPROMReadUint32(4 * InputNo); } } //GSM parameters - these are saved to EEPROM not RAM //There are checks in the Write functions that total datalength does not exceed eeprom size unsigned int NextAddress = EEPROMCFGDataStart + 2 + CFGFileLen; //Signal Strength Request SigStrStartAddr = NextAddress; //Length of signal strength command ReadCharFromFile(); SigStrCmdLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, SigStrCmdLen); NextAddress++; //Signal Strength Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,SigStrCmdLen); NextAddress += SigStrCmdLen; //Signal Strength Response Delimeter DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,1); NextAddress ++; //Signal Strength Response length ReadNumberFromFile(3); SigStrRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, SigStrRespLen); NextAddress++; //Communication Test Commands TestCmdStartAddr = NextAddress; //Number of Test Commands ReadNumberFromFile(3); NGSMTestCmds = atoi(TempStr10); EEPROMWriteUint8(NextAddress, NGSMTestCmds); NextAddress++; //Parameters for each Test Command for(byte GSMTestCmdNo = 1; GSMTestCmdNo <= NGSMTestCmds; GSMTestCmdNo++) { //Command Length ReadNumberFromFile(3); GSMTestCmdLen[GSMTestCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMTestCmdLen[GSMTestCmdNo - 1]); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMTestCmdLen[GSMTestCmdNo - 1]); NextAddress += GSMTestCmdLen[GSMTestCmdNo - 1]; //Response Length ReadNumberFromFile(3); GSMTestRespLen[GSMTestCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMTestRespLen[GSMTestCmdNo - 1]); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMTestRespLen[GSMTestCmdNo - 1]); NextAddress += GSMTestRespLen[GSMTestCmdNo - 1]; } //GSM Initialisation Commands GSMInitStartAddr = NextAddress; //Number of Initialisation Commands ReadNumberFromFile(3); NGSMInitCmds = atoi(TempStr10); EEPROMWriteUint8(NextAddress, NGSMInitCmds); NextAddress++; //Parameters for each Test Command for(byte GSMInitCmdNo = 1; GSMInitCmdNo <= NGSMInitCmds; GSMInitCmdNo++) { //Command Length ReadNumberFromFile(3); GSMInitCmdLen[GSMInitCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMInitCmdLen[GSMInitCmdNo - 1]); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMInitCmdLen[GSMInitCmdNo - 1]); NextAddress += GSMInitCmdLen[GSMInitCmdNo - 1]; //Response Length ReadNumberFromFile(3); GSMInitRespLen[GSMInitCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMInitRespLen[GSMInitCmdNo - 1]); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMInitRespLen[GSMInitCmdNo - 1]); NextAddress += GSMInitRespLen[GSMInitCmdNo - 1]; } //FTP Initialisation Commands FTPInitStartAddr = NextAddress; //Number of Initialisation Commands ReadNumberFromFile(3); NFTPInitCmds = atoi(TempStr10); EEPROMWriteUint8(NextAddress, NFTPInitCmds); NextAddress++; //Parameters for each Test Command for(byte FTPInitCmdNo = 1; FTPInitCmdNo <= NFTPInitCmds; FTPInitCmdNo++) { //Command Length ReadNumberFromFile(3); FTPInitCmdLen[FTPInitCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, FTPInitCmdLen[FTPInitCmdNo - 1]); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,FTPInitCmdLen[FTPInitCmdNo - 1]); NextAddress += FTPInitCmdLen[FTPInitCmdNo - 1]; //Response Length ReadNumberFromFile(3); FTPInitRespLen[FTPInitCmdNo - 1] = atoi(TempStr10); EEPROMWriteUint8(NextAddress, FTPInitRespLen[FTPInitCmdNo - 1]); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,FTPInitRespLen[FTPInitCmdNo - 1]); NextAddress += FTPInitRespLen[FTPInitCmdNo - 1]; } //FTP Commands FTPSendStartAddr = NextAddress; //Command to set target filename (AT+FTPPUTNAME=) //Length ReadNumberFromFile(3); GSMFileCmdLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFileCmdLen); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFileCmdLen); NextAddress += GSMFileCmdLen; //Response Length ReadNumberFromFile(3); GSMFileRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFileRespLen); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFileRespLen); NextAddress += GSMFileRespLen; //Command to request write (AT+FTPPUT=1) //Length ReadNumberFromFile(3); GSMFTPStartCmdLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPStartCmdLen); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPStartCmdLen); NextAddress += GSMFTPStartCmdLen; //Response Length ReadNumberFromFile(3); GSMFTPStartRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPStartRespLen); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); //response string includes cr-lf after OK, FileToEEPROM(NextAddress,GSMFTPStartRespLen); NextAddress += GSMFTPStartRespLen; //command to write data (AT+FTPPUT=2,) (last value is bytes to write, needs to match response above) //Length ReadNumberFromFile(3); GSMFTPReqCmdLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPReqCmdLen); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPReqCmdLen); NextAddress += GSMFTPReqCmdLen; //Response Length ReadNumberFromFile(3); GSMFTPReqRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPReqRespLen); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPReqRespLen); NextAddress += GSMFTPReqRespLen; //command to close transfer (AT+FTPPUT=2,0) //Length ReadNumberFromFile(3); GSMFTPEndCmdLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPEndCmdLen); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPEndCmdLen); NextAddress += GSMFTPEndCmdLen; //Response Length ReadNumberFromFile(3); GSMFTPEndRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPEndRespLen); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPEndRespLen); NextAddress += GSMFTPEndRespLen; //command to close FTP Session (AT+SAPBR=0,1) //Length ReadNumberFromFile(3); GSMFTPEndSessLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPEndSessLen); NextAddress++; //Command DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPEndSessLen); NextAddress += GSMFTPEndSessLen; //Response Length ReadNumberFromFile(3); GSMFTPEndSessRespLen = atoi(TempStr10); EEPROMWriteUint8(NextAddress, GSMFTPEndSessRespLen); NextAddress++; //Response DataFile.seek(FindNext(DataFile,':') + 1); FileToEEPROM(NextAddress,GSMFTPEndSessRespLen); NextAddress += GSMFTPEndSessRespLen; /*//MODBUS parameters //Baud rate (read as multiple chars) memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string DataFile.seek(FindNext(DataFile,':') + 1); i=0; do { DataRead = DataFile.read(); TempStr10[i] = DataRead; i++; } while ((DataRead != 13) && (i < 7)); //read up to 6 chars or carriage return SerBaud = atol(TempStr10); //can be over 65535 so use atol (ascii to long) instead of atoi (ascii to int) //Parity DataFile.seek(FindNext(DataFile,':') + 1); SerParity = DataFile.read(); //Stop Bits DataFile.seek(FindNext(DataFile,':') + 1); SerStop = DataFile.read(); //Set SerConfig from constants switch (SerParity) { case 'N': if(SerStop == '1') { SerConfig = SERIAL_8N1; //This breaks the MODBUS RTU standard but is used by some devices } else //2 stop bits by default { SerConfig = SERIAL_8N2; } break; case 'E': if(SerStop == '2') { SerConfig = SERIAL_8E2; //This breaks the MODBUS RTU standard } else //1 stop bit by default { SerConfig = SERIAL_8E1; } break; default: //Odd Parity if(SerStop == '2') { SerConfig = SERIAL_8O2; //This breaks the MODBUS RTU standard } else //1 stop bit by default { SerConfig = SERIAL_8O1; } break; } //Set the RegOffset bugfix variable depending on selected comms parameters //Note: 8E2 not tested, no adjustment made if ((SerBaud == 19200) || (SerBaud == 115200) || (SerConfig == SERIAL_8N2) || (SerConfig == SERIAL_8E1)) { RegOffset = 1; } //Registers to log DataFile.seek(FindNext(DataFile,':') + 1); do //scan the remaining data in the file to see if there are 2 commas on each line { LineStartPos = DataFile.position(); //save this position, so can return here CommaCount = 0; //reset the count do { DataRead = DataFile.read(); if (DataRead == ',') { CommaCount++;//count up the commas } } while ((DataRead != 255) && (DataRead != 13)); //keep going until end of file or carriage return if (CommaCount == 2) //if it's got 2 commas assume a valid slaveid, type and register { DataFile.seek(LineStartPos); i=0; memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string do { DataRead = DataFile.read(); TempStr10[i] = DataRead; i++; } while ((DataRead != ',') && (i<7)); SlaveId = atoi(TempStr10); //slave id DataRead = DataFile.read(); //this should be the register type switch (DataRead) { case 'C': Func = READ_COIL_STATUS; break; case 'S': Func = READ_INPUT_STATUS; break; case 'I': Func = READ_INPUT_REGISTERS; break; default: //H - Holding registers Func = READ_HOLDING_REGISTERS; break; } DataFile.read(); //move to the next char i=0; memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string do { DataRead = DataFile.read(); TempStr10[i] = DataRead; i++; } while ((DataRead != 13) && (i<7)); DataFile.read(); //move to the next char SlaveReg = atoi(TempStr10); //get the register number // Initialize packets (pointer to packet, slave device ID, Function code constant, address to read (zero based),number of registers to read,subscript of first returned value in HoldingRegs array //x is the zero based subscript value modbus_construct(&Packets[x], SlaveId, Func, SlaveReg, 1, x); x++; } else //not two commas on line, assume end of file { InvalidEntry = 1; } } while((!InvalidEntry) && (x OldSecs)) //Seconds greater than old value { Secs = Secs + RtcData[0] - OldSecs; //Increment seconds counter //Check if we've missed whole minutes if (RtcData[1] != OldMinute) { if(RtcData[1] > OldMinute) { Secs = Secs + (60 * ( RtcData[1] - OldMinute)); } else { //Minute rollover over hour Secs = Secs + (60 * ( RtcData[1] + (60 - OldMinute))); } } } if ((RtcData[0] <= OldSecs)) //Minutes must have changed; if seconds havent, then minutes have as evaluated by higher level IF { //Seconds have returned to zero or are same : add difference in seconds through zero (if seconds the same this will add a minute) Secs = Secs + RtcData[0] + (60 - OldSecs); //Increment seconds counter //Add change in minutes //Minute rollover already accounted for in seconds calculation so subtract 1 from the minute increment in the below calculations if(RtcData[1] > OldMinute) { Secs = Secs + (60 * ( RtcData[1] - OldMinute - 1)); } else { //Minute rollover over hour Secs = Secs + (60 * ( RtcData[1] + (60 - OldMinute - 1))); } } OldSecs = RtcData[0]; //Update old value to new value OldMinute = RtcData[1]; //Synch to hour start check if(LoggerStatus == 'H') //Awaiting a new hour at the start of logging { RtcData[2] = HCRTC.GetHour(); if(RtcData[2] != OldHour) //Change status to Go if hour has changed on the RTC { LoggerStatus = 'G'; OldHour = RtcData[2]; //Delay FTPing until first whole hour of logging has occurred... OldDay = HCRTC.GetDay(); //...or whole day as appropriate Secs = 0; } } } } if ((Secs >= LogInterval) && (LoggerStatus != 'H')) //time to log and not waiting for new hour { Secs = Secs - LogInterval; //Reset seconds counter //Assemble filename from date and time //Retrieve each part of date and time as a numeric value (byte data type) RtcData[0] = HCRTC.GetSecond(); //Seconds RtcData[1] = HCRTC.GetMinute(); //Minutes RtcData[2] = HCRTC.GetHour(); //Hours RtcData[3] = HCRTC.GetDay(); //Date RtcData[4] = HCRTC.GetMonth(); //Month RtcData[5] = HCRTC.GetYear(); //Year //Store Previous filename as this will be the file to FTP memcpy(OldFileName, FileName, sizeof(FileName)); //Create the filename from the time in the form YYMMDDHH.csv byte j=0; //j is character number in the filename (zero based, 0..7 for the date part) //loop over parts of the date and time as stored in RtcData //Start with year, then month...seconds for (byte i = 5; ((i >= 3) || (((i >= 2) && (UploadRate == 1)))); i--) //For each part of date { //Add the two digits to the filename: all parts of date and time are 1 or 2 digits FileName[j] = RtcData[i] / 10 + 0x30; //get first digit by integer division by 10 and convert to ascii code of digit by adding 30 j++; FileName[j] = RtcData[i] % 10 + 0x30; //get second digit by remainder when divided by 10 and convert to ascii code of digit by adding 30 j++; } //add file extension FileName[j+0] = '.'; FileName[j+1] = 'c'; FileName[j+2] = 's'; FileName[j+3] = 'v'; FileName[j+4] = 0; //end the string //Open the log file SdFile::dateTimeCallback(DateTime); //Set datestamp for file DataFile = SD.open(FileName, FILE_WRITE); //create it if it doesn't exist if (DataFile) { if (MultiCol == 'Y') //Multi column format for Excel { if(!DataFile.size()) //If file is zero length then add header { DataFile.print(F("Date Time")); for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { DataFile.print(F(",")); DataFile.print(PulseName[InputNo]); } DataFile.println(); //New line } //Insert Date and Time into file DataFile.print(HCRTC.GetDateString()); DataFile.print(F(" ")); // space between DATE and TIME DataFile.print(HCRTC.GetTimeString()); for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { DataFile.print(F(",")); DataFile.print(PulseReading[InputNo]); } DataFile.println(); //New line } else //Single Column Satchwell format { for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) //Write separate line for each input { //Input Name DataFile.print(PulseName[InputNo]); DataFile.print(F(",")); //Date '-' delimited DataFile.print(RtcData[3]); DataFile.print(F("-")); DataFile.print(RtcData[4]); DataFile.print(F("-")); DataFile.print(RtcData[5]); DataFile.print(F(",")); //Time DataFile.print(HCRTC.GetTimeString()); DataFile.print(F(",")); //Reading and new line DataFile.println(PulseReading[InputNo]); } } DataFile.close(); //close the file } //If on the hour or day as applicable, send data via FTP if (((RtcData[2] != OldHour) && (UploadRate == 1)) || ((RtcData[3] != OldDay) && (UploadRate == 2)) || NewSave == 0) //Check for hour or day rollover, or FTP retry { //If new data file available, flag new save if (((RtcData[2] != OldHour) && (UploadRate == 1)) || ((RtcData[3] != OldDay) && (UploadRate == 2))) { NewSave = 1; } //Save latest readings to EEPROM //Only do this if first attempt to FTP this batch of data (NewSave == 1) if (NewSave == 1) { WriteReadsToEEPROM(); } //FTP the file byte FTPSuccess = 0; //FTP successful? if (NewSave == 1) { //Update the filename on first attempt, same filename used for retries memcpy(FTPFile, OldFileName, sizeof(OldFileName)); } //Prepare the GSM and Test Comms byte FTPOK = 0; FTPOK = FTPTest(); if (FTPOK == 1) //Only do the below if comms OK { //Check file exists if (SD.exists(FTPFile)) { DataFile = SD.open(FTPFile, FILE_READ); //Open File //Check File opened if (DataFile) { //Check file not empty if(DataFile.size()) { //FTP operation FTPSuccess = FTPSend(); } DataFile.close(); } } } //Reset oldHour and oldDay OldHour = RtcData[2]; OldDay = RtcData[3]; //Set NewSave to 1 if FTP successful, will next FTP when day/hour increments, or to 0 if FTP unsuccessful, to retry FTP of same file on logging interval throughout the period until next new file available if(FTPSuccess == 1) //Only if FTP successful, ; if not successful FTP will retry whenever files updated { NewSave = 1; } else { NewSave = 0; } } } //End of writing results for logging interval //Update Signal Strength UpdateGSMSigStr(); //Check input Statuses and increment readings ReadPulseInputs(); } } //__________ //Callback Function to set timestamp for files void DateTime(uint16_t* DDate, uint16_t* TTime) { HCRTC.RTCRead(I2CDS1307Add); //Update RTC data // return date using FAT_DATE macro to format fields *DDate = FAT_DATE((2000 + HCRTC.GetYear()), HCRTC.GetMonth(), HCRTC.GetDay()); // return time using FAT_TIME macro to format fields *TTime = FAT_TIME(HCRTC.GetHour(), HCRTC.GetMinute(), HCRTC.GetSecond()); } //__________ //Copy data from EEPROM to file void EEPROMToFile(unsigned int StartAddress,unsigned int NBytes) { for (unsigned int j = 0; j < NBytes; j++) { DataFile.write(EEPROM.read(StartAddress + j)); //Write converts to ASCII characters } } //__________ //Copy data from EEPROM to GSM void EEPROMToGSM(unsigned int StartAddress,unsigned int NBytes, byte CRLF) { gsm.flush(); for (unsigned int j = 0; j < NBytes; j++) { gsm.print((char)EEPROM.read(StartAddress + j)); } if (CRLF == 1) { gsm.println();//Send CR + LF } } //__________ //Read an unsigned 16 bit integer from 2 EEPROM addresses unsigned int EEPROMReadUint16(unsigned int StartAddress) { byte TempBytes[2]; //Temporary store for the upper and lower byte for (byte j = 0; j < 2; j++) { TempBytes[j] = EEPROM.read(StartAddress + j); //First argument is address, second is value } return ((int)(TempBytes[0]) << 8) + ((int)(TempBytes[1])); } //__________ //Read an unsigned 32 bit integer from 4 EEPROM addresses unsigned long EEPROMReadUint32(unsigned int StartAddress) { byte TempBytes[4]; //Temporary store for the bytes, unsigned long is four bytes for (byte j = 0; j < 4; j++) { TempBytes[j] = EEPROM.read(StartAddress + j); //Argument is address } //Convert to unsigned long return ((long)(TempBytes[0]) << 24) + ((long)(TempBytes[1]) << 16) + ((long)(TempBytes[2]) << 8) + ((long)(TempBytes[3])); } //__________ //Write an unsigned 8 bit integer to EEPROM address with EEPROM overrun check void EEPROMWriteUint8(unsigned int StartAddress, byte ValToWrite) { //Check the data will fit in EEPROM, E2END returns maximum address which varies by Arduino model if(StartAddress > E2END) { oled.println(F("Config file")); oled.println(F("too large! ")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; return; } EEPROM.write(StartAddress, ValToWrite); //First argument is address, second is value } //__________ //Write an unsigned 16 bit integer to 2 EEPROM addresses void EEPROMWriteUint16(unsigned int StartAddress, unsigned int ValToWrite) { //Check the data will fit in EEPROM, E2END returns maximum address which varies by Arduino model if(StartAddress + 1 > E2END) { oled.println(F("Config file")); oled.println(F("too large! ")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; return; } byte TempBytes[2]; TempBytes[0] = (byte) ((ValToWrite & 0x0000FF00) >> 8 ); TempBytes[1] = (byte) ((ValToWrite & 0X000000FF) ); for (byte j = 0; j < 2; j++) { EEPROM.write(StartAddress + j, TempBytes[j]); //First argument is address, second is value } } //__________ //Write an unsigned 32 bit integer to 4 EEPROM addresses void EEPROMWriteUint32(unsigned int StartAddress, unsigned long ValToWrite) { //Check the data will fit in EEPROM, E2END returns maximum address which varies by Arduino model if(StartAddress + 3 > E2END) { oled.println(F(" EEPROM")); oled.println(F(" overflow!")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; return; } byte TempBytes[4]; //Temporary store for the bytes, unsigned long is four bytes TempBytes[0] = (byte) ((ValToWrite & 0xFF000000) >> 24 ); TempBytes[1] = (byte) ((ValToWrite & 0x00FF0000) >> 16 ); TempBytes[2] = (byte) ((ValToWrite & 0x0000FF00) >> 8 ); TempBytes[3] = (byte) ((ValToWrite & 0X000000FF) ); for (byte j = 0; j < 4; j++) { EEPROM.write(StartAddress + j, TempBytes[j]); //First argument is address } } //__________ //Copy data from file to EEPROM void FileToEEPROM(unsigned int StartAddress,unsigned int NBytes) { //Check the data will fit in EEPROM, E2END returns maximum address which varies by Arduino model if((StartAddress + NBytes - 1) > E2END) { oled.println(F("Config file")); oled.println(F("too large! ")); digitalWrite(PinLEDR, LOW); //RED LED ON SetupError = 1; return; } //Write Data for (unsigned int j = 0; j < NBytes; j++) { EEPROM.write(StartAddress + j, DataFile.read()); } } //__________ //Function to find the next instance of a character in the sd card file unsigned int FindNext(File DF,byte CharToFind) { byte DataRead; do { DataRead = DF.read(); } while ((DataRead != 255) && (DataRead != CharToFind)); //end of file or character position return DF.position(); } //__________ byte FTPSend() //Return 1 if OK and 0 if error { //Send data to ftp server char RespChar = 255; //Read individual digits of response byte RespValid = 0; //Valid response from modem? unsigned int TempAddress = FTPSendStartAddr + 1; //Skip length of command gsm.flush(); //Finish write operation GSMFlushInput(); //Flush input data //Set Destination Filename, AT+FTPPUTNAME="xxx" EEPROMToGSM(TempAddress, GSMFileCmdLen,0); //Final parameter 0 - don't send CRLF TempAddress += GSMFileCmdLen; TempAddress += 1; //Skip response length //Send double quotes gsm.write(34); //Send Filename for (byte CharNo = 0; CharNo < 12; CharNo++) { gsm.print(FTPFile[CharNo]); if(FTPFile[CharNo + 1] == 0) { break; //End at null character } } //Send double quotes and CRLF gsm.write(34); gsm.println(); //Read response RespValid = ReadGSMResponse(GSMFileRespLen, TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } TempAddress += GSMFileRespLen; TempAddress += 1; //Skip length of next command //Start file writing session, AT+FTPPUT=1 gsm.flush(); //Finish write operation GSMFlushInput(); //Flush input data EEPROMToGSM(TempAddress, GSMFTPStartCmdLen,1); TempAddress += GSMFTPStartCmdLen; TempAddress += 1; //Read fixed part of response RespValid = ReadGSMResponse(GSMFTPStartRespLen, TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } TempAddress += GSMFTPStartRespLen; TempAddress += 1; //Skip length of next command //Read maximum number of bytes than can be transmitted unsigned long NCharToTx = 0; //Read the response character by character until either buffer empty or 4 digits read, convert to unsigned long for (byte CharNo = 0; CharNo < 4; CharNo++) { RespChar = GSMRead(GSMLongTimeout); if(RespChar != 0) //0 returned if serial buffer empty, in which case skip adding digits { NCharToTx *= 10; //Multiply existing digits by 10 NCharToTx += (RespChar - '0'); //convert ASCII value to int by subtracting ASCII value of 0 (48) from ASCII value of digit, add to value } } //Initialise file length and position unsigned long FileLen; FileLen = DataFile.size(); unsigned long FilePos = 0; //Position of next character to transmit, zero based //if max transmit length >0, Send data in packets until all file sent if (NCharToTx > 0) { gsm.flush(); //Finish write operation do //Repeat until all data sent (NCharToTx > 0) { //Write data in packets reading length of next packet each time //Send Command AT+FTPPUT=2,NCharToTx ...don't forget to increment position in eeprom to read next GSMFlushInput(); //Flush input data //Adjust number of characters to transmit if characters remaining < transmit limit if(NCharToTx > (FileLen - FilePos)) { NCharToTx = (FileLen - FilePos); } //Send Command EEPROMToGSM(TempAddress, GSMFTPReqCmdLen, 0); gsm.println(NCharToTx); TempAddress += GSMFTPReqCmdLen; TempAddress += 1; //Skip length of response //Read only fixed part of response RespValid = ReadGSMResponse(GSMFTPReqRespLen, TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } //Discard rest of response (or it will be returned when trying to read response after bytes written) GSMFlushInput(); //Send the characters for (unsigned long FileChar = 0; FileChar < NCharToTx; FileChar++) { DataFile.seek(FilePos); gsm.print((char)DataFile.read()); FilePos++; } gsm.flush(); //Finish write operation //Read response when data teansmission complete: OK+FTPPUT: 1,1,.....(ignore rest) (use max delay GSMVeryLongTimeout as can take a while) TempAddress -= GSMFTPStartRespLen; TempAddress -= GSMFTPReqCmdLen; TempAddress -= 2; //Skip lengths of commands //Read only fixed part of response RespValid = ReadGSMResponse(GSMFTPReqRespLen, TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } //Increment next command address back to the request send command for next loop iteration TempAddress += GSMFTPStartRespLen; TempAddress += 1; //Skip length of next command } while (FilePos < FileLen); //So will end when no more characters to send } else { return 0; //Return error if NCharToTx returned by modem is 0 } //Close the FTP send and session //Increment EEPROM location to read over both the FTP send and response commands to the desired FTP close TempAddress += GSMFTPReqCmdLen; //Request command TempAddress += 1; //Request command length of response TempAddress += GSMFTPReqRespLen; //Request command valid response TempAddress += 1; //Skip length of End command //Send AT+FTPPUT=2,0 gsm.flush(); GSMFlushInput(); //Flush input data //Send Command EEPROMToGSM(TempAddress, GSMFTPEndCmdLen, 1); TempAddress += GSMFTPEndCmdLen; TempAddress += 1; //Skip length of response //Await response OK+FTPPUT: 1,.....(ignore rest) (use max delay GSMVeryLongTimeout) RespValid = ReadGSMResponse(GSMFTPEndRespLen, TempAddress, GSMVeryLongTimeout); if(!RespValid) { return 0; } gsm.flush(); GSMFlushInput(); //Flush input data //Increment next command address TempAddress += GSMFTPEndRespLen; TempAddress += 1; //Skip length of next command //AT+SAPBR=0,1 to end FTP session //Send Command EEPROMToGSM(TempAddress, GSMFTPEndSessLen, 1); TempAddress += GSMFTPEndSessLen; TempAddress += 1; //Skip length of response //Await response OK RespValid = ReadGSMResponse(GSMFTPEndSessRespLen, TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } } //__________ //Test GSM is ready for FTP operation, reset and reconfigure as necessary byte FTPTest() //Return 1 if OK and 0 if error { //Update Signal Strength UpdateGSMSigStr(); //If no signal then try rebooting modem if (GsmSig == 0) { ResetGSM(); UpdateGSMSigStr(); } if (GsmSig >= 10) { //Comms Tests - if first call to this sub don't test, all parameters will be reset anyway unsigned int TempAddress = TestCmdStartAddr + 2; //+1 for number of commands, +1 for length of first command byte RespValid = 0; //Valid response from modem? if(FirstModemCall == 0) { for(byte TestCmdNo = 0; TestCmdNo < NGSMTestCmds; TestCmdNo++) { gsm.flush(); //Finish write operation GSMFlushInput(); //Flush input data //Send Command EEPROMToGSM(TempAddress, GSMTestCmdLen[TestCmdNo], 1); TempAddress += GSMTestCmdLen[TestCmdNo]; TempAddress += 1; //Skip length of response //Receive Response RespValid = ReadGSMResponse(GSMTestRespLen[TestCmdNo], TempAddress,GSMLongTimeout); if(!RespValid) { FirstModemCall = 1; break; //Skip further tests } TempAddress += GSMTestRespLen[TestCmdNo]; TempAddress += 1; //Skip length of next command } } //If any of the tests failed, issue a hardware reset and Send all the config commands if(FirstModemCall == 1) { ResetGSM(); gsm.println(("ATE0")); //Turn off command echo, will return OK but response not checked //GSM Initialisation Commands TempAddress = GSMInitStartAddr + 2; //+1 for number of commands, +1 for length of first command for(byte InitCmdNo = 0; InitCmdNo < NGSMInitCmds; InitCmdNo++) { gsm.flush(); //Finish write operation GSMFlushInput(); //Flush input data //Send Command EEPROMToGSM(TempAddress, GSMInitCmdLen[InitCmdNo], 1); TempAddress += GSMInitCmdLen[InitCmdNo]; TempAddress += 1; //Skip length of response //Receive Response RespValid = ReadGSMResponse(GSMInitRespLen[InitCmdNo], TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } TempAddress += GSMInitRespLen[InitCmdNo]; TempAddress += 1; //Skip length of next command } } FirstModemCall == 0; //Initialisation not required in future unless tests indicate a reset is required //FTP Initialisation Commands TempAddress = FTPInitStartAddr + 2; //+1 for number of commands, +1 for length of first command for(byte InitCmdNo = 0; InitCmdNo < NFTPInitCmds; InitCmdNo++) { gsm.flush(); //Finish write operation GSMFlushInput(); //Flush input data //Send Command EEPROMToGSM(TempAddress, FTPInitCmdLen[InitCmdNo], 1); TempAddress += FTPInitCmdLen[InitCmdNo]; TempAddress += 1; //Skip length of response //Receive Response RespValid = ReadGSMResponse(FTPInitRespLen[InitCmdNo], TempAddress,GSMLongTimeout); if(!RespValid) { return 0; } TempAddress += FTPInitRespLen[InitCmdNo]; TempAddress += 1; //Skip length of next command } return 1; } else { return 0; //Signal strength < 10% } } //__________ void GSMFlushInput() //Flush input buffer of GSM by reading all available data { char RespChar2 = 255; do { RespChar2 = GSMRead(GSMTimeout); } while (RespChar2 != 0); } //__________ //Read GSM response with timeout char GSMRead(unsigned int TimeOutLen) { //Set timeout datum unsigned long GSMTimeoutDatum = millis(); do { ReadPulseInputs(); } while ((!gsm.available()) && (!IntervalCheck(GSMTimeoutDatum, TimeOutLen))); if(gsm.available()) { return gsm.read(); } else { return 0; //Return null character } } //__________ byte IntervalCheck(unsigned long Datum, unsigned long Interval) //return 1 if interval has elapsed { unsigned long CurrentTime = millis(); if ((CurrentTime < Datum) && (((LongIntMax - Datum) + (CurrentTime)) > Interval)) { return 1; } else if ((CurrentTime < Datum) && (((LongIntMax - Datum) + (CurrentTime)) <= Interval)) { return 0; } else if ((CurrentTime - Datum) > Interval) { return 1; } else { return 0; } } //__________ //Function to read value of button input, return 1 if button pressed (allowing for debounce) byte ReadButton() { if(IntervalCheck(OldButTime, ButInt)) { //Minimum interval between keypresses to avoid multiple triggers // read the button value if (!digitalRead(PinButton)) //Button is LOW when pressed { OldButTime = millis(); //Store time of this button press for debounce //Force LCD refresh ASAP - using oldBLTime as just set to millis, saves creating another variable if(OldButTime > LcdUdInt) { //Setting previous update time to zero will trigger PrevLcdUd = 0; } else { PrevLcdUd = LongIntMax - LcdUdInt; } return 1; //button pressed } else { return 0; //button not pressed } } else //Insufficient time since previous button action: ignore button state { return 0; } } //__________ //Function to read character from file to temporary string void ReadCharFromFile() { byte DataRead; //Raw data from file DataFile.seek(FindNext(DataFile,':') + 1); DataRead = DataFile.read(); memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string TempStr10[0] = DataRead; } //__________ byte ReadGSMResponse(byte RespLen, unsigned int StartAddr, unsigned int TimeoutLen1) //Read response from GSM, if not expected return 0 else return 1. Parameters are response length, response start address { char RespChar3 = 255; unsigned long GSMTimeoutDatum = millis(); //Wait for response for (byte CharNo = 0; CharNo < RespLen + 2; CharNo++) //Read input including CR + LF before data { RespChar3 = GSMRead(TimeoutLen1); //Read input if(CharNo > 1) //First characters returned are CR LF, skip these { if(RespChar3 != (char)EEPROM.read(StartAddr + CharNo - 2)) //Does response match expected response? { return 0; //Return error } } } return 1; //Response matches } //__________ void ReadNumberFromFile(byte MaxDigits) { byte DataRead; //Raw data from file DataFile.seek(FindNext(DataFile,':') + 1); byte CharNo = 1; memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string do { DataRead = DataFile.read(); TempStr10[CharNo - 1] = DataRead; CharNo++; } while ((DataRead != 13) && (CharNo < MaxDigits + 1)); } //__________ //Function to read pulse inputs, check button and update display void ReadPulseInputs() { for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { //Store current input state byte InValue = digitalRead(PinIn[InputNo]); //Set 1 if high (closed) and 0 if low (open) if (InValue != OldInputState[InputNo]) { //State has changed, log the time and store to oldvalue PrevPulseTime[InputNo] = millis(); OldInputState[InputNo] = InValue; NewPulse[InputNo] = true; } else { //State has not changed if((IntervalCheck(PrevPulseTime[InputNo],PulseDebounce)) && (NewPulse[InputNo] == true) && (InValue == 1)) { //Debounce period exceeded, pulse not yet counted and contact is closed //Increment the reading PulseReading[InputNo] += PulseWeight[InputNo]; //Record that the puse has been logged for this contact closure NewPulse[InputNo] = false; //Flash the Green LED if the input = that on the display if(InputNo == DispReg) { digitalWrite(PinLEDG, LOW); } } //(do nothing if state not changed and debounce not reached, pulse already logged or contact has opened) } } //Turn LED off after interval if(!digitalRead(PinLEDG)); //LED is on (low) { if((IntervalCheck(PrevPulseTime[DispReg],LEDPulseTime))) { digitalWrite(PinLEDG, HIGH); } } // read the button and take action if pressed (ReadButton Function returns 1) if (ReadButton()) //Action on keypress { //Increase register displayed on LCD if(DispReg == (NPulseInputs - 1)) { DispReg = 0; } else { DispReg++; } //Clear pulse indicator LED (as the time datum used to turn it off will now be for the wrong input digitalWrite(PinLEDG, HIGH); } //Update the LCD periodically if(IntervalCheck(PrevLcdUd, LcdUdInt)) { //Read the clock HCRTC.RTCRead(I2CDS1307Add); //Display Signal Strength oled.set1X(); oled.setCursor(75,0); //column pixel, row in 8 pixel blocks. oled.print(F(" ")); //Erase signal strength oled.setCursor(75,0); oled.print(GsmSig); oled.print(F("%")); //Display day of month oled.setCursor(0,2); oled.print(F(" ")); oled.setCursor(0,2); oled.print(HCRTC.GetDateString()); //Date //Display HH:MM:SS oled.setCursor(0,4); oled.print(HCRTC.GetTimeString()); //Time //Display register subscript (1-based) oled.set2X(); oled.setCursor(0,6); oled.print(DispReg + 1); //Display register value (unsigned int - so 5 digits) oled.setCursor(25,6); oled.print(F(" ")); oled.setCursor(25,6); oled.print(PulseReading[DispReg]); //Reset LCD refresh timer PrevLcdUd=millis(); } } //__________ //Function to Hardware Reset the GSM void ResetGSM() { //Hardware reset digitalWrite(GsmRST, LOW); //Reset, the GSM lights go out unsigned long TimeDatum = millis(); do { ReadPulseInputs(); } while (!IntervalCheck(TimeDatum, 1000)); //1 second reset digitalWrite(GsmRST, HIGH); //Return to normal operation, Network LED flashes fast while searching for a network then slow when a network is found. TimeDatum = millis(); do { ReadPulseInputs(); } while (!IntervalCheck(TimeDatum, 30000)); //30s to reconnect to network } //__________ //Update Signal Strength void UpdateGSMSigStr() { char SigStrDelim = (char)EEPROM.read(SigStrStartAddr + 1 + SigStrCmdLen); //Retrieve response delimiter from EEPROM unsigned long GSMTimeoutDatum = millis(); gsm.flush(); //No need to flush input as read and discarded up to : delimiter memset(TempStr10, 0, sizeof(TempStr10)); //Clear the temporary string EEPROMToGSM(SigStrStartAddr + 1, SigStrCmdLen, 1); //Send the signal strength request do { ReadPulseInputs(); } while ((!gsm.available()) && (!IntervalCheck(GSMTimeoutDatum, GSMTimeout))); if(gsm.available()) { do { TempStr10[0] = GSMRead(GSMTimeout); } while (TempStr10[0] != SigStrDelim); //Read response up to the delimiter //Read the bytes that are the signal strength for (byte CharNo = 0; CharNo < SigStrRespLen; CharNo++) { TempStr10[CharNo] = GSMRead(GSMTimeout); } GsmSig = atoi(TempStr10) * 3; //Convert to number, multiply by 3 to give a rough percentage, before multiplying 0-9 is marginal quality, 10-19 is good quality and 20-31 is excellent quality. 99 = not detectable/unknown if (GsmSig == 297) //99 unscaled = not known { GsmSig = 0; } } else { GsmSig = 0; } } //__________ //Function to write latest meter reads to EEPROM //Write Meter reads to eeprom memory for future use void WriteReadsToEEPROM() { for (byte InputNo = 0; InputNo < NPulseInputs; InputNo++) { EEPROMWriteUint32((4 * InputNo),PulseReading[InputNo]); } }