Bastiaan Slee
Published © GPL3+

Barbecue Planner with Amazon DRS integration

A barbecue shaped alarm that tells you when it’s going to be barbecue weather. It will order your supplies through the Amazon DRS system.

AdvancedFull instructions provided2 days884
Barbecue Planner with Amazon DRS integration

Things used in this project

Hardware components

Adafruit Monochrome 1.3" 128x64 OLED graphic display
×1
Adafruit Analog 2-axis Joystick with Select Button
×1
Adafruit NeoPixel 7x RGB LED
×1

Software apps and online services

AWS SNS
Amazon Web Services AWS SNS
AWS SQS (Simple Queue Service)
Amazon Web Services AWS SQS (Simple Queue Service)
Arduino IDE
Arduino IDE
Dash Replenishment`
Amazon Web Services Login With Amazon
Firmata
PyCharm
YR.no weather forecast
SQLite

Hand tools and fabrication machines

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

Story

Read more

Custom parts and enclosures

Complete set of code

This is all the used code, database and dateutil-tz files

Schematics

Fritzing diagram

Code

Arduino Sketch: BarbecuePlanner_Firmata.ino

C/C++
The StandardFirmata sketch, edited to support my NeoPixel.
To be loaded to the MPU
/*
  Firmata is a generic protocol for communicating with microcontrollers
  from software on a host computer. It is intended to work with
  any host computer software package.

  To download a host software package, please clink on the following link
  to open the list of Firmata client libraries your default browser.

  https://github.com/firmata/arduino#firmata-client-libraries

  Copyright (C) 2006-2008 Hans-Christoph Steiner.  All rights reserved.
  Copyright (C) 2010-2011 Paul Stoffregen.  All rights reserved.
  Copyright (C) 2009 Shigeru Kobayashi.  All rights reserved.
  Copyright (C) 2009-2016 Jeff Hoefs.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  See file LICENSE.txt for further informations on licensing terms.

  Last updated by Jeff Hoefs: January 10th, 2016

*/

/*
  README

  StandardFirmataPlus adds additional features that may exceed the Flash and
  RAM sizes of Arduino boards such as ATMega328p (Uno) and ATMega32u4
  (Leonardo, Micro, Yun, etc). It is best to use StandardFirmataPlus with higher
  memory boards such as the Arduino Mega, Arduino Due, Teensy 3.0/3.1/3.2.

  All Firmata examples that are appended with "Plus" add the following features:

  - Ability to interface with serial devices using UART, USART, or SoftwareSerial
    depending on the capatilities of the board.




  At the time of this writing, StandardFirmataPlus will still compile and run
  on ATMega328p and ATMega32u4-based boards, but future versions of this sketch
  may not as new features are added.
*/

#include <Servo.h>
#include <Wire.h>
#include <Firmata.h>

#define I2C_WRITE                   B00000000
#define I2C_READ                    B00001000
#define I2C_READ_CONTINUOUSLY       B00010000
#define I2C_STOP_READING            B00011000
#define I2C_READ_WRITE_MODE_MASK    B00011000
#define I2C_10BIT_ADDRESS_MODE_MASK B00100000
#define I2C_END_TX_MASK             B01000000
#define I2C_STOP_TX                 1
#define I2C_RESTART_TX              0
#define I2C_MAX_QUERIES             8
#define I2C_REGISTER_NOT_SPECIFIED  -1

// the minimum interval for sampling analog input
#define MINIMUM_SAMPLING_INTERVAL   1


/* NEOPIXELS */
#include <Adafruit_NeoPixel.h>
#define NEOPIXEL_DATA 0x74
#define NEOPIXEL_COLOR 0
#define NEOPIXEL_FIRE 1
#define NEOPIXEL_PIN 6
Adafruit_NeoPixel *neopixel_strip = NULL;
 
// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long FirePreviousMillis = 0;        // will store last time LED was updated
long FireInterval = 1000;           // interval at which to blink (milliseconds)
int FireRandWAIT = 1;
int FireStatus = 0;




/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

#ifdef FIRMATA_SERIAL_FEATURE
SerialFirmata serialFeature;
#endif

/* analog inputs */
int analogInputsToReport = 0; // bitwise array to store pin reporting

/* digital input ports */
byte reportPINs[TOTAL_PORTS];       // 1 = report this port, 0 = silence
byte previousPINs[TOTAL_PORTS];     // previous 8 bits sent

/* pins configuration */
byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else

/* timer variables */
unsigned long currentMillis;        // store the current value from millis()
unsigned long previousMillis;       // for comparison with currentMillis
unsigned int samplingInterval = 19; // how often to run the main loop (in ms)

/* i2c data */
struct i2c_device_info {
  byte addr;
  int reg;
  byte bytes;
  byte stopTX;
};

/* for i2c read continuous more */
i2c_device_info query[I2C_MAX_QUERIES];

byte i2cRxData[64];
boolean isI2CEnabled = false;
signed char queryIndex = -1;
// default delay time between i2c read request and Wire.requestFrom()
unsigned int i2cReadDelayTime = 0;

Servo servos[MAX_SERVOS];
byte servoPinMap[TOTAL_PINS];
byte detachedServos[MAX_SERVOS];
byte detachedServoCount = 0;
byte servoCount = 0;

boolean isResetting = false;

// Forward declare a few functions to avoid compiler errors with older versions
// of the Arduino IDE.
//void setPinModeCallback(byte, int);
//void reportAnalogCallback(byte analogPin, int value);
//void sysexCallback(byte, byte, byte*);

/* utility functions */
void wireWrite(byte data)
{
#if ARDUINO >= 100
  Wire.write((byte)data);
#else
  Wire.send(data);
#endif
}

byte wireRead(void)
{
#if ARDUINO >= 100
  return Wire.read();
#else
  return Wire.receive();
#endif
}

/*==============================================================================
 * FUNCTIONS
 *============================================================================*/

void attachServo(byte pin, int minPulse, int maxPulse)
{
  if (servoCount < MAX_SERVOS) {
    // reuse indexes of detached servos until all have been reallocated
    if (detachedServoCount > 0) {
      servoPinMap[pin] = detachedServos[detachedServoCount - 1];
      if (detachedServoCount > 0) detachedServoCount--;
    } else {
      servoPinMap[pin] = servoCount;
      servoCount++;
    }
    if (minPulse > 0 && maxPulse > 0) {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse);
    } else {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin));
    }
  } else {
    Firmata.sendString("Max servos attached");
  }
}

void detachServo(byte pin)
{
  servos[servoPinMap[pin]].detach();
  // if we're detaching the last servo, decrement the count
  // otherwise store the index of the detached servo
  if (servoPinMap[pin] == servoCount && servoCount > 0) {
    servoCount--;
  } else if (servoCount > 0) {
    // keep track of detached servos because we want to reuse their indexes
    // before incrementing the count of attached servos
    detachedServoCount++;
    detachedServos[detachedServoCount - 1] = servoPinMap[pin];
  }

  servoPinMap[pin] = 255;
}


void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) {
  // allow I2C requests that don't require a register read
  // for example, some devices using an interrupt pin to signify new data available
  // do not always require the register read so upon interrupt you call Wire.requestFrom()
  if (theRegister != I2C_REGISTER_NOT_SPECIFIED) {
    Wire.beginTransmission(address);
    wireWrite((byte)theRegister);
    Wire.endTransmission(stopTX); // default = true
    // do not set a value of 0
    if (i2cReadDelayTime > 0) {
      // delay is necessary for some devices such as WiiNunchuck
      delayMicroseconds(i2cReadDelayTime);
    }
  } else {
    theRegister = 0;  // fill the register with a dummy value
  }

  Wire.requestFrom(address, numBytes);  // all bytes are returned in requestFrom

  // check to be sure correct number of bytes were returned by slave
  if (numBytes < Wire.available()) {
    Firmata.sendString("I2C: Too many bytes received");
  } else if (numBytes > Wire.available()) {
    Firmata.sendString("I2C: Too few bytes received");
  }

  i2cRxData[0] = address;
  i2cRxData[1] = theRegister;

  for (int i = 0; i < numBytes && Wire.available(); i++) {
    i2cRxData[2 + i] = wireRead();
  }

  // send slave address, register and received bytes
  Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData);
}

void outputPort(byte portNumber, byte portValue, byte forceSend)
{
  // pins not configured as INPUT are cleared to zeros
  portValue = portValue & portConfigInputs[portNumber];
  // only send if the value is different than previously sent
  if (forceSend || previousPINs[portNumber] != portValue) {
    Firmata.sendDigitalPort(portNumber, portValue);
    previousPINs[portNumber] = portValue;
  }
}

/* -----------------------------------------------------------------------------
 * check all the active digital inputs for change of state, then add any events
 * to the Serial output queue using Serial.print() */
void checkDigitalInputs(void)
{
  /* Using non-looping code allows constants to be given to readPort().
   * The compiler will apply substantial optimizations if the inputs
   * to readPort() are compile-time constants. */
  if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false);
  if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false);
  if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false);
  if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false);
  if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false);
  if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false);
  if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false);
  if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false);
  if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false);
  if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false);
  if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false);
  if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false);
  if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false);
  if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false);
  if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false);
  if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false);
}

// -----------------------------------------------------------------------------
/* sets the pin mode to the correct state and sets the relevant bits in the
 * two bit-arrays that track Digital I/O and PWM status
 */
void setPinModeCallback(byte pin, int mode)
{
  if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE)
    return;

  if (Firmata.getPinMode(pin) == PIN_MODE_I2C && isI2CEnabled && mode != PIN_MODE_I2C) {
    // disable i2c so pins can be used for other functions
    // the following if statements should reconfigure the pins properly
    disableI2CPins();
  }
  if (IS_PIN_DIGITAL(pin) && mode != PIN_MODE_SERVO) {
    if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
      detachServo(pin);
    }
  }
  if (IS_PIN_ANALOG(pin)) {
    reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting
  }
  if (IS_PIN_DIGITAL(pin)) {
    if (mode == INPUT || mode == PIN_MODE_PULLUP) {
      portConfigInputs[pin / 8] |= (1 << (pin & 7));
    } else {
      portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
    }
  }
  Firmata.setPinState(pin, 0);
  switch (mode) {
    case PIN_MODE_ANALOG:
      if (IS_PIN_ANALOG(pin)) {
        if (IS_PIN_DIGITAL(pin)) {
          pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
          // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
          digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        }
        Firmata.setPinMode(pin, PIN_MODE_ANALOG);
      }
      break;
    case INPUT:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
        // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
        digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        Firmata.setPinMode(pin, INPUT);
      }
      break;
    case PIN_MODE_PULLUP:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP);
        Firmata.setPinMode(pin, PIN_MODE_PULLUP);
        Firmata.setPinState(pin, 1);
      }
      break;
    case OUTPUT:
      if (IS_PIN_DIGITAL(pin)) {
        if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
          // Disable PWM if pin mode was previously set to PWM.
          digitalWrite(PIN_TO_DIGITAL(pin), LOW);
        }
        pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
        Firmata.setPinMode(pin, OUTPUT);
      }
      break;
    case PIN_MODE_PWM:
      if (IS_PIN_PWM(pin)) {
        pinMode(PIN_TO_PWM(pin), OUTPUT);
        analogWrite(PIN_TO_PWM(pin), 0);
        Firmata.setPinMode(pin, PIN_MODE_PWM);
      }
      break;
    case PIN_MODE_SERVO:
      if (IS_PIN_DIGITAL(pin)) {
        Firmata.setPinMode(pin, PIN_MODE_SERVO);
        if (servoPinMap[pin] == 255 || !servos[servoPinMap[pin]].attached()) {
          // pass -1 for min and max pulse values to use default values set
          // by Servo library
          attachServo(pin, -1, -1);
        }
      }
      break;
    case PIN_MODE_I2C:
      if (IS_PIN_I2C(pin)) {
        // mark the pin as i2c
        // the user must call I2C_CONFIG to enable I2C for a device
        Firmata.setPinMode(pin, PIN_MODE_I2C);
      }
      break;
    case PIN_MODE_SERIAL:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handlePinMode(pin, PIN_MODE_SERIAL);
#endif
      break;
    default:
      Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
  }
  // TODO: save status to EEPROM here, if changed
}

/*
 * Sets the value of an individual pin. Useful if you want to set a pin value but
 * are not tracking the digital port state.
 * Can only be used on pins configured as OUTPUT.
 * Cannot be used to enable pull-ups on Digital INPUT pins.
 */
void setPinValueCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
    if (Firmata.getPinMode(pin) == OUTPUT) {
      Firmata.setPinState(pin, value);
      digitalWrite(PIN_TO_DIGITAL(pin), value);
    }
  }
}

void analogWriteCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS) {
    switch (Firmata.getPinMode(pin)) {
      case PIN_MODE_SERVO:
        if (IS_PIN_DIGITAL(pin))
          servos[servoPinMap[pin]].write(value);
        Firmata.setPinState(pin, value);
        break;
      case PIN_MODE_PWM:
        if (IS_PIN_PWM(pin))
          analogWrite(PIN_TO_PWM(pin), value);
        Firmata.setPinState(pin, value);
        break;
    }
  }
}

void digitalWriteCallback(byte port, int value)
{
  byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;

  if (port < TOTAL_PORTS) {
    // create a mask of the pins on this port that are writable.
    lastPin = port * 8 + 8;
    if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
    for (pin = port * 8; pin < lastPin; pin++) {
      // do not disturb non-digital pins (eg, Rx & Tx)
      if (IS_PIN_DIGITAL(pin)) {
        // do not touch pins in PWM, ANALOG, SERVO or other modes
        if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
          pinValue = ((byte)value & mask) ? 1 : 0;
          if (Firmata.getPinMode(pin) == OUTPUT) {
            pinWriteMask |= mask;
          } else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
            // only handle INPUT here for backwards compatibility
#if ARDUINO > 100
            pinMode(pin, INPUT_PULLUP);
#else
            // only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier
            pinWriteMask |= mask;
#endif
          }
          Firmata.setPinState(pin, pinValue);
        }
      }
      mask = mask << 1;
    }
    writePort(port, (byte)value, pinWriteMask);
  }
}


// -----------------------------------------------------------------------------
/* sets bits in a bit array (int) to toggle the reporting of the analogIns
 */
//void FirmataClass::setAnalogPinReporting(byte pin, byte state) {
//}
void reportAnalogCallback(byte analogPin, int value)
{
  if (analogPin < TOTAL_ANALOG_PINS) {
    if (value == 0) {
      analogInputsToReport = analogInputsToReport & ~ (1 << analogPin);
    } else {
      analogInputsToReport = analogInputsToReport | (1 << analogPin);
      // prevent during system reset or all analog pin values will be reported
      // which may report noise for unconnected analog pins
      if (!isResetting) {
        // Send pin value immediately. This is helpful when connected via
        // ethernet, wi-fi or bluetooth so pin states can be known upon
        // reconnecting.
        Firmata.sendAnalog(analogPin, analogRead(analogPin));
      }
    }
    // TODO: save status to EEPROM here, if changed
  }
}

void reportDigitalCallback(byte port, int value)
{
  if (port < TOTAL_PORTS) {
    reportPINs[port] = (byte)value;
    // Send port value immediately. This is helpful when connected via
    // ethernet, wi-fi or bluetooth so pin states can be known upon
    // reconnecting.
    if (value) outputPort(port, readPort(port, portConfigInputs[port]), true);
  }
  // do not disable analog reporting on these 8 pins, to allow some
  // pins used for digital, others analog.  Instead, allow both types
  // of reporting to be enabled, but check if the pin is configured
  // as analog when sampling the analog inputs.  Likewise, while
  // scanning digital pins, portConfigInputs will mask off values from any
  // pins configured as analog
}

/*==============================================================================
 * SYSEX-BASED commands
 *============================================================================*/

void sysexCallback(byte command, byte argc, byte *argv)
{
  byte mode;
  byte stopTX;
  byte slaveAddress;
  byte data;
  int slaveRegister;
  unsigned int delayTime;

  switch (command) {
    case I2C_REQUEST:
      mode = argv[1] & I2C_READ_WRITE_MODE_MASK;
      if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) {
        Firmata.sendString("10-bit addressing not supported");
        return;
      }
      else {
        slaveAddress = argv[0];
      }

      // need to invert the logic here since 0 will be default for client
      // libraries that have not updated to add support for restart tx
      if (argv[1] & I2C_END_TX_MASK) {
        stopTX = I2C_RESTART_TX;
      }
      else {
        stopTX = I2C_STOP_TX; // default
      }

      switch (mode) {
        case I2C_WRITE:
          Wire.beginTransmission(slaveAddress);
          for (byte i = 2; i < argc; i += 2) {
            data = argv[i] + (argv[i + 1] << 7);
            wireWrite(data);
          }
          Wire.endTransmission();
          delayMicroseconds(70);
          break;
        case I2C_READ:
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX);
          break;
        case I2C_READ_CONTINUOUSLY:
          if ((queryIndex + 1) >= I2C_MAX_QUERIES) {
            // too many queries, just ignore
            Firmata.sendString("too many queries");
            break;
          }
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          queryIndex++;
          query[queryIndex].addr = slaveAddress;
          query[queryIndex].reg = slaveRegister;
          query[queryIndex].bytes = data;
          query[queryIndex].stopTX = stopTX;
          break;
        case I2C_STOP_READING:
          byte queryIndexToSkip;
          // if read continuous mode is enabled for only 1 i2c device, disable
          // read continuous reporting for that device
          if (queryIndex <= 0) {
            queryIndex = -1;
          } else {
            queryIndexToSkip = 0;
            // if read continuous mode is enabled for multiple devices,
            // determine which device to stop reading and remove it's data from
            // the array, shifiting other array data to fill the space
            for (byte i = 0; i < queryIndex + 1; i++) {
              if (query[i].addr == slaveAddress) {
                queryIndexToSkip = i;
                break;
              }
            }

            for (byte i = queryIndexToSkip; i < queryIndex + 1; i++) {
              if (i < I2C_MAX_QUERIES) {
                query[i].addr = query[i + 1].addr;
                query[i].reg = query[i + 1].reg;
                query[i].bytes = query[i + 1].bytes;
                query[i].stopTX = query[i + 1].stopTX;
              }
            }
            queryIndex--;
          }
          break;
        default:
          break;
      }
      break;
    case I2C_CONFIG:
      delayTime = (argv[0] + (argv[1] << 7));

      if (delayTime > 0) {
        i2cReadDelayTime = delayTime;
      }

      if (!isI2CEnabled) {
        enableI2CPins();
      }

      break;
    case SERVO_CONFIG:
      if (argc > 4) {
        // these vars are here for clarity, they'll optimized away by the compiler
        byte pin = argv[0];
        int minPulse = argv[1] + (argv[2] << 7);
        int maxPulse = argv[3] + (argv[4] << 7);

        if (IS_PIN_DIGITAL(pin)) {
          if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
            detachServo(pin);
          }
          attachServo(pin, minPulse, maxPulse);
          setPinModeCallback(pin, PIN_MODE_SERVO);
        }
      }
      break;
    case SAMPLING_INTERVAL:
      if (argc > 1) {
        samplingInterval = argv[0] + (argv[1] << 7);
        if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) {
          samplingInterval = MINIMUM_SAMPLING_INTERVAL;
        }
      } else {
        //Firmata.sendString("Not enough data");
      }
      break;
    case EXTENDED_ANALOG:
      if (argc > 1) {
        int val = argv[1];
        if (argc > 2) val |= (argv[2] << 7);
        if (argc > 3) val |= (argv[3] << 14);
        analogWriteCallback(argv[0], val);
      }
      break;
    case CAPABILITY_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(CAPABILITY_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write((byte)INPUT);
          Firmata.write(1);
          Firmata.write((byte)PIN_MODE_PULLUP);
          Firmata.write(1);
          Firmata.write((byte)OUTPUT);
          Firmata.write(1);
        }
        if (IS_PIN_ANALOG(pin)) {
          Firmata.write(PIN_MODE_ANALOG);
          Firmata.write(10); // 10 = 10-bit resolution
        }
        if (IS_PIN_PWM(pin)) {
          Firmata.write(PIN_MODE_PWM);
          Firmata.write(DEFAULT_PWM_RESOLUTION);
        }
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write(PIN_MODE_SERVO);
          Firmata.write(14);
        }
        if (IS_PIN_I2C(pin)) {
          Firmata.write(PIN_MODE_I2C);
          Firmata.write(1);  // TODO: could assign a number to map to SCL or SDA
        }
#ifdef FIRMATA_SERIAL_FEATURE
        serialFeature.handleCapability(pin);
#endif
        Firmata.write(127);
      }
      Firmata.write(END_SYSEX);
      break;
    case PIN_STATE_QUERY:
      if (argc > 0) {
        byte pin = argv[0];
        Firmata.write(START_SYSEX);
        Firmata.write(PIN_STATE_RESPONSE);
        Firmata.write(pin);
        if (pin < TOTAL_PINS) {
          Firmata.write(Firmata.getPinMode(pin));
          Firmata.write((byte)Firmata.getPinState(pin) & 0x7F);
          if (Firmata.getPinState(pin) & 0xFF80) Firmata.write((byte)(Firmata.getPinState(pin) >> 7) & 0x7F);
          if (Firmata.getPinState(pin) & 0xC000) Firmata.write((byte)(Firmata.getPinState(pin) >> 14) & 0x7F);
        }
        Firmata.write(END_SYSEX);
      }
      break;
    case ANALOG_MAPPING_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(ANALOG_MAPPING_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127);
      }
      Firmata.write(END_SYSEX);
      break;

    case SERIAL_MESSAGE:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handleSysex(command, argc, argv);
#endif
      break;
      
    /* NEOPIXELS */
    case NEOPIXEL_DATA:
      {
        if (argv[0] == NEOPIXEL_COLOR)
        {
          int NeoPixelIndex = argv[1];
          int NeoPixelRed = argv[2];
          int NeoPixelGreen = argv[3];
          int NeoPixelBlue = argv[4];
          neopixel_strip->setPixelColor(NeoPixelIndex, NeoPixelRed, NeoPixelGreen, NeoPixelBlue);
          neopixel_strip->show();
        }
        else if (argv[0] == NEOPIXEL_FIRE)
        {
          FireStatus = argv[1];
          if (FireStatus == 1){
            neopixel_fire();
          }
        }
      }
      break;

  }
}

void enableI2CPins()
{
  byte i;
  // is there a faster way to do this? would probaby require importing
  // Arduino.h to get SCL and SDA pins
  for (i = 0; i < TOTAL_PINS; i++) {
    if (IS_PIN_I2C(i)) {
      // mark pins as i2c so they are ignore in non i2c data requests
      setPinModeCallback(i, PIN_MODE_I2C);
    }
  }

  isI2CEnabled = true;

  Wire.begin();
}

/* disable the i2c pins so they can be used for other functions */
void disableI2CPins() {
  isI2CEnabled = false;
  // disable read continuous mode for all devices
  queryIndex = -1;
}

/*==============================================================================
 * SETUP()
 *============================================================================*/

void systemResetCallback()
{
  isResetting = true;

  // initialize a defalt state
  // TODO: option to load config from EEPROM instead of default

#ifdef FIRMATA_SERIAL_FEATURE
  serialFeature.reset();
#endif

  if (isI2CEnabled) {
    disableI2CPins();
  }

  for (byte i = 0; i < TOTAL_PORTS; i++) {
    reportPINs[i] = false;    // by default, reporting off
    portConfigInputs[i] = 0;  // until activated
    previousPINs[i] = 0;
  }

  for (byte i = 0; i < TOTAL_PINS; i++) {
    // pins with analog capability default to analog input
    // otherwise, pins default to digital output

    if (25 == i) {
      // This is the NEOPIXEL pin D6, it cannot double as an Analog pin A7, so first set it to DIGITAL
      setPinModeCallback(i, OUTPUT);
    } else if (IS_PIN_ANALOG(i)) {
      // turns off pullup, configures everything
      setPinModeCallback(i, PIN_MODE_ANALOG);
    } else if (IS_PIN_DIGITAL(i)) {
      // sets the output to 0, configures portConfigInputs
      setPinModeCallback(i, OUTPUT);
    }

    servoPinMap[i] = 255;
  }
  // by default, do not report any analog inputs
  analogInputsToReport = 0;

  detachedServoCount = 0;
  servoCount = 0;

  /* send digital inputs to set the initial state on the host computer,
   * since once in the loop(), this firmware will only send on change */
  /*
  TODO: this can never execute, since no pins default to digital input
        but it will be needed when/if we support EEPROM stored config
  for (byte i=0; i < TOTAL_PORTS; i++) {
    outputPort(i, readPort(i, portConfigInputs[i]), true);
  }
  */
  isResetting = false;
}

void setup()
{
  //Serial.begin(9600);
  //while (!Serial);             // wait for serial monitor
  //Serial.println("\nFirmata debug");

  Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);

  Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
  Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
  Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
  Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
  Firmata.attach(SET_PIN_MODE, setPinModeCallback);
  Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback);
  Firmata.attach(START_SYSEX, sysexCallback);
  Firmata.attach(SYSTEM_RESET, systemResetCallback);

  // Save a couple of seconds by disabling the startup blink sequence.
  Firmata.disableBlinkVersion();

  // to use a port other than Serial, such as Serial1 on an Arduino Leonardo or Mega,
  // Call begin(baud) on the alternate serial port and pass it to Firmata to begin like this:
  Serial1.begin(57600);
  Firmata.begin(Serial1);
  // However do not do this if you are using SERIAL_MESSAGE

  //Firmata.begin(57600);
  while (!Serial1); // wait for serial port to connect. Needed for ATmega32u4-based boards

  systemResetCallback();  // reset to default config

  /* NEOPIXELS */
  // Parameter 1 = number of pixels in strip
  // Parameter 2 = Arduino pin number (most are valid)
  // Parameter 3 = pixel type flags, add together as needed:
  //   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
  //   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
  //   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
  //   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
  //   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
  neopixel_strip = new Adafruit_NeoPixel(7, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

  neopixel_strip->setBrightness(255);
  neopixel_strip->begin();
  neopixel_strip->show(); // Initialize all pixels to 'off'
  //FireStatus = 1;
  
}

/*==============================================================================
 * LOOP()
 *============================================================================*/
void loop()
{
  byte pin, analogPin;

  /* DIGITALREAD - as fast as possible, check for changes and output them to the
   * FTDI buffer using Serial.print()  */
  checkDigitalInputs();

  /* STREAMREAD - processing incoming messagse as soon as possible, while still
   * checking digital inputs.  */
  while (Firmata.available())
    Firmata.processInput();

  // TODO - ensure that Stream buffer doesn't go over 60 bytes

  currentMillis = millis();
  if (currentMillis - previousMillis > samplingInterval) {
    previousMillis += samplingInterval;
    /* ANALOGREAD - do all analogReads() at the configured sampling interval */
    for (pin = 0; pin < TOTAL_PINS; pin++) {
      if (IS_PIN_ANALOG(pin) && Firmata.getPinMode(pin) == PIN_MODE_ANALOG) {
        analogPin = PIN_TO_ANALOG(pin);
        if (analogInputsToReport & (1 << analogPin)) {
          Firmata.sendAnalog(analogPin, analogRead(analogPin));
        }
      }
    }
    // report i2c data for all device with read continuous mode enabled
    if (queryIndex > -1) {
      for (byte i = 0; i < queryIndex + 1; i++) {
        readAndReportData(query[i].addr, query[i].reg, query[i].bytes, query[i].stopTX);
      }
    }
  }

  if (FireStatus == 1){
    if(currentMillis - FirePreviousMillis > FireInterval) {
      // save the last time you blinked the LED 
      FirePreviousMillis = currentMillis;   
      neopixel_fire();
    }
  } else {
    neopixel_strip->setPixelColor(1, 0, 0, 0);
    neopixel_strip->setPixelColor(2, 0, 0, 0);
    neopixel_strip->setPixelColor(3, 0, 0, 0);
    neopixel_strip->setPixelColor(4, 0, 0, 0);
    neopixel_strip->setPixelColor(5, 0, 0, 0);
    neopixel_strip->setPixelColor(6, 0, 0, 0);
    neopixel_strip->show();
  }

#ifdef FIRMATA_SERIAL_FEATURE
  serialFeature.update();
#endif

}


 

/* NEOPIXEL FIRE */
void neopixel_fire() {
  uint16_t FireRandPIX, FireRandRED, FireRandGREEN, FireRandBLUE;
  FireRandPIX = random (1, 7);    // generate PIXEL radom, circle is LED 1-6, but as number is rounded, use 7 as max
  FireRandWAIT = random (0, 20);    // generate WAIT random to get a flickering effect
  
  FireRandRED = random (3, 8);    // generate RED led random value
  FireRandGREEN = random (1, 2);    // generate GREEN led random value
  FireRandBLUE = random (0, 0);    // generate BLUE led random value

  neopixel_strip->setPixelColor(FireRandPIX, FireRandRED, FireRandGREEN, FireRandBLUE);
  neopixel_strip->show();
  FireInterval = FireRandWAIT;
}

Python code: BarbecuePlanner.py

Python
Python script for running the Barbecue: NeoPixels, OLED, Joystick, getting weather forecasts, making Replanish request, processing the SQS queue. To be loaded to the MCU
#!/usr/bin/python
# -*- coding: utf8 -*-

import sqlite3
import qrcode
import time
import os.path
import requests
import thread
import json
import fcntl, socket, struct #needed for getting IP Address and HW Address (=device ID)
from oled.device import sh1106
from oled.render import canvas
from PyMata_bsl.pymata import PyMata
from PIL import ImageFont, Image, ImageDraw
from boto3.session import Session

import smtplib
from email.MIMEText import MIMEText
import string

import random



# Analog Input Pin
BUTTON_X = 9
BUTTON_Y = 10
# Digital Input Pin
BUTTON_SEL = 11

# Indices for data passed to callback function
PIN_MODE = 0  # This is the PyMata Pin MODE = ANALOG = 2 and DIGITAL = 0x20:
PIN_NUMBER = 1
DATA_VALUE = 2


# Settings needed for Amazon, like my DRS credentials, and Device details
Amazon_client_id = "amzn1.application-oa2-client.**************************"
Amazon_client_secret = "**************************""
Amazon_device_model = '**************************"'

Amazon_sqs_access_key_id = '**************************"'
Amazon_sqs_secret_access_key = '**************************"'
Amazon_sqs_region_name = 'us-east-1'
Amazon_sqs_queue_name='.**************************"'


# SMTP settings for email
smtp_host = "**************************""
smtp_port = "587"
smtp_user= "**************************""
smtp_password = "**************************""




# IP Address (external facing network port)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
BarbecuePlanner_ip_address = str(s.getsockname()[0])

# Hardware Address (MAC address of external facing network port)
ifname = 'apcli0'
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
BarbecuePlanner_hw_address = str(':'.join(['%02x' % ord(char) for char in info[18:24]]).replace(":", ""))


# This is the NeoPixel alarming color tool
ShowNeoPixel = 0

# Data change callback functions
AnalogBase = {'0':0};
AnalogRecent = {'0':0};
AnalogTempVal = {'0':0};
i = 1
while i < 30:
    AnalogBase[str(i)] = 0
    AnalogRecent[str(i)] = 0
    AnalogTempVal[str(i)] = 0
    i += 1

IsRunningPotentiometer = 0
def cb_potentiometer(data):
    global IsRunningPotentiometer
    if IsRunningPotentiometer == 0:
        IsRunningPotentiometer = 1
        AnalogTempVal[str(data[PIN_NUMBER])] = AnalogRecent[str(data[PIN_NUMBER])]
        if (AnalogBase[str(data[PIN_NUMBER])] == 0):
            AnalogBase[str(data[PIN_NUMBER])] = data[DATA_VALUE]
            print("Analog Data: ", " Pin: ", data[PIN_NUMBER], " Data Value: 0")

        if (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] > 50):
            AnalogRecent[str(data[PIN_NUMBER])] = 1
        elif (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] <= 50 and AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] >= -50):
            AnalogRecent[str(data[PIN_NUMBER])] = 0
        elif (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] < -50):
            AnalogRecent[str(data[PIN_NUMBER])] = -1

        if AnalogTempVal[str(data[PIN_NUMBER])] <> AnalogRecent[str(data[PIN_NUMBER])]:
            #print("Analog Data: ", " Pin: ", data[PIN_NUMBER], " Data Value: ", AnalogRecent[str(data[PIN_NUMBER])])

            BTN_Statex = ''
            if data[PIN_NUMBER] == BUTTON_X and AnalogRecent[str(data[PIN_NUMBER])] == -1: BTN_Statex += 'Up '
            if data[PIN_NUMBER] == BUTTON_X and AnalogRecent[str(data[PIN_NUMBER])] == 1: BTN_Statex += 'Down '
            if data[PIN_NUMBER] == BUTTON_Y and AnalogRecent[str(data[PIN_NUMBER])] == -1: BTN_Statex += 'Right '
            if data[PIN_NUMBER] == BUTTON_Y and AnalogRecent[str(data[PIN_NUMBER])] == 1: BTN_Statex += 'Left '
            if BTN_Statex != '':
                print("BTN: " + BTN_Statex.strip())
                runScreen(None, BTN_Statex.strip())
        IsRunningPotentiometer = 0


def cb_push_button(data):
    print("Digital Data:", " Pin: ", data[PIN_NUMBER], " Data Value: ", data[DATA_VALUE])



def UpdateAmazonRefreshToken(threadName, sleepTime):
    # Thread that will keep our connection with AmazonDRS alive
    # Will refresh our AuthToken based on the RefreshToken.
    connRefreshToken = sqlite3.connect('BarbecuePlanner.db')
    while cmd_exit == 0:
        cursorRefreshToken = connRefreshToken.execute("SELECT AmazonRefreshToken FROM BarbecueSettings LIMIT 1")
        for rowRefreshToken in cursorRefreshToken:
            if str(rowRefreshToken[0]) <> '':
                auth_grant_url = "https://api.amazon.com/auth/o2/token"
                auth_grant_data = {
                    "grant_type": "refresh_token",
                    "refresh_token": str(rowRefreshToken[0]),
                    "client_id": Amazon_client_id,
                    "client_secret": Amazon_client_secret,
                    "redirect_uri": "https://" + BarbecuePlanner_ip_address + "/amazon_login"
                }
                auth_grant_headers = {
                    "Content-Type": "application/x-www-form-urlencoded"
                }
                response_tokenrefresh = requests.post(auth_grant_url, data=auth_grant_data, headers=auth_grant_headers)
                connRefreshToken.execute("UPDATE BarbecueSettings SET AmazonAccessToken = '" + response_tokenrefresh.json()['access_token'] + "', AmazonRefreshToken = '" + response_tokenrefresh.json()['refresh_token'] + "';")
                connRefreshToken.commit()
        time.sleep(sleepTime)
    connRefreshToken.close()





# Create the Boto3 Session
boto3 = Session(
    aws_access_key_id=Amazon_sqs_access_key_id,
    aws_secret_access_key=Amazon_sqs_secret_access_key,
    region_name=Amazon_sqs_region_name,
)

# Get the service resource
sqs = boto3.resource('sqs')



def UpdateAmazonSQS(threadName, sleepTime):
    global ShowNeoPixel
    connSQSmessages = sqlite3.connect('BarbecuePlanner.db')
    
    while cmd_exit == 0:
        print 'Getting Amazon SQS messages from queue'

        # Get the queue
        queue = sqs.get_queue_by_name(QueueName=Amazon_sqs_queue_name)
        
        # Process messages by printing out body and optional author name
        for message in queue.receive_messages(VisibilityTimeout=1, MaxNumberOfMessages=10, WaitTimeSeconds=5):
            SQSMessageBody = json.loads(message.body)['Message']
            print json.dumps(json.loads(SQSMessageBody), indent=4, sort_keys=True)
        
            print json.loads(SQSMessageBody)['notificationInfo']['notificationType']
        
            if json.loads(SQSMessageBody)['deviceInfo']['deviceIdentifier']['serialNumber'] == BarbecuePlanner_hw_address:
                ShowNeoPixel = 0
                # This is my device
        
                if json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "OrderPlacedNotification":
                    connSQSmessages.execute("UPDATE BarbecueEventOrder SET Status = 2, Asin = '" + json.loads(SQSMessageBody)['orderInfo']['productInfo'][0]['asin'] + "', EstDeliveryDate = '" + json.loads(SQSMessageBody)['orderInfo']['productInfo'][0]['estimatedDeliveryDate'] + "' WHERE InstanceId = '" + json.loads(SQSMessageBody)['orderInfo']['instanceId'] + "' and Status in (0,1,6);")
                    connSQSmessages.commit()
        
                elif json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "OrderCancelledNotification":
                    connSQSmessages.execute("UPDATE BarbecueEventOrder SET Status = 3, EstDeliveryDate = '' WHERE InstanceId = '" + json.loads(SQSMessageBody)['orderInfo']['instanceId'] + "' and Status in (0,1,2,6);")
                    connSQSmessages.commit()
        
                elif json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "ItemShippedNotification":
                    connSQSmessages.execute("UPDATE BarbecueEventOrder SET Status = 4, Asin = '" + json.loads(SQSMessageBody)['orderInfo']['productInfo'][0]['asin'] + "', EstDeliveryDate = '" + json.loads(SQSMessageBody)['orderInfo']['productInfo'][0]['estimatedDeliveryDate'] + "' WHERE InstanceId = '" + json.loads(SQSMessageBody)['orderInfo']['instanceId'] + "' and Status in (0,1,2,6);")
                    connSQSmessages.commit()
        
                elif json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "SubscriptionChangedNotification":
                    for key, value in json.loads(SQSMessageBody)['subscriptionInfo']['slotsSubscriptionStatus'].items():
        
                        if json.loads(SQSMessageBody)['subscriptionInfo']['slotsSubscriptionStatus'][str(key)]['subscribed']:
                            # add/update subscription
                            connSQSmessages.execute("UPDATE AmazonSlots SET Selected = 0 WHERE SlotId = '" + str(key) + "';")
                            connSQSmessages.commit()
                            connSQSmessages.execute("UPDATE AmazonSlots SET Selected = 1 WHERE SlotId = '" + str(key) + "' AND Asin = '" + str(json.loads(SQSMessageBody)['subscriptionInfo']['slotsSubscriptionStatus'][str(key)]['productInfoList'][0]['asin']) + "';")
                            connSQSmessages.commit()
                        else:
                            # remove subscription
                            connSQSmessages.execute("UPDATE AmazonSlots SET Selected = 0 WHERE SlotId = '" + str(key) + "';")
                            connSQSmessages.commit()
        
                elif json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "DeviceRegisteredNotification":
                    pass
        
                elif json.loads(SQSMessageBody)['notificationInfo']['notificationType'] == "DeviceDeregisteredNotification":
                    pass

                # message received and processed
                firmata.neopixel_color(0, 0, 0, 255)
                time.sleep(1)
                firmata.neopixel_color(0, 0, 0, 0)

            # Let the queue know that the message is processed
            message.delete()
            print('')

        print 'Done getting Amazon SQS messages from queue, wait for next round'
        firmata.neopixel_color(0, 0, 0, 0)
        time.sleep(sleepTime)

    connSQSmessages.close()
    





MenuLine = 0
PartyLine = 0
ForecastLine = 0
ForecastFromLine = 0
FriendLine = 0
FriendFromLine = 0
FriendSelected = ''
FriendOK = 0
menu_screen = 0
menu_next = 0
BbqSelectedDate = time.strftime("%Y-%m-%d")

# Function for menu and others
def runScreen(GotoScreen=None, ButtonDirection=None):
    global ShowNeoPixel
    ShowNeoPixel = 0
    global menu_screen, menu_next, MenuLine, PartyLine, ForecastLine, ForecastFromLine, BbqSelectedDate, FriendLine, FriendFromLine, FriendSelected, FriendOK
    connRunScreen = sqlite3.connect('BarbecuePlanner.db')

    # Check if Joystick is used
    if GotoScreen != None:
        BTN_State = 'Neutral'
        menu_screen = GotoScreen
        menu_next = GotoScreen

        if GotoScreen == 0:  # Homescreen
            MenuLine = 0

        if GotoScreen == 10:  # BQQ Time
            ForecastLine = 1
            ForecastFromLine = 1

    else:
        BTN_State = ButtonDirection


    if menu_screen == 0:  # home screen to show temperatures
        if BTN_State == 'Up':
            if MenuLine > 0: MenuLine -= 1
            if MenuLine == 2: MenuLine = 1
            menu_next = 0
        elif BTN_State == 'Down':
            MenuLine += 1
            if MenuLine == 2: MenuLine = 3
            if (MenuLine > 3): MenuLine = 3
            menu_next = 0
        elif BTN_State == 'Left':
            menu_next = 0
        elif BTN_State == 'Right' or BTN_State == 'Press':
            if MenuLine == 0: menu_next = 10 #Forecast

            if MenuLine == 1: #Party
                cursorRunScreen1 = connRunScreen.execute("SELECT count(Party) FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "'")
                for row1 in cursorRunScreen1:
                    if str(row1[0]) > 0:
                        menu_next = 2
                    else:
                        menu_next = 0

            if MenuLine == 3: menu_next = 1 #Setup


    elif menu_screen == 1:  # QR setup link
        menu_next = 0


    elif menu_screen == 2:  # Party page
        if BTN_State == 'Up':
            if PartyLine > 0: PartyLine -= 1
            menu_next = 2
        elif BTN_State == 'Down':
            PartyLine += 1
            menu_next = 2
        elif BTN_State == 'Left':
            menu_next = 0
        elif BTN_State == 'Right' or BTN_State == 'Press':
            menu_next = 0


    elif menu_screen == 10:  # BBQ Time
        if BTN_State == 'Up':
            if ForecastLine > 1: ForecastLine -= 1
            menu_next = 10
        elif BTN_State == 'Down':
            ForecastLine += 1
            menu_next = 10
        elif BTN_State == 'Left':
            menu_next = 0
        elif BTN_State == 'Right' or BTN_State == 'Press':
            menu_next = 11

    elif menu_screen == 11:  # BBQ Time Details
        if BTN_State == 'Up':
            if ForecastLine > 1: ForecastLine -= 1
            menu_next = 11
        elif BTN_State == 'Down':
            ForecastLine += 1
            menu_next = 11
        elif BTN_State == 'Left':
            menu_next = 10
        elif BTN_State == 'Right'or BTN_State == 'Press':
            cursorRunScreen1 = connRunScreen.execute("SELECT Date, Advice, Party FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' ORDER BY Date ASC LIMIT 1 OFFSET " + str(ForecastLine - 1))
            for row1 in cursorRunScreen1:
                if str(row1[1]) == '1' and str(row1[2]) == '0':
                    menu_next = 12
                else:
                    menu_next = 11

    elif menu_screen == 12:  # BBQ Time Invite Friends
        if BTN_State == 'Up':
            if FriendLine > 1: FriendLine -= 1
            menu_next = 12
        elif BTN_State == 'Down':
            FriendLine += 1
            menu_next = 12
        elif BTN_State == 'Left':
            menu_next = 11
        elif BTN_State == 'Right' or BTN_State == 'Press':
            if FriendOK == 1:
                menu_next = 13
            else:
                menu_next = 12

                cursorRunScreen = connRunScreen.execute("SELECT BarbecueFriends.Name, ifnull(BarbecueEventFriends.Friend,'') FROM BarbecueFriends LEFT OUTER JOIN BarbecueEventFriends on (BarbecueFriends.Name = BarbecueEventFriends.Friend and BarbecueEventFriends.Date = '" + BbqSelectedDate + "') ORDER BY BarbecueFriends.Name ASC LIMIT 1 OFFSET " + str(FriendLine - 1) + ";")
                for row in cursorRunScreen:
                    FriendSelected = row[0]
                    if row[1] == '':
                        connRunScreen.execute("INSERT INTO BarbecueEventFriends (Friend, Date) SELECT '" + FriendSelected + "', '" + BbqSelectedDate + "' WHERE NOT EXISTS (SELECT 1 FROM BarbecueEventFriends WHERE Friend = '" + FriendSelected + "' AND Date = '" + BbqSelectedDate + "');")
                        connRunScreen.commit()
                    else:
                        connRunScreen.execute("DELETE FROM BarbecueEventFriends WHERE Friend = '" + FriendSelected + "' AND Date = '" + BbqSelectedDate + "';")
                        connRunScreen.commit()

    elif menu_screen == 13:  # Confirmation
        if BTN_State == 'Up':
            menu_next = 13
        elif BTN_State == 'Down':
            menu_next = 13
        elif BTN_State == 'Left':
            menu_next = 12
        elif BTN_State == 'Right'or BTN_State == 'Press':
            menu_next = 14


    print menu_next

    # Text to be rotated...
    rotate_text = ">"
    img_txt = Image.new('1', font_small.getsize(rotate_text))
    draw_txt = ImageDraw.Draw(img_txt)
    draw_txt.text((0, 0), rotate_text, font=font_small, fill=255)




    if menu_next == 0:  # home screen to show temperatures
        i = 0

        MenuTextForecast = 'Forecast'
        cursorRunScreen = connRunScreen.execute("SELECT count(Date) FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' AND Advice = '1'")
        for row in cursorRunScreen:
            if (row[0] > 0): MenuTextForecast = 'Forecast << BBQ'

        MenuTextParty = 'Party: none'
        cursorRunScreen = connRunScreen.execute("SELECT count(Date), Date FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' AND Party = '1' ORDER BY Date LIMIT 1")
        for row in cursorRunScreen:
            if (row[0] > 0): MenuTextParty = 'Party: ' + time.strftime("%a %d %b", time.strptime(str(row[1]), "%Y-%m-%d"))

        MenuText = [MenuTextForecast,MenuTextParty,'','Setup website']

        with canvas(device) as draw:
            draw.bitmap((-13, 0), logo, fill=1)
            draw.text((39, -2), "BarbecuePlanner", font=font_small, fill=255)
            draw.line((39, 9, device.width - 1, 9), fill=255)

            while i < 4:
                if i == (MenuLine):
                    draw.rectangle((39, 12 + (i * 12), device.width - 1, 24 + (i * 12)),outline=255, fill=255)
                    draw.text((40, 13 + (i * 12)), MenuText[i], font=font_small, fill=0)
                else:
                    draw.text((40, 13 + (i * 12)), MenuText[i], font=font_small, fill=255)
                i += 1



    elif menu_next == 1:  # QR code for web-setup
        img = GenerateQR('')
        with canvas(device) as draw:
            draw.rectangle((0, 0, device.width - 1, device.height - 1), outline=0, fill=0)
            draw.bitmap((0, 0), img, fill=1)
            draw.text((60, 0), 'Scan the', font=font_small, fill=255)
            draw.text((60, 12), 'QR code', font=font_small, fill=255)

            IpTextW, IpTextH = font_small.getsize('https://'+BarbecuePlanner_ip_address+'/')
            draw.text((device.width - IpTextW, 54), 'https://'+BarbecuePlanner_ip_address+'/', font=font_small, fill=255)



    elif menu_next == 2:  # Party info page
        cursorRunScreen = connRunScreen.execute("SELECT count(Date), Date FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' AND Party = '1' ORDER BY Date LIMIT 1")
        for row in cursorRunScreen:
            PartyDate = str(row[1])

        PartyTotalLine = 5
        cursorRunScreen = connRunScreen.execute("SELECT COUNT(Friend) FROM BarbecueEventFriends WHERE Date = '" + PartyDate + "'")
        for row in cursorRunScreen:
            PartyTotalLine += row[0]
        cursorRunScreen = connRunScreen.execute("SELECT COUNT(InstanceId) FROM BarbecueEventOrder WHERE Date = '" + PartyDate + "'")
        for row in cursorRunScreen:
            PartyTotalLine += row[0]

        if (PartyLine > PartyTotalLine-3): PartyLine  = PartyTotalLine-3

        cursorRunScreen = connRunScreen.execute("SELECT Date, Party, TempAft, TempEv, RainAft, RainEv FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' AND Party = 1 ORDER BY Date ASC LIMIT 1")
        for row in cursorRunScreen:
            ForecastTextBox = [time.strftime("%A %d %B", time.strptime(str(row[0]), "%Y-%m-%d")),
                                "Temp " + str(float(row[2])) + ' / ' + str(float(row[3])) + ' "C',
                                "Rain " + str(float(row[4])) + ' / ' + str(float(row[5])) + ' mm']

        FriendsTextBox = []
        cursorRunScreen = connRunScreen.execute("SELECT Friend FROM BarbecueEventFriends WHERE Date = '" + PartyDate + "'")
        for row in cursorRunScreen:
            FriendsTextBox += ['  ' + str(row[0])]

        OrderTextBox = []
        cursorRunScreen = connRunScreen.execute("SELECT DISTINCT AmazonSlots.SlotDescription, AmazonSlots.SlotType, BarbecueEventOrder.Status FROM BarbecueEventOrder left outer join AmazonSlots on (BarbecueEventOrder.SlotId = AmazonSlots.SlotId) WHERE BarbecueEventOrder.Date = '" + PartyDate + "' ORDER BY AmazonSlots.SlotDescription ASC")
        for row in cursorRunScreen:
            OrderText = str(row[0])
            if str(row[1]) <> '':
                OrderText += " (" + str(row[1]) + "p.)"

            OrderText += " - "

            if str(row[2]) == '1':
                OrderText += "Submitted"
            elif str(row[2]) == '2':
                OrderText += "Confirmed"
            elif str(row[2]) == '3':
                OrderText += "Canceled"
            elif str(row[2]) == '4':
                OrderText += "Shipped"
            elif str(row[2]) == '5':
                OrderText += "Error"
            elif str(row[2]) == '6':
                OrderText += "Test order"
            else:
                OrderText += "???"

            OrderTextBox += ['  ' + OrderText]


        PartyTextBox = ForecastTextBox \
                       + ["Invited friends:"] \
                       + FriendsTextBox \
                       + ["Amazon DRS Order:"] \
                       + OrderTextBox


        with canvas(device) as draw:
            draw.text((0, -2), "PARTY time", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)

            i = PartyLine
            while i < PartyLine+3:
                draw.text((1, 22 + ((i-PartyLine) * 12)), PartyTextBox[i], font=font_small, fill=255)
                i += 1

            if PartyLine > 1:
                draw.bitmap((device.width-12,22), img_txt.rotate(90, expand=1), fill=1)

            if PartyLine < PartyTotalLine-3:
                draw.bitmap((device.width - 12, device.height-6), img_txt.rotate(270, expand=1), fill=1)



    elif menu_next == 10:  # BQQ Time
        ForecastTotalLine = 0
        cursorRunScreen = connRunScreen.execute("SELECT count(Date) FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "'")
        for row in cursorRunScreen:
            if (ForecastLine > row[0]): ForecastLine = row[0]
            ForecastTotalLine = row[0]

        if (ForecastLine == 0): ForecastLine = 1
        if (ForecastLine <= 3): ForecastFromLine = 1
        if (ForecastLine > 3): ForecastFromLine = (ForecastLine - 2)

        i = ForecastFromLine
        cursorRunScreen = connRunScreen.execute("SELECT Date, Advice, Party FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' ORDER BY Date ASC LIMIT 3 OFFSET " + str(ForecastFromLine-1))
        with canvas(device) as draw:
            draw.text((0, -2), "Forecast", font=font_large, fill=255)
            draw.line((0, 18, device.width - 1, 18), fill=255)
            for row in cursorRunScreen:
                PartyText = ""
                if str(row[2]) == '1': PartyText = " << PARTY"
                elif str(row[1]) == '1': PartyText = " << BBQ"
                if i == (ForecastLine):
                    draw.rectangle((0, 21 + ((i-ForecastFromLine) * 12), device.width, 33 + ((i-ForecastFromLine) * 12)), outline=255, fill=255)
                    draw.text((1, 22 + ((i-ForecastFromLine) * 12)), time.strftime("%a %d %b", time.strptime(str(row[0]),"%Y-%m-%d")) + PartyText, font=font_small, fill=0)
                else:
                    draw.text((1, 22 + ((i-ForecastFromLine) * 12)), time.strftime("%a %d %b", time.strptime(str(row[0]),"%Y-%m-%d")) + PartyText, font=font_small, fill=255)
                i += 1

            if ForecastFromLine > 1:
                draw.bitmap((device.width-12,22), img_txt.rotate(90, expand=1), fill=1)

            if ForecastTotalLine > i-1:
                draw.bitmap((device.width - 12, device.height-6), img_txt.rotate(270, expand=1), fill=1)




    elif menu_next == 11:  # BBQ Time Details
        cursorRunScreen = connRunScreen.execute("SELECT count(Date) FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "'")
        for row in cursorRunScreen:
            if (ForecastLine > row[0]): ForecastLine = row[0]

        if (ForecastLine == 0): ForecastLine = 1

        with canvas(device) as draw:

            cursorRunScreen1 = connRunScreen.execute("SELECT Date, Advice, Party FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' ORDER BY Date ASC LIMIT 1 OFFSET " + str(ForecastLine - 1))
            for row1 in cursorRunScreen1:
                if str(row1[2]) == '1':
                    draw.text((0, -2), "PARTY time", font=font_large, fill=255)
                elif str(row1[1]) == '1':
                    draw.text((0, -2), "BBQ weather", font=font_large, fill=255)
                else:
                    draw.text((0, -2), "Forecast", font=font_large, fill=255)
                draw.line((0, 18, device.width-1, 18), fill=255)

                cursorRunScreen = connRunScreen.execute("SELECT Date, Party, TempAft, TempEv, RainAft, RainEv FROM BarbecueEvents WHERE Date > '" + time.strftime("%Y-%m-%d") + "' ORDER BY Date ASC LIMIT 1 OFFSET " + str(ForecastLine - 1))
                for row in cursorRunScreen:
                    BbqSelectedDate = row[0]
                    draw.text((0, 22), time.strftime("%A %d %B", time.strptime(str(row[0]), "%Y-%m-%d")), font=font_small, fill=255)
                    draw.text((0, 34), "Temp " + str(float(row[2])) + ' / ' + str(float(row[3])) + ' "C', font=font_small, fill=255)
                    draw.text((0, 46), "Rain " + str(float(row[4])) + ' / ' + str(float(row[5])) + ' mm', font=font_small, fill=255)

                if str(row1[1]) == '1' and str(row1[2]) == '0':
                    draw.rectangle((device.width-38, device.height-12, device.width - 1, device.height), outline=255, fill=255)
                    draw.text((device.width-37, device.height-11), "Party >", font=font_small, fill=0)





    elif menu_next == 12:  # BBQ Time Invite Friends
        FriendOK = 0
        cursorRunScreen = connRunScreen.execute("SELECT count(Name) FROM BarbecueFriends")
        for row in cursorRunScreen:
            if (FriendLine > row[0] + 1): FriendLine = row[0] + 1
            FriendsTotalLine = row[0] + 1

        if (FriendLine == 0): FriendLine = 1
        if (FriendLine <= 3): FriendFromLine = 1
        if (FriendLine > 3): FriendFromLine = (FriendLine - 2)

        i = FriendFromLine
        cursorRunScreen = connRunScreen.execute("SELECT BarbecueFriends.Name, ifnull(BarbecueEventFriends.Friend,'') FROM BarbecueFriends left outer join BarbecueEventFriends on (BarbecueFriends.Name = BarbecueEventFriends.Friend and BarbecueEventFriends.Date = '" + BbqSelectedDate + "') ORDER BY BarbecueFriends.Name ASC LIMIT 3 OFFSET " + str(FriendFromLine-1) + ";")
        with canvas(device) as draw:
            draw.text((0, -2), "Invite Friends", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)
            for row in cursorRunScreen:
                if i == (FriendLine):
                    draw.rectangle((0, 21 + ((i-FriendFromLine) * 12), device.width - 1, 33 + ((i-FriendFromLine) * 12)), outline=255, fill=255)
                    draw.ellipse((1, 22 + ((i - FriendFromLine) * 12),1 + 10, 22 + ((i - FriendFromLine) * 12) + 10), outline=0, fill=255)
                    if str(row[1]) != '': draw.ellipse((4, 25 + ((i - FriendFromLine) * 12), 8, 29 + ((i - FriendFromLine) * 12)), outline=0, fill=0)
                    draw.text((15, 22 + ((i-FriendFromLine) * 12)), str(row[0]), font=font_small, fill=0)
                else:
                    draw.ellipse((1, 22 + ((i - FriendFromLine) * 12), 1 + 10, 22 + ((i - FriendFromLine) * 12) + 10), outline=255, fill=0)
                    if str(row[1]) != '': draw.ellipse((4, 25 + ((i - FriendFromLine) * 12), 8, 29 + ((i - FriendFromLine) * 12)), outline=255, fill=255)
                    draw.text((15, 22 + ((i - FriendFromLine) * 12)), str(row[0]), font=font_small, fill=255)
                i += 1

            if FriendLine == i:
                cursorRunScreen1 = connRunScreen.execute("SELECT count(Friend) FROM BarbecueEventFriends WHERE Date = '" + BbqSelectedDate + "';")
                for row1 in cursorRunScreen1:
                    FriendOK = 1
                    InviteText = "OK, invite " + str(row1[0]) + " friends >"
                    InviteTextW,InviteTextH = font_small.getsize(InviteText)
                    draw.rectangle((device.width - InviteTextW - 3, 21 + ((i - FriendFromLine) * 12), device.width - 1, 33 + ((i - FriendFromLine) * 12)), outline=255, fill=255)
                    draw.text((device.width - InviteTextW - 2, 22 + ((i - FriendFromLine) * 12)), InviteText, font=font_small, fill=0)
                i += 1

            if FriendFromLine > 1:
                draw.bitmap((device.width-12,22), img_txt.rotate(90, expand=1), fill=1)

            if FriendsTotalLine > i-1:
                draw.bitmap((device.width - 12, device.height-6), img_txt.rotate(270, expand=1), fill=1)



    elif menu_next == 13:  # BBQ Confirmation
        cursorRunScreen1 = connRunScreen.execute("SELECT count(Friend) FROM BarbecueEventFriends WHERE Date = '" + BbqSelectedDate + "';")
        for row1 in cursorRunScreen1:
            InviteNr = row1[0]
            if InviteNr > 9:
                InviteNrTxt = "11-20"
            else:
                InviteNrTxt = "1-10"

        with canvas(device) as draw:
            draw.text((0, -2), "Confirmation", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)
            draw.text((0, 22), "Party at " + time.strftime("%a %d %B", time.strptime(BbqSelectedDate, "%Y-%m-%d")), font=font_small, fill=255)
            draw.text((0, 34), "Invitation to " + str(InviteNr) + " friends", font=font_small, fill=255)
            draw.text((0, 46), "Order for " + InviteNrTxt + " persons", font=font_small, fill=255)
            draw.rectangle((device.width-23, device.height-12, device.width - 1, device.height), outline=255, fill=255)
            draw.text((device.width-22, device.height-11), "OK >", font=font_small, fill=0)




    elif menu_next == 14:  # Invitation sending
        cursorRunScreen1 = connRunScreen.execute("SELECT count(Friend) FROM BarbecueEventFriends WHERE Date = '" + BbqSelectedDate + "';")
        for row1 in cursorRunScreen1:
            InviteNr = row1[0] # Nr of friends, excluding yourself

        with canvas(device) as draw:
            draw.text((0, -2), "Processing", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)
            draw.text((0, 22), "Getting ready...", font=font_small, fill=255)

        firmata.neopixel_color(0, 10, 0, 10)


        connRunScreen.execute("UPDATE BarbecueEvents SET Party = 1 WHERE Date = '" + BbqSelectedDate + "';")
        connRunScreen.commit()


        ### Send email invitations
        cursorRunScreen1 = connRunScreen.execute("SELECT BarbecueUserName, BarbecueUserEmail FROM BarbecueSettings LIMIT 1")
        for row2 in cursorRunScreen1:
            sender_name = str(row2[0])
            sender_email = str(row2[1])

        receiver_name = ""
        receiver_email = ""
        cursorRunScreen1 = connRunScreen.execute("SELECT BarbecueFriends.Name, BarbecueFriends.Email FROM BarbecueEventFriends inner join BarbecueFriends on (BarbecueEventFriends.Friend = BarbecueFriends.Name) WHERE BarbecueEventFriends.Date = '" + BbqSelectedDate + "'")
        for row2 in cursorRunScreen1:
            receiver_name += str(row2[0]) + ', '
            receiver_email += str(row2[1]) + ';'

        rcpt_email = receiver_email.split(";") + [sender_email]

        email_subject = 'BarbecuePlanner invitation'
        email_body = 'Hello ' + receiver_name + '\r\n\r\nAt ' + time.strftime("%A %d %B %Y", time.strptime(BbqSelectedDate, "%Y-%m-%d")) + ', ' + sender_name + ' will be holding a barbecue party, and you are invited!\r\nPlease let ' + sender_name \
               + ' know if you will attend. ' + sender_name + ' can then let his BarbecuePlanner know what to order from Amazon Dash Replenishment.\r\n\r\nTrust to see you at the party!\r\n\r\nRegards,\r\n' \
               + sender_name + '\r\n\r\n\r\n\r\nThis message was sent from BarbecuePlanner "' + BarbecuePlanner_hw_address + '"'

        email_msg = string.join((
            "From: %s" % sender_email,
            "To: %s" % receiver_email,
            "Cc: %s" % sender_email,
            "Subject: %s" % email_subject,
            "",
            email_body,
        ), "\r\n")

        server = smtplib.SMTP()
        server.connect(smtp_host, smtp_port)
        server.starttls()
        server.login(smtp_user, smtp_password)
        server.sendmail(sender_email, rcpt_email, email_msg)
        server.close()

        firmata.neopixel_color(0, 0, 255, 0)
        time.sleep(1)
        firmata.neopixel_color(0, 10, 0, 10)

        with canvas(device) as draw:
            draw.text((0, -2), "Processing", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)
            draw.text((0, 22), "Sent " + str(InviteNr) + " invitations", font=font_small, fill=255)





        cursorRunScreen = connRunScreen.execute("SELECT AmazonAccessToken, AmazonRefreshToken FROM BarbecueSettings LIMIT 1")
        for row in cursorRunScreen:
            if str(row[0]) <> '':

                replenish_url = "https://dash-replenishment-service-na.amazon.com/replenish/"

                replenish_headers = {
                    "Authorization": "Bearer " + str(row[0]),
                    "x-amzn-accept-type": "com.amazon.dash.replenishment.DrsReplenishResult@1.0",
                    "x-amzn-type-version": "com.amazon.dash.replenishment.DrsReplenishInput@1.0"
                }

                SlotId = "a79d13f8-1b5e-451d-bbc7-fc01f28fc8c5" #Charcoal
                response_replenish_Charcoal = requests.post(replenish_url+SlotId, headers=replenish_headers)
                print json.dumps(response_replenish_Charcoal.json(), indent=4, sort_keys=True)

                if 'detailCode' in response_replenish_Charcoal.json():
                    if (response_replenish_Charcoal.json()['detailCode'] == 'STANDARD_ORDER_PLACED'):
                        SlotCharcoalResponse = 'OK'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Charcoal.json()['eventInstanceId'] + "', '" + SlotId + "', 1);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                    elif (response_replenish_Charcoal.json()['detailCode'] == 'ORDER_INPROGRESS'):
                        SlotCharcoalResponse = 'WAIT'
                        firmata.neopixel_color(0, 123, 123, 0)
                    elif (response_replenish_Charcoal.json()['detailCode'] == 'TEST_ORDER_PLACED'):
                        SlotCharcoalResponse = 'TEST'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Charcoal.json()['eventInstanceId'] + "', '" + SlotId + "', 6);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                else:
                    SlotCharcoalResponse = 'ERROR'
                    firmata.neopixel_color(0, 255, 0, 0)

                time.sleep(1)
                firmata.neopixel_color(0, 10, 0, 10)

                with canvas(device) as draw:
                    draw.text((0, -2), "Processing", font=font_large, fill=255)
                    draw.line((0, 18, device.width - 1, 18), fill=255)
                    draw.text((0, 22), "Sent " + str(InviteNr) + " invitations", font=font_small, fill=255)
                    draw.text((0, 34), "Ordered Charcoal (" + SlotCharcoalResponse + ")", font=font_small, fill=255)


                if InviteNr > 9:
                    SlotId = "4b5bd131-00ec-4912-a1ca-d73d24116a5c" #Softdrinks (11-20 persons)
                else:
                    SlotId = "098a5ffb-20b9-48e9-bf67-32f685762fe1" #Softdrinks (1-10 persons)
                response_replenish_Softdrinks = requests.post(replenish_url+SlotId, headers=replenish_headers)
                print json.dumps(response_replenish_Softdrinks.json(), indent=4, sort_keys=True)

                if 'detailCode' in response_replenish_Softdrinks.json():
                    if (response_replenish_Softdrinks.json()['detailCode'] == 'STANDARD_ORDER_PLACED'):
                        SlotSoftdrinksResponse = 'OK'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Softdrinks.json()['eventInstanceId'] + "', '" + SlotId + "', 1);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                    elif (response_replenish_Softdrinks.json()['detailCode'] == 'ORDER_INPROGRESS'):
                        SlotSoftdrinksResponse = 'WAIT'
                        firmata.neopixel_color(0, 123, 123, 0)
                    elif (response_replenish_Softdrinks.json()['detailCode'] == 'TEST_ORDER_PLACED'):
                        SlotSoftdrinksResponse = 'TEST'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Softdrinks.json()['eventInstanceId'] + "', '" + SlotId + "', 6);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                else:
                    SlotSoftdrinksResponse = 'ERROR'
                    firmata.neopixel_color(0, 255, 0, 0)

                time.sleep(1)
                firmata.neopixel_color(0, 10, 0, 10)

                with canvas(device) as draw:
                    draw.text((0, -2), "Processing", font=font_large, fill=255)
                    draw.line((0, 18, device.width - 1, 18), fill=255)
                    draw.text((0, 22), "Sent " + str(InviteNr) + " invitations", font=font_small, fill=255)
                    draw.text((0, 34), "Ordered Charcoal (" + SlotCharcoalResponse + ")", font=font_small, fill=255)
                    draw.text((0, 46), "Ordered Drinks (" + SlotSoftdrinksResponse + ")", font=font_small, fill=255)


                if InviteNr > 9:
                    SlotId = "aee675b4-14cc-4e18-ab96-d7ff6d6bf965" #Sauce (11-20 persons)
                else:
                    SlotId = "4bffbcd7-d6c9-401a-9005-e8f80d62d254"  #Sauce (1-10 persons)
                response_replenish_Sauce = requests.post(replenish_url+SlotId, headers=replenish_headers)
                print json.dumps(response_replenish_Sauce.json(), indent=4, sort_keys=True)

                if 'detailCode' in response_replenish_Sauce.json():
                    if (response_replenish_Sauce.json()['detailCode'] == 'STANDARD_ORDER_PLACED'):
                        SlotSauceResponse = 'OK'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Sauce.json()['eventInstanceId'] + "', '" + SlotId + "', 1);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                    elif (response_replenish_Sauce.json()['detailCode'] == 'ORDER_INPROGRESS'):
                        SlotSauceResponse = 'WAIT'
                        firmata.neopixel_color(0, 123, 123, 0)
                    elif (response_replenish_Sauce.json()['detailCode'] == 'TEST_ORDER_PLACED'):
                        SlotSauceResponse = 'TEST'
                        connRunScreen.execute("INSERT INTO BarbecueEventOrder (Date, InstanceId, SlotId, Status) VALUES ('" + BbqSelectedDate + "', '" + response_replenish_Sauce.json()['eventInstanceId'] + "', '" + SlotId + "', 6);")
                        connRunScreen.commit()
                        firmata.neopixel_color(0, 0, 255, 0)
                else:
                    SlotSauceResponse = 'ERROR'
                    firmata.neopixel_color(0, 255, 0, 0)

                time.sleep(1)
                firmata.neopixel_color(0, 10, 0, 10)

                with canvas(device) as draw:
                    draw.text((0, -2), "Processing", font=font_large, fill=255)
                    draw.line((0, 18, device.width - 1, 18), fill=255)
                    draw.text((0, 22), "Ordered Charcoal (" + SlotCharcoalResponse + ")", font=font_small, fill=255)
                    draw.text((0, 34), "Ordered Drinks (" + SlotSoftdrinksResponse + ")", font=font_small, fill=255)
                    draw.text((0, 46), "Ordered Sauce (" + SlotSauceResponse + ")", font=font_small, fill=255)


        time.sleep(1)
        firmata.neopixel_color(0, 0, 255, 0)


        with canvas(device) as draw:
            draw.text((0, -2), "Done !!!", font=font_large, fill=255)
            draw.line((0, 18, device.width-1, 18), fill=255)
            draw.text((0, 22), "Get ready to party!", font=font_small, fill=255)

        time.sleep(1)
        firmata.neopixel_color(0, 10, 0, 10)
        time.sleep(1)
        firmata.neopixel_color(0, 0, 0, 0)
        menu_screen = menu_next
        thread.start_new_thread(runScreen, (0,None))







    menu_screen = menu_next
    connRunScreen.close()







def GenerateQR(url):
    # https://github.com/lincolnloop/python-qrcode
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=2,
        border=1,
    )
    qr.add_data('https://' + BarbecuePlanner_ip_address + '/' + url)
    qr.make(fit=True)

    img = qr.make_image()
    return img



def CheckSetup(threadName, sleepTime):
    global ShowNeoPixel
    ShowNeoPixel = 0
    SetupComplete = -1
    while cmd_exit == 0 and SetupComplete <> 0:
        SetupComplete = 0
        cursor = conn.execute("SELECT YrCity, BarbecueTemp, BarbecueRain, AmazonAccessToken, BarbecueUserName, BarbecueUserEmail FROM BarbecueSettings")
        for row in cursor:
            if row[0] == '': SetupComplete += 1
            if row[1] == '': SetupComplete += 1
            if row[2] == '': SetupComplete += 1
            if row[3] == '': SetupComplete += 1
            if row[4] == '': SetupComplete += 1
            if row[5] == '': SetupComplete += 1

        cursor = conn.execute("SELECT count(Day) FROM BarbecueDays WHERE YesNo = 1")
        for row in cursor:
            if row[0] == 0: SetupComplete += 1

        print ''
        if SetupComplete > 0:
            print "setup needed"
            img = GenerateQR('s')

            with canvas(device) as draw:
                draw.rectangle((0, 0, device.width - 1, device.height - 1), outline=0, fill=0)
                draw.bitmap((0, 0), img, fill=1)
                draw.text((60, 0), 'Scan the', font=font_small, fill=255)
                draw.text((60, 12), 'QR code', font=font_small, fill=255)
                draw.text((60, 24), 'to start!', font=font_small, fill=255)

                IpTextW, IpTextH = font_small.getsize('https://'+BarbecuePlanner_ip_address+'/s')
                draw.text((device.width - IpTextW, 54), 'https://'+BarbecuePlanner_ip_address+'/s', font=font_small, fill=255)

            i = 0
            while i < sleepTime/2:
                firmata.neopixel_color(0, 255, 0, 0)
                time.sleep(1)
                firmata.neopixel_color(0, 0, 0, 0)
                time.sleep(1)
                i += 1

        else:
            print "setup complete"
            with canvas(device) as draw:
                draw.bitmap((32, 0), logo, fill=1)
            firmata.neopixel_color(0, 0, 255, 0)
            time.sleep(1)
            firmata.neopixel_color(0, 0, 0, 0)



def NeoPixelAlert(ThreadName,AlertType):
    global ShowNeoPixel
    ShowNeoPixel = 1
    print 'Start NeoPixelAlert, type:' + AlertType

    NeoPixelRed = 0
    NeoPixelBlue = 0
    NeoPixelGreen = 0
    if AlertType == 'ForecastAlert':
        while cmd_exit == 0 and ShowNeoPixel == 1:
            NeoPixelRed = random.randint(20, 200)
            NeoPixelBlue = random.randint(20, 200)
            NeoPixelGreen = random.randint(20, 200)
            firmata.neopixel_color(0, NeoPixelRed, NeoPixelBlue, NeoPixelGreen)
            time.sleep(0.1)
        firmata.neopixel_color(0, 0, 0, 0)


def CheckYR(threadName, sleepTime):
    global ShowNeoPixel
    connCheckYR = sqlite3.connect('BarbecuePlanner.db')

    while cmd_exit == 0:
        print 'Getting YR weather'

        cursorCheckYR = connCheckYR.execute("SELECT YrCity, BarbecueTemp, BarbecueRain FROM BarbecueSettings")
        for row in cursorCheckYR:
            city = str(row[0])
            BarbecueTemp = row[1]
            BarbecueRain = row[2]

        BarbecueDays = []
        cursorCheckYR = connCheckYR.execute("SELECT Day FROM BarbecueDays where YesNo = 1")
        for row in cursorCheckYR:
            BarbecueDays.append(row[0])

        xml_url = "http://www.yr.no/place/" + city + "/forecast.xml"

        # Get general data for location
        response = requests.post(xml_url)
        yrFullXML = response.text

        yrStartForecastPos = yrFullXML.find('<tabular>')
        yrEndForecastPos = yrFullXML.find('</tabular>', yrStartForecastPos) + 10
        yrForecast = yrFullXML[yrStartForecastPos:yrEndForecastPos]

        # Extract the tml table logic into an Element Tree
        from xml.etree import ElementTree as ET
        root = ET.fromstring(yrForecast)

        list = {}
        # For each row, get the column data and apply calculations
        for row in root.iter('time'):
            #    print row.attrib
            TimePeriod = int(row.get('period'))
            TimeStart = row.get('from')
            Temperature = row.find('temperature').get('value')
            Precipitation = row.find('precipitation').get('value')
            SymbolName = row.find('symbol').get('name')

            DayStart = time.strftime("%Y-%m-%d", time.strptime(TimeStart, "%Y-%m-%dT%H:%M:%S"))

            if DayStart not in list:
                list[DayStart] = {}

            list[DayStart][str(TimePeriod)] = {'TimeStart': TimeStart, 'Temperature': Temperature,
                                               'Precipitation': Precipitation, 'SymbolName': SymbolName}

        for key in sorted(list):

            if ("2" in list[key]) and ("3" in list[key]):
                AvgTemp = [float(list[key]["2"]["Temperature"]), float(list[key]["3"]["Temperature"])]
                # avg = str(sum(AvgTemp)/float(len(AvgTemp)))
                TotRain = float(list[key]["2"]["Precipitation"]) + float(list[key]["3"]["Precipitation"])

...

This file has been truncated, please download it to see its full contents.

Python code: BarbecueServer.py

Python
Python script for running the webserver: Registring the LWA account and DRS setup. To be loaded to the MCU
#!/usr/bin/python
# -*- coding: utf8 -*-

import sqlite3
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import ssl
from os import curdir, sep
import cgi
import requests
import json
import time
import fcntl, socket, struct  # needed for getting IP Address and HW Address (=device ID)


conn = sqlite3.connect('BarbecuePlanner.db')


global YrnoEditDef, SetupEditDef, AmazonEditDef, FriendsEditDef, EventsEditDef
SetupEditDef = '<a href=setup>&raquo;&raquo; Edit setup</a>'
YrnoEditDef = '<a href=yrno>&raquo;&raquo; Edit setup</a>'
AmazonEditDef = '<a href=amazon>&raquo;&raquo; Edit setup</a>'
FriendsEditDef = '<a href=friends>&raquo;&raquo; Edit setup</a>'
EventsEditDef = '' #'<a href=events>&raquo;&raquo; Edit setup</a>'


# Unique Amazon credentials
Amazon_client_id = "amzn1.application-oa2-client.**************************""
Amazon_client_secret = "**************************""
Amazon_device_model = '**************************"'
Amazon_is_test_device = False


# IP Address (external facing network port)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
BarbecuePlanner_ip_address = str(s.getsockname()[0])

# Hardware Address (MAC address of external facing network port)
ifname = 'apcli0'
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
BarbecuePlanner_hw_address = str(':'.join(['%02x' % ord(char) for char in info[18:24]]).replace(":", ""))




def FullPageRender(YrnoText,YrnoEdit,SetupText,SetupEdit,AmazonText,AmazonEdit,FriendsText,FriendsEdit,EventsText,EventsEdit):
    return '''
        <!DOCTYPE html>
        <html>
        <head>
            <link rel="stylesheet" href="webserver/BarbecuePlanner.css">
            <link rel="icon" href="webserver/favicon.png" type="image/png">
        </head>
        <body>
        <table class="table table-striped">
            <tr>
                <td colspan=2><a href="/"><img src="webserver/BarbecuePlanner_icon.png"><font style="font-size: 24px;"></a><b>BarbecuePlanner setup page</b></font></td>
            </tr>
            <tr>
                <td colspan=2></td>
            </tr>

            <tr>
                <td><b>Barbecue Setup</b></td>
                <td>
    ''' + \
    str(SetupText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(SetupEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><img src="webserver/yrno_logo.png" height="34" width="34"></td>
                <td>
    ''' + \
    str(YrnoText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(YrnoEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><img src="webserver/amazon_logo.png" height="34" width="100"></td>
                <td>
    ''' + \
    str(AmazonText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(AmazonEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><b>Friends</b></td>
                <td>
    ''' + \
    str(FriendsText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(FriendsEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><b>Events</b></td>
                <td>
    ''' + \
    str(EventsText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(EventsEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td width=100></td>
                <td></td>
            </tr>
        </table>
        </body>
    '''


def SetupShowRender():
    SetupTemp = '???'
    SetupRain = '???'
    SetupDays = '???'
    UserName = '???'
    UserEmail = '???'

    cursor = conn.execute("SELECT BarbecueTemp, BarbecueRain, BarbecueUserName, BarbecueUserEmail FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '': SetupTemp = str(row[0])
        if str(row[1]) <> '': SetupRain = str(row[1])
        if str(row[2]) <> '': UserName = str(row[2])
        if str(row[3]) <> '': UserEmail = str(row[3])

    cursor = conn.execute("SELECT Day FROM BarbecueDays WHERE YesNo = 1 ORDER BY OrderId")
    for row in cursor:
        SetupDays += str(row[0]) + ", "

    return "Minimum temperature: " + SetupTemp + " &deg;C<br>Max rain: " + SetupRain + " mm<br>BBQ Days: " + SetupDays[3:-2] \
           + "<br><br>Name: " + UserName + "<br>Email:" + UserEmail




def SetupEditRender():
    SetupText = "<form method='POST' action='/setup'>"

    cursor = conn.execute("SELECT BarbecueTemp, BarbecueRain, BarbecueUserName, BarbecueUserEmail FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        SetupText += "Fill in the minimum temperature:<br><input type='text' name='BarbecueTemp' size='3' value='" + str(row[0]) + "'> &deg;C<br><br>"
        SetupText += "Fill in the maximum rain:<br><input type='text' name='BarbecueRain' size='3' value='" + str(row[1]) + "'> mm<br><br>"


        SetupText += "Select your BBQ Days:<br>"
        cursor2 = conn.execute("SELECT Day, YesNo FROM BarbecueDays ORDER BY OrderId")
        for row2 in cursor2:
            if str(row2[1]) == '1': DayChecked = ' checked'
            else: DayChecked = ''

            SetupText += "<input type='checkbox' name='" + str(row2[0]) + "' value='1'" + DayChecked + ">" + str(row2[0]) + "<br>"

        SetupText += "<br><br>"
        SetupText += "Fill in your details:<br><table style='border:0;'>"
        SetupText += "<tr><td width='35'>Name:</td><td><input type='text' name='UserName' size='20' value='" + str(row[2]) + "'></td>"
        SetupText += "<tr><td width='35'>Email:</td><td><input type='text' name='UserEmail' size='20' value='" + str(row[3]) + "'></td>"
        SetupText += "</table><br><br>"
    SetupText += "<input type='submit' value='Save' name='Save'></form>"

    return SetupText



def YrnoShowRender():
    YrnoStr = ''
    # Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and NRK
    YrnoCity = '???'
    cursor = conn.execute("SELECT YrCity FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        if str(row[0].encode('utf8')) <> '': YrnoCity = str(row[0].encode('utf8'))

    YrnoStr += "Your location: <a href='http://www.yr.no/place/" + YrnoCity + "'>" + YrnoCity + "</a>"
    YrnoStr += "<br><br><i>Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and the NRK. Please see <a href='http://www.yr.no/'>their website</a> for details</i>"
    if YrnoCity.encode('utf8') <> '???': YrnoStr += "<br><br>" + ForecastShowRender()

    return YrnoStr



def YrnoEditRender(RequestCountry,RequestProvince,RequestCity):
    PageSetupStr = "<form method='POST' action='/yrno'>Select your location:<br>"


    RequestCountryMatch = 0
    PageSetupStr += "<select name='YrCountry' onchange='this.form.submit()'>"
    cursor = conn.execute("SELECT DISTINCT Country FROM YrCities ORDER BY Country")
    for row in cursor:
        if RequestCountry == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestCountryMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestCountryMatch == 0:
        cursor = conn.execute("SELECT DISTINCT Country FROM YrCities ORDER BY Country LIMIT 1;")
        for row in cursor:
            RequestCountry = str(row[0].encode('utf8'))


    PageSetupStr += " / "

    RequestProvinceMatch = 0
    PageSetupStr += "<select name='YrProvince' onchange='this.form.submit()'>"
    cursor = conn.execute("SELECT DISTINCT Province FROM YrCities WHERE Country = '"+RequestCountry+"' ORDER BY Province")
    for row in cursor:
        if RequestProvince == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestProvinceMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestProvinceMatch == 0:
        cursor = conn.execute("SELECT DISTINCT Province FROM YrCities WHERE Country = '" + RequestCountry + "' ORDER BY Province LIMIT 1;")
        for row in cursor:
            RequestProvince = str(row[0].encode('utf8'))

    PageSetupStr += " / "


    RequestCityMatch = 0
    PageSetupStr += "<select name='YrCity' onchange='this.form.submit()'>"
    cursor = conn.execute("SELECT DISTINCT City FROM YrCities WHERE Country = '"+RequestCountry+"' AND Province = '"+RequestProvince+"' ORDER BY City")
    for row in cursor:
        if RequestCity == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestCityMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestCityMatch == 0:
        cursor = conn.execute("SELECT DISTINCT City FROM YrCities WHERE Country = '" + RequestCountry + "' AND Province = '"+RequestProvince+"' ORDER BY City LIMIT 1;")
        for row in cursor:
            RequestCity = str(row[0].encode('utf8'))


    PageSetupStr += "<br><br>Or enter your custom location:<br>"

    PageSetupStr += "<input type='text' name='YrLink' size='100' value='"
    cursor = conn.execute("SELECT DISTINCT Link FROM YrCities WHERE Country = '"+RequestCountry+"' AND Province = '"+RequestProvince+"' AND City = '"+RequestCity+"' ORDER BY City LIMIT 1")
    for row in cursor:
        PageSetupStr += str(row[0].encode('utf8'))
    PageSetupStr += "'>"

    PageSetupStr += "<br><br>"
    PageSetupStr += "<input type='submit' value='Save' name='Save'></form>"

    PageSetupStr += "<br><br><i>Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and the NRK. Please see <a href='http://www.yr.no/'>their website</a> for details</i>"

    return PageSetupStr




def ForecastShowRender():
    ForecastText = '<table style="border:0;"><tr><td><b>Day</b></td><td><b>Forecast</b></td></tr>'

    cursor = conn.execute("SELECT Date, Forecast, Party FROM BarbecueEvents WHERE Date >= '" + time.strftime("%Y-%m-%d") + "' ORDER BY Date ASC")
    for row in cursor:
        ForecastText += '<tr><td width="100">' + str(row[0]) + '</td><td>' + str(row[1]) + '</td></tr>'

    ForecastText += '</table>'
    return ForecastText



def AmazonRefreshToken():
    cursor = conn.execute("SELECT AmazonRefreshToken FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '':
            AmazonRefreshToken = str(row[0])

            auth_grant_url = "https://api.amazon.com/auth/o2/token"

            auth_grant_data = {
                "grant_type": "refresh_token",
                "refresh_token": AmazonRefreshToken,
                "client_id": Amazon_client_id,
                "client_secret": Amazon_client_secret,
                "redirect_uri": "https://" + BarbecuePlanner_ip_address + "/amazon_login"
            }

            auth_grant_headers = {
                "Content-Type": "application/x-www-form-urlencoded"
            }

            response_tokeninfo = requests.post(auth_grant_url, data=auth_grant_data, headers=auth_grant_headers)
            print json.dumps(response_tokeninfo.json(), indent=4, sort_keys=True)

            conn.execute("UPDATE BarbecueSettings SET AmazonAccessToken = '" + response_tokeninfo.json()['access_token'] + "', AmazonRefreshToken = '" + response_tokeninfo.json()['refresh_token'] + "';")
            conn.commit()


def AmazonShowRender():
    AmazonText = ''

    cursor = conn.execute("SELECT AmazonAccessToken, AmazonRefreshToken FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '':
            AmazonText += "Your Amazon account is linked<br><br>"
            AmazonText += AmazonShowSubscriptionInfo()
    if AmazonText == '': AmazonText =   "You don't have an Amazon account linked"

    return AmazonText


def AmazonShowSubscriptionInfo():
    AmazonRefreshToken();

    AmazonText = "<table style='border:0;'>"

    AmazonText += "<tr><td width='200'><b>Product group</b></td><td><b>Selected product</b></td></tr>"

    cursor = conn.execute("SELECT DISTINCT slot.SlotId, slot.SlotDescription, slot.SlotType, ifnull(SelAsin.Asin,''), ifnull(SelAsin.ProductDescription,'') FROM AmazonSlots AS slot LEFT OUTER JOIN AmazonSlots AS SelAsin on (slot.SlotId = SelAsin.SlotId and SelAsin.Selected = 1)")
    for row in cursor:
        AmazonText += "<tr><td width='200'>" + str(row[1])

        if str(row[2]) <> '':
            AmazonText += " (" + str(row[2]) + " persons)</td>"
        else:
            AmazonText += "</td>"

        if str(row[3]) <> '':
            AmazonText += "<td><a href='https://www.amazon.com/dp/" + str(row[3]) + "'>" + str(row[4]) + "</a></td></tr>"
        else:
            AmazonText += "<td><i>no selection</i></td></tr>"

    AmazonText += "</table>"
    return AmazonText



def AmazonEditRender():
    AmazonRefreshToken();

    AmazonButtons = "<table style='border:0;'>" + \
                    "<tr><td width='200'><b>Login with Amazon:</b></td><td><a href id='LoginWithAmazon'><img border='0' alt='Login with Amazon' src='webserver/amazon_button_yellow.png' width='32' height='32'></a></td></tr>" \
                    "<tr><td><b>Logout:</b></td><td><a href id='LogoutAmazon'></a><img border='0' alt='Logout' src='webserver/amazon_button_grey.png' width='32' height='32'></td></tr>" \
                    "<tr><td><b>DashReplanishment settings:</b></td><td><img border='0' alt='Device Settings' src='webserver/amazon_button_grey.png' width='32' height='32'></td></tr>" \
                    "</table>"


    cursor = conn.execute("SELECT AmazonAccessToken, AmazonRefreshToken FROM BarbecueSettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '': AmazonButtons = "<table style='border:0;'>" \
                                              "<tr><td width='300'><b>Login with Amazon:</b></td><td><a href id='LoginWithAmazon'></a><img border='0' alt='Login with Amazon' src='webserver/amazon_button_grey.png' width='32' height='32'></td></tr>" \
                                              "<tr><td><b>Logout:</b></td><td><a href id='LogoutAmazon'><img border='0' alt='Logout' src='webserver/amazon_button_dark.png' width='32' height='32'></a></td></tr>" \
                                              "<tr><td><b>DashReplanishment settings:</b></td><td><a href='https://drs-web.amazon.com/settings?access_token=" + str(row[0]) + "&exitUri=https://" + str(BarbecuePlanner_ip_address) + "/amazon'><img border='0' alt='Device Settings' src='webserver/amazon_button_yellow.png' width='32' height='32'></a></td></tr>" \
                                              "</table>" \
                                              + AmazonShowSubscriptionInfo()


    AmazonText = '''
               <div id="amazon-root"></div>
                <script type="text/javascript">
                window.onAmazonLoginReady = function() {
                amazon.Login.setClientId(''' + "'" + str(Amazon_client_id) + "'" + ''');
                };
                (function(d) {
                  var a = d.createElement('script');
                  a.type = 'text/javascript';
                  a.async = true;
                  a.id = 'amazon-login-sdk';
                  a.src = 'https://api-cdn.amazon.com/sdk/login1.js';
                  d.getElementById('amazon-root').appendChild(a);
                })(document);
                </script>''' + \
                AmazonButtons + \
                '''<script type="text/javascript">
                       document.getElementById('LoginWithAmazon').onclick = function() {
                       var options = new Object();
                       var scope = new Object();
                       var scope = ('profile', 'dash:replenish');
                       var scope_data = new Object();
                       scope_data['dash:replenish'] = {"device_model":''' + "'" + str(Amazon_device_model) + "'" + ''', "serial":''' + \
                "'" + BarbecuePlanner_hw_address + "'" + \
                ''', "is_test_device":''' + str(Amazon_is_test_device).lower() + '''};
                       options['scope_data'] = scope_data;
                       options['scope'] = scope;
                       options['response_type'] = 'code';
                       amazon.Login.authorize(options,'https://''' + str(BarbecuePlanner_ip_address) + '''/amazon_login');
                       return false;
                       };
                </script>
                <script type="text/javascript">
                      document.getElementById('LogoutAmazon').onclick = function() {
                      amazon.Login.logout();
                      location.href = 'https://''' + str(BarbecuePlanner_ip_address) + '''/amazon_logout';
                      return false;
                      };
                 </script>
                '''

    return AmazonText



def FriendShowRender():
    FriendText = '<table style="border:0;"><tr><td><b>Name</b></td><td><b>E-mail address</b></td></tr>'

    cursor = conn.execute("SELECT Name, Email FROM BarbecueFriends ORDER BY Name Desc")
    for row in cursor:
        FriendText += '<tr><td width="200">' + str(row[0]) + '</td><td>' + str(row[1]) + '</td></tr>'

    FriendText += '</table>'
    return FriendText






def EventsShowRender():
    EventText = '<table style="border:0;"><tr><td width="100"><b>Day</b><br><br></td><td><b>Event information</b><br><br></td></tr>'

    cursor = conn.execute("SELECT DISTINCT Date, Forecast FROM BarbecueEvents WHERE BarbecueEvents.Party = 1 ORDER BY Date DESC;")
    for row in cursor:
        EventText += '<tr><td style="vertical-align:top;">' + str(row[0]) + '</td><td><b>Forecast:</b><br>' + str(row[1]) + '<br><br>'

        EventText += '<b>You invited:</b><br>'
        cursor2 = conn.execute("SELECT Friend FROM BarbecueEventFriends WHERE Date = '" + str(row[0]) + "' ORDER BY Friend ASC")
        for row2 in cursor2:
            EventText += str(row2[0]) + ', '
        EventText += 'yourselves<br><br>'

        EventText += '<table style="border:0;">'
        EventText += '<tr><td width="400" style="vertical-align:top;"><b>You ordered:</b></td><td width="150" style="vertical-align:top;"><b>Status</b></td><td style="vertical-align:top;"><b>Estimated Delivery Date</b></td></tr>'
        cursor3 = conn.execute("SELECT DISTINCT ifnull(AmazonAsin.Asin,''), ifnull(AmazonAsin.ProductDescription,'<i>Awaiting Amazon confirmation</i>'), BarbecueEventOrder.Status, ifnull(BarbecueEventOrder.EstDeliveryDate,'<i>???</i>'), AmazonSlot.SlotDescription, AmazonSlot.SlotType FROM BarbecueEventOrder "\
                               "left outer join AmazonSlots AmazonSlot on (BarbecueEventOrder.SlotId = AmazonSlot.SlotId) "\
                               "left outer join AmazonSlots AmazonAsin on (BarbecueEventOrder.Asin = AmazonAsin.Asin) "\
                               "WHERE BarbecueEventOrder.Date = '" + str(row[0]) + "' ORDER BY AmazonSlot.SlotDescription ASC")
        for row3 in cursor3:
            EventText += '<tr><td style="vertical-align:top;">' + str(row3[4])
            if str(row3[5]) <> '':
                EventText += " (" + str(row3[5]) + " persons)"

            if str(row3[0]) <> '':
                EventText += " - <a href='https://www.amazon.com/dp/" + str(row3[0]) + "'>" + str(row3[1]) + "</a>"
            else:
               EventText += " - " + str(row3[1])

            EventText += "</td><td style='vertical-align:top;'>"

            if str(row3[2]) == '1':
                EventText += "Order submitted"
            elif str(row3[2]) == '2':
                EventText += "Order confirmed"
            elif str(row3[2]) == '3':
                EventText += "Canceled"
            elif str(row3[2]) == '4':
                EventText += "Shipped"
            elif str(row3[2]) == '5':
                EventText += "Error"
            elif str(row3[2]) == '6':
                EventText += "Test order submitted"
            else:
                EventText += "<i>???</i>"

            EventText += '</td><td style="vertical-align:top;">' + str(row3[3]) + '</td></tr>'

        EventText += '<tr><td>Fun, lots of fun</td><td></td><td></tr>'
        EventText += '</table>'

        EventText += '</td></tr>'

    EventText += '</table>'
    return EventText




# This class will handles any incoming request from the browser
class myHandler(BaseHTTPRequestHandler):
    def do_HEAD(self):
        '''
        Handle a HEAD request.
        '''
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()


    # Handler for the GET requests
    def do_GET(self):
        args = {}
        idx = self.path.find('?')
        if idx >= 0:
            rpath = self.path[:idx]
            args = cgi.parse_qs(self.path[idx + 1:])
        else:
            rpath = self.path

        if rpath == "/s":
            self.send_response(303)
            self.send_header('Content-type', 'text/html')
            self.send_header('Location', '/setup')  # This will navigate to the setup page
            self.end_headers()
            return

        try:
            # Check the file extension required and
            # set the right mime type

            sendReply = False

            if rpath.endswith(".html"):
                mimetype = 'text/html'
                sendReply = True
            if rpath.endswith(".png"):
                mimetype = 'image/png'
                sendReply = True
            if rpath.endswith(".jpg"):
                mimetype = 'image/jpg'
                sendReply = True
            if rpath.endswith(".gif"):
                mimetype = 'image/gif'
                sendReply = True
            if rpath.endswith(".js"):
                mimetype = 'application/javascript'
                sendReply = True
            if rpath.endswith(".css"):
                mimetype = 'text/css'
                sendReply = True

            if sendReply == True:
                # Open the static file requested and send it
                f = open(curdir + rpath)
                self.send_response(200)
                self.send_header('Content-type', mimetype)
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return




            print rpath
            print args

            YrnoText = YrnoShowRender()
            YrnoEdit = YrnoEditDef
            SetupText = SetupShowRender()
            SetupEdit = SetupEditDef
            AmazonText = AmazonShowRender()
            AmazonEdit = AmazonEditDef
            FriendsText = FriendShowRender()
            FriendsEdit = FriendsEditDef
            EventsText = EventsShowRender()
            EventsEdit = EventsEditDef


            if rpath == "/":
                pass



            if rpath == "/yrno":
                YrnoCountry = ''
                YrnoProvince = ''
                YrnoCity = ''

                cursor = conn.execute("SELECT Country, Province, City FROM BarbecueSettings inner join YrCities on YrCities.Link = BarbecueSettings.YrCity")
                for row in cursor:
                    YrnoCountry = str(row[0].encode('utf8'))
                    YrnoProvince = str(row[1].encode('utf8'))
                    YrnoCity = str(row[2].encode('utf8'))

                YrnoText = YrnoEditRender(YrnoCountry,YrnoProvince,YrnoCity)
                YrnoEdit = ''




            if rpath == "/setup":
                SetupText = SetupEditRender()
                SetupEdit = ''



            if rpath == "/amazon":
                AmazonText = AmazonEditRender()
                AmazonEdit = ''


            if rpath == "/amazon_login":
                auth_grant_code = str(args['code'][0])

                auth_grant_url = "https://api.amazon.com/auth/o2/token"

                auth_grant_data = {
                    "grant_type": "authorization_code",
                    "code": auth_grant_code,
                    "client_id": Amazon_client_id,
                    "client_secret": Amazon_client_secret,
                    "redirect_uri": "https://" + BarbecuePlanner_ip_address + "/amazon_login"
                }

                auth_grant_headers = {
                    "Content-Type": "application/x-www-form-urlencoded"
                }

                response_tokeninfo = requests.post(auth_grant_url, data=auth_grant_data, headers=auth_grant_headers)
                print json.dumps(response_tokeninfo.json(), indent=4, sort_keys=True)

                conn.execute("UPDATE BarbecueSettings SET AmazonAccessToken = '" + response_tokeninfo.json()['access_token'] + "', AmazonRefreshToken = '" + response_tokeninfo.json()['refresh_token'] + "';")
                conn.commit()

                AmazonText = "Thanks for your Amazon DRS login!<br><br>" + AmazonEditRender()
                AmazonEdit = ''


            if rpath == "/amazon_logout":
                cursor = conn.execute("SELECT AmazonAccessToken FROM BarbecueSettings LIMIT 1")
                for row in cursor:
                    if str(row[0]) <> '': AmazonAccessToken = str(row[0])

                logout_headers = {
                    "Authorization": "Bearer " + AmazonAccessToken,
                    "x-amzn-accept-type": "com.amazon.dash.replenishment.DrsDeregisterResult@1.0",
                    "x-amzn-type-version": "com.amazon.dash.replenishment.DrsDeregisterInput@1.0"
                }

                response_tokeninfo = requests.delete('https://dash-replenishment-service-na.amazon.com/deviceModels/' + Amazon_device_model + '/devices/' + BarbecuePlanner_hw_address + '/registration', headers=logout_headers)
                print json.dumps(response_tokeninfo.json(), indent=4, sort_keys=True)

                conn.execute("UPDATE BarbecueSettings SET AmazonAccessToken = '', AmazonRefreshToken = '';")
                conn.execute("UPDATE AmazonSlots SET Selected = 0;")
                conn.commit()

                # Send the html message
                AmazonText = "You are now logged out from Amazon DRS<br><br>" + AmazonEditRender()
                AmazonEdit = ''



            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(FullPageRender(YrnoText,YrnoEdit,SetupText,SetupEdit,AmazonText,AmazonEdit,FriendsText,FriendsEdit,EventsText,EventsEdit))

            return






        except IOError:
            self.send_error(404, 'File Not Found: %s' % rpath)

    # Handler for the POST requests
    def do_POST(self):
        args = {}
        idx = self.path.find('?')
        if idx >= 0:
            rpath = self.path[:idx]
            args = cgi.parse_qs(self.path[idx + 1:])
        else:
            rpath = self.path

        if rpath == "/s":
            rpath == "/setup"

        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD': 'POST',
                     'CONTENT_TYPE': self.headers['Content-Type'],
                     })

        print form

        try:
            # Check the file extension required and set the right mime type
            if rpath.endswith(""):
                print rpath
                print args

                YrnoText = YrnoShowRender()
                YrnoEdit = YrnoEditDef
                SetupText = SetupShowRender()
                SetupEdit = SetupEditDef
                AmazonText = AmazonShowRender()
                AmazonEdit = AmazonEditDef
                FriendsText = FriendShowRender()
                FriendsEdit = FriendsEditDef
                EventsText = EventsShowRender()
                EventsEdit = EventsEditDef

                if rpath == "/":
                    pass



                if rpath == "/yrno":
                    try:
                        YrnoSave = form["Save"].value
                        if YrnoSave == 'Save':
                            conn.execute("UPDATE BarbecueSettings SET YrCity = '" + str(form["YrLink"].value) + "';")
                            conn.commit()
                            YrnoText = YrnoShowRender()

                        else:
                            YrnoText = YrnoEditRender(form["YrCountry"].value, form["YrProvince"].value, form["YrCity"].value)
                            YrnoEdit = ''

                    except:
                        YrnoText = YrnoEditRender(form["YrCountry"].value,form["YrProvince"].value,form["YrCity"].value)
                        YrnoEdit = ''



                if rpath == "/setup":
                    try:
                        YrnoSave = form["Save"].value
                        if YrnoSave == 'Save':
                            conn.execute("UPDATE BarbecueSettings SET BarbecueTemp = " + form["BarbecueTemp"].value + ", BarbecueRain = " + form["BarbecueRain"].value + ", BarbecueUserName = '" + form["UserName"].value + "', BarbecueUserEmail= '" + form["UserEmail"].value + "';")
                            conn.commit()

                            conn.execute("UPDATE BarbecueDays SET YesNo = 0;")
                            conn.commit()

                            try:
                                if form["Monday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Monday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Tuesday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Tuesday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Wednesday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Wednesday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Thursday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Thursday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Friday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Friday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Saturday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Saturday';")
                                    conn.commit()
                            except:
                                pass

                            try:
                                if form["Sunday"].value == '1':
                                    conn.execute("UPDATE BarbecueDays SET YesNo = 1 WHERE Day = 'Sunday';")
                                    conn.commit()
                            except:
                                pass

                            SetupText = SetupShowRender()

                        else:
                            SetupText = SetupEditRender()
                            SetupEdit = ''

                    except:
                        SetupText = SetupEditRender()
                        SetupEdit = ''




                # Send the html message
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                self.wfile.write(FullPageRender(YrnoText,YrnoEdit,SetupText,SetupEdit,AmazonText,AmazonEdit,FriendsText,FriendsEdit,EventsText,EventsEdit))


        except IOError:
            self.send_error(404, 'File Not Found: %s' % rpath)

try:
    # Create a web server and define the handler to manage the incoming request
    server = HTTPServer(('', 443), myHandler)
    server.socket = ssl.wrap_socket(server.socket, server_side=True, certfile='webserver/certificate.pem.crt', keyfile='webserver/private.pem.key')
    print 'Started httpserver on port 443'

    # Wait forever for incoming http requests
    server.serve_forever()

except KeyboardInterrupt:
    print '^C received, shutting down the web server'
    server.socket.close()

Credits

Bastiaan Slee

Bastiaan Slee

5 projects • 34 followers
Tinkerer in the field of Home Automation, with the main goal: fun! Using Raspberry Pi, Arduino (+clones), LoRaWAN, NodeRed, 3D Printing

Comments