Adding ESPuP Holiday Light files
This commit is contained in:
parent
654a373fd5
commit
fa7d60333b
2 changed files with 276 additions and 0 deletions
170
ESP8266_MicroPython_Holiday_Lights/lights.html
Executable file
170
ESP8266_MicroPython_Holiday_Lights/lights.html
Executable file
|
|
@ -0,0 +1,170 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>ESP8266 MicroPython Smart Holiday Lights</title>
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-lg-offset-2">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>MicroPython Smart Holiday Lights</h1>
|
||||||
|
<p class="lead">Control your holiday lights using MicroPython and the
|
||||||
|
ESP8266 WiFi microcontroller.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4 col-lg-offset-4">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="boardURL">Board URL</label>
|
||||||
|
<input type="text" class="form-control" id="boardURL" value="ws://192.168.4.1:8266">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" class="form-control" id="password" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="animation">Animation</label>
|
||||||
|
<select class="form-control" id="animation">
|
||||||
|
<option value="solid">Solid</option>
|
||||||
|
<option value="chase">Chase</option>
|
||||||
|
<option value="smooth">Smooth</option>
|
||||||
|
<option value="blank">Blank (off)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="periodMS">Animation Period (milliseconds)</label>
|
||||||
|
<input type="text" class="form-control" id="periodMS" value="250">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="colors">Colors</label>
|
||||||
|
<select class="form-control" id="colors">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="mirror" checked>
|
||||||
|
Mirror Colors
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary" type="button" id="update">Update Lights</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-3.1.1.min.js"
|
||||||
|
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
// Global configuration:
|
||||||
|
var colors = [
|
||||||
|
{ name: 'Blue - Red', value: [[0,0,255], [16,0,128], [32,0,64], [64,0,32], [128,0,16], [255,0,0]] },
|
||||||
|
{ name: 'Red - Green', value: [[255,0,0], [128,16,0], [64,32,0], [32,64,0], [16,128,0], [0,255,0]] },
|
||||||
|
{ name: 'Blue - White', value: [[0,0,255], [16,16,255], [32,32,255], [64,64,255], [128,128,255], [255,255,255]] }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Function to connect to the WebREPL on the specified board URL and with the
|
||||||
|
// specified password. Once connected the onConnect callback will be called
|
||||||
|
// and a websocket object passed in. If an error occurs the onError callback
|
||||||
|
// is called with the error event object as a parameter.
|
||||||
|
function webreplConnect(url, password, onConnect, onError) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
ws.onopen = function() {
|
||||||
|
// Print messages received from the WebREPL. Uncomment this to help with
|
||||||
|
// debugging.
|
||||||
|
ws.onmessage = function(e) {
|
||||||
|
console.log(e.data);
|
||||||
|
}
|
||||||
|
// First send the password immediately since it's required to use WebREPL.
|
||||||
|
ws.send(password + '\r\n'); // Send password and CR LF (like pressing enter).
|
||||||
|
// Next send a Ctrl-C to stop any running main loop.
|
||||||
|
ws.send('\x03');
|
||||||
|
// Fire the connected callback.
|
||||||
|
onConnect(ws);
|
||||||
|
};
|
||||||
|
ws.onerror = function(e) {
|
||||||
|
// Something went wrong! Call the user's error handler.
|
||||||
|
onError(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a file to a board using the WebREPL. WS should be a websocket connection
|
||||||
|
// to a board, filename is the name of the file to save on the board, and data
|
||||||
|
// should be a Uint8Array of data for the file.
|
||||||
|
function putFile(ws, fileName, data) {
|
||||||
|
// Construct a header to specify a file put operation.
|
||||||
|
var size = data.length;
|
||||||
|
var rec = new Uint8Array(2 + 1 + 1 + 8 + 4 + 2 + 64);
|
||||||
|
rec[0] = 'W'.charCodeAt(0);
|
||||||
|
rec[1] = 'A'.charCodeAt(0);
|
||||||
|
rec[2] = 1; // put
|
||||||
|
rec[3] = 0;
|
||||||
|
rec[4] = 0; rec[5] = 0; rec[6] = 0; rec[7] = 0; rec[8] = 0; rec[9] = 0; rec[10] = 0; rec[11] = 0;
|
||||||
|
rec[12] = size & 0xff; rec[13] = (size >> 8) & 0xff; rec[14] = (size >> 16) & 0xff; rec[15] = (size >> 24) & 0xff;
|
||||||
|
rec[16] = fileName.length & 0xff; rec[17] = (fileName.length >> 8) & 0xff;
|
||||||
|
for (var i = 0; i < 64; ++i) {
|
||||||
|
if (i < fileName.length) {
|
||||||
|
rec[18 + i] = fileName.charCodeAt(i);
|
||||||
|
} else {
|
||||||
|
rec[18 + i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send put file request header that was constructed above.
|
||||||
|
ws.send(rec);
|
||||||
|
// Next send the file data.
|
||||||
|
ws.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Main code that runs when the page is loaded.
|
||||||
|
// Populate the color selection list.
|
||||||
|
colors.forEach(function(color, i) {
|
||||||
|
$('#colors').append($('<option></option>').attr('value', i).text(color.name));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the board state when the update button is clicked.
|
||||||
|
$('#update').click(function() {
|
||||||
|
// Build the board's new configuration.
|
||||||
|
var config = {
|
||||||
|
colors: colors[$('#colors').val()].value,
|
||||||
|
period_ms: Number($('#periodMS').val()),
|
||||||
|
mirror_colors: $('#mirror').prop('checked'),
|
||||||
|
animation: $('#animation').val()
|
||||||
|
}
|
||||||
|
var boardURL = $('#boardURL').val();
|
||||||
|
var password = $('#password').val();
|
||||||
|
// Connect to the board's REPL.
|
||||||
|
webreplConnect(boardURL, password, function(ws) {
|
||||||
|
console.log('Connected!');
|
||||||
|
// Connected, now convert config state to JSON bytes.
|
||||||
|
var configJSON = JSON.stringify(config);
|
||||||
|
var configBytes = new Uint8Array(configJSON.length);
|
||||||
|
for (var i = 0; i < configJSON.length; ++i) {
|
||||||
|
configBytes[i] = configJSON.charCodeAt(i);
|
||||||
|
}
|
||||||
|
// Send the new configuration file.
|
||||||
|
putFile(ws, 'config.json', configBytes);
|
||||||
|
// Reset the board so the new configuration takes effect.
|
||||||
|
// Note that sending a Ctrl-B doesn't seem to work as the WebREPL won't
|
||||||
|
// restart on soft reset.
|
||||||
|
ws.send('import machine\r\nmachine.reset()\r\n');
|
||||||
|
// Close the connection.
|
||||||
|
ws.close();
|
||||||
|
console.log('Updated lights!');
|
||||||
|
},
|
||||||
|
function(e) {
|
||||||
|
console.log('Error connecting to MicroPython board!');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
106
ESP8266_MicroPython_Holiday_Lights/lights.py
Executable file
106
ESP8266_MicroPython_Holiday_Lights/lights.py
Executable file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# ESP8266 MicroPython smart holiday lights project code.
|
||||||
|
# This will animate NeoPixels that can be controlled from the included
|
||||||
|
# lights.html web page.
|
||||||
|
# Author: Tony DiCola
|
||||||
|
# License: MIT License
|
||||||
|
import machine
|
||||||
|
import neopixel
|
||||||
|
import utime
|
||||||
|
import ujson
|
||||||
|
|
||||||
|
|
||||||
|
# Static configuration that never changes:
|
||||||
|
PIXEL_PIN = machine.Pin(15, machine.Pin.OUT) # Pin connected to the NeoPixels.
|
||||||
|
PIXEL_COUNT = 32 # Number of NeoPixels.
|
||||||
|
CONFIG_FILE = 'config.json' # Name of animation config file.
|
||||||
|
|
||||||
|
|
||||||
|
# Mirror the colors to make a ramp up and ramp down with no repeated colors.
|
||||||
|
def mirror(values):
|
||||||
|
# Add the input values in reverse order to the end of the array.
|
||||||
|
# However slice off the very first and very last items (the [1:-1] syntax)
|
||||||
|
# to prevent the first and last values from repeating.
|
||||||
|
# For example an input of:
|
||||||
|
# [1, 2, 3]
|
||||||
|
# Returns:
|
||||||
|
# [1, 2, 3, 2]
|
||||||
|
# Instead of returning:
|
||||||
|
# [1, 2, 3, 3, 2, 1]
|
||||||
|
# Which would duplicate 3 and 1 as you loop through the elements.
|
||||||
|
values.extend(list(reversed(values))[1:-1])
|
||||||
|
return values
|
||||||
|
|
||||||
|
# Linear interpolation helper:
|
||||||
|
def _lerp(x, x0, x1, y0, y1):
|
||||||
|
return y0 + (x - x0) * ((y1 - y0)/(x1 - x0))
|
||||||
|
|
||||||
|
# Animation functions:
|
||||||
|
def blank(config, np, pixel_count):
|
||||||
|
# Turn off all the pixels.
|
||||||
|
np.fill((0,0,0))
|
||||||
|
np.write()
|
||||||
|
|
||||||
|
def solid(config, np, pixel_count):
|
||||||
|
# Solid pulse of all pixels at the same color.
|
||||||
|
colors = config['colors']
|
||||||
|
elapsed = utime.ticks_ms() // config['period_ms']
|
||||||
|
current = elapsed % len(colors)
|
||||||
|
np.fill(colors[current])
|
||||||
|
np.write()
|
||||||
|
|
||||||
|
def chase(config, np, pixel_count):
|
||||||
|
# Chasing animation of pixels through different colors.
|
||||||
|
colors = config['colors']
|
||||||
|
elapsed = utime.ticks_ms() // config['period_ms']
|
||||||
|
for i in range(PIXEL_COUNT):
|
||||||
|
current = (elapsed+i) % len(colors)
|
||||||
|
np[i] = colors[current]
|
||||||
|
np.write()
|
||||||
|
|
||||||
|
def smooth(config, np, pixel_count):
|
||||||
|
# Smooth pulse of all pixels at the same color. Interpolates inbetween colors
|
||||||
|
# for smoother animation.
|
||||||
|
colors = config['colors']
|
||||||
|
period_ms = config['period_ms']
|
||||||
|
ticks = utime.ticks_ms()
|
||||||
|
step = ticks // period_ms
|
||||||
|
offset = ticks % period_ms
|
||||||
|
color0 = colors[step % len(colors)]
|
||||||
|
color1 = colors[(step+1) % len(colors)]
|
||||||
|
color = (int(_lerp(offset, 0, period_ms, color0[0], color1[0])),
|
||||||
|
int(_lerp(offset, 0, period_ms, color0[1], color1[1])),
|
||||||
|
int(_lerp(offset, 0, period_ms, color0[2], color1[2])))
|
||||||
|
np.fill(color)
|
||||||
|
np.write()
|
||||||
|
|
||||||
|
|
||||||
|
# Setup code:
|
||||||
|
# Initialize NeoPixels and turn them off.
|
||||||
|
np = neopixel.NeoPixel(PIXEL_PIN, PIXEL_COUNT)
|
||||||
|
np.fill((0,0,0))
|
||||||
|
np.write()
|
||||||
|
|
||||||
|
# Try loading the animation configuration, otherwise fall back to a blank default.
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, 'r') as infile:
|
||||||
|
config = ujson.loads(infile.read())
|
||||||
|
except OSError:
|
||||||
|
# Couldn't load the config file, so fall back to a default blank animation.
|
||||||
|
config = {
|
||||||
|
'colors': [[0,0,0]],
|
||||||
|
'mirror_colors': False,
|
||||||
|
'period_ms': 250,
|
||||||
|
'animation': 'blank'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mirror the color array if necessary.
|
||||||
|
if config['mirror_colors']:
|
||||||
|
config['colors'] = mirror(config['colors'])
|
||||||
|
|
||||||
|
# Determine which animation function should be called.
|
||||||
|
animation = globals().get(config['animation'], blank)
|
||||||
|
|
||||||
|
# Main loop code:
|
||||||
|
while True:
|
||||||
|
animation(config, np, PIXEL_COUNT)
|
||||||
|
utime.sleep(0.01)
|
||||||
Loading…
Reference in a new issue