* Project Real-time Bus Arrival Alarm * Parts - WIZwiki-W7500 Platform - Seeed Grove OLED display - Seeed Grove 4-digit display - Seeed Grove Buzzer * Detailed description Real Time Bus Information will show you when your bus is due to arrive at your bus stop. * Target Bus Information System - GyeongGi Bus Information, Korea (www.gbis.go.kr)

Dependencies:   WIZnetInterface mbed DigitDisplay HTTPClient NTPClient SeeedGrayOLED

main.cpp

Committer:
hkjung
Date:
2015-08-26
Revision:
0:879add9a219d

File content as of revision 0:879add9a219d:

#include "mbed.h"
#include "EthernetInterface.h"

#include "HTTPClient.h"     // HTTP Client library
#include "NTPClient.h"      // NTP Client library (Network Time Protocol)

#include "DigitDisplay.h"   // Seeed Grove: 4-digit Display
#include "SeeedGrayOLED.h"  // Seeed Grove: OLED Display 0.96" 96x96

Serial pc(USBTX, USBRX);

PwmOut Buzzer(D6);
DigitDisplay display(D0, D1); // 4-Digit Display connected to UART Grove connector
SeeedGrayOLED SeeedGrayOled(D14, D15);
DigitalOut myled(LED1);

Ticker tick;

////////////////////////////////////////////////////////////////
// Defines
////////////////////////////////////////////////////////////////

/* User adjustable defines */
////////////////////////////////////////////////////////////////
/* Debug Message Enable */
#define _DEBUG_BUS_ARRIVAL_ALARM_       0

/* Number of Displayed Bus Info */
#define MAX_BUS_INFO                    3       // The maximum bus information request can be up to Three (3)

/* Display mode */
#define DISPLAY_MODE_CHANGE_CNT_SEC     10      // The OLED display holding time(sec) for each bus information displayed
#define BUS_NUM_BLINK_ENABLE            1       // blink the 4-digit display when displayed bus numbers

/* Alarm Sound Enabled and Time */
#define ALARM_SOUND_ENABLE              1
#define SOUND_START_HOUR                6
#define SOUND_START_MIN                 15
#define SOUND_END_HOUR                  07
#define SOUND_END_MIN                   00

/* HTTP client */
//#define MAX_HTTPC_RETRY_CNT             2       // not used
////////////////////////////////////////////////////////////////

/* Buzzer */
#define VOLUME                          0.2     // 0.02
#define BPM                             100.0

/* Display mode define */
#define DISPLAY_TIME_MODE               0   // do not change!

/* Bus and Station (Busstop) info for GBIS openAPI */
#define DEFAULT_STATION_NUM             38270 //(Gwangju -> Seoul)
#define DEFAULT_STATION_ID              234000302       
// for test
//#define DEFAULT_STATION_NUM           38269 //(Seoul -> Gwangju)
//#define DEFAULT_STATION_ID            234000294       

typedef struct
{    
    uint8_t * busNum;       // Bus number, This value should be input at initialize stage
    uint16_t stationNum;    // Station number, notused, This value should be input at initialize stage
    uint32_t routeId;       // Bus route id for openAPI querying (openapi.gbis.go.kr)   // This value should be input at initialize stage
    uint32_t stationId;     // Bus station id for openAPI querying (openapi.gbis.go.kr) // This value should be input at initialize stage
    uint8_t resultCode;     // 0; success, 4; bus info none
    uint8_t predictTime1;   // 1st bus arrival predict time ('1' means the bus gone)
    uint8_t predictTime2;   // 2st bus arrival predict time ('1' means the none of next bus info)
    uint8_t remainSeatCnt1; // 1st bus remain seat count
    uint8_t remainSeatCnt2; // 2st bus remain seat count    
}BUSINFO;

////////////////////////////////////////////////////////////////
// Function declaration
////////////////////////////////////////////////////////////////
void BuzzerSound(void);                     // Buzzer
void Display_BusNumber(uint8_t * busnum);   // 4-digit display
uint8_t get_businfo(BUSINFO * bus);         // Get Bus information (HTTP client) 

// OLED display
void Draw_OLED_init(void);
void Draw_OLED_default(void);
void Draw_OLED_busInfo(uint8_t * busnum, uint8_t seats);
void Draw_OLED_arrivalMin(uint8_t min);
void Draw_OLED_nextMin(uint8_t nextmin);
void Draw_OLED_busList(void);

// for ticker
void beat();

////////////////////////////////////////////////////////////////
// Global variable declaration
////////////////////////////////////////////////////////////////
BUSINFO businfo[MAX_BUS_INFO];
uint8_t flag_display_mode[MAX_BUS_INFO+1] = {0, };
uint8_t display_mode = 0;
uint8_t buzzer_sound_enable = 0;

time_t beat_cTime;
struct tm *beat_localtime;

uint8_t mac[6] = {0x00,0x08,0xDC,0x01,0x02,0x03};   // MAC address, It must be modified as users MAC address

int main()
{   
    pc.baud(115200);   
    EthernetInterface ethernet;   
    
    uint8_t i;
        
    ////////////////////////////////////////////////////////////////
    // Realtime Bus Information: Initialization
    ////////////////////////////////////////////////////////////////     
    
    /* Bus number and route ID */
    uint8_t busnum_1117[4] = {1, 1, 1, 7};
    uint32_t busnum_1117_routeId = 234000069;
    
    uint8_t busnum_1113[4] = {1, 1, 1, 3};
    uint32_t busnum_1113_routeId = 234000042;
    
    uint8_t busnum_1005[4] = {1, 0, 0, 5};
    uint32_t busnum_1005_routeId = 234000065;
    
    // Bus info structure initialize    
    businfo[0].busNum = busnum_1117;
    businfo[0].stationNum = DEFAULT_STATION_NUM;
    businfo[0].routeId = busnum_1117_routeId;
    businfo[0].stationId = DEFAULT_STATION_ID;    
    
    businfo[1].busNum = busnum_1113;    
    businfo[1].stationNum = DEFAULT_STATION_NUM;
    businfo[1].routeId = busnum_1113_routeId;    
    businfo[1].stationId = DEFAULT_STATION_ID;
    
    businfo[2].busNum = busnum_1005;    
    businfo[2].stationNum = DEFAULT_STATION_NUM;
    businfo[2].routeId = busnum_1005_routeId;    
    businfo[2].stationId = DEFAULT_STATION_ID;
    
    // OLED display initialize
    SeeedGrayOled.init();             // Initialize SEEED OLED display
    SeeedGrayOled.clearDisplay();     // Clear Display
    SeeedGrayOled.setNormalDisplay(); // Set Normal Display Mode
    
    // OLED display initial draw     
    Draw_OLED_default();    //Draw_OLED_init();
    
    printf("\r\n==========================================\r\n");
    printf(" Real-time Bus Arrival Alarm\r\n");
    printf("==========================================\r\n");
    printf(" Buzzer Sound START Time: %d:%.2d\r\n", SOUND_START_HOUR, SOUND_START_MIN);
    printf(" Buzzer Sound END Time  : %d:%.2d\r\n", SOUND_END_HOUR, SOUND_END_MIN);
    printf("==========================================\r\n");
    
    // Ethernet initialize
    int ret = ethernet.init(mac);    
    printf("\r\nWIZwiki-W7500 Networking Started\r\n");
    wait(1); // 1 second for stable state

    if (!ret) {
        printf("Initialized, MAC: %s\r\n", ethernet.getMACAddress());
        ret = ethernet.connect();
        if (!ret) {
            printf("IP: %s, MASK: %s, GW: %s\r\n",
                      ethernet.getIPAddress(), ethernet.getNetworkMask(), ethernet.getGateway());
        } else {
            printf("Error ethernet.connect() - ret = %d\r\n", ret);
            exit(0);
        }
    } else {
        printf("Error ethernet.init() - ret = %d\r\n", ret);
        exit(0);
    }
    
    // NTP initialize
    NTPClient ntp;    
    printf("\r\nTrying to update time...\r\n");    
    if (ntp.setTime("211.233.40.78") == 0) 
    {
        printf("Set time successfully\r\n");
        time_t ctTime;
        ctTime = time(NULL);
        ctTime += 32400; // GMT+9/Seoul
        printf("Time is set to (GMT+9): %s\r\n", ctime(&ctTime));
    }
    else
    {
        printf("Error\r\n");
    }
    
    // 4-Digit display initialize
    display.write(0, 0);
    display.write(1, 0);
    display.write(2, 0);
    display.write(3, 0);
    display.setColon(true);
    tick.attach(&beat, 0.5);
    
    // Initial flag setting for get the bus info
    flag_display_mode[DISPLAY_TIME_MODE] = 1;
    
    printf(">> Initialize Done\r\n");    
    
    // Main routine
    while(1) {
        
        if(display_mode == DISPLAY_TIME_MODE) // Display: Time mode
        {
            if(flag_display_mode[display_mode])
            {   
                Draw_OLED_default();   
                flag_display_mode[display_mode] = 0;
                
                // Get the buses info: Bus arrival time preparation (beforehand)
                for(i = 0; i < MAX_BUS_INFO; i++)
                {
                    get_businfo(&businfo[i]);                                    
                    
#if (_DEBUG_BUS_ARRIVAL_ALARM_) //Debug message
                    printf("Businfo #%d\r\n", i);
                    printf("bus.resultCode = %c\r\n", businfo[i].resultCode);
                    printf("1st Bus arrive after %d min\r\n", businfo[i].predictTime1);    
                    printf("1st Bus remain seats %d\r\n", businfo[i].remainSeatCnt1);
                    printf("2nd Bus arrive after %d min\r\n", businfo[i].predictTime2);
                    printf("2nd Bus remain seats %d\r\n\r\n", businfo[i].remainSeatCnt2);
#endif
                }
            }
        }
        else // Display: Bus info mode
        {
            i = display_mode - 1;
            if(flag_display_mode[display_mode])
            {                   
                Draw_OLED_busInfo(businfo[i].busNum, businfo[i].remainSeatCnt1);
                Draw_OLED_arrivalMin(businfo[i].predictTime1);
                if(businfo[i].predictTime2 == 1)
                {
                    Draw_OLED_nextMin(0);
                }
                else
                {
                    Draw_OLED_nextMin(businfo[i].predictTime2);   
                }
                
                // Buzzer sound!
                // Each case means the displayed time(Remaining time until the bus arrival, minute) to ringing the buzzer
                switch(businfo[i].predictTime1)
                {
                    case 5:
                    case 6:
                    case 7:                                                
                    case 8:
                    case 9:                        
                    case 10:                                         
                        BuzzerSound();                       
                        break;
                    default:
                        break;
                }
                
                flag_display_mode[display_mode] = 0;                
            }
        }       
    }
}

////////////////////////////////////////////////////////////////
// Member function of Ticker interface (attaching to a ticker)
////////////////////////////////////////////////////////////////
void beat()
{
    static uint8_t colon = 0;
    static uint8_t myled_blink = 1;
    static uint8_t display_mode_change_cnt = 0; // initial count value; 5 sec reduced    
        
    // Get current local time info
    beat_cTime = time(NULL);
    beat_cTime += 32400; // GMT+9/Seoul    
    beat_localtime = localtime(&beat_cTime);    
    
    // Display Mode change handler (Time / Bus mode)
    display_mode_change_cnt++; // ++ every 0.5sec
    if((display_mode_change_cnt/2) >= DISPLAY_MODE_CHANGE_CNT_SEC)
    {
        display_mode++;        
        
        if(display_mode > MAX_BUS_INFO)
        {
            display_mode = DISPLAY_TIME_MODE; // 0
        }
         
        // Skip: (resultCode == 4) means 'Bus arrival info does not appear'
        while((display_mode != DISPLAY_TIME_MODE) && (businfo[display_mode-1].resultCode == 4))
        {
            display_mode++;
            
            if(display_mode > MAX_BUS_INFO)
            {
                display_mode = DISPLAY_TIME_MODE; // 0
            }
        }        
        
        flag_display_mode[display_mode] = 1;
        display_mode_change_cnt = 0;
    }
    
    // 4-Digit Display handler
    if(display_mode == DISPLAY_TIME_MODE) // Time mode
    {
        display.on();
        display.setColon(colon);    
        if (colon) {                             
            display.write(0, beat_localtime->tm_hour / 10);
            display.write(1, beat_localtime->tm_hour % 10);        
            display.write(2, beat_localtime->tm_min / 10);
            display.write(3, beat_localtime->tm_min % 10);            
        }            
        //colon = 1 - colon;  // moved below     
    }
    else // Bus info mode                
    {       
#if (BUS_NUM_BLINK_ENABLE)
        if(colon)
        {        
            Display_BusNumber(businfo[display_mode-1].busNum);
        }
        else
        {
            Display_BusNumber(NULL);
        }
#else
        Display_BusNumber(businfo[display_mode-1].busNum);
#endif
           
    }
    
    // Buzzer sound enable handler 
    buzzer_sound_enable = 0;
#if (ALARM_SOUND_ENABLE)
    if(((beat_localtime->tm_hour == SOUND_START_HOUR) && (beat_localtime->tm_min >= SOUND_START_MIN)) || (beat_localtime->tm_hour > SOUND_START_HOUR))
    {
        if((beat_localtime->tm_hour < SOUND_END_HOUR) || ((beat_localtime->tm_hour == SOUND_END_HOUR) && (beat_localtime->tm_min < SOUND_END_MIN)))
        {
            buzzer_sound_enable = 1;
        }
    }
#endif
    
    // LED blink; Device working indicator
    myled_blink ^= 1;
    myled = myled_blink ;
    
    // Invert every 0.5sec
    colon = 1 - colon;
}

////////////////////////////////////////////////////////////////
// Get Bus Information (HTTP client) Functions
////////////////////////////////////////////////////////////////
uint8_t get_businfo(BUSINFO * bus)
{
    uint8_t ret = 0;
    char str[2048] = {0, };
    char get_req[512] =  {0, };    
    char predictTime1[3], predictTime2[3];
    char remainSeatCnt1[3], remainSeatCnt2[3];    
    char *CurrentAddr = 0;
    
    //uint8_t retry_cnt = 0;            // Unimplemented, MAX_HTTPC_RETRY_CNT
    
    HTTPClient httpc;
    
    sprintf(get_req, "http://openapi.gbis.go.kr/ws/rest/busarrivalservice?serviceKey=test&routeId=%ld&stationId=%ld", bus->routeId, bus->stationId);    
    httpc.get(get_req, str, sizeof(str), 1000);
    
    CurrentAddr = strstr(str,"<resultCode>");
    bus->resultCode = *(CurrentAddr+12);  
        
    if(bus->resultCode == '0')
    {    
        CurrentAddr = strstr(str,"<predictTime1>");
        if((*(CurrentAddr+15)) == '<')
        {
            predictTime1[0] = *(CurrentAddr+14);
            predictTime1[1] = 0;
            predictTime1[2] = 0;
        }
        else
        {
            predictTime1[0] = *(CurrentAddr+14);
            predictTime1[1] = *(CurrentAddr+15);
            predictTime1[2] = 0;
        }
        bus->predictTime1 = (uint8_t)atoi(predictTime1);
            
        CurrentAddr = strstr(str,"<predictTime2>");
        if((*(CurrentAddr+15)) == '<')
        {
            predictTime2[0] = *(CurrentAddr+14);
            predictTime2[1] = 0;
            predictTime2[2] = 0;
        }
        else
        {
            predictTime2[0] = *(CurrentAddr+14);
            predictTime2[1] = *(CurrentAddr+15);
            predictTime2[2] = 0;
        }
        bus->predictTime2 = (uint8_t)atoi(predictTime2);
        
        CurrentAddr = strstr(str,"<remainSeatCnt1>");
        if((*(CurrentAddr+17)) == '<')
        {
            remainSeatCnt1[0] = *(CurrentAddr+16);
            remainSeatCnt1[1] = 0;
            remainSeatCnt1[2] = 0;
        }
        else
        {
            remainSeatCnt1[0] = *(CurrentAddr+16);
            remainSeatCnt1[1] = *(CurrentAddr+17);
            remainSeatCnt1[2] = 0;
        }
        bus->remainSeatCnt1 = (uint8_t)atoi(remainSeatCnt1);
        
        if(bus->predictTime2 > 1)   // (predictTime2 == 1) => The next bus information is not confirmed.
        {        
            CurrentAddr = strstr(str,"<remainSeatCnt2>");
            if((*(CurrentAddr+17)) == '<')
            {
                remainSeatCnt2[0] = *(CurrentAddr+16);
                remainSeatCnt2[1] = 0;
                remainSeatCnt2[2] = 0;
            }
            else
            {
                remainSeatCnt2[0] = *(CurrentAddr+16);
                remainSeatCnt2[1] = *(CurrentAddr+17);
                remainSeatCnt2[2] = 0;
            }
            bus->remainSeatCnt2 = (uint8_t)atoi(remainSeatCnt2);
        }
        
        ret = 1;
    }
    
    return ret;
}

////////////////////////////////////////////////////////////////
// Piezo Buzzer Functions
////////////////////////////////////////////////////////////////

void playNote(float frequency, float duration, float volume) 
{
    Buzzer.period(1.0/(double)frequency);
    Buzzer = ((double)volume/2.0);
    wait(duration);
    Buzzer = 0.0;
}

void BuzzerSound(void)
{
    // Calculate duration of a quarter note from bpm
    float beat_duration = 60.0 / BPM;    

    if(buzzer_sound_enable)
    {
        //playNote(329.628, (0.75 * (double)beat_duration), VOLUME);
        //playNote(261.626, (0.75 * (double)beat_duration), VOLUME);
        playNote(529.628, (0.50 * (double)beat_duration), VOLUME);
        playNote(301.626, (0.50 * (double)beat_duration), VOLUME);
    }
}

////////////////////////////////////////////////////////////////
// 4-Digit Display Functions
////////////////////////////////////////////////////////////////

void Display_BusNumber(uint8_t * busnum)
{       
    display.setColon(false);
    
    if(busnum != NULL)
    {
        display.on();
        display.write(0, busnum[0]);
        display.write(1, busnum[1]);
        display.write(2, busnum[2]);
        display.write(3, busnum[3]);
    }
    else
    {
        display.off();    
    }
}

////////////////////////////////////////////////////////////////
// OLED Display Functions
////////////////////////////////////////////////////////////////

void Draw_OLED_init(void)
{
    uint8_t empty_busnum[4] = {0, 0, 0, 0};
    
    Draw_OLED_busInfo(empty_busnum, 0);
    Draw_OLED_nextMin(0);    
}

void Draw_OLED_default(void)
{
    SeeedGrayOled.setTextXY(0, 1);
    SeeedGrayOled.putString("Bus NearBy ");
    SeeedGrayOled.setTextXY(1, 1);    
    SeeedGrayOled.putString("========== ");
    
    Draw_OLED_arrivalMin('b');
    
    SeeedGrayOled.setTextXY(11, 0);
    SeeedGrayOled.putString("WIZnet::Eric");    
}

void Draw_OLED_busList(void)
{
    uint8_t i;
    char buf[15] = {0, };
    
    SeeedGrayOled.setTextXY(0, 1);
    SeeedGrayOled.putString("           ");
    SeeedGrayOled.setTextXY(1, 1);    
    SeeedGrayOled.putString("Bus List:");
    
    Draw_OLED_arrivalMin('t');   
    
    for(i = 0; i < MAX_BUS_INFO; i++)
    {        
        SeeedGrayOled.setTextXY(3+i, 2);
        sprintf(buf, "- %.1d%.1d%.1d%.1d", businfo[i].busNum[0], businfo[i].busNum[1], businfo[i].busNum[2], businfo[i].busNum[3]);
        SeeedGrayOled.putString(buf);
    }
    
    SeeedGrayOled.setTextXY(11, 1);
    SeeedGrayOled.putString("           ");
}

void Draw_OLED_busInfo(uint8_t * busnum, uint8_t seats)
{
    char buf[15] = {0, };
    sprintf(buf, "Bus  %.1d%.1d%.1d%.1d ", busnum[0], busnum[1], busnum[2], busnum[3]);    
    SeeedGrayOled.setTextXY(0, 1);
    SeeedGrayOled.putString(buf);
    sprintf(buf, "Seats  %.2d ", seats);    
    SeeedGrayOled.setTextXY(1, 1);
    SeeedGrayOled.putString(buf);    
}

void Draw_OLED_nextMin(uint8_t nextmin)
{
    char buf[15] = {0, };
    if(nextmin == 0)
    {
        sprintf(buf, "Next: -- min");
    }
    else
    {
        sprintf(buf, "Next: %.2d min", nextmin);
    }
    
    SeeedGrayOled.setTextXY(11, 0);
    SeeedGrayOled.putString(buf);
}

void Draw_OLED_arrivalMin(uint8_t min)
{
    switch(min)
    {
         case 't':
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("            ");
            break;
        case 'b':
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("   ~~~~~   ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("  ~     ~  ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("  ~     ~  ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("  ~~~~~~~  ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("  ~ ~~~ ~  ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("  ~~~~~~~  ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("   ~~ ~~    ");
            break;
        case 0:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString(" ~  ~ ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString(" ~  ~ ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString(" ~  ~ ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString(" ~  ~ ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString(" ~  ~ ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~  ");
            break;
        case 1:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("           ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("    ~      ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("   ~~      ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("  ~~~~~~~  ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("   ~~      ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("    ~      ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      Gone! ");
            break;
        case 2:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;
        case 3:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;
         case 4:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~ ~  ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~ ~  ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~ ~  ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("        ~  ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("        ~  ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("        ~   ");
            break;
        case 5:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;
        case 6:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~    ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;
        case 7:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("         ~  ");
            break;
        case 8:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;
        case 9:
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("      ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("      ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("         ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("      ~~~~  ");
            break;        
        case 10:
        case 11: // 10
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("  ~~  ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("   ~  ~~~~  ");
            break;
        case 12:
        case 13:
        case 14: // 12
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("  ~~  ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("   ~  ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("   ~  ~    ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("   ~  ~    ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("   ~  ~~~~  ");
            break;
        case 15:
        case 16: // 15        
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("  ~~  ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("   ~  ~    ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("   ~  ~    ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("   ~  ~~~~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("   ~  ~~~~  ");
            break;
        case 17:
        case 18: // 17
        case 19: // 17
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString("  ~~  ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString("   ~  ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString("   ~     ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString("   ~     ~  ");
            break;
        case 20: // 20
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("    ~ ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("    ~ ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString(" ~~~~ ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString(" ~    ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString(" ~    ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~  ");
            break;
        default: // 20
            SeeedGrayOled.setTextXY(3, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~ ");
            SeeedGrayOled.setTextXY(4, 0);
            SeeedGrayOled.putString("    ~ ~  ~ ");
            SeeedGrayOled.setTextXY(5, 0);
            SeeedGrayOled.putString("    ~ ~  ~ ");
            SeeedGrayOled.setTextXY(6, 0);
            SeeedGrayOled.putString(" ~~~~ ~  ~ ");
            SeeedGrayOled.setTextXY(7, 0);
            SeeedGrayOled.putString(" ~    ~  ~ ");
            SeeedGrayOled.setTextXY(8, 0);
            SeeedGrayOled.putString(" ~    ~  ~ ");
            SeeedGrayOled.setTextXY(9, 0);
            SeeedGrayOled.putString(" ~~~~ ~~~~ ~");
            break;
    }
}