Professional Documents
Culture Documents
This report is submitted in partial fulfilment of the requirements of the B.Sc. Honours Degree in Networking Applications & Services, of the Dublin Institute of Technology
March 15th, 2014 Supervisor: Mr Frank Duignan School of Electrical and Electronic Engineering
DT080B
Acknowledgements
I would just like to take this opportunity to thank Ms Paula Kelly for her suggestions and input on messaging oriented middleware, and Ms Brid Mcloughlin for her proof reading and editorial services and most of all Mr Frank Duignan, for his time, effort and patience in guiding and motivating me towards the completion of the project. His help and expertise, were invaluable, and his contribution to the project was far beyond what was required or expected of him.
Declaration
This final year project is presented in partial fulfilment of the requirements for a the B.Sc. Honours Degree in Networking Applications & Services It is entirely my own work and has not been submitted to any other university or higher education institutio n, or for any other academic award in this University. The work was completed under the guidance of Mr Frank Duignan. Furthermore, I took reasonable care to ensure that the work is original, and, to the best of my knowledge, does not breach copyright law, and has not been taken from other sources except where such work has been cited and acknowledged within the text. Signed _____________________________________________________ D.I.T Student Number _________________________________________ Date _______________________________________________________ Page 2 of 71
DT080B
Table of Contents
Acknowledgements................................................................................................................ 2 Declaration............................................................................................................................. 2 Abstract .................................................................................................................................. 4 Chapter 1 Introduction ........................................................................................................... 5 Chapter 2 What is ZeroMQ ................................................................................................. 7 Chapter 3 ZeroMQ Sockets ................................................................................................... 9 Chapter 5 ZMQ Pattern Examples:...................................................................................... 19 Chapter 6 Implementation Motion sensor ........................................................................... 29 Chapter 7 Implementation Twitter....................................................................................... 32 Chapter 8 Implementation Analogue sensors ...................................................................... 34 Chapter 9 Implementation Adding the camera .................................................................... 39 Chapter 10 Implementation push button and outputs .......................................................... 42 Chapter 12 Implementation Outputs.................................................................................... 46 Chapter 13 Implementation Email ....................................................................................... 48 Chapter 14 Implementation Text Messaging....................................................................... 51 Chapter 15 Implementation Facebook ................................................................................. 54 Chapter 16 Discussion ......................................................................................................... 55 Conclusion ........................................................................................................................... 56 Bibliography ........................................................................................................................ 57 Appendices .......................................................................................................................... 58
Page 3 of 71
DT080B
Abstract
The principle objective of this paper is to investigate the Zmq messaging library. This was achieved using a Raspberry PI acting as a home monitoring system with several sensors. This system used the ZMQ messaging library, to send data to a server, which decides on appropriate action and sends messages back to the PI and alerts through social media and text. Some challenging aspects of this project were using analogue sensors with a digital system, deciding on which ZMQ pattern to use, and adding an image to the text updates. Each Module was built and tested separately before being integrated into the main system The system successfully demonstrates a basic ZMQ Pattern.
Page 4 of 71
DT080B
Chapter 1 Introduction
The initial plan for this project was for the Raspberry PI to act as a home monitoring system with several sensors including both analogue and digital sensors. This system would use the ZMQ messaging library to send data to a server, which would then decide on appropriate action and send alerts through social media and text. Several problems had to be addressed in this project including: Which ZeroMQ messaging pattern to use? How to integrate analogue sensors into a digital system? How to decide and take action for each condition? How to integrate social media updating into the system? How to send a text message from a pi system? How to automatically send an update at a certain time? How to use a received message to activate an action?
It was decided to use a Request Reply ZeroMQ pattern as each input from the sensors would require action. For example, if it was dark the PI would be required to turn on a light. The system also would be required to send updates on a timed basis as to the status. In the sensor array, the sensors required were a digital motion sensor, a temperature sensor, a light sensor, and a push or panic button. The temperature and light sensors were analogue sensors and as the Raspberry PI has no analogue input, an analogue to digital converter would be required. An If-else conditional statement was used to monitor the status of the sensors and if one of the conditions was found to be true to send a message using a separate Publish function, this same function would listen for a reply from the server and take appropriate action when said message was received. On the server, the received message is checked to see if it is one of a list of known statements, if the statement is true, respond with the appropriate message, and update the communications suite. The communications suite would consist of functions to update e-mail, text, Twitter and Facebook.
Page 5 of 71
DT080B
For example, if the light level drops below a certain level, the system will Publish the message its dark, on the server side. When the message its dark is received, the server responds with the message turn on lights and updates the communications suite. On the client side, when the message turn on lights is received , the pi sends a voltage high out through assigned pin to activate an external light.
Page 6 of 71
DT080B
the client and server applications become simpler, more secure, and much easier to write. "It gives you sockets that carry whole messages across various transports like inproc, IPC, TCP, and multicast; you can connect sockets N-to-N with patterns like fanout, pubsub, task distribution, and request-reply" .
Page 7 of 71
DT080B
The ZeroMQ API provides sockets (a kind of generalisation over the traditional IP and Unix domain sockets), each of which can represent a many-to-many connection between endpoints. Operating with a message-wise granularity, they require that a messaging pattern be used, and are particularly optimized for that kind of pattern. The key differences to conventional sockets are that, generally speaking, conventional sockets present a synchronous interface to either connection-oriented reliable byte streams or connection-less unreliable datagrams. In comparison, 0MQ sockets present an abstraction of an asynchronous message queue, with the exact queuing details depending on the socket type in use. Where conventional sockets transfer streams of bytes or discrete datagrams, 0MQ sockets transfer discrete messages. 0MQ sockets, being asynchronous, mean that the timings of the physical connection setup and teardown, reconnection and delivery are transparent to the user and organized by 0MQ itself. Further to this, messages may be queued in the event that a peer is unavailable to receive them. Conventional sockets allow only strict one-to-one (two peers), many-to-one (many clients, one server), or in some cases one-to-many (multicast) relationships. With the exception of ZMQ::PAIR, 0MQ sockets may be connected to multiple endpoints using connect(), while simultaneously accepting incoming connections from multiple endpoints bound to the socket using bind(), thus allowing many-to-many relationships. Programming with ZeroMQ ZeroMQ as a library works through sockets by following certain network communication patterns. It is designed to work asynchronously, and that is where the MQ suffix to its name comes - from queuing messages before sending them. ZeroMQ offers four different types of transport for communication. These are: In-Process (INPROC): Local (in-process) communication transport. Inter-Process (IPC): Local (inter-process) communication transport. TCP: Unicast communication transport using TCP. PGM: Multicast communication transport using PGM.
Page 8 of 71
DT080B
ZeroMQ Socket Types Conventional sockets allow only strict one-to-one (two peers), many-to-one (many clients, one server), or in some cases one-to-many (multicast) relationships. With the exception of ZMQ_PAIR, ZMQ sockets can be connected to multiple endpoints using zmq_connect(), while simultaneously accepting incoming connections from multiple endpoints bound to the socket using zmq_bind(), thus allowing many-to-many relationships ZeroMQ sockets being asynchronous means that the timings of the physical connection setup and tear down, reconnect and delivery, are transparent to the user and organized by ZeroMQ itself. Further, messages may be queued in the event that a peer is unavailable to receive them. ZeroMQ sockets are not thread safe. Applications MUST NOT use a socket from multiple threads except after migrating a socket from one thread to another with a "full fence" memory barrier. This ensures that all load and store operations prior to the fence will have been committed prior to any loads and stores issued following the fence ( iMatix Corporation, 2012).
Socket types: To recap briefly what ZeroMQ does, it routes and queues messages according to precise recipes called patterns. These patterns provide ZeroMQ's intelligence. ZeroMQ's patterns are hard-coded and are implemented by pairs of sockets with matching types. In other words, to understand ZeroMQ patterns you need to understand socket types and how they work together. The following present the socket types defined by ZeroMQ, grouped by the general messaging pattern, which is built from related socket types. The way these sockets work depend on the type of socket chosen and the flow of messages being sent depends on the chosen patterns.
Page 9 of 71
DT080B
The socket combinations that are valid for a connect-bind pair (either side can bind): PUB and SUB REQ and REP REQ and ROUTER DEALER and REP DEALER and ROUTER DEALER and DEALER ROUTER and ROUTER PUSH and PULL PAIR and PAIR
Creating and destroying sockets (zmq_socket(), zmq_close()). Configuring sockets by setting options on them and checking them if necessary (zmq_setsockopt(),zmq_getsockopt()). Plugging sockets into the network topology by creating ZeroMQ connections to and from them (zmq_bind(), zmq_connect()). Using the sockets to carry data by writing and receiving messages on them (zmq_send(),zmq_recv()).
Note: that sockets are always void pointers and messages are structures. So sockets are passed as such, in all functions that work with messages, like zmq_send() and zmq_recv(). Creating, destroying, and configuring sockets works as you would expect for any object. The ZMQ_HWM option sets the high water mark for the specified socket. The high water mark is a hard limit on the maximum number of outstanding messages ZeroMQ will queue in memory for any single peer that the specified socket is communicating with. If this limit has been reached the socket enters an exceptional state and depending on the socket type, ZeroMQ takes appropriate action such as blocking or dropping sent messages( iMatix Corporation, 2012).
Page 10 of 71
DT080B
A socket of type ZMQ_REQ is used by a client to send requests to and receive replies from a service. This socket type allows only an alternating sequence of zmq_send(request) and subsequent zmq_recv(reply) calls. Each request sent is round robined among all services, and each reply received is matched with the last issued request. When a ZMQ_REQ socket enters an exceptional state due to having reached the high water mark for all services, or if there are no services at all, then any zmq_send() operations on the socket shall block until the exceptional state ends or at least one service becomes available for sending; messages are not discarded. ( iMatix Corporation, 2012)
Table 1 ZMQ REQ Charachteristics
Compatible peer sockets Direction Send/receive pattern Outgoing routing strategy Incoming routing strategy
Block
Page 11 of 71
DT080B
A socket of type ZMQ_REP is used by a service to receive requests from and send replies to a client. This socket type allows only an alternating sequence of zmq_recv(request) and subsequent zmq_send(reply) calls. Each request received is fair-queued from among all clients, and each reply sent is routed to the client that issued the last request. If the original requester doesn't exist anymore the reply is silently discarded. When a ZMQ_REP socket enters an exceptional state due to having reached the high water mark for a client, then any replies sent to the client in question shall be dropped until the exceptional state ends.
Table 2 ZMQ REP Charachteristics
ZMQ_REQ Bidirectional Receive, Send, Receive, Send, Fair-queued Last peer Drop
Send/receive pattern
Incoming routing strategy Outgoing routing strategy ZMQ_HWM option action (iMatix Corporation, 2012)
Page 12 of 71
DT080B
A socket of type ZMQ_PUB is used by a publisher to distribute data. Messages sent are distributed in a fan out fashion to all connected peers. The zmq_recv() function is not implemented for this socket type. When a ZMQ_PUB socket enters an exceptional state due to having reached the high water mark for a subscriber, then any messages that are sent to the subscriber in question shall instead be dropped until the exceptional state ends. The zmq_send() function shall never block for this socket type.
Table 3 ZMQ PUB Charachteristics
Summary of ZMQ_PUB characteristics Compatible peer sockets Direction Send/receive pattern ZMQ_SUB Unidirectional Send only
Incoming routing strategy Outgoing routing strategy ZMQ_HWM option action (iMatix Corporation, 2012)
Page 13 of 71
DT080B
A socket of type ZMQ_SUB is used by a subscriber, to subscribe to data distributed by a publisher. Initially a ZMQ_SUB socket is not subscribed to any messages, use the ZMQ_SUBSCRIBE option of zmq_setsockopt() to specify which messages to subscribe to.
The zmq_send() function is not implemented for this socket type. Table 4 ZMQ SUB Charachteristics
Summary of ZMQ_SUB characteristics Compatible peer sockets Direction Send/receive pattern Incoming routing strategy ZMQ_PUB Unidirectional Receive only Fair-queued
N/A Drop
Page 14 of 71
DT080B
A socket of type ZMQ_PUSH is used by a pipeline node to send messages to downstream pipeline nodes. Messages are round-robined to all connected downstream nodes. The zmq_recv() function is not implemented for this socket type. When a ZMQ_PUSH socket enters an exceptional state due to having reached the high water mark for all downstream nodes, or if there are no downstream nodes at all, then any zmq_send() operations on the socket shall block until the exceptional state ends or at least one downstream node becomes available for sending; messages are not discarded. Deprecated alias: ZMQ_DOWNSTREAM.
Table 5 ZMQ PUSH Charachteristics
Compatible peer sockets Direction Send/receive pattern Incoming routing strategy Outgoing routing strategy
Block
Page 15 of 71
DT080B
A socket of type ZMQ_PULL is used by a pipeline node to receive messages from upstream pipeline nodes. Messages are fair-queued from among all connected upstream nodes. The zmq_send() function is not implemented for this socket type. Deprecated alias: ZMQ_UPSTREAM.
Table 6 ZMQ PULL Characteristics
Send/receive pattern Incoming routing strategy Outgoing routing strategy ZMQ_HWM option action ( iMatix Corporation, 2012)
Page 16 of 71
DT080B
A socket of type ZMQ_DEALER is an advanced pattern used for extending request/reply sockets. Each message sent is round-robined among all connected peers, and each message received is fair-queued from all connected peers. When a ZMQ_DEALER socket enters an exceptional state due to having reached the high water mark for all peers, or if there are no peers at all, then any zmq_send() operations on the socket shall block until the exceptional state ends or at least one peer becomes available for sending; messages are not discarded. When a ZMQ_DEALER socket is connected to a ZMQ_REP socket, each message sent must consist of an empty message part, the delimiter, followed by one or more body parts.
Table 7 ZMQ Dealer Characteristics
Summary of ZMQ_DEALER characteristics Compatible peer sockets Direction ZMQ_ROUTER, ZMQ_REQ, ZMQ_REP Bidirectional
Send/receive pattern Outgoing routing strategy Incoming routing strategy ZMQ_HWM option action ( iMatix Corporation, 2012)
Page 17 of 71
DT080B
A socket of type ZMQ_ROUTER is an advanced pattern used for extending request/reply sockets. When receiving messages the socket pre-appends a message part containing the identity of the originating peer to the message before passing it to the application. Messages received are fair-queued from among all connected peers. When sending messages a ZMQ_ROUTER removes the first part of the message and uses it to determine the identity of the peer that the message shall be routed to. If the peer does not exist anymore, the message is silently discarded. When a ZMQ_ROUTER socket enters an exceptional state of the high water mark for all peers, or if there are no peers at all, then any messages sent to the socket shall be dropped until the exceptional state ends. Likewise, any messages routed to a non-existent peer or a peer for which the individual high water mark has been reached shall also be dropped. When a ZMQ_REQ socket is connected to a ZMQ_ROUTER socket, in addition to the identity of the originating peer, each message received shall contain an empty delimiter message part. Hence, the entire structure of each received message as seen by the application becomes: one or more identity parts, delimiter part, one or more body parts, all replies to a ZMQ_REQ socket must include the delimiter part.
Table 8 ZMQ Router Characteristics
Direction Send/receive pattern Outgoing routing strategy Incoming routing strategy ZMQ_HWM option action
Page 18 of 71
DT080B
(Hintjens, March 2013) In the above example the CLIENT sends the message Hello. The SERVER sends a World back The REQ socket can send a message, but must not send anything else until it gets a response or ZMQ will error. Likewise a REP socket cannot send a message unless it first receives one.
Page 19 of 71
DT080B
# Server import zmq context = zmq.Context() # Socket to talk to server print("Connecting to hello world server") socket = context.socket(zmq.REP) socket.bind("tcp://*:5555") while True: print("waiting for client request") msg = socket.recv() print("Message received %s % msg)message = socket.send("World )
Figure 3 REP-REQ Server Python Code
# Client import zmq context = zmq.Context() print("Connecting to hello world server") socket = context.socket(zmq.REQ) socket.connect("tcp://localhost:5555") for request in range(10): print("Sending request % s " % request) socket.send("Hello") # Get the reply. message = socket.recv() print("Received reply % s [ % s ]" %(request, message))
Page 20 of 71
DT080B
The Publish-Subscribe Pattern is used for one-to-many distribution of data from a single publisher to multiple subscribers in a fan out fashion.
Figure 4 Publish Subscribe
(Hintjens, March 2013) Some points about the Publish-Subscribe (PUB-SUB) pattern:
A subscriber can connect to more than one publisher using one connect call each time. Data will then arrive and be interleaved, "fair-queued", so that no single publisher drowns out the others.
If a publisher has no connected subscribers then it will simply drop all messages. If using TCP and a subscriber is slow, messages will queue up on the publisher. Publishers are protected against this using the "high-water mark" in sockets.
Page 21 of 71
DT080B
import zmq from random import randint context = zmq.Context() socket = context.socket(zmq.PUB) socket.bind("tcp://*:5556") while True: zipcode = randint(1, 10) temperature = randint(-20, 50) humidity = randint(10, 15) print Publish data:,(zipcode,temperature, humidity) socket.send("% d % d % d" % (zipcode,temperature, relhumidity))
Figure 6 PUB-SUB Subscriber Python Code
import zmq import sys context = zmq.Context() socket = context.socket(zmq.SUB) socket.connect("tcp://localhost:5556") zip_filter = sys.argv[1] if len(sys.argv) > 1 else print Collectin updates from weather service%s % zip_filter socket.setsockopt(zmq.SUBSCRIBE, zip_filter) #process 5 records for record in range(5): data = socket.recv()
Page 22 of 71
DT080B
The Pipeline Pattern is used for distributing data to nodes arranged in a pipeline. Data always flows down the pipeline and each stage of the pipeline is connected to at least one node. When a pipeline stage is connected to multiple nodes data is round-robined among all connected nodes.
Figure 7 parallel pipeline
(Hintjens, March 2013) The workers connect upstream to the ventilator and downstream to the sink. This means you can add workers arbitrarily. If the workers are bound to their endpoints, you would need (a) more endpoints and (b) to modify the ventilator and/or the sink each time you added a worker. We say that the ventilator and sink are stable parts of our architecture and the workers are dynamic parts of it. We have to synchronize the start of the batch with all workers being up and running. This is a fairly common gotcha in ZeroMQ and there is no easy solution. The ZMQ_connect Page 23 of 71
DT080B
method takes a certain amount of time. So when a set of workers connect to the ventilator, the first one to successfully connect will get a whole load of messages in that short time while the others are also connecting. If you don't synchronize the start of the batch somehow, the system won't run in parallel at all. Try removing the wait in the ventilator, and see what happens. The ventilator's PUSH socket distributes tasks to workers evenly, assuming they are all connected before the batch starts going out. This is called load balancing and it's something that will be addressed below in more detail. The sink's PULL socket collects results from workers evenly. This is called fair-queuing.
Page 24 of 71
DT080B
# Task ventilator # Binds PUSH socket to tcp://localhost:5557 # Sends batch of tasks to workers via that socket # # Author: Lev Givon <lev(at)columbia(dot)edu> import zmq import random import time try: raw_input except NameError: # Python 3 raw_input = input context = zmq.Context() # Socket to send messages on sender = context.socket(zmq.PUSH) sender.bind("tcp://*:5557") # Socket with direct access to the sink: used to syncronize start of batch sink = context.socket(zmq.PUSH) sink.connect("tcp://localhost:5558") print("Press Enter when the workers are ready: ") _ = raw_input() print("Sending tasks to workers") # The first message is "0" and signals start of batch sink.send(b'0') # Initialize random number generator random.seed() # Send 100 tasks total_msec = 0 for task_nbr in range(100): # Random workload from 1 to 100 msecs workload = random.randint(1, 100) total_msec += workload sender.send_string(u'%i' % workload) print("Total expected cost: %s msec" % total_msec) # Give 0MQ time to deliver time.sleep(1)
( iMatix Corporation, 2012)
Page 25 of 71
DT080B
# Task worker # Connects PULL socket to tcp://localhost:5557 # Collects workloads from ventilator via that socket # Connects PUSH socket to tcp://localhost:5558 # Sends results to sink via that socket # # Author: Lev Givon <lev(at)columbia(dot)edu> import sys import time import zmq context = zmq.Context() # Socket to receive messages on receiver = context.socket(zmq.PULL) receiver.connect("tcp://localhost:5557") # Socket to send messages to sender = context.socket(zmq.PUSH) sender.connect("tcp://localhost:5558") # Process tasks forever while True: s = receiver.recv() # Simple progress indicator for the viewer sys.stdout.write('.') sys.stdout.flush() # Do the work time.sleep(int(s)*0.001) # Send results to sink sender.send(b'')
( iMatix Corporation, 2012)
Page 26 of 71
DT080B
Page 27 of 71
DT080B
(Hintjens, March 2013) In the above example, the CLIENT sends a message to ROUTER and waits for a response. The ROUTER then processes message and sends to some Worker via the DEALER. The DEALER sends a message to Worker and waits for one response. The WORKER processes the message and sends a response back. The DEALER gets the message from WORKER, sends along to ROUTER. The ROUTER gets message and sends back to CLIENT, and the CLIENT does something with it!
Page 28 of 71
DT080B
On the Raspberry PI the a program was written to monitor the pin on which the motion sensor was connected when this pin goes high a Publish Function is called, This Publish function enacts a Zmq context, binds to the server address and sends a message . It waits or listens for a reply. When the reply is received the system returns to the monitoring state. Highlighted in Figure 13 below is the code for the main motion detector monitoring function and in Figure 14 is the code for the publish function.
Page 29 of 71
DT080B
Page 30 of 71
DT080B
Page 31 of 71
DT080B
# error handling except TwythonError as e: print e This was tested first on in Linux then on the Raspberry Pi before being implemented by the motion sensor. The results are below in Figure 18 Page 32 of 71
DT080B
Page 33 of 71
DT080B
(Kalinsky, 2002) SPI specifies four signals: clock (SCLK); master data output, slave data input (MOSI); master data input, slave data output (MISO); and slave select (SS),Figure 19 shows these signals in a single-slave configuration. SCLK is generated by the master and input to all slaves. MOSI carries data from master to slave. MISO carries data from slave back to master. A slave device is selected when the master asserts its CSS
In addition to these wires we have n wires for n slave devices on the bus. Each one of these wires carries the these wires carries the chip select signal (S S or CS ) for its respective device. Only one slave device can have its slave device can have its chip select signal asserted by the master controller at a time. These relationships are These relationships are illustrated in
Page 34 of 71
DT080B
(Kalinsky, 2002) The operation of the SPI bus is conceptually simple. Both the master controller and each slave device contain a shift register. When the chip select signal of a slave device is asserted (usually by being pulled low), the MISO wires are used to connect its shift register with that of the master device. Clock pulses are then generated by the master device, to shift data between the two shift registers enabling communication. In this sense the read and write operation are combined. For example, by shifting the contents of the master device shift register to that of the slave device, we are also shifting the data in the slave device shift register to that of the master.
Figure 21 Shifting from Master to Slave
Page 35 of 71
DT080B
Finally, there are 4 different SPI modes that can be used. Each mode defines a particular clock phase (CPHA) and polarity (CPOL) with respect to the data. In this project SPI mode 0 which is also known as mode (0,0) or mode (CPHA=0,CPOL=0) was used. (Al-Hertani., 2013). The Linux Kernel in recent Raspberry Pi Releases supports the SPI as a native device so doesnt require bit-banging. However, as its disabled by default it needs to load the module before we can use the SPI device. Additionally the permissions and/or ownerships of the files in /dev/ need to be changed so that they can be accessed from out programs without needing to be root or run them with sudo. The MCP3008 chip is an SPI based analogue to digital converter (ADC). It has 8 analogue input channels that can be configured for single ended and differential ADC conversions. The MCP3008 is a 10-bit ADC that can convert 200 kilo samples per second (200ksps).
Figure 22 MCP3008 Pin Out
(Microchip, n.d.) As shown in Figure 23 the VDD pin was connected to the 3.3V power source from the Raspberry PI. The AGND (Analog ground) and DGND (Digitalground) pins were connected directly to ground as a reference point. The VREF pin is the reference voltage, which is the largest possible voltage that the ADC can interpret. The largest voltage the Raspberry pi can accept is 3.3V so the VREF pin was connected to 3.3V. So if 3.3V is Page 36 of 71
DT080B
sampled on any of the ADCs channels it would be interpreted as the maximum digital value that can be represented by this 10-bit ADC (i.e. 2). Similarly, the
smallest analogue voltage that the ADC can detect (also known as the LSB size) is , which in this case is , represents a digital value of 1. The
equation that converts between the analogue voltage and its digital interpretation is given by: where VIN is the analogue input voltage
To check the setup and implementation of the sensors, a convert temps, convert volts and read channel functions were added to the client as seen in Figure 24 , and the code in
Figure 25 was added to the main program. Figure 24 Analogue Sensor code
# Analogue Sensor Input ###################################################### #resets all ports back to input mode GPIO.cleanup() # Open SPI bus spi = spidev.SpiDev() spi.open(0,0) # Use BCM GPIO references instead of physical pin numbers # referring to the pins by the "Broadcom SOC channel" number GPIO.setmode(GPIO.BCM) # Function to read SPI data from MCP3008 chip # Channel must be an integer 0-7(8 total inputs) def ReadChannel(channel): adc = spi.xfer2([1,(8+channel)<<4,0]) data = ((adc[1]&3) << 8) + adc[2]
Page 37 of 71
DT080B
Page 38 of 71
DT080B
Page 39 of 71
DT080B
The addition of pictures to the message proved a challenge. The message is sent as a string. The text is already a string, and converting temperature and light from integers to strings are quite simple. Adding the image was difficult but a solution was found in converting every pixel of the image into a hexadecimal word and the base 64 library. The base 64 algorithms is used for encoding and decoding arbitrary binary strings into text strings .This enabled the system to have 4 separate text strings: a text string of the image, a standard text string, temperature string, and light levels string. The next problem was how to combine all these strings and then separate them on the other side. To combine strings In Python you simply add them, for example string1 + string 2 = new string. But in order to separate them a unique character is required to split. After some research it was found that : is unique . Thus, to combine the strings into a separable message became msg = text +":"+ image +":"+light+":"+temp: and to split them the message is split at (":"). Thus text, image, light, temp = message.split(":"), this splits the
Page 40 of 71
DT080B
message at : and puts each element into its relative variable. For example, the received message is some text: an Image :Light level: a Temperature : . The image is then re-encoded into an image from a string using base64.The variables and image are used for decision-making, and are passed into the various functions.
Page 41 of 71
DT080B
Page 42 of 71
DT080B
Figure 30. To implement this, the following code was added to assign a BCM pin and a pin
mode:
Button = 25 satus = 0 print "PIR Module Test (CTRL-C to exit)" # Set pin as input GPIO.setup(PIR,GPIO.IN) #Set the button to input GPIO.setup(Button,GPIO.IN)
And the following was added to the If-Else output string in the client.
if GPIO.input(Button)==1: print "button pushed" Publish("Button")
Page 43 of 71
DT080B
Page 44 of 71
DT080B
Page 45 of 71
DT080B
Page 46 of 71
DT080B
The client code was updated to activate each LED, as its message arrives which can be seen in Figure 36.
Figure 36 LED Output code client
socket.send(msg) # Get the reply message = socket.recv() print ("Received Reply ", message) #copy message to X X = message #If Else String for returned value from server if X == "Detected": print "Turning on Red light " # Define GPIO pin to use on Pi GPIO_RLED = 17 print "Led Module Test (CTRL-C to exit)"
Page 47 of 71
DT080B
to exit)"
"
to exit)"
to exit)"
Page 48 of 71
DT080B
Page 49 of 71
DT080B
server = smtplib.SMTP() #connect to smtp host server.connect( host,port) #start Transport Layer Security server.starttls() #Login to mail server server.login(username,password) #Sbject Field sub = ('Subject: Update From PI ') #Mimemultipart allows Multiple attachments Divides the message into parts, Each part has headers including the type of data, filename, encoding used msg = email.MIMEMultipart.MIMEMultipart() msg['From'] = fromaddr msg['To'] = toaddrs msg['Subject'] = sub msg.attach(MIMEText(text)) msg.attach(MIMEText('\nsent via python From PI Server', 'plain')) # 'plain ' is a plain text type #file import file as read only in bytewise process f = open(filename,'rb') #ctypy provides C compatible data types, and allows calling functions in DLLs or shared libraries. #load file and guess what it is. ctype, encoding = mimetypes.guess_type(filename) if ctype is None or encoding is not None: ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) if maintype == 'text': part = MIMEText(f.read(), _subtype=subtype) elif maintype == 'image': part = MIMEImage(f.read(), _subtype=subtype) elif maintype == 'audio': part = MIMEAudio(f.read(), _subtype=subtype) else: part = MIMEBase(maintype, subtype) msg.set_payload(f.read()) #attach the file part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(filename)) msg.attach(part) #close the file f.close() #send the mail server.sendmail(username,tolist,msg.as_string()) #Quit server server.quit() # code refrence http://docs.python.org/2/library/email-examples.html return
Page 50 of 71
DT080B
Page 51 of 71
DT080B
Page 52 of 71
DT080B
#Function text taking in text , temp and light variables def Text (Text,temp,light): #This was originally meant to be an mms message service but mms is un available in ireland from this service. #There is no Python api for txtlocal(webbased sms service) this is adapted from a pHp version #set the localtime variable localtime = time.asctime( time.localtime(time.time()) ) #Set The message variable"str = The alert plus Time message = Text+(localtime)+"\nCurrent Conditions"+"\nTemp is =\t"+temp+"C"+"\nLight Level is =\t"+light+" lumina" # Set username and sender name. # Sender name between 3 and 11 characters in length username = "almat1981@gmail.com" sender = "Pi Project" # unique hash Gotten from textlocal Php program hash = "e373cafb7f4919f3b4f729781b2e7bb17e42cdc2" # Set the phone number you wish to send the # Multiple numbers separated by comma numbers = ("353872924405") message to.
# Set flag to 1 to simulate for testing # To send real message set this flag to 0 test_flag = 0 #PHP arrays are ordered mappings, so used a Python OrderedDict instead of a regular dict so that the order of insertion is preserved values = {"test" : test_flag, "uname" : username, "hash" : hash, "message" : message, "from" : sender, "selectednums" : numbers } #url texts are sent from url = "http://www.txtlocal.com/sendsmspost.php" #Convert a mapping object or a sequence of two-element tuples to a percentencoded string, suitable to pass to urlopen() postdata = urllib.urlencode(values) #Open the URL url, which is the Request object Postdata. req = urllib2.Request(url, postdata) #Try to send print "Attempt to send SMS ..." try: #Open the URL response = urllib2.urlopen(req) response_url = response.geturl() if response_url==url: print "SMS sent!" except urllib2.URLError, e: print "Send failed!" print e.reason return
Page 53 of 71
DT080B
Page 54 of 71
DT080B
Page 55 of 71
DT080B
Chapter 16 Discussion
ZeroMQ is not a message broker. It is often mistaken for one because of its name. What ZeroMQ is, is a library that supports certain network communication patterns using sockets. The "MQ" part comes in because ZeroMQ uses queues internally to buffer messages so that you do not block your application when sending data. When you say socket.send(...), ZeroMQ actually enqueues a message to be sent later by a dedicated communication thread. This communication thread and its state are encapsulated in the ZeroMQ context object. The ZeroMq system is an excellent high speed messaging library upon which to build your own broker. It is aimed at high volume speed critical applications where cost and saleability are paramount. As such it was probably a bad choice for a domestic monitoring system but would be a very good choice if one decides to design a program such as the next Twitter, for example. Despite this, even using it in this context could have been improved. In the client server REP REQ pattern, if the server and client had been swapped it would have allowed for greater flexibility. Currently the system detects motion (on the PI/Client), sends a message to the server and waits for response, blocking all ports. Whereas if swapped, upon power up a ready message would be sent to the PI (now server), and when the PI detects motion it sends the response to the external system (now Client), this then uses that message to do work (update the communications suite). 5. Ideally however, a dealer router pattern as seen in Dealer Router Multithreading
Figure 11 could have been used. This would have allowed full proper multi-threading inside
the system. In addition, I believe that it would have allowed much better performance and possibly for ZMQ to handle the social media aspect as well as just the machine to machine messaging. This however would have required writing the APIs from scratch. The python programing language is simple and powerful. It allows to be done is a few lines of code what would take hundreds of lines in C++ for example. The programs themselves are tiny in size compared to what the equivalent C++, or Java program would be. Both programs together are less than 20kb. This makes it ideal for programming in space sensitive areas.
Page 56 of 71
DT080B
Conclusion
This project fulfils all the sections of the initial brief. It uses the Raspberry Pi as a monitoring system and uses Zmq to send a message to the server. The server controls what output will go to the Pi and through that server the system interacts with social media. As with any project of this type it is never complete, it can always be improved or refined but given the time constraints it performs well. As a learning experience, this project has been excellent. It allowed me to cover a lot of ground from learning about the Python programming language, message oriented
middleware, low end of the communication protocols, and the interaction of social media APIs.
Page 57 of 71
DT080B
Bibliography
iMatix Available [Accessed 7 03 2014]. Al-Hertani., Available at: H., 2013. http://hertaville.com/. [Online] Corporation, 2012. at: MQ API Reference. [Online] http://api.zeromq.org/
http://hertaville.com/2013/07/24/interfacing-an-spi-adc-mcp3008-chip-to-the-
raspberry-pi-using-c/ [Accessed 07 01 2014]. Amy Brown, G. W., 08 May 2012. The Architecture of Open Source Applications, Volume II. 1st ed. California: Creative Commons: Attribution. Anon., n.d. [Online]
http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-
http://www.igvita.com/2010/09/03/zeromq-modern-fast-networking-stack/
[Accessed 21 01 2014]. halherta, Available at: July 24, 2013 . http://hertaville.com/. [Online]
http://hertaville.com/2013/07/24/interfacing-an-spi-adc-mcp3008-chip-to-the-
raspberry-pi-using-c/ [Accessed 07 03 2014]. Hintjens, P., March 2013. ZeroMQ Messaging for Many Applications. 1st ed. Sebastopol, CA: O'Reilly Media. Kalinsky, Available D. K. at: a. R., 2002. embedded.com. [Online]
http://www.embedded.com/electronics-blogs/beginner-s-
Page 58 of 71
DT080B
Scherfke, S., 2012. Designing and Testing PyZMQ Applications, Oldenburg, Germany: OFFIS Institute for Information Technology Oldenburg, Germany. Tezer, Available at: O., n.d. [Online]
https://www.digitalocean.com/community/articles/how-to-work-with-the-
zeromq-messaging- library
Appendices
MCP3008 DataSheet http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf TM36 Datasheet: http://www.analog.com/static/imported-files/data_sheets/TMP35_36_37.pdf
Figure 1Request Reply Pattern ............................................................................................ 19 Figure 2 REP-REQ Pattern Client Python Code ................................................................. 20 Figure 3 REP-REQ Server Python Code ............................................................................. 20 Figure 4 Publish SubScribe ................................................................................................. 21 Figure 5 PUB-SUB Publisher Python Code ........................................................................ 22 Figure 6 PUB-SUB Subscriber Python Code ...................................................................... 22 Figure 7 parallel pipeline ..................................................................................................... 23 Figure 8 Ventalator Code Python Example ......................................................................... 25 Figure 9 Worker Code In Python......................................................................................... 26 Figure 10 Sink Code Python Example................................................................................. 27 Figure 11 Dealer Router Multithreading: ............................................................................ 28 Figure 12 Motion sensor ...................................................................................................... 29 Figure 13 Client Code Main Program ................................................................................. 30 Page 59 of 71
DT080B
Figure 14 Client Code Publish Function ............................................................................ 31 Figure 15 Server Code ......................................................................................................... 31 Figure 16 Tweet Function Call ............................................................................................ 32 Figure 17 Twitter Function Code ........................................................................................ 32 Figure 18 Tweet Results ...................................................................................................... 33 Figure 19 Single Master Slave Implementation .................................................................. 34 Figure 20 Single master, multiple slave implementation .................................................... 35 Figure 21 .............................................................................................................................. 35 Figure 22 MCP3008 Pin Out ............................................................................................... 36 Figure 23 Setup of Analogue Sensors ................................................................................. 37 Figure 24 Analogue Sensor code ......................................................................................... 37 Figure 25 Code to Read from analogue sensors .................................................................. 38 Figure 26 Capture Pic Code................................................................................................. 39 Figure 27 client Code........................................................................................................... 40 Figure 28 Server code .......................................................................................................... 41 Figure 29 Results of Camera and analogue sensor addition ................................................ 41 Figure 30 Push button .......................................................................................................... 42 Figure 31 Push button results .............................................................................................. 43 Figure 32 All Sensors IF Else .............................................................................................. 43 Figure 33 Server If else all sensors:..................................................................................... 44 Figure 34 Publish function: ................................................................................................. 45 Figure 35 LED ..................................................................................................................... 46 Figure 36 LED Output code client....................................................................................... 46 Figure 37 Calling Email function ........................................................................................ 48 Figure 38 Email function ..................................................................................................... 48 Figure 39 Email Results: ..................................................................................................... 50 Page 60 of 71
DT080B
Figure 40 Text Function Code ............................................................................................ 52 Figure 41 Facebook Function .............................................................................................. 54 Figure 42 FaceBook Wall Post ............................................................................................ 54
Page 61 of 71
DT080B
temp = ((data * 330)/float(1023))-50 temp = round(temp,places) return temp # Define sensor channels light_channel = 0 temp_channel = 1 # Define delay between readings delay = 5 def CapturePic(): # Explicitly open a new file called my_image.jpg my_file = open('my_image.jpg', 'wb') with picamera.PiCamera() as camera: camera.start_preview() time.sleep(2) camera.capture(my_file) # Note that at this point the data is in the file cache, but may # not actually have been written to disk yet my_file.close() # Now the file has been closed, other processes should be able to # read the image successfully def Publish(Text): light_level = ReadChannel(light_channel) temp_level = ReadChannel(temp_channel)
Page 62 of 71
DT080B
Page 63 of 71
DT080B
while True : #Set satus to false satus = 0 #Get the time and set variable equal to it upDateTime = datetime.datetime.now() #UpDate on the hour and half hour,(by adding day this could be set to any time or day) if (upDateTime.minute ==00 or upDateTime.minute ==30) and (upDateTime.second ==30): #set satus to true satus = 1 # Read the light sensor data
Page 64 of 71
DT080B
# When The time is righ UpDate if satus==1: print "Time for Update" Publish("Satus Update") satus ==0 # Read PIR state if GPIO.input(PIR)==1: # PIR is triggered print " Motion detected!" Publish("Motion") # If button high (pushed) if GPIO.input(Button)==1: print "button pushed" Publish("Button") # When temp below approx 11 Degrees if temp_level <180: print "Brrr It's Cold" Publish("Cold") # When temp above approx 30 Degrees if temp_level >300: print "Dam Its hot" Publish("Hot") # From trial and error Dark Room if light_level>1000: print "Its Dark" Publish("Dark") # Wait for 10 milliseconds time.sleep(0.01) except KeyboardInterrupt: print " Quit" # Reset GPIO settings GPIO.cleanup() return if __name__ == "__main__": main()
Server Code:
#!/usr/bin.python # -*- coding: latin-1 -*# Author: Alan Matthews # PI Monitor server in Python # Binds REP socket to tcp://*:5555 # 02/03/14 # Added Exception handling # Version 0.1.3 ######################################################################### ###########################################
Page 65 of 71
DT080B
#For more information on amy modules please see http://docs.python.org/2.7/ #The ZeroMQ Liblary import zmq #JSON (JavaScript Object Notation)used as a lightweight data interchange format import json #provides data encoding and decoding import base64 #This module provides various time-related functions import time #This module provides access to variables used or maintained by the interpreter and functions that interact with the interpreter. import sys #This module defines an SMTP client session object that can be used to send mail to any Internet machine with an SMTP import smtplib #This module provides a way of using operating system functionality.e.g to read or write a file import os #The email module is a library for managing email messages, including MIME import email #Python's email package contains many classes and functions for composing and parsing email messages #by only importing only the classes we need, this saves us from having to use the full module name later. from email.MIMEMultipart import MIMEMultipart from email.Utils import COMMASPACE from email.MIMEBase import MIMEBase from email.parser import Parser from email.MIMEImage import MIMEImage from email.MIMEText import MIMEText from email.MIMEAudio import MIMEAudio #For guessing MIME type based on file name extension import mimetypes #twython is a pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs from twython import Twython, TwythonError #Facebook graph Api import facebook #This module provides a high-level interface for fetching data across the World Wide Web. # module defines functions and classes which help in opening URLs and breaking URL strings up into components import urllib,urllib2,urlparse ######################################################################### ########################################### # FACEBOOK WALL UPDATER ######################################################################### ########################################### # Variables ,Text,Temp, and light passed in from main (server ) program. def FaceBook(Text,temp,light): #Variable localtime ste to current time using time module localtime = time.asctime( time.localtime(time.time())) #Contents of post message=Text+(localtime)+"\nCurrent Conditions"+"\nTemp is =\t"+temp+"C"+"\nLight Level is =\t"+light+" lumina" #FaceBook Api Access Token and App ID
Page 66 of 71
DT080B
access_token='CAACEdEose0cBAMi7w6VtbzOO3DS2cQwK6MIc4Fx1CZBabqUJELzRT6NXAo CRrKZA9y7DpXzhQzVQmmqYeey7Te5U4mM3IG6pA10wOTanoxSr8JcxCzsstDDZByhBl279RoR He0Hn2hdxwI6XAMZBeKnmTo72wPZC6I8mD0bvaiguN4Mb0VabbEXdG4zn8A68ZD' FACEBOOK_APP_ID =100007647638324 #Create a FaceBook Graph object facebook_graph = facebook.GraphAPI(access_token) #Posting Text only To Wall To attach a pic must be registered as a developer. facebook_graph.put_wall_post(message,profile_id='me') print "Facebook Wall Updated" ######################################################################### ########################################### # Twitter Function ######################################################################### ########################################### def Tweet(Text,temp,light): #set the localtime variable localtime = time.asctime( time.localtime(time.time())) #Set The message variable"msg" = The alert plus localTime, + temp and light msg = Text+(localtime)+"\nCurrent Conditions"+"\nTemp is =\t"+temp+"C"+"\nLight Level is =\t"+light+" lumina" #Twitter Access Keys CONSUMER_KEY = 'RbwVNyNI25q3U1y2TKhdw' CONSUMER_SECRET = '09dK2OkqVuVo9I12mUzltm01gi4YtnTXSZfvLwpA4' ACCESS_KEY = '2317027770-HgSg4Q4Xn4uzDpZwKpABUz67QxFVb8ah57LB51D' ACCESS_SECRET = 'a8FiPxgpbBif93qaUQpa3w5zaq0bq6KizUkvV8SDnJZZn' #Use Twython api To sign in to twitter api = Twython(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_KEY,ACCESS_SECRET) #Open jpg image, "rb" is read only byte for photo = open('my_image.jpg', 'rb') try: #Update satus api.update_status_with_media(status=msg, media=photo) # error handling except TwythonError as e: print e ######################################################################### ########################################### # Text Message Service ######################################################################### ########################################### #Function text taking in text , temp and light variables def Text (Text,temp,light): #This was originaly ment to be an mms message service but mms is un avaiable in ireland from this service. #There is no Python api for txtlocal(webbased sms service) this is adapted from a pHp version #set the localtime variable localtime = time.asctime( time.localtime(time.time()) ) #Set The message variable"str = The alert plus Time message = Text+(localtime)+"\nCurrent Conditions"+"\nTemp is =\t"+temp+"C"+"\nLight Level is =\t"+light+" lumina" # Set username and sender name. # Sender name between 3 and 11 characters in length
Page 67 of 71
DT080B
# Set flag to 1 to simulate for testing # To send real message set this flag to 0 test_flag = 0 #PHP arrays are ordered mappings, so used a Python OrderedDict instead of a regular dict so that the order of insertion is preserved values = {"test" : test_flag, "uname" : username, "hash" : hash, "message" : message, "from" : sender, "selectednums" : numbers } #url texts are sent from url = "http://www.txtlocal.com/sendsmspost.php" #Convert a mapping object or a sequence of two-element tuples to a percent-encoded string, suitable to pass to urlopen() postdata = urllib.urlencode(values) #Open the URL url, which is the Request object Postdata. req = urllib2.Request(url, postdata) #Try to send print "Attempt to send SMS ..." try: #Open the URL response = urllib2.urlopen(req) response_url = response.geturl() if response_url==url: print "SMS sent!" except urllib2.URLError, e: print "Send failed!" print e.reason return ######################################################################### ########################################### # Email function ######################################################################### ########################################### #Pass the three variables into the email function def Email (Text,temp,light): #From Address Can be any address fromaddr = "alanspiproject2013@gmail.com" # Address To Must be a valid addresses tolist = "alanspiproject2013@gmail.com" # Message body text = Text+"\nCurrent Conditions"+"\nTemp is ="+temp+" Degrees C"+"\nLight Level is ="+light+" lumina" # Attachment location /path/to/file in this instance file is in same folder so name is sufficent filename = 'my_image.jpg' # Credentials username = "alanspiproject2013@gmail.com" password = "Al1981mat" #SMTP host address and port number
Page 68 of 71
DT080B
host = 'smtp.gmail.com' port = 587 # Create a stmp object clled server server = smtplib.SMTP() #connect to smtp host server.connect( host,port) #start Transport Layer Security server.starttls() #Login to mail server server.login(username,password) #Sbject Field sub = ('Subject: Update From PI ') #Mimemultipart allows Multiple attachments Divides the message into parts, Each part has headers including the type of data, filename, encoding used msg = email.MIMEMultipart.MIMEMultipart() msg['From'] = fromaddr msg['To'] = toaddrs msg['Subject'] = sub msg.attach(MIMEText(text)) msg.attach(MIMEText('\nsent via python From PI Server', 'plain')) # 'plain ' is a plain text type #file import file as read only in bytewise process f = open(filename,'rb') #ctypy provides C compatible data types, and allows calling functions in DLLs or shared libraries. #load file and guess what it is. ctype, encoding = mimetypes.guess_type(filename) if ctype is None or encoding is not None: ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) if maintype == 'text': part = MIMEText(f.read(), _subtype=subtype) elif maintype == 'image': part = MIMEImage(f.read(), _subtype=subtype) elif maintype == 'audio': part = MIMEAudio(f.read(), _subtype=subtype) else: part = MIMEBase(maintype, subtype) msg.set_payload(f.read()) #attach the file part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(filename)) msg.attach(part) #close the file f.close() #send the mail server.sendmail(username,tolist,msg.as_string()) #Quit server server.quit() # code refrence http://docs.python.org/2/library/email-examples.html return ######################################################################### ########################################### # Main server program ######################################################################### ########################################### def Server(): #start of ZmQ Server or main program
Page 69 of 71
DT080B
Page 70 of 71
DT080B
######################################################################### ########################################### # Main Program ######################################################################### ########################################### def main(): #run till control c print "Hit control-c to Quit" try: Server() except KeyboardInterrupt: print "You hit control-c Goodbye" if __name__ == "__main__": main()
Page 71 of 71