├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── package-lock.json ├── package.json └── utils ├── stream.js └── stream_mam.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 r.k 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SensorNode 2 | SensorNode-client application for [IOTA](http://iota.org). 3 | 4 | Either data is being pushed to [Tangle](https://thetangle.org/) or send as [Masked Authenticated Message](https://blog.iota.org/introducing-masked-authenticated-messaging-e55c1822d50e), or both. 5 | 6 | 7 | 8 | (*Image by [Wyn Tiedmers](https://www.wynt.de/)*) 9 | 10 | ### Installation: 11 | 12 | Clone this repository: 13 | ``` 14 | git clone https://github.com/rckey/SensorNode 15 | ``` 16 | Install node_modules: 17 | ``` 18 | cd SensorNode/ 19 | npm install 20 | ``` 21 | Install mam.node.js: 22 | ``` 23 | cd node_modules/ 24 | git clone https://github.com/rckey/mam.node.js 25 | ``` 26 | 27 | ### Creating streams: 28 | 29 | You can create streamobjects with the following parameters: 30 | 31 | ###### Parameters: 32 | Parameter | Function | Default 33 | ------------ | ------------- | ------------- 34 | host | (Remote-) node we're connecting to. | 0.0.0.0 35 | port | Iota-api port on the node. | 14265 36 | id | Identifies the streamobject. | "SensorNode" 37 | location | Nodes location, eg. 'lat': 52.26 'lng': 13.42. | {'lat': 40.65, 'lng': -73.91} 38 | seed | Seed for creating transactions/MAM-messages. | [generated] 39 | rec | Receiving address (tanglestream only). | "GPB9PBNCJTPGF..." 40 | tag | Tag for Transactions (tanglestream only). | "SENSORNODEROCKS" 41 | depth | Depth for tip-selection (tanglestream only). | 3 42 | wait | Discards packets till the current packet has been send. | true 43 | fetch | Enable continuous fetching from MAM-root when multiple nodes stream from the same seed (mamstream only).| false 44 | 45 | #### First Stream (TANGLE): 46 | ``` 47 | streams.push(new STREAM ({ 48 | 'host': 'http://[remote node / localhost]', 49 | 'port': [port] 50 | 51 | [OPTIONAL PARAMETERS] 52 | 53 | })) 54 | ``` 55 | 56 | #### Second Stream (MAM): 57 | ``` 58 | streams.push(new MAM_STREAM ({ 59 | 'host': 'http://[remote node / localhost]', 60 | 'port': [port] 61 | 62 | [OPTIONAL PARAMETERS] 63 | 64 | })) 65 | 66 | ``` 67 | 68 | #### Add data sources 69 | 70 | Data sources are (async) functions which return the data you want to stream, 71 | eg: 72 | 73 | ``` 74 | async function functionFoo () { 75 | return await sensorA.read(); 76 | } 77 | async function functionBar () { 78 | return await sensorB.read(); 79 | } 80 | ``` 81 | ``` 82 | streams[0].addSource(functionFoo); 83 | streams[1].addSource(functionBar); 84 | ``` 85 | 86 | ## Cool! Whats next? 87 | 88 | Run with ``` npm start [delay] ``` where delay specifies a timeout between each push (default 60 seconds). 89 | 90 | Thats it. Have fun providing data over the iota protocol. ;) 91 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const STREAM = require('./utils/stream'); 2 | const MAM_STREAM = require('./utils/stream_mam'); 3 | 4 | const timeout = (process.argv[2] >= 0 ? process.argv[2] : 60); 5 | /* depends on node performance ^^ */ 6 | var streams = []; 7 | 8 | //############################################# 9 | //## SETUP SENSORS ## 10 | //############################################# 11 | 12 | /* example function */ 13 | 14 | async function readSensor () { 15 | 16 | let h = (Math.random() * (50.0 - 30.0) + 30.0).toFixed(2) + ' %RH'; 17 | let t = (Math.random() * (30.0 - 20.0) + 20.0).toFixed(2) + ' °C'; 18 | let p = (Math.random() * (1000.0 - 900.0) + 900.0).toFixed(2) + ' hPa'; 19 | let g = (Math.random() * (35000.0 - 25000.0) + 25000.0).toFixed(2) + ' Ohms'; 20 | 21 | let json = { 22 | 'humidity': h, 23 | 'temperature': t, 24 | 'pressure': p, 25 | 'gasResistance': g 26 | } 27 | 28 | return await json; 29 | } 30 | 31 | /* coordinate generators (for testing) */ 32 | 33 | function lat () { 34 | return Math.round((Math.random() * 0.05 + 40.65) * 1000) / 1000; 35 | } 36 | 37 | function lng () { 38 | return Math.round((Math.random() * 0.05 + -73.91) * 1000) / 1000; 39 | } 40 | 41 | //############################################# 42 | //## SETUP STREAMS ## 43 | //############################################# 44 | 45 | /* MAMSTREAM */ 46 | streams.push(new MAM_STREAM ({ 47 | 'host': 'http://0.0.0.0', 48 | 'port': 14265, 49 | 'id': 'SensorNode', 50 | 'location': {'lat': lat(), 'lng': lng()} 51 | })) 52 | 53 | /* TANGLESTREAM */ 54 | /* 55 | streams.push(new STREAM ({ 56 | 'host': 'http://0.0.0.0', 57 | 'port': 14265, 58 | 'id': 'SensorNode', 59 | 'location': {'lat': lat(), 'lng': lng()}, 60 | 'tag': 'SENSORNODEROCKS', 61 | 'depth': 3 62 | })) 63 | */ 64 | 65 | streams[0].addSource(readSensor); 66 | //streams[1].addSource(readSensor); 67 | 68 | //############################################# 69 | //## EXECUTION HEAD ## 70 | //############################################# 71 | 72 | console.log('\n╔════════════════════════════╗'); 73 | console.log('║ SensorNode v1.2 ║'); 74 | console.log('╚════════════════════════════╝'); 75 | console.log(); 76 | console.log('Timeout: ' + timeout + ' sec'); 77 | console.log('Streams: ' + streams.length); 78 | 79 | function run () { 80 | 81 | streams.forEach(stream => { 82 | stream.handle(); 83 | }) 84 | 85 | setTimeout(run, timeout*1000); 86 | } 87 | 88 | /* start */ 89 | run(); 90 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SensorNode", 3 | "version": "1.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "2.6.0", 9 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", 10 | "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", 11 | "requires": { 12 | "lodash": "4.17.5" 13 | } 14 | }, 15 | "bignumber.js": { 16 | "version": "4.1.0", 17 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", 18 | "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" 19 | }, 20 | "crypto-js": { 21 | "version": "3.1.9-1", 22 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", 23 | "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" 24 | }, 25 | "iota.lib.js": { 26 | "version": "0.4.7", 27 | "resolved": "https://registry.npmjs.org/iota.lib.js/-/iota.lib.js-0.4.7.tgz", 28 | "integrity": "sha1-wq8aeIa4sOI5pLVLXKqd0jnC1+s=", 29 | "requires": { 30 | "async": "2.6.0", 31 | "bignumber.js": "4.1.0", 32 | "crypto-js": "3.1.9-1", 33 | "xmlhttprequest": "1.8.0" 34 | } 35 | }, 36 | "lodash": { 37 | "version": "4.17.5", 38 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", 39 | "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" 40 | }, 41 | "xmlhttprequest": { 42 | "version": "1.8.0", 43 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 44 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SensorNode", 3 | "version": "1.1.0", 4 | "description": "SensorNode-client application for IOTA.", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "r.k.", 11 | "license": "MIT", 12 | "dependencies": { 13 | "iota.lib.js": "^0.4.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /utils/stream.js: -------------------------------------------------------------------------------- 1 | //############################################# 2 | //## SETUP ## 3 | //############################################# 4 | 5 | let IOTA = require('iota.lib.js'); 6 | 7 | //############################################# 8 | //## TANGLESTREAM CONSTRUCTOR ## 9 | //############################################# 10 | 11 | class STREAM { 12 | 13 | constructor (_stream) { 14 | 15 | this.iota = new IOTA({ 16 | 'host': _stream.host || '0.0.0.0', 17 | 'port': _stream.port || 14265 18 | }); 19 | 20 | this.id = _stream.id || 'SensorNode'; 21 | this.location = _stream.location || {'lat': 40.65, 'lng': -73.91}; 22 | this.source = null; 23 | 24 | this.seed = _stream.seed || this.generateSeed(); 25 | this.rec_address = _stream.rec || 'GPB9PBNCJTPGFZ9CCAOPCZBFMBSMMFMARZAKBMJFMTSECEBRWMGLPTYZRAFKUFOGJQVWVUPPABLTTLCIA'; /*nowhere*/ 26 | this.tag = _stream.tag || 'SENSORNODEROCKS'; 27 | 28 | this.wait = (_stream.wait == false ? false : true); /* discards packets till the current packet has been send */ 29 | this.busy = false; 30 | 31 | this.depth = _stream.depth || 3; 32 | } 33 | 34 | //############################################# 35 | //## ADD DATA SOURCE ## 36 | //############################################# 37 | 38 | addSource (_s) { 39 | this.source = _s; 40 | } 41 | 42 | //############################################# 43 | //## HANDLE SOURCE ## 44 | //############################################# 45 | 46 | handle () { 47 | 48 | /* abort sending while first fetch or lock until message is send */ 49 | if (this.wait && this.busy) 50 | return null; 51 | 52 | /* if (this.wait) */ 53 | this.busy = true; 54 | 55 | this.source().then(data => { 56 | this.attachToTangle(data); 57 | }).catch(error => {console.log(error);}) 58 | 59 | } 60 | 61 | //############################################# 62 | //## ATTACH TO TANGLE ## 63 | //############################################# 64 | 65 | attachToTangle (_data) { 66 | 67 | const scope = this; 68 | const time = Date.now(); 69 | const ts = '\x1b[94m' + time + '\x1b[0m '; 70 | 71 | let json = { 72 | 'id': this.id, 73 | 'location': this.location, 74 | 'timestamp': time, 75 | 'data': _data, 76 | } 77 | 78 | console.log('\nJSON (\x1b[94mTangle\x1b[0m):') 79 | console.log(json); 80 | 81 | let trytes = this.iota.utils.toTrytes(JSON.stringify(json)); 82 | //console.log("\nTRYTES:\n" + trytes); 83 | 84 | console.log('\n\x1b[93m[attaching ' + time + ']\x1b[0m\n'); 85 | 86 | var transfersArray = [{ 87 | 'address': this.rec_address, 88 | 'value': 0, 89 | 'message': trytes, 90 | 'tag': this.tag 91 | }] 92 | 93 | /* PREPARE TRANSFERS */ 94 | this.iota.api.prepareTransfers(this.seed, transfersArray, function(err, bundle) { 95 | 96 | if (err) { 97 | console.log(ts + '\x1b[41mERROR\x1b[0m (' + err + ')'); 98 | return -1; 99 | } else { 100 | 101 | /* PUSH TO TANGLE */ 102 | scope.iota.api.sendTrytes(bundle, scope.depth, 14, function(err, result) { 103 | 104 | if (err) { 105 | console.log(ts + '\x1b[41mERROR\x1b[0m (' + err + ')'); 106 | return -2; 107 | } else { 108 | console.log(ts + '\x1b[32mATTACHED (hash: ' + result[0].hash + ')\x1b[0m'); 109 | /* if (scope.wait) */ 110 | scope.busy = false; 111 | } 112 | }) 113 | 114 | } 115 | 116 | }) 117 | 118 | } 119 | 120 | //############################################# 121 | //## HELPER ## 122 | //############################################# 123 | 124 | generateSeed () { 125 | 126 | var seed = ""; 127 | var trytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ9"; 128 | 129 | for (var i = 0; i < 81; i++) 130 | seed += trytes.charAt(Math.floor(Math.random() * trytes.length)); 131 | 132 | return seed; 133 | } 134 | 135 | } 136 | 137 | //############################################# 138 | //## EXPORTS ## 139 | //############################################# 140 | 141 | module.exports = STREAM; 142 | -------------------------------------------------------------------------------- /utils/stream_mam.js: -------------------------------------------------------------------------------- 1 | //############################################# 2 | //## SETUP ## 3 | //############################################# 4 | 5 | let IOTA = require('iota.lib.js'); 6 | let MAM = require('mam.node.js'); 7 | 8 | //############################################# 9 | //## MAMSTREAM CONSTRUCTOR ## 10 | //############################################# 11 | 12 | class MAM_STREAM { 13 | 14 | constructor (_stream) { 15 | 16 | this.iota = new IOTA({ 17 | 'host': _stream.host || '0.0.0.0', 18 | 'port': _stream.port || 14265 19 | }); 20 | 21 | this.id = _stream.id || 'SensorNode'; 22 | this.location = _stream.location || {'lat': 40.65, 'lng': -73.91}; 23 | this.source = null; 24 | 25 | this.seed = _stream.seed || this.generateSeed(); 26 | this.tree = null; 27 | 28 | this.wait = (_stream.wait == false ? false : true); /* discards packets till the current packet has been send */ 29 | this.fetch = (_stream.fetch == true ? true : false); /* enables permanent fetching*/ 30 | this.busy = false; 31 | this.sync = false; 32 | 33 | // Initiate the mam state with the given seed at index 0. 34 | this.mamState = MAM.init(this.iota, this.seed, 2, 0); 35 | /* mamState = MAM.changeMode(mamState, 'restricted', password) */ 36 | } 37 | 38 | //############################################# 39 | //## ADD DATA SOURCE ## 40 | //############################################# 41 | 42 | addSource (_s) { 43 | this.source = _s; 44 | } 45 | 46 | //############################################# 47 | //## HANDLE SOURCE ## 48 | //############################################# 49 | 50 | handle () { 51 | 52 | /* abort sending while first fetch or lock until message is send */ 53 | if (this.sync || (this.wait && this.busy)) 54 | return null; 55 | 56 | /* if (this.wait) */ 57 | this.busy = true; 58 | 59 | this.source().then(data => { 60 | this.send(data); 61 | }).catch(error => {console.log(error);}) 62 | 63 | } 64 | 65 | //############################################# 66 | //## INITIATE MAM ## 67 | //############################################# 68 | 69 | send (_data) { 70 | 71 | const scope = this; 72 | const time = Date.now(); 73 | const ts = '\x1b[95m' + time + '\x1b[0m '; 74 | 75 | let json = { 76 | 'id': this.id, 77 | 'location': this.location, 78 | 'timestamp': time, 79 | 'data': _data, 80 | } 81 | 82 | // Fetch all the messages in the stream. 83 | this.fetchCount(json, scope).then(v => { 84 | 85 | /* finished fetching up */ 86 | this.sync = false; 87 | 88 | this.mamState = MAM.init(this.iota, this.seed, 2, v.messages.length); 89 | /* mamState = MAM.changeMode(mamState, 'restricted', password) */ 90 | 91 | this.publish(json, scope).then(result => { 92 | 93 | console.log(ts + '\x1b[32mSENT (hash: ' + result[0].hash + ')\x1b[0m'); 94 | 95 | /* if (scope.wait) */ 96 | scope.busy = false; 97 | 98 | }).catch(err => { console.error(ts + '\x1b[41mERROR\x1b[0m (' + err + ')'); }) 99 | 100 | }).catch(err => { console.error(ts + '\x1b[41mERROR\x1b[0m (' + err + ')'); }); 101 | 102 | } 103 | 104 | //############################################# 105 | //## MAM ## 106 | //############################################# 107 | 108 | async fetchCount (_json, _scope) { 109 | 110 | let trytes = _scope.iota.utils.toTrytes('START'); 111 | let message = MAM.create(_scope.mamState, trytes); 112 | 113 | if (_scope.tree == null) { 114 | 115 | console.log('\n\x1b[45mThe first root:\x1b[0m'); 116 | console.log(message.root); 117 | _scope.sync = true; 118 | 119 | } else { ++_scope.tree.messages.length; } 120 | 121 | console.log('\nJSON (\x1b[95mMaM\x1b[0m):'); 122 | console.log(_json); 123 | console.log(); 124 | 125 | if (_scope.fetch || _scope.tree == null) { 126 | // Fetch all the messages upward from the first root. 127 | console.log('\x1b[93m[fetching]\x1b[0m'); 128 | _scope.tree = await MAM.fetch(message.root, 'public', null, null); 129 | /* _scope.tree = await MAM.fetch(message.root, 'restricted', password, null); */ 130 | } 131 | 132 | return _scope.tree; 133 | } 134 | 135 | async publish (_json, _scope) { 136 | 137 | let packet = JSON.stringify(_json); 138 | 139 | let trytes = _scope.iota.utils.toTrytes(packet) 140 | let message = MAM.create(_scope.mamState, trytes); 141 | // Set the mam state so we can keep adding messages. 142 | _scope.mamState = message.state; 143 | // Attach the message. 144 | console.log('\x1b[93m[sending ' + _json.timestamp +']\x1b[0m\n'); 145 | return await MAM.attach(message.payload, message.address); 146 | } 147 | 148 | //############################################# 149 | //## HELPER ## 150 | //############################################# 151 | 152 | generateSeed () { 153 | 154 | var seed = ""; 155 | var trytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ9"; 156 | 157 | for (var i = 0; i < 81; i++) 158 | seed += trytes.charAt(Math.floor(Math.random() * trytes.length)); 159 | 160 | return seed; 161 | } 162 | 163 | } 164 | 165 | //############################################# 166 | //## EXPORTS ## 167 | //############################################# 168 | 169 | module.exports = MAM_STREAM; 170 | --------------------------------------------------------------------------------