Wizard of Oz + Blynk

A ‘Wizard of Oz’ prototype is a way to put together a semi-working demonstration and realisation of a design proposal. In the case of our studio, for the exhibition, you will want to think about how your data expressions might be manually triggered on demand, since you do want the public audience to see the work in action.

The Blynk app is an excellent prototyping helper that allows you to construct mobile app-based interactions. This can come in really handy if you'd like to demonstrate your exhibition remotely using your phone. There are also Blynk nodes we can use on IoTa, which means full control of data flows and event triggers between Photons and data feeds are possible.

Learn more about Blynk here before tackling this recipe.

(Choosing whether to incorporate Blynk functionality of course depends on your design decisions – some of you will have exhibits that constantly flow and transform physically according to live data, others might rely on kinetic interactions to trigger specific ‘moments’. Some of you might not want to involve any mobile devices)

Here we are using the Neopixel 8-pixel strip, together with a simple analogue sensor, to demonstrate how all this works. Once configured and linked properly via your flows in IoTa, you will be able to trigger events from the Blynk app, and also receive data from your flows.


Libraries Used

(learn how to import them in the Build IDE):


Code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
	Wizard of Oz Prototyping & Blynk – an example on how to to use Blynk as a prototyping tool in the Photon and IoTa environment

	The MIT License (MIT)
	Copyright © 2017 Chuan Khoo

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be included
	in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
	THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
	OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
	OTHER DEALINGS IN THE SOFTWARE.
*/

// This #include statement was automatically added by the Particle IDE.
#include <blynk.h>
#include <neopixel.h>

#define BLYNK_AUTH  "enterTokenHere"
#define BLYNK_PRINT Serial

// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D0
#define PIXEL_COUNT 8
#define PIXEL_TYPE WS2812B
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

// how long between each sensor update? feel free to change this value,
// but this must not be anything less than 500 milliseconds!
#define INTERVAL_BETWEEN_LOGS   500

// initialise the timer
Timer dataUpdateTimer(INTERVAL_BETWEEN_LOGS, readSensor);

bool doNextWipe = false;
int cRed, cGreen, cBlue;     // used to store any new colour from Blynk
uint32_t newCol = 0;             // stores composite colour value

void setup() {
    strip.begin();
    strip.show();

 Serial.begin(57600);
    Blynk.begin(BLYNK_AUTH);

    delay(1000);

    dataUpdateTimer.start();        // start our dataUpdateTimer
}


void loop() {
    Blynk.run();    // always needs to be called in loop()

    if(doNextWipe) {
        doNextWipe = false;
        colorWipe(newCol, 50);  // change 50 to adjust speed of transition
    }
}


void readSensor() {
    // you can still read and act on the data from sensors
    // connected to your Photon!

    int sensorReading = analogRead(A0);

    // explicitly send out data readings everytime readSensor is called
    // (start with a 500ms interval, too short and Blynk trips up!)
    Blynk.virtualWrite(V2, sensorReading);
}



//////////// NEOPIXEL-specific

// Set all pixels in the strip to a solid color, then wait (ms)
void colorAll(uint32_t c, uint8_t wait) {
  uint16_t i;

  for(i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
  }
  strip.show();
  delay(wait);
}

// Fill the dots one after the other with a color, wait (ms) after each one
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Slightly different, this makes the rainbow equally distributed throughout, then wait (ms)
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) { // 1 cycle of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}




//////////// BLYNK-specific
BLYNK_WRITE(V0) {
    // example where the state of a Blynk interface object can
    // be used to toggle things on your Photon – great for buttons
    switch(param.asInt()) {
        case 1:
            Serial.println("Received a V0, argument is 1");
            // feel free to call something else here when V1 = 1
            break;
        case 0:
            Serial.println("Received a V0, argument is 0");
            // likewise, when you receive a '0' argument on V0
            break;
    }
}

BLYNK_WRITE(V1) {
    // takes data from Blynk's colour picker – the zRGBA object
    cRed = param[0].asInt();
    cGreen = param[1].asInt();
    cBlue = param[2].asInt();

    // some math to combine separate RGB into a 24-bit value
    newCol = 0;
    newCol |= (cRed & 255) << 16;
    newCol |= (cGreen & 255) << 8;
    newCol |= (cBlue & 255);

    // get ready for the next colour wipe!    
    doNextWipe = true;
}