I started my "career" in electronics not long ago when the first Raspberry PI became available. But looking on the specs I decided that the BeagleBone Black suits my needs better and bought one. What I liked most about it after playing with it for a while was the c9 IDE and node.js. Therefore I decided to do the programming for this challenge in node.js, too.
The Cloud
As the cloud service I have chosen Pushbullet because it has:
- an easy, restfull API
- clients for all OSes
- integration in Tasker
Downside is: It does not offer end to end encryption. Therefore I had to Implement it. Since I do not have a lot of clients pre-shared key works well and is used in the implementation. The Pushbullet API is abstracted in a node.js module for even more convenience.
Provisional Client
The client consists of two parts, Tasker and a javascript code. Tasker is "Total Automation for Android" (Tasker website) a tool I use to automate many task on my Android devices. It is much like IFTTT but on the device not in the cloud. For security and privacy reasons "on device" is the preferred solution. What can Tasker do? Again citing from the website:
Tasker is an application for Android which performs tasks (sets of actions) based on contexts(application, time, date, location, event, gesture) in user-defined profiles or in clickable or timer home screen widgets.
With the Pushbullet integration every message received by the Pushbullet client a Tasker event is triggered. This can be used to start a task like launch a specific app or play a ring tone. Unfortunately the message content can not be accessed from the event. This would be necessary to start different tasks on different status changes of keys. To the rescue one of the available tasks is "code" which can run javascript programs. Let us step through the relevant portions of the javascript (nodejs):
var aes = require('crypto-js/aes'); var PushBullet = require('pushbullet'); var pusher = new PushBullet('xxxx'); //replace this with your token
this code snippet imports aes encryption from crypto-js and the nodejs libraray for Pushbullet. The last line instances and initializes the Pushbullet API. The returned object ('pusher') can be used to talk to the cloud. To initialize you need an application token which you can find in your account settings. This is a very weak authentication Pushbullet supports OAuth, too but I haven't tried it yet.
var options = {limit: 100};
For the Pushbullet API we must set a limit to the maximal returned pushes to prevent memory overruns etc.
Then call the search for the last message using the history function:
pusher.history(options, function(error, response) { if(error) throw error; if(response.pushes.length <= 0) throw 'Error: no pushes'; var i = 0; do { if (response.pushes[i].active && (response.pushes[i].sender_name == 'IoT-test' || response.pushes[i].receiver_email == '@ljkawsfowe') && response.pushes[i].type == 'note') { //var message = 'U2FsdGVkX1+Zls89vOpjlkXCcUQhDQrBEzZoH+3iuhU='; var message = response.pushes[i].body; if (message) { var decrypted = aes.decrypt(message, "geheim"); var mask = 1; // works for Max_KEYS < 16 for (var i = 0; i < MAX_KEYS; i++) { setGlobal("Key" + i, ((decrypted.words[0] & mask) != 0)?"true":"false"); mask = mask << 1; } pusher.deletePush(response.pushes[i].iden); } break; } else i++; } while (i < options.limit); // ToDo: Error: no push found });
Line 3 & 4 are for error handling. Then setup the loop for searching the response for through the past messages for one from the controller. I have set up a 'channel' for this purpose. In pushbullet channels are private by default. That's good! Depending on the interface used to send the push the sender_name or receiver_email are set. I was not able to figure out if this is a bug or if there are other differences I am not able to see. In response.pushes[i].body there should be a base 64 encoded AES encrypted message of one block. In line 15 the 'decrypted' object contains the block in 16 bit integers. The for loop in lines 18 - 22 converts the one bit status into individual Boolean variables. In line 20 a tasker API function is used to set android environment variables. The (decrypted.words[0] & mask) != 0)?"true":"false" makes use of the 'conditional operator' which is defined as:
variablename = (condition) ? value1:value2 variablename = value1 // if condition = true variablename = value2 // if condition = false
Therefore the statement in the client code could have been written as:
bitvalue = decrypted.words[0] & mask; if (bitvalue > 0) bit_bool = 'true'; else bit_bool = 'false';
Which is more readable, by far, but obiously less compact.
So if the decrypted message starting whith 00000101b the code inside the for loop (line 18 - 22) sets the following global variables:
Key1 = 'true'; Key2 = 'false'; Key3 = 'true'; Key4 = 'false'; Key5 = 'false'; Key6 = 'false'; Key7 = 'false'; Key8 = 'false';
This values can then be used by other Tasker actions.
Line 24 deletes the message as it is processed now.
The provisional Client lacks some functions I've proposed and testing. But I'd like to share it because the development takes me to long to and I do also value your comments which have been always very useful in the past.
Top Comments