Megan Rose Bryant
Published © GPL3+

Wally Flow - Mindfulness with Walabot and Alexa

Wally Flow replaces bulky and uncomfortable wearable mindfulness trackers by seamlessly integrating mindfulness into the workspace.

IntermediateFull instructions provided6 hours1,470

Things used in this project

Hardware components

Walabot Developer Pack
Walabot Developer Pack
×1
Echo Dot
Amazon Alexa Echo Dot
×1
Logitech K400R Wireless Touch Keyboard
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
LED Strip 5v White
×1
Jumper wires (generic)
Jumper wires (generic)
×1
TP Link HS105 Smart Plug
×1
Adafruit SmartiPi Case
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Walabot Stand

The file used is called pistand.stl . It was published by adafruit as a stand for the OctoPrint Raspberry Pi Rig 3.5" PiTFT Touch Display, but I found it works great for Walabot.

Schematics

Wally Flow LED Schematics

Wally Flow Jumper Position

For this project, Walabot needs an external power supply. Maker sure the jumper on the back of the board is in the left most position.

Code

Walabot Mindfulness Program

Python
This program is run on the local machine
#!/ust/bin/python
# WallyFlow Mindfullnes App
# Python 3.5.3
# Raspberry Pi 3 B

from os import system
from imp import load_source

import RPi.GPIO as GPIO
import math
import time
import MySQLdb
import warnings


#Walabot Parameters
#######################################
# Walabot_SetArenaR - input parameters 
minInCm, maxInCm, resInCm = 10, 150, 5

# Walabot_SetArenaTheta - input parameters
minInDeg, maxInDeg, resInDeg = -45, 45, 5

# Walabot_SetArenaPhi - input parameters
minPhiInDeg, maxPhiInDeg, resPhiInDeg = -90, 90, 10  

#Threshold
threshold = 100


#Mindfullness Parameters
#######################################
#Maximum Stillness Time (in seconds)
maxStillTime = 60 * 10

#Initialize Last Average Position to 0
lastPos = 0

#Minimum Average Change needed to update LAP
minDelta = 50

#Initialize Last Move Time to current time
lastMoveTime = time.time()

#Initialize tEnd to lastMoveTime plus maxStillTime
tEnd = lastMoveTime + maxStillTime

#Initialize Starting Time
startTime = time.time()

#Initialize Movement Count
moveCount = 0

#Configure Raspberry Pi GPIO for LED Strip
#######################################

#Suppress Pin Warnings
with warnings.catch_warnings():
    warnings.simplefilter('ignore')
    #Set GPIO Mode to Broadcom SOC Channel (A numberins scheme)
    GPIO.setmode(GPIO.BCM)

    #Set GPIO Pin 4 to output 
    GPIO.setup(4, GPIO.OUT)

    #Set GPIO Pin 4 Starting at 0 or 'Off'
    GPIO.output(4,0)


#Set up Connection to MySQL db
#######################################

db = MySQLdb.connect(user="wallyflow",
                     passwd="wallyflow",
                     host="localhost",
                     db="wallyflowdb")
cursor = db.cursor()

#Create Class for Initializing and controling Walabot
#######################################

class Walabot:
    def __init__(self):
        #Instead of importing Walabot API, load the API directly
        self.wlbt = load_source('WalabotAPI',
                                '/usr/share/walabot/python/WalabotAPI.py')
        #Initialize the Walabot
        self.wlbt.Init()

    #Connection Function
    def connect(self):
        print("Connecting...")
        while True:
            try:
                self.wlbt.ConnectAny()
                print("Walabot Connected")
                return
            except self.wlbt.WalabotError as err:
                #If there is an error, wait 1 second and retry
                print("Connecting...")
                time.sleep(1)

    #Configure Walabot    
    def configure(self):
        print("Configuring...")
        time.sleep(1)

        # Set Profile to PROF_SENSOR_NARROW For lower resolution
        # images but faster capture rate
        self.wlbt.SetProfile(self.wlbt.PROF_SENSOR_NARROW)
        
        #Set Arena Values Using Previously Declared Variables
        self.wlbt.SetArenaR(minInCm, maxInCm, resInCm)
        self.wlbt.SetArenaTheta(minInDeg, maxInDeg, resInDeg)
        self.wlbt.SetArenaPhi(minPhiInDeg, maxPhiInDeg, resPhiInDeg)

        # Wally Flow detects changes in movement, so no filter type
        # MTI would only track targets that move
        self.wlbt.SetDynamicImageFilter(self.wlbt.FILTER_TYPE_NONE)
        
        print("Walabot Configured")

    #Start Walabot
    def start(self):
        print("Starting...")
        time.sleep(1)
        
        while True:
            try:
                self.wlbt.Start()
                print("Walabot Started")
                return
            except self.wlbt.WalabotError as err:
                #Retry if error detected
                print("Starting...")
                time.sleep(1)
                
    # Calibration is optional in Wally Flow
    # Use calibration if Walabot is picking up too much background
    def calibrate(self):
        print("Calibrating...")
        self.wlbt.StartCalibration()
        
        #While the Walabot is still calibrating, trigger Walabot
        while self.wlbt.GetStatus()[0] == self.wlbt.STATUS_CALIBRATING:
            self.wlbt.Trigger()
        print("Calibrated")
        
    # Not used in main program, but useful for debugging    
    def getStatusString(self):
        status = self.wlbt.GetStatus()[0]
        if status == 0:
            return "STATUS_DISCONNECTED"
        elif status == 1:
            return "STATUS_CONNECTED"
        elif status == 2:
            return "STATUS_IDLE"
        elif status == 3:
            return "STATUS_SCANNING"
        elif status == 4:
            return "STATUS_CALIBRATING"

    #The 'Get Action' for Wally Flow is 'GetSensorTargets'
    def getTargets(self):
        self.wlbt.Trigger()
        return self.wlbt.GetSensorTargets()

    #Print out the target info, useful for debugging
    def printSensorTarget(self,target):
            print('Target:\nx: {}\ny: {}\nz: {}\namplitude: {}\n'.format(
                target.xPosCm, target.yPosCm, target.zPosCm, target.amplitude))

    #Stop and Disconnect Walabot at the end of each session to avoid errors
    def stopAndDisconnect(self):
        print("Stopping..")
        time.sleep(1)
        self.wlbt.Stop()
        self.wlbt.Disconnect()
        print("Disconnected")

   #Main WallyFlow Function     
    def WallyFlow(self):
        global lastPos, lastMoveTime, tEnd, moveCount
        
        targets = wlbt.getTargets()
        currPos = 0
        count = len(targets)        


        if count != 0:
            #Detect the 'main' target, i.e. the person sitting
            mainTarget = max(targets, key = lambda t: t[3])

            #Calculate the angle to determine the Main Target's current position
            angle = math.degrees(math.atan(mainTarget.yPosCm/mainTarget.zPosCm))
            currPos += abs(angle)
            #Current Delta is the difference between MT's current and last positions
            currDelta = abs(currPos - lastPos)

            #Check if current delta is greated than specified minimum
            if currDelta > minDelta:
                #Update Last Move Time to Current Time
                lastMoveTime = time.time()
                #End Time is the time when the MT should next move by
                tEnd = lastMoveTime + maxStillTime
                moveCount += 1
                lastPos = currPos

                #Suppress MySQL Print Statement Warnings
                with warnings.catch_warnings():
                    warnings.simplefilter('ignore')

                    #Convert to Readable Time Formats
                    currentTime = time.strftime("%D %H:%M",time.localtime(time.time()))
                    cleanlastMoveTime =  time.strftime("%H:%M",time.localtime(time.time()))

                    #Update Last Move Time
                    sql = "INSERT INTO lmtTable(LastMoveTime, TimeStamp) VALUES ('{}', '{}')".format(cleanlastMoveTime, currentTime)
                    cursor.execute(sql)

                    #Update Time End
                    clean_tEnd = time.strftime("%H:%M",time.localtime(tEnd))
                    sql = "INSERT INTO teTable(TimeEnd, TimeStamp) VALUES ('{}', '{}')".format(clean_tEnd, currentTime)
                    cursor.execute(sql)

                    
                    #Update Move Count
                    sql = "INSERT INTO mcTable(MoveCount, TimeStamp) VALUES ('{}', '{}')".format(moveCount, currentTime)
                    cursor.execute(sql)

                    #Update Still Time
                    stillTime = round((time.time() - startTime)/60,2)
                    sql = "INSERT INTO stillTable(StillTime, TimeStamp) VALUES ('{}', '{}')".format(stillTime, currentTime)
                    cursor.execute(sql)
                    db.commit()

                print("Moved. Delta: ", round(currDelta,2))
                
                return currDelta
            else:
                return 0
        else:
            return 0
        
#Main Function
if __name__ == '__main__':
    """
    This program is set to run at system boot and get
    customized values for MaxStillTime and MinDelta each time
    """
    mstMinutes = input("Set maximum stillness time (in minutes): ")
    maxStillTime = float(mstMinutes) * 60
    print("Maximum stillness time set to {} minutes".format(mstMinutes))

    minDelta = int(input("Set minimum delta (integer between 10 and 60): "))
    print("Minimum delta is set to {}".format(minDelta))

    # Walabot Initialization Cycle
    wlbt = Walabot()
    wlbt.connect()
    wlbt.configure()
    wlbt.start()

    print("Connecting to Wally Flow Database...")

    #Suppress printed warnings if databases already exists
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')

        #Mark the time stamp, which acts as a key
        timeStamp = time.strftime("%D %H:%M",time.localtime(time.time()))

        #Initialize the Wally Flow Database
        sql = "CREATE DATABASE IF NOT EXISTS wallyflowdb"
        cursor.execute(sql)

        #Initialize Start Time Table
        sql = "CREATE TABLE IF NOT EXISTS stTable(startTime TEXT, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)

        #Clean start time and Initialize Start Time Table        
        cleanstartTime = time.strftime("%H:%M:%S",time.localtime(startTime))
        sql = "INSERT INTO stTable(startTime, TimeStamp) VALUES ('{0}', '{1}')".format(cleanstartTime, timeStamp)
        cursor.execute(sql)

        #Initialize Max Still Time Table        
        sql = "CREATE TABLE IF NOT EXISTS mstTable( MaxStillTime TIME, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)
        sql = "INSERT INTO mstTable(maxStillTime, TimeStamp) VALUES ('{}', '{}')".format(maxStillTime, timeStamp)
        cursor.execute(sql)

        #Initialize Last Move Time Table      
        sql = "CREATE TABLE IF NOT EXISTS lmtTable( LastMoveTime TIME, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)

        #Initialize Time End Table
        sql = "CREATE TABLE IF NOT EXISTS teTable( TimeEnd TIME, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)

        #Initialize Move Count Table
        sql = "CREATE TABLE IF NOT EXISTS mcTable( MoveCount INT, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)

        #Initialize Still Table
        sql = "CREATE TABLE IF NOT EXISTS stillTable( StillTime INT, TimeStamp VARCHAR(255) )"
        cursor.execute(sql)
        
    print("Connected to Wally Flow Database.")
    time.sleep(1)  
    print("Starting WallyFlow. Be Mindful!")
    
    #UPDATE START TIME UPDATE MAX STILL TIME
    try:
        
        #Wally Flow runs continuously until cancelled or machine shutdown
        while True:
            
            wlbt.WallyFlow()
            if time.time() > tEnd:
                
                #Turn LED Strip On
                GPIO.output(4,1)
                
                #Wait 1 Second (Maximizes 'blinking light' effect)
                time.sleep(1)
                
                #Turn LED Strip Off
                GPIO.output(4,0)
                
                pass
            
    except KeyboardInterrupt:
        
        #Turn off LEDS on WallyFlow Cancellation
        GPIO.output(4,0)
        
        #MySQL Database Commit 
        db.commit()
        
        #Stop and Disconnect Walabot
        wlbt.stopAndDisconnect()

Wally Flow Lambda Function

JavaScript
This is the lambda function. If you are using it to create you own lambda, remember it requires the alexa-sdk, so you will need to create a deployment package in the form of a zip file.
const Alexa = require('alexa-sdk');
const AWS = require('aws-sdk');
const APP_ID = 'amzn1.ask.skill.1a61b436-2b39-448c-a2bb-1ad391fe666b';
const handlers = {

  "AboutMindfulnessIntent": function () {
    var speechOutput = "Mindfulness is the quality or state of being concious or aware of something. ";
    this.emit(':tell', speechOutput);
  },

  "AboutWallyFlowIntent": function () {
    var speechOutput = "Wally Flow helps you be more mindful by gently raising your awareness of periods of extended inactivity.";
    this.emit(':tell',speechOutput);
  },

  "AboutWalabot": function() {
    var speechOutput = "Walabot is a programmable 3D imaging sensor. It has flexible sensor system with between 3 and 18 antennas. Walabot can see through solid objects, track movements, and detect surroundings and speed. To purchase a Walabot, go to Walabot.com. ";
    this.emit(':tell', speechOutput);
  },

  "AMAZON.HelpIntent": function () {
    var speechOutput = "Here are some things you can say. Tell me about Walabot. Tell me about Wally Flow. Tell me about Mindfulness. You can also say stop if you're done. So how can I help?";
    this.emit(':ask',speechOutput,speechOutput);
  },

  "AMAZON.StopIntent": function () {
    var speechOutput = "Goodbye";
    this.emit(':tell', speechOutput);
  },

  "AMAZON.CancelIntent": function () {
    var speechOutput = "Goodbye";
    this.emit(':tell', speechOutput);
  },

  "LaunchRequest": function () {
    var speechText = "Welcome to Wally Flow. ";
    var repromptText = "For instructions on what you can say, please say help me.";
    this.emit(':tell',speechText,repromptText);
  }
};

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.appId = APP_ID
    alexa.registerHandlers(handlers);
    alexa.execute();
};

Wally Flow Alexa JSON

JSON
Wally Flow JSON Alexa Skill - in Submission
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "wally flow",
            "intents": [
                {
                    "name": "AMAZON.StopIntent",
                    "samples": [
                        "end",
                        "stop"
                    ]
                },
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AboutWalabotIntent",
                    "slots": [],
                    "samples": [
                        "define walabot",
                        "Tell me more about Walabot",
                        "What is Walabot",
                        "Tell me about Walabot"
                    ]
                },
                {
                    "name": "AboutWallyFlowIntent",
                    "slots": [],
                    "samples": [
                        "Tell me more about Wally Flow",
                        "Tell me about Wally Flow",
                        "What is Wally Flow"
                    ]
                },
                {
                    "name": "AboutMindfulnessIntent",
                    "slots": [],
                    "samples": [
                        "Tell me more about mindfulness",
                        "Define mindfulness",
                        "What is Mindfulness",
                        "Tell me about Mindfulness"
                    ]
                }
            ],
            "types": []
        }
    }
}

Github

Credits

Megan Rose Bryant

Megan Rose Bryant

2 projects • 4 followers
Thanks to Scott Mendenhall.

Comments