Things used in this project

Hardware components:
Hexiwear docking bd
NXP Hexiwear
×1
MikroElektronika Hexiware Docking Station
×1
MikroElektronika Alcohol Click Board
×1
Super Glue
×1
M3 countersink Screw
×1
Mfr 25frf52 10k sml
Resistor 10k ohm
×1
Polymaker Polywood PLA
×1
Polymaker PolyPlus PLA (White)
×1
MikroElektronika Text to Speech Click Board
×1
Mouser 1" 8ohm speaker
×1
Mouser 3.5mm Headphone Jack
×1
Software apps and online services:
ARM mBed OS 5
Xcode
Apple Xcode
D94d qxu
Autodesk Fusion 360
Hand tools and fabrication machines:
3drag
3D Printer (generic)
09507 01
Soldering iron (generic)

Custom parts and enclosures

iBreathe Breathalyzer
Here's the Fusion360 model for the enclosure
iBreathe Assembly Parts
A ballooned Drawing showing the different components
iBreathe Breathalyzer 3D Print STL
Use these files to 3 print your enclosure
iBreathe Breathalyzer 3D print files.zip

Schematics

Alcohol Click Board 3V3 modification
Use this to help an Alcohol Click Board behave nicely when using a Hexiware with mBed OS

Code

Talking iBreathe Breathalyzer for quick referenceC/C++
/****************************************************************************
* Title                 :   iBreathe Breathalyzer
* Filename              :   Talking_breathalyzer
* Author                :   Dave Clarke
* Origin Date           :   27/09/2016
* Notes                 :   Breathalyzer utilizing Hexiware, Alcohol click and 
                            Wolksense. Now with added voice thanks to the 
                            Text-To-Speech Click board
*****************************************************************************/
/**************************CHANGE LIST **************************************
*
*    Date    Software Version    Initials       Description
*  07/10/16       1.0.0            DC        Interface Created.
*
*****************************************************************************/

/**
 * @page TEST_CFG Test Configurations
 * <h3> Test configuration : </h3>
 * @par
 * <ul>
 * <li><b> MCU           </b> :      MK64FN1M0XXX12               </li>
 * <li><b> Dev. Board    </b> :      HEXIWEAR                     </li>
 * <li><b> Oscillator    </b> :      12 MHz external              </li>
 * <li><b> Ext. Modules  </b> :      Alcohol Click on mikroBUS 1  </li>
 * <li><b> Ext. Modules  </b> :      TTS Click on mikroBUS 2      </li>
 * <li><b> SW            </b> :      mBed OS5    </li>
 * </ul>
 */

/**
 * @mainpage
 * <h3> Breathalyser created with HEXIWEAR and mBed OS 5 </h3>
 * @par This will show you how much you've drunk and tell you with an Emoticon if
 * you're too hammered to even consider driving. Using the Hexiware app the readings
 * are transmitted to the cloud via bluetooth. Is it time to give up drinking yet?!
 * It now talks to you! 
 
 * <h3> Alcohol Features </h3>
 * @par Text to Speech Click, Alcohol click, Hexiwear docking station, Hexiware
 */

/******************************************************************************
* Includes
*******************************************************************************/

#include "mbed.h"
#include "Hexi_KW40Z.h"
#include "Hexi_OLED_SSD1351.h"
#include "OLED_types.h"
#include "OpenSans_Font.h"
#include "string.h"
#include "iBreatheImages.h"
#include "text_to_speech.h"
#include "text_to_speech_hal.h"
#include "text_to_speech_hw.h"
#include "text_to_speech_img.h"


/******************************************************************************
* Module Variable Definitions
*******************************************************************************/

#define LED_ON      0
#define LED_OFF     1

                          // Pointers to:  
const uint8_t   *welcome, // Welcome screen image
                *blank,   // blank image
                *blow,    // Start Blowing Image
                *drink,   // You've been drinking image
                *drive,   // Don't drive image
                *hang,    // You'll have a hangover image
                *ini,     // Initialising image
                *sober;   // Sober as a judge image

const float     Vadc_3V3  = 0.00005035;    // 16-Bit ADC step 3V3/65535   = 0.05035 mV
       
float           Vrl = 0,                   // Output voltage
                ambientAlc = 0,            // Abmient Output voltage from sensor
                SensorRes = 0,             // SensorRes (Ohm) - Sensor resistance
                SensorRes1 = 0,            // SensorRes (Ohm) - Sensor resistance
                ppm = 0,                   // ppm
                ppm_1 = 0,                 // Ambient ppm variable  
                ratio = 0;                 // SensorRes/LoadRes ratio
              
unsigned short  adc_rd = 0; //Initialise anologue read variable

const uint8_t   ppmText[] = "ppm:"; // text for PPM label

char            text[20],           // Text array variables
                text2[20],
                text3[20];

float           value[20],  // initial sensor set up values
                value1[20]; // initial sensor set up values

bool            isFirstBoot = true;                   
              

/* Indication Flags */
static volatile bool        _tts_rdy_f;
static volatile bool        _spc_rdy_f;
static volatile bool        _tts_fin_f;
static volatile bool        _spc_fin_f;

/* Error Buffers */
static uint16_t             _req_err;
static uint16_t             _err_code;

/* Default Configuration */
static ACONF_t              _audio_conf;
static TTSCONF_t            _tts_conf;
static PMANCONF_t           _pman_conf;

static bool                 _flush_enable;

/* Timer flag and counter */
static volatile bool        _ticker_f;
static volatile uint16_t    _ticker;

/* Input and output buffers */
static  ISC_REQ_t   _last_req;
static  ISC_RESP_t  _last_rsp;

static uint8_t test[ 8 ] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
int frequency = 750000;
VER_t *version;
/******************************************************************************
* Function Prototypes
*******************************************************************************/
/* Checks for indications */
static int _parse_ind( void );

/* Message block and error callback function pointers */
static void ( *_fatal_err_callback )( uint16_t *err_code );
static void ( *_err_callback )( uint16_t *err_code );
static void ( *_msg_block_callback )( uint16_t *msg_code,
                                      uint16_t *err_code ); 

void voiceinit(void);
void sysinit(void);
void ReadSensor();
void CalculatePPM(int times, bool amb);
void ambient(int times);
void StartHaptic(void);
void StopHaptic(void const *n);
void ButtonUp(void);
void txTask(void);
void fatal_err( uint16_t *err );
void msg_blk( uint16_t *req, uint16_t *err );

/******************************************************************************
* Instance setups
*******************************************************************************/


/* Define timer for haptic feedback */
RtosTimer hapticTimer(StopHaptic, osTimerOnce);

//set up Analog read pin for alcohol sensor
AnalogIn Alcohol(PTB2);

/* Instantiate the SSD1351 OLED Driver */ 
SSD1351 oled(PTB22,PTB21,PTC13,PTB20,PTE6, PTD15); /* (MOSI,SCLK,POWER,CS,RST,DC) */
      
/* Get OLED Class Default Text Properties */
oled_text_properties_t textProperties = {0};

/* Instantiate the Hexi KW40Z Driver (UART TX, UART RX) */ 
KW40Z kw40z_device(PTE24, PTE25);

/* LED and Haptic Set ups */
DigitalOut redLed(LED1);
DigitalOut greenLed(LED2);
DigitalOut blueLed(LED3);
DigitalOut haptic(PTB9);

/* Instantiate the Click Text to Speech pinout */ 
DigitalOut TTS_RST(PTB19);
DigitalOut TTS_CS(PTC3);
DigitalOut TTS_MUTE(PTB3);
DigitalIn  TTS_RDY(PTB8);

//Instantiate SPI for comms with Speak module
SPI TextToSpeech(PTC6, PTC7, PTC5); // MOSI, MISO, SCK

// Debug Serial
Serial pc(USBTX, USBRX);

/******************************************************************************
* Bluetooth button functions and passkey function
*******************************************************************************/

void ButtonRight(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void ButtonLeft(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void PassKey(void)
{
    StartHaptic();
    strcpy((char *) text,"PAIR CODE");
    oled.TextBox((uint8_t *)text,0,25,95,18);
  
    /* Display Bond Pass Key in a 95px by 18px textbox at x=0,y=40 */
    sprintf(text3,"%d", kw40z_device.GetPassKey());
    oled.TextBox((uint8_t *)text3,0,40,95,18);
}

/******************************************************************************
* Main
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
   
     
     /* Set pointers to the BMPs stored in  iBreatheImages.c */
     welcome = iBreatheWS_bmp;    // Welcome screen image 
     blank = iBreatheBlank_bmp;   // blank image
     blow = iBreatheBlow_bmp;    // Start Blowing Image
     drink = iBreatheDrink_bmp;   // You've been drinking image
     drive = iBreatheDrive_bmp;   // Don't drive image
     hang = iBreatheHang_bmp;    // You'll have a hangover image
     ini = iBreatheini_bmp;     // Initialising image
     sober = iBreatheSober_bmp;   // Sober as a judge image
     
    /* Set initial Values */
     sysinit(); 
    
    /* initialise voice engine */  
     voiceinit();
     
    /* Welcome message on reset */    
     tts_speak( "Hello, welcome to the eye brethalizer " );
     wait(0.5);
     tts_speak( "please press start.  " ); 
}

/******************************************************************************
* Public Function Definitions
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function readSensor(void)
* -------------------------------------------------------------------------------------------------
* Overview: Read sensor
* Input: None
* Output: None
**************************************************************************************************/

void ReadSensor()
{
    /* Read 16 Bit Analog value */
    adc_rd = Alcohol.read_u16();

    // pause 200ms 
    Thread::wait(200);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function CalculatePPM(Int times, bool amb)
* -------------------------------------------------------------------------------------------------
* Overview: Calculation of PPM
* Input: times = Number of samples to take, amb sets either ambient reading or test reading (true for test)
* Output: None
**************************************************************************************************/

void CalculatePPM(int times, bool amb)
{
        float   lgPPM;
        
        /* Read values x times */
        for(int x = 0; x < times; x++)
        {
            ReadSensor();    
            value[x] = ((float)adc_rd  * Vadc_3V3);
            StartHaptic();
            Thread::wait(50);
        }    
        
        /* Calculate the average value for accuratcy */
        for(int y = 0; y < times; y++)
        {
            Vrl += value[y];
        }
        Vrl = Vrl / times;       

        /* Set SensorRes reference value */
        SensorRes =    (Vrl / 3.3);
        
        /* Set ratio */
        ratio     = SensorRes1 / SensorRes;

        /* Convert to PPM */
        lgPPM = ( log10( ratio ) * -1.5512 ) + 2.5911;
        
        /* If true create test result, flase creates reference result */
        if (amb == true)
        {
            ppm = pow( 10, lgPPM );
        }
        else
        {
            ppm_1 = pow( 10, lgPPM );
        }    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ambient(int times)
* -------------------------------------------------------------------------------------------------
* Overview: Reading of the Ambient Voltgage of the sensor for reference
* Input: times = Number of samples to take
* Output: None
**************************************************************************************************/

void ambient(int times)
{
    /* Read ambient values x times flashing green led*/
    for(int x = 0; x < times; x++)
    {
        redLed      = LED_OFF;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        ReadSensor();
        value1[x] = (float)adc_rd  * Vadc_3V3; 
        
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        Thread::wait(48);   
    }
    
    /* Calculate the average value for accuratcy */
    for(int y = 0; y < times; y++)
    {
        ambientAlc+=value1[y];            
    }
    ambientAlc = ambientAlc / times;
  
    /* Set SensorRes1 reference value */
    SensorRes1 =   (ambientAlc / 3.3);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StartHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Start Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StartHaptic(void)
{
    hapticTimer.start(50);
    haptic = 1;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StopHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Stop Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StopHaptic(void const *n) {
    haptic = 0;
    hapticTimer.stop();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ButtonUp(void)
* -------------------------------------------------------------------------------------------------
* Overview: Function called whe up button pressed, Begins Breathilyzer testing procedure
* Input:  None
* Output: None
**************************************************************************************************/

void ButtonUp(void)
{
    StartHaptic();
    
    bool Ref = false;
    bool Test = true;
        
    /* LED set to green for test beginning*/
    redLed      = LED_OFF;
    greenLed    = LED_ON;
    blueLed     = LED_OFF;
    
    /* Fill 96px by 96px Screen with 96px by 96px Initialising Image starting at x=0,y=0 */   
    oled.DrawImage(ini,0,0);
    
    /* first boot bug work around to stop junk values */
    if (isFirstBoot == true)
    {
        /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
        ambient(1);
        CalculatePPM(1,Ref);
        isFirstBoot = false;
    }
    
    
    /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
    tts_speak( "initializing" );
    ambient(10);
    CalculatePPM(10,Ref);
    
    
    /* Fill 96px by 96px Screen with 96px by 96px Blowing Image starting at x=0,y=0 */  
    oled.DrawImage(blow,0,0);
    tts_speak( "please blow until buzzing stops" );
    /*read breathe alcohol levels with 10 samples and set flag to show it's breathilyzer test figure*/
    CalculatePPM(10,Test);
    
    /*Calculate the difference in Alcohol level based on Ambient and test sample*/
    ppm = ppm - ppm_1;
    
    /*Throw away any values less than 0*/
    if (ppm < 0)
    {
         ppm = 0;
    }     
    
    /* Fill 96px by 96px Screen with 96px by 96px Blank Background Image starting at x=0,y=0 */
    oled.DrawImage(blank,0,0);     
    tts_speak( "Test Complete" );
    Thread::wait(500);
    /* Show Calculated alcohol level in PPM and send data via bluetooth to the cloud */
    oled.SetTextProperties(&textProperties);
    oled.Label(ppmText,20,36);
    tts_speak( "you blew" );
    sprintf(text,"%.2f",ppm);
    Thread::wait(50);
    tts_speak( text );
    oled.Label((uint8_t *)text,50,36);
    Thread::wait(50);
    tts_speak( "pee pee emm" );
    Thread::wait(1000);
    
    /* Currently sending to the Pressure variable as a temp place holder */
    kw40z_device.SendiBreathe(ppm); // this is used for custom Hexiware app
    kw40z_device.SendPressure(ppm * 10); // using this to record on Wolksense Cloud
    
    
    /* You've got a Hangover coming!*/
    if ( ppm > 200)
    {
        tts_speak("you have got a hang over coming");
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        StartHaptic();
        
        /* Fill 96px by 96px Screen with 96px by 96px Hangover Image starting at x=0,y=0 */
        oled.DrawImage(hang,0,0);
    }   
    
    /* You Shouldn't drive */
    else if (ppm < 200 && ppm > 150)
    {
        tts_speak("you shouldn't drive");
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_ON;
        
        StartHaptic();
      
        /* Fill 96px by 96px Screen with 96px by 96px Don't Drive Image starting at x=0,y=0 */      
        oled.DrawImage(drive,0,0);
    } 
    
    /* You've had a drink */
    else if (ppm < 150 && ppm > 50)
    {
        tts_speak("you have had a drinky");
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_ON;
        
        StartHaptic();
    
        /* Fill 96px by 96px Screen with 96px by 96px Had a drink Image starting at x=0,y=0 */     
        oled.DrawImage(drink,0,0);
    }
    
    /* Sober as a judge*/   
    else
    {
        tts_speak("you are as sober as a judge");
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        
        StartHaptic();
        oled.DrawImage(sober,0,0);    
    }
    Thread::wait(5000);
    
    /* Go back to start screen */
    sysinit();   
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void sysint(void)
* -------------------------------------------------------------------------------------------------
* Overview: System Initial Values Set up
* Input: None
* Output: None
**************************************************************************************************/
void sysinit(void)
{
     /* Set LED to Blue by default*/      
    redLed      = LED_OFF;
    greenLed    = LED_OFF;
    blueLed     = LED_ON;
    
    /* Turn on the backlight of the OLED Display */
    oled.DimScreenON();
    
    /* Fill 96px by 96px Screen with 96px by 96px Welcome Image starting at x=0,y=0 */
    oled.DrawImage(welcome,0,0);
    
    /* Register callbacks to application functions */
    kw40z_device.attach_buttonUp(&ButtonUp);
    kw40z_device.attach_buttonLeft(&ButtonLeft);
    kw40z_device.attach_buttonRight(&ButtonRight);
    kw40z_device.attach_passkey(&PassKey);
    
    /* Send sensor data to bluetooth */
    kw40z_device.SendSetApplicationMode(GUI_CURRENT_APP_SENSOR_TAG);
    
    /* Change font color to White */
    oled.GetTextProperties(&textProperties); 
    textProperties.fontColor   = COLOR_WHITE;
    oled.SetTextProperties(&textProperties);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void voiceinit(void)
* -------------------------------------------------------------------------------------------------
* Overview: initialise voice engine
* Input: None
* Output: None
**************************************************************************************************/
void voiceinit(void)
{
    
    /*setting up the SPI defaults*/
    TextToSpeech.lock();
    TextToSpeech.format(8,3);
    TextToSpeech.frequency(frequency);
    TextToSpeech.unlock();
    
    /* initialise voice */
    pc.printf("System Init Done!\r\n");
    tts_init();
    pc.printf("tts Init Done!\r\n");
    tts_setup();
    pc.printf("tts setup Done!\r\n");
    tts_msg_block_callback( msg_blk );
    tts_fatal_err_callback( fatal_err );
    tts_config( 0x10, false, TTSV_US, 0x0080 );
    tts_unmute();
    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function msg_blk( uint16_t *req, uint16_t *err )
* -------------------------------------------------------------------------------------------------
* Overview: receive a blocked message from  S1V30120;
**************************************************************************************************/
void msg_blk( uint16_t *req, uint16_t *err )
{
    char txt[ 6 ];
    
    pc.printf( " MSG BLOCKED \r\n" );
    sprintf( txt, "%x\r\n", *req );
    pc.printf( txt );
    sprintf( txt, "%x\r\n", *err );
    pc.printf( txt );
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function fatal_err( uint16_t *err )
* -------------------------------------------------------------------------------------------------
* Overview: error detected
**************************************************************************************************/
void fatal_err( uint16_t *err )
{
    pc.printf( "Fatal Error Detected" );
    tts_init();
    tts_fatal_err_callback( fatal_err );
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* text_to_speech_hal.c
* -------------------------------------------------------------------------------------------------
* Overview: Functions to control SPI interface.
**************************************************************************************************/

void tts_hal_cs_high()
{
    
    TTS_CS = 1;
}

void tts_hal_cs_low()
{
    TTS_CS = 0;
    
}

void tts_hal_mut_high()
{
    TTS_MUTE = 1;
}

void tts_hal_mut_low()
{
    TTS_MUTE = 0;
}

void tts_hal_reset( void )
{
    tts_hal_cs_high();
    TTS_RST = 0;
    tts_hal_write_pad(1);
    wait(0.01);
    TTS_RST = 1;
    wait(POR_TIME);
}

bool tts_hal_msg_rdy( void )
{
    return TTS_RDY;
}

void tts_hal_init()
{
    tts_hal_reset();
    tts_hal_cs_high();
    tts_hal_mut_low();
}

void tts_hal_write( uint8_t *buffer,
                    uint16_t count, bool boot )
{
    TextToSpeech.lock();
    while( count-- )
    {
        if(!boot)
            pc.printf("%02X\r\n", *buffer);
        
        TextToSpeech.write( *buffer++  );
        
     }   
    TextToSpeech.unlock();
}


void tts_hal_write_pad( int cnt )
{
    TextToSpeech.lock();
    tts_hal_cs_low();
    while(cnt--)
    {
         TextToSpeech.write( PADDING_BYTE );
    }  

    tts_hal_cs_high();
    TextToSpeech.unlock();
}

void tts_hal_read( uint8_t *buffer,
                   uint16_t count )
{
    TextToSpeech.lock();
    while(count--)
    {   
        *buffer++ = TextToSpeech.write( DUMMY_BYTE ); //read spi bus
      
       
        //pc.printf("buffer = %X\n\r", *buffer);
    }
    TextToSpeech.unlock();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* text_to_speech.c
* -------------------------------------------------------------------------------------------------
* Overview: Functions to control the setup and speech controls
**************************************************************************************************/

static int _parse_ind( void )
{
    uint16_t rsp_idx = tts_rsp_idx();
    if ( rsp_idx == ISC_MSG_BLOCKED_RESP )
    {
        uint8_t rsp_data[ 4 ] = { 0 };

        _req_err = 0;
        _err_code = 0;

        tts_rsp_data( rsp_data );

        _req_err |= rsp_data[ 0 ];
        _req_err |= rsp_data[ 1 ] << 8;
        _err_code |= rsp_data[ 2 ];
        _err_code |= rsp_data[ 3 ] << 8;

        if( _msg_block_callback != NULL )
            _msg_block_callback( &_req_err, &_err_code );

        return 1;

    } else if ( rsp_idx == ISC_ERROR_IND ) {

         uint8_t rsp_data[ 4 ] = { 0 };

        _req_err = 0;
        _err_code = 0;

        tts_rsp_data( rsp_data );

        _err_code |= rsp_data[ 0 ];
        _err_code |= rsp_data[ 1 ] << 8;

        if ( _err_code && _err_code < 0x8000 )
        {
            if( _err_callback != NULL )
                _err_callback( &_err_code );

        } else if ( _err_code && _err_code > 0x7FFF ) {

            if( _fatal_err_callback != NULL )
                _fatal_err_callback( &_err_code );
        }

        return 1;

    } else if ( rsp_idx == ISC_TTS_READY_IND ) {

        _tts_rdy_f = 1;

    } else if ( rsp_idx == ISC_SPCODEC_READY_IND ) {

        _spc_rdy_f = 1;

    } else if ( rsp_idx == ISC_TTS_FINISHED_IND ) {

        _tts_fin_f = 1;

    } else if ( rsp_idx == ISC_SPCODEC_FINISHED_IND ) {

        _spc_fin_f = 1;
    }

    return 0;
}

void tts_init()
{
    _req_err = 0;
    _err_code = 0;

    _tts_rdy_f = true;
    _spc_rdy_f = true;
    _tts_fin_f = true;
    _spc_fin_f = true;

    _audio_conf.as = 0x00;
    _audio_conf.ag = 0x43;
    _audio_conf.amp = 0x00;
    _audio_conf.asr = ASR_11KHZ;
    _audio_conf.ar = 0x00;
    _audio_conf.atc = 0x00;
    _audio_conf.acs = 0x00;
    _audio_conf.dc = 0x00;

    _tts_conf.sr = 0x01;
    _tts_conf.voice = 0x00;
    _tts_conf.ep = 0x00;
    _tts_conf.lang = TTSV_US;
    _tts_conf.sr_wpm_lsb = 0xc8;
    _tts_conf.sr_wpm_msb = 0x00;
    _tts_conf.ds = 0x00;
    _tts_conf.res = 0x00;

    _pman_conf.am_lsb = 0x01;
    _pman_conf.am_msb = 0x00;
    _pman_conf.spi_clk = 0x01;
    _pman_conf.pad = PADDING_BYTE;

    _flush_enable = false;

    _msg_block_callback = NULL;
    _fatal_err_callback = NULL;
    _err_callback = NULL;

    tts_hw_init();
}

void tts_msg_block_callback( void( *msg_blk_ptr )( uint16_t *req_ptr,
                                                   uint16_t *err_ptr ) )
{
    _msg_block_callback = msg_blk_ptr;
}

void tts_fatal_err_callback( void( *fatal_err_ptr )( uint16_t *err_ptr ) )
{
    _fatal_err_callback = fatal_err_ptr;
}

void tts_err_callback( void( *error_ptr )( uint16_t *err_ptr ) )
{
    _err_callback = error_ptr;
}

void tts_mute()
{
    tts_mute_cmd( true );
}

void tts_unmute()
{
    tts_mute_cmd( false );
}


void tts_setup()
{
    //check HW version
    tts_version_boot(); 
    
    int succ = tts_image_load( (uint8_t*)TTS_INIT_DATA, sizeof( TTS_INIT_DATA ) );
   
    if ( succ != 0x0001 )
    {
            // image load failed, try turning it off and on again
            pc.printf("tts init data failed!\n\r");
            pc.printf("returned value: %X\n\r", succ);
            while(1);
    }        
    pc.printf("tts image load done\n\r"); 
    succ =  tts_image_exec();
    
    if ( succ != 0x0001 )
    {
        // image boot failed, try turning it off and on again
        pc.printf("tts image exec failed!\n\r");
        pc.printf("returned value: %X\n\r", succ);
        while(1);
    } 
    pc.printf("tts image exec done\n\r");
    
    tts_interface_test();
    pc.printf("tts interface test done\n\r");
    
    tts_power_default_config();
    pc.printf("tts power default done\n\r");
    
    tts_audio_default_config();
    pc.printf("tts audio default done\n\r");
    
    tts_volume_set( 0 );
    pc.printf("tts volume set done\n\r");
    
    tts_default_config();
    pc.printf("tts default config done\n\r");
}

void tts_version_boot( void )
{
    
    uint8_t tmp_resp[ 16 ] = { 0 };

    wait( RESET_TO_BOOT_TIME );
   
    tts_parse_req( ISC_VERSION_REQ_BOOT, NULL, 0 );
   
    while( !tts_rsp_chk( ISC_VERSION_RESP_BOOT ) )
    {
        tts_get_resp();
    }
    tts_hal_write_pad( 16);
    tts_rsp_data( tmp_resp );
    pc.printf("hwver0 %X \n\r", tmp_resp[ 0 ]);
    pc.printf("hwver1 %X \n\r", tmp_resp[ 1 ]);
}

uint16_t tts_image_load(const uint8_t *image,
                         uint16_t count )
{
    uint16_t tmp_resp = 0;
    uint16_t index = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
   
    while ( ( count - index ) > ( BOOT_MESSAGE_MAX - 4  ) )
    {
       
       tts_parse_boot_img( image + index, BOOT_MESSAGE_MAX - 4  );
       wait(0.01);
       index += ( BOOT_MESSAGE_MAX - 4 );
    }
    tts_parse_boot_img( image + index, count - index );
    wait(0.01);           
    
    while( !tts_rsp_chk( ISC_BOOT_LOAD_RESP ) )
    {
        tts_get_resp();
          
        if( _parse_ind() )
            return _err_code;
    }
    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;
  
    return tmp_resp;
}

uint16_t tts_image_exec()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_BOOT_RUN_REQ, NULL, 0 );
     
    while( !tts_rsp_chk( ISC_BOOT_RUN_RESP ) )
    {
       tts_get_resp();
       
        if( _parse_ind() )
            return _err_code;
    }
    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return  tmp_resp;
}

uint16_t tts_interface_test()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    
    wait( BOOT_TO_MAIN_MODE );
    
    tts_parse_req( ISC_TEST_REQ, test, 8 );
           
    while( !tts_rsp_chk( ISC_TEST_RESP ) )
    {
        tts_get_resp();
      
        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

 uint16_t tts_version_main( VER_t *buffer )
{
    char tmp_char[ 3 ] = { 0 };
    uint32_t tmp_fwf = 0;
    uint32_t tmp_fwef = 0;
    uint8_t tmp_resp[ 20 ] = { 0 };

    tts_parse_req( ISC_VERSION_REQ_MAIN, NULL, 0 );

    while( !tts_rsp_chk( ISC_VERSION_RESP_MAIN ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( tmp_resp );
    sprintf(tmp_char, "%c", tmp_resp[ 0 ]);
    strcpy( buffer->hwver, tmp_char );
    strcat( buffer->hwver, "." );
    sprintf(tmp_char, "%c", tmp_resp[ 1 ]);
    strcat( buffer->hwver, tmp_char );
    sprintf(tmp_char, "%c", tmp_resp[ 2 ]);
    strcpy( buffer->fwver, tmp_char );
    strcat( buffer->fwver, "." );
    sprintf(tmp_char, "%c", tmp_resp[ 3 ]);
    strcat( buffer->fwver, tmp_char );
    strcat( buffer->fwver, "." );
    sprintf(tmp_char, "%c", tmp_resp[ 12 ]);
    strcat( buffer->fwver, tmp_char );

    tmp_fwf |= tmp_resp[ 4 ];
    tmp_fwf |= tmp_resp[ 5 ] << 8;
    tmp_fwf |= tmp_resp[ 6 ] << 16;
    tmp_fwf |= tmp_resp[ 7 ] << 24;
    buffer->fwf = (FF_t)tmp_fwf;
    tmp_fwef |= tmp_resp[ 8 ];
    tmp_fwef |= tmp_resp[ 9 ] << 8;
    tmp_fwef |= tmp_resp[ 10 ] << 16;
    tmp_fwef |= tmp_resp[ 11 ] << 24;
    buffer->fwef = (EFF_t)tmp_fwef;

    return 0x0000;
}

uint16_t tts_power_default_config()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_PMAN_CONFIG_REQ, ( uint8_t* )&_pman_conf, 4 );

    while( !tts_rsp_chk( ISC_PMAN_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_standby_enter()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_PMAN_STANDBY_ENTRY_REQ, NULL, 0 );

    while( !tts_rsp_chk( ISC_PMAN_STANDBY_ENTRY_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_standby_exit()
{
    wait( STBY_MODE_ENTERY );
    tts_parse_req( ISC_PMAN_STANDBY_EXIT_IND, NULL, 0 );

    while( !tts_rsp_chk( ISC_PMAN_STANDBY_EXIT_IND ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    return 0x0000;
}

uint16_t tts_audio_default_config()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_AUDIO_CONFIG_REQ, ( uint8_t* )&_audio_conf, 8 );

    while( !tts_rsp_chk( ISC_AUDIO_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_audio_config( int8_t audio_gain,
                           ASR_t sample_rate,
                           bool dac_control )
{
    ACONF_t audio_conf;
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    if ( audio_gain < -48 || audio_gain > 18 )
        return 0xFFFF;

    if ( sample_rate != 0 || sample_rate != 1 || sample_rate != 3 )
        return 0xFFFF;

    audio_conf.ag = ( uint8_t )audio_gain;
    audio_conf.asr = sample_rate;
    audio_conf.dc = dac_control;

    tts_parse_req( ISC_AUDIO_CONFIG_REQ, ( uint8_t* )&audio_conf, 8 );

    while( !tts_rsp_chk( ISC_AUDIO_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_volume_set( int16_t gain )
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_gain[ 2 ] = { 0 };

    tmp_gain[ 0 ] = gain & 0x00FF;
    tmp_gain[ 1 ] = ( gain & 0xFF00 ) >> 8;

    tts_parse_req( ISC_AUDIO_VOULME_REQ, tmp_gain, 2 );

    while( !tts_rsp_chk( ISC_AUDIO_VOLUME_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_audio_mute()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_mute[ 2 ] = { 1, 0 };

    tts_parse_req( ISC_AUDIO_MUTE_REQ, tmp_mute, 2 );

    while( !tts_rsp_chk( ISC_AUDIO_MUTE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_audio_unmute()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_mute[ 2 ] = { 0, 0 };

    tts_parse_req( ISC_AUDIO_MUTE_REQ, tmp_mute, 2 );

    while( !tts_rsp_chk( ISC_AUDIO_MUTE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_default_config()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_TTS_CONFIG_REQ, ( uint8_t* )&_tts_conf, 8 );

    while( !tts_rsp_chk( ISC_TTS_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_config( uint8_t voice_type,
                     bool epson_parse,
                     TTSV_t language,
                     uint16_t speaking_rate )
{
    TTSCONF_t tts_conf;
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    if ( voice_type > 8 )
        return 0xFFFF;

    if ( language > 4 )
        return 0xFFFF;

    if ( speaking_rate < 0x004B || speaking_rate > 0x0258 )
        return 0xFFFF;

    tts_conf.voice = voice_type;
    tts_conf.ep = epson_parse;
    tts_conf.lang = language;
    tts_conf.sr_wpm_lsb = ( speaking_rate & 0x00FF );
    tts_conf.sr_wpm_msb = ( speaking_rate & 0xFF00 ) >> 8;

    tts_parse_req( ISC_TTS_CONFIG_REQ, ( uint8_t* )&tts_conf, 8 );

    while( !tts_rsp_chk( ISC_TTS_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_speak( char *word )
{
    bool tmp_f              = false;
    char *wptr              = word;
    uint8_t raw_resp[ 2 ]   = { 0 };
    uint16_t tmp_resp       = 0;
    uint32_t wlen           = strlen( wptr );

    tts_parse_speak_req( ISC_TTS_SPEAK_REQ, _flush_enable, wptr, wlen );

    _tts_rdy_f = 0;
    _tts_fin_f = 0;

    while( !( tmp_f && _tts_rdy_f ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;

        if( tts_rsp_chk( ISC_TTS_SPEAK_RESP ) )
        {
            tts_rsp_data( raw_resp );

            tmp_resp |= raw_resp[ 0 ];
            tmp_resp |= raw_resp[ 1 ] << 8;
            tmp_f = true;
        }
    }

    return tmp_resp;
}

uint16_t tts_pause( void )
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_pause[ 2 ] = { 1, 0 };

    tts_parse_req( ISC_TTS_PAUSE_REQ, tmp_pause, 2 );

    while( !tts_rsp_chk( ISC_TTS_PAUSE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_unpause( void )
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_pause[ 2 ] = { 0 };

    tts_parse_req( ISC_TTS_PAUSE_REQ, tmp_pause, 2 );

    while( !tts_rsp_chk( ISC_TTS_PAUSE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_stop( bool reset )
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_reset[ 2 ] = { 0 };

    if( reset )
        tmp_reset[ 0 ] = 0x01;

    tts_parse_req( ISC_TTS_STOP_REQ, tmp_reset, 2 );

    while( !tts_rsp_chk( ISC_TTS_STOP_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_user_dict( bool erase,
                        uint8_t *udict_data,
                        uint16_t count )
{
    uint16_t cnt = 2;
    uint16_t tmp_rsp = 0;
    uint8_t rsp_data[ 2 ] = { 0 };
    uint8_t tmp_data[ BOOT_MESSAGE_MAX ] = { 0 };

    if ( erase )
        tmp_data[ 0 ] = 1;

    while ( count-- )
        tmp_data[ cnt ++ ] = *( udict_data++ );

    tts_parse_req( ISC_TTS_UDICT_DATA_REQ, tmp_data, count + 2 );

    while( !tts_rsp_chk( ISC_TTS_UDICT_DATA_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( rsp_data );

    tmp_rsp |= rsp_data[ 0 ];
    tmp_rsp |= rsp_data[ 1 ] << 8;

    return tmp_rsp;
}

uint16_t tts_codec_configure()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_codec[ 32 ] = { 0 };

    tmp_codec[ 0 ] = 0x01;
    tmp_codec[ 1 ] = 0x01;
    tmp_codec[ 24 ] = 0x02;

    tts_parse_req( ISC_SPCODEC_CONFIG_REQ , tmp_codec, 32 );

    while( !tts_rsp_chk( ISC_SPCODEC_CONFIG_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_codec_start( uint8_t *codec_data,
                          uint16_t count )
{
    bool tmp_f = false;
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    if ( count != 512 || count != 1024 || count != 2048 )
        return 0xFFFF;

    while( !_spc_fin_f )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_parse_req( ISC_SPCODEC_START_REQ , codec_data, count );

    _spc_rdy_f = 0;
    _spc_fin_f = 0;

    while( !( tmp_f && _spc_rdy_f ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;

        if( tts_rsp_chk( ISC_TTS_SPEAK_RESP ) )
        {
            tts_rsp_data( raw_resp );

            tmp_resp |= raw_resp[ 0 ];
            tmp_resp |= raw_resp[ 1 ] << 8;
            tmp_f = true;
        }
    }

    return tmp_resp;
}

uint16_t tts_codec_pause()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_data[ 2 ] = { 1, 0 };

    tts_parse_req( ISC_SPCODEC_PAUSE_REQ , tmp_data, 2 );

    while( !tts_rsp_chk( ISC_SPCODEC_PAUSE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_codec_unpause()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_data[ 2 ] = { 0 };

    tts_parse_req( ISC_SPCODEC_PAUSE_REQ , tmp_data, 2 );

    while( !tts_rsp_chk( ISC_SPCODEC_PAUSE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}

uint16_t tts_codec_stop( bool reset )
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
    uint8_t tmp_data[ 2 ] = { 0 };

    if( reset )
        tmp_data[ 0 ] = 1;

    tts_parse_req( ISC_SPCODEC_STOP_REQ, tmp_data, 2 );

    while( !tts_rsp_chk( ISC_SPCODEC_PAUSE_RESP ) )
    {
        tts_get_resp();

        if( _parse_ind() )
            return _err_code;
    }

    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return tmp_resp;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* text_to_speech_hw.c
* -------------------------------------------------------------------------------------------------
* Overview: Mainly read and write functions with parsing
**************************************************************************************************/

void _read_rsp()
{
    uint8_t tmp_byte = 0;
    uint16_t tmp_len = 0;

    tts_hal_cs_low();
    
    tts_hal_read( &tmp_byte, 1 );

    if( tmp_byte == START_MESSAGE )
    {
        tts_hal_read( ( uint8_t* )&_last_rsp, 2 );
        tmp_len |= _last_rsp.len[ 0 ];
        tmp_len |= _last_rsp.len[ 1 ] << 8;
        tts_hal_read( ( uint8_t* )&_last_rsp + 2, tmp_len );
        
    } else {

        wait( 0.005 );
    }
   
    tts_hal_cs_high();
}

void _write_req(bool boot)
{
    uint16_t cnt = 0;
    uint8_t start = START_MESSAGE;

    cnt |= _last_req.len[ 0 ];
    cnt |= _last_req.len[ 1 ] << 8;
    
    tts_hal_cs_low();
    
    
    if(boot) // use for debug - displays the current buffer on serial
    {
        tts_hal_write( &start, 1, true );
        tts_hal_write( ( uint8_t* )&_last_req, cnt, true);
    }
    else
    {
        tts_hal_write( &start, 1, false );
        tts_hal_write( ( uint8_t* ) &_last_req, cnt, false );
    }    
   
    tts_hal_cs_high();
    
}

/******************************************************************************
* Public Function Definitions
*******************************************************************************/
void tts_hw_init( void )
{
    tts_hal_init();

    _ticker_f = false;
    _ticker = 0;

    _last_req.idx[ 0 ] = 0;
    _last_req.idx[ 1 ] = 0;
    _last_req.len[ 0 ] = 0;
    _last_req.len[ 1 ] = 0;
    memset( _last_req.payload, 0, MAIN_MESSAGE_MAX );

    _last_rsp.idx[ 0 ] = 255;
    _last_rsp.idx[ 1 ] = 255;
    _last_rsp.len[ 0 ] = 0;
    _last_rsp.len[ 1 ] = 0;
    memset( _last_rsp.payload, 0, RESP_MESSAGE_MAX );
}

void tts_tick_isr()
{
    _ticker++;

    if( _ticker > 500 )
        _ticker_f == true;
}

void tts_mute_cmd( bool cmd )
{
    if( cmd )
        tts_hal_mut_high();
    else
        tts_hal_mut_low();
}

void tts_parse_req( uint16_t req,
                    uint8_t *payload,
                    uint16_t pl_len )
{
    uint8_t *pl = payload;
    uint16_t i = 0;
    uint16_t tmp = pl_len + 4;
     
    _last_req.len[ 0 ] = tmp & 0x00FF;
    _last_req.len[ 1 ] = ( tmp & 0xFF00 ) >> 8;
    _last_req.idx[ 0 ] = req & 0x00FF;
    _last_req.idx[ 1 ] = ( req & 0xFF00 ) >> 8;
    _last_rsp.idx[ 0 ] = 0xFF;
    _last_rsp.idx[ 1 ] = 0xFF;
    
    if ( payload != NULL )
    {
        while ( pl_len-- )
            _last_req.payload[ i++ ] = *( pl++ );
           
    }
    
    _write_req(true);
}

void tts_parse_boot_img( const uint8_t *payload,
                         uint16_t pl_len )
{
    uint16_t i = 0;
    uint16_t tmp = pl_len + 0x04;
    
    _last_req.len[ 0 ] = tmp & 0x00FF;
    _last_req.len[ 1 ] = ( tmp & 0xFF00 ) >> 8;
    _last_req.idx[ 0 ] = 0x00;
    _last_req.idx[ 1 ] = 0x10;
    _last_rsp.idx[ 0 ] = 0xFF;
    _last_rsp.idx[ 1 ] = 0xFF;

    if ( payload != NULL )
    {
        while ( pl_len-- )
            _last_req.payload[ i++ ] = payload[ i ];  
    }

    _write_req(true);
}

void tts_parse_speak_req( uint16_t req,
                          uint8_t flush_en,
                          char *word,
                          uint16_t word_len )
{
    char *ptr = word;
    uint16_t i = 1;
    uint16_t tmp = word_len;

    word_len += 7;

    _last_req.len[ 0 ] = word_len & 0x00FF;
    _last_req.len[ 1 ] = ( word_len & 0xFF00 ) >> 8;
    _last_req.idx[ 0 ] = req & 0x00FF;
    _last_req.idx[ 1 ] = ( req & 0xFF00 ) >> 8;
    _last_rsp.idx[ 0 ] = 0xFF;
    _last_rsp.idx[ 1 ] = 0xFF;

    if( flush_en )
    {
        _last_req.payload[ 0 ] = 1;

    } else {

        _last_req.payload[ 0 ] = 0;
    }

    while( tmp-- )
        _last_req.payload[ i++ ] = *( ptr++ );

    _last_req.payload[ i++ ] = 0x20;
    _last_req.payload[ i ] =   0x00;

    _write_req(true);
}

void tts_get_resp()
{
    // only read if ready
    if( tts_hal_msg_rdy() )
    {
        _read_rsp();
    }
}

bool tts_rsp_chk( uint16_t idx )
{
    uint16_t tmp = 0;

    tmp |= _last_rsp.idx[ 0 ];
    tmp |= _last_rsp.idx[ 1 ] << 8;
 
   return ( idx == tmp ) ? true : false;
}

uint16_t tts_rsp_idx()
{
    uint16_t tmp = 0;

    tmp |= _last_rsp.idx[ 0 ];
    tmp |= _last_rsp.idx[ 1 ] << 8;

    return tmp;
}

void tts_rsp_data( uint8_t *buffer )
{
    uint8_t *bfr = buffer;
    uint16_t cnt = 0;
    uint8_t *ptr = _last_rsp.payload;

    cnt |= _last_rsp.len[ 0 ];
    cnt |= _last_rsp.len[ 1 ] << 8;
    cnt -= 4;

    while( cnt-- )
        *( bfr++ ) = *( ptr++ );
}

/*************** END OF FUNCTIONS *********************************************/
Main.cpp for quick refC/C++
Main.cpp for quick reference, for the latest versions always check the repo
/****************************************************************************
* Title                 :   iBreathe Breathalyzer
* Filename              :   breathalyzer
* Author                :   Dave Clarke
* Origin Date           :   27/09/2016
* Notes                 :   Breathalyzer utilizing Hexiware, Alcohol click and Wolksense
*****************************************************************************/
/**************************CHANGE LIST **************************************
*
*    Date    Software Version    Initials       Description
*  27/09/16       1.0.0            DC        Interface Created.
*
*****************************************************************************/

/**
 * @page TEST_CFG Test Configurations
 * <h3> Test configuration : </h3>
 * @par
 * <ul>
 * <li><b> MCU           </b> :      MK64FN1M0XXX12               </li>
 * <li><b> Dev. Board    </b> :      HEXIWEAR                     </li>
 * <li><b> Oscillator    </b> :      12 MHz external              </li>
 * <li><b> Ext. Modules  </b> :      Alcohol Click on mikroBUS 1  </li>
 * <li><b> SW            </b> :      mBed OS5    </li>
 * </ul>
 */

/**
 * @mainpage
 * <h3> Breathalyser created with HEXIWEAR and mBed OS 5 </h3>
 * @par This will show you how much you've drunk and tell you with an Emoticon if
 * you're too hammered to even consider driving. Using the Hexiware app the readings
 * are transmitted to the cloud via bluetooth. Is it time to give up drinking yet?!
 
 * <h3> Alcohol Features </h3>
 * @par Alcohol click, Hexiwear docking station, Hexiware
 */

/******************************************************************************
* Includes
*******************************************************************************/

#include "mbed.h"
#include "Hexi_KW40Z.h"
#include "Hexi_OLED_SSD1351.h"
#include "OLED_types.h"
#include "OpenSans_Font.h"
#include "string.h"
#include "iBreatheImages.h"

/******************************************************************************
* Module Variable Definitions
*******************************************************************************/

#define LED_ON      0
#define LED_OFF     1

                          // Pointers to:  
const uint8_t   *welcome, // Welcome screen image
                *blank,   // blank image
                *blow,    // Start Blowing Image
                *drink,   // You've been drinking image
                *drive,   // Don't drive image
                *hang,    // You'll have a hangover image
                *ini,     // Initialising image
                *sober;   // Sober as a judge image

const float     Vadc_3V3  = 0.00005035;    // 16-Bit ADC step 3V3/65535   = 0.05035 mV
       
float           Vrl = 0,                   // Output voltage
                ambientAlc = 0,            // Abmient Output voltage from sensor
                SensorRes = 0,             // SensorRes (Ohm) - Sensor resistance
                SensorRes1 = 0,            // SensorRes (Ohm) - Sensor resistance
                ppm = 0,                   // ppm
                ppm_1 = 0,                 // Ambient ppm variable  
                ratio = 0;                 // SensorRes/LoadRes ratio
              
unsigned short  adc_rd = 0; //Initialise anologue read variable

const uint8_t   ppmText[] = "ppm:"; // text for PPM label

char            text[20],           // Text array variables
                text2[20],
                text3[20];

float           value[20],  // initial sensor set up values
                value1[20]; // initial sensor set up values

bool            isFirstBoot = true;                   
                

/******************************************************************************
* Function Prototypes
*******************************************************************************/

void sysinit(void);
void ReadSensor();
void CalculatePPM(int times, bool amb);
void ambient(int times);
void StartHaptic(void);
void StopHaptic(void const *n);
void ButtonUp(void);
void txTask(void);

/******************************************************************************
* Instance setups
*******************************************************************************/
/* Define timer for haptic feedback */
RtosTimer hapticTimer(StopHaptic, osTimerOnce);

//set up Analog read pin for alcohol sensor
AnalogIn Alcohol(PTB2);

/* Instantiate the SSD1351 OLED Driver */ 
SSD1351 oled(PTB22,PTB21,PTC13,PTB20,PTE6, PTD15); /* (MOSI,SCLK,POWER,CS,RST,DC) */
      
/* Get OLED Class Default Text Properties */
oled_text_properties_t textProperties = {0};

/* Instantiate the Hexi KW40Z Driver (UART TX, UART RX) */ 
KW40Z kw40z_device(PTE24, PTE25);

/* LED and Haptic Set ups */
DigitalOut redLed(LED1);
DigitalOut greenLed(LED2);
DigitalOut blueLed(LED3);
DigitalOut haptic(PTB9);

/******************************************************************************
* Bluetooth button functions and passkey function
*******************************************************************************/

void ButtonRight(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void ButtonLeft(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void PassKey(void)
{
    StartHaptic();
    strcpy((char *) text,"PAIR CODE");
    oled.TextBox((uint8_t *)text,0,25,95,18);
  
    /* Display Bond Pass Key in a 95px by 18px textbox at x=0,y=40 */
    sprintf(text3,"%d", kw40z_device.GetPassKey());
    oled.TextBox((uint8_t *)text3,0,40,95,18);
}

/******************************************************************************
* Main
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
     /* Set pointers to the BMPs stored in  iBreatheImages.c */
     welcome = iBreatheWS_bmp;    // Welcome screen image 
     blank = iBreatheBlank_bmp;   // blank image
     blow = iBreatheBlow_bmp;    // Start Blowing Image
     drink = iBreatheDrink_bmp;   // You've been drinking image
     drive = iBreatheDrive_bmp;   // Don't drive image
     hang = iBreatheHang_bmp;    // You'll have a hangover image
     ini = iBreatheini_bmp;     // Initialising image
     sober = iBreatheSober_bmp;   // Sober as a judge image
       
     /* Set initial Values */
     sysinit();
}

/******************************************************************************
* Public Function Definitions
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function readSensor(void)
* -------------------------------------------------------------------------------------------------
* Overview: Read sensor
* Input: None
* Output: None
**************************************************************************************************/

void ReadSensor()
{
    /* Read 16 Bit Analog value */
    adc_rd = Alcohol.read_u16();

    // pause 200ms 
    Thread::wait(200);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function CalculatePPM(Int times, bool amb)
* -------------------------------------------------------------------------------------------------
* Overview: Calculation of PPM
* Input: times = Number of samples to take, amb sets either ambient reading or test reading (true for test)
* Output: None
**************************************************************************************************/

void CalculatePPM(int times, bool amb)
{
        float   lgPPM;
        
        /* Read values x times */
        for(int x = 0; x < times; x++)
        {
            ReadSensor();    
            value[x] = ((float)adc_rd  * Vadc_3V3);
            StartHaptic();
            Thread::wait(50);
        }    
        
        /* Calculate the average value for accuratcy */
        for(int y = 0; y < times; y++)
        {
            Vrl += value[y];
        }
        Vrl = Vrl / times;       

        /* Set SensorRes reference value */
        SensorRes =    (Vrl / 3.3);
        
        /* Set ratio */
        ratio     = SensorRes1 / SensorRes;

        /* Convert to PPM */
        lgPPM = ( log10( ratio ) * -1.5512 ) + 2.5911;
        
        /* If true create test result, flase creates reference result */
        if (amb == true)
        {
            ppm = pow( 10, lgPPM );
        }
        else
        {
            ppm_1 = pow( 10, lgPPM );
        }    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ambient(int times)
* -------------------------------------------------------------------------------------------------
* Overview: Reading of the Ambient Voltgage of the sensor for reference
* Input: times = Number of samples to take
* Output: None
**************************************************************************************************/

void ambient(int times)
{
    /* Read ambient values x times flashing green led*/
    for(int x = 0; x < times; x++)
    {
        redLed      = LED_OFF;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        ReadSensor();
        value1[x] = (float)adc_rd  * Vadc_3V3; 
        
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        Thread::wait(48);   
    }
    
    /* Calculate the average value for accuratcy */
    for(int y = 0; y < times; y++)
    {
        ambientAlc+=value1[y];            
    }
    ambientAlc = ambientAlc / times;
  
    /* Set SensorRes1 reference value */
    SensorRes1 =   (ambientAlc / 3.3);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StartHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Start Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StartHaptic(void)
{
    hapticTimer.start(50);
    haptic = 1;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StopHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Stop Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StopHaptic(void const *n) {
    haptic = 0;
    hapticTimer.stop();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ButtonUp(void)
* -------------------------------------------------------------------------------------------------
* Overview: Function called whe up button pressed, Begins Breathilyzer testing procedure
* Input:  None
* Output: None
**************************************************************************************************/

void ButtonUp(void)
{
    StartHaptic();
    
    bool Ref = false;
    bool Test = true;
        
    /* LED set to green for test beginning*/
    redLed      = LED_OFF;
    greenLed    = LED_ON;
    blueLed     = LED_OFF;
    
    /* Fill 96px by 96px Screen with 96px by 96px Initialising Image starting at x=0,y=0 */   
    oled.DrawImage(ini,0,0);
    
    /* first boot bug work around to stop junk values */
    if (isFirstBoot == true)
    {
        /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
        ambient(1);
        CalculatePPM(1,Ref);
        isFirstBoot = false;
    }
    
    /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
    ambient(10);
    CalculatePPM(10,Ref);
    
    
    /* Fill 96px by 96px Screen with 96px by 96px Blowing Image starting at x=0,y=0 */  
    oled.DrawImage(blow,0,0);
    
    /*read breathe alcohol levels with 10 samples and set flag to show it's breathilyzer test figure*/
    CalculatePPM(10,Test);
    
    /*Calculate the difference in Alcohol level based on Ambient and test sample*/
    ppm = ppm - ppm_1;
    
    /*Throw away any values less than 0*/
    if (ppm < 0)
    {
         ppm = 0;
    }     
    
    /* Fill 96px by 96px Screen with 96px by 96px Blank Background Image starting at x=0,y=0 */
    oled.DrawImage(blank,0,0);     
    
    /* Show Calculated alcohol level in PPM and send data via bluetooth to the cloud */
    oled.SetTextProperties(&textProperties);
    oled.Label(ppmText,20,36);
    sprintf(text,"%.2f",ppm);
    oled.Label((uint8_t *)text,50,36);
    Thread::wait(1000);
    
    /* Currently sending to the Pressure variable as a temp place holder */
    kw40z_device.SendiBreathe(ppm); // this is used for custom Hexiware app
    kw40z_device.SendPressure(ppm * 10); // using this to record on Wolksense Cloud
    
    
    /* You've got a Hangover coming!*/
    if ( ppm > 200)
    {
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        StartHaptic();
        
        /* Fill 96px by 96px Screen with 96px by 96px Hangover Image starting at x=0,y=0 */
        oled.DrawImage(hang,0,0);
    }   
    
    /* You Shouldn't drive */
    else if (ppm < 200 && ppm > 150)
    {
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_ON;
        
        StartHaptic();
      
        /* Fill 96px by 96px Screen with 96px by 96px Don't Drive Image starting at x=0,y=0 */      
        oled.DrawImage(drive,0,0);
    } 
    
    /* You've had a drink */
    else if (ppm < 150 && ppm > 50)
    {
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_ON;
        
        StartHaptic();
    
        /* Fill 96px by 96px Screen with 96px by 96px Had a drink Image starting at x=0,y=0 */     
        oled.DrawImage(drink,0,0);
    }
    
    /* Sober as a judge*/   
    else
    {
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        
        StartHaptic();
        oled.DrawImage(sober,0,0);    
    }
    Thread::wait(5000);
    
    /* Go back to start screen */
    sysinit();   
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void sysint(void)
* -------------------------------------------------------------------------------------------------
* Overview: System Initial Values Set up
* Input: None
* Output: None
**************************************************************************************************/
void sysinit(void)
{
    /* Set LED to Blue by default*/      
    redLed      = LED_OFF;
    greenLed    = LED_OFF;
    blueLed     = LED_ON;
    
    /* Turn on the backlight of the OLED Display */
    oled.DimScreenON();
    
    /* Fill 96px by 96px Screen with 96px by 96px Welcome Image starting at x=0,y=0 */
    oled.DrawImage(welcome,0,0);
    
    /* Register callbacks to application functions */
    kw40z_device.attach_buttonUp(&ButtonUp);
    kw40z_device.attach_buttonLeft(&ButtonLeft);
    kw40z_device.attach_buttonRight(&ButtonRight);
    kw40z_device.attach_passkey(&PassKey);
    
    /* Send sensor data to bluetooth */
    kw40z_device.SendSetApplicationMode(GUI_CURRENT_APP_SENSOR_TAG);
    
    /* Change font color to White */
    oled.GetTextProperties(&textProperties); 
    textProperties.fontColor   = COLOR_WHITE;
    oled.SetTextProperties(&textProperties);
}
Talking iBreathe Breathalyzer
Using the Text To Speech click board to add a voice to the iBreathalyzer
iBreathe Breathalyzer
This is a copy from my mBed repo
iBreathe Breathalyzer Hexiware iOS App
Modified Hexiware app code to work with iBreathe Breathalyzer

Credits

Me autodesk fghsyejwbd
Dave Clarke

Industrial Designer, Maker of Things, Lover of Phi and all around nice guy

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

DooUcoffee machine
Advanced
  • 573
  • 58

Work in progress

Internet of things in the world of coffee with Udoo - coffee machine 3.0 entirely managed by µController.

DooUcoffee machine

Team DooUcoffee

IoT Santa Tracker on Colorful World Map
Advanced
  • 415
  • 2

Full instructions

Use MKR1000 to show the Santa location in real-time on a colorful world map made of neopixels.

Velo Bling-Bling
Advanced
  • 6,415
  • 144

Full instructions

LED lights for bicycle wheels. 16 multi color LEDs on both PCB sides. Displays images and dynamic info like speed. BLE and USB interface.

Snowmelt System Monitor
Advanced
  • 1,596
  • 30

My town of Holland, Michigan has a large snowmelt system. We need a way to monitor its performance over time. IoT to the rescue!

FRDM-K82F Camera Based Parking Assistant
Advanced
  • 2,004
  • 33

Full instructions

Use Computer Vision to make a parking assistant that allows you to consistently park a car into your garage with perfect alignment.

Directional Motion-Detecting USB Web Cam Using a FRDM-K82F
Advanced
  • 2,241
  • 37

Full instructions

Use simple image processing and an image sensor to detect whether a person enters or leaves a room.

ProjectsCommunitiesContestsLiveJobsBetaFree StoreBlogAdd projectSign up / Login
Respect project