Update
This commit is contained in:
parent
2e82e7e4d8
commit
f25b1a643f
File diff suppressed because one or more lines are too long
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
|
@ -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