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
|
||||
src/credentials.h
|
||||
*.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>
|
||||
**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>
|
||||
**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:
|
||||
udpServer.write(Command::Ping);
|
||||
udpServer.write(StepCount);
|
||||
break;
|
||||
|
||||
case Command::GetMode:
|
||||
udpServer.write(Command::GetMode);
|
||||
udpServer.write(currentModeIdentifier);
|
||||
currentMode->write(&udpServer);
|
||||
break;
|
||||
|
||||
case Command::SetMode:
|
||||
@ -163,6 +165,7 @@ void handleRequest(uint8_t* packet)
|
||||
{
|
||||
udpServer.write(Command::Error);
|
||||
udpServer.write(Command::SetMode);
|
||||
udpServer.write(newIdentifier);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -186,7 +189,7 @@ IMode* createMode(uint8_t identifier)
|
||||
switch (identifier)
|
||||
{
|
||||
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::Slide: return new SlideMode();
|
||||
//case Mode::ADC: return new ADCInputMode();
|
||||
|
87
web/app.js
87
web/app.js
@ -1,50 +1,55 @@
|
||||
var protocol = require('./protocol');
|
||||
var dgram = require('dgram');
|
||||
var express = require('express');
|
||||
var client = require('./client');
|
||||
|
||||
var on = 0;
|
||||
var speed = 256;
|
||||
var httpPort = 3127;
|
||||
|
||||
var stairsHost = '10.138.2.12';
|
||||
var stairsUdpPort = 3126;
|
||||
|
||||
|
||||
function lsb(value) { return value & 0xFF; }
|
||||
function msb(value) { return (value >> 8) & 0xFF; }
|
||||
client.init(stairsHost, stairsUdpPort);
|
||||
|
||||
|
||||
var app = express();
|
||||
|
||||
|
||||
/*
|
||||
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()
|
||||
app.get('/ping', function(req, res)
|
||||
{
|
||||
var address = client.address();
|
||||
console.log('UDP client listening on ' + address.address + ":" + address.port);
|
||||
});
|
||||
client.ping(function(data, error)
|
||||
{
|
||||
if (error)
|
||||
res.status(500);
|
||||
|
||||
client.on('message', function (message, remote)
|
||||
{
|
||||
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'));
|
||||
res.send(data);
|
||||
});
|
||||
}, 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",
|
||||
"main": "app.js",
|
||||
"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