Skip to content

Commit 87af8db

Browse files
committed
Merge pull request particle-iot#255 from monkbroc/feature/device-animation
Use SVG animations for RGB LED demonstrations
2 parents 59d6ba3 + 1b2fd8b commit 87af8db

File tree

14 files changed

+1383
-142
lines changed

14 files changed

+1383
-142
lines changed

scripts/fork.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
var cloneDeep = require('lodash').cloneDeep;
44
var path = require('path');
55
var fs = require('fs');
6+
var titleize = require('../templates/helpers/titleize');
67

78
module.exports = function(options) {
89
var key = options.key;
10+
var keySingular = key.replace(/s$/, "");
911
var redirectTemplate = fs.readFileSync(path.resolve(options.redirectTemplate));
1012

1113
return function(files, metalsmith, done) {
@@ -28,6 +30,7 @@ module.exports = function(options) {
2830
});
2931

3032
newFile[value] = true;
33+
newFile[keySingular] = titleize(value);
3134
files[newName] = newFile;
3235
forkLocations[value] = '/' + newName.replace(extension, '');
3336
});

src/assets/images/core.svg

Lines changed: 326 additions & 0 deletions
Loading
File renamed without changes.

src/assets/images/photon.svg

Lines changed: 616 additions & 0 deletions
Loading

src/assets/js/animated-core.js

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/assets/js/device-animation.js

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
var DeviceAnimation = function(element, deviceType) {
2+
var deviceSpecs = {
3+
"Photon": {
4+
image: "/assets/images/photon.svg",
5+
zoom: 2,
6+
width: 144,
7+
height: 77,
8+
led: {
9+
x: 93.5,
10+
y: 38.5,
11+
radius: 30
12+
}
13+
},
14+
"Core": {
15+
image: "/assets/images/core.svg",
16+
zoom: 2,
17+
width: 143,
18+
height: 77,
19+
led: {
20+
x: 104,
21+
y: 38.5,
22+
radius: 30
23+
}
24+
}
25+
};
26+
27+
var device = deviceSpecs[deviceType];
28+
29+
// Create SVG canvas
30+
var showLight = false;
31+
var draw = SVG(element).size(device.zoom * device.width, device.zoom * device.height);
32+
var group = draw.group().scale(device.zoom).translate(0.5, 0.5);
33+
34+
// Add the device image
35+
var img = group.image(device.image).size(device.width, device.height).loaded(function() {
36+
showLight = true;
37+
});
38+
39+
// Add the LED with color gradient
40+
var led = group.circle(device.led.radius).center(device.led.x, device.led.y);
41+
42+
var light_s1, light_s2;
43+
var light = group.gradient('radial', function(stop) {
44+
light_s1 = stop.at(0, "#000", 0);
45+
light_s2 = stop.at(0.3, "#000", 0);
46+
stop.at(1, "#000", 0);
47+
});
48+
light.radius(0.6);
49+
led.fill(light);
50+
51+
// Change the color of the LED gradient
52+
function colorLed(color, opacity) {
53+
if(!showLight) {
54+
return;
55+
}
56+
light_s1.update(0, color, opacity);
57+
light_s2.update(0.3, color, opacity);
58+
}
59+
60+
// Delay the next step using the animation callback
61+
function afterDelay(duration, next) {
62+
var t = Date.now();
63+
var delayFn = function() {
64+
var now = Date.now();
65+
if(now - t >= duration) {
66+
next();
67+
} else {
68+
requestAnimationFrame(delayFn);
69+
}
70+
};
71+
requestAnimationFrame(delayFn);
72+
}
73+
74+
// Turn LED on then off
75+
function blinkOnce(color, onDuration, offDuration, next) {
76+
onDuration = onDuration || 100;
77+
offDuration = offDuration || onDuration;
78+
79+
colorLed(color, 1);
80+
afterDelay(onDuration, function() {
81+
colorLed(color, 0);
82+
afterDelay(offDuration, next);
83+
});
84+
}
85+
86+
// Turn LED on then off multiple times
87+
function blinkMultiple(color, times, onDuration, offDuration, next) {
88+
blinkOnce(color, onDuration, offDuration, function() {
89+
if(times > 1) {
90+
blinkMultiple(color, times - 1, onDuration, offDuration, next);
91+
} else {
92+
next();
93+
}
94+
});
95+
}
96+
97+
// Fade LED from full color to transparent
98+
function breatheOnce(color, duration, next) {
99+
duration = duration || 2000;
100+
101+
// Reduce redraws by only changing color every 100ms
102+
var interval = 100;
103+
var t = Date.now();
104+
led.animate(duration, ">").during(function(pos) {
105+
var now = Date.now();
106+
var delta = now - t;
107+
if(delta > interval) {
108+
t = now - (delta % interval);
109+
colorLed(color, pos);
110+
}
111+
}).loop(1, true).after(next);
112+
}
113+
114+
// Fade LED multiple times
115+
function breatheMultiple(color, times, duration, next) {
116+
breatheOnce(color, duration, function() {
117+
if(times > 1) {
118+
breatheMultiple(color, times - 1, duration, next);
119+
} else {
120+
next();
121+
}
122+
});
123+
}
124+
125+
// Public API
126+
127+
// Color formats: hex RGB, names
128+
// http://www.w3.org/TR/css3-color/#svg-color
129+
130+
// Fade LED and repeat forever
131+
function breathe(color, duration) {
132+
breatheOnce(color, duration, function() {
133+
breathe(color, duration);
134+
});
135+
}
136+
137+
// Blink LED and repeat forever
138+
function blink(color, onDuration, offDuration) {
139+
blinkOnce(color, onDuration, offDuration, function() {
140+
blink(color, onDuration, offDuration);
141+
});
142+
}
143+
144+
// Blink the SOS pattern used when the firmware crashes
145+
function sos(code, color) {
146+
code = code || 1;
147+
color = color || "red";
148+
var shortDuration = 150;
149+
var longDuration = 300;
150+
var offDuration = 100;
151+
var pauseDuration = 900;
152+
153+
function blinkSos(next) {
154+
blinkMultiple(color, 3, shortDuration, offDuration, function() {
155+
afterDelay(offDuration, function() {
156+
blinkMultiple(color, 3, longDuration, offDuration, function() {
157+
afterDelay(offDuration, function() {
158+
blinkMultiple(color, 3, shortDuration, offDuration, function() {
159+
afterDelay(pauseDuration, next);
160+
});
161+
});
162+
});
163+
})
164+
});
165+
}
166+
167+
function blinkSosCodeSos() {
168+
blinkSos(function() {
169+
blinkMultiple(color, code, longDuration, longDuration, function() {
170+
blinkSos(function() {
171+
afterDelay(3 * pauseDuration, blinkSosCodeSos);
172+
});
173+
});
174+
});
175+
}
176+
177+
blinkSosCodeSos();
178+
}
179+
180+
// Perform a pattern of blink, breathe, on and off
181+
// Each argument contains a phrase describing part of the pattern.
182+
// The pattern is looped forever.
183+
//
184+
// Each argument is a string of this format:
185+
// action [color] [onDuration] [offDuration] [repetitions]
186+
//
187+
// Available actions: blink, breathe, on, off
188+
//
189+
// Color format: HTML name, hex code #aabbcc, rgb(1, 2, 3), rgba(1, 3, 4, 0.5)
190+
//
191+
// Duration: number followed by ms like 100ms
192+
//
193+
// Repetitions: number followed by times like 3 times (default 1 time)
194+
//
195+
// Examples:
196+
// blink green
197+
// breathe #00ffff 1000ms
198+
// blink red 100ms 300ms 3 times
199+
// on yellow 400ms
200+
//
201+
var phraseRegex = /(\w+)(\s+[a-zA-Z#]+[a-zA-Z0-9]+)?(\([^)]*\))?(\s+\d+ms)?(\s+\d+ms)?(\s+\d+ times?)?/;
202+
function pattern(/* variable arguments */) {
203+
// Convert variable arguments lists to an array
204+
var patternPhrases = Array.prototype.slice.call(arguments);
205+
206+
function performPatternPhrase(i) {
207+
var phrase = patternPhrases[i];
208+
var m = phraseRegex.exec(phrase);
209+
if(phrase && m !== null) {
210+
var action = m[1];
211+
var color = [m[2], m[3]].join("");
212+
var onDuration = parseInt(m[4]) || 0;
213+
var offDuration = parseInt(m[5]) || 0;
214+
var times = parseInt(m[6]) || 1;
215+
216+
var next = function() {
217+
performPatternPhrase(i + 1);
218+
};
219+
220+
switch(action) {
221+
case "breathe":
222+
breatheMultiple(color, times, onDuration, next);
223+
break;
224+
case "blink":
225+
blinkMultiple(color, times, onDuration, offDuration, next);
226+
break;
227+
case "on":
228+
colorLed(color, 1);
229+
afterDelay(onDuration, next);
230+
break;
231+
case "off":
232+
colorLed(null, 0);
233+
afterDelay(onDuration, next);
234+
break;
235+
}
236+
} else {
237+
// Prevent infinite loop in case of bad pattern
238+
if(i != 0) {
239+
performPatternPhrase(0);
240+
}
241+
}
242+
}
243+
244+
performPatternPhrase(0);
245+
}
246+
247+
return {
248+
breathe: breathe,
249+
blink: blink,
250+
sos: sos,
251+
pattern: pattern
252+
};
253+
};
254+
255+
// Apply DeviceAnimation to elements with data-device-animation on page load
256+
$(document).ready(function() {
257+
$('[data-device-animation]').each(function() {
258+
var el = $(this);
259+
var type = el.data("device-type");
260+
var animation = el.data("device-animation");
261+
var params = el.data("device-params");
262+
263+
var deviceAnimation = DeviceAnimation(this, type);
264+
deviceAnimation[animation].apply(deviceAnimation, params);
265+
});
266+
});

src/assets/js/svg.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/less/getting-started.less

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,8 @@ ul.devices {
7474
}
7575
}
7676
}
77+
78+
.device-animation {
79+
margin: 40px 0;
80+
text-align: center;
81+
}

0 commit comments

Comments
 (0)