This is my first tutorial so please let meknow how I can improve.
This project is all visual alarms. There are afair amount of glitches that I have to work through but overall I think it is asuccess. The basic idea of the project is to create a webservice to get eventsfrom google calendar. The webservice will use an MQTT broker to send messagesto a Raspberry Pi which can control LEDs.
Let's begin.
First we need to set up our hardware. I recommend following this tutorial here if you need an in depth guide but the short of it will be like this:
on the right we have our power source connected to power and ground on the breadboard. The three black 3-pronged things are MOSFETs, you can find them in the materials description but essentially a MOSFET is a fancy logical and gate. It will constantly be receiving power from the right most leg but it will only let power through if there is also a signal from the left leg. If there is power and a signal it will route power through the middle leg which we have connected to our LED strip. And that's as complicated as the hardware gets.
Let's get a test going for the LEDsOpen terminal on the RaspberryPi then run:
$ sudo apt-get install build-essential unzip wget
This is where my tutorial starts to differ from the linked tutorial above. Instead of just using the terminal to send signals through the pi, we will create a small program in node.js to control our lights.
Create a new node.js workspace in a new directory.
$ npm init
Enter through all of the dialogue.
$ npm i pigpio
This installs the input/output library which we will use to control the I/O pins on the pi.
The way that we control the leds is by sending output signals to the breadboard through pins, 17, 22, and 24 with each being its own color. If you only set up pin 22 then you will only be able to activate the green led.
Side note: if you did follow the other tutorial make sure you run
$ sudo killall pigpiod
Because only one process can use the pigpiod daemon at a time so your node program will fail.
// import the pigpio package
var Gpio = require('pigpio').Gpio
// initialize our variables, one for each color
// specify the pin and mode (in or out)
var ledRed = new Gpio(22, {mode: Gpio.OUTPUT})
// set red to full brightness
ledRed.pwmWrite(255)
// set red to half brightness
ledRed.pwmWrite(127)
// set red to off
ledRed.pwmWrite(0)
the pwmWrite() function can take any integer value between 0-255 with 0 being off (black) and 255 being full red. Now let's initialize blue and green too
// import the pigpio package
var Gpio = require('pigpio').Gpio
// initialize our variables, one for each color
// specify the pin and mode (in or out)
var ledRed = new Gpio(22, {mode: Gpio.OUTPUT})
var ledGreen = new Gpio(17, {mode: Gpio.OUTPUT})
var ledBlue = new Gpio(24, {mode: Gpio.OUTPUT})
ledRed.pwmWrite(127)
ledBlue.pwmWrite(255)
ledGreen.pwmWrite(0)
// now we should have a nice purple color
Cool!
Web ServiceOn your main computer complete step 1 of this tutorial from google api docs. Create a new working directory and run the following npm commands
$ npm install mqtt --save
$ npm install googleapis@27 --save
Now that you have the correct stuff installed its time for a lot of code.
const fs = require('fs');
const mkdirp = require('mkdirp');
const readline = require('readline');
const {google} = require('googleapis');
const OAuth2Client = google.auth.OAuth2;
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
const TOKEN_PATH = 'credentials.json';
var CREDENTIALS;
var client = {}
/**
* Authorize request from google then request calendar events
*/
function connectAndGetStuffFromGoogle() {
// Load client secrets from a local file.
fs.readFile('client_secret.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
CREDENTIALS = JSON.parse(content)
authorize(CREDENTIALS, listEvents);
});
}
/* Code from Google API Tutorials */
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* @param {Object} credentials The authorization client credentials.
* @param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return callback(err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
/**
* Lists the next 10 events on the user's primary calendar.
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listEvents(auth) {
const calendar = google.calendar({version: 'v3', auth});
calendar.events.list({
calendarId: 'primary',
timeMin: (new Date()).toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime',
}, (err, {data}) => {
if (err) return console.log('The API returned an error: ' + err);
const events = data.items;
if (events.length) {
console.log('Upcoming events:');
events.map((event, i) => {
// if the event is an all day event by definition it isnt an alarm
if(typeof event.start.dateTime != 'undefined') {
const start = event.start.dateTime || event.start.date;
console.log(`${event.summary}`);
console.log('Event starts at ' + start)
// client.publish(ALARM_TOPIC, start.toString())
}
});
} else {
console.log('No upcoming events found.');
}
});
}
Copy and past this code. You will notice that one line is commented out towards the end. We will get to that in just a second. Go ahead and run this code
$ node quickstart.js
Note that sudo is not needed in this case because we are not using Piod. When you run the program you should be prompted with a console message asking you to click the link given. Follow the link to authorize your webservice for access to read/write privilege on your Google calendar. You will be given an access token to past into the console and that will create and save a credentials file which your computer will use from this point onward.
Adding MQTT FunctionalityMQTT is an alternative to HTTP and it designed for the internet of things. This means it is lightweight and easy to handle. It also is a one way communication as opposed to HTTP which will always return a response. This has advantages to performance for small devices because you only need to listen for certain types of messages. Clients subscribe to MQTT topics to recieve all messages coming from that topic and client publish to that topic to send a message to anyone listening. Think of how the twitter tagging system works. You just send a message out to the world and anyone looking at that hashtag can see what you said. However MQTT thankfully doesn't universally send messages our to the internet. You need to create your own MQTT server and then you will be given unique info with which to connect to pub/sub to the topics in that server.
I recommend using the services from cloudmqtt.com it is free and easy to set up.
Create an MQTT server and once you have go a head and make a new file called "mqtt_credentials.json"
copy and past the following code with your info
{
"broker_url" : "mqtt://cloudmqtt.com:portnum",
"client_username": "username",
"client_password": "password"
}
Save this file to the working directory.
Now we are going to add the MQTT funcitonality to our Pi. Paste this code right after all of the variable are initialized and before the comment that says Authorize
const TOKEN_PATH = 'credentials.json';
var CREDENTIALS;
var client = {}
...
// Load client secrets from a local file.
fs.readFile('mqtt_credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials
var mqtt_cred = JSON.parse(content)
const {broker_url, client_username, client_password} = mqtt_cred
client = mqtt.connect(broker_url, {
username: client_username,
password: client_password
})
// When we connect to the MQTT broker, subscribe to the desired topics and
// publish that the server is listening to DEBUG
client.on('connect', function () {
console.log(DEVICE + ' is connected to MQTT')
client.subscribe(DEBUG_TOPIC)
client.publish(DEBUG_TOPIC, DEVICE + ' began listening to topic: ' + DEBUG_TOPIC + ' at ' + new Date().toString())
client.subscribe(REQUEST_TOPIC)
})
// When we recieve a published message
client.on('message', function (topic, message) {
// if the message is a request then initialize communication with google
if(topic === REQUEST_TOPIC) {
console.log('\n++++++++++begin getting events+++++++++++')
// We must begin with authoriation every time otherwhise we will not
// have access to events added after the first authorization
connectAndGetStuffFromGoogle()
}
else if(topic === DEBUG_TOPIC) // if debug then print message to terminal
console.log('Debug Message: ' + message.toString())
})
});
/**
* Authorize request from google then request calendar events
*/
Also at the beginning of the file make sure to put:
// includes mqtt
var mqtt = require('mqtt')
// different topics for different purposes
const ALARM_TOPIC = 'alarm'
const DEBUG_TOPIC = 'debug'
const REQUEST_TOPIC = 'request'
// for debug, will always print the origin device
const DEVICE = 'server'
This should be fully functional.
Connecting the PiNow we need to get our Pi connected to MQTT. Copy and past your "mqtt_credentials.json" into your pi's working directory. now add our code to listen for the messages and wait for them to be triggered
/* This file contains the code to be run by my RaspberryPi */
// Message Queuing Telemetry Transport Protocol (MQTT) is the standard
// alternative to HTTP for IOT devices. We will use it to communicate our
// alarm data from our server to our microcontroller or
var mqtt = require('mqtt')
var Gpio = require('pigpio').Gpio
var fs = require('fs')
const TOPIC = 'alarm' // All messages for communicating alarms
const DEBUG_TOPIC = 'debug' // All messages printed for debugging purposes
const REQUEST_TOPIC = 'request' // All messages for requesting events
const DEVICE = 'RaspberryPi' // Which device is this?
// Create a set to store all the timestamp of when to activate
var comingEvents = new Set()
// For each color you need to specify on what GPIO pin is connected and that we
// will use in mode OUTPUT
var ledRed = new Gpio(22, {mode: Gpio.OUTPUT}); // set the red led to pin #22
var ledGreen = new Gpio(17, {mode: Gpio.OUTPUT});// set the green led to pin #17
var ledBlue = new Gpio(24, {mode: Gpio.OUTPUT});// set the blue led to pin #24
/* On Start */
testLEDs(); // Make quick test for the leds
// Load client secrets from a local file.
fs.readFile('mqtt_credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials
var mqtt_cred = JSON.parse(content)
const {broker_url, client_username, client_password} = mqtt_cred
client = mqtt.connect(broker_url, {
username: client_username,
password: client_password
})
// When we connect to the server subscribe to the topic for debug and main topics
client.on('connect', function () {
console.log(DEVICE + ' is connected to MQTT')
// Connect to debug topic
client.subscribe(DEBUG_TOPIC)
client.publish(DEBUG_TOPIC, DEVICE + ' began listening to topic: ' +
TOPIC + ' at ' + new Date().toString())
// Connect to alarm topic
client.subscribe(TOPIC)
client.publish(REQUEST_TOPIC, 'RequestEvents')
// Request upcoming events
setInterval(() => client.publish(REQUEST_TOPIC, 'RequestEvents'), 30000)
// Check if the alarms have past their date
setInterval(checkAlarm, 7000)
})
client.on('message', function(topic, message) {
if(topic == TOPIC) {// if topic is alarm, wait for alarm
if(!(message == 'RequestEvents')) {
console.log(`\nReceived: ${message.toString()}`)
addToEvents(new Date(message))
}
}
// else if(topic == DEBUG_TOPIC) // if topic is debug print debug message
//console.log(message)
})
});
/* On Start Finish */
/**
* calculates how long the Pi needs to wait before activating the Leds
* @param {Date} startDate The starting time of the event
*/
function addToEvents(startDate) {
console.log(`look here ${startDate}`)
var startTime = startDate.getTime();
if(!comingEvents.has(startTime)) {
comingEvents.add(startTime)
console.log(`\nAdded ${startTime} alarms`)
}
}
/**
* recursivelty calls iteself to activate the leds slowly
* @param {int} r - The desired red value | default: 0
* @param {int} g - The desired green value | default: 0
* @param {int} b - The desired blue value | default: 0
* @param {int} speed - The delay between increasing value | default: 50
*/
function increaseLEDs(r = 0, g = 0, b = 0, speed = 50) {
if (r === 0 && g === 0 && b === 0)
console.log('pew pew lights')
ledRed.pwmWrite(r)
ledGreen.pwmWrite(g)
ledBlue.pwmWrite(b)
if(r < 255) // first incremement the red value until at max then same for b, g
setTimeout(() => increaseLEDs(r + 5, g, b, speed), speed);
else if(b < 255)
setTimeout(() => increaseLEDs(r, g, b + 5, speed), speed);
else if(g < 255)
setTimeout(() => increaseLEDs(r, g + 5, b, speed), speed);
else {
//time to go back to black
setTimeout(() => setLEDs(), 3000)
return;
}
}
/**
* Checks if the alarms are past their date then activates the alarms if they
* are past their time to activate it sets them off
*/
function checkAlarm() {
setLEDs() // set leds to 0 for convienience
console.log('\nChecking Alarms')
var dateNow = new Date() // get current dateTime
var timeNow = dateNow.getTime() // get current date in the form of a timestamp
// holds the alarms that we will remove after they have been activated
var removeUs = []
// For each time in our set of timestamps see if it is time to activate
comingEvents.forEach(function(element) {
// if the current time is greater than the time of the alarm then it is
// time to activate the alarm
if(timeNow > element) {
console.log('Activating Alarm')
increaseLEDs();
// push to list to signify the alarm has been handled
removeUs.push(element);
} else { // calculate time until alarm sounds
const timeLeft = element - timeNow
console.log(`${timeLeft}ms until alarm activation`)
}
});
removeUs.forEach(function(element) { // remove all activated alarms
console.log(`removing ${element} from comingEvents`)
comingEvents.delete(element)
})
}
/**
* set the LEDs to the given values
* @param {int} r - The desired red value | default: 0
* @param {int} g - The desired green value | default: 0
* @param {int} b - The desired blue value | default: 0
*/
function setLEDs(r = 0, g = 0, b = 0) {
ledRed.pwmWrite(r)
ledBlue.pwmWrite(g)
ledGreen.pwmWrite(b)
}
/**
* Sends GPIO signals to light up the leds to test their functionality
*/
function testLEDs() {
// Set all LEDs to 0 (off)
setLEDs()
console.log('Test red');
// Turn on red led then wait 500ms before setting to off
ledRed.pwmWrite(255);
setTimeout(function() {
ledRed.pwmWrite(0)
console.log('Test green');
// Turn on green led then wait 500ms before setting to off
ledGreen.pwmWrite(255);
setTimeout(function() {
ledBlue.pwmWrite(0), 500
console.log('Test blue');
// Turn on blue led then wait 500ms before setting to off
ledBlue.pwmWrite(255);
setTimeout(() => ledGreen.pwmWrite(0), 500)
}, 500)
}, 500)
// After the test turn them off
setTimeout(() => increaseLEDs(), 1000);
}
Now this code is fairly well documented so it should be pretty self explanatory. I am new to js so the reason I used setTimeout so often is because I don't know how to force the program to run sequentially without starting the next functions before the last have finished.
to run now on the server just use
$ node quickstart.js
to run on the pi use
$ sudo node index.js
Comments