-
Notifications
You must be signed in to change notification settings - Fork 14
/
index.js
261 lines (243 loc) · 12 KB
/
index.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
// This file is part of node-enocean.
// node-enocean. is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// node-enocean. is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with node-enocean. If not, see <http://www.gnu.org/licenses/>.
var SerialPort = require( "serialport" )
var EventEmitter = require( 'events' ).EventEmitter
var fs = require( "fs" )
var Telegram = require( "./modules/telegram.js" )
var crc = require( "./modules/crc.js" )
var Memory = require( "./modules/memory.js" )
var eepDesc = require("./modules/eepDesc.js")
var transcoder = require("eep-transcoder")
var parser = require("serialport-enocean-parser")
function SerialPortListener( config ) {
// read the config object passed to the constructor. fill the non existin fileds with defaults
this.timeout = config.timeout ? config.timeout : 60 // set the timeout for tech in and forget auto mode to 60 s if not otehrwise specified
this.configFilePath = config.configFilePath ? config.configFilePath : __dirname + "/config.json" // use the default config file. its recommended to use your own especially when hacking on this module.
this.sensorFilePath = config.sensorFilePath ? config.sensorFilePath : __dirname + "/modules/knownSensors.json" // same goes for the sensor file in which learnd sensors are stored
if(!fs.existsSync(this.configFilePath)){
fs.writeFileSync(this.configFilePath,'{"base":"00000000"}')
}
if(!fs.existsSync(this.sensorFilePath)){
fs.writeFileSync(this.sensorFilePath,'{}')
}
var configFile = require(this.configFilePath) // load the config file
this.base = configFile.base // load the base address stored in the config file. It should be initialized with "00000000"
var serialPort = null // pointer to the serialport object
var tmp = null // tmp hold parts of uncomplete telegrams
this.crc = crc // pointer to the module for calculatimg checksums. Its only used externaly and is just a helper for external modules, because we are not generating telegrams here
var state = "" //part of the getBase Hack. Sometimes the call to get base does not return a response. state is used to repeat the process until we have a base address
// the eepResolvers used to extract data from known sensors. you can push your own handlers here
this.eepResolvers = require("./modules/eep.js")
// eepDesc is an Array with Description of all eeps and eep funcs used to look up description (in plain english)
this.eepDesc = eepDesc
// an array of emmiter, all events emitted are emitted on all emitters. start with self, but you can push your own emitters here.
// for example: add a socket.io object to this array, to have all events automaticly forwarded to the browser.
this.emitters = [ this ]
// used to close the serial port. usefull for CLI inerfaces or tests
this.close = function( callback ) {
serialPort.close( function(){
this.removeAllListeners()
this.db.close(callback)
}.bind(this))
}.bind(this)
this.listen = function(port){
// open the serial port
// use /dev/ttyUSBx for USB Sticks
// use /dev/ttyAMA0 for enocean pi
// use /dev/COM1 for USB Sticks on Windows
serialPort = new SerialPort( port , { baudRate: 57600 , parser: parser, autoOpen: false } )
serialPort.on("error", function (error) {console.log(error)});
serialPort.open(function(err, result ) {
if (err) {
console.log(err);
this.emitters.forEach(function(emitter) {
emitter.emit("error", err)
});
} else {
// when the serial port successfully opened
this.mem = new Memory( this , { sensorFilePath : this.sensorFilePath, memoryDirectoryPath : config.memoryDirectoryPath } ) // initialize the Memory implementation used for learning an forgetting sensors. all meaningfull events are emitted there
if( configFile.base === "00000000" || !configFile.hasOwnProperty( "base" ) ) { // if we dont know the base address yet
this.getBase( ) // get the base address from the attached device
} else { // if we know the base address
state = "ready" // part of the getBase Hack
this.emitters.forEach( function( emitter ) {
emitter.emit( "ready" ) // emit the ready event. we are now ready to receive and send telegrams
} )
}
serialPort.on( 'data' ,function( data ) {
this.receive(data)
}.bind( this ) ) // bind "this" to the enocean object
serialPort.on("error", function (error) {
this.emitters.forEach(function(emitter) {
emitter.emit("error", error)
});
}.bind(this));
serialPort.on("disconnect", function (error) {
this.emitters.forEach(function(emitter) {
emitter.emit("disconnect", error)
});
}.bind(this));
serialPort.on("close", function () {
this.emitters.forEach(function(emitter) {
emitter.emit("close")
});
}.bind(this));
}
}.bind( this ) ) // bind "this" to the enocean objec
}
this.receive = function( buf ) {
// handle the receiving of telegrams. this can be used to simulate the recieving part... good for testing ;-)
var telegram = new Telegram( ) // create a new telegram
telegram.loadFromBuffer( buf ) // and fill it with the data from the Buffer
this.emitters.forEach( function( emitter ) {
emitter.emit( "data" , telegram ) // tell everyone we've got a telegram
} )
if( telegram.packetType == 2 ) {
// handle getting the base address. if we request request the base address from our device, it response with a telegram of type 2
// the telegram implemetation has already extracted the address.
if( telegram.hasOwnProperty( "base" ) ) { // if this Response holds our base address
this.base = telegram.base // make it globaly available
configFile.base = telegram.base // write to the config file
fs.writeFile( this.configFilePath , JSON.stringify( configFile , null , 4 ), function( err ) {
if(err) {
// error saving config file
} else {
// when the file was successfully saved
state = "ready" // part of the get Base Hack
this.emitters.forEach( function( emitter ) {
// emit the ready event. when we start listening and we dont know the base address, the ready event is not fired. so do it here.
// but remember that every call to getBase also emits "ready"
emitter.emit( "ready" )
emitter.emit( "base" , telegram.base ) // also fire the base event to everyone. also propagete the base address
} )
}
}.bind( this ) ) // bind "this" to the enocean object
}
this.emitters.forEach( function( emitter ) {
emitter.emit( "response" , telegram ) // emit all other response telegrams to everyone
} )
}
}.bind( this )
this.send = function( msg ) {
// very simple send implemetation. expects a string (hex)
try{
var buf1 = new Buffer( msg , "hex" ) // turn msg into a Buffer
if (serialPort.isOpen){
serialPort.write( buf1 ) // write it to the serial port
this.emitters.forEach( function( emitter ) {
emitter.emit( "sent" , msg ) // emit a sent event when we where able to sen something. does not mean the sending itself was successful though
} )
}
}catch(err){
this.emitters.forEach( function( emitter ) {
emitter.emit( "sent-error" , { err : err , msg : msg } ) // emit en error whe somthing went wrong
} )
}
}
this.sendAsync = function( msg ) {
// very simple send implemetation. expects a string (hex)
var p = new Promise(function(resolve,reject){
try{
if (serialPort.isOpen){
var buf1 = new Buffer( msg , "hex" ) // turn msg into a Buffer
serialPort.write( buf1 , function(err){
serialPort.drain(err=>{if(err){reject(err)}else{resolve()}})
}) // write it to the serial port
this.emitters.forEach( function( emitter ) {
emitter.emit( "sent" , msg ) // emit a sent event when we where able to sen something. does not mean the sending itself was successful though
} )
}
}catch(err){
this.emitters.forEach( function( emitter ) {
emitter.emit( "sent-error" , { err : err , msg : msg } ) // emit en error whe somthing went wrong
} )
}
}.bind(this))
return p
}
this.getBase = function(){
// code to get the base address ( 55 0001 00 05 70 08 38 )
// 55 = startbyte
// 0001 = datalength (data is only one Byte)
// 00 = optionallength t
// 05 = telegramtype (05 = Common Command)
// 70 = header checksum
// 08 = Command Code (08 = Read Base)
// 38 = CRC of Data
this.send( "5500010005700838" )
// somtimes the controler does not returen the base address.
// if the address is not know, this may cause the program to hang (ie. not fire the "ready" event)
// to fix this, see if the ready event got fired after 1 second, if not fire request the base addres again.
// this is a dirty hack i know... so what, it works ;-)
setTimeout( function( ) {
if( state !== "ready" ) this.send( "5500010005700838" )
}.bind( this ) , 1000 )
}
this.getData2 = function(eep,data){
var ret2 = transcoder.decode(data,eep)
if( ret2 !== null ) {
return ret2.decoded
}
}
this.getData = function( eep , data ) {
// used to read data from known telegrams
// can be used as a utilty externaly
// eep is an eep as a string ( f.e. a5-03-02 )
// data is the data part of a telegram as a string
var ret = null // set return to null
for( var i = 0 ; i < this.eepResolvers.length ; i++ ) { // loop through all eepResolvers
ret = this.eepResolvers[i]( eep , data ) // try to decode the data
if( ret !== null ) {
//var ret2=decode(data,eep)
//console.log(data)
return ret // if a resolver returns something other than null, we have an answer. return it and be done
// if not try next.
}
}
// we obviuosly dont have an implementation for this eep yet. return an unknown value
return [ {
type : "unknown" ,
unit : "unknown" ,
value : "unknown"
} ]
}
// a helper function
this.pad = function ( num , size ) { // fill a string with leading zeros up to size
var s = "00000000000000000000000000000000" + num // maximum number of zero we need
return s.substr( s.length - size ) // cut to size
}
this.register = function( socket ) {
// rergister for event emitters.
// we are lsitening for the following events:
socket.on( "start-learning" , this.startLearning ) // start learn mode
socket.on( "start-forgetting" , this.startForgetting ) // start forget mode
socket.on( "stop-learning" , this.stopLearning ) // stop learn mode
socket.on( "stop-forgetting" , this.stopForgetting ) // stop forget mode
socket.on( "send" , this.send ) // send data; expects a string as parameter
socket.on( "learn" , this.learn ) // manualy learn/save/update a sensor description. expects a sensor description
socket.on( "forget" ,this.forget ) // deletes a sensor
socket.on( "get-all-sensors" , function( ) {
socket.emit("all-sensors", this.getSensors( ) ) // returns a list of all known sensors to this one socket
}.bind( this ) )
socket.on( "get-sensor-info" , function( id ) {
socket.emit("sensors-info", this.info( id ) ) // returns info for one single sensor
}.bind( this) )
socket.on( "get-last-sensor-value" , async function( id ) {
socket.emit("last-sensors-value", await this.getLastValues( id ) ) // returns info for one single sensor
}.bind( this) )
}
}
SerialPortListener.prototype.__proto__ = EventEmitter.prototype // inherit from EventEmitter
module.exports = function (config) {
if (config == undefined) config = {} //if called with no config, pass an empty one
return new SerialPortListener(config) // return a constructor
}