Ish Ot Jr.
Published © MIT

Hexy, A Virtual Pet That Spurs Good Decisions!

Hexy monitors your activity throughout the day, reacting with exuberance to movement, and falling asleep or even angering during inactivity.

IntermediateFull instructions provided1 hour1,408

Things used in this project

Hardware components

Hexiwear
NXP Hexiwear
×1

Software apps and online services

Arm mbed OS and Compiler

Story

Read more

Schematics

Hexy Block Diagram

based on an image from http://www.hexiwear.com/hardware/
File missing, please reupload.

Code

main.cpp

C/C++
Main source code file
/*
 * Hexy, a virtual pet that encourages good decisions! < ^.^ >
 *
 * @IShJR
 * http://ishotjr.com/
 * http://ishotjr.github.io/
 */

#include "mbed.h"
#include "Hexi_OLED_SSD1351.h"  // OLED display driver
#include "FXAS21002.h"          // Gyroscope sensor
#include "OpenSans_Font.h"      // allow font choice
#include "string.h"


#define INACTIVITY_THRESHOLD 60 // seconds

// vibration
DigitalOut vibe(PTB9);

// timer
Timer timer;

// timer value at last "movement" (seconds)
float last_movement = 0;

// SSD1351 OLED driver and text properties
SSD1351 oled(PTB22,PTB21,PTC13,PTB20,PTE6, PTD15); /* (MOSI,SCLK,POWER,CS,RST,DC) */
oled_text_properties_t textProperties = {0};


// update Hexy and debug numbers
void refresh_status(char* hexy_text, char* timer_text, char* movement_text,
                    char* last_movement_text, Color_t hexy_color) {
    char text[20]; // text buffer

    // change font color to green plus use larger font for Hexy
    textProperties.fontColor   = hexy_color;
    textProperties.font        = OpenSans_12x18_Regular;
    textProperties.alignParam   = OLED_TEXT_ALIGN_CENTER;
    //textProperties.background = NULL;
    oled.SetTextProperties(&textProperties);

    // display Hexy at center (actually, offset a fair bit now to make room for debug)
    strcpy(text,hexy_text);
    oled.TextBox((uint8_t *)text,(96-64)/2,(96-18-24)/2,64,18);
        
    // set text properties to grey/regular size for the timer and movement
    textProperties.fontColor = COLOR_GRAY;
    textProperties.font        = OpenSans_10x15_Regular;
    oled.SetTextProperties(&textProperties); 
    
    strcpy(text,movement_text);
    // display RMS in 64px by 16px textbox above last movement
    oled.TextBox((uint8_t *)text,(96-64)/2,96-16-16-16,64,16);  

    strcpy(text,last_movement_text);
    // display last movement time in 64px by 16px textbox above timer
    oled.TextBox((uint8_t *)text,(96-64)/2,96-16-16,64,16);       
            
    strcpy(text,timer_text);
    // display time in 64px by 16px textbox at bottom center
    oled.TextBox((uint8_t *)text,(96-64)/2,96-16,64,16);       
}

// generic "animation" (toggles frame*_text for duration_ms)
void animate(int duration_ms, char* frame1_text, char* frame2_text, 
             bool is_loading, float gyro_rms, bool do_vibrate) {
                    
    char text[20], gyro_rms_text[20], last_movement_text[20]; // text buffers
    Color_t hexy_color;

    for (int i = 0; i < duration_ms; i = i + 500) {    

        // TODO: refactor - lots of duplication here!

        // TODO: note that this means rms is only updated once per duration!
        // (seems to work really well regardless though?!)
        
        // turn red if beyond inactivity threshold
        if ((timer.read() - last_movement) > INACTIVITY_THRESHOLD) {
            hexy_color = COLOR_RED;
        } else {
            hexy_color = COLOR_GREEN;
        }

        if (gyro_rms > 0) {
            sprintf(gyro_rms_text,"%.2f",gyro_rms);            
        } else {
            // placeholder if 0 or negative
            strcpy(gyro_rms_text,"-");    
        }
        
        if (last_movement > 0) {
            sprintf(last_movement_text,"%.2f",last_movement);            
        } else {
            // placeholder if 0 or negative
            strcpy(last_movement_text,"-");    
        }
        
        if (is_loading) {
            strcpy(text,"Loading.. ");    
        } else {
            // format the timer reading
            sprintf(text,"%.2f",timer.read());            
        }

        refresh_status(frame1_text, text, gyro_rms_text, 
                       last_movement_text, hexy_color);
        Thread::wait(250);

        // turn red if beyond inactivity threshold
        if ((timer.read() - last_movement) > INACTIVITY_THRESHOLD) {
            hexy_color = COLOR_RED;
        } else {
            hexy_color = COLOR_GREEN;
        }
        
        // vibrate during second frame if set (not both, i.e. pulse vs. continual)
        if (do_vibrate && ((timer.read() - last_movement) > INACTIVITY_THRESHOLD)) {
            vibe = 1;
        }
        
        if (last_movement > 0) {
            sprintf(last_movement_text,"%.2f",last_movement);            
        } else {
            // placeholder if 0 or negative
            strcpy(last_movement_text,"-");    
        }
        
        if (is_loading) {
            strcpy(text,"Loading...");    
        } else {
            sprintf(text,"%.2f",timer.read());            
        }
        refresh_status(frame2_text, text, gyro_rms_text, 
                       last_movement_text, hexy_color);
        Thread::wait(250);
        vibe = 0; // stop vibration (whether set or not)
    }    
}

// sleepy animation (+vibration)
void hexy_sleep(int duration_ms, bool is_loading, float gyro_rms) {
    animate(duration_ms, "< -_- > ..z", "< -o- > .zZ", is_loading, gyro_rms, true);
}

// happy animation
void hexy_happy(int duration_ms, bool is_loading, float gyro_rms) {
    animate(duration_ms, "< o_o >", "< ^.^ >", is_loading, gyro_rms, false);
}

// select animation based on movement
void hexy_update(int duration_ms, bool is_loading, float gyro_rms) {
    if (gyro_rms > 10) {
        // TODO: ^^^ adjust/smooth?
        // (seems to work amazingly well without though...?)
        
        // yay, movement!
        hexy_happy(500, false, gyro_rms);

        // update last movement
        last_movement = timer.read();
    } else {
        // boo, no movement!
        hexy_sleep(500, false, gyro_rms);        
    }
}

// setup and loop
int main() {
    char text[20]; // text buffer
    // TODO: do something about overflow after half an hour, e.g. switch to use RTC?

    // FXAS21002 pin connections for Hexiwear
    FXAS21002 gyro(PTC11,PTC10);
    // storage for the data from the gyro
    float gyro_data[3];  
    float gyro_rms=0.0;

    gyro.gyro_config();
        
    oled.GetTextProperties(&textProperties);    

     // center align
    textProperties.alignParam   = OLED_TEXT_ALIGN_CENTER;

    // turn on the OLED backlight
    oled.DimScreenON();
    
    // fills the screen with solid black
    oled.FillScreen(COLOR_BLACK);

    // display app title at top center
    strcpy((char *) text,"Hexy");
    oled.TextBox((uint8_t *)text,(96-48)/2,4,48,16);
    
    // give the user a chance to get acclimated
    hexy_happy(2000, true, -1);
    
    // start timer
    timer.start();
    last_movement = timer.read();

    while (true) {
        // poll gyro and update Hexy forever!
        gyro.acquire_gyro_data_dps(gyro_data);
        gyro_rms = sqrt(((gyro_data[0]*gyro_data[0])+(gyro_data[1]*gyro_data[1])+(gyro_data[2]*gyro_data[2]))/3);
        hexy_update(500, false, gyro_rms);
    }
}


    
    

hexy-mvp Mercurial repository

Import into the mbed Compiler or CLI to build!

Credits

Ish Ot Jr.

Ish Ot Jr.

21 projects • 173 followers

Comments