![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
Water Meter demo for C027N.
Dependencies: C027_Support_N CIoT_MessagingCommon IapSupport WaterMeterSupport mbed
This is the main programme for the CIoT Water Meter Demo. It builds the target code and brings in the Message Codec, IAP (NVRAM) and Water Meter Support libraries. Together with the PC application, contained in /media/uploads/RobMeades/water_meter_demo_2015-05-22.zip, it forms the complete CIoT Water Meter Demo.
Note that the PC application relies on the following:
- MS Visual Studio for C# 2012 (for compilation),
- Microsoft .NET 4.5,
- Microsoft DirectX end-user runtime installed in the following location: <WINDOWS directory>\Microsoft.NET\DirectX for Managed Code\1.0.2902.0\Microsoft.DirectX.AudioVideoPlayback.dll.
Note also that some of the DLL files, e.g. IotMeterDllWrapper.cs, may be referenced in the wrong places by the MSVC project (as this is ripped out of my development folders). Rest assured that all the files you need are there, somewhere (e.g. in the Resources folder), they may just need to be reattached to the project.
main.cpp
- Committer:
- RobMeades
- Date:
- 2015-05-22
- Revision:
- 0:e72ea6decc70
File content as of revision 0:e72ea6decc70:
/* Main application file for the device side of the * MWC demo 2015. * * Copyright (C) u-blox Melbourn Ltd * u-blox Melbourn Ltd, Melbourn, UK * * All rights reserved. * * This source file is the sole property of u-blox Melbourn Ltd. * Reproduction or utilization of this source in whole or part is * forbidden without the written consent of u-blox Melbourn Ltd. */ #include "mbed.h" #include "IapApi.h" #include "MDM.h" #include "IotMeterApi.hpp" #include "WaterMeterApi.hpp" #define FILE_REV "Revision 1.0" #define FILE_CL "Pushed by RobMeades" #define FILE_DATETIME "2015/05/15" // ---------------------------------------------------------------- // GENERAL COMPILE-TIME CONSTANTS // ---------------------------------------------------------------- // The time for which the flash turns the LED on or off for #define FLASH_DURATION_MS 250 // The maximum number of datagrams that are available for use // at any one time (i.e. the total of send and receive) #define MAX_NUM_DATAGRAMS 10 // The number of milliseconds to wait for a received message // after a send #define RECEIVE_DURATION_MS 1000 // The number of seconds to wait for device registration #define WAIT_REG_S 300 // The number of SOS flashes at an assert before restarting // if not in development mode; in development mode there // is no restart, just flashing forever #define NUM_ASSERT_SOS_BEFORE_RESTART 3 // Baud rate of the PC interface //#define PC_BAUD_RATE 9600 #define PC_BAUD_RATE 9600 // The maximum length of a MACTEST string (as raw bytes) #define MACTEST_STRING_MAX_SIZE 48 // The time to wait for a user to interrupt to change // the mactest string #define USER_WAIT_DECISECONDS 50 // The size of the stack required by IAP #define SIZE_STACK_FOR_IAP 32 // An ID for the NVRAM contents so that we // can tell if it's there. #define NVRAM_ID 0x12345678 // NVRAM bytes to write (can be a set of fixed sizes, min 256) #define NVRAM_SIZE 256 // Macro to convert to upper case #define TO_UPPER(x) (((x) >= 'a' && (x) <= 'z') ? x & ~0x20 : x) // Macro to tell is something is odd #define IS_ODD(x) (((x) & 0x01) == 0x01 ? true : false) // Macro to tell is something is even #define IS_EVEN(x) (((x) & 0x01) == 0x01 ? false : true) // Highlight for talking to the user at the PC side #define COLOUR_HIGHLIGHT "\033[33m" // Default colour #define COLOUR_DEFAULT "\033[39m" // ---------------------------------------------------------------- // TYPES // ---------------------------------------------------------------- /// A type to hold a datagram // Used for encoding AT commands on the send side and // receiving messages on the receive side. typedef struct DatagramTag_t { uint32_t size; char pBody[MAX_DATAGRAM_SIZE_RAW]; } Datagram_t; /// A linked list entry holding a datagram typedef struct DatagramEntryTag_t { bool inUse; Datagram_t datagram; DatagramEntryTag_t * pPrevEntry; DatagramEntryTag_t * pNextEntry; } DatagramEntry_t; /// Debug flashes typedef enum DebugSosTypeTag_t { DEBUG_SOS_WATER_METER_PROBLEM = WAKE_UP_CODE_WATER_METER_PROBLEM, DEBUG_SOS_AT_COMMAND_PROBLEM = WAKE_UP_CODE_AT_COMMAND_PROBLEM, DEBUG_SOS_NETWORK_SEND_PROBLEM = WAKE_UP_CODE_NETWORK_SEND_PROBLEM, DEBUG_SOS_MEMORY_ALLOC_PROBLEM = WAKE_UP_CODE_MEMORY_ALLOC_PROBLEM, DEBUG_SOS_PROTOCOL_PROBLEM = WAKE_UP_CODE_PROTOCOL_PROBLEM, DEBUG_SOS_GENERIC_FAILURE = WAKE_UP_CODE_GENERIC_FAILURE, DEBUG_SOS_REBOOT = WAKE_UP_CODE_REBOOT, } DebugSosType_t; /// Context data, which will be in NVRAM // and must be no bigger than NVRAM_SIZE typedef struct NvContextTag_t { uint32_t id; //<! An ID for the NVRAM bool devModeOnNotOff; //<! Development mode WakeUpCode_t wakeUpCode; //<! Wake-up code uint32_t readingIntervalSeconds; //<! The read interval bool ledOnNotOff; //<! The LED state bool flashOnNotOff; //<! The Flash state char mactestString[MACTEST_STRING_MAX_SIZE]; //<! The MACTEST string, only used with phase 1 modules //<! this is a string of bytes (so NOT the hex coded form) //<! and no terminator is stored uint32_t mactestStringSize; //<! The size of the MACTEST string } NvContext_t; typedef union NvramTag_t { char rawBytes[NVRAM_SIZE]; NvContext_t nvContext; } Nvram_t; // Note that order is important below: modules of newer phases // should be of higher values typedef enum ModulePhase_t { MODULE_PHASE_UNKNOWN, MODULE_PHASE_1, //<! As used at Mobile World Congress Barcelona 2015 MODULE_PHASE_2, //<! As used at Mobile World Congress Shanghai 2015 MODULE_PHASE_3 } ModulePhase; // ---------------------------------------------------------------- // PRIVATE VARIABLES // ---------------------------------------------------------------- // Non-volatile storage area, aligned on a 4-byte boundary Nvram_t __attribute__((aligned(4))) nvram; // Instantiate the NVRAM handling code static IAP iap; // Pointer to instance of the Neul modem, populated once we // get into main() as its constructor requires some initialisation // that is only complete on entry into main() MDMSerial *pgNeul = NULL; // Instantiate the interface to the water meter static WaterMeterHandler gWaterMeter; // Instantiate the messaging API to the C027N // as an IoT metering device static MessageCodec gMessageCodec; // The module phase static ModulePhase gModulePhase = MODULE_PHASE_UNKNOWN; // A good default MACTEST string static char defaultMactestString[] = {0x02, 0x00, 0x00, 0x00, 0x60, 0xb3, 0xc8, 0x37, 0x20, 0x0e, 0x1a, 0x35, 0x16, 0x00, 0x00, 0x00, 0x45, 0x23, 0x01, 0x00, 0x01, 0x02, 0x01, 0x00}; // The LED static DigitalOut led(LED); static const int gpioTable[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13}; // The connected GPIO(s) static DigitalOut pump((PinName) gpioTable[GPIO_WATER_PUMP]); // Head of free datagram linked list static DatagramEntry_t gFreeDatagramListHeadUnused; // Head of send datagram linked list static DatagramEntry_t gSendDatagramListHead; // Head of receive datagram linked list static DatagramEntry_t gRecvDatagramListHead; const char hexTable[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', \ '9', 'a', 'b', 'c', 'd', 'e', 'f'}; // ---------------------------------------------------------------- // GENERIC PRIVATE FUNCTION PROTOTYPES // ---------------------------------------------------------------- static void doDebugFlash (uint32_t numberOfFlashes); static void debugSos(DebugSosType_t debugSosType); #ifdef DEBUG static void debugPrintLists (void); #endif static void getModulePhase (void); static bool isValidHex (char digit); static uint8_t hexToNibble (char digit); static uint8_t hexTobyte (char digit1, char digit2); static char peekChar (void); static char getChar (void); static uint32_t getUserHexString (uint32_t size, char * buf); static void printByteString (uint32_t size, const char * buf); static void printMactestString (uint32_t size, const char * string); static void configureModule (void); static void assert (bool condition, DebugSosType_t debugSosType, const char * pFormat, ...); static void assertAlways (DebugSosType_t debugSosType, const char * pFormat, ...); static bool writeNvContext (Nvram_t * pNvram); static NvContext_t * resetNvContext (void); static NvContext_t * readNvContext (void); static void doSendFlash (void); static void doRecvFlash (void); static bool setGpio (GpioState_t * pGpioState); static bool getGpio (GpioState_t * pGpioState); static void setWakeUpCode (WakeUpCode_t wakeUpCode); static void setDevMode (bool onNotOff); static void setReadingIntervalSeconds (uint32_t readingIntervalSeconds); static void setLed (bool onNotOff); static void setFlash (bool onNotOff); static bool init (void); static void deInit (void); static Datagram_t * allocDatagram (DatagramEntry_t * pDatagramListHead); static Datagram_t * allocSendDatagram (void); static Datagram_t * allocRecvDatagram (void); static void freeDatagram (Datagram_t *pDatagram, DatagramEntry_t * pDatagramListHead); static void freeRecvDatagram (Datagram_t *pDatagram); static void freeAllDatagrams (DatagramEntry_t *pDatagramListHead); static void freeUnusedDatagrams (DatagramEntry_t *pDatagramListHead); static void freeUnusedSendDatagrams (void); static void freeUnusedRecvDatagrams (void); static bool waitReg (uint32_t durationSeconds); static bool sendAndReceive (void); static bool queueInitialDatagram (void); static bool queuePeriodicDatagram (void); static bool handleSerialNumberGetReqDlMsg (void); static bool handleRebootReqDlMsg (bool devModeOnNotOff); static bool enqueueReadingIntervalSetGetCnfUlMsg (bool setNotGet); static bool handleReadingIntervalSetReqDlMsg (uint32_t readingIntervalSeconds); static bool handleReadingIntervalGetReqDlMsg (void); static bool enqueueGpioSetGetCnfUlMsg (bool setNotGet, GpioState_t * gpioState); static bool handleGpioSetReqDlMsg (GpioState_t * pGpioState); static bool handleGpioGetReqDlMsg (uint8_t gpio); static bool enqueueLedSetGetCnfUlMsg (bool setNotGet); static bool handleLedSetReqDlMsg (bool onNotOff); static bool handleLedGetReqDlMsg (void); static bool enqueueFlashSetGetCnfUlMsg (bool setNotGet); static bool handleFlashSetReqDlMsg (bool onNotOff); static bool handleFlashGetReqDlMsg (void); static bool processReceivedDatagrams (void); static MDMSerial *getMDMSerial(void); // ---------------------------------------------------------------- // GENERIC PRIVATE FUNCTIONS // ---------------------------------------------------------------- // Debug static void doDebugFlash (uint32_t numberOfFlashes) { uint32_t x; uint32_t y; bool devModeOnNotOff = readNvContext()->devModeOnNotOff; led = 0; wait_ms (1000); for (y = 0; (y < NUM_ASSERT_SOS_BEFORE_RESTART) | devModeOnNotOff; y++) { for (x = 0; x < numberOfFlashes; x++) { led = !led; wait_ms (FLASH_DURATION_MS); led = !led; wait_ms (FLASH_DURATION_MS); } wait_ms (2000); } // Restart the system and try again printf ("Resetting system...\n"); wait_ms (2000); NVIC_SystemReset(); } static void debugSos(DebugSosType_t debugSosType) { switch (debugSosType) { case (DEBUG_SOS_WATER_METER_PROBLEM): { doDebugFlash (3); } break; case (DEBUG_SOS_AT_COMMAND_PROBLEM): { doDebugFlash (4); } break; case (DEBUG_SOS_NETWORK_SEND_PROBLEM): { doDebugFlash (5); } break; case (DEBUG_SOS_MEMORY_ALLOC_PROBLEM): { doDebugFlash (6); } break; case (DEBUG_SOS_PROTOCOL_PROBLEM): { doDebugFlash (7); } break; case (DEBUG_SOS_GENERIC_FAILURE): { doDebugFlash (8); } break; case (DEBUG_SOS_REBOOT): { doDebugFlash (9); } break; default: { doDebugFlash (10); } break; } } /// Print out the datagram lists for debugging #ifdef DEBUG static void debugPrintLists (void) { uint32_t x; DatagramEntry_t * pEntry; printf ("LIST CONTENTS:\n"); printf ("Free list head is at 0x%ld.\n", &gFreeDatagramListHeadUnused); pEntry = &gFreeDatagramListHeadUnused; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { printf (" %d: prev 0x%lx, next 0x%lx, inUse 0x%x, datagram.size %d, datagram \"%s\".\n", x, pEntry->pPrevEntry, pEntry->pNextEntry, pEntry->inUse, pEntry->datagram.size, pEntry->datagram.pBody); pEntry = pEntry->pNextEntry; } printf ("Send list head is at 0x%ld.\n", &gSendDatagramListHead); pEntry = &gSendDatagramListHead; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { printf (" %d: prev 0x%lx, next 0x%lx, inUse 0x%x, datagram.size %d, datagram \"%s\".\n", x, pEntry->pPrevEntry, pEntry->pNextEntry, pEntry->inUse, pEntry->datagram.size, pEntry->datagram.pBody); pEntry = pEntry->pNextEntry; } printf ("Recv list head is at 0x%ld.\n", &gRecvDatagramListHead); pEntry = &gRecvDatagramListHead; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { printf (" %d: prev 0x%lx, next 0x%lx, inUse 0x%x, datagram.size %d, datagram \"%s\".\n", x, pEntry->pPrevEntry, pEntry->pNextEntry, pEntry->inUse, pEntry->datagram.size, pEntry->datagram.pBody); pEntry = pEntry->pNextEntry; } } #endif // Set the phase of the module in a global variable static void getModulePhase (void) { char * pVersion = NULL; pVersion = pgNeul->getVersion(); if (pVersion != NULL) { printf ("Version: \"%s\", so ", pVersion); if (strcmp(pVersion, "Neul CIoT Phase 2") == 0) { gModulePhase = MODULE_PHASE_2; printf ("module is MODULE_PHASE_2.\n"); } else if (strcmp(pVersion, "Neul CIoT") == 0) { gModulePhase = MODULE_PHASE_1; printf ("module is MODULE_PHASE_1.\n"); } else { printf ("module is MODULE_PHASE_UNKNOWN.\n"); } } } #if 0 // Convert to upper case // Not using this as found it didn't seem to work for the Chinese using their // terminal program. Potential character set issues maybe? // Instead, please do a direct comparison against upper and lower case versions static char toUpper (char digit) { if (digit >= 'a' && digit <= 'z') { digit &= ~0x20; } return digit; } #endif // Return true if digit is a valid hex // character, otherwise false static bool isValidHex (char digit) { uint32_t x; bool isValid = false; for (x = 0; (x < sizeof (hexTable)) && !isValid; x++) { if (TO_UPPER(digit) == TO_UPPER(hexTable[x])) { isValid = true; } } return isValid; } // Take a hex nibble and convert to a byte. static uint8_t hexToNibble (char digit) { uint8_t nibbleValue = 0; digit = TO_UPPER (digit); if (isValidHex (digit)) { if (digit >= '0' && digit <= '9') { nibbleValue = digit - '0'; } else { if (digit >= 'A' && digit <= 'F') { nibbleValue = digit - 'A' + 10; } } } return nibbleValue; } // Take a hex value made up of digit1 and digit2 // and return the corresponding byte value. // So if digit1 were 'a' and digit2 '3' the hex value // would be 0xA3. Invalid hex digits are ignored. static uint8_t hexTobyte (char digit1, char digit2) { return (hexToNibble (digit1) << 4) + hexToNibble (digit2); } // See if a character has been entered // at the PC serial port and return it, // or otherwise return 0. static char peekChar (void) { char input = 0; Serial pc (USBTX, USBRX); pc.baud (PC_BAUD_RATE); if (pc.readable()) { input = pc.getc(); } return input; } // Wait for a character to be entered // at the PC serial port and return it. static char getChar (void) { Serial pc (USBTX, USBRX); pc.baud (PC_BAUD_RATE); return pc.getc(); } // Get a string of numbers from the user as a hex // string and store it as a string of bytes in buf // String entry is terminated with <enter>, which is // NOT stored with the string and also no // terminator is stored. // If characters that aren't a valid part of a hex // string are entered they will be ignored. static uint32_t getUserHexString (uint32_t size, char * buf) { char input; char hexChar1 = 0; bool done = false; uint32_t charCount = 0; uint32_t byteCount = 0; Serial pc (USBTX, USBRX); pc.baud (PC_BAUD_RATE); while (!done && (byteCount < size)) { input = pc.getc(); if (input == '\r') { done = true; } else { if (isValidHex(input)) { pc.putc(input); if (IS_EVEN (charCount)) { hexChar1 = input; } else { buf[byteCount] = hexTobyte(hexChar1, input); byteCount++; } charCount++; } } } pc.putc('\n'); return byteCount; } /// Print a byte string as hex static void printByteString (uint32_t size, const char * buf) { char hexBuf[MACTEST_STRING_MAX_SIZE * 2]; uint32_t x = 0; uint32_t y = 0; for (x = 0; (x < size) && (y < sizeof (hexBuf)); x++) { hexBuf[y] = hexTable[(buf[x] >> 4) & 0x0f]; // upper nibble y++; if (y < sizeof (hexBuf)) { hexBuf[y] = hexTable[buf[x] & 0x0f]; // lower nibble y++; } } printf ("%.*s", (int) y, hexBuf); } // Print out the AT+MACTEST string static void printMactestString (uint32_t size, const char * string) { if (size > 0) { printf ("The MACTEST string will be:\n"); printf ("AT+MACTEST=%d,", (int) size); printByteString(size, string); printf ("\n"); } else { printf ("No MACTEST string has been set.\n"); } } // Enter transparent AT command mode until // CTRL-C is detected. static void transparentMode (void) { char input; bool done = false; Serial pc (USBTX, USBRX); pc.baud (PC_BAUD_RATE); while (!done) { // transfer data from pc to modem if (pc.readable() && pgNeul->writeable()) { input = pc.getc(); if (input == '\x03') // CTRL-C { done = true; } else { pc.putc (input); // echo pgNeul->putc (input); } } // transfer data from modem to pc if (pgNeul->readable() && pc.writeable()) { pc.putc (pgNeul->getc()); } } } // User configuration of the module // Should only be called after the module has been initialised static void configureModule (void) { bool done = false; uint32_t x; printf ("%s\n", COLOUR_HIGHLIGHT); /* get the phase of the module */ getModulePhase(); printf ("\n"); if (gModulePhase == MODULE_PHASE_UNKNOWN) { printf ("Phase of module SW unknown, skipping config...\n"); } else { if (gModulePhase == MODULE_PHASE_1) { // Print out the AT+MACTEST command on the debug port and give the PC side // some time to change it printMactestString (readNvContext()->mactestStringSize, readNvContext()->mactestString); printf ("Press 'y' within 5 seconds to change this, 'n' to continue. \n"); } else { printf ("Press 'y' within 5 seconds to enter AT command mode. \n"); } for (x = 0; (x < USER_WAIT_DECISECONDS) && !done; x++) { char input; led = !led; wait_ms (100); input = peekChar(); if (input != 0) { printf ("You pressed '%c' (0x%x).\n", input, input); } if ((input == 'Y') || (input == 'y')) { uint32_t y; led = 1; if (gModulePhase == MODULE_PHASE_1) { while (!done) { char string[MACTEST_STRING_MAX_SIZE]; printf ("\n"); printf ("Enter the new MACTEST string (just the part beyond the\n"); printf ("comma) and press <enter>, or just press <enter> to clear\n"); printf ("the current MACTEST string. Backspace/delete will not work\n"); printf ("but you can check your entry, and try again, before it is\n"); printf ("committed: "); y = getUserHexString (sizeof (string), string); printf ("\n"); printMactestString (y, string); printf ("\n"); printf ("Is this correct? Press 'y' to save it, 'n' to try again,\n"); printf ("any other key to continue without changes. "); input = getChar(); printf ("\nYou pressed '%d'.\n", input); if ((input == 'Y') || (input == 'y')) { printf ("Saving...\n"); nvram.nvContext.mactestStringSize = y; memcpy (nvram.nvContext.mactestString, string, y); writeNvContext (&nvram); done = true; } else { if (input != 'N') { printf ("OK, continuing...\n"); done = true; } } } } else { printf ("\n"); printf ("You are now in transparent AT command mode with the modem.\n"); printf ("Enter whatever commands are required to configure the modem\n"); printf ("and press CTRL-C to continue.\n"); transparentMode(); done = true; printf ("\nContinuing...\n"); } } else { if ((input == 'N') || (input == 'n')) { done = true; printf ("OK, continuing...\n"); } } } if (x == USER_WAIT_DECISECONDS) { printf ("Timed out, no key-press detected...\n"); } if ((gModulePhase == MODULE_PHASE_1) && (readNvContext()->mactestStringSize > 0)) { // Send the mactest string to the module // No need to do anything for a phase 2 module SW as those values are stored // in NVRAM inside the module printf ("Sending the MACTEST string to the module...\n"); if (pgNeul->sendMactest (readNvContext()->mactestStringSize, readNvContext()->mactestString)) { printf ("Done.\n"); } else { printf ("Module responded with ERROR.\n"); } } } led = 0; printf (COLOUR_DEFAULT); } // Asserts static void assert (bool condition, DebugSosType_t debugSosType, const char * pFormat, ...) { if (!condition) { printf ("\n!!! ASSERT !!! ASSERT !!! ASSERT !!! ASSERT !!! ASSERT !!!\n"); va_list args; va_start (args, pFormat); printf (pFormat, args); va_end (args); printf ("\n!!! ASSERT !!! ASSERT !!! ASSERT !!! ASSERT !!! ASSERT !!!\n\n\n"); wait_ms (2000); setWakeUpCode ((WakeUpCode_t) debugSosType); debugSos (debugSosType); } } static void assertAlways (DebugSosType_t debugSosType, const char * pFormat, ...) { assert (false, debugSosType, pFormat); } /// Write to NVRAM, returning true if successful, // otherwise false. The pointer pNvContext must be // on a 4 byte boundary and the structure size must // be a multiple of 4. static bool writeNvContext (Nvram_t * pNvram) { bool success = false; uint32_t iapResult; #ifdef DEBUG printf ("Writing structure at 0x%08x to NVRAM.\n", pNvram); #endif assert (((((uint32_t) pNvram) & 0x03) == 0), DEBUG_SOS_GENERIC_FAILURE, "pNvContext must be on a 4 byte boundary."); assert (((sizeof (pNvram->rawBytes) & 0x03) == 0), DEBUG_SOS_GENERIC_FAILURE, "sizeof (pNvram->rawBytes) must be a multiple of 4."); // Blank check: mbed erases all flash contents after downloading a new executable iapResult = iap.blank_check (FLASH_SECTOR (USER_FLASH_AREA_START), FLASH_SECTOR (USER_FLASH_AREA_START)); #ifdef DEBUG printf ("NVRAM: blank check result = 0x%x\n", iapResult); #endif // Erase sector, if required if (iapResult == SECTOR_NOT_BLANK) { iap.prepare (FLASH_SECTOR (USER_FLASH_AREA_START), FLASH_SECTOR (USER_FLASH_AREA_START)); iapResult = iap.erase (FLASH_SECTOR (USER_FLASH_AREA_START), FLASH_SECTOR (USER_FLASH_AREA_START)); #ifdef DEBUG printf ("NVRAM: erase result = 0x%x\n", iapResult); #endif } // Copy RAM to Flash iap.prepare (FLASH_SECTOR (USER_FLASH_AREA_START), FLASH_SECTOR (USER_FLASH_AREA_START)); iapResult = iap.write (&(pNvram->rawBytes[0]), sector_start_adress[FLASH_SECTOR (USER_FLASH_AREA_START)], sizeof (pNvram->rawBytes)); #ifdef DEBUG printf ("NVRAM: copied: SRAM (0x%08x) -> Flash (0x%08x), %d bytes, result = 0x%x\n", pNvram, sector_start_adress[FLASH_SECTOR (USER_FLASH_AREA_START)], sizeof (*pNvram), iapResult); #endif // Compare iapResult = iap.compare (&(pNvram->rawBytes[0]), sector_start_adress[FLASH_SECTOR (USER_FLASH_AREA_START)], sizeof (pNvram->rawBytes)); printf ("NVRAM: compare result = \"%s\"\n", iapResult ? "FAILED" : "OK" ); if (iapResult == CMD_SUCCESS) { success = true; } return success; } /// Write defaults to NVRAM and return // a pointer to the reset contents. static NvContext_t * resetNvContext (void) { printf ("*** Resetting NVRAM to defaults.\n"); nvram.nvContext.id = NVRAM_ID; nvram.nvContext.devModeOnNotOff = false; nvram.nvContext.wakeUpCode = WAKE_UP_CODE_OK; nvram.nvContext.readingIntervalSeconds = DEFAULT_READING_INTERVAL_SECONDS; nvram.nvContext.flashOnNotOff = true; nvram.nvContext.ledOnNotOff = false; memcpy (nvram.nvContext.mactestString, defaultMactestString, sizeof (defaultMactestString)); nvram.nvContext.mactestStringSize = sizeof (defaultMactestString); // Ignore the return value - must always return an answer writeNvContext (&nvram); return &(nvram.nvContext); } /// Read from NVRAM, always guaranteed to produce // a result (may be default values). static NvContext_t * readNvContext (void) { NvContext_t * pNvContext = NULL; uint32_t iapResult; // Blank check iapResult = iap.blank_check (FLASH_SECTOR (USER_FLASH_AREA_START), FLASH_SECTOR (USER_FLASH_AREA_START)); if (iapResult == SECTOR_NOT_BLANK) { if (nvram.nvContext.id != NVRAM_ID) { #ifdef DEBUG printf ("Loading NVRAM...\n"); #endif memcpy (&(nvram.rawBytes[0]), (const char *) USER_FLASH_AREA_START, sizeof (nvram.rawBytes)); } if (nvram.nvContext.id == NVRAM_ID) { // Contents are valid pNvContext = &(nvram.nvContext); } else { #ifdef DEBUG printf ("NVRAM contents invalid.\n"); // Contents are not valid so set them up #endif pNvContext = resetNvContext (); } } else { #ifdef DEBUG printf ("NVRAM blank.\n"); #endif // Contents are not there so set them up pNvContext = resetNvContext (); } return pNvContext; } /// Do the flash thing, should be called on a Send static void doSendFlash (void) { if (readNvContext()->flashOnNotOff) { led = !led; wait_ms (FLASH_DURATION_MS); led = !led; } } /// Do the other flash thing, should be called on a Receive static void doRecvFlash (void) { if (readNvContext()->flashOnNotOff) { led = !led; wait_ms (FLASH_DURATION_MS /4); led = !led; wait_ms (FLASH_DURATION_MS /4); led = !led; wait_ms (FLASH_DURATION_MS /4); led = !led; } } /// Set the state of a GPIO static bool setGpio (GpioState_t * pGpioState) { bool success = false; if (pGpioState->gpio < (sizeof (gpioTable) / sizeof (gpioTable[0]))) { if (pGpioState->inputNotOutput) { DigitalIn((PinName) gpioTable[pGpioState->gpio]); } else { DigitalOut * pOutput = new DigitalOut((PinName) gpioTable[pGpioState->gpio]); *pOutput = pGpioState->onNotOff; } success = true; } return success; } /// Get the state of a GPIO static bool getGpio (GpioState_t * pGpioState) { bool success = false; if (pGpioState->gpio < (sizeof (gpioTable) / sizeof (gpioTable[0]))) { if (pGpioState->inputNotOutput) { DigitalIn *pInput = new DigitalIn((PinName) gpioTable[pGpioState->gpio]); pGpioState->onNotOff = *pInput; } else { DigitalOut *pOutput = new DigitalOut((PinName) gpioTable[pGpioState->gpio]); pGpioState->onNotOff = *pOutput; } success = true; } return success; } /// Set the wake-up code static void setWakeUpCode (WakeUpCode_t wakeUpCode) { if (nvram.nvContext.wakeUpCode != wakeUpCode) { nvram.nvContext.id = NVRAM_ID; nvram.nvContext.wakeUpCode = wakeUpCode; writeNvContext (&nvram); } } /// Set development mode static void setDevMode (bool onNotOff) { if (nvram.nvContext.devModeOnNotOff != onNotOff) { nvram.nvContext.id = NVRAM_ID; nvram.nvContext.devModeOnNotOff = onNotOff; writeNvContext (&nvram); } } /// Set the reading interval in seconds static void setReadingIntervalSeconds (uint32_t readingIntervalSeconds) { if (nvram.nvContext.readingIntervalSeconds != readingIntervalSeconds) { nvram.nvContext.id = NVRAM_ID; nvram.nvContext.readingIntervalSeconds = readingIntervalSeconds; writeNvContext (&nvram); } } /// Set the LED state static void setLed (bool onNotOff) { if (nvram.nvContext.ledOnNotOff != onNotOff) { nvram.nvContext.id = NVRAM_ID; nvram.nvContext.ledOnNotOff = onNotOff; writeNvContext (&nvram); } led = nvram.nvContext.ledOnNotOff; } /// Set the Flash state static void setFlash (bool onNotOff) { if (nvram.nvContext.flashOnNotOff != onNotOff) { nvram.nvContext.id = NVRAM_ID; nvram.nvContext.flashOnNotOff = onNotOff; writeNvContext (&nvram); } } /// Initialise ourselves static bool init (void) { bool success = true; uint32_t x; GpioState_t waterPumpGpio = {GPIO_WATER_PUMP, false, false}; DatagramEntry_t * pPrevEntry = &gFreeDatagramListHeadUnused; DatagramEntry_t ** ppEntry = &(gFreeDatagramListHeadUnused.pNextEntry); memset (&gFreeDatagramListHeadUnused, 0, sizeof (gFreeDatagramListHeadUnused)); memset (&gSendDatagramListHead, 0, sizeof (gSendDatagramListHead)); memset (&gRecvDatagramListHead, 0, sizeof (gRecvDatagramListHead)); // Create a malloc()ed free list attached to the (unused) static // head of the free list for (x = 1; (x < MAX_NUM_DATAGRAMS) && success; x++) // from 1 as it's from head.pNextEntry onwards { *ppEntry = (DatagramEntry_t *) malloc (sizeof (DatagramEntry_t)); if (*ppEntry != NULL) { memset (*ppEntry, 0, sizeof (**ppEntry)); // Link it in (*ppEntry)->pPrevEntry = pPrevEntry; (*ppEntry)->pNextEntry = NULL; (*ppEntry)->inUse = false; if (pPrevEntry != NULL) { pPrevEntry->pNextEntry = *ppEntry; } pPrevEntry = *ppEntry; // Next ppEntry = &((*ppEntry)->pNextEntry); } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "failed malloc()."); success = false; } } #ifdef DEBUG debugPrintLists (); #endif if (success) { /* set the water pump control pin to an output and to off */ success = setGpio (&waterPumpGpio); assert (success, DEBUG_SOS_GENERIC_FAILURE, "failed to set water pump pin."); if (success) { // Check NVRAM printf ("\n=== IAP: NVRAM check ===\n"); printf (" device-ID = 0x%08x\n", iap.read_ID()); printf (" serial# = 0x%08x\n", iap.read_serial()); printf (" CPU running at %ld MHz\n", SystemCoreClock / 1000000); printf (" user reserved flash area: start_address = 0x%08lx\n", (long unsigned int) iap.reserved_flash_area_start()); printf (" size = %d bytes\n", iap.reserved_flash_area_size()); printf (" read_BootVer = 0x%08x\n", iap.read_BootVer()); printf (" local ID value = 0x%08lx\n", readNvContext()->id); // And finally, setup the LED. led = readNvContext()->ledOnNotOff; } } return success; } /// Tidy up static void deInit (void) { uint32_t x = 0; DatagramEntry_t * pEntry = NULL; // Return send datagram memory to the free list freeAllDatagrams (&gSendDatagramListHead); // Return receive datagram memory to the free list freeAllDatagrams (&gRecvDatagramListHead); // Free the free list from the end pEntry = &gFreeDatagramListHeadUnused; for (x = 0; (pEntry->pNextEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { DatagramEntry_t * pNextOne = pEntry->pPrevEntry; free (pEntry); pEntry = pNextOne; } } /// Allocate room for a datagram from the given list, returning a pointer // to it or NULL if unsuccessful static Datagram_t * allocDatagram (DatagramEntry_t * pDatagramListHead) { Datagram_t * pAlloc = NULL; uint32_t x = 0; DatagramEntry_t * pEntry; // If there is already an unused entry in the list, just return it pEntry = pDatagramListHead; for (x = 0; (pEntry != NULL) && (pEntry->inUse) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } if ((pEntry != NULL) && (!pEntry->inUse)) { #ifdef DEBUG printf ("allocDatagram: found existing unused entry in list.\n"); #endif // Good, use this one memset (&(pEntry->datagram), 0, sizeof (pEntry->datagram)); pEntry->inUse = true; pAlloc = &(pEntry->datagram); } else { #ifdef DEBUG printf ("allocDatagram: finding malloc()ed entry in the free list...\n"); #endif // Find the malloc()ed entry at the end of the free list pEntry = &gFreeDatagramListHeadUnused; for (x = 0; (pEntry->pNextEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } // If there is one, move it to the given list if ((pEntry != NULL) && (pEntry->pNextEntry == NULL) && pEntry != &gFreeDatagramListHeadUnused) { DatagramEntry_t * pWantedEntry = pEntry; // Unlink it from the end of the free list if (pWantedEntry->pPrevEntry != NULL) { pWantedEntry->pPrevEntry->pNextEntry = pWantedEntry->pNextEntry; pWantedEntry->pPrevEntry = NULL; } // Attach it to the end of the given list pEntry = pDatagramListHead; for (x = 0; (pEntry->pNextEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } if (pEntry->pNextEntry == NULL) { pEntry->pNextEntry = pWantedEntry; pWantedEntry->pPrevEntry = pEntry; pWantedEntry->inUse = true; pAlloc = &(pWantedEntry->datagram); } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "allocDatagram: failed to find end of list (x = %d).", x); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "allocDatagram: failed to find malloc()ed entry in free list (x = %d).", x); } } return pAlloc; } /// Allocate room for a send datagram, returning a pointer // to it or to NULL if unsuccessful static Datagram_t * allocSendDatagram (void) { return allocDatagram (&gSendDatagramListHead); } /// Allocate room for a receive datagram, returning a pointer // to it or to NULL if unsuccessful static Datagram_t * allocRecvDatagram (void) { return allocDatagram (&gRecvDatagramListHead); } /// Free a datagram from the given list static void freeDatagram (Datagram_t *pDatagram, DatagramEntry_t * pDatagramListHead) { uint32_t x = 0; DatagramEntry_t * pEntry; if (pDatagram != NULL) { #ifdef DEBUG printf ("Freeing a datagram (at 0x%lx)...\n", pDatagram); #endif // Find the entry in the list pEntry = pDatagramListHead; for (x = 0; (pEntry != NULL) && (&(pEntry->datagram) != pDatagram) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } if ((pEntry != NULL) && (&(pEntry->datagram) == pDatagram)) { DatagramEntry_t * pWantedEntry = pEntry; #ifdef DEBUG printf ("Found the datagram.\n"); #endif // Found it, mark it as not in use and, if it is a malloc()ed // entry, unlink it from the current list and move it to the // free list pWantedEntry->inUse = false; memset (&(pWantedEntry->datagram), 0, sizeof (pWantedEntry->datagram)); if (pWantedEntry != pDatagramListHead) { #ifdef DEBUG printf ("freeDatagram: moving malloc()ed entry to free list.\n"); #endif if (pWantedEntry->pPrevEntry != NULL) { pWantedEntry->pPrevEntry->pNextEntry = pWantedEntry->pNextEntry; pWantedEntry->pPrevEntry = NULL; } if (pWantedEntry->pNextEntry != NULL) { pWantedEntry->pNextEntry->pPrevEntry = pWantedEntry->pPrevEntry; pWantedEntry->pNextEntry = NULL; } // Find the end of the free list pEntry = &gFreeDatagramListHeadUnused; for (x = 0; (pEntry->pNextEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } // Link it there if (pEntry->pNextEntry == NULL) { pEntry->pNextEntry = pWantedEntry; pWantedEntry->pPrevEntry = pEntry; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "freeDatagram: failed to find end of free list (x = %d).", x); } } #ifdef DEBUG else { printf ("freeDatagram: just marking head entry as unused.\n"); } #endif } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "freeDatagram: couldn't find entry in list (pDatagram = 0x%lx, gDatagramListHead = 0x%lx, x = %d).", pDatagram, pDatagramListHead, x); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "freeDatagram: NULL pointer passed in."); } } /// Free a specific datagram from the receive list static void freeRecvDatagram (Datagram_t *pDatagram) { freeDatagram (pDatagram, &gRecvDatagramListHead); } // Empty a datagram list (either send or receive) static void freeAllDatagrams (DatagramEntry_t *pDatagramListHead) { uint32_t x; DatagramEntry_t * pEntry = NULL; pEntry = pDatagramListHead; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry->inUse = false; pEntry = pEntry->pNextEntry; } freeUnusedDatagrams(pDatagramListHead); } /// Free unused datagrams from the send or receive lists static void freeUnusedDatagrams (DatagramEntry_t *pDatagramListHead) { uint32_t x = 0; DatagramEntry_t * pEntry = NULL; if ((pDatagramListHead != NULL) && (pDatagramListHead != &gFreeDatagramListHeadUnused)) { // Go to the end of the list pEntry = pDatagramListHead; for (x = 0; (pEntry->pNextEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { pEntry = pEntry->pNextEntry; } if ((pEntry != NULL) && (pEntry->pNextEntry == NULL)) { // Now work backwards up the list freeing unused things until we get // to the head for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { DatagramEntry_t * pNextOne = pEntry->pPrevEntry; // Check that this entry is unused and not the first static entry then, // if it is, free it if ((!(pEntry->inUse)) && (pEntry != pDatagramListHead)) { freeDatagram (&(pEntry->datagram), pDatagramListHead); #ifdef DEBUG printf ("freeUnusedDatagrams: freeing an entry.\n"); #endif } pEntry = pNextOne; } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "freeUnusedDatagrams : failed to find end of list (x = %d).", x); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "freeUnusedDatagrams: NULL or free list head pointer passed in (== 0x%lx).", pDatagramListHead); } } /// Free unused datagrams from the send list static void freeUnusedSendDatagrams (void) { freeUnusedDatagrams (&gSendDatagramListHead); } /// Free unused datagrams from the receive list static void freeUnusedRecvDatagrams (void) { freeUnusedDatagrams (&gRecvDatagramListHead); } /// Wait for the device to register on the network static bool waitReg (uint32_t durationSeconds) { uint32_t count = 0; bool success = false; while (!success && (count < durationSeconds)) { success = pgNeul->checkNetStatus (NULL); wait_ms (1000); count++; } return success; } /// Do Sending (working through the inUse datagrams in the Send queue) and // then a Receive of a single datagram (putting it in the Receive queue). // \return true if the send was successful, otherwise false. static bool sendAndReceive (void) { bool success = true; // Set to true as otherwise will fail if there's nothing to send uint32_t x; DatagramEntry_t * pEntry; Datagram_t *pRecvDatagram; bool somethingInRxQueue = false; #ifdef DEBUG printf ("\nSending datagram(s)...\n"); #endif // Do the sending first pEntry = &gSendDatagramListHead; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS) && success; x++) { if (pEntry->inUse) { success = false; printf ("\nSending datagram %ld...\n", x); // Send the datagram. if (pgNeul != NULL) { success = pgNeul->datagramSend (pEntry->datagram.size, pEntry->datagram.pBody); } if (success) { doSendFlash(); printf ("Sent.\n"); } else { assertAlways (DEBUG_SOS_NETWORK_SEND_PROBLEM, "Couldn't send datagram."); } // Lose it always, don't want to clog up pEntry->inUse = false; } pEntry = pEntry->pNextEntry; // Move to the next entry } freeUnusedSendDatagrams(); // Now the receiving do { somethingInRxQueue = false; pRecvDatagram = allocRecvDatagram (); if (pRecvDatagram != NULL) { #ifdef DEBUG printf ("Checking for downlink...\n"); #endif pRecvDatagram->size = sizeof (pRecvDatagram->pBody); // Don't check the return value here (as we don't want to fail // if nothing has been received) check the size returned instead if (pgNeul != NULL) { somethingInRxQueue = pgNeul->datagramRecv ((int *) &(pRecvDatagram->size), pRecvDatagram->pBody, RECEIVE_DURATION_MS); } // If something was received say so, otherwise free the buffer if (somethingInRxQueue && (pRecvDatagram->size > 0)) { doRecvFlash (); printf ("A datagram was received.\n"); } else { freeRecvDatagram (pRecvDatagram); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate a datagram for receive."); } } while (somethingInRxQueue); return success; } /// Prepare an InitInd, plus a SerialNumberInd. // \param indNotCnf if true encode an initInt, else encode an initCnf // \return true if the preparation was successful, otherwise false. static bool queueInitialDatagram (void) { bool success = false; InitIndUlMsg_t initIndUlMsg; SerialNumberIndUlMsg_t serialNumberIndUlMsg; Datagram_t * pDatagram; uint32_t bytesEncoded = 0; printf ("\nPreparing initial datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { printf ("Reading serial number from water meter...\n"); // Read the serial number from the water meter gWaterMeter.readSerialNumber (&(serialNumberIndUlMsg.serialNumber)); // Ignore the return value and just set success == true so that the code // can run without a water meter attached. success = true; if (success) { printf ("Serial number is %ld.\n", serialNumberIndUlMsg.serialNumber); // Encode the InitInd and SerialNumberInd messages into a datagram printf ("Encoding datagram...\n"); // Set up the InitInd contents initIndUlMsg.wakeUpCode = readNvContext()->wakeUpCode; printf ("Wake-up code is 0x%x.\n", initIndUlMsg.wakeUpCode); setWakeUpCode (WAKE_UP_CODE_OK); bytesEncoded += gMessageCodec.encodeInitIndUlMsg (&(pDatagram->pBody[bytesEncoded]), &initIndUlMsg); bytesEncoded += gMessageCodec.encodeSerialNumberIndUlMsg (&(pDatagram->pBody[bytesEncoded]), &serialNumberIndUlMsg); pDatagram->size = bytesEncoded; } else { assertAlways (DEBUG_SOS_WATER_METER_PROBLEM, "Failed to read serial number from water meter."); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Prepare the periodic datagram. // \return true if the preparation was successful, otherwise false. static bool queuePeriodicDatagram (void) { bool success = false; VolumeIndUlMsg_t volumeIndMsg; RssiIndUlMsg_t rssiIndMsg; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("\nPreparing periodic datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Read the volume in litres printf ("Reading volume from water meter...\n"); gWaterMeter.readLitres (&(volumeIndMsg.volumeLitres)); printf ("Volume is %ld litres.\n", volumeIndMsg.volumeLitres); // Ignore the return value and just set success == true so that the code // can run without a water meter attached. success = true; if (success) { // Read the RSSI printf ("Reading RSSI from module...\n"); if (pgNeul != NULL) { success = pgNeul->getRssi ((int *) &(rssiIndMsg.rssi)); printf ("RSSI is %ld.\n", rssiIndMsg.rssi); } if (success) { printf ("Encoding datagram...\n"); // Encode the VolumeInd and RssiInd messages into a datagram bytesEncoded += gMessageCodec.encodeVolumeIndUlMsg (&(pDatagram->pBody[bytesEncoded]), &volumeIndMsg); bytesEncoded += gMessageCodec.encodeRssiIndUlMsg (&(pDatagram->pBody[bytesEncoded]), &rssiIndMsg); pDatagram->size = bytesEncoded; } else { assertAlways (DEBUG_SOS_AT_COMMAND_PROBLEM, "Failed to read RSSI from module."); } } else { assertAlways (DEBUG_SOS_WATER_METER_PROBLEM, "Failed to read volume from water meter."); } } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Handle a SerialNumberGetReq message static bool handleSerialNumberGetReqDlMsg (void) { bool success = false; SerialNumberGetCnfUlMsg_t serialNumberGetCnfUlMsg; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("\nHandling SerialNumberGetReqDlMsg.\n"); printf ("Reading serial number from water meter...\n"); // Read the serial number from the water meter gWaterMeter.readSerialNumber (&(serialNumberGetCnfUlMsg.serialNumber)); // Ignore the return value and just set success == true so that the code // can run without a water meter attached. success = true; if (success) { printf ("Serial number is %ld.\n", serialNumberGetCnfUlMsg.serialNumber); printf ("Preparing SerialNumberGetCnfUlMsg response in a datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Encode the serialNumberGetCnfUlMsg into a datagram printf ("Encoding datagram...\n"); bytesEncoded += gMessageCodec.encodeSerialNumberGetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &serialNumberGetCnfUlMsg); pDatagram->size = bytesEncoded; success = true; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } } else { assertAlways (DEBUG_SOS_WATER_METER_PROBLEM, "Failed to read volume from water meter."); } return success; } /// Handle a RebootReq message static bool handleRebootReqDlMsg (bool devModeOnNotOff) { bool success = true; printf ("\nHandling RebootReqDlMsg, devMode is 0x%d.\n", devModeOnNotOff); setDevMode (devModeOnNotOff); wait_ms (1000); printf ("Resetting system...\n"); wait_ms (2000); NVIC_SystemReset(); return success; } /// Enqueue a ReadingIntervalSetCnf or a ReadingIntervalGetCnf message static bool enqueueReadingIntervalSetGetCnfUlMsg (bool setNotGet) { bool success = false; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("Preparing ReadingIntervalxxxCnfUlMsg response...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Encode the ledxxxCnfUlMsg into a datagram printf ("Encoding datagram...\n"); if (setNotGet) { ReadingIntervalSetCnfUlMsg_t readingIntervalSetCnfUlMsg; readingIntervalSetCnfUlMsg.readingIntervalSeconds = readNvContext()->readingIntervalSeconds; bytesEncoded += gMessageCodec.encodeReadingIntervalSetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &readingIntervalSetCnfUlMsg); } else { ReadingIntervalGetCnfUlMsg_t readingIntervalGetCnfUlMsg; readingIntervalGetCnfUlMsg.readingIntervalSeconds = readNvContext()->readingIntervalSeconds; bytesEncoded += gMessageCodec.encodeReadingIntervalGetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &readingIntervalGetCnfUlMsg); } pDatagram->size = bytesEncoded; success = true; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Handle a ReadingIntervalSetReq message static bool handleReadingIntervalSetReqDlMsg (uint32_t readingIntervalSeconds) { printf ("\nHandling ReadingIntervalSetReqDlMsg, set to %ld.\n", readingIntervalSeconds); setReadingIntervalSeconds (readingIntervalSeconds); return enqueueReadingIntervalSetGetCnfUlMsg (true); } /// Handle a ReadingIntervalGetReq message static bool handleReadingIntervalGetReqDlMsg (void) { printf ("\nHandling ReadingIntervalGetReqDlMsg.\n"); return enqueueReadingIntervalSetGetCnfUlMsg (false); } /// Enqueue a GpioSetCnf or a GpioGetCnf message static bool enqueueGpioSetGetCnfUlMsg (bool setNotGet, GpioState_t * gpioState) { bool success = false; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("Preparing GpioxxxCnfUlMsg response in a datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Encode the gpioxxxCnfUlMsg into a datagram printf ("Encoding datagram...\n"); if (setNotGet) { GpioSetCnfUlMsg_t gpioSetCnfUlMsg; gpioSetCnfUlMsg.gpioState = *gpioState; bytesEncoded += gMessageCodec.encodeGpioSetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &gpioSetCnfUlMsg); } else { GpioGetCnfUlMsg_t gpioGetCnfUlMsg; gpioGetCnfUlMsg.gpioState = *gpioState; bytesEncoded += gMessageCodec.encodeGpioGetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &gpioGetCnfUlMsg); } pDatagram->size = bytesEncoded; success = true; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Handle a GpioSetReq message static bool handleGpioSetReqDlMsg (GpioState_t * pGpioState) { bool success = false; printf ("\nHandling GpioSetReqDlMsg, gpio %d, inputNotOutput 0x%x, onNotOff 0x%x.\n", pGpioState->gpio, pGpioState->inputNotOutput, pGpioState->onNotOff); success = setGpio (pGpioState); if (success) { success = enqueueGpioSetGetCnfUlMsg (true, pGpioState); } return success; } /// Handle a GpioGetReq message static bool handleGpioGetReqDlMsg (uint8_t gpio) { bool success = false; GpioState_t gpioState; printf ("\nHandling GpioGetReqDlMsg, gpio %d.\n", gpio); gpioState.gpio = gpio; success = getGpio (&gpioState); if (success) { success = enqueueGpioSetGetCnfUlMsg (false, &gpioState); } return success; } /// Enqueue an LedSetCnf or an LedGetCnf message static bool enqueueLedSetGetCnfUlMsg (bool setNotGet) { bool success = false; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("Preparing LedxxxCnfUlMsg response in a datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Encode the ledxxxCnfUlMsg into a datagram printf ("Encoding datagram...\n"); if (setNotGet) { LedSetCnfUlMsg_t ledSetCnfUlMsg; ledSetCnfUlMsg.onNotOff = readNvContext()->ledOnNotOff; bytesEncoded += gMessageCodec.encodeLedSetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &ledSetCnfUlMsg); } else { LedGetCnfUlMsg_t ledGetCnfUlMsg; ledGetCnfUlMsg.onNotOff = readNvContext()->ledOnNotOff; bytesEncoded += gMessageCodec.encodeLedGetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &ledGetCnfUlMsg); } pDatagram->size = bytesEncoded; success = true; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Handle an LedSetReq message static bool handleLedSetReqDlMsg (bool onNotOff) { printf ("\nHandling LedSetReqDlMsg, set gLedOnNotOff to 0x%x.\n", onNotOff); setLed (onNotOff); return enqueueLedSetGetCnfUlMsg (true); } /// Handle an LedGetReq message static bool handleLedGetReqDlMsg (void) { printf ("\nHandling LedGetReqDlMsg.\n"); return enqueueLedSetGetCnfUlMsg (false); } /// Enqueue a FlashSetCnf or a FlashGetCnf message static bool enqueueFlashSetGetCnfUlMsg (bool setNotGet) { bool success = false; uint32_t bytesEncoded = 0; Datagram_t * pDatagram; printf ("Preparing FlashxxxCnfUlMsg response in a datagram...\n"); pDatagram = allocSendDatagram(); if (pDatagram != NULL) { // Encode the flashSetCnfUlMsg or flashGetCnfUlMsg into a datagram printf ("Encoding datagram...\n"); if (setNotGet) { FlashSetCnfUlMsg_t flashSetCnfUlMsg; flashSetCnfUlMsg.onNotOff = readNvContext()->flashOnNotOff; bytesEncoded += gMessageCodec.encodeFlashSetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &flashSetCnfUlMsg); } else { FlashGetCnfUlMsg_t flashGetCnfUlMsg; flashGetCnfUlMsg.onNotOff = readNvContext()->flashOnNotOff; bytesEncoded += gMessageCodec.encodeFlashGetCnfUlMsg (&(pDatagram->pBody[bytesEncoded]), &flashGetCnfUlMsg); } pDatagram->size = bytesEncoded; success = true; } else { assertAlways (DEBUG_SOS_MEMORY_ALLOC_PROBLEM, "Couldn't allocate send datagram."); } return success; } /// Handle a FlashSetReq message static bool handleFlashSetReqDlMsg (bool onNotOff) { printf ("\nHandling FlashSetReqDlMsg, set gFlashOnNotOff to 0x%x.\n", onNotOff); setFlash (onNotOff); return enqueueFlashSetGetCnfUlMsg (true); } /// Handle a FlashGetReq message static bool handleFlashGetReqDlMsg (void) { printf ("\nHandling FlashGetReqDlMsg.\n"); return enqueueFlashSetGetCnfUlMsg (false); } /// Process any received datagrams, queueing any response datagrams // as appropriate. static bool processReceivedDatagrams (void) { bool success = true; uint32_t x; DlMsgUnion_t dlMsg; MessageCodec::DecodeResult_t decodeResult; DatagramEntry_t * pEntry; const char * pBuffer; #ifdef DEBUG printf ("\nProcessing received datagrams...\n"); #endif pEntry = &gRecvDatagramListHead; for (x = 0; (pEntry != NULL) && (x < MAX_NUM_DATAGRAMS); x++) { if (pEntry->inUse) { printf ("\nProcessing %ld (%ld bytes).\n", x, pEntry->datagram.size); pBuffer = pEntry->datagram.pBody; // Go through the entire datagram buffer looking for messages while (pBuffer < (pEntry->datagram.pBody + pEntry->datagram.size)) { decodeResult = gMessageCodec.decodeDlMsg (&pBuffer, pEntry->datagram.size, &dlMsg); switch (decodeResult) { case (MessageCodec::DECODE_RESULT_REBOOT_REQ_DL_MSG): { success = handleRebootReqDlMsg (dlMsg.rebootReqDlMsg.devModeOnNotOff); } break; case (MessageCodec::DECODE_RESULT_SERIAL_NUMBER_GET_REQ_DL_MSG): { success = handleSerialNumberGetReqDlMsg(); } break; case (MessageCodec::DECODE_RESULT_READING_INTERVAL_SET_REQ_DL_MSG): { success = handleReadingIntervalSetReqDlMsg (dlMsg.readingIntervalSetReqDlMsg.readingIntervalSeconds); } break; case (MessageCodec::DECODE_RESULT_READING_INTERVAL_GET_REQ_DL_MSG): { success = handleReadingIntervalGetReqDlMsg(); } break; case (MessageCodec::DECODE_RESULT_GPIO_SET_REQ_DL_MSG): { success = handleGpioSetReqDlMsg (&(dlMsg.gpioSetReqDlMsg.gpioState)); } break; case (MessageCodec::DECODE_RESULT_GPIO_GET_REQ_DL_MSG): { success = handleGpioGetReqDlMsg(dlMsg.gpioGetReqDlMsg.gpio); } break; case (MessageCodec::DECODE_RESULT_LED_SET_REQ_DL_MSG): { success = handleLedSetReqDlMsg (dlMsg.ledSetReqDlMsg.onNotOff); } break; case (MessageCodec::DECODE_RESULT_LED_GET_REQ_DL_MSG): { success = handleLedGetReqDlMsg(); } break; case (MessageCodec::DECODE_RESULT_FLASH_SET_REQ_DL_MSG): { success = handleFlashSetReqDlMsg (dlMsg.flashSetReqDlMsg.onNotOff); } break; case (MessageCodec::DECODE_RESULT_FLASH_GET_REQ_DL_MSG): { success = handleFlashGetReqDlMsg (); } break; case (MessageCodec::DECODE_RESULT_FAILURE): { success = false; assertAlways (DEBUG_SOS_PROTOCOL_PROBLEM, "Message decode failure."); } break; case (MessageCodec::DECODE_RESULT_INPUT_TOO_SHORT): { success = false; assertAlways (DEBUG_SOS_PROTOCOL_PROBLEM, "Message decode failure due to input too short."); } break; case (MessageCodec::DECODE_RESULT_OUTPUT_TOO_SHORT): { success = false; assertAlways (DEBUG_SOS_PROTOCOL_PROBLEM, "Message decode failure due to output buffer too short."); } break; case (MessageCodec::DECODE_RESULT_UNKNOWN_MSG_ID): { success = false; assertAlways (DEBUG_SOS_PROTOCOL_PROBLEM, "Unknown message ID."); } break; default: { success = false; assertAlways (DEBUG_SOS_PROTOCOL_PROBLEM, "Unknown decode result: 0x%x.", decodeResult); } break; } } pEntry->inUse = false; // Done with this one now } pEntry = pEntry->pNextEntry; // Move to the next entry } freeUnusedRecvDatagrams(); return success; } #ifdef C027N_USE_SOFT_RADIO // SoftModem should be connected to header pins 0 (Rx) and 1 (Tx) MDMSerial gNeulSoftRadio (D1, D0, 57600, NC, NC); #else // When compiling under GCC off-line, this works. // However, when building under the on-line IDE // it seems one has to define MDMSerial using the D1 and D0 // pins _outside_ a function, static is just not good enough, // whereas we must define MDMSerial using MDMTXD and MDMRXD // _inside_ a function since it depends on some initialisation // that is performed just before entry to main. // Hence the lines above. But I'm leaving this here to // show how it is meant to work. // Create the Neul modem instance static MDMSerial *getMDMSerial(void) { //#ifdef C027N_USE_SOFT_RADIO // printf ("\nC027N_USE_SOFT_RADIO is defined.\n"); // printf ("The on-board modem will be ignored.\n"); // printf ("SoftModem should be connected to header pins 0 (Rx) and 1 (Tx).\n"); // static MDMSerial gNeul (D1, D0, 57600, NC, NC); //#else static MDMSerial gNeul (MDMTXD, MDMRXD, 57600, NC, NC); //#endif return &gNeul; } #endif // ---------------------------------------------------------------- // PUBLIC FUNCTIONS // ---------------------------------------------------------------- int realMain (uint8_t * pReserve) { bool success = false; Serial pc (USBTX, USBRX); pc.baud (PC_BAUD_RATE); // Use pReserve to stop any attempts by the compiler to get rid of it printf ("Reserved 0x%08lX-0x%08lX for IAP stack.\n", (long unsigned int) pReserve, (long unsigned int) (pReserve + SIZE_STACK_FOR_IAP - 1)); printf ("\n%s revision details:\n", __FILE__); printf ("%s (European time).\n", FILE_DATETIME); printf ("%s, %s.\n\n", FILE_REV, FILE_CL); printf ("This build will still run if no water meter is connected.\n"); printf ("The water volume and serial number will be garbage.\n"); // Instantiate the Neul modem class #ifdef C027N_USE_SOFT_RADIO pgNeul = &gNeulSoftRadio; #else pgNeul = getMDMSerial(); #endif if (pgNeul != NULL) { // Initialise main success = init(); } else { assertAlways (DEBUG_SOS_GENERIC_FAILURE, "Unable to instantiate modem class."); } if (success) { // Initialise the water meter printf ("Initialising water meter...\n"); //gWaterMeter.setDebugOn(true); gWaterMeter.init(); // Initialise the Neul module printf ("Initialising module...\n"); pgNeul->setDebug(3); // Normally set to 2, set to 3 for maximum debug success = pgNeul->init(); if (success) { // Allow the user to configure the module configureModule(); if (gModulePhase > MODULE_PHASE_1) { // Wait for registration to complete but // only if phase 2 or higher as the CONNECT // string was never returned in earlier versions printf ("Waiting for device to register...\n"); success = waitReg (WAIT_REG_S); } if (success) { // Prepare the initial messages in a datagram success = queueInitialDatagram(); while (success) { Timer timer; uint32_t readingIntervalSeconds = readNvContext()->readingIntervalSeconds; timer.start(); // Prepare the periodic messages in a datagram success = queuePeriodicDatagram (); // Now do sends and then receives for the interval printf ("\nSending and then receiving datagrams for %ld seconds...\n", readingIntervalSeconds); while ((timer.read() < readingIntervalSeconds) && success) { success = sendAndReceive() && processReceivedDatagrams(); printf ("."); } printf ("\n"); } assertAlways (DEBUG_SOS_GENERIC_FAILURE, "Exited main loop with success = false."); } else { assertAlways (DEBUG_SOS_NETWORK_SEND_PROBLEM, "Registration failed."); } } else { assertAlways (DEBUG_SOS_AT_COMMAND_PROBLEM, "Modem initialisation failed."); } // Tidy up deInit(); } else { assertAlways (DEBUG_SOS_GENERIC_FAILURE, "Initialisation failed."); } return 0; } // This is necessary as the IAP functions use the top SIZE_STACK_FOR_IAP bytes of // RAM for their own purposes int main() { uint8_t reserve[SIZE_STACK_FOR_IAP]; return realMain (reserve); } // End Of File