Simon-Lennert RaeschChristoph Eickhoff
Published © Apache-2.0

Smart Kitchen Cube

Alexa controlled timers at a glimpse. A really fancy egg-timer with added temperature control.

IntermediateFull instructions provided5 hours4,563

Things used in this project

Hardware components

Amazon Echo
Amazon Alexa Amazon Echo
×1
Arduino MKR1000
Arduino MKR1000
×1
Photon
Particle Photon
×1
Temperature Probe
×1
DIYmall LED Ring
×1
DFRobot Wireless Charging Module 5V/300mA
×1
WemosD1 Battery Shield
×1
3.7V 1000mAh Lipo
×1
30x30cm Plexiglas GS Sheet
×1
OLED
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
particle.io IDE
MakerCase

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)
Hot glue gun (generic)
Hot glue gun (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

MakerCase Cube Case

This is the generated case file to be used with the lasercutter. We tried three different sizes for prototyping. While you are free to use this one, you might want to head to the website and generate one that better fits your needs and parts.

Schematics

Base

Cube

Code

Arduino Code

C/C++
Code for the Arduino MKR1000
#include <Wire.h>
#include <SPI.h>
#include <WiFi101.h>
#include <Encoder.h>
#include <TaskScheduler.h>
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
#include "math.h"


#include "secret.h"

#define ADC_SAMPLES 10 //more smples to avoid spikes
#define ADC_ZERO_THRESHOLD  10 // values smaler than this are set to Zero
#define ADC_RESOLUTION  1024  // resulution of your adc, at arduino 1024 
#define RESISTOR  100000  //your voltage dividing resistor

#define BCOEFFICIENT 3528 //constant from termistor datasheet
#define THERMISTORNOMINAL 100000 //constant from termistor datasheet
#define TEMPERATURENOMINAL 25 //constant from termistor datasheet

char ssid[] = SSID; //wifi ssid (wifiname)
char pass[] = WLANPASS; //wifi password

String accesstoken = ACCESS_TOKEN; //particle accesstoken from particle ide
String particleDeviceID = PARTICLE_DEVICE_ID; // id of your photon from particle ide

//wifi things
int status = WL_IDLE_STATUS;
WiFiClient client;

char server[] = "api.particle.io"; // particle rest api


void sendTempEvent(); // send actual the temperature to your cube every 5 seconds
Task particleEventTask(5000, TASK_FOREVER, &sendTempEvent);

void sendTempSetEvent();//  send chosen temperature to your cube, executed once per button press
Task particleSetEventTask(1, TASK_FOREVER, &sendTempSetEvent);

void updateDisplay(); //  update the oled display, executed every 500 ms
Task displayTask(500, TASK_FOREVER, &updateDisplay);

Scheduler runner; // task scheduler

Encoder myEnc(4, 5); // initialize the rotary encoder
long oldPosition  = -999;
bool changed = false; // true if the new chosen value is not send to particle
bool lastShown = false; // for blinking

volatile unsigned long lastTime = 0; // needed to debounce the button
volatile unsigned long debounceTime = 20; // needed to debounce the button

Adafruit_SSD1306 display(-1); // initialize the oled display, -1 because there is no resetpin at our display

void setup() {

  pinMode(A1, INPUT); //initialize input for the button

  //initialize the display
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  updateDisplay();
  // initialize wifi connection and wait for success
  while (status != WL_CONNECTED) {
    status = WiFi.begin(ssid, pass);
    delay(1000);
  }
  //initialize  the scheduler and at our tasks
  runner.init();
  runner.addTask(displayTask);
  displayTask.enable();
  runner.addTask(particleEventTask);
  particleEventTask.enable();
  runner.addTask(particleSetEventTask);

  attachInterrupt(1, interruptRoutine, LOW); // attach an interrupt for the button
}

void loop() {
  runner.execute(); // just run the scheduler
}

// executed on buttonpress, do some debounce stuff and on sucess schedule the task to send the chosen temperature
void interruptRoutine() {
  if ((millis() - lastTime) > debounceTime) {
    lastTime = millis();
    changed = false;
    particleSetEventTask.enable();
  }
}

// convert a given resistance to degree celsius
float convertResistanceToTemperature(float resistance) {
  float temp;
  temp = resistance / THERMISTORNOMINAL;
  temp = log(temp);
  temp /= BCOEFFICIENT;
  temp += 1.0 / (TEMPERATURENOMINAL + 273.15);
  temp = 1.0 / temp;
  temp -= 273.15;
  return temp;
}

// calc the resistance of the termistor at the pin
float calcResistance ( const uint8_t pin ) {
  float reading = 0;

  for ( uint8_t i = 0; i < ADC_SAMPLES; i++ ) {
    reading += analogRead(pin);
  }
  if ( reading <= (ADC_ZERO_THRESHOLD * ADC_SAMPLES) ) {
    // probe disconnected, should be zero
    return 0;
  } else {
    reading /= (float)ADC_SAMPLES;
    reading = (ADC_RESOLUTION / reading) - 1;
    return (RESISTOR / reading);
  }
}

// repaint the display, executed every 500 ms
void updateDisplay() {
  updateEncoderPosition();
  float resistance = calcResistance(A1);
  float temperature =  convertResistanceToTemperature(resistance);

  display.clearDisplay();

  display.fillRect(60, 0, 2, 64, WHITE);
  display.setTextColor(WHITE);


  display.setCursor(0, 0);
  display.setTextSize(1);
  display.print("act. temp");

  display.setCursor(70, 0);
  display.print("set temp");

  display.setCursor(0, 17);
  display.setTextSize(2);
  display.print(temperature, 0);
  display.setTextSize(1);
  display.print((temperature - ((int)temperature)) * 10.0f, 0);

  display.setCursor(35, 22);
  display.print((char)247);
  display.print("C");

  if ((changed & !lastShown) | !changed) { // let the choosen temperature blink if not sent to particle yet
    display.setCursor(70, 17);
    display.setTextSize(2);
    display.print( (float)oldPosition , 0);
    display.setTextSize(1);
    display.print( (oldPosition - ((int)oldPosition )) * 10.0f, 0);

    display.setCursor(105, 22);
    display.print((char)247);
    display.print("C");
    lastShown  = true;
  } else {
    lastShown = false;
  }

  display.display();
}
//update the chosen temperature from the rotary encoder
void updateEncoderPosition() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    changed = true;
  }
}
//send chosen temperature to particle
void sendTempSetEvent() {
  String temperatureString  = String(oldPosition, DEC);
  sendEvent("tempset", temperatureString);
  particleSetEventTask.disable(); // disable execution since next buttonpress
}
// send the actual temperature to particle, executed every 5 seconds
void sendTempEvent() {
  float resistance = calcResistance(A1);
  float temperature =  convertResistanceToTemperature(resistance);
  String temperatureString  = String(temperature, DEC);
  sendEvent("tempin", temperatureString);
}
// call a given function at the particle cloud, via h ttp post
void sendEvent(String eventName, String data) {

  String payload =  "data=" + data + "&access_token=" + accesstoken;
  if (client.connectSSL(server, 443)) {
    client.println("POST /v1/devices/" + particleDeviceID + "/" + eventName + " HTTP/1.1");
    client.println("Host: api.particle.io");
    client.println("User-Agent: Arduino/unowifi");
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.println("Connection: close");
    client.print("Content-Length: ");
    client.println(payload.length());
    client.println();
    client.println(payload);
  }

  while (client.available()) {
    char c = client.read();
    Serial.print(c); // just for debug
  }
  if (!client.connected()) {
    client.stop();
  }
}

Photon Code

C/C++
#include <neopixel.h>
#include "Particle.h"
#define PIXEL_PIN D0 // pin you want to connect your ledstripe to
#define PIXEL_COUNT 64 // adjust here how many leds you want, but you have to bye them first
#define PIXEL_TYPE WS2812B
SYSTEM_MODE(SEMI_AUTOMATIC); // set to semiautomatic to save power

String alexa_device = "cube"; // <<==== set your devicename here

//init stuff
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
long counterTime = PIXEL_COUNT ;
long endTime = millis() + (counterTime * 1000);
float temperature = 0;
float targetTemperature = -1;

void setup()
{
  watchVoltage(); // first check battery voltage
  Particle.connect(); // connect to wifi and cloud
  // register functions for countdown an temperature purposes
  Particle.function("countdown", countdown);
  Particle.function("adjust", countdown_adjust);
  Particle.function("tempin", temperatureIn);
  Particle.function("tempset", temperatureSet);
  Particle.variable("alexa_device", alexa_device);  // needed to tell the skill, we are a smart kitchen cube
  // init the leds
  strip.begin();
  strip.setBrightness(180);
  strip.show();
}

void loop()
{
  watchVoltage();//  check battery voltage
  if (targetTemperature != -1) { // check if we want to work either with temperatures or run a countdown
    showTemperature();
  } else {
    showTimer();
  }

}

//check voltage on pin A1 power down if its to low
void watchVoltage() {
  int raw =  analogRead(A1);
  raw +=  analogRead(A1);
  raw +=  analogRead(A1);
  raw +=  analogRead(A1);
  raw /= 4;
  float voltage = raw * 0.0008f * 2.0f;

  if (voltage <= 3.0f && voltage  > 2) {
    //Power down
    for (uint16_t i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, 0);
    }
    System.sleep(SLEEP_MODE_DEEP, 300);
  } else   if (voltage < 3.2f) {
    //TODO send warning to alexa, not that easy
  }
}

// show the actual temperature relative to the target temperature
void showTemperature()
{
  int remaining = targetTemperature - temperature;

  if (remaining > 0) {

    int numPixels = map(remaining, 0, targetTemperature, 0, strip.numPixels()) ;
    for (uint16_t i = 0; i < strip.numPixels(); i++) {
      if (i < numPixels) {
        strip.setPixelColor(i, strip.Color(255, 0, 0));
      } else {
        strip.setPixelColor(i, 0);
      }
    }

    float lastPixel = (map(remaining, 0, targetTemperature, 0, strip.numPixels()) / 1000.0f) - numPixels;

    strip.setPixelColor(numPixels, strip.Color(lastPixel * 255, 0, 0));

  } else {
    for (uint16_t i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, 0);
    }

    targetTemperature  = -1; // reached target temperature, stop temperature mode
  }
  strip.show();
}

// show the contdown timer
void showTimer()
{
  long now = millis();
  long remaining = endTime - now;

  if (remaining > 0) {

    int numPixels = map(remaining, 0, counterTime, 0, strip.numPixels()) / 1000;
    for (uint16_t i = 0; i < strip.numPixels(); i++) {
      if (i < numPixels) {
        strip.setPixelColor(i, strip.Color(255, 0, 0));
      } else {
        strip.setPixelColor(i, 0);
      }
    }

    float lastPixel = (map(remaining, 0, counterTime, 0, strip.numPixels()) / 1000.0f) - numPixels;

    strip.setPixelColor(numPixels, strip.Color(lastPixel * 255, 0, 0));

  } else {
    for (uint16_t i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, 0);
    }
  }
  strip.show();
}

// callback for cloud function to start countdown
int countdown(String time) {
  counterTime = atoi(time);
  endTime = millis() + counterTime * 1000;
  return counterTime;
}
// callback for cloud function to adjust countdown
int countdown_adjust(String time) {
  counterTime += atoi(time);
  endTime = millis() + counterTime * 1000;
  return counterTime;
}
// callback for cloud function to set the actual temperature called every 5 seconds
int temperatureIn(String tempString)
{
  float temp = atof(tempString);
  temperature = temp;
}
// callback for cloud function to set the target temperature
int temperatureSet(String tempString)
{
  float temp = atof(tempString);
  targetTemperature = temp;
}

secret.h

C/C++
add to arduino sketch
#define WLANPASS "wlanpass"
#define SSID "ssid"
#define PARTICLE_DEVICE_ID "paricle_device_id"
#define ACCESS_TOKEN "your_access_token"

Credits

Simon-Lennert Raesch

Simon-Lennert Raesch

4 projects • 3 followers
Christoph Eickhoff

Christoph Eickhoff

1 project • 1 follower

Comments