├── .gitignore
├── .meteor
├── .finished-upgraders
├── .gitignore
├── .id
├── packages
├── release
└── versions
├── LICENSE
├── README.md
├── client
├── client.js
├── mqtt.css
└── mqtt.html
├── mqtt.js
├── packages.json
├── packages
└── npm
│ ├── .gitignore
│ ├── .npm
│ └── package
│ │ ├── .gitattributes
│ │ ├── .gitignore
│ │ ├── README
│ │ └── npm-shrinkwrap.json
│ ├── index.js
│ ├── package.js
│ ├── test.js
│ └── versions.json
└── server
├── config.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 |
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 7qk64mfblh5z9nbhm1
8 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | standard-app-packages
7 | insecure
8 | npm
9 | pinglamb:bootstrap3
10 |
11 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@0.9.1
2 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | application-configuration@1.0.1
2 | autoupdate@1.0.6
3 | binary-heap@1.0.0
4 | blaze-tools@1.0.0
5 | blaze@2.0.0
6 | callback-hook@1.0.0
7 | check@1.0.0
8 | ctl-helper@1.0.3
9 | ctl@1.0.1
10 | ddp@1.0.8
11 | ejson@1.0.1
12 | follower-livedata@1.0.1
13 | geojson-utils@1.0.0
14 | html-tools@1.0.0
15 | htmljs@1.0.0
16 | id-map@1.0.0
17 | insecure@1.0.0
18 | jquery@1.0.0
19 | json@1.0.0
20 | logging@1.0.2
21 | meteor-platform@1.0.1
22 | meteor@1.0.3
23 | minifiers@1.0.2
24 | minimongo@1.0.2
25 | mongo@1.0.4
26 | npm@0.0.0
27 | observe-sequence@1.0.2
28 | ordered-dict@1.0.0
29 | pinglamb:bootstrap3@3.2.1
30 | random@1.0.0
31 | reactive-dict@1.0.1
32 | reactive-var@1.0.1
33 | reload@1.0.1
34 | retry@1.0.0
35 | routepolicy@1.0.0
36 | session@1.0.1
37 | spacebars-compiler@1.0.2
38 | spacebars@1.0.1
39 | standard-app-packages@1.0.1
40 | templating@1.0.5
41 | tracker@1.0.2
42 | underscore@1.0.0
43 | webapp@1.0.3
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Niko Köbler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Meteor MQTT Client
2 | ==================
3 |
4 | [Meteor](http://www.meteor.com) [MQTT](http://mqtt.org/) client using [mqtt package from NPM](https://www.npmjs.org/package/mqtt).
5 |
--------------------------------------------------------------------------------
/client/client.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // subscribe to the published collection
4 | Meteor.subscribe("mqttMessages");
5 |
6 | // we need a dependency later on to refresh the topic query
7 | var topicDep = new Deps.Dependency;
8 |
9 | Session.setDefault("configValues", {});
10 |
11 | // this is the dependend function to retrieve and set the topic query
12 | Deps.autorun(function(){
13 | topicDep.depend();
14 | Meteor.call("getTopicQuery", function(err, obj) {
15 | Session.set("topicQuery", obj);
16 | });
17 | });
18 |
19 | Meteor.startup(function() {
20 | Meteor.call("getConfigValues", function(err, values) {
21 | Session.set("configValues", values);
22 | });
23 | });
24 |
25 | Template.config.host = function() {
26 | return Session.get("configValues")["mqttHost"];
27 | };
28 |
29 | Template.config.port = function() {
30 | return Session.get("configValues")["mqttPort"];
31 | };
32 |
33 | Template.messages.topicQuery = function() {
34 | return Session.get("topicQuery");
35 | };
36 |
37 | Template.messages.lastMessages = function () {
38 | return Messages.find({}, {sort: {ts: -1}});
39 | };
40 |
41 | // just for a better readability in the UI
42 | Template.msg.tsString = function() {
43 | return this.ts.toLocaleString();
44 | };
45 |
46 | // the start/stop button events, call the server-side methods
47 | Template.admin.events({
48 | 'click #startClient': function() {
49 | Meteor.call("startClient");
50 | },
51 | 'click #stopClient': function() {
52 | Meteor.call("stopClient");
53 | }
54 | });
55 |
56 | // the events for changing the topic query (for button click and pressing enter)
57 | Template.topic.events({
58 | 'click #sendTopicQuery': function() {
59 | _sendTopic();
60 | },
61 | 'keyup #topicQuery': function(e) {
62 | if (e.type == "keyup" && e.which == 13) {
63 | _sendTopic();
64 | }
65 | }
66 | });
67 |
68 | // the click-event for publishing a message
69 | Template.publish.events({
70 | 'click #publishMessage': function() {
71 | var elTopic = document.getElementById("topic");
72 | var elMessage = document.getElementById("message");
73 | Meteor.call("publishMessage", elTopic.value, elMessage.value, function() {
74 | elTopic.value = "";
75 | elMessage.value = "";
76 | elTopic.focus();
77 | });
78 | }
79 | });
80 |
81 | // get the new query from the input field and send it to the server, reset field
82 | // tell the dependency, that it has changed and has to be run again
83 | var _sendTopic = function() {
84 | var el = document.getElementById("topicQuery");
85 | var topicQuery = el.value;
86 | Meteor.call("setTopicQuery", topicQuery, function() {
87 | topicDep.changed();
88 | el.value = "";
89 | el.focus();
90 | });
91 | };
--------------------------------------------------------------------------------
/client/mqtt.css:
--------------------------------------------------------------------------------
1 | /* CSS declarations go here */
2 | body {
3 | margin: 10px;
4 | }
--------------------------------------------------------------------------------
/client/mqtt.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Meteor MQTT Dashboard
4 |
5 |
6 |
7 |
8 |
Meteor MQTT Dashboard
9 | {{> config}}
10 | {{> admin}}
11 |
12 |
13 | {{> topic}}
14 |
15 |
16 | {{> publish}}
17 |
18 |
19 | {{> messages}}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Broker: {{host}}:{{port}}
32 |
33 |
34 |
35 | Topic Query
36 |
37 |
38 |
39 |
40 |
41 | Messages for topic query {{topicQuery}}
42 |
43 |
44 |
45 | Time |
46 | Topic |
47 | Message |
48 |
49 |
50 |
51 | {{#each lastMessages}}
52 | {{> msg}}
53 | {{/each}}
54 |
55 |
56 |
57 |
58 |
59 |
60 | {{tsString}} |
61 | {{topic}} |
62 | {{message}} |
63 |
64 |
65 |
66 |
67 | Publish a message
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/mqtt.js:
--------------------------------------------------------------------------------
1 | // initializing the collection, used from client and server
2 | Messages = new Meteor.Collection("mqtt-messages");
3 |
--------------------------------------------------------------------------------
/packages.json:
--------------------------------------------------------------------------------
1 | {
2 | "mqtt": "0.3.11"
3 | }
--------------------------------------------------------------------------------
/packages/npm/.gitignore:
--------------------------------------------------------------------------------
1 | .build*
2 |
--------------------------------------------------------------------------------
/packages/npm/.npm/package/.gitattributes:
--------------------------------------------------------------------------------
1 | /npm-shrinkwrap.json eol=lf
2 | /README eol=lf
3 | /.git* eol=lf
4 |
--------------------------------------------------------------------------------
/packages/npm/.npm/package/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/packages/npm/.npm/package/README:
--------------------------------------------------------------------------------
1 | This directory and the files immediately inside it are automatically generated
2 | when you change this package's NPM dependencies. Commit the files in this
3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
4 | so that others run the same versions of sub-dependencies.
5 |
6 | You should NOT check in the node_modules directory that Meteor automatically
7 | creates; if you are using git, the .gitignore file tells git to ignore it.
8 |
--------------------------------------------------------------------------------
/packages/npm/.npm/package/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "mqtt": {
4 | "version": "0.3.11",
5 | "dependencies": {
6 | "readable-stream": {
7 | "version": "1.0.27-1",
8 | "dependencies": {
9 | "core-util-is": {
10 | "version": "1.0.1"
11 | },
12 | "isarray": {
13 | "version": "0.0.1"
14 | },
15 | "string_decoder": {
16 | "version": "0.10.25-1"
17 | },
18 | "inherits": {
19 | "version": "2.0.1"
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/npm/index.js:
--------------------------------------------------------------------------------
1 | var Future = Npm.require('fibers/future');
2 | Async = {};
3 |
4 | Meteor.require = function(moduleName) {
5 | var module = Npm.require(moduleName);
6 | return module;
7 | };
8 |
9 | Async.runSync = Meteor.sync = function(asynFunction) {
10 | var future = new Future();
11 | var sent = false;
12 | var payload;
13 |
14 | var wrappedAsyncFunction = Meteor.bindEnvironment(asynFunction, function(err) {
15 | console.error('Error inside the Async.runSync: ' + err.message);
16 | returnFuture(err);
17 | });
18 |
19 | setTimeout(function() {
20 | wrappedAsyncFunction(returnFuture);
21 | }, 0);
22 |
23 | future.wait();
24 | sent = true;
25 |
26 | function returnFuture(error, result) {
27 | if(!sent) {
28 | payload = { result: result, error: error};
29 | future.return();
30 | }
31 | }
32 |
33 | return payload;
34 | };
35 |
36 | Async.wrap = function(arg1, arg2) {
37 | if(typeof arg1 == 'function') {
38 | var func = arg1;
39 | return wrapFunction(func);
40 | } else if(typeof arg1 == 'object' && typeof arg2 == 'string') {
41 | var obj = arg1;
42 | var funcName = arg2;
43 | return wrapObject(obj, [funcName])[funcName];
44 | } else if(typeof arg1 == 'object' && arg2 instanceof Array) {
45 | var obj = arg1;
46 | var funcNameList = arg2;
47 | return wrapObject(obj, funcNameList);
48 | } else {
49 | throw new Error('unsupported argument list');
50 | }
51 |
52 | function wrapObject(obj, funcNameList) {
53 | var returnObj = {};
54 | funcNameList.forEach(function(funcName) {
55 | if(obj[funcName]) {
56 | var func = obj[funcName].bind(obj);
57 | returnObj[funcName] = wrapFunction(func);
58 | } else {
59 | throw new Error('instance method not exists: ' + funcName);
60 | }
61 | });
62 | return returnObj;
63 | }
64 |
65 | function wrapFunction(func) {
66 | return function() {
67 | var args = arguments;
68 | response = Meteor.sync(function(done) {
69 | Array.prototype.push.call(args, done);
70 | func.apply(null, args);
71 | });
72 |
73 | if(response.error) {
74 | //we need to wrap a new error here something throw error object comes with response does not
75 | //print the correct error to the console, if there is not try catch block
76 | var error = new Error(response.error.message);
77 | for(var key in response.error) {
78 | if(error[key] === undefined) {
79 | error[key] = response.error[key];
80 | }
81 | }
82 | throw error;
83 | } else {
84 | return response.result;
85 | }
86 | };
87 | }
88 | };
--------------------------------------------------------------------------------
/packages/npm/package.js:
--------------------------------------------------------------------------------
1 | var path = Npm.require('path');
2 | var fs = Npm.require('fs');
3 | var packagesJsonFile = path.resolve('./packages.json');
4 |
5 | //creating `packages.json` file for the first-time if not exists
6 | if(!fs.existsSync(packagesJsonFile)) {
7 | fs.writeFileSync(packagesJsonFile, '{\n \n}')
8 | }
9 |
10 | try {
11 | var fileContent = fs.readFileSync(packagesJsonFile);
12 | var packages = JSON.parse(fileContent.toString());
13 | Npm.depends(packages);
14 | } catch(ex) {
15 | console.error('ERROR: packages.json parsing error [ ' + ex.message + ' ]');
16 | }
17 |
18 | Package.describe({
19 | summary: "complete npm integration/support for Meteor"
20 | });
21 |
22 | Package.on_use(function (api, where) {
23 | api.export('Async');
24 |
25 | var packagesFile = './.meteor/packages';
26 | if(fs.existsSync(packagesFile) && isNewerMeteor) {
27 | api.add_files(['index.js', '../../packages.json'], 'server');
28 | } else {
29 | api.add_files(['index.js'], 'server');
30 | }
31 |
32 | function isNewerMeteor() {
33 | return fs.readFileSync(packagesFile, 'utf8').match(/\nstandard-app-packages/);
34 | }
35 | });
36 |
37 | Package.on_test(function (api) {
38 | api.use(['tinytest']);
39 | api.add_files(['index.js', 'test.js'], 'server');
40 | });
41 |
--------------------------------------------------------------------------------
/packages/npm/test.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Async.runSync - with done()', function(test) {
2 | var output = Async.runSync(function(done) {
3 | setTimeout(function() {
4 | done(null, 10001);
5 | }, 10);
6 | });
7 |
8 | test.equal(output.result, 10001);
9 | test.equal(output.error, null);
10 | });
11 |
12 | Tinytest.add('Async.runSync - with error()', function(test) {
13 | var output = Async.runSync(function(done) {
14 | setTimeout(function() {
15 | done({message: 'error-message', code: 402});
16 | }, 10);
17 | });
18 |
19 | test.equal(output.result, undefined);
20 | test.equal(output.error.code, 402);
21 | });
22 |
23 | Tinytest.add('Async.runSync - with error in the callback', function(test) {
24 | var output = Async.runSync(function(done) {
25 | throw new Error('SOME_ERROR');
26 | });
27 |
28 | test.equal(output.result, undefined);
29 | test.equal(output.error.message, 'SOME_ERROR');
30 | });
31 |
32 | Tinytest.add('Async.wrap function mode - success', function(test) {
33 | function wait(timeout, callback) {
34 | setTimeout(function() {
35 | callback(null, 'okay');
36 | }, timeout);
37 | };
38 |
39 | var enclosedWait = Async.wrap(wait);
40 | var output = enclosedWait(100);
41 |
42 | test.equal(output, 'okay');
43 | });
44 |
45 | Tinytest.add('Async.wrap function mode - error', function(test) {
46 | function wait(timeout, callback) {
47 | setTimeout(function() {
48 | var error = new Error('THE_ERROR');
49 | error.code = 500;
50 | callback(error);
51 | }, timeout);
52 | };
53 |
54 | var enclosedWait = Async.wrap(wait);
55 | try {
56 | enclosedWait(100);
57 | test.fail('there must be an error');
58 | } catch(err) {
59 | test.ok(err.message.match('THE_ERROR'));
60 | test.equal(err.code, 500);
61 | }
62 |
63 | });
64 |
65 | Tinytest.add('Async.wrap object mode - success', function(test) {
66 | function Wait() {
67 | this.start = function(timeout, callback) {
68 | setTimeout(function() {
69 | callback(null, 'okay');
70 | }, timeout);
71 | };
72 | }
73 |
74 | var wait = new Wait();
75 |
76 | var enclosedWait = Async.wrap(wait, 'start');
77 |
78 | var output = enclosedWait(100);
79 | test.equal(output, 'okay');
80 | });
81 |
82 | Tinytest.add('Async.wrap object mode - funcName not exists', function(test) {
83 | function Wait() {
84 | this.start = function(timeout, callback) {
85 | setTimeout(function() {
86 | callback(null, 'okay');
87 | }, timeout);
88 | };
89 | }
90 |
91 | var wait = new Wait();
92 | try {
93 | var enclosedWait = Async.wrap(wait, 'startz');
94 | test.fail('shoud throw an error');
95 | } catch(ex) {
96 |
97 | }
98 | });
99 |
100 | Tinytest.add('Async.wrap object mode - multi function mode', function(test) {
101 | function Wait() {
102 | this.start = function(timeout, callback) {
103 | setTimeout(function() {
104 | callback(null, 'okay');
105 | }, timeout);
106 | };
107 |
108 | this.start2 = function(timeout, callback) {
109 | setTimeout(function() {
110 | callback(null, 'okay');
111 | }, timeout);
112 | };
113 | }
114 |
115 | var wait = new Wait();
116 | var enclosedWait = Async.wrap(wait, ['start', 'start2']);
117 | enclosedWait.start(100);
118 | enclosedWait.start2(100);
119 | });
--------------------------------------------------------------------------------
/packages/npm/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | [
4 | "meteor",
5 | "1.0.3"
6 | ],
7 | [
8 | "underscore",
9 | "1.0.0"
10 | ]
11 | ],
12 | "pluginDependencies": [],
13 | "toolVersion": "meteor-tool@1.0.27",
14 | "format": "1.0"
15 | }
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | config = {
2 | mqttHost: "broker.mqttdashboard.com",
3 | mqttPort: 1883
4 | };
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // default topic query
4 | var topicQuery = "#";
5 |
6 | // for development purposes, delete the DB on startup, don't collect too much old data
7 | Meteor.startup(function () {
8 | Messages.remove({});
9 | });
10 |
11 | // data has to be published, autopublish is turned off!
12 | // return only the last 10 messages to the client
13 | Meteor.publish("mqttMessages", function() {
14 | return Messages.find({}, {sort: {ts: -1}, limit: 10});
15 | });
16 |
17 | // initialize the mqtt client from mqtt npm-package
18 | var mqtt = Meteor.require("mqtt");
19 | var mqttClient = mqtt.createClient(config.mqttPort, config.mqttHost);
20 | mqttClient
21 | .on("connect", function() {
22 | console.log("client connected");
23 | })
24 | .on("message", function(topic, message) {
25 | console.log(topic + ": " + message);
26 | // build the object to store
27 | var msg = {
28 | message: message,
29 | topic: topic,
30 | ts: new Date()
31 | };
32 | // add the message to the collection (see below...)
33 | addMsgToCollection(msg);
34 | });
35 |
36 | // function is called when message is received (see above)
37 | // to get access to Meteor resources from non-Meteor callbacks, this has to be bound in Meteor environment
38 | var addMsgToCollection = Meteor.bindEnvironment(function(message) {
39 | Messages.insert(message);
40 | });
41 |
42 | // some methods called by the client
43 | Meteor.methods({
44 | // start receiving messages with the set topic-query
45 | startClient: function() {
46 | console.log("startClient called");
47 | mqttClient.subscribe(topicQuery);
48 | },
49 | // stop receiving messages
50 | stopClient: function() {
51 | console.log("stopClient called");
52 | mqttClient.unsubscribe(topicQuery);
53 | },
54 | // set a new topic query, unsubscribe from the old and subscribe to the new one
55 | setTopicQuery: function(newTopicQuery) {
56 | console.log("set new Topic: " + newTopicQuery);
57 | mqttClient.unsubscribe(topicQuery).subscribe(newTopicQuery);
58 | topicQuery = newTopicQuery;
59 | },
60 | // send the topic query to the caller
61 | getTopicQuery: function() {
62 | return topicQuery;
63 | },
64 | // publishes a message with a topic to the broker
65 | publishMessage: function(topic, message) {
66 | console.log("message to send: " + topic + ": " + message);
67 | mqttClient.publish(topic, message, function() {
68 | console.log("message sent: " + message);
69 | });
70 | },
71 | getConfigValues: function() {
72 | return config;
73 | }
74 | });
75 |
76 | // delete every 120 seconds old data (messages) from the collection/mongodb
77 | Meteor.setInterval(function() {
78 | Messages.remove({});
79 | }, 2*60*1000);
--------------------------------------------------------------------------------