-
Notifications
You must be signed in to change notification settings - Fork 5
/
hardware-osbo.js
284 lines (260 loc) · 8.39 KB
/
hardware-osbo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// Copyrigth (C) Pascal Martin, 2014.
//
// NAME
//
// hardware - a module to hide the interface to the OpenSprinkler OSBo.
//
// SYNOPSYS
//
// This module implements an interface to the board that controls
// the sprinkler (typically triacs or relays that control the solenoids).
//
// Each sprinkler triac or relay is called a "zone" (because it generally
// controls a watering valve, which waters a zone).
//
// This module allows porting the sprinkler software to different
// hardware interfaces. Only one hardware interface is supported at
// a given time: you must have installed the right driver.
//
// This specific implementation supports the OpenSprinkler OSBo board.
//
// This module does not support the real-time clock or the relay (yet).
//
// To enable this driver, create 'hardware.js' as a symbolic link to
// 'hardware-osbo.js'.
//
// DESCRIPTION
//
// var hardware = require('./hardware');
//
// hardware.configure (hardwareConfig, userConfig, options);
//
// Initialize the hardware module from the configuration.
// This method can be called as often as necessary (typically
// when the user configuration has changed).
//
// hardware.info ();
//
// Return a data structure that describes the hardware managed by
// this driver. The data structure contains the following elements:
// id A short unique identification string for the driver.
// title A human-readable string that describes the hardware.
// zones.add If true, the end-user may add (or remove) zones.
// zones.pin If true, the end-user may set the pin name and active
// state ('on' state).
// zones.max If set, defines the maximum number of zones supported
// by the hardware. If zones.max is defined and zones.add
// is set to false, then the number of zones is fixed.
//
// hardware.userDefined (attribute);
//
// Return true when the user may change the given attribute.
// The supported attributes are:
// "zones" The number of zones.
// "zones.pin" The I/O pin, and the active pin level, for each zone.
//
// hardware.get (attribute);
//
// Return the current value of the given attribute.
// The supported attributes are:
// "zones" The maximum number of zones. (Used only if not user
// defined).
//
// hardware.setZone (zone, on);
//
// Set one zone on (on == true) or off (on == false).
//
// CAUTION! This takes effect only the next time that the
// function hardware.apply() is called.
//
// Each zone is identified by a number (identifying zones by name
// is the responsibility of the application layer).
//
// hardware.apply ();
//
// Push the current zone controls to the outside world.
//
// hardware.rainSensor ();
//
// Return true or false, true if rain is detected. Always return
// false if there is no rain sensor.
//
// hardware.button ();
//
// Return true or false, true if button is pressed. Always return
// false if there is no button.
//
// hardware.rainInterrupt (callback);
// hardware.buttonInterrupt (callback);
//
// Set each callback to be called when the corresponding input
// has changed. The parameter to the callback is a Javascript
// structure guaranteed to contain an (oddly named) "output"
// item that contains the value of the input pin.
//
// HARDWARE CONFIGURATION
//
// osbo.pins.data The I/O pin to use for the data signal.
//
// osbo.pins.clock The I/O pin to use for the clock signal.
//
// osbo.pins.enable The I/O pin to use for the output enable signal.
//
// osbo.pins.latch The I/O pin to use for the data latch signal.
//
// osbo.pins.rain The I/O pin to use for the rain sensor.
//
// osbo.pins.edge The active edge of the rain sensor (RISING, FALLING).
//
// USER CONFIGURATION
//
// production This flag determines if we use the real hardware
// (true) or else a simulation for debug (false).
//
// zones The name of each zone (an array of structures
// containing item 'name'). This module only considers
// the number of zones that have been configured by
// the user, in order to decide how many extensions
// are present.
//
var debugLog = function (text) {}
function verboseLog (text) {
console.log ('[DEBUG] Hardware(osbo): '+text);
}
function errorLog (text) {
console.error ('[ERROR] Hardware(osbo): '+text);
}
try {
var io = require('bonescript');
}
catch (err) {
errorLog ('cannot access module bonescript');
var io = null;
}
var piodb = new Object(); // Make sure it exists (simplify validation).
exports.configure = function (config, user, options) {
if (options && options.debug) {
debugLog = verboseLog;
}
if ((! io) || (! user.production)) {
debugLog ('using debug I/O module');
io = require('./iodebug');
}
piodb = new Object();
piodb.pins = new Object();
piodb.pins.data = "P9_11";
piodb.pins.clock = "P9_13";
piodb.pins.enable = "P9_14";
piodb.pins.latch = "P9_15";
piodb.pins.rain = "P9_15";
piodb.pins.edge = io.FALLING;
if (config.osbo) {
// We need to go item by item so to not overwrite defaults.
if (config.osbo.data) {
piodb.pins.data = config.osbo.data;
}
if (config.osbo.clock) {
piodb.pins.clock = config.osbo.clock;
}
if (config.osbo.enable) {
piodb.pins.enable = config.osbo.enable;
}
if (config.osbo.latch) {
piodb.pins.latch = config.osbo.latch;
}
if (config.osbo.rain) {
piodb.pins.rain = config.osbo.rain;
}
if (config.osbo.edge) {
piodb.pins.edge = config.osbo.edge;
}
}
io.pinMode(piodb.pins.rain, io.INPUT);
io.pinMode(piodb.pins.clock, io.OUTPUT);
io.pinMode(piodb.pins.enable, io.OUTPUT);
io.digitalWrite(piodb.pins.enable, io.HIGH);
io.pinMode(piodb.pins.data, io.OUTPUT);
io.pinMode(piodb.pins.latch, io.OUTPUT);
var zonecount = user.zones.length;
piodb.zones = new Array();
for(var i = 0; i < zonecount; i++) {
piodb.zones[i] = new Object();
piodb.zones[i].value = io.LOW;
}
piodb.changed = true; // We do not know the status, assume the worst.
}
exports.info = function (attribute) {
return {id:"osbo",title:"Open Sprinkler OSBo Board",zones:{add:true,pin:false}};
}
exports.rainSensor = function () {
if (! piodb.pins) {
return false;
}
if (! piodb.pins.rain) {
return false;
}
if (io.digitalRead(piodb.pins.rain) > 0) {
return false;
}
return true;
}
exports.button = function () {
// No button on the OpenSprinkler OSBo board.
return false;
}
exports.rainInterrupt = function (callback) {
if (! piodb.pins) {
return false;
}
if (! piodb.pins.rain) {
return null;
}
io.attachInterrupt(piodb.pins.rain, true, piodb.pins.edge, callback);
}
exports.buttonInterrupt = function (callback) {
// No button on the OpenSprinkler OSBo board.
return null;
}
exports.setZone = function (zone, on) {
if (! piodb.zones) {
return null;
}
if (on) {
value = io.HIGH;
} else {
value = io.LOW;
}
if (piodb.zones[zone].value != value) {
debugLog ('Zone '+zone+' set to '+value);
piodb.zones[zone].value = value;
piodb.changed = true;
}
}
exports.apply = function () {
if (! piodb.pins) {
return null;
}
if (! piodb.changed) {
return null;
}
io.digitalWrite(piodb.pins.enable, io.HIGH);
io.digitalWrite(piodb.pins.clock, io.LOW);
io.digitalWrite(piodb.pins.latch, io.LOW);
var zonecount = piodb.zones.length;
var filler = (8 - (zonecount % 8)) % 8; // All bits missing in last byte.
if (filler > 0) {
io.digitalWrite(piodb.pins.data, io.LOW);
for(var i = filler; i > 0; i--) {
io.digitalWrite(piodb.pins.clock, io.LOW);
io.digitalWrite(piodb.pins.clock, io.HIGH);
}
}
for(var i = zonecount - 1; i >= 0; i--) {
io.digitalWrite(piodb.pins.clock, io.LOW);
io.digitalWrite(piodb.pins.data, piodb.zones[i].value);
io.digitalWrite(piodb.pins.clock, io.HIGH);
}
io.digitalWrite(piodb.pins.latch, io.HIGH);
io.digitalWrite(piodb.pins.enable, io.LOW);
piodb.changed = false;
}