SMS message display on LED Matrix board with printer option

Dependencies:   AdafruitThermalPrinter HT1632_LedMatrix VodafoneUSBModem mbed-rtos mbed

main.cpp

Committer:
SomeRandomBloke
Date:
2013-07-29
Revision:
8:401c7a593dd0
Parent:
7:b46d5d98434a

File content as of revision 8:401c7a593dd0:

/**
 * Mbed SMS to Printer and LED Matrix Displays
 * Based on SMS example from VodafoneUSBModem library and
 * 3GReceiptPrinter app from Ashley Mills.
 *
 * Requires libraries:
 * AdafruitThermalPrinter - Port of Arduino library by Ashley Mills
 * VodafoneUSBModem - Driver for Vodafone K3370 Mobile Broadband dongle
 * HT1632_LedMatrix - LED Matrix library by Andrew Lindsay, port of Arduino library by Andrew Lindsay
 *
 * @author Andrew Lindsay
 *
 * @section LICENSE
 *
 * Copyright (c) 2012 Andrew Lindsay (andrew [at] thiseldo [dot] co [dot] uk)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *  Display received SMS on scrolling LED matrix with optional output to thermal printer.
 *
 * TODO: Still have issue with restarts when using printer.
 * mbed-rtos and serial problem?
 * Incoming queue for multiple messages, give each at least 60 to be displayed
 *
 */

#define USE_LED
#undef USE_PRINTER

#include <ctype.h>

#include "mbed.h"
//#include "beep.h"
#include "VodafoneUSBModem.h"
//#include "LinkMonitor.h"

#ifdef USE_LED
#include "HT1632_LedMatrix.h"
#endif
#ifdef USE_PRINTER
#include "AdafruitThermal.h"
#endif

//#define DEBUG 1

#ifdef USE_LED
// Default scrolling message includes own number obtained using USSD request
#define INFO_MSG "       Send a message to "
//#define INFO_MSG "       Welcome to the IoT London Showcase. Send a message to "
//#define INFO_MSG "       Welcome to the Reading Geek Night. Send a message to "
#endif

#define BRIGHTNESS 10

// Vodafone USSD commands
#define USSD_COMMAND_OWN_NUMBER "*#100#"
#define USSD_COMMAND_BALANCE "*#134#"
#define USSD_COMMAND_TIME "*#103#"

#ifdef DEBUG
Serial debug_pc(USBTX, USBRX); // tx, rx
#endif

#ifdef USE_PRINTER
AdafruitThermal printer(p28,p27);   // setup printer
#endif
// Define a maximum size for storage arrays, is 160 (SMS size) + a bit more.
#define MAX_MSG_LENGTH 192

// Month list used in converting to integer for set_time
char months[12][4] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

#ifdef USE_LED

#define LED_MAX_DISPLAY_X 4
#define LED_MAX_DISPLAY_Y 1


// create object to control the LED Matrix
HT1632_LedMatrix led = HT1632_LedMatrix(p7, p5, p19, p17, p18, p20);    //, LED_MAX_DISPLAY_X,LED_MAX_DISPLAY_Y );
//HT1632_LedMatrix led = HT1632_LedMatrix(p7, p5, p17, p18, p19, p20);    //, LED_MAX_DISPLAY_X,LED_MAX_DISPLAY_Y );
#define DISPDELAY 90
#endif

// Message buffers. New message waiting to be displayed and current message being displayed
//#define MAX_NUM_MSGS 10
static char cmdBuf[12];
static char newMsgBuf[MAX_MSG_LENGTH];
static char msgBuf[MAX_MSG_LENGTH];
//static int currentMsg = 0;
//static int numberOfMsgs = 0;
static bool getNextMessage = true;

#ifdef USE_LED
static char ownNumber[20];

int crtPos = 0;
int msgx = 1;    // position on message screen of current character, set to 1 to allow for first scroll
bool resetMessage = false;

void matrixDemo( void );
#endif

// Define object for buzzer to signal new message received
//Beep buzzer(p21);

// Define the onboard LEDs to use as status indicators
DigitalOut led1(LED1);      // Activity
DigitalOut led2(LED2);      // Activity, alternates with led2
DigitalOut led3(LED3);      // SMS received, turns off after processed
DigitalOut led4(LED4);      // USSD request in progress

int threadRestartCount = 0;

// Convert string buffer to upper case, is destructive. Buffer will be changed.
char* stoupper( char* s )
{
    char* p = s;
    while (*p = toupper( *p )) p++;
    return s;
}


#ifdef USE_PRINTER
void timestampMessage( char *msg )
{
    char timebuf[50];
    // Print date/time, then message
    time_t seconds = time(NULL);
    strftime(timebuf, 50, "%d/%m/%Y %X\n", localtime(&seconds));
    printer.print(timebuf);
    printer.print(msg);
    // linefeed a couple of times
    printer.feed(3);
}
#endif

#ifdef USE_LED
// Load a new message

void setNewMessage( char *newMsgStart )
{
    strncpy( newMsgBuf, newMsgStart, MAX_MSG_LENGTH );
    resetMessage = true;
 //   buzzer.beep(1000,0.5);
}


void resetMessageBuffers( )
{
    strcpy( msgBuf, "          " );
    newMsgBuf[0] = '\0';
}
#endif

void sendUSSDCommand( VodafoneUSBModem *_modem, char *ussdCommand, bool setScrolling )
{
    led4 = 1;
#ifdef DEBUG
    debug_pc.printf("Sending %s on USSD channel\n", ussdCommand);
#endif
    int ret = _modem->sendUSSD(ussdCommand, newMsgBuf, MAX_MSG_LENGTH);
    // Check for correct response
    if(ret) {
        // Non 0 value indicates an error??
#ifdef DEBUG
        debug_pc.printf("Send USSD command returned %d\n", ret);
#endif
        led4 = 0;
        return;
    }

    // Should only display message if one has been received.
#ifdef DEBUG
    debug_pc.printf("Result of command: %s\n", newMsgBuf);
#endif

    led4 = 0;

#ifdef USE_LED
    resetMessage = setScrolling;
#endif
}

void setTime(VodafoneUSBModem *_modem )
{
    char numBuf[10];
    // Set RTC using received message, format is DD-MMM-YYYY HH:MM
    // Month is in text name, e.g. NOV, time is using 24 hour clock.
    struct tm t;
    int retryCount = 3;

    while( retryCount ) {
        sendUSSDCommand( _modem, USSD_COMMAND_TIME, false );

#ifdef DEBUG
        debug_pc.printf("Time received is %s\n", newMsgBuf);
#endif

        t.tm_sec = 0;    // 0-59
        strncpy(numBuf, &newMsgBuf[15], 2 );
        t.tm_min = atoi(numBuf);    // 0-59
        strncpy(numBuf, &newMsgBuf[12], 2 );
        t.tm_hour = atoi(numBuf);   // 0-23
        strncpy(numBuf, &newMsgBuf[0], 2 );
        t.tm_mday = atoi(numBuf);   // 1-31
        strncpy(numBuf, &newMsgBuf[3], 3 );
        t.tm_mon = 0;     // 0-11
        for( int i=0; i<12; i++ ) {
            if( strncmp( months[i], numBuf, 3 ) == 0 ) {
                t.tm_mon = i;     // 0-11
                break;
            }
        }
        strncpy(numBuf, &newMsgBuf[9], 2 );
        t.tm_year = 100 + atoi( numBuf );  // year since 1900

        if( t.tm_year >110 ) {
            // convert to timestamp and display
            time_t seconds = mktime(&t);
            set_time( seconds );
            retryCount = 0;     // No more retries, terminate while
        } else {
            // Failed to set time, decrement tries
            retryCount--;
        }
    }
}

void getOwnNumber(VodafoneUSBModem *_modem, char *numPtr  )
{
    int retryCount = 3;

    while( retryCount ) {
        sendUSSDCommand( _modem, USSD_COMMAND_OWN_NUMBER, false );

#ifdef DEBUG
        debug_pc.printf("Own Number received %s\n", newMsgBuf);
#endif
        if( strlen(newMsgBuf) > 0 ) {
            // Save number in array pointed to be numPtr
            strncpy(ownNumber, newMsgBuf, strlen(newMsgBuf) );
            retryCount = 0;
        } else
            retryCount--;
    }
}


char linkStateStr[6][13] = { "Unknown     ", "Registering ", "Denied      ", "No Signal   ", "Home Network", "Roaming     " };
char bearerStr[6][5] = {"Unkn", "GSM ", "EDGE", "3G  ", "HSPA", "LTE " };

void receiveSMS(void const*)
{
    VodafoneUSBModem modem;
    time_t seconds = time(NULL);
    int pRssi = 0;
    LinkMonitor::REGISTRATION_STATE pRegistrationState;
    LinkMonitor::BEARER pBearer;

    threadRestartCount++;

    // Getting the link state seems to speed up startup of the library
    // Sometimes startup doesnt happen - need way to retry this
    modem.getLinkState( &pRssi,&pRegistrationState, &pBearer);
#ifdef DEBUG
    debug_pc.printf("Link state Rssi: %d, Registration state %x Bearer %x\n",pRssi,pRegistrationState, pBearer);
#endif
    sprintf(newMsgBuf, "Link State: %s on %s          ", linkStateStr[pRegistrationState], bearerStr[pBearer] );

#ifdef USE_LED
    setNewMessage( newMsgBuf );
#endif
#ifdef USE_PRINTER
    timestampMessage( newMsgBuf );
#endif

    Thread::wait(3000);

    // Get own number from the  network
    getOwnNumber( &modem, ownNumber );
#ifdef USE_LED
    sprintf(newMsgBuf, "Own number %s", ownNumber );
    setNewMessage( newMsgBuf );
#endif

#ifdef USE_PRINTER
    // Check if time already set, if not then get it. Use year = 0 as test
    struct tm *t = localtime(&seconds);
    if( t->tm_year == 0 )
        setTime( &modem );

    sprintf(newMsgBuf, "Thread Start %d\r\n", threadRestartCount );
    timestampMessage( newMsgBuf );
#endif
#ifdef USE_LED
    //strcpy( &msgBuf[currentMsg][0], INFO_MSG );
    sprintf(newMsgBuf, "%s %s          ", INFO_MSG, ownNumber );
    setNewMessage( newMsgBuf );

    led.displayOn();
#endif
    char num[17];
    size_t smsCount;    
    getNextMessage = true;

    while(true) {
        if( modem.getSMCount(&smsCount) == OK ) {
            if( smsCount > 0 && getNextMessage ) {
                led3 = 1;
                getNextMessage = false;
#ifdef DEBUG
                debug_pc.printf("%d SMS to read\n", smsCount);
#endif
                if( modem.getSM(num, newMsgBuf, MAX_MSG_LENGTH) == OK ) {
#ifdef DEBUG
                    debug_pc.printf("%s : %s\n", num, newMsgBuf);
#endif
#ifdef USE_PRINTER
// Print date/time, then message
                    timestampMessage( newMsgBuf );
#ifndef USE_LED
                    // Force processing of next messages if only printer being used
                    // If LED display then update when displayed once
                    getNextMessage = true;
#endif
#endif
#ifdef USE_LED
                    resetMessage = true;
#endif
                    strncpy( cmdBuf, newMsgBuf, 10 );
                    stoupper( cmdBuf );  // This is a destructive function, original characters are uppercased
                    if( strncmp( cmdBuf, "BALANCE", 7 ) == 0 ) {
                        sendUSSDCommand( &modem, USSD_COMMAND_BALANCE, true );
#ifdef USE_PRINTER
                        timestampMessage( newMsgBuf );
#endif
#ifdef USE_LED
                        resetMessage = true;
                    } else if ( strncmp( cmdBuf, "INFO", 4 ) == 0 ) {
                        sprintf(newMsgBuf, "%s %s          ", INFO_MSG, ownNumber );
                        setNewMessage( newMsgBuf );
#endif
//                  } else if ( strncmp( cmdBuf, "DEMO", 4 ) == 0 ) {
//                        matrixDemo();
//                        sprintf(newMsgBuf, "%s %s          ", INFO_MSG, ownNumber );
//                        setNewMessage( newMsgBuf );
#ifdef USE_LED
                    } else if ( strncmp( cmdBuf, "CLEAR", 5 ) == 0 ) {
                        setNewMessage( "      " );
#endif
                    }
                }
            }
        }
        Thread::wait(3000);
        led3 = 0;
    }
}

#ifdef USE_LED
void displayScrollingLine(void const*)
{
    int y,xmax,ymax;
    led.getXYMax(&xmax,&ymax);
    bool msgFinished = true;

    while(true) {
        // shift the whole screen 6 times, one column at a time; making 1 character
        if( strlen( &msgBuf[0] ) > 10 ) {
            for (int x=0; x < 6; x++) {
                led.scrollLeft(1, 1);
                msgx--;
                if( msgx <= 0 ) {
                    msgFinished = true;
                    getNextMessage = true;
                }

                // fit as much as we can on the available display space

                while (!led.putChar(msgx,0,msgBuf[crtPos]))  { // zero return if it all fitted
                    led.getXY(&msgx,&y);
                    crtPos++; // we got all of the character on!!
                    if (crtPos >= strlen(msgBuf)) {
                        crtPos = 0;
                    }
                }
                led.putShadowRam();
                Thread::wait(DISPDELAY);
            }
        } else {
            getNextMessage = true;
            msgFinished = true;
        }
        // Look for a new message being available and current message has been displayed
        
        // TODO add minimum interval between message changes, e.g. 60s
        if( resetMessage && msgFinished ) {
            led.clear();
            led.setBrightness(BRIGHTNESS);
            crtPos = 0;
            msgx = 1;
            msgFinished = false;

            strncpy( msgBuf, newMsgBuf, MAX_MSG_LENGTH );
            if( strlen( msgBuf ) > 10 ) {
                strcat( msgBuf, "          ");
            } else {
                led.putString(0,0, msgBuf);
            }
            resetMessage = false;
        }
    }
}


/*
void matrixDemo( void )
{
    int xmax,ymax;
    led.getXYMax(&xmax,&ymax);
    led.clear();
    for( int n=0; n<3; n++ ) {
        for( int i=0; i<8; i++ ) {
            led.drawRectangle(i,i,xmax-i,ymax-i, 1);
            wait(0.05);
        }
        for( int i=7; i>=0; i-- ) {
            led.drawRectangle(i,i,xmax-i,ymax-i, 0);
            wait(0.05);
        }
    }
    led.clear();
    for( int n=0; n<3; n++ ) {
        for(int i=0; i<(xmax/2); i++) {
            led.drawCircle((xmax/2), 7, i, 1 );
            wait( 0.05 );
        }
        for(int i=(xmax/2); i>=0; i--) {
            led.drawCircle((xmax/2), 7, i, 0 );
            wait( 0.05 );
        }
    }
    wait(2);

    led.clear();
    led.init(LED_MAX_DISPLAY_X,LED_MAX_DISPLAY_Y);
}
*/

void showTime()
{
    time_t seconds = time(NULL);
    char timebuf[20];
    strftime(timebuf, 20, "%X   ", localtime(&seconds));
    led.putString(12,8, timebuf );
}

void showDate()
{
    time_t seconds = time(NULL);
    char timebuf[20];
    strftime(timebuf, 20, "%d/%m/%Y   ", localtime(&seconds));
    led.putString(4,8, timebuf );
}
#endif


int main()
{
#ifdef DEBUG
    debug_pc.baud(57600);
#ifdef USE_LED
    debug_pc.printf("SMS to LED Matrix display\n");
#endif
#ifdef USE_PRINTER
    printer.begin();
    debug_pc.printf("SMS To Printer\n");
    timestampMessage("SMS To Printer Starting");
#endif
#endif
#ifdef USE_LED
    int displayCount = 10;
    bool displayTime = true;

    led.init(LED_MAX_DISPLAY_X,LED_MAX_DISPLAY_Y);    // Use all displays as 128x8 display
    led.clear();
    led.setBrightness(BRIGHTNESS);
    //led.displayOff(); // Turn off display for now until receiver task has started
    bool twoLineDisplay = (LED_MAX_DISPLAY_X > 1);
    led.putString( 0, 0, "SMS to LED Display" );

//    Thread::wait(3000);   // Wait for display to initialise after power up
#endif

    // Startup beep
 //   buzzer.beep(1000,0.5);
    
    Thread::wait(30000);     // Wait for bit longer to ensure dongle powers up

    // Set initial blank message
    resetMessageBuffers();

    Thread receiveTask(receiveSMS, NULL, osPriorityNormal, 1024 * 8);   // try 6 next
#ifdef USE_LED
    Thread displayTask(displayScrollingLine, NULL, osPriorityNormal, 1024 * 4);
#endif

    // Show we are still working by alternitively flashing LED1 and LED2, once a second
    led1 = 0;       // Working
    led2 = 1;       // Alternate with led1
    led3 = 0;       // Received and processing SMS
    led4 = 0;       // Processing USSD message queue
    while(1) {
#ifdef USE_LED
        // Only display date/time is we have a 2 row display
        if( twoLineDisplay ) {
            if( --displayCount <= 0 ) {
                displayTime = !displayTime;
                displayCount = 10;
                led.putString(0,8, "            " );
            }
            if( displayTime ) {
                showTime();
            } else {
                showDate();
            }
        }
#endif
        led1=!led1;
        led2=!led2;
        Thread::wait(1000);
    }
}