Things used in this project

Hardware components:
Photon new
Particle Photon
×1
Echo
Amazon Alexa Amazon Echo
×1
TFV12 Cloud Beast Vape
×1
INTERNATIONAL RECTIFIER IRLB3034PBF N CH MOSFET, 40V, 195A, TO-220AB
×2
Sidiou Group 26650 Lithium Ion Battery Protected 3.7V 4800mAh Rechargeable Battery
×2
26650 Li-ion Rechargeable Battery Holder SMD Surface Mount
×2
TrustFire TR-006 Dual-Slot 25500 / 26650 / 26700 / 18650 / 16340 Battery Charger (AC 110~240V)
×2
15K Ohm Resistors, 1/4 W
This comes in a pack of many resistors, but only 2 Resistors are needed (1 for each MOSFET)
×1
High Airflow 12V Computer Fan
The fan needs to move a bunch of air for it to work well.
×1
HS-422 Servo Motor
×1
M3 10mm SS Screws
×2
M3 35mm SS Screw
×4
M3 15mm SS Screw
×2
M3 20mm SS Screw
×1
M3 Lock Nut
×2
M3 Washer
×2
M3 15mm Nylon Screw
×1
M3 20mm Nylon Screw
×1
12002 04
Breadboard (generic)
×1
Super Miracle Bubbles
×1
Software apps and online services:
D94d qxu
Autodesk Fusion 360
Hand tools and fabrication machines:
Tevo Little Monster

Custom parts and enclosures

Single 40mm Fan Mount
The fan mount should be printed in TPU with Shore Rating of no less than 85A. .8mm nozzle, 20% Infill, 15mm/s, 1 Perimeter, 4 Top and Bottom Layers, and supports are recommended.
Fogger Stand
The stand can be printed in plain PLA.
Male Spout Swivel Mount
This piece can be printed in PLA. 90% infill is required since the two arms will need to flex when attaching the male spout
Female Spout with Reservoir
This piece can be printed in PLA. To get good quality female screw threads you should print with a .3mm nozzle.
Pull Arm
This piece can be printed in PLA
Servo Arm
This piece can be printed in PLA. A .3mm nozzle is required so that you get the fine grove walls in the hole that attaches to the servo gear axil.
Servo Anchor Mount
This piece can be printed in PLA
Fogger Tub
This piece can be printed in PLA. Its best to print this using an .8mm nozzle and over extrude by 10%-20% so that you get a good seal between layers to prevent any leaks.
Vape Mouth Piece
This mouth piece replaces the one that comes with your vape device. The end of the mouth piece is angled to get a good seal with the male spout as it is raised up by the servo
Male Spout
This piece can be printed in PLA. To get good quality male screw threads you should print with a .3mm nozzle.

Schematics

AWS Lambda to Particle Photon
This diagram shows how Alexa Skill invokes the Lambda Function which posts to the Particle Photon cloud to invoke a function on Particle Photon.
Alexatophoton pymfemgvgg
Fogger Circuit Fritzing
Fritzingfogger keywu5fx2r

Code

Particle Photon Fog MachineArduino
This is the embedded code for the fog machine.
int pwmFoggerPin = D1;
int pwmLedPin = D2;
int pwmFanPin = D3;
int notifyLedPin = D7;
int servoBubbleWandPin = A7;

// Adjust the SERVO_ANGLE_OFFSET to set the spout to the proper angle to pick up bubble juice and seal tightly with mouth piece.
// ********************************************************************************************************************
int SERVO_ANGLE_OFFSET = 0; 
//*********************************************************************************************************************

double FOGGER_VOLTS = 5;
double FAN_VOLTS = 14;
int MAX_MILLISECONDS_TO_FOG_WITHOUT_CATCHING_ON_FIRE = 1200; //3000 or more is a fire hazard!!

Servo bubbleWandServo;

void setup() {
    pinMode(pwmFoggerPin, OUTPUT);
    pinMode(pwmFanPin, OUTPUT);
    pinMode(pwmLedPin, OUTPUT);
    pinMode(notifyLedPin, OUTPUT);
    
    bubbleWandServo.attach(servoBubbleWandPin);
    moveBubbleWandDown();
    
    RGB.control (true);
    
    Particle.subscribe("SpitFire", spitFire, MY_DEVICES);
    Particle.function("flame", flame);
    Particle.function("blowBubbles", blowBubbles);
    Particle.function("makeFog", makeFog);
    Particle.function("fogBubbles", fogBubbles);
    Particle.function("servoUp", servoUp);
    Particle.function("servoDown", servoDown);
    Particle.function("fanOn", fanOn);
    Particle.function("fanOff", fanOff);
    
    turnFanOn(FAN_VOLTS); delay(1000);
    makeSmoke(false);
    //Running indicator...Used wnen calibrating servo arm and starting up fogger.
    digitalWrite(notifyLedPin, HIGH);   // turn the LED on
    delay(2000);              // wait for two seconds
    digitalWrite(notifyLedPin, LOW);    // turn the LED off by making the voltage LOW
}

void loop() {

}

void makeSmoke(bool makeSmoke) {
    for (int repeatCount = 0; repeatCount < 2; repeatCount++) {
        turnFanOn(FAN_VOLTS); delay(1000);
        RGB.color(255,0,0);
        moveBubbleWandHome();
        delay(100);
        if (makeSmoke){
            turnFogOn(FOGGER_VOLTS);
        }
        analogWrite(pwmLedPin, 255);
        RGB.color(0,255,0);
        delay(MAX_MILLISECONDS_TO_FOG_WITHOUT_CATCHING_ON_FIRE);
        turnFogOff(); 
        RGB.color(0,255,0);
        
        blinkEyes(4,475);

        moveBubbleWandDown();
        analogWrite(pwmLedPin, 0);
        turnFanOff();
        delay(800);
    }
}

void blinkEyes(int number, int delayBetweenBlinks) {
    bool turnOn = (analogRead(pwmLedPin)>512); //Range = 0 to 1023
    for (int count = 0; count < number * 2; count++) {
        if (turnOn) {
            analogWrite(pwmLedPin, 0);
            delay(delayBetweenBlinks/2);
        } else {
            analogWrite(pwmLedPin, 255);
            delay(delayBetweenBlinks/2);
        }
        turnOn = !turnOn;
    } 
}

void moveBubbleWandDown() {
    bubbleWandServo.write(50 + SERVO_ANGLE_OFFSET);//50
}

void moveBubbleWandHome() {
    bubbleWandServo.write(160 - SERVO_ANGLE_OFFSET);//165
}

int computeAnalogWriteValueToSetVoltage(double volts) {
    double batteryVolts = 4;
    int numberOfBatteries = 4;
    int analogWriteValueToSetVoltage = volts / (batteryVolts * numberOfBatteries) * 255;
    
    if (analogWriteValueToSetVoltage > 255) {
        analogWriteValueToSetVoltage = 255;
    }
    
    return analogWriteValueToSetVoltage;
}

void turnFogOn(double volts){
    analogWrite(pwmFoggerPin, computeAnalogWriteValueToSetVoltage(volts)); 
}

void turnFogOff(){
    analogWrite(pwmFoggerPin, 0);
}

void turnFanOn(double volts){
    //analogWrite(pwmFanPin, 255-computeAnalogWriteValueToSetVoltage(volts)); //For Marks' Fan
    analogWrite(pwmFanPin, computeAnalogWriteValueToSetVoltage(volts)); 
}

void turnFanOff(){
    //analogWrite(pwmFanPin, 255);  //For Marks' Fan
    analogWrite(pwmFanPin, 0);
}

int blowBubbles(String Command) {
    makeSmoke(false);
    return 1;
}

//Invoked by alexa skill
int makeFog(String Command) {
    RGB.color(0,128,128);
    makeSmoke(true);
    
    return 1;
}

//Invoked by alexa skill
int fogBubbles(String Command) {
    RGB.color(0,0,255);
    makeSmoke(true);
    
    return 1;
}

//Invoked by alexa skill
int servoUp(String Command) {
    moveBubbleWandHome();
    return 1;
}

//Invoked by alexa skill
int servoDown(String Command) {
    moveBubbleWandDown();
    return 1;
}

//Invoked by alexa skill
int fanOn(String Command) {
    turnFanOn(FAN_VOLTS);
    return 1;
}

//Invoked by alexa skill
int fanOff(String Command) {
    turnFanOff();
    return 1;
}

//Invoked by alexa skill
int flame(String Command) {
    makeSmoke(true);
    return 1;
}

//Invoked by alexa skill
void spitFire(const char *event, const char *data) {
    makeSmoke(true);
}
Fog Machine Alexa SkillJavaScript
This code runs in Amazon Lambda to invoke functions in the Particle Photon code.
'use strict';
var http = require('https');

function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
    return {
        outputSpeech: {
            type: 'PlainText',
            text: output,
        },
        card: {
            type: 'Simple',
            title: `SessionSpeechlet - ${title}`,
            content: `SessionSpeechlet - ${output}`,
        },
        reprompt: {
            outputSpeech: {
                type: 'PlainText',
                text: repromptText,
            },
        },
        shouldEndSession,
    };
}

function buildResponse(sessionAttributes, speechletResponse) {
    return {
        version: '1.0',
        sessionAttributes,
        response: speechletResponse,
    };
}


// --------------- Functions that control the skill\'s behavior -----------------------
function getWelcomeResponse(callback) {
    const sessionAttributes = {};
    const cardTitle = 'Welcome';
    const speechOutput = 'Welcome to Mark and Dans fog bubble awesomeness spooky fog bubble thingy! Please say blow bubbles, make fog, or make fog bubbles!';
    const repromptText = 'Please say blow bubbles, make fog, or make fog bubbles!';
    const shouldEndSession = false;

    callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function handleSessionEndRequest(callback) {
    const cardTitle = 'Session Ended';
    const speechOutput = 'Happy Halloween!';
    const shouldEndSession = true;

    callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}

function blowBubbles(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Blowing bubbles';

    invokeParticleMicrocontroller('blowBubbles', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function makeFog(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Make Fog';
    
    invokeParticleMicrocontroller('makeFog', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
    
}

function makeFogBubbles(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Make Fog Bubbles';

    invokeParticleMicrocontroller('fogBubbles', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function moveServoDown(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Move Servo Down';

    invokeParticleMicrocontroller('servoDown', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function moveServoUp(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Move Servo Up';

    invokeParticleMicrocontroller('servoUp', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function turnFanOn(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Turn Fan On';

    invokeParticleMicrocontroller('fanOn', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function turnFanOff(intent, session, callback) {
    const cardTitle = intent.name;
    let repromptText = '';
    let sessionAttributes = {};
    const shouldEndSession = false;
    let speechOutput = 'Turn Fan Off';

    invokeParticleMicrocontroller('fanOff', function(resp){
        callback(sessionAttributes,buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

// ----------------------------------------------
function invokeParticleMicrocontroller(particleFunction, callback){

    var deviceId = '2d0030000d499999999999';  // This is the Particle Photon Device ID
    
    var options = {
        hostname: 'api.particle.io',
        port: 443,
        path: '/v1/devices/' + deviceId + '/' + particleFunction,
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': '*.*'
        }
    };

    var postData = "access_token=999999999999999999999999999999999999999";   // This is the Particle Photon access token

    console.log("Post Data: " + postData);

    // Call Particle API
    var req = http.request(options, function(res) {
        console.log('STATUS: ' + res.statusCode);
        console.log('HEADERS: ' + JSON.stringify(res.headers));

        var body = "";

        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            console.log('BODY: ' + chunk);

            body += chunk;
        });

        res.on('end', function () {
            callback(body);
        });
    });

    req.on('error', function(e) {
        console.log('problem with request: ' + e.message);
    });

    // write data to request body
    req.write(postData);
    req.end();
}

// --------------- Events -----------------------

function onSessionStarted(sessionStartedRequest, session) {
    console.log(`onSessionStarted requestId=${sessionStartedRequest.requestId}, sessionId=${session.sessionId}`);
}

function onLaunch(launchRequest, session, callback) {
    console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);

    getWelcomeResponse(callback);
}

function onIntent(intentRequest, session, callback) {
    console.log(`onIntent requestId=${intentRequest.requestId}, sessionId=${session.sessionId}`);

    const intent = intentRequest.intent;
    const intentName = intentRequest.intent.name;

    if (intentName === 'BlowBubblesIntent') {
        blowBubbles(intent, session, callback);
    } else if (intentName === 'MakeFogIntent') {
        makeFog(intent, session, callback);
    } else if (intentName === 'MakeFogBubblesIntent') {
        makeFogBubbles(intent, session, callback);
    } else if (intentName === 'MoveServoUpIntent') {
        moveServoUp(intent, session, callback);
    } else if (intentName === 'MoveServoDownIntent') {
        moveServoDown(intent, session, callback);
    } else if (intentName === 'TurnFanOnIntent') {
        turnFanOn(intent, session, callback);
    } else if (intentName === 'TurnFanOffIntent') {
        turnFanOff(intent, session, callback);
    } else if (intentName === 'AMAZON.HelpIntent') {
        getWelcomeResponse(callback);
    } else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
        handleSessionEndRequest(callback);
    } else {
        throw new Error('Invalid intent');
    }
}

function onSessionEnded(sessionEndedRequest, session) {
    console.log(`onSessionEnded requestId=${sessionEndedRequest.requestId}, sessionId=${session.sessionId}`);
}

// --------------- Main handler -----------------------

// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = (event, context, callback) => {
    try {
        console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);

        if (event.session.new) {
            onSessionStarted({ requestId: event.request.requestId }, event.session);
        }

        if (event.request.type === 'LaunchRequest') {
            onLaunch(event.request,
                event.session,
                (sessionAttributes, speechletResponse) => {
                    callback(null, buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === 'IntentRequest') {
            onIntent(event.request,
                event.session,
                (sessionAttributes, speechletResponse) => {
                    callback(null, buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === 'SessionEndedRequest') {
            onSessionEnded(event.request, event.session);
            callback();
        }
    } catch (err) {
        callback(err);
    }
};
Alexa Fogger Intent SchemaJSON
This JSON goes in the Interaction Model of the Alexa Developer's Console.
{
  "intents": [
    {
      "intent": "BlowBubblesIntent"
    },
    {
      "intent": "MakeFogIntent"
    },
    {
      "intent": "MakeFogBubblesIntent"
    },
    {
      "intent": "MoveServoDownIntent"
    },
    {
      "intent": "MoveServoUpIntent"
    },
    {
      "intent": "TurnFanOnIntent"
    },
    {
      "intent": "TurnFanOffIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}
Fogger Alexa UtterancesAsciiDoc
This needs set in the Interaction Model Sample Utterances of the Alexa Developers Console. These are what people say to interact with the Alexa Skill.
BlowBubblesIntent blow bubbles
MakeFogIntent make fog
MakeFogBubblesIntent make fog bubbles
MakeFogBubblesIntent to say hello
MakeFogBubblesIntent happy halloween
MoveServoDownIntent move servo down
MoveServoUpIntent move servo up
MoveServoUpIntent move servo home
TurnFanOnIntent turn fan on
TurnFanOnIntent fan on
TurnFanOffIntent turn fan off
TurnFanOffIntent fan off

Credits

Image
Mark Brady Ingle
2 projects • 6 followers
Suffering from tinkeritis! I love learning new things and developing new applications that make people say...Wow! Thats Cool!
Contact
Dan
Dan Thyer
2 projects • 7 followers
Husband, father, business owner, software architect, IoT, web/mobile development, microcontrollers- basically a total geek. Microsoft RD/MVP
Contact

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

Add projectSign up / Login