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.

Revision:
0:e72ea6decc70
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri May 22 11:45:19 2015 +0000
@@ -0,0 +1,2165 @@
+/* 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