Do you want to command your Raspberry Pi via Whatsapp and have this system monitored and brought up by Nagios in case it dies?
Follow this guide! 🙂
Requirements:
- Spare SIM card (number will be used by your Raspberry Pi)
- A phone to keep the SIM card on during the registration process only
- A Raspberry Pi (Debian 8 recommended)
- Nagios
Let’s do it!
Step 1: Put your SIM in the phone and make sure the SIM can receive text messages (no data is required)
Step 2: Install/configure your Raspberry Pi
Installation
Yuwsup
To make all this magic happening, we’re going to use Yowsup
Here some easy steps to install on Raspian: (you can use also pip install yowsup2):
$ sudo apt-get install git python-dev libncurses5-dev
$ git clone git://github.com/tgalal/yowsup.git
$ cd yowsup
$ sudo python setup.py install
Once installed, you need to register your phone number, extract the password and use it to configure the following scripts.
To register, create a file called mydetails and add the following (replace country code and phone number accordingly):
cc=44
phone=447711111123
After that, run this:
python yowsup-cli registration --config mydetails --requestcode sms
You should receive a text on your phone with a 6 digits code (xxx-xxx format). Use the following command to get the password:
python yowsup-cli registration --config mydetails --register xxx-xxx
After a little while, you should see some output like this:
status: ok
kind: free
pw: 9BkIpOaLpCk1LxuQIK8Vrt6XwNkj=
price: 0.79
price_expiration: 1434674994
currency: GBP
cost: 0.79
expiration: 1463544490
login: 4425784541474
type: new
Grab the pw bit and add append to your mydetails file:
cc=44
phone=447711111123
password=9BkIpOaLpCk1LxuQIK8Vrt6XwNkj=
Now you can test using the below bash script (demo.sh):
#!/bin/bash
echo -e "\e[31m"
echo 'Once you get the prompt just use /L to go online'
echo 'After that you can send a message in this way:'
echo '/message send 449988776655 "Hello from the Raspberry Pi!"'
echo -e "\e[0m\n"
yowsup-cli demos --yowsup --config mydetails
All should (hopefully) work! 🙂
Python scripts for yowsup
The following scripts and configurations are based on the following:
- the user “piuser” is the one who will run the main scripts
- scripts are stored into /home/piuser/WhatsappOnPi/scripts
- the user “nagios” will need some extra privileges to run some scripts
In /home/piuser/WhatsappOnPi/scripts create the following scripts:
1) whatsapp.py
This script is the one that keeps layer.py script up and running.
from yowsup.stacks import YowStackBuilder
from yowsup.common import YowConstants
from yowsup.layers import YowLayerEvent
from layer import EchoLayer
from yowsup.layers.auth import YowAuthenticationProtocolLayer
from yowsup.layers.coder import YowCoderLayer
from yowsup.layers.network import YowNetworkLayer
from yowsup.env import YowsupEnv
from mysettings import *
#Uncomment to log
#import logging
#logging.basicConfig(level=logging.DEBUG)
CREDENTIALS = (phone, password)
if __name__== "__main__":
stackBuilder = YowStackBuilder()
stack = stackBuilder\
.pushDefaultLayers(True)\
.push(EchoLayer)\
.build()
stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, CREDENTIALS) #setting credentials
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) #sending the connect signal
stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0]) #whatsapp server address
stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
stack.setProp(YowCoderLayer.PROP_RESOURCE, YowsupEnv.getCurrent().getResource()) #info about us as WhatsApp client
stack.loop( timeout = 0.5, discrete = 0.5 )
2) layer.py
This script is the main one that you need to customise as you’d like:
# -*- coding: utf-8 -*-
import os, subprocess, time, re
from yowsup.layers import YowLayer
from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities import TextMessageProtocolEntity
from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity
from mysettings import *
ap = set(allowedPersons)
# Message on Start Up
startcommand='yowsup-cli demos -l %s:%s -s %s "*[INFO] System Started*: `uptime`" 2>&1 > /dev/null ' % (phone, password, destphone)
subprocess.call(startcommand , shell=True)
def sendMessage(self, messageProtocolEntity, msg):
outgoingMessageProtocolEntity = TextMessageProtocolEntity(
''+msg+'',
to = messageProtocolEntity.getFrom())
self.toLower(outgoingMessageProtocolEntity)
class EchoLayer(YowInterfaceLayer):
@ProtocolEntityCallback("message")
def onMessage(self, messageProtocolEntity):
#send receipt otherwise we keep receiving the same message over and over
if True:
receipt = OutgoingReceiptProtocolEntity(messageProtocolEntity.getId(), messageProtocolEntity.getFrom(), 'read', messageProtocolEntity.getParticipant())
self.toLower(receipt)
if messageProtocolEntity.getFrom(False) in ap:
message_body = messageProtocolEntity.getBody().lower().strip(' \t\r\n\0')
#print (message_body)
# Local System Control
if 'help' in (message_body):
msg='Commands available:\nrestart <device>\nuptime\ndf\nlast\nrouter'
sendMessage(self, messageProtocolEntity, msg)
#elif 'reboot' in (message_body):
# result=subprocess.check_output(["sudo", "reboot"])
# msg='reboot: '+result+''
# sendMessage(self, messageProtocolEntity, msg)
elif 'uptime' in (message_body):
result=subprocess.check_output(["uptime"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'df' in (message_body):
result=subprocess.check_output(["df", "-h"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'last' in (message_body):
result=subprocess.check_output(["last"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'router' in (message_body):
result=subprocess.check_output(["/usr/lib/nagios/plugins/check_router_speed"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
# Reboots Control
# ---------------
# this uses a wrapper called 'restart_device'
# bash script with 'case' that issues specific commands over ssh
# to restart different hosts
elif message_body.startswith('restart'):
cmd = message_body.split('restart', 1)[1]
if 'skyhub' in (cmd):
result=subprocess.check_output(["sudo", "restart_device", "router1"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'asus8uk' in (cmd):
result=subprocess.check_output(["sudo", "restart_device", "router2"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'raspberrino' in (cmd):
result=subprocess.check_output(["sudo", "restart_device", "raspberrypi"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
elif 'raspbxino' in (cmd):
result=subprocess.check_output(["sudo", "restart_device", "pbx"])
msg=''+result+''
sendMessage(self, messageProtocolEntity, msg)
else:
msg='Usage: restart (router1|router2|raspberrypi|pbx)'
sendMessage(self, messageProtocolEntity, msg)
else:
msg='Command '+messageProtocolEntity.getBody()+' unknown.\nUse: help'
sendMessage(self, messageProtocolEntity, msg)
else:
# Report
msg='** Alert** \nSender: '+messageProtocolEntity.getFrom()+' '+messageProtocolEntity.getBody()+''
outgoingMessageProtocolEntity = TextMessageProtocolEntity(
''+msg+'',
to = '%[email protected]' % destphone )
self.toLower(outgoingMessageProtocolEntity)
# Reply
msg='No'
sendMessage(self, messageProtocolEntity, msg)
@ProtocolEntityCallback("receipt")
def onReceipt(self, entity):
ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", entity.getType(), entity.getFrom())
self.toLower(ack)
3) mysettings.py
This is included in both scripts and it needs to be updated accordingly:
#################################################################################################
# TO EDIT - add your phone number and password
#
phone="447711111123"
password="9BkIpOaLpCk1LxuQIK8Vrt6XwNkj="
destphone="<your_personal_number>"
#allowedPersons=['<your_personal_number>','<another_number_allowed_to_talk_with_your_pi>']
allowedPersons=['<your_personal_number>']
#
#################################################################################################
Now let’s create a wrapper to start the script: /usr/local/bin/whatsapp_start
#!/bin/bash
# kill process if running
for PID in $(ps aux | grep -i "[p]ython.*whatsapp" | awk '{print $2}') ; do
kill -9 $PID > /dev/null 2>&1
done
# restart process
python /home/piuser/WhatsappOnPi/scripts/whatsapp.py &
And now let’s append this into /etc/rc.local:
echo "Starting whatsapp service"
su - piuser -c /usr/local/bin/whatsapp_start
Done!
Every time we reboot the server, the script will start!
But… what happens if the script dies or something goes wrong?
Answer: Nagios!
Create custom plugin script for Nagios and save it in /usr/lib/nagios/plugins/check_whatsapp
NOTE: Make sure to follow the notes in this script to proper setup visudo
#!/bin/bash
####################################################################################
# user 'nagios' needs to be allowed to run 'netstat' using sudo
# and /usr/local/bin/whatsapp_start to run as 'piuser'
#
# Required lines in visudo:
#
# Cmnd_Alias WHATSAPP_CMD = /bin/su - piuser -c /usr/local/bin/whatsapp_start
# nagios ALL=(ALL) NOPASSWD:/bin/netstat,WHATSAPP_CMD
#
####################################################################################
sudo netstat -pant | grep -q "ESTABLISHED.*python"
if [ $? -eq 0 ] ; then
echo "OK- Whatsapp is up and running"
exit 0
else
echo "CRITICAL- Whatsapp service stopped or not connected. Attempting restart."
sudo su - piuser -c /usr/local/bin/whatsapp_start
exit 2
fi
Now let’s enable this script in /etc/nagios/nrpe_local.cfg:
command[check_whatsapp]=/usr/lib/nagios/plugins/check_whatsapp
On the Nagios SERVER, let’s add the new service.
Following my current setup mentioned here, I’m going to add the following in /etc/nagios3/conf.d/hostgroups_services.cfg
#######################################################################################
# Check Whatsapp Service
define hostgroup {
hostgroup_name whatsapp-servers
alias whatsapp servers
}
define service {
hostgroup_name whatsapp-servers
service_description whatsapp service
normal_check_interval 5
retry_check_interval 2
check_command check_nrpe_1arg!check_whatsapp
use generic-service
}
#######################################################################################
When the service is configured, we need to append this service on the host where we want the check to be executed and verified (config in /etc/nagios3/conf.d/hosts.cfg – eg:)
hostgroups allservers,ssh-servers,http-servers,whatsapp-servers
A couple of restarts/reloads (nagios client and nagios server), and the check should be now visible in the web interface! 🙂
NOTE: You might see “Waiting for this message. This may take a while.” on your Whatsapp while trying to talk with your Pi. And you can wait as much as you like, but it won’t get fixed by itself.
So… how make things working again?
What I’ve done to fix it is:
- stopping nagios3 (setup to try to restart Whatsapp script if down)
- kill the whatsapp python script running
- use the above demo.sh script to send/receive some manual messages
- if you can chat (send/receive correctly), you can now stop demo.sh script and start again your whatsapp python script
This always fixed this issue for me 🙂
Apologies for the typos and mistakes. This has been published more as a note for me than a proper how-to
Source: http://www.techradar.com/how-to/computing/how-to-control-a-raspberry-pi-using-whatsapp-1315610/2
Many thanks to Paul for the initial python scripts templates 🙂