This commit is contained in:
Tony Garnock-Jones 2016-05-02 17:15:05 -04:00
parent 2e82e7e4d8
commit f25b1a643f
12 changed files with 2655 additions and 1731 deletions

2433
dist/syndicate.js vendored

File diff suppressed because one or more lines are too long

32
dist/syndicate.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -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

BIN
examples/iot/img/remote.png Normal file

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

63
examples/iot/index.html Normal file
View File

@ -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">
&nbsp;<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>

224
examples/iot/index.js Normal file
View File

@ -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));
};
});

38
examples/iot/style.css Normal file
View File

@ -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;
}