This week I was able to make progress on a couple of remaining items. First, I was able to get email notifications working and secondly, I was able to get recipes downloaded. I still need to get the recipe data into the PLC, but I should finish that up shortly. All code for this project is available at https://github.com/frellwan/SciFy-Pi.git under the Serial/df1 folder.
Email Notification
The typical protocol for sending mail on the Internet is the Simple Mail Transfer Protocol (SMTP). I will be using Twisted Mail to send SMTP messages when alarm conditions are detected in the PLC. I outlined the steps necessary to install Twisted in a previous post, and Twisted Mail is part of that install.
SMTP is a plain-text protocol. To send an email, we need to know the IP address or hostname of an SMTP server. To do this we need to look up the mail exchange (MX) servers for the hostname we will be sending mail to (e.g. ME@google.com – MX lookup would give us the hostname of the mail exchange server for google.com). Most SMTP messages are sent using port 25.
A simple way to do this with Twisted Mail:
MXCALCULATOR = relaymanager.MXCalculator() def getMailExchange(host): def cbMX(mxRecord): return str(mxRecord.name) return MXCALCULATOR.getMX(host).addCallback(cbMX)
In my company I am able to send email from a fictitious email address (such as MACHINENUMBER@hostname.com) and it will be delivered without issue as long as it goes to a hostname.com email address. This allows me to have each machine send emails that will allow the recipient to know which machine is having problems. These emails lack the authentication headers of typical emails and will usually end up in a spam filter when I attempt to send to an email address outside of my company’s mail exchange hostname.
Sending a SMTP email is a fairly easy task with Twisted Mail:
def sendEmail(mailFrom, mailTo, msg, subject=""): def dosend(host): mstring = "From: %s\nTo: %s\nSubject: %s\n\n%s\n" msgfile = StringIO(mstring % (mailFrom, mailTo, subject, msg)) d = defer.Deferred() factory = smtp.ESMTPSenderFactory(None, None, mailFrom, mailTo, msgfile, d, requireAuthentication=False, requireTransportSecurity=False) reactor.connectTCP(host, 25, factory) return d return getMailExchange(mailTo.split("@")[1]).addCallback(dosend)
Recipe Download
As mentioned in the project description, I will be accessing a recipe database on an external server through an FTP connection. The PLC will set a bit when the recipe files need to be downloaded from the server. When this bit is set an FTP connection is established and a file that contains the names of all the recipe files is downloaded and then each recipe name is read from the file and downloaded separately.
if (bits[2]): # self.transferred added so multiple downloads won't be initiated if (not self.transferred): #Download Recipes from Server serialLog.debug("Downloading Recipes") d = self.ftpEndpoint.connect(FTPClientAFactory()) d.addCallback(getRecipeFiles) d.addErrback(self.FTPfail, 'startFTPTransfer') self.transferred = True else: self.transferred = False
def getRecipeFiles(ftpProtocol, localDir): def downloadRecipes(self): filename = localDir + '/' + 'families.csv' fObj = open(filename, 'r') for recipe in fObj: recipeName = recipe.strip() + '.csv' recipeDir = localDir + recipeName recipeFile = FileReceiver(recipeDir) d = ftpProtocol.retrieveFile(recipeName, recipeFile) d.addErrback(fail, "getRecipeFiles") # Download recipes familynames = localDir + '/families.csv' recipeFile = FileReceiver(familynames) d = ftpProtocol.retrieveFile('families.csv', recipeFile) d.addCallback(downloadRecipes) d.addErrback(fail, "getRecipeFiles")