Max Paul
Published © GPL3+

Interactive WiFi-Controlled Dancing Christmas Hat

Add interactivity to an electric Christmas dancing hat.

IntermediateFull instructions provided888
Interactive WiFi-Controlled Dancing Christmas Hat

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board
×1
DS18B20 Programmable Resolution 1-Wire Digital Thermometer
Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer
×1
SparkFun Electret Microphone Breakout
SparkFun Electret Microphone Breakout
×1
Perma-Proto Breadboard Half Size
Perma-Proto Breadboard Half Size
×1
Adafruit 4-pin JST SM Plug + Receptacle Cable Set
×2
D-Sub 9 pin pair of connectors male+female
×1
Several good glasses of wine
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Connecting the components

It is my VERY first project with Fritzing. It may well contain errors. Please excuse and find out. The connecting is described in the comment header of the code.

Code

_2018-12-03_TH.ino

Arduino
The C code
/* ******************************************************************************************************************
 * ******************************************************************************************************************
 * KEVIN 4.0
 * The automated Christmas Jelly Bag Cap
 * Version 1.0
 * Dec. 2018
 * ******************************************************************************************************************
 * ******************************************************************************************************************
 * Hardware:
 * 1x Electrical Christmas Jelly Bag Cap. Bought somewhere on a street Christmas market.
 * Please search for "Dancing Christmas Hat"
 * https://www.alibaba.com/product-detail/Christmas-Gifts-Funny-Plush-Christmas-Electronic_60823418260.html?spm=a2700.7724857.normalList.31.29245b42qCqfeN
 * There are many similar devices available in the web. They seemingly work according to the same control unit or in a very similar way.
 * 
 * 1x ESP8266 D1 Mini Wemos compatible
 * 1x Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board
 * 
 * 1x Breakout Board for Electret Microphone (Adafruit)
 * https://electronics.semaf.at/Breakout-Board-for-Electret-Microphone
 * 
 * 1x Temperature sensor: One Wire Digital Temperature Sensor - DS18B20
 * https://electronics.semaf.at/One-Wire-Digital-Temperature-Sensor-DS18B20
 * 
 * 1x LED strip (bought in DYI market for 1 EUR. Any similar product is fine.
 * 1x USB Power Bank (as power supply)
 * 1x PCB Board - I used the Adafruit Perma-Proto Half-sized Breadboard
 * https://www.adafruit.com/product/571
 * 
 * 2x 4-pin cable connector. I used this one: 4-pin JST SM Plug - Receptacle Cable Set. Any similar is fine as well
 * https://electronics.semaf.at/4-pin-JST-SM-Plug-Receptacle-Cable-Set
 * 
 * Cables, wires, simple push button, plastic box, soldering iron
 * Several good glasses of wine. 
 * ******************************************************************************************************************
 * ******************************************************************************************************************
 * The project adds interactivity to a motorized Dancing Christmas Hat. I was bored by the ever same melody and rhythm
 * of the tail of the hat. My idea was to make it more live.
 * At the end the hat can be operated in several modes:
 * 
 * METRONOME MODE:
 * Here the tail swings in a specific frequency (beats per minute BPM). It is useful for playing music with it. The software features a setlist of 
 * music tunes of a concert and the respective BPM
 * 
 * MICROPHONE MODE:
 * The tail swings, if the sound level recorded by the microphone exceeds a certain level. Funny to remote control it with handclapping or applause
 *.
 * MANUAL:
 * Commands given via a webpage and wifi connection or by pushing a button on the control device
 * 
 * TEMPERATURE MODE:
 * A temperature sensor records the ambient temperature. The hat will swing faster if it becomes hotter. In temperature mode, client websites reload automatically every minute.
 * This feature is driven by Javascript of the HTML header.
 * 
 * ADMIN/CLIENT
 * The software detects an "admin" via a dedicated IP address. This admin has full rights and features a more detailed control panel on the website.
 * The admin can turn on and off the public interaction. If turned off, "clients" can only see the temperature and read the list of musicians
 * If turned on, "clients" can remotely trigger actions of the hat: simple swing, rattling and combination thereof. Feel free to add other interactions!
 * The reason for turining on and off were in the use case during a concert. I didn't like all time remote pushes. Also of course the server of the ESP8266 is not
 * prepared for high loads and timing will suffer a lot if many "clients" are sending HTML requests.
 * 
 * 
 * ******************************************************************************************************************
 * ******************************************************************************************************************
 * Comments:
 * 
 * The code has been written by Max Paul
 * I consider myself to be a more or less inexperienced programmer 
 * in particular concerning embedded software.
 * Many code snipplets and demo programs have been used. I cordially thank all
 * providers of these libraries and software parts. No IP is claimed by myself for
 * all of such components.
 * The software also contains lots of remainders of trial and error attempts to include other 
 * sensors or other versions. Also, some functions did not reach the goal, but remained in semi-finished
 * status in the program. More time would have been necessary to clean up and stramline
 * naming conventions, etc.
 * Furthermore, the code is not written in efficient way and does not save resources on the target HW.
 * As such, please do not blaim me - even not for obvious errors or neglicence. I am open
 * for all improvement suggestions. At the end I was glad to finish the project in due time.
 * The cap was used in a Christmas concert and hence provided further functionality concerning this
 * use-case (display of a setlist of the evening program and list of musicians on the remote devices)
 * Maybe there are still some German words in the code, please feel free to translate...
 * 
 * Thanks for your understanding, Merry Christmas and greetings from Vienna!
 * max.paul@cardeas.at
 * 
 * ******************************************************************************************************************
 * ******************************************************************************************************************
 * 
 * Breadboard wiring scheme
   Push button: GND / D
   Thermosensor: left: GND - Center: D3 Right: +3.3V D3 - +3.3: 4.7k Resistor
   Mirophone:   GND/3.3 Signal: A0
 *
 * DRV8833:
 * Motor:
 * AIN1  - D1 of 8266
 * AIN2  - D2 of 8266
 * AOUT1 - Motor
 * AOUT2 - Motor
 * 
 * LED Strip:
 * BIN1  - D6 of 8266
 * BIN2  - D5 of 8266
 * BOUT1 - LED Strip
 * BOUT2 - LED Strip
 * 
 * VMotor
 * +/- 4.5 V from external battery
 * 
 * AS   - GND
 * SLP  - 3.3V
 * 
 * ESP8266:
 * A0   - Microphone Audio
 * D1   - AIN1 of 8833
 * D2   - AIN2 of 8833
 * D3   - Data of Thermo sensor, 4,7k Resistor to +3.3V
 * D5   - BIN2 of 8833
 * D6   - BIN1 of 8833
 * D7   - Push button to GND
 * 
 * 
 * Motor:       D1/D2 - AIN1/2 D1 - AIN1, D2 AIN2
   LED Strip:   D5/D6 - BIN1/2 D5 - BIN2, D6-BIN1
   SLP-+3.3V
   GND-AS
   AOUT1-Blue
   AOUT2-Brown
   + gGelb
   GND Black

   8266: 3V3+ - 3.3 Rail
   G - GND

   D-Sub connector pins
   1 Thermo         green
   2 Audio          blue
   3 BOUT 1         pink
   4 BOUT 2         grey
   5 Taster - D7    brown/blue

   6 +3.3           red/white
   7 GND            black/grey
   8 GND 2          black/grey
   9 +3.3 2         red/white

*/

// Several compiler switches
// _DEBUG_ will create lot of serial output for debugging purpose
// _SOFT_AP_ operates the ESP8266 in Access Point mode. In this mode, less functionality is possible, but you won't need a separate router
// _LED_MOTOR_ is useful for debugging and programming without the tail hardware present. In this mode, motor movement is indicated as flickering LED

//#define _DEBUG_
//#define _SOFT_AP_
//#define _LED_MOTOR_


// Load Wi-Fi library
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266mDNS.h>

extern "C" {
#include<user_interface.h>
}

// Definitions for motor control output pins
#define       MotorOutPin1 D1                 // GPIO 5
#define       MotorOutPin2 D2                 // GPIO 4
#define       LightOutPin1 D5                 // Used for LED light chain
#define       LightOutPin2 D6                 // Used for LED light chain


// Definitions for motor movement and tail position
#define       RIGHT                     1
#define       LEFT                      2
#define       CENTER                    3
#define       FAST                     50     // the smaller the number = the stronger the motor. 100 would be a good value without the cap
#define       SLOW                    800
#define       RASPY                     1     // Initial intention was to have different modes of motor movement. In reality, there was hardly a difference between rasp and gentle mode
#define       GENTLY                    2
#define       SHAKE_DELAY             500     // Wait 500ms between shakes
#define       MOTORCOMPENSATION        20     // Compensation for harder resistance in one direction of move. It is additional motor running time
int           giMotorDirection   = RIGHT;     // Initial motor direction will be rightwise
int           giMotorRuntime       = 240;     // 130 would be a good value without the cap
const int     ciRattles             = 20;     // Number of rattles
int           giWingPosition         = 0;     // Indicates the position of the wing/tail. Initially undefined

// Definitions for temperature sensor
#define       ONE_WIRE_BUS              D3    //Pin to which is attached a temperature sensor
#define       ONE_WIRE_MAX_DEV           1    //The maximum number of devices
OneWire       oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
int           giNumberOfTempSensors;          //Number of temperature devices found
DeviceAddress devAddr[ONE_WIRE_MAX_DEV];      //An array device temperature sensors
float         tempDev[ONE_WIRE_MAX_DEV];      //Saving the last measurement of temperature
float         tempDevLast[ONE_WIRE_MAX_DEV];  //Previous temperature measurement
long          lastTemp;                       //The last measurement

// Definitions for microphone
const int     sampleWindow = 100;             // Sample window width in mS (250 mS = 4Hz)
unsigned int  gKnock;                         // Variable to store recorded sound level
int           giMaxPeak = 816;                // Initial sensitivity level, has to be adjusted according to experience and trial

// Definitions for program and button state machine
#define       LED_HIGH                  LOW
#define       LED_LOW                   HIGH
#define       MOTOR_STATE_ON            1
#define       MOTOR_STATE_OFF           0
#define       LIGHT_STATE_ON            1
#define       LIGHT_STATE_OFF           0
#define       WIFI_STATE_CONNECTED      1
#define       WIFI_STATE_NOT_CONNECTED  0
#define       PUBLIC_STATE_ON           1
#define       PUBLIC_STATE_OFF          0
#define       STATE_START               1
#define       STATE_METRONOME           2
#define       STATE_MICROPHONE          3
#define       STATE_TEMPERATURE         4
#define       STATE_OTHER               5
int           giMotorState              = MOTOR_STATE_OFF;            // Initially the tail movement shall be switched off
int           giLightState              = LIGHT_STATE_OFF;            // Initially light shall be switched off
int           giWifiState               = WIFI_STATE_NOT_CONNECTED;   // State will be changed after successfull connection
int           giPublicState             = PUBLIC_STATE_OFF;           // Initially other clients shall not be able to control the tail
int           giLight                   = LIGHT_STATE_OFF;            // Light is off
int           giState                   = STATE_START;                // Program state machine in initial mode


// Shortcuts to switch internal LED and lights
#define       LEDON                     digitalWrite(LED_BUILTIN, LED_HIGH);
#define       LEDOFF                    digitalWrite(LED_BUILTIN, LED_LOW);
#define       LIGHTON                   digitalWrite(LightOutPin1, LOW); \
                                        digitalWrite(LightOutPin2, HIGH);\
                                        giLight = LIGHT_STATE_ON;
#define       LIGHTOFF                  digitalWrite(LightOutPin1, LOW); \
                                        digitalWrite(LightOutPin2, LOW); \
                                        giLight = LIGHT_STATE_OFF;

// Definitions for Wifi to log on
#ifdef _SOFT_AP_
const char*   ssid                      = "KVN40";
const char*   password                  = "kevin";
const char*   adminIP                   = "192.168.4.2";    // Special IP address reserved for admin client in soft AP mode. Normally the first device to connect to the AP
#else
const char*   ssid                      = "KVN40";
const char*   password                  = "kevin";
const char*   adminIP                   = "192.168.0.3";    // Special IP address reserved for admin client. This address has to be statically assigned at the router to the admin device.
#endif

int           giAdminConnected          = false;            // Variable to indicate, if admin client is present. Admin client is identified through statically assigned IP address adminIP

// Set web server port number to 80
ESP8266WebServer gESPServer(80);

// CSS style sheet
#define       CSS_HDR_1  "<style>"
#define       CSS_HDR_2  "html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"
#define       CSS_HDR_3  " .button { background-color: #195B6A; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer; width: 250px;}"
#define       CSS_HDR_4  " .button2 {background-color: #669900;} .buttonred {background-color: #990000;} .buttongreen {background-color: #009900;} .button3 {width: 250px; height: 200px; font-size: 42px;} .buttonadmin {width: 250px;} .pagetitle { border: none; color: #8B0000; padding: 16px 40px; text-decoration: none; font-size: 40px; margin: 10px;}"
#define       CSS_HDR_5  " .textarea, h1 { color: #8B0000; text-decoration: none; font-size: 36px; margin: 3px;}"
#define       CSS_HDR_6  " .slidecontainer {font-size: 80px; width: 100%; margin: 25px 0px; padding-bottom: 20px; }"
#define       CSS_HDR_7  " .slider {-webkit-appearance: none; width: 100%; height: 50px; background: #d3d3d3; outline: none; opacity: 0.7; -webkit-transition: .2s; transition: opacity .2s;} .slider:hover {opacity: 1;} .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 25px; height: 50px; background: #4CAF50; cursor: pointer;} .slider::-moz-range-thumb {width: 25px; height: 50px; background: #4CAF50; cursor: pointer;}"
#define       CSS_HDR_8  "</style>"
#define       CSS_HDR    CSS_HDR_1 \
                         CSS_HDR_2 \
                         CSS_HDR_3 \
                         CSS_HDR_4 \
                         CSS_HDR_5 \
                         CSS_HDR_6 \
                         CSS_HDR_7 \
                         CSS_HDR_8

// HTML Website header including Javascript functions
// Since the device was used during a live bigband concert there is functionality present to display the evening program and the list of musicians. Such code can be erased if not desired.
#define       HTML_HEAD_NO_REFRESH    \
                        "<html>"      \
                          "<head>"    \
                            "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">" \
                            "<title class=\"pagetitle\">" \
                              APP_TITLE   \
                            "</title>"    \
                            CSS_HDR       \
                            "<script>"    \
                              "var eveningprogram = [];"  \
                              "var musicians      = [];"  \
                              "var toTimeout;"            \
                              "var strListe   = \"\";"    \
                              "var strSetList = \"\";"    \
                              "var iCurrentTitle = 10;"   \
                              "eveningprogram.push({song:\"Japan\",                            BPM:160},"  \
                                                  "{song:\"Bewitched\",                        BPM:160},"  \
                                                  "{song:\"You are the sunshine\",             BPM:128},"  \
                                                  "{song:\"That's life\",                      BPM:76 },"  \
                                                  "{song:\"New York, New York\",               BPM:112},"  \
                                                  "{song:\"When you wish\",                    BPM:120},"  \
                                                  "{song:\"Die Liste\",                        BPM:160},"  \
                                                  "{song:\"Beauty and the beast\",             BPM:66 },"  \
                                                  "{song:\"Feliz navidad\",                    BPM:160},"  \
                                                  "{song:\"Pause\",                            BPM:0  },"  \
                                                  "{song:\"Baby, its cold outside\",           BPM:104},"  \
                                                  "{song:\"Go, tell it on the mountain\",      BPM:124},"  \
                                                  "{song:\"A child is born\",                  BPM:68 },"  \
                                                  "{song:\"Winter Wonderland\",                BPM:110},"  \
                                                  "{song:\"Have yourself\",                    BPM:72 },"  \
                                                  "{song:\"I'll be home for Christmas\",       BPM:72 },"  \
                                                  "{song:\"Let it snow\",                      BPM:110},"  \
                                                  "{song:\"Have yourself\",                    BPM:104},"  \
                                                  "{song:\"The Christmas song\",               BPM:72 },"  \
                                                  "{song:\"Santa Claus is coming\",            BPM:144},"  \
                                                  "{song:\"White Christmas\",                  BPM:76 },"  \
                                                  "{song:\"All I want for Christmas\",         BPM:160});" \
                              "musicians.push(     {instrument:\"Altsax 1:\",                  name:\"Name 1\"},"   \
                                                  "{instrument:\"Altsax 2:\",                  name:\"Name 2\"},"   \
                                                  "{instrument:\"Tenorsax 1:\",                name:\"Name 3\"},"   \
                                                  "{instrument:\"Tenorsax 2:\",                name:\"Name 4\"},"   \
                                                  "{instrument:\"Baritonsax:\",                name:\"Name 5\"},"   \
                                                  "{instrument:\"\",                           name:\"\"},"         \
                                                  "{instrument:\"Trompete 1:\",                name:\"Name 6\"},"   \
                                                  "{instrument:\"Trompete 2:\",                name:\"Name 7\"},"   \
                                                  "{instrument:\"Trompete 3:\",                name:\"Name 8\"},"   \
                                                  "{instrument:\"Trompete 4:\",                name:\"Name 9\"},"   \
                                                  "{instrument:\"\",                           name:\"\"},"         \
                                                  "{instrument:\"Posaune 1:\",                 name:\"Name 10\"},"  \
                                                  "{instrument:\"Posaune 2:\",                 name:\"Name 11\"},"  \
                                                  "{instrument:\"Posaune 3:\",                 name:\"Name 12\"},"  \
                                                  "{instrument:\"\",                           name:\"\"},"         \
                                                  "{instrument:\"Gitarre:\",                   name:\"Name 13\"},"  \
                                                  "{instrument:\"Bass:\",                      name:\"Name 14\"},"  \
                                                  "{instrument:\"Drums:\",                     name:\"Name 15\"},"  \
                                                  "{instrument:\"Klavier:\",                   name:\"Name 16\"},"  \
                                                  "{instrument:\"\",                           name:\"\"},"         \
                                                  "{instrument:\"Vocal:\",                     name:\"Name 17\"},"  \
                                                  "{instrument:\"Vocal:\",                     name:\"Name 18\"},"  \
                                                  "{instrument:\"\",                           name:\"\"},"         \
                                                  "{instrument:\"Bandleader:\",                name:\"Name 19\"});" \
                              "function fnPreviousTitle() {" \
                                "if (iCurrentTitle>0) iCurrentTitle--;" \
                                  "document.getElementById(\"CurrentTitle\").innerHTML = eveningprogram[iCurrentTitle].song;" \
                                  "document.getElementById(\"CurrentBPM\").innerHTML   = eveningprogram[iCurrentTitle].BPM;"  \
                                  "document.getElementById(\"SongBPM\").innerHTML      = eveningprogram[iCurrentTitle].BPM;"  \
                                  "document.getElementById(\"inputMetronom\").value    = eveningprogram[iCurrentTitle].BPM;"  \
                                "}" \
                              "function fnNextTitle() {" \
                                "if (iCurrentTitle<eveningprogram.length-1) iCurrentTitle++;" \
                                  "document.getElementById(\"CurrentTitle\").innerHTML = eveningprogram[iCurrentTitle].song;" \
                                  "document.getElementById(\"CurrentBPM\").innerHTML   = eveningprogram[iCurrentTitle].BPM;"  \
                                  "document.getElementById(\"inputMetronom\").value    = eveningprogram[iCurrentTitle].BPM;"  \
                                "}" \
                              "function fnUpdateBPM(val) {" \
                                "document.getElementById(\"CurrentBPM\").innerHTML = val;" \
                                "}" \
                              "function fnUpdateMicro(val) {" \
                                "document.getElementById(\"CurrentMIC\").innerHTML = val;" \
                                "}" \
                              "musicians.forEach(fnConcatMusicianList);" \
                                "function fnConcatMusicianList(value) {" \
                                "strListe = strListe + value.instrument + \" \" + value.name + \"<br>\"; " \
                                "}" \
                              "eveningprogram.forEach(fnConcatSetList);" \
                                "function fnConcatSetList(value) {" \
                                "strSetList = strSetList + value.song + \"<br>\"; " \
                                "}" \
                              "function fnTogglemusicians() {" \
                                "clearTimeout(toTimeout);" \
                                "if (document.getElementById(\"Textarea\").style.display === \"block\")  {" \
                                  "document.getElementById(\"Textarea\").style.display = \"none\";" \
                                "}" \
                                "else {" \
                                  "document.getElementById(\"Textarea\").innerHTML = strListe;" \
                                  "document.getElementById(\"Textarea\").style.display = \"block\";" \
                                "}" \
                              "}" \
                              "function fnToggleSetList() {" \
                                "clearTimeout(toTimeout);" \
                                "if (document.getElementById(\"Textarea\").style.display === \"block\")  {" \
                                  "document.getElementById(\"Textarea\").style.display = \"none\";" \
                                "}" \
                                "else {" \
                                  "document.getElementById(\"Textarea\").innerHTML = strSetList;" \
                                  "document.getElementById(\"Textarea\").style.display = \"block\";" \
                                "}" \
                              "}" \
                              "function fnSetTimeout() {" \
                                "toTimeout = setTimeout(fnReloadPage, 60000);" \
                              "}" \
                              "function fnReloadPage(){" \
                                "location.reload(true);" \
                              "}"\
                            "</script>" \
                          "</head>"

// Response status of HTTP request
#define       HTTP_STATUS_OK                      200
#define       HTTP_STATUS_ACCEPTED                202
#define       HTTP_STATUS_NO_CONTENT              204
#define       HTTP_STATUS_NOT_FOUND               404
#define       APP_TITLE                           "Kevin - Christmas Jelly Bag Cap 4.0"
#define       SVR_ARG_BPM                         "rangeBPM"
#define       SVR_ARG_MAXRANGE                    "sensitivity"

// Definitions for Metronome Mode
#define       UPDATE_BPM                          10000
int           giInterval                          = 1000;       //  1000 =  1sec = 60BPM
int           giBPM                               = 90;         //  90BPM
int           giUpdateBPM                         = UPDATE_BPM; // 10000 = 10sec

// PushButton
// On the device there is a single push button. It can be operated in two modes: In the MENU mode it switches between the program state machine modes:
// STATE_START (1), STATE_METRONOME (2), STATE_MICROPHONE (3), STATE_TEMPERATURE (4), STATE_OTHER (5)
// If pushed for longer time (MULTIPUSH_INTERVAL), it toggles to a "MANUAL mode" and vice versa.
// In this mode it will trigger tail swings and other movements as it is defined in the main loop "Manual 1", etc.
// The push button engine features an anto-bouncing evaluation.
#define       STATE_BUTTON_MENU                   1             // Button state machine: In menu mode, button selects program state machine
#define       STATE_BUTTON_MANUAL_MODE            2             // in manual mode, commands are immediately performed after button was pressed
#define       PUSHBUTTONPIN                       D7            // GPIO 13
#define       BOUNCE_INTERVAL                     200           // 200 milliseconds
#define       MULTIPUSH_INTERVAL                  2000          // 2 seconds time to push several times
#define       MAX_PUSHINTERRUPTS                  50            // Count 50 pushes (incl. bounces)
volatile long glPushInterrupts[MAX_PUSHINTERRUPTS];             // Array to store the pushes
volatile int  giPushInterrupts                    = 0;          // Total pushes (incl. bounces)
int           giNumberOfPushButtonPressed         = 0;          // Total real pushes
int           giButtonState                       = STATE_BUTTON_MENU;


  //---------------------------------------------------------------------------------
  //-------------------------------------- Code Begin -------------------------------
  //---------------------------------------------------------------------------------
#ifdef _DEBUG_
  //Convert device id to String - used only to print out address of temperature sensor
  String GetAddressToString(DeviceAddress deviceAddress) {
    String str = "";
    for (uint8_t i = 0; i < 8; i++) {
      if ( deviceAddress[i] < 16 ) str += String(0, HEX);
      str += String(deviceAddress[i], HEX);
    }
    return str;
  }
#endif

  // Performs an entire swing
  void TailSwing(int iMode, int iSpeed) {
    int iMotorRun;
    switch (iSpeed) {
      case SLOW:
        iMotorRun = giMotorRuntime * 0.6;
        break;
      case FAST:
        iMotorRun = giMotorRuntime;
        break;
      default:
        iMotorRun = giMotorRuntime;
        break;
    }

    switch (iMode) {
      case RASPY:
        switch (giMotorDirection) {
          case LEFT:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              digitalWrite(MotorOutPin1, HIGH);
              analogWrite(MotorOutPin2, iSpeed);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin2, HIGH);
            giMotorDirection = RIGHT;
            giWingPosition = LEFT;
#endif
            break;
          case RIGHT:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              analogWrite(MotorOutPin1, iSpeed);
              digitalWrite(MotorOutPin2, HIGH);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin1, HIGH);
            giMotorDirection = LEFT;
            giWingPosition = RIGHT;
#endif
            break;
          default:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              analogWrite(MotorOutPin1, iSpeed);
              digitalWrite(MotorOutPin2, HIGH);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin1, HIGH);
            giMotorDirection = LEFT;
            giWingPosition = RIGHT;
#endif
            break;
        }
        break;
      case GENTLY:
        switch (giMotorDirection) {
          case LEFT:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              digitalWrite(MotorOutPin1, iSpeed);
              analogWrite(MotorOutPin2, LOW);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin1, LOW);
            giMotorDirection = RIGHT;
            giWingPosition = LEFT;
#endif
            break;
          case RIGHT:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              analogWrite(MotorOutPin1, LOW);
              digitalWrite(MotorOutPin2, iSpeed);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin2, LOW);
            giMotorDirection = LEFT;
            giWingPosition = RIGHT;
#endif
            break;
          default:
#ifdef _LED_MOTOR_
            LEDON
            LIGHTON
            delay(iMotorRun - 10);
            LEDOFF
            LIGHTOFF
            delay(15);
#else
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTOFF
            }
            if (giMotorState == MOTOR_STATE_ON) {
              analogWrite(MotorOutPin1, LOW);
              digitalWrite(MotorOutPin2, iSpeed);
            }
            delay(iMotorRun);
            if (giLightState == LIGHT_STATE_ON) {
              LIGHTON
            }
            digitalWrite(MotorOutPin2, LOW);
            giMotorDirection = LEFT;
            giWingPosition = RIGHT;
#endif
            break;
        }
        break;
      default:
        break;
    }
  }

  // Moves tail in center position and rattle for ciRattles number of times
  void Rattle(int iNumOfRattles) {
    int iI;
    switch (giMotorDirection) {
      case LEFT:
        // If not yet happened: Move tail to center position
        if (giWingPosition != CENTER) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
            digitalWrite(MotorOutPin2, LOW);
          }
#endif
          delay(int(giMotorRuntime / 2));
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
        }
        for (iI = 0; iI < iNumOfRattles; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTOFF
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, LOW);
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
          delay(int(giMotorRuntime / 5) + MOTORCOMPENSATION);
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTON
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
            digitalWrite(MotorOutPin2, LOW);
          }
#endif
          delay(int(giMotorRuntime / 5));
        }
        if (giMotorState == MOTOR_STATE_ON) {
          digitalWrite(MotorOutPin2, HIGH);
        }
        giMotorDirection = RIGHT;
        break;
      case RIGHT:
        // Falls noch nicht geschehen: Mtze aufstellen
        if (giWingPosition != CENTER) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, LOW);
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
          delay(int(giMotorRuntime / 2));
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
          }
#endif
        }
        for (iI = 0; iI < iNumOfRattles; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTOFF
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
            digitalWrite(MotorOutPin2, LOW);
          }
#endif
          delay(int(giMotorRuntime / 5));
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTON
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, LOW);
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
          delay(int(giMotorRuntime / 5) + MOTORCOMPENSATION); // add to compensate
        }
        if (giMotorState == MOTOR_STATE_ON) {
          digitalWrite(MotorOutPin1, HIGH);
        }
        giMotorDirection = LEFT;
        break;
      default:
        // If not yet happened: Move tail to center position
        if (giWingPosition != CENTER) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, LOW);
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
          delay(int(giMotorRuntime / 2));
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
          }
#endif
        }
        for (iI = 0; iI < iNumOfRattles; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTOFF
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, HIGH);
            digitalWrite(MotorOutPin2, LOW);
          }
#endif
          delay(int(giMotorRuntime / 10));
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          if (giLightState == LIGHT_STATE_ON) {
            LIGHTON
          }
          if (giMotorState == MOTOR_STATE_ON) {
            digitalWrite(MotorOutPin1, LOW);
            digitalWrite(MotorOutPin2, HIGH);
          }
#endif
          delay(int(giMotorRuntime / 10));
        }
        if (giMotorState == MOTOR_STATE_ON) {
          digitalWrite(MotorOutPin1, HIGH);
        }
        giMotorDirection = LEFT;
        break;
    }
    giWingPosition = CENTER;
  }

  // Moves tail in center position and shake for iNumberOfShakes number of times
  void Shake(int iNumberOfShakes) {
    int iI;
    switch (giMotorDirection) {
      case LEFT:
        // If desired: Move tail into center position
        /*    if (giWingPosition != CENTER) {
              digitalWrite(MotorOutPin1, HIGH);
              digitalWrite(MotorOutPin2, LOW);
              delay(int(giMotorRuntime/2));
              digitalWrite(MotorOutPin2, HIGH);
            }*/
        //    digitalWrite(LED_BUILTIN, HIGH);
        for (iI = 0; iI < iNumberOfShakes; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          digitalWrite(MotorOutPin1, LOW);
          digitalWrite(MotorOutPin2, HIGH);
#endif
          delay(int(giMotorRuntime / 5) + MOTORCOMPENSATION); // add to compensate
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          digitalWrite(MotorOutPin1, HIGH);
          digitalWrite(MotorOutPin2, LOW);
#endif
          delay(int(giMotorRuntime / 5));
#ifndef _LED_MOTOR_
          digitalWrite(MotorOutPin2, HIGH);
          //      digitalWrite(LED_BUILTIN, HIGH);
#endif
          delay(SHAKE_DELAY);
        }
        delay(SHAKE_DELAY);
        giMotorDirection = RIGHT;
        break;
      case RIGHT:
        // If desired: Move tail into center position
        /*    if (giWingPosition != CENTER) {
              digitalWrite(MotorOutPin1, LOW);
              digitalWrite(MotorOutPin2, HIGH);
              delay(int(giMotorRuntime/2));
              digitalWrite(MotorOutPin1, HIGH);
            } */
        for (iI = 0; iI < iNumberOfShakes; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          digitalWrite(MotorOutPin1, HIGH);
          digitalWrite(MotorOutPin2, LOW);
#endif
          delay(int(giMotorRuntime / 5) + 10);
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          digitalWrite(MotorOutPin1, LOW);
          digitalWrite(MotorOutPin2, HIGH);
#endif
          delay(int(giMotorRuntime / 5) + MOTORCOMPENSATION); // add to compensate
#ifndef _LED_MOTOR_
          digitalWrite(MotorOutPin1, HIGH);
#endif
          //      digitalWrite(LED_BUILTIN, HIGH);
          delay(SHAKE_DELAY);
        }
        delay(SHAKE_DELAY);
        giMotorDirection = LEFT;
        break;
      default:
        // If desired: Move tail into center position
        /*    if (giWingPosition != CENTER) {
              digitalWrite(MotorOutPin1, LOW);
              digitalWrite(MotorOutPin2, HIGH);
              delay(int(giMotorRuntime/2));
              digitalWrite(MotorOutPin1, HIGH);
            } */
        for (iI = 0; iI < iNumberOfShakes; iI++) {
#ifdef _LED_MOTOR_
          LEDON
          LIGHTON
#else
          digitalWrite(MotorOutPin1, HIGH);
          digitalWrite(MotorOutPin2, LOW);
#endif
          delay(int(giMotorRuntime / 5));
#ifdef _LED_MOTOR_
          LEDOFF
          LIGHTOFF
#else
          digitalWrite(MotorOutPin1, LOW);
          digitalWrite(MotorOutPin2, HIGH);
#endif
          delay(int(giMotorRuntime / 5) + MOTORCOMPENSATION); // add to compensate
#ifndef _LED_MOTOR_
          digitalWrite(MotorOutPin1, HIGH);
          digitalWrite(LED_BUILTIN, HIGH);
#endif
          delay(SHAKE_DELAY);
        }
        delay(SHAKE_DELAY);
        giMotorDirection = LEFT;
        break;
    }
    giWingPosition = CENTER;
  }

  //Setting the temperature sensor
  void SetupDS18B20() {
    DS18B20.begin();
#ifdef _DEBUG_
    Serial.print("Parasite power is: ");
    if ( DS18B20.isParasitePowerMode() ) {
      Serial.println("ON");
    }
    else {
      Serial.println("OFF");
    }
#endif
    giNumberOfTempSensors = DS18B20.getDeviceCount();
#ifdef _DEBUG_
    Serial.print( "Device count: " );
    Serial.println( giNumberOfTempSensors );
#endif
    lastTemp = millis();
    DS18B20.requestTemperatures();
    // Loop through each device, print out address
    for (int i = 0; i < giNumberOfTempSensors; i++) {
      // Search the wire for address
      if ( DS18B20.getAddress(devAddr[i], i) ) {
        //devAddr[i] = tempDeviceAddress;
#ifdef _DEBUG_
        Serial.print("Found device ");
        Serial.print(i, DEC);
        Serial.print(" with address: " + GetAddressToString(devAddr[i]));
        Serial.println();
#endif
      }
#ifdef _DEBUG_
      else {
        Serial.print("Found ghost device at ");
        Serial.print(i, DEC);
        Serial.print(" but could not detect address. Check power and cabling");
      }

      //Get resolution of DS18b20
      Serial.print("Resolution: ");
      Serial.print(DS18B20.getResolution( devAddr[i] ));
      Serial.println();

      //Read temperature from DS18b20
      float tempC = DS18B20.getTempC( devAddr[i] );
      Serial.print("Temp C: ");
      Serial.println(tempC);
#endif
    }
  }

  void handlePushButtonInterrupt() {
    glPushInterrupts[giPushInterrupts] = millis();
    if (giPushInterrupts < (MAX_PUSHINTERRUPTS - 1)) giPushInterrupts++;
  }

  int EvaluatePushButton() {
    int iStartIndex = 0;
    int iEndIndex = 1;
    int iIndex;
    int iPushes;
    giNumberOfPushButtonPressed = 1;
#ifdef _DEBUG_
    Serial.print("giPushInterrupts: ");
    Serial.println(giPushInterrupts);
#endif
    if (giPushInterrupts > 1) {
      if (digitalRead(PUSHBUTTONPIN) == LOW) {
        if (giButtonState == STATE_BUTTON_MENU) {
          giButtonState = STATE_BUTTON_MANUAL_MODE;
        }
        else {
          giButtonState = STATE_BUTTON_MENU;
        }
        giPushInterrupts = 0;
        giNumberOfPushButtonPressed = 0;
        // Long button press detected
        return 0;
      }
      else {
        for (iIndex = 0; iIndex < giPushInterrupts - 1; iIndex++) {
          if (glPushInterrupts[iEndIndex] - glPushInterrupts[iStartIndex] < BOUNCE_INTERVAL) {
            // Bounce Push detected
            if (iEndIndex < MAX_PUSHINTERRUPTS) {
              iEndIndex++;
            }
          }
          else {
            // Real Push detected
            giNumberOfPushButtonPressed++;
            if (iStartIndex < MAX_PUSHINTERRUPTS) {
              iStartIndex = iIndex + 1;
            }
            if (iEndIndex < MAX_PUSHINTERRUPTS - 1) {
...

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

Credits

Max Paul

Max Paul

1 project • 1 follower

Comments