Added ReST API and a simple web application
Added OpenSCAD file for the project case
This commit is contained in:
parent
b3d980c2ac
commit
4fbbee8f5b
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
.piolibdeps
|
.piolibdeps
|
||||||
src/credentials.h
|
src/credentials.h
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
web/node_modules/
|
||||||
|
97
case/StairCase.scad
Normal file
97
case/StairCase.scad
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
caseBottom();
|
||||||
|
|
||||||
|
$fa = 1;
|
||||||
|
$fs = 1;
|
||||||
|
|
||||||
|
module caseBottom()
|
||||||
|
{
|
||||||
|
innerX = 80;
|
||||||
|
innerY = 70;
|
||||||
|
innerZ = 35;
|
||||||
|
wallThickness = 2;
|
||||||
|
|
||||||
|
totalX = innerX + (2 * wallThickness);
|
||||||
|
totalY = innerY + (2 * wallThickness);
|
||||||
|
totalZ = innerZ + wallThickness;
|
||||||
|
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
cube([totalX, totalY, totalZ]);
|
||||||
|
translate([wallThickness, wallThickness, wallThickness])
|
||||||
|
cube([innerX, innerY, totalZ]);
|
||||||
|
|
||||||
|
// Hole for the LED cables
|
||||||
|
translate([-1, totalY / 2, 25])
|
||||||
|
rotate([0, 90, 0])
|
||||||
|
cylinder(d = 15, h = wallThickness + 2);
|
||||||
|
|
||||||
|
// Hole for the power cable
|
||||||
|
translate([62, totalY + 1, wallThickness + 2.5])
|
||||||
|
rotate([90, 0, 0])
|
||||||
|
cylinder(d = 5, h = wallThickness + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
translate([wallThickness, wallThickness, wallThickness])
|
||||||
|
{
|
||||||
|
translate([4, 4, 0])
|
||||||
|
PCA9685Mount();
|
||||||
|
|
||||||
|
translate([40, 20, 0])
|
||||||
|
ESP8266PlusPowerMount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module PCA9685Mount()
|
||||||
|
{
|
||||||
|
totalX = 25.5;
|
||||||
|
totalY = 62.5;
|
||||||
|
pinDistanceX = 19;
|
||||||
|
pinDistanceY = 56;
|
||||||
|
pinDiameter = 2.3;
|
||||||
|
pinHeight = 3;
|
||||||
|
supportDiameter = 4;
|
||||||
|
supportHeight = 4;
|
||||||
|
|
||||||
|
offsetX = (totalX - pinDistanceX) / 2;
|
||||||
|
offsetY = (totalY - pinDistanceY) / 2;
|
||||||
|
|
||||||
|
for (x = [offsetX, offsetX + pinDistanceX])
|
||||||
|
for (y = [offsetY, offsetY + pinDistanceY])
|
||||||
|
translate([x, y, 0])
|
||||||
|
{
|
||||||
|
cylinder(d = supportDiameter, h = supportHeight);
|
||||||
|
translate([0, 0, supportHeight])
|
||||||
|
cylinder(d = pinDiameter, h = pinHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// I didn't put any actual mount points in the perfboard,
|
||||||
|
// so I'll settle for some corner pieces to align it and
|
||||||
|
// use the ultimate maker's friend: hot glue.
|
||||||
|
module ESP8266PlusPowerMount()
|
||||||
|
{
|
||||||
|
innerX = 34;
|
||||||
|
innerY = 35;
|
||||||
|
cornerSize = 6;
|
||||||
|
cornerThickness = 2;
|
||||||
|
cornerHeight = 2;
|
||||||
|
|
||||||
|
totalX = innerX + (2 * cornerThickness);
|
||||||
|
totalY = innerY + (2 * cornerThickness);
|
||||||
|
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
cube([totalX, totalY, cornerHeight]);
|
||||||
|
|
||||||
|
translate([cornerThickness, cornerThickness, -1])
|
||||||
|
cube([innerX, innerY, cornerHeight + 2]);
|
||||||
|
|
||||||
|
translate([-1, cornerSize, -1])
|
||||||
|
cube([totalX + 2, totalY - (2 * cornerSize), cornerHeight + 2]);
|
||||||
|
|
||||||
|
translate([cornerSize, -1, -1])
|
||||||
|
cube([totalX - (2 * cornerSize), totalY + 2, cornerHeight + 2]);
|
||||||
|
}
|
||||||
|
}
|
@ -110,5 +110,6 @@ Lights one step at a time, moving up or down.
|
|||||||
|
|
||||||
Parameters:<br>
|
Parameters:<br>
|
||||||
**interval** (word): How long each step is lit before moving to the next.<br>
|
**interval** (word): How long each step is lit before moving to the next.<br>
|
||||||
|
**brightness** (word): value in range 0 (off) to 4095 (fully on).
|
||||||
**direction** (byte): Determines the starting step / direction. Either Bottom/Up (0) or Top/Down (1).<br>
|
**direction** (byte): Determines the starting step / direction. Either Bottom/Up (0) or Top/Down (1).<br>
|
||||||
**fadeOutTime** (word): If greater than 0 each step will fade out instead of turning off instantly after moving to the next. Specified in milliseconds.
|
**fadeOutTime** (word): If greater than 0 each step will fade out instead of turning off instantly after moving to the next. Specified in milliseconds.
|
@ -134,11 +134,13 @@ void handleRequest(uint8_t* packet)
|
|||||||
{
|
{
|
||||||
case Command::Ping:
|
case Command::Ping:
|
||||||
udpServer.write(Command::Ping);
|
udpServer.write(Command::Ping);
|
||||||
|
udpServer.write(StepCount);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::GetMode:
|
case Command::GetMode:
|
||||||
udpServer.write(Command::GetMode);
|
udpServer.write(Command::GetMode);
|
||||||
udpServer.write(currentModeIdentifier);
|
udpServer.write(currentModeIdentifier);
|
||||||
|
currentMode->write(&udpServer);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::SetMode:
|
case Command::SetMode:
|
||||||
@ -163,6 +165,7 @@ void handleRequest(uint8_t* packet)
|
|||||||
{
|
{
|
||||||
udpServer.write(Command::Error);
|
udpServer.write(Command::Error);
|
||||||
udpServer.write(Command::SetMode);
|
udpServer.write(Command::SetMode);
|
||||||
|
udpServer.write(newIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -186,7 +189,7 @@ IMode* createMode(uint8_t identifier)
|
|||||||
switch (identifier)
|
switch (identifier)
|
||||||
{
|
{
|
||||||
case Mode::Static: return new StaticMode();
|
case Mode::Static: return new StaticMode();
|
||||||
//case Mode::Custom: return new CustomMode();
|
case Mode::Custom: return new CustomMode();
|
||||||
case Mode::Alternate: return new AlternateMode();
|
case Mode::Alternate: return new AlternateMode();
|
||||||
//case Mode::Slide: return new SlideMode();
|
//case Mode::Slide: return new SlideMode();
|
||||||
//case Mode::ADC: return new ADCInputMode();
|
//case Mode::ADC: return new ADCInputMode();
|
||||||
|
87
web/app.js
87
web/app.js
@ -1,50 +1,55 @@
|
|||||||
var protocol = require('./protocol');
|
var express = require('express');
|
||||||
var dgram = require('dgram');
|
var client = require('./client');
|
||||||
|
|
||||||
var on = 0;
|
var httpPort = 3127;
|
||||||
var speed = 256;
|
|
||||||
|
var stairsHost = '10.138.2.12';
|
||||||
|
var stairsUdpPort = 3126;
|
||||||
|
|
||||||
|
|
||||||
function lsb(value) { return value & 0xFF; }
|
client.init(stairsHost, stairsUdpPort);
|
||||||
function msb(value) { return (value >> 8) & 0xFF; }
|
|
||||||
|
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.get('/ping', function(req, res)
|
||||||
/*
|
|
||||||
Alternating
|
|
||||||
|
|
||||||
var message = new Buffer([protocol.Command.SetMode, protocol.Mode.Alternate, lsb(500), msb(500), lsb(128), msb(128)]);
|
|
||||||
client.send(message, 0, message.length, 3126, '10.138.2.12', function(err, bytes) {
|
|
||||||
if (err) throw err;
|
|
||||||
console.log('UDP message sent');
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
var client = dgram.createSocket('udp4');
|
|
||||||
client.on('listening', function()
|
|
||||||
{
|
{
|
||||||
var address = client.address();
|
client.ping(function(data, error)
|
||||||
console.log('UDP client listening on ' + address.address + ":" + address.port);
|
{
|
||||||
});
|
if (error)
|
||||||
|
res.status(500);
|
||||||
|
|
||||||
client.on('message', function (message, remote)
|
res.send(data);
|
||||||
{
|
|
||||||
console.log('< ' + remote.address + ':' + remote.port +' - ' + message.toString('hex'));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
setInterval(function()
|
|
||||||
{
|
|
||||||
// 0x00, 0x10 = 4096
|
|
||||||
on += speed;
|
|
||||||
if (on <= 0 || on >= 4096)
|
|
||||||
speed = -speed;
|
|
||||||
|
|
||||||
var message = new Buffer([protocol.Command.SetMode, protocol.Mode.Static, lsb(on), msb(on)]);
|
|
||||||
client.send(message, 0, message.length, 3126, '10.138.2.12', function(err, bytes) {
|
|
||||||
if (err) throw err;
|
|
||||||
console.log('> ' + '10.138.2.12' + ':' + '3126' + ' - ' + message.toString('hex'));
|
|
||||||
});
|
});
|
||||||
}, 200);
|
});
|
||||||
|
|
||||||
|
app.get('/getMode', function(req, res)
|
||||||
|
{
|
||||||
|
client.getMode(function(data, error)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
res.status(500);
|
||||||
|
|
||||||
|
res.send(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/setMode/:mode', function(req, res)
|
||||||
|
{
|
||||||
|
client.setMode(req.params.mode, req.query, function(data, error)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
res.status(500);
|
||||||
|
|
||||||
|
res.send(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(express.static('static'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(httpPort, function ()
|
||||||
|
{
|
||||||
|
console.log('Stairs ReST service running on port ' + httpPort);
|
||||||
|
});
|
233
web/client.js
Normal file
233
web/client.js
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
var dgram = require('dgram');
|
||||||
|
var protocol = require('./protocol');
|
||||||
|
var BufferReader = require('buffer-reader');
|
||||||
|
|
||||||
|
|
||||||
|
var responseHandlers = {};
|
||||||
|
|
||||||
|
function registerResponseHandler(command, callback)
|
||||||
|
{
|
||||||
|
if (!responseHandlers.hasOwnProperty(command))
|
||||||
|
responseHandlers[command] = [callback];
|
||||||
|
else
|
||||||
|
responseHandlers[command].push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function callResponseHandlers(command, reader, error)
|
||||||
|
{
|
||||||
|
if (!responseHandlers.hasOwnProperty(command))
|
||||||
|
return;
|
||||||
|
|
||||||
|
newHandlers = [];
|
||||||
|
responseHandlers[command].forEach(function(callback)
|
||||||
|
{
|
||||||
|
if (!callback(reader, error))
|
||||||
|
newHandlers.push(callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
responseHandlers[command] = newHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var serverHost = '';
|
||||||
|
var serverPort = 0;
|
||||||
|
|
||||||
|
var client = dgram.createSocket('udp4');
|
||||||
|
client.on('message', function (message, remote)
|
||||||
|
{
|
||||||
|
console.log(message.toString('hex'));
|
||||||
|
if (message.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var reader = new BufferReader(message);
|
||||||
|
|
||||||
|
if (reader.nextInt8() !== protocol.Command.Reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var command = reader.nextInt8();
|
||||||
|
if (command === protocol.Command.Error)
|
||||||
|
callResponseHandlers(reader.nextInt8(), reader, true)
|
||||||
|
else
|
||||||
|
callResponseHandlers(command, reader, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function requestResponse(buffer, callback)
|
||||||
|
{
|
||||||
|
if (buffer === null || buffer.length == 0) return;
|
||||||
|
console.log('> ' + buffer.toString('hex'));
|
||||||
|
|
||||||
|
var command = buffer.readInt8(0);
|
||||||
|
var cancelled = false;
|
||||||
|
|
||||||
|
var timeout = setTimeout(function()
|
||||||
|
{
|
||||||
|
cancelled = true;
|
||||||
|
callback(null, true);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
registerResponseHandler(command, function(reader, error)
|
||||||
|
{
|
||||||
|
if (cancelled) return;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
callback(reader, error);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.send(buffer, 0, buffer.length, serverPort, serverHost, function(err, bytes)
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
onError();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function readModeData(mode, reader)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case protocol.Mode.Static:
|
||||||
|
return {
|
||||||
|
brightness: reader.nextInt16LE()
|
||||||
|
};
|
||||||
|
|
||||||
|
case protocol.Mode.Custom:
|
||||||
|
var values = [];
|
||||||
|
while (reader.tell() < reader.buf.length)
|
||||||
|
values.push(reader.nextInt16LE());
|
||||||
|
|
||||||
|
return {
|
||||||
|
brightness: values
|
||||||
|
};
|
||||||
|
|
||||||
|
case protocol.Mode.Alternate:
|
||||||
|
return {
|
||||||
|
interval: reader.nextInt16LE(),
|
||||||
|
brightness: reader.nextInt16LE()
|
||||||
|
};
|
||||||
|
|
||||||
|
case protocol.Mode.Slide:
|
||||||
|
return {
|
||||||
|
interval: reader.nextInt16LE(),
|
||||||
|
brightness: reader.nextInt16LE(),
|
||||||
|
direction: reader.nextInt8(),
|
||||||
|
fadeOutTime: reader.nextInt16LE()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function lsb(value) { return value & 0xFF; }
|
||||||
|
function msb(value) { return (value >> 8) & 0xFF; }
|
||||||
|
|
||||||
|
function writeModeData(mode, data)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case protocol.Mode.Static:
|
||||||
|
if (typeof(data.brightness) == 'undefined') data.brightness = 0;
|
||||||
|
|
||||||
|
return new Buffer([protocol.Command.SetMode, mode, lsb(data.brightness), msb(data.brightness)]);
|
||||||
|
|
||||||
|
case protocol.Mode.Custom:
|
||||||
|
var brightness = typeof(data.brightness) !== 'undefined' ? data.brightness.split(',') : [];
|
||||||
|
|
||||||
|
var valueCount = Math.min(16, brightness.length);
|
||||||
|
var buffer = Buffer.alloc(2 + (valueCount * 2));
|
||||||
|
buffer.writeInt8(protocol.Command.SetMode, 0);
|
||||||
|
buffer.writeInt8(mode, 1);
|
||||||
|
|
||||||
|
for (var index = 0; index < valueCount; index++)
|
||||||
|
buffer.writeInt16LE(brightness[index], 2 + (index * 2));
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
|
||||||
|
case protocol.Mode.Alternate:
|
||||||
|
if (typeof(data.brightness) == 'undefined') data.brightness = 0;
|
||||||
|
if (typeof(data.interval) == 'undefined') data.interval = 500;
|
||||||
|
|
||||||
|
return new Buffer([protocol.Command.SetMode, mode,
|
||||||
|
lsb(data.interval), msb(data.interval),
|
||||||
|
lsb(data.brightness), msb(data.brightness)]);
|
||||||
|
|
||||||
|
case protocol.Mode.Slide:
|
||||||
|
if (typeof(data.brightness) == 'undefined') data.brightness = 0;
|
||||||
|
if (typeof(data.interval) == 'undefined') data.interval = 500;
|
||||||
|
if (typeof(data.direction) == 'undefined') data.direction = 0;
|
||||||
|
if (typeof(data.fadeOutTime) == 'undefined') data.fadeOutTime = 0;
|
||||||
|
|
||||||
|
return new Buffer([protocol.Command.SetMode, mode,
|
||||||
|
lsb(data.interval), msb(data.interval),
|
||||||
|
lsb(data.brightness), msb(data.brightness),
|
||||||
|
data.direction,
|
||||||
|
lsb(data.fadeOutTime), msb(data.fadeOutTime)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
{
|
||||||
|
init: function(host, port)
|
||||||
|
{
|
||||||
|
serverHost = host;
|
||||||
|
serverPort = port;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
ping: function(callback)
|
||||||
|
{
|
||||||
|
requestResponse(new Buffer([protocol.Command.Ping]),
|
||||||
|
function(reader, error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
callback(
|
||||||
|
{
|
||||||
|
stepCount: reader.nextInt8()
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
callback(null, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getMode: function(callback)
|
||||||
|
{
|
||||||
|
requestResponse(new Buffer([protocol.Command.GetMode]),
|
||||||
|
function(reader, error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
var data = { mode: reader.nextInt8() };
|
||||||
|
data.data = readModeData(data.mode, reader);
|
||||||
|
callback(data, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
callback(null, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setMode: function(mode, data, callback)
|
||||||
|
{
|
||||||
|
if (!protocol.Mode.hasOwnProperty(mode))
|
||||||
|
return;
|
||||||
|
|
||||||
|
requestResponse(writeModeData(protocol.Mode[mode], data),
|
||||||
|
function(reader, error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
var data = { mode: reader.nextInt8() };
|
||||||
|
data.data = readModeData(data.mode, reader);
|
||||||
|
callback(data, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
callback(null, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -4,5 +4,9 @@
|
|||||||
"description": "Stairs lighting project",
|
"description": "Stairs lighting project",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"author": "Mark van Renswoude",
|
"author": "Mark van Renswoude",
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-reader": "^0.1.0",
|
||||||
|
"express": "^4.15.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
66
web/static/index.html
Normal file
66
web/static/index.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<title>Stairs demo</title>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<img src="loader.gif" class="loader" data-bind="attr: { 'style': 'visibility: ' + (loading() ? 'visible' : 'hidden') }" />
|
||||||
|
|
||||||
|
<div class="main"><span style="color:#ff0000;">T</span><span style="color:#ff1200;">h</span><span style="color:#ff2400;">e</span><span style="color:#ff3600;"> </span><span style="color:#ff4900;">A</span><span style="color:#ff5b00;">m</span><span style="color:#ff6d00;">a</span><span style="color:#ff7f00;">z</span><span style="color:#ff9100;">i</span><span style="color:#ffa400;">n</span><span style="color:#ffb600;">g</span><span style="color:#ffc800;"> </span><span style="color:#ffda00;">S</span><span style="color:#ffed00;">t</span><span style="color:#ffff00;">a</span><span style="color:#dbff00;">i</span><span style="color:#b6ff00;">r</span><span style="color:#92ff00;">s</span><span style="color:#6dff00;"> </span><span style="color:#49ff00;">L</span><span style="color:#24ff00;">i</span><span style="color:#00ff00;">g</span><span style="color:#00ff24;">h</span><span style="color:#00ff49;">t</span><span style="color:#00ff6d;">i</span><span style="color:#00ff92;">n</span><span style="color:#00ffb6;">g</span><span style="color:#00ffdb;"> </span><span style="color:#00ffff;">F</span><span style="color:#00dbff;">r</span><span style="color:#00b6ff;">o</span><span style="color:#0092ff;">n</span><span style="color:#006dff;">t</span><span style="color:#0049ff;">e</span><span style="color:#0024ff;">n</span><span style="color:#0000ff;">d</span></div>
|
||||||
|
<div class="sub">"Much web technology. Wow." <span class="quote">- No one, ever</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">Mode</div>
|
||||||
|
<div class="mode">
|
||||||
|
<div class="selection"><input type="radio" name="mode" value="Static" id="Static" data-bind="checked: mode" /><label for="Static">Static</label></div>
|
||||||
|
<div class="selection"><input type="radio" name="mode" value="Custom" id="Custom" data-bind="checked: mode" /><label for="Custom">Custom</label></div>
|
||||||
|
<div class="selection"><input type="radio" name="mode" value="Alternate" id="Alternate" data-bind="checked: mode" /><label for="Alternate">Alternating</label></div>
|
||||||
|
<div class="selection"><input type="radio" name="mode" value="Slide" id="Slide" data-bind="checked: mode" /><label for="Slide">Sliding</label></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="parameters" data-bind="visible: mode() == 'Static'" style="display: none">
|
||||||
|
<div class="header">Static parameters</div>
|
||||||
|
<div class="parameter">
|
||||||
|
Brightness: <input type="range" min="0" max="4095" data-bind="value: static.brightness, valueUpdate: 'input'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="parameters" data-bind="visible: mode() == 'Custom'" style="display: none">
|
||||||
|
<div class="header">Custom parameters</div>
|
||||||
|
<!-- ko foreach: custom.brightness -->
|
||||||
|
<div class="parameter">
|
||||||
|
Step <span data-bind="text: $root.custom.brightness().length - $index()"></span>: <input type="range" min="0" max="4095" data-bind="value: $data, valueUpdate: 'input'" />
|
||||||
|
</div>
|
||||||
|
<!-- /ko -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="parameters" data-bind="visible: mode() == 'Alternate'" style="display: none">
|
||||||
|
<div class="header">Alternating parameters</div>
|
||||||
|
<div class="parameter">
|
||||||
|
Interval: <input type="number" data-bind="value: alternate.interval" />
|
||||||
|
</div>
|
||||||
|
<div class="parameter">
|
||||||
|
Brightness: <input type="range" min="0" max="4095" data-bind="value: alternate.brightness, valueUpdate: 'input'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="parameters" data-bind="visible: mode() == 'Slide'" style="display: none">
|
||||||
|
<div class="header">Sliding parameters</div>
|
||||||
|
<div class="parameter">
|
||||||
|
Brightness: <input type="range" min="0" max="4095" data-bind="value: slide.brightness, valueUpdate: 'input'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
web/static/loader.gif
Normal file
BIN
web/static/loader.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 847 B |
132
web/static/script.js
Normal file
132
web/static/script.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
var StairsViewModel = function()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.mode = ko.observable('Static');
|
||||||
|
self.static =
|
||||||
|
{
|
||||||
|
brightness: ko.observable(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.custom =
|
||||||
|
{
|
||||||
|
brightness: ko.observableArray([])
|
||||||
|
};
|
||||||
|
|
||||||
|
self.alternate =
|
||||||
|
{
|
||||||
|
interval: ko.observable(500),
|
||||||
|
brightness: ko.observable(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.slide =
|
||||||
|
{
|
||||||
|
interval: ko.observable(500),
|
||||||
|
brightness: ko.observable(4096),
|
||||||
|
direction: ko.observable(0),
|
||||||
|
fadeOutTime: ko.observable(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.loading = ko.observable(true);
|
||||||
|
self.updatingFromServer = false;
|
||||||
|
|
||||||
|
self.autoSetTimeout = null;
|
||||||
|
self.autoSetMode = ko.computed(function()
|
||||||
|
{
|
||||||
|
if (self.loading()) return;
|
||||||
|
|
||||||
|
var url = '/setMode/' + encodeURIComponent(self.mode());
|
||||||
|
switch (self.mode())
|
||||||
|
{
|
||||||
|
case 'Static':
|
||||||
|
url += '?brightness=' + encodeURIComponent(self.static.brightness());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Custom':
|
||||||
|
url += '?brightness=' + encodeURIComponent(self.custom.brightness().map(function(value) { return value(); }).join());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Alternate':
|
||||||
|
url += '?interval=' + encodeURIComponent(self.alternate.interval()) +
|
||||||
|
'&brightness=' + encodeURIComponent(self.alternate.brightness());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Slide':
|
||||||
|
url += '?interval=' + encodeURIComponent(self.slide.interval()) +
|
||||||
|
'&brightness=' + encodeURIComponent(self.slide.brightness()) +
|
||||||
|
'&direction=' + encodeURIComponent(self.slide.direction()) +
|
||||||
|
'&fadeOutTime=' + encodeURIComponent(self.slide.fadeOutTime());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Exit after checking all the parameters, so the observers
|
||||||
|
// are properly subscribed
|
||||||
|
if (self.updatingFromServer) return;
|
||||||
|
|
||||||
|
|
||||||
|
if (self.autoSetTimeout !== null)
|
||||||
|
{
|
||||||
|
clearTimeout(self.autoSetTimeout);
|
||||||
|
self.autoSetTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.autoSetTimeout = setTimeout(function()
|
||||||
|
{
|
||||||
|
// TODO retry on failure
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
url: url,
|
||||||
|
dataType: 'json',
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(self.autoSetTimeout);
|
||||||
|
self.autoSetTimeout = null;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
self.ping = function()
|
||||||
|
{
|
||||||
|
self.loading(true);
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
url: '/ping',
|
||||||
|
dataType: 'json',
|
||||||
|
cache: false
|
||||||
|
})
|
||||||
|
.done(function(data)
|
||||||
|
{
|
||||||
|
self.updatingFromServer = true;
|
||||||
|
|
||||||
|
// Initialize the 'Custom' values based on the step count
|
||||||
|
var values = [];
|
||||||
|
for (var index = 0; index < data.stepCount; index++)
|
||||||
|
values.push(ko.observable(0));
|
||||||
|
|
||||||
|
self.custom.brightness(values);
|
||||||
|
self.loading(false);
|
||||||
|
|
||||||
|
self.updatingFromServer = false;
|
||||||
|
})
|
||||||
|
.fail(function()
|
||||||
|
{
|
||||||
|
setTimeout(self.ping, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(function()
|
||||||
|
{
|
||||||
|
var viewModel = new StairsViewModel();
|
||||||
|
ko.applyBindings(viewModel);
|
||||||
|
|
||||||
|
viewModel.ping();
|
||||||
|
});
|
78
web/static/style.css
Normal file
78
web/static/style.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
body
|
||||||
|
{
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
font-family: 'Verdana', 'Arial', sans-serif;
|
||||||
|
font-size: 12pt;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loader
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.title
|
||||||
|
{
|
||||||
|
background-color: black;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .main
|
||||||
|
{
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .sub
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote
|
||||||
|
{
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.container
|
||||||
|
{
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header
|
||||||
|
{
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mode
|
||||||
|
{
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode .selection
|
||||||
|
{
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode .selection > input
|
||||||
|
{
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.parameter
|
||||||
|
{
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user