Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Larry DeatonDaniel CorleyJohn Burnham
Published © GPL3+

!wet -- An Automated Facilities Request System

Notify the facilities department when a bathroom runs out of paper towels, soap, etc. while you are still in the bathroom.

IntermediateFull instructions provided8 hours2,987

Things used in this project

Hardware components

Adafruit HUZZAH ESP8266 Breakout
Adafruit HUZZAH ESP8266 Breakout
You would need one of these per bathroom installation.
×1
TTL-232R-3V3 USB-to-Serial Cable
×1
Red Pushbutton
3 push buttons per request panel in each bathroom
×3
AA Battery Holder ( holds 4 batteries )
×1
Adafruit Tiny Breadboard
×1
Adafruit AA Battery - 4 pack
×1
Canakit Raspberry Pi 3 Complete Starter Kit - 32 GB Edition
We believe we could use one Raspberry Pi 3 per building since the communication is occurring on the WiFi network.
×1
Plastic Craft Box -- 5 2/3" x 3 5/8" x 1 1/8"
×1
LED (generic)
LED (generic)
We used 3 bicolor LEDs for this project.
×3

Software apps and online services

Mosquitto
NodeMCU firmware
NodeMCU firmware
Cayenne
myDevices Cayenne
Raspbian
Raspberry Pi Raspbian

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Generic Drill

Story

Read more

Schematics

notWet v1 Breadboard Schematic

Code

AmazonDashButtonSource.txt

JavaScript
Amazon Dash button Lambda function for sending an email based on the button press to the facilities request department.
'use strict';

/**
* This is a sample Lambda function that sends an Email on click of a
* button. It creates a SNS topic, subscribes an endpoint (EMAIL)
* to the topic and publishes to the topic.
*
* Follow these steps to complete the configuration of your function:
*
* 1. Update the email environment variable with your email address.
* 2. Enter a name for your execution role in the "Role name" field.
*    Your function's execution role needs specific permissions for SNS operations
*    to send an email. We have pre-selected the "AWS IoT Button permissions"
*    policy template that will automatically add these permissions.
*/

const AWS = require('aws-sdk');

const EMAIL = 'youremailhere@gmail.com';
const SNS = new AWS.SNS({ apiVersion: '2010-03-31' });


function findExistingSubscription(topicArn, nextToken, cb) {
    const params = {
        TopicArn: topicArn,
        NextToken: nextToken || null,
    };
    SNS.listSubscriptionsByTopic(params, (err, data) => {
        if (err) {
            console.log('Error listing subscriptions.', err);
            return cb(err);
        }
        const subscription = data.Subscriptions.filter((sub) => sub.Protocol === 'email' && sub.Endpoint === EMAIL)[0];
        if (!subscription) {
            if (!data.NextToken) {
                cb(null, null); // indicate that no subscription was found
            } else {
                findExistingSubscription(topicArn, data.NextToken, cb); // iterate over next token
            }
        } else {
            cb(null, subscription); // a subscription was found
        }
    });
}

/**
* Subscribe the specified EMAIL to a topic.
*/
function createSubscription(topicArn, cb) {
    // check to see if a subscription already exists
    findExistingSubscription(topicArn, null, (err, res) => {
        if (err) {
            console.log('Error finding existing subscription.', err);
            return cb(err);
        }
        if (!res) {
            // no subscription, create one
            const params = {
                Protocol: 'email',
                TopicArn: topicArn,
                Endpoint: EMAIL,
            };
            SNS.subscribe(params, (subscribeErr) => {
                if (subscribeErr) {
                    console.log('Error setting up email subscription.', subscribeErr);
                    return cb(subscribeErr);
                }
                // subscription complete
                console.log(`Subscribed ${EMAIL} to ${topicArn}.`);
                cb(null, topicArn);
            });
        } else {
            // subscription already exists, continue
            cb(null, topicArn);
        }
    });
}

/**
* Create a topic.
*/
function createTopic(topicName, cb) {
    SNS.createTopic({ Name: topicName }, (err, data) => {
        if (err) {
            console.log('Creating topic failed.', err);
            return cb(err);
        }
        const topicArn = data.TopicArn;
        console.log(`Created topic: ${topicArn}`);
        console.log('Creating subscriptions.');
        createSubscription(topicArn, (subscribeErr) => {
            if (subscribeErr) {
                return cb(subscribeErr);
            }
            // everything is good
            console.log('Topic setup complete.');
            cb(null, topicArn);
        });
    });
}

/**
* The following JSON template shows what is sent as the payload:
{
    "serialNumber": "GXXXXXXXXXXXXXXXXX",
    "batteryVoltage": "xxmV",
    "clickType": "SINGLE" | "DOUBLE" | "LONG"
}
*
* A "LONG" clickType is sent if the first press lasts longer than 1.5 seconds.
* "SINGLE" and "DOUBLE" clickType payloads are sent for short clicks.
*
* For more documentation, follow the link below.
* http://docs.aws.amazon.com/iot/latest/developerguide/iot-lambda-rule.html
*/
exports.handler = (event, context, callback) => {
    console.log('Received event:', event.clickType);
    var params;

    // create/get topic
    createTopic('aws-iot-button-sns-topic', (err, topicArn) => {
        if (err) {
            return callback(err);
        }
        console.log(`Publishing to topic ${topicArn}`);
        // publish message
        // define email incase if statements don't hit
        //     const params = {
        //    Message: `This is the default define`,
        //    Subject: `Hello from your IoT Button ${event.serialNumber}: ${event.clickType}`,
        //    TopicArn: topicArn,
        //    };
        
            params = {
            Message: `This is the default define.`,
            Subject: `Hello from your IoT Button ${event.serialNumber}: ${event.clickType}`,
            TopicArn: topicArn,
            };
        if (event.clickType == "SINGLE") {
            params = {
            Message: `There is a paper towel issue in the bathroom located in room ID:BR-C-404`,
            Subject: `Bathroom needs servicing room BR-C-404`,
            TopicArn: topicArn,
            };
        }
        else if (event.clickType == "DOUBLE") {
            params = {
            Message: `There is a soap issue in the bathroom located in room ID:BR-C-404`,
            Subject: `Bathroom needs servicing room BR-C-404`,
            TopicArn: topicArn,
            };
        }
        else if (event.clickType == "LONG"){
            params = {
            Message: `There is a plumbing issue in the bathroom located in room ID:BR-C-404`,
            Subject: `Bathroom needs servicing room BR-C-404`,
            TopicArn: topicArn,
            };
        }
        
        
        // result will go to function callback
        SNS.publish(params, callback);
    });
};

Raspberry Pi Source Code

Python
This code handles the reception of the button press event from the ESP8266, then creates the facilities request to the appropriate end user via a gmail account. Upon submitting the request, the code then polls the gmail account to determine when the request has been assigned and when it has been completed. Once completed, it notifies the ESP8266 board so that the indication LED can be changed.
#######################################################################
# facilities.py - Monitors a Mosquitto MQTT queue for facility events
# from an array of facility panels in bathrooms.  These panels consist 
# of a 3 button panel and an ESP8266 board to gain WIFI access and 
# send publish messages to the MQTT broker.  These
# panels are used to indicate issues in the bathrooms that need attention.
# Currently, these are known as:
#   Top Button = Paper Towels = Button 1 value
#   Middle Button = Soap Dispenser Issue = Button 2 value
#   Bottom Button = Plumbing Issue = Button 3 value
# 
# The hardware to do this is already developed (AdaFruit Huzzah
# ESP8266 with NodeCMU), along with the Lua software to run on
# the ESP8266. 
#
# This code is expecting messages from the Mosquitto MQTT broker
# which include a sensor name (room number) followed by 3 values for 
# the 3 buttons on each panel. An example of the contents would be:
#    "BR-C-404 1 0 1"
#  
# where a value of 1 indicates the button is currently pressed
# and a value of 0 indicates the button is not pressed.  These messages
# are published under the topic "/facilities".
#
# Upon receiving the button press event, this code then determines 
# the current state of the facilities request for this type of service
# request.  If no facilities request has been made, then an email is 
# generated to the facilities request email address via a 
# gmail account. After the request email has been made, future button 
# presses for the same service will not result in additional emails 
# to the facilities department. 
# 
# The facilities request department then assigns the issue internally 
# in their system, which generates an email back to the gmail account
# with a subject of 'Your Facilities Request #00047546 has been assigned'.
#
# Once the bathroom item is serviced, the facilities request department
# will mark the work order as completed, which generates an email back
# to the gmail account with the subject of "Your Facilities Request 
#  #00047546 is complete. Can you please complete our Survey?".  When this
# happens, this script then publishes back to the Mosquitto MQTT broker
# on the topic "/completed".  When the ESP8266 board receives this message,
# it resets the LED near the button.
# 
# In order to track the number of requests serviced, we created a 
# dashboard in the Cayenne myDevices tool.  Once the Cayenne software
# is loaded on the Raspberry Pi, it uses another MQTT broker to send and 
# receive messages with the dashboard.  This code then sends messages to 
# local Cayenne MQTT broker, which publishes them to the dashboard.  
# Each statistic that is tracked gets a unique channel number within
# the Cayenne MQTT server.  The channel numbers are as follows:
#     
#     Problem Item      Action Taken      MQTT Channel Number
#   ===========================================================
#     Paper Towels      Button Pressed         1
#     Soap Dispenser    Button Pressed         2
#     Plumbing Issue    Button Pressed         3
#     Paper Towels      Submitted Requests     4
#     Soap Dispenser    Submitted Requests     5
#     Plumbing Issue    Submitted Requests     6
#     Paper Towels      Completed Requests     7
#     Soap Dispenser    Completed Requests     8
#     Plumbing Issue    Completed Requests     9
#     Paper Towels      Reset Last Request     10
#     Soap Dispenser    Reset Last Request     11
#     Plumbing Issue    Reset Last Request     12
#
#  Pressing the Reset Last Request button on the dashboard sends a value 
#  of '1' to the MQTT broker on the Raspberry Pi on that particular channel.
#  This causes the Pi to reset the state of the current outstanding request for #  that particular problem.  This then allows the button to pressed again.
#
######################################################################
 
########################
# Libraries
########################
 
import os
import string
import paho.mqtt.client as mqtt
import Adafruit_IO
import time
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from gmail import Gmail
# Cayenne stuff
import cayenne.client
import time
 
########################
# Globals
########################
 
# -- Change these as needed for your installation --
 
localBroker = "xx.xx.xx.xx"      # Local MQTT broker ( IP Address of the Raspberry Pi )
localPort = 1883            # Local MQTT port
localUser = "mosquitto"         # Local MQTT user
localPass = "hackathon"         # Local MQTT password
localTopic = "/facilities"      # Local MQTT topic to monitor
publishTopic = "/completed"     # Used to publish complete messages to
localTimeOut = 120          # Local MQTT session timeout
# Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
MQTT_USERNAME  = "CAYENNE_USERNAME"
MQTT_PASSWORD  = "CAYENNE_PASSWORD"
MQTT_CLIENT_ID = "CAYENNE_CLIENT_ID"

# Gmail account to use for sending facilities requests and looking for responses.
sender = 'gmailaccount@gmail.com'
sender_password = 'gmailpassword'
# Receivers of the facilities request messages
receivers = 'facilitiesrequestemailaddress@yourcompany.com'
 
sensorList = {}             # List of sensor objects


###########################
#  Create a cayenne client 
###########################
cayenneClient = cayenne.client.CayenneMQTTClient()
 
########################
# Classes and Methods
########################
class sensor():
    def __init__(self):
        self.name = ""      # Name of sensor in MQTT ( Room Number in this case )
        self.humanName = "" # Human-meaningful name (e.g., "front door")
        self.buttonState = ["Not Pressed","Not Pressed","Not Pressed"]  # State of the object: unknown, Not Pressed, Pressed
        self.buttonName = ["Paper Towels", "Soap Dispenser", "Plumbing"]  # Names for each button
        self.facRequestState = ["Not Started","Not Started", "Not Started"]  # State of the facilities request: Not Started, Sent, Received, Completed
        self.requestID = [ 0, 0, 0 ]            # used to store the internal request ID assigned to this button press in the facilities request system
        self.statsButtonPresses = [0, 0, 0]     # stats to track button presses on each button
        self.statsRequestSent = [ 0, 0, 0]      # stats to track requests sent on each button
        self.statsRequestCompleted = [ 0, 0, 0] # stats to track completed reqeusts on each button


    def setButtonState(self, index, newstate):
        # Store the new state for this button index
        self.buttonState[index] = newstate
      
    def getButtonState(self, index):
        # get the button state for this button index
        return self.buttonState[index]

    def setFacRequestState(self, index, newState):
        # store the facilities request state for this button index
        self.facRequestState[index] = newState

    def getFacRequestState(self, index):
        # get the facilities request state for this button index
        return self.facRequestState[index]

    def setFacRequestID(self, index, id):
        # store the facilities request ID for this button index
        self.requestID[index] = id

    def getFacRequestID(self, index):
        # get the facilities request ID for this button index
        return self.requestID[index]
                
    def setname(self, newName, humanName):
        self.name = newName
        self.humanName = humanName

    def getname(self):
        return self.humanName
                        
    def getButtonName(self,index):
        # get the button name for this button index
        return self.buttonName[index]
         
    def checkButtonState(self, index, newState):
        # check the button state for this button index to see if it has changed
        if ("unknown" == self.buttonState[index]):
            self.buttonState[index] = newState
            return 0
        else:
            if (newState != self.buttonState[index]):
                # state has changed
                self.buttonState[index] = newState
                # if the button is now in the pressed state
                if ("Pressed" == self.buttonState[index]):
                    # increment our stats for this button
                    self.statsButtonPresses[index] +=1
                    ########################
                    # Cayenne Update
                    ########################
                    # Index is 0, 1, 2 for the different buttons
                    #  0 = Paper Towels
                    #  1 = Soap 
                    #  2 = Plumbing
                    # Channel Numbers for Button Presses are:
                    #  1 = Paper Towels
                    #  2 = Soap
                    #  3 = Plumbing
                    cayenneClient.virtualWrite(index+1, self.statsButtonPresses[index])
                    # return -1 for button press
                    return -1
                else:
                    # return 1 for button not pressed
                    return 1
        return 0

    def incrementStatsRequestSent(self, index ):
        # increment stats on request sent for this button index
        self.statsRequestSent[index] +=1
        ######################
        # Google Sheets Update
        ######################
        #sheet.update_cell(index+2,3,self.statsRequestSent[index])
        #
        ########################
        # Cayenne Update
        ########################
        # Index is 0, 1, 2 for the different buttons
        #  0 = Paper Towels
        #  1 = Soap 
        #  2 = Plumbing
        # Channel Numbers for StatsRequestSent are:
        #  4 = Paper Towels
        #  5 = Soap
        #  6 = Plumbing
        cayenneClient.virtualWrite(index+1+3, self.statsRequestSent[index])

    def incrementStatsRequestCompleted(self, index ):
        # increment stats on requests completed for this button index
        self.statsRequestCompleted[index] +=1
        ########################
        # Cayenne Update
        ########################
        # Index is 0, 1, 2 for the different buttons
        #  0 = Paper Towels
        #  1 = Soap 
        #  2 = Plumbing
        # Channel Numbers for StatsRequestcompleted are:
        #  7 = Paper Towels
        #  8 = Soap
        #  9 = Plumbing
        cayenneClient.virtualWrite(index+1+6, self.statsRequestCompleted[index])
    
class sensorList():
    def __init__(self):
        self.sensorList = {}

    def dumpSensorStats(self):
        #dump some stats
        for ids in self.sensorList:
            print "Sensor:"+ids
            # print the stats for each button associated with this sensor
            print "B1 Presses:"+str(self.sensorList[ids].statsButtonPresses[0])
            print "   ReqSent:"+str(self.sensorList[ids].statsRequestSent[0])
            print "   ReqComp:"+str(self.sensorList[ids].statsRequestCompleted[0])
            print "B2 Presses:"+str(self.sensorList[ids].statsButtonPresses[1])
            print "   ReqSent:"+str(self.sensorList[ids].statsRequestSent[1])
            print "   ReqComp:"+str(self.sensorList[ids].statsRequestCompleted[1])
            print "B3 Presses:"+str(self.sensorList[ids].statsButtonPresses[2])
            print "   ReqSent:"+str(self.sensorList[ids].statsRequestSent[2])
            print "   ReqComp:"+str(self.sensorList[ids].statsRequestCompleted[2])
            # send to Cayenne a periodic update on our stats since it doesn't seem to update sometimes
            for index in xrange(0, 3):
                cayenneClient.virtualWrite(index+1, self.sensorList[ids].statsButtonPresses[index])
                cayenneClient.virtualWrite(index+1+3, self.sensorList[ids].statsRequestSent[index])
                cayenneClient.virtualWrite(index+1+6, self.sensorList[ids].statsRequestCompleted[index])
                cayenneClient.virtualWrite(10,0)
                cayenneClient.virtualWrite(11,0)
                cayenneClient.virtualWrite(12,0)


    def addSensor(self, sensorName, humanName):
        # add a new sensor into the list
        self.sensorList[sensorName] = sensor()
        self.sensorList[sensorName].setname(sensorName, humanName)
 
    def getSensorName(self, sensorID):
        if sensorID in self.sensorList:
            #print 'Sensor Found'
            return self.sensorList[sensorID].getname()
        else:
            print 'Unknown sensor, adding new sensor'
            return "unknown"

    def sendEmail(self, sensorID, index):
        #send an email to facilities request
        msg = MIMEMultipart()
        msg['From'] = sender
        msg['To'] = receivers
        msg['Subject'] = "Bathroom needs servicing room "+sensorID
        body = 'There is a '+self.sensorList[sensorID].getButtonName(index)+" issue in the bathroom located in room ID:"+sensorID
        msg.attach(MIMEText(body,'plain'))
        email_text = msg.as_string()
        try:
            server = smtplib.SMTP_SSL('smtp.gmail.com',465)
            server.ehlo()
            server.login(sender,sender_password)
            server.sendmail(sender,receivers,email_text)
            server.close()
            self.sensorList[sensorID].incrementStatsRequestSent(index)
            print "Email has been sent"
            #update the facilities request state
            self.sensorList[sensorID].setFacRequestState(index,"Sent")
        except smtplib.SMTPException:
            print "Error: unable to send email"

    def processFacilitiesRequest(self, sensorID, index, rv, currentState):
        # if rv is -1, then a button has been pressed
        if (0 > rv ):
            # button pressed
            # checkt the state we are in
            if ("Not Started" == currentState):
                # first button press, generate an email to facilities for this item
                print "First Button press, sending facilities request for "+self.sensorList[sensorID].getButtonName(index)
                self.sendEmail(sensorID, index)
            else:
                # if we have already sent the request to facilities, just ignore the button
                if ("Sent" == currentState):
                    print "Already sent facilities request, ignoring"
                else:
                    # if we have received a response from facilities, ignore button presses
                    if ("Received" == currentState):
                        print "Received facilities request response, ignoring"
                    else:
                        if ("Completed" == currentState):
                            print "Request completed, ignoring"
                        else:
                            print "Uknown state"
                        
    def sensorState(self, sensorID, monitorState1, monitorState2, monitorState3):
        # Analyze the new states for each button for this sensor
        # check the first button
        rv1 = self.sensorList[sensorID].checkButtonState(0,monitorState1)
        if (0 != rv1):
            # State changed!
            if (0 > rv1):
                outBuf = "State Change: Button 1 Pressed "+sensorID
                print(outBuf)
            else:
                outBuf = "State Change: Button 1 Not Pressed "+sensorID
                print(outBuf)
            # get the current facilities request state for this button
            requestState = self.sensorList[sensorID].getFacRequestState(0)
            # process this button state change
            self.processFacilitiesRequest(sensorID,0,rv1,requestState)
 
        # check the second button ( same logic )
        rv2 = self.sensorList[sensorID].checkButtonState(1,monitorState2)
        if (0 != rv2):
            # State changed!
            if (0 > rv2):
                outBuf = "State Change: Button 2 Pressed "+sensorID
                print(outBuf)
            else:
                outBuf = "State Change: Button 2 Not Pressed "+sensorID
                print(outBuf)
            requestState = self.sensorList[sensorID].getFacRequestState(1)
            self.processFacilitiesRequest(sensorID,1,rv2,requestState)

        # check the third button 
        rv3 = self.sensorList[sensorID].checkButtonState(2,monitorState3)
        if (0 != rv3):
            # State changed!
            if (0 > rv3):
                outBuf = "State Change: Button 3 Pressed "+sensorID
                print(outBuf)
            else:
                outBuf = "State Change: Button 3 Not Pressed "+sensorID
                print(outBuf)
            requestState = self.sensorList[sensorID].getFacRequestState(2)
            self.processFacilitiesRequest(sensorID,2,rv3,requestState)

    def setFacRequestState(self, sensorID, index, newState, publisher):
        # set the request state for this sensor and this button index
        self.sensorList[sensorID].setFacRequestState(index,newState)
        #if new state is completed, then publish back to the ESP8266
        #so he can turn off the flashing LED
        if (newState == "Completed"):
            publisher.publish(publishTopic,payload=sensorID+" "+str(index+1))
            # set to Not Started state so we can start looking for 
            # button presses again
            self.sensorList[sensorID].setFacRequestState(index,"Not Started")
            # increment our completed requests stats for this button index
            self.sensorList[sensorID].incrementStatsRequestCompleted(index)
            # Reset the button state for this 
            self.sensorList[sensorID].setButtonState(index,"Not Pressed")
                
                
    def setFacRequestID(self, sensorID, index, requestNo):
        # just a wrapper fucntion to set the request ID on the sensor object
        self.sensorList[sensorID].setFacRequestID(index, requestNo)

    def getFacRequestID(self, sensorID, index):
        # just a wrapper fucntion to get the request ID on the sensor object
        return self.sensorList[sensorID].getFacRequestID(index)
 
########################
# Functions
########################
 

# Return state string for button press value 
def returnState(state):
    actualState = string.atoi(state)
    if (0 == actualState):
        return "Not Pressed"
    if (1 == actualState):
        return "Pressed"
    else:
        return "unknown"
 
########################
# Main
########################
 
if "__main__" == __name__:
    # Set timer
 
    sensList = sensorList()
    pubMan = mqtt.Client("pi")  # Publisher client for sending the completed messages
    #sensList.addSensor("BR-C-404", "Mens room")

    def on_publish( client, userdata, result):
                 print("Data published")
                 
    # The callback for when the client receives a CONNACK response from the server.
    def on_connect(client, userdata, flags, rc):
        print("Connected with result code "+str(rc))
 
        # Subscribing in on_connect() means that if we lose the connection and
        # reconnect then subscriptions will be renewed.
        client.subscribe("/facilities")
 
    # The callback for when a PUBLISH message is received from the server.
    # This is the message coming from the ESP8266 when a button is pressed
    def on_message(client, userdata, msg):
        # decode the message
        (sensorID, button1State, button2State, button3State) = string.split(msg.payload)
        # lookup the sensor name to see if we know about this sensor ( room number )
        sensorName = sensList.getSensorName(sensorID)
        if ("unknown" == sensorName):
            # sensor not found, so add it into our list of sensors
            sensList.addSensor(sensorID,"test")
        # Get the string value for each value of the button presses
        button1String = returnState(button1State)
        button2String = returnState(button2State)
        button3String = returnState(button3State)
        # Evaluate the sensor state for each button press state
        sensList.sensorState(sensorID, button1String,button2String,button3String)
        # print some debug
        print(sensorName+" ["+sensorID+"] b1="+button1String+" b2="+button2String+" b3="+button3String)

    ########################
    # Cayenne Callback function
    ########################
    # The callback for when a message is received from Cayenne.
    def cayenne_on_message(message):
        print("message received: " + str(message))
        if (message.channel == 10):
            print("resetting the paper towel state machine")
            # set to Not Started state so we can start looking for 
            # button presses again
            index = 0
            sensorID = "BR-C-404"
            sensList.setFacRequestState(sensorID, index,"Completed",pubMan)
            # reset the cayenne pushbutton back to 0 so it will send a '1' again
            cayenneClient.virtualWrite(10,0)
        if (message.channel == 11):
            print("resetting the soap dispenser state machine")
            # set to Not Started state so we can start looking for 
            # button presses again
            index = 1
            sensorID = "BR-C-404"
            sensList.setFacRequestState(sensorID, index,"Completed",pubMan)
            # reset the cayenne pushbutton back to 0 so it will send a '1' again
            cayenneClient.virtualWrite(11,0)
        if (message.channel == 12):
            print("resetting the plumbing state machine")
            # set to Not Started state so we can start looking for 
            # button presses again
            index = 2
            sensorID = "BR-C-404"
            sensList.setFacRequestState(sensorID, index,"Completed",pubMan)
            # reset the cayenne pushbutton back to 0 so it will send a '1' again
            cayenneClient.virtualWrite(12,0)
    def checkEmail():
        # This function checks gmail for new unread messages from the facilities request department
        # If these messages have the correct subject lines, these are parsed to determine the state
        # of the facilities request
        g = Gmail()
        try:
            # login to gmail
            g.login(sender,sender_password)
        except:
            print 'Could not log into Gmail'
            return
        # get messages that are unread and from facilities request
        # NOTE: sometimes this errors out due to hostname lookup
        unread = g.inbox().mail(unread=True, sender=receivers)
                
        for email in unread:
            print 'Got an email'
            email.read()
            email.fetch()
            print 'Subject:' + email.subject

            # look at the subject to see if we care about this one
            if (email.subject.find("has been assigned") != -1):
                print "Found assigned email response -- new work order"
                # new work order created
                # extract the facilities request number from the subject line
                # To do this:
                #    1) get second part of string after "Your Facilities Request"
                #    2) Get first part before the first space ( should be "#83838383" after this )
                #    3) Look for "#" character
                #    4) Get the rest of the digits after "#" character
                #    5) convert it to an integer
                requestNoString = (email.subject.split("Your Facilities Request",1)[1]).lstrip()
                requestNoString = requestNoString.split(" ",1)[0]
                print "requestNoString"+requestNoString
                if (requestNoString[0] == "#"):
                    print "Request Number string is:"+requestNoString[1:]
                    requestNo = int(requestNoString[1:])
                else:
                    requestNo = 0
                # Get the sensorID from the body of the email
                # To do this:
                #    1) Look for the "room ID:" string and get anything after it
                #    2) Get anything from before the next carriage return / line feed
                #    3) strip any leading or trailing spaces
                #    4) get the first 8 characters ( assumes name format of "BR-C-404" )
                sensorID = email.body.split("room ID:",1)[1]
                sensorID = sensorID.split("\r\n",1)[0]
                sensorID = sensorID.lstrip()
                sensorID = sensorID[0:8]
                print "Sensor ID from return email is :"+sensorID
                # Look in the body to determine which button this request pertains to
                if (email.body.find("Paper Towels") != -1):
                    index = 0
                    print("Paper Towels issue")
                if (email.body.find("Soap Dispenser") != -1):
                    index = 1
                    print("Soap Dispenser issue")
                if (email.body.find("Plumbing") != -1):
                    index = 2
                    print("Plumbing issue")
                # See if we know about this sensor
                sensorName = sensList.getSensorName(sensorID)
                if ("unknown" == sensorName):
                    sensList.addSensor(sensorID,"test")

                # change the state of the facilities request
                sensList.setFacRequestState(sensorID, index,"Received", pubMan)
                sensList.setFacRequestID(sensorID, index,requestNo)                                                         
            else:
                if (email.subject.find("is complete") != -1):
                    print "Found complete email response -- completed work order"
                    # work order has been completed
                    # make sure the request number matches
                    sensorID = email.body.split("room ID:",1)[1]
                    sensorID = sensorID.split("\r\n",1)[0]
                    sensorID = sensorID.lstrip()
                    sensorID = sensorID[0:8]
                    print "Sensor ID from return email is :"+sensorID
                    if (email.body.find("Paper Towels") != -1):
                        index = 0
                        print("Paper Towels issue")
                    if (email.body.find("Soap Dispenser") != -1):
                        index = 1
                        print("Soap Dispenser issue")
                    if (email.body.find("Plumbing") != -1):
                        index = 2
                        print("Plumbing issue")
                    sensorName = sensList.getSensorName(sensorID)
                    if ("unknown" == sensorName):
                        sensList.addSensor(sensorID, "test")
                    # change the state of the facilities request
                    sensList.setFacRequestState(sensorID, index,"Completed",pubMan)
                    print "Completed work order #"+str(sensList.getFacRequestID(sensorID, index))
                else:
                    print "Uknown email contents"
        # logout of gmail each time we check for new emails
        g.logout()
                
    # ************************
    # Main Function Here
    # ************************

    cayenneClient.on_message = cayenne_on_message
    cayenneClient.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID)
    

    
    # setup the mqtt client that listens for messages from the MQTT broker
    client = mqtt.Client()
        # setup callbacks for the client
    client.on_connect = on_connect
    client.on_message = on_message
    
    # setup callbacks for the publisher ( messages to the ESP8266 )
    pubMan.on_publish = on_publish
    # connect to the MQTT broker as a publisher
    pubMan.connect(localBroker,localPort, localTimeOut)
    # connect to the MQTT broker as a client
    client.connect(localBroker, localPort, localTimeOut)

    

    loop_flag = 1
    counter = 0
    cayenneReset = 0
    # start looking for messages from the MQTT broker
    client.loop_start()
    pubMan.loop_start()


    # begin loop
    while loop_flag==1:
        cayenneClient.loop()
        time.sleep(1)
        print "In the loop...."
        # Check for new facilities request emails in gmail
        checkEmail()
        counter += 1
        if (counter > 5 ):
            print "Dumping Stats"
            counter = 0
            sensList.dumpSensorStats()
            #time.sleep(3)
            # use the line below to publish every so often to the ESP8266 ( used for testing)
            #pubMan.publish(publishTopic,payload="BR-C-404"+" 1")
            if (cayenneReset == 0):
                for channel in range(0,3):
                    print "Initializing index"+str(channel)
                    cayenneClient.virtualWrite(channel+1, 0)
                    cayenneClient.virtualWrite(channel+1+3, 0)
                    cayenneClient.virtualWrite(channel+1+6, 0)
                cayenneReset = 1
                cayenneClient.virtualWrite(10,0)
                cayenneClient.virtualWrite(11,0)
                cayenneClient.virtualWrite(12,0)
    # disconnect the client and stop the loop
    client.disconnect()
    client.loop_stop()
    # disconnect the publisher and stop the loop
    pubMan.disconnect()
    pubMan.loop_stop()    
    quit()

Init.lua

Lua
Initial code for the ESP8266 to detect a button press on bootup so that the device will stay in boot mode to allow new code to be loaded. This code launches startup.lua if no button press is detected.
-- ###############################################################
-- Init.lua 
-- 
-- Modified By: Daniel Corley
--
-- Functions to look for button press on boot to determine whether to run
-- the normal main startup program ( 'startup.lua' ) or not.  
--
-- ###############################################################

-- Define some GPIO pins
Button1_12 = 6 


-- Check the state of button 1  before firing up the program.
gpio.mode(Button1_12, gpio.INPUT, gpio.PULLUP)

-- Read the pins
stateB1 = gpio.read(Button1_12)


-- If the button is pressed, do NOT startup the main program.
if ((stateB1 == 0)) then 
    print("Skipping main program.")
elseif (file.exists("startup.lua")) then
    print("Starting main program...")
    dofile("startup.lua")
else
    print("Main program is not present.")
end

Startup.lua

Lua
This code runs on the ESP8266 and connects to the local WiFi network. Then, it launches the notwet.lua file that communicates with the Raspberry Pi.
-- ###############################################################
-- Startup.lua 
-- 
-- Modified By: Daniel Corley
--
-- Connects to the local WiFi network and launches the CMDFILE
-- file ('notwet.lua') after the connection is established
--
-- ###############################################################

SSID    = "WiFi_SSID"    -- replace with the SSID of the local WiFi Network
APPWD   = ""             -- local WiFi Network password

CMDFILE = "notwet.lua"   -- File that is executed after connection
 
wifiTrys     = 15     -- Counter of attempts to connect to wifi
NUMWIFITRYS  = 200*10    -- Maximum number of WIFI Testings while waiting for connection

-- Some LED stuff
Led1R_14  = 5
Led1G_4  = 2 --1    -- NOTE: Rev A h/w
Led2R_2  = 4
Led2G_5  = 1 --2    -- NOTE: Rev A h/w
Led3R_15 = 8
Led3G_16 = 0
LedRedPin = { Led1R_14, Led2R_2, Led3R_15 }
LedGreenPin = { Led1G_4, Led2G_5, Led3G_16 } 

function setLedColor(ledNumber, color)
    if (color == "red") then
        gpio.write(LedRedPin[ledNumber], gpio.HIGH)
        gpio.write(LedGreenPin[ledNumber], gpio.LOW)
        --print("Setting LED " .. tostring(i) .. " Red")
    elseif (color == "green") then
        gpio.write(LedRedPin[ledNumber], gpio.LOW)
        gpio.write(LedGreenPin[ledNumber], gpio.HIGH)
        --print("Setting LED " .. tostring(i) .. " Green")
    elseif (color == "yellow") then
        gpio.write(LedRedPin[ledNumber], gpio.HIGH)
        gpio.write(LedGreenPin[ledNumber], gpio.HIGH)
        --print("Setting LED " .. tostring(i) .. " Yellow")
    else
        -- Off
        gpio.write(LedRedPin[ledNumber], gpio.LOW)
        gpio.write(LedGreenPin[ledNumber], gpio.LOW)
        --print("Setting LED " .. tostring(i) .. " OFF")
    end        
end

-- Provide a rolling LED pattern
function rollLeds(index)
    -- R G Y
    local colors = { "red", "green", "yellow" }
    
    for i=1,3 do
        setLedColor(i, colors[((index + i) % 3) + 1])
    end    
end

function launch()
    print("Connected to WIFI!")
    print("IP Address: " .. wifi.sta.getip())
    -- Call our command file. Note: if you foul this up you'll brick the device!
    dofile(CMDFILE)
    makeConn()
end
 
function checkWIFI() 
    if (wifiTrys > NUMWIFITRYS) then
        print("Sorry. Not able to connect")    
        for i=1,3 do
            setLedColor(i, "red")
        end
        tmr.alarm(4, 2500, 0, ESP.reset())
    else
        ipAddr = wifi.sta.getip()
        if ((ipAddr ~= nil) and (ipAddr ~= "0.0.0.0")) then
            tmr.alarm(5, 500, 0, launch)
        else
            -- Reset alarm again
            tmr.alarm(4, 100, 0, checkWIFI)
            print("Checking WIFI..." .. wifiTrys)
            wifiTrys = wifiTrys + 1

            -- Roll LEDs
            rollLeds(wifiTrys)
        end 
    end 
end
 
print("-- Starting up! ")

-- LED 1 (R & G)    
gpio.mode(Led1R_14, gpio.OUTPUT)
gpio.write(Led1R_14, gpio.LOW)    
gpio.mode(Led1G_4, gpio.OUTPUT)
gpio.write(Led1G_4, gpio.LOW)

-- LED 2 (R & G)    
gpio.mode(Led2R_2, gpio.OUTPUT)
gpio.write(Led2R_2, gpio.LOW)    
gpio.mode(Led2G_5, gpio.OUTPUT)
gpio.write(Led2G_5, gpio.LOW)

-- LED 3 (R & G)    
gpio.mode(Led3R_15, gpio.OUTPUT)
gpio.write(Led3R_15, gpio.LOW)    
gpio.mode(Led3G_16, gpio.OUTPUT)
gpio.write(Led3G_16, gpio.LOW)

-- Lets see if we are already connected by getting the IP
ipAddr = wifi.sta.getip()
if ( ( ipAddr == nil ) or  ( ipAddr == "0.0.0.0" ) ) then
    -- We aren't connected, so let's connect
    print("Configuring WIFI....")
    wifi.setmode( wifi.STATION )
    wifi.sta.config( SSID , APPWD)
    print("Waiting for connection")
    tmr.alarm(4 , 2500 , 0 , checkWIFI)
else
    -- We are connected, so just run the launch code.
    launch()
end

NotWet.lua

Lua
This code is running on the ESP8266 after the WiFi connection is established. This code detects button presses and sends data messages to the MQTT Broker running on the Raspberry Pi. It also receives messages from the Raspberry Pi MQTT Broker to indicate when a facilities request has been completed.
-- ###############################################################
-- notwet.lua 
-- 
-- Modified By: Daniel Corley
--
-- Functions to support button press detection of
-- normally-open buttons that will be part of a facilities service
-- notification system.
--
-- ###############################################################
--
-- Original Source Code By:
--    Phil Moyer
--    Adafruit
--    May 2016
--
--    This code is open source, released under the BSD license. All
--    redistribution must include this header.

--    Note: these are just dumb sensors programmed to insert their
--    values into the local network MQTT queue (aka topic). The
--    Raspberry Pi (either the one running the broker or another one)
--    has all the smarts about what to do with the data in the topic
--    queue.
--
--    Note 2: I'll be improving this code as I learn more about Lua.
--    I recommend the book "Programming in Lua" by Roberto
--    Ierusalimschy (one of the designers of Lua).
--
-- ###############################################################


-- ###############################################################
-- Global variables and parameters.
-- ###############################################################

sensorID = "BR-C-404"       -- a sensor identifier for this device ( Bathroom identifier)
tgtHost = "xx.xx.xx.xx"     -- target host IP Address (broker) -- the Raspberry Pi
tgtPort = 1883              -- target port (broker listening on)
mqttUserID = "mosquitto"    -- account to use to log into the broker
mqttPass = "hackathon"      -- broker account password
mqttTimeOut = 120           -- connection timeout
pubTopicQueue = "/facilities"   -- the MQTT topic queue to publish on
subTopicQueue = "/completed"    -- The MQTT topic queue to subscribe to
mqttQosLvl = 0              -- Assurred delivery of msgs
mqttCleanSession = 1

sleepTime = 100             -- main loop sleep time in ms

-- HW Config
-- GPIO Mappings
-- Name contains Huzzah GPIO number. Value is NodeMCU gpio pin number.
Button1_12 = 6
Button2_13 = 7
Button3_0 = 3

Led1R_14  = 5
Led1G_4  = 2 --1    -- NOTE: Rev A h/w
Led2R_2  = 4
Led2G_5  = 1 --2    -- NOTE: Rev A h/w
Led3R_15 = 8
Led3G_16 = 0

-- ###############################################################
-- Functions
-- ###############################################################

function setupGpio()
    -- Set up Button GPIO pins to trigger on falling edge.
    -- Button 1
    gpio.mode(Button1_12, gpio.INT, gpio.PULLUP)
    gpio.trig(Button1_12, "down", B1pressed)

    -- Button 2
    gpio.mode(Button2_13, gpio.INT, gpio.PULLUP)
    gpio.trig(Button2_13, "down", B2pressed)

    -- Button 3
    gpio.mode(Button3_0, gpio.INT, gpio.PULLUP)
    gpio.trig(Button3_0, "down", B3pressed)

    -- LED 1 (R & G)
    gpio.mode(Led1R_14, gpio.OUTPUT)
    gpio.write(Led1R_14, gpio.LOW)
    gpio.mode(Led1G_4, gpio.OUTPUT)
    gpio.write(Led1G_4, gpio.LOW)

    -- LED 2 (R & G)
    gpio.mode(Led2R_2, gpio.OUTPUT)
    gpio.write(Led2R_2, gpio.LOW)
    gpio.mode(Led2G_5, gpio.OUTPUT)
    gpio.write(Led2G_5, gpio.LOW)

    -- LED 3 (R & G)
    gpio.mode(Led3R_15, gpio.OUTPUT)
    gpio.write(Led3R_15, gpio.LOW)
    gpio.mode(Led3G_16, gpio.OUTPUT)
    gpio.write(Led3G_16, gpio.LOW)
end

-- Button Status Variables and functions
debounceDelay = 20      -- ms
debounceAlarmId1 = 0
debounceAlarmId2 = 1
debounceAlarmId3 = 2
buttonPressed = { 0, 0, 0 }

-- BUTTON 1 --
function B1pressed()
    local buttonIndex = 1
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the up event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button1_12, "none")

    -- Force the LED to RED while the button is pressed.
    gpio.write(LedRedPin[buttonIndex], gpio.HIGH)
    gpio.write(LedGreenPin[buttonIndex], gpio.LOW)

    tmr.alarm(debounceAlarmId1, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button1_12, "high", B1released)
    end)
    -- finally react to the down event
    print("Button 1 Pressed!")

    buttonPressed[buttonIndex] = 1

    -- Turn on the LED for this button
    LedColorOn[buttonIndex] = "red"
    LedBlink[buttonIndex] = 0
end

function B1released()
    local buttonIndex = 1
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the down event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button1_12, "none")
    tmr.alarm(debounceAlarmId1, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button1_12, "down", B1pressed)
    end)
    -- finally react to the release event
    print("Button 1 Released!")

    -- Only do anything if there has been a state change.
    if (buttonPressed[buttonIndex] == 1) then
        -- Trigger a publish event
        pubEvent()
         -- Reset the button state variable
        buttonPressed[buttonIndex] = 0

        -- Set the LED to red for the next second
        LedColorOn[buttonIndex] = "red"
        LedBlink[buttonIndex] = 1
    end
end

-- BUTTON 2 --
function B2pressed()
    local buttonIndex = 2
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the up event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button2_13, "none")

    -- Force the LED to RED while the button is pressed.
    gpio.write(LedRedPin[buttonIndex], gpio.HIGH)
    gpio.write(LedGreenPin[buttonIndex], gpio.LOW)

    tmr.alarm(debounceAlarmId2, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button2_13, "high", B2released)
    end)
    -- finally react to the down event
    print("Button 2 Pressed!")

    buttonPressed[buttonIndex] = 1

    -- Turn on the LED for this button
    LedColorOn[buttonIndex] = "red"
    LedBlink[buttonIndex] = 0
end

function B2released()
    local buttonIndex = 2
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the down event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button2_13, "none")
    tmr.alarm(debounceAlarmId2, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button2_13, "down", B2pressed)
    end)
    -- finally react to the release event
    print("Button 2 Released!")

    -- Only do anything if there has been a state change.
    if (buttonPressed[buttonIndex] == 1) then
        -- Trigger a publish event
        pubEvent()
         -- Reset the button state variable
        buttonPressed[buttonIndex] = 0

        -- Set the LED to red for the next second
        LedColorOn[buttonIndex] = "red"
        LedBlink[buttonIndex] = 1
    end
end

-- BUTTON 3 --
function B3pressed()
    local buttonIndex = 3
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the up event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button3_0, "none")

    -- Force the LED to RED while the button is pressed.
    gpio.write(LedRedPin[buttonIndex], gpio.HIGH)
    gpio.write(LedGreenPin[buttonIndex], gpio.LOW)

    tmr.alarm(debounceAlarmId3, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button3_0, "high", B3released)
    end)
    -- finally react to the down event
    print("Button 3 Pressed!")

    buttonPressed[buttonIndex] = 1

    -- Turn on the LED for this button
    LedColorOn[buttonIndex] = "red"
    LedBlink[buttonIndex] = 0
end

function B3released()
    local buttonIndex = 3
    -- don't react to any interupts from now on and wait 50ms until the interrupt for the down event is enabled
    -- within that 50ms the switch may bounce to its heart's content
    gpio.trig(Button3_0, "none")
    tmr.alarm(debounceAlarmId3, debounceDelay, tmr.ALARM_SINGLE, function()
        gpio.trig(Button3_0, "down", B3pressed)
    end)
    -- finally react to the release event
    print("Button 3 Released!")

    -- Only do anything if there has been a state change.
    if (buttonPressed[buttonIndex] == 1) then
        -- Trigger a publish event
        pubEvent()
         -- Reset the button state variable
        buttonPressed[buttonIndex] = 0

        -- Set the LED to red for the next second
        LedColorOn[buttonIndex] = "red"
        LedBlink[buttonIndex] = 1
    end
end

-- LED Status Variables
LedColorOn = { "off", "off", "off" }        -- Color when in 'on' state
LedColorOff = { "off", "off", "off" }       -- Color when in 'off' state
LedState = { "off", "off", "off" }          -- Value written to hw
LedBlink = { 0, 0, 0 }                      -- Blink = 1, Constant = 0
LedCount = 0
LedToggle = 0

LedOffTime = 1900
LedOnTime = 100

LedRedPin = { Led1R_14, Led2R_2, Led3R_15 }
LedGreenPin = { Led1G_4, Led2G_5, Led3G_16 }

-- This function handles LED blinking, etc.
function updateLeds()
    -- Update LedCount
    LedCount = LedCount - sleepTime

    -- Reset the LedCount
    if (LedCount <= 0) then
        if (LedToggle > 0) then
            LedToggle = 0
            LedCount = LedOffTime
        else
            LedToggle = 1
            LedCount = LedOnTime
        end
    end

    -- loop through each LED and update its state
    for i=1,3 do
        -- Determine if the LED is supposed to blink or not.
        if (LedBlink[i] == 0) then
            -- Stay on forever
            LedState[i] = LedColorOn[i]
        else
            if (LedToggle > 0) then
                LedState[i] = LedColorOn[i]
            else
                LedState[i] = LedColorOff[i]
            end
        end

        -- Write the state of this LED to the hardware
        setLedColor(i, LedState[i])
    end -- for

end

function setLedColor(ledNumber, color)
    if (color == "red") then
        gpio.write(LedRedPin[ledNumber], gpio.HIGH)
        gpio.write(LedGreenPin[ledNumber], gpio.LOW)
        --print("Setting LED " .. tostring(i) .. " Red")
    elseif (color == "green") then
        gpio.write(LedRedPin[ledNumber], gpio.LOW)
        gpio.write(LedGreenPin[ledNumber], gpio.HIGH)
        --print("Setting LED " .. tostring(i) .. " Green")
    elseif (color == "yellow") then
        gpio.write(LedRedPin[ledNumber], gpio.HIGH)
        gpio.write(LedGreenPin[ledNumber], gpio.HIGH)
        --print("Setting LED " .. tostring(i) .. " Yellow")
    else
        -- Off
        gpio.write(LedRedPin[ledNumber], gpio.LOW)
        gpio.write(LedGreenPin[ledNumber], gpio.LOW)
        --print("Setting LED " .. tostring(i) .. " OFF")
    end
end

-- Function pubEvent() publishes the button values to the defined queue.
function pubEvent()
    -- build buffer. Format is: sensorID val1 val2 val3
    pubValue = sensorID .. " " .. tostring(buttonPressed[1]) .. " " .. tostring(buttonPressed[2]) .. " " .. tostring(buttonPressed[3])
    print("Publishing to " .. pubTopicQueue .. ": " .. pubValue)   -- print a status message
    mqttBroker:publish(pubTopicQueue, pubValue, mqttQosLvl, 0)  -- publish
end

-- Reconnect to MQTT when we receive an "offline" message.
function reconn()
    print("Disconnected, reconnecting....")
    conn()
end

-- Establish a connection to the MQTT broker with the configured parameters.
function conn()
    print("Making connection to MQTT broker")
    mqttBroker:connect(tgtHost, tgtPort, 0, onConnect, onFailToConnect)
end

-- If the MQTT connection fails, light the LEDs yellow.
function onFailToConnect(client, reason)
    print("failed reason: "..reason)

    -- Set all the LEDs to yellow.
    for i=1,3 do
        LedColorOn[i] = "yellow"
        LedBlink[i] = 0
    end
end

function onConnect(client)
    print ("connected")

    -- Subscribe to the subTopicQueue
    mqttBroker:subscribe(subTopicQueue, mqttQosLvl, function(conn) print("Subscribed successfully to " .. subTopicQueue) end)

    -- Send an initial message w/ all buttons low.
    pubEvent()

    -- Set all the LEDs to green.
    for i=1,3 do
        LedColorOn[i] = "green"
        LedBlink[i] = 0
    end
end

-- This handles messages received from the MQTT broker
function rcvMessage(client, topic, data)
   -- Dump msg to console
    if (data ~= nil) then
        print(topic .. ":" .. data)

        -- Tokenize the incoming message...
        tokens = {}
        for w in data:gmatch("%S+") do table.insert(tokens, w) end

        if (tokens[1] == sensorID) then
            -- This msg is for us.
            val = tonumber(tokens[2])

            if ((val < 1) or (val > 3)) then
                print("Invalid button number rx'd (" .. val .. ")")
            else
                print("Received ack for button #" .. val)

                -- Set the LED back to green
                LedColorOn[val] = "green"
                LedBlink[val] = 0
            end
        else
            print("Received msg for wrong sensorID")
        end
    else
        print("Received empty msg on " .. topic)
    end
end

-- Call this first! --
-- makeConn() instantiates the MQTT control object, sets up callbacks,
-- connects to the broker, and then uses the timer to send sensor data.
-- This is the "main" function in this library. This should be called
-- from init.lua (which runs on the ESP8266 at boot), but only after
-- it's been vigorously debugged.
--
-- Note: once you call this from init.lua the only way to change the
-- program on your ESP8266 will be to reflash the NodeCMU firmware!

function makeConn()

    -- Setup the buttons and LEDs.
    setupGpio()

    -- Instantiate a global MQTT client object
    print("Instantiating mqttBroker")
    mqttBroker = mqtt.Client(sensorID, mqttTimeOut, mqttUserID, mqttPass, mqttCleanSession)

    -- Set up the event callbacks
    print("Setting up callbacks")
    mqttBroker:on("connect", function(client) print ("connected") end)
    mqttBroker:on("offline", reconn)

    mqttBroker:on("message", rcvMessage)

    -- Connect to the Broker
    conn()

    -- Use the watchdog to call our sensor publication routine
    -- every dataInt seconds to send the sensor data to the
    -- appropriate topic in MQTT.
    tmr.alarm(3, sleepTime, 1, updateLeds)
end

-- ###############################################################
-- "Main"
-- ###############################################################

-- Nothing to see here.  Please move along.

Credits

Larry Deaton
2 projects • 5 followers
Embedded software engineer with 25 years of experience developing telecommunications, video security, and streaming products.
Daniel Corley
0 projects • 2 followers
Embedded systems developer since 1998.
John Burnham
0 projects • 2 followers
Embedded Design Engineer. Hobbies include microcontrollers, robotics, muscle cars, and fixing things.

Comments