IoT example
This commit is contained in:
parent
4372df1b40
commit
21a53ba948
|
@ -76,3 +76,34 @@ directly, and one using the high-level Syndicate DSL.
|
|||
- Low-level implementation
|
||||
- [DEMO](textfield/)
|
||||
- [Source code](textfield/index.js) in plain JavaScript
|
||||
|
||||
## IoT Demo
|
||||
|
||||
This is a model of a home automation system.
|
||||
|
||||
The idea is to alert a homeowner to the possibility they have left the
|
||||
stove switched on beyond the time they intended to.
|
||||
|
||||
Components in the model include:
|
||||
|
||||
- a switch for the stove;
|
||||
- an electric power meter, which monitors the power drawn by the
|
||||
stove;
|
||||
- a TV, which displays alerts to the user; and
|
||||
- a remote control for the system, which can be used to switch off
|
||||
the stove remotely.
|
||||
|
||||
When the stove is switched on, a timer is started, and if a certain
|
||||
time goes by without the stove being switched off, an alert is shown
|
||||
on the TV.
|
||||
|
||||
The example was inspired by a talk given in May 2016 at the
|
||||
[PL Seminar at Northeastern University's College of Computer Science](http://prl.ccs.neu.edu/seminars.html)
|
||||
by
|
||||
[Charles Consel](http://phoenix.inria.fr/index.php/members/54-charles-consel)
|
||||
about the
|
||||
[DiaSuite](http://phoenix.inria.fr/research-projects/diasuite) system
|
||||
that he and his collaborators have been developing.
|
||||
|
||||
- [DEMO](iot/)
|
||||
- [Source code](iot/index.js) using the Syndicate/js DSL
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 207 KiB |
|
@ -0,0 +1,63 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Syndicate: IoT Demo</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
||||
<script src="../../dist/syndicatecompiler.js"></script>
|
||||
<script src="../../dist/syndicate.js"></script>
|
||||
<script type="text/syndicate-js" src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>IoT Demo</h1>
|
||||
|
||||
<h2>Devices</h2>
|
||||
|
||||
<section>
|
||||
<section>
|
||||
<h3>TV</h3>
|
||||
<div id="tv-container">
|
||||
<div id="tv"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Stove switch</h3>
|
||||
<div id="stove-switch"></div>
|
||||
<button id="stove-switch-on">Turn on switch</button>
|
||||
<button id="stove-switch-off">Turn off switch</button>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Remote control</h3>
|
||||
<button id="remote-control"><img src="img/remote.png"></button>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Power draw meter</h3>
|
||||
<div id="power-draw-meter"></div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<h2>Fault injection</h2>
|
||||
|
||||
<section>
|
||||
<section>
|
||||
<h3>Chaos Monkey</h3>
|
||||
<div>
|
||||
<button id="kill-power-draw-monitor">Kill power draw monitor</button>
|
||||
<button id="kill-stove-switch">Kill stove-switch</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="spawn-power-draw-monitor">Spawn power draw monitor</button>
|
||||
<button id="spawn-stove-switch">Spawn stove-switch</button>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
<pre id="ds-state"></pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,224 @@
|
|||
assertion type switchState(on);
|
||||
assertion type powerDraw(watts);
|
||||
assertion type time(now);
|
||||
assertion type remoteClick();
|
||||
assertion type tvAlert(text);
|
||||
assertion type switchAction(on);
|
||||
assertion type componentPresent(name);
|
||||
|
||||
assertion type DOM(containerSelector, fragmentClass, spec);
|
||||
assertion type jQuery(selector, eventType, event);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// TV
|
||||
|
||||
function spawnTV() {
|
||||
actor {
|
||||
this.alerts = [];
|
||||
this.alertFragment = ["ul"];
|
||||
|
||||
this.computeDisplay = function () {
|
||||
var self = this; // omg javascript
|
||||
this.alertFragment = ["ul"];
|
||||
this.alerts.forEach(function (t) {
|
||||
self.alertFragment.push(["li", t]);
|
||||
});
|
||||
};
|
||||
|
||||
forever {
|
||||
assert DOM('#tv', 'alerts', Syndicate.seal(this.alertFragment));
|
||||
|
||||
on asserted tvAlert($text) {
|
||||
this.alerts.push(text);
|
||||
this.computeDisplay();
|
||||
}
|
||||
|
||||
on retracted tvAlert($text) {
|
||||
this.alerts = this.alerts.filter(function (t) { return t !== text; });
|
||||
this.computeDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Remote control and listener
|
||||
|
||||
function spawnRemoteControl() {
|
||||
actor {
|
||||
forever {
|
||||
assert componentPresent('remote control');
|
||||
on message jQuery('#remote-control', 'click', _) {
|
||||
:: remoteClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function spawnRemoteListener() {
|
||||
actor {
|
||||
this.stoveIsOn = false;
|
||||
// In principle, we should start up in "power undefined" state and
|
||||
// count clicks we get in that state; when we then learn the real
|
||||
// state, if we've been clicked, turn it off. We don't do this
|
||||
// here, for simplicity.
|
||||
|
||||
forever {
|
||||
on asserted powerDraw($watts) {
|
||||
this.stoveIsOn = watts > 0;
|
||||
}
|
||||
|
||||
on message remoteClick() {
|
||||
if (this.stoveIsOn) {
|
||||
:: switchAction(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Stove switch and power draw monitor
|
||||
|
||||
function spawnStoveSwitch() {
|
||||
actor {
|
||||
this.powerOn = false;
|
||||
state {
|
||||
assert componentPresent('stove switch');
|
||||
assert switchState(this.powerOn);
|
||||
|
||||
assert DOM('#stove-switch', 'switch-state',
|
||||
Syndicate.seal(["img", [["src",
|
||||
"img/stove-coil-element-" +
|
||||
(this.powerOn ? "hot" : "cold") + ".jpg"]]]));
|
||||
|
||||
on message jQuery('#stove-switch-on', 'click', _) { this.powerOn = true; }
|
||||
on message jQuery('#stove-switch-off', 'click', _) { this.powerOn = false; }
|
||||
|
||||
on message switchAction($newState) {
|
||||
this.powerOn = newState;
|
||||
}
|
||||
} until {
|
||||
case message jQuery('#kill-stove-switch', 'click', _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function spawnPowerDrawMonitor() {
|
||||
actor {
|
||||
this.watts = 0;
|
||||
state {
|
||||
assert componentPresent('power draw monitor');
|
||||
assert powerDraw(this.watts);
|
||||
|
||||
assert DOM('#power-draw-meter', 'power-draw',
|
||||
Syndicate.seal(["p", "Power draw: ",
|
||||
["span", [["class", "power-meter-display"]],
|
||||
this.watts + " W"]]));
|
||||
|
||||
on asserted switchState($on) {
|
||||
this.watts = on ? 1500 : 0;
|
||||
}
|
||||
} until {
|
||||
case message jQuery('#kill-power-draw-monitor', 'click', _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Clock and "timeout listener"
|
||||
|
||||
function spawnClock() {
|
||||
actor {
|
||||
setInterval(Syndicate.Dataspace.wrap(function () {
|
||||
:: time(+(new Date()));
|
||||
}), 200);
|
||||
forever {
|
||||
assert componentPresent('real time clock');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function spawnTimeoutListener() {
|
||||
var message = tvAlert('Stove on too long?');
|
||||
actor {
|
||||
this.mostRecentTime = 0;
|
||||
this.powerOnTime = null;
|
||||
|
||||
forever {
|
||||
on asserted powerDraw($watts) {
|
||||
this.powerOnTime = (watts > 0) ? this.mostRecentTime : null;
|
||||
}
|
||||
on message time($now) {
|
||||
this.mostRecentTime = now;
|
||||
if (this.powerOnTime !== null && this.mostRecentTime - this.powerOnTime > 3000) {
|
||||
Syndicate.Dataspace.stateChange(Syndicate.assert(message));
|
||||
} else {
|
||||
Syndicate.Dataspace.stateChange(Syndicate.retract(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Failure monitor
|
||||
|
||||
function spawnFailureMonitor() {
|
||||
function messageFor(who) {
|
||||
return tvAlert('FAILURE: ' + who);
|
||||
}
|
||||
|
||||
actor {
|
||||
forever {
|
||||
on asserted componentPresent($who) {
|
||||
Syndicate.Dataspace.stateChange(Syndicate.retract(messageFor(who)));
|
||||
}
|
||||
on retracted componentPresent($who) {
|
||||
Syndicate.Dataspace.stateChange(Syndicate.assert(messageFor(who)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Chaos Monkey
|
||||
|
||||
function spawnChaosMonkey() {
|
||||
actor {
|
||||
forever {
|
||||
on message jQuery('#spawn-power-draw-monitor', 'click', _) {
|
||||
spawnPowerDrawMonitor();
|
||||
}
|
||||
on message jQuery('#spawn-stove-switch', 'click', _) {
|
||||
spawnStoveSwitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Main
|
||||
|
||||
$(document).ready(function () {
|
||||
ground dataspace G {
|
||||
Syndicate.JQuery.spawnJQueryDriver();
|
||||
Syndicate.DOM.spawnDOMDriver();
|
||||
|
||||
spawnTV();
|
||||
spawnRemoteControl();
|
||||
spawnRemoteListener();
|
||||
spawnStoveSwitch();
|
||||
spawnPowerDrawMonitor();
|
||||
spawnClock();
|
||||
spawnTimeoutListener();
|
||||
|
||||
spawnFailureMonitor();
|
||||
|
||||
spawnChaosMonkey();
|
||||
}
|
||||
|
||||
G.dataspace.onStateChange = function (mux, patch) {
|
||||
$("#ds-state").text(Syndicate.prettyTrie(mux.routingTable));
|
||||
};
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
#tv-container {
|
||||
background: url('img/tvscreen.gif');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
#tv-container .alerts {
|
||||
color: white;
|
||||
margin: 3em;
|
||||
max-width: 11em;
|
||||
}
|
||||
|
||||
.power-meter-display {
|
||||
font-size: 24pt;
|
||||
background-color: black;
|
||||
color: red;
|
||||
padding: 0.2em;
|
||||
margin: 0.2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background: lightgrey;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: solid grey 1px;
|
||||
}
|
||||
|
||||
body > section {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
body > section > section {
|
||||
margin: 1em;
|
||||
}
|
Loading…
Reference in New Issue