This follows a similar idea to my second project, but takes it in a somewhat different direction, fully integrating it into my home network using the MQTT protocol. For those interested in what MQTT is, have a look here.
Images
Here are the three PC nodes in the network…
Jarvis (Iron Man reference), which is the system I usually work at…
Phoenix, which was the codename for this laptop when it was being developed. It sits in the same room as Jarvis, so their temps have effects on each other…
… and Tars (Interstellar reference). This hosts a Minecraft server and resides in my basement… quite dusty at the moment thanks to the kitchen renovation taking place directly above…
Here’s the the Arduino-powered gateway, which can be connected to any PC to interface with the Arduino MQTT bridge. It has been repurposed from the previous project.
The front of the device…
The rear of the device…
And here are a shot of the network in action…
Here’s a shot of the 3 PC nodes interacting with each other. Right now, they’re all running great, sending each other encouraging messages.
And here’s a shot of what might happen if you neglect your own personal health too much, leading the PCs to go on-strike…
While this is just “Wizard of Ozzed” for the sake of having a quick and dirty example (as well as some humor in my choice of commands…), this is something that can happen if you don’t take care of yourself.
Description
There’s quite a bit to cover here, so bear with me.
As I had stated in the briefing for my previous project, I tend to take much better care of my computers than I do myself (especially when it comes to sleep schedule). The previous idea was one way of attempting to remedy this, but it had some shortcomings. Namely, it was far too easy to game the system with regard to reporting temperatures of the systems, and it had no connection to the systems in order to assist in dealing with certain situations (i.e., when a system is running too hot, what do you do?).
The goal is to allow any and all nodes in the network to communicate their vitals to each other. There are 3 PC nodes (all running Manjaro Linux), as well as a human node. This human node consists of an Arduino (for capturing my vitals at any given moment using the same sensors as in the last project), as well as a PC for reading the output of the Arduino and sending it to the other nodes in the network via the MQTT protocol.
Most of the time, the systems simply communicate their vitals with each other and send each other little messages based on what may be happening at that moment. For instance, one PC may be under heavy load, but its temperatures are in-check. The other PCs will see that and send a message to spur it on further–something along the lines of, “Hey, you’re doing great!” (because especially in times like these, we can all use a little bit of affirmation).
In another instance, a PC may be under very little load, but the temps could still be high. This would indicate a serious problem with that machine (perhaps a fan failure or serious heat in the room). This would cause the other PCs in the network to “sound the alarm” and notify me that something is seriously wrong.
Certain audible responses are triggered based on what the situation is. For instance, if a PC is “sounding the alarm,” it will ring the terminal bell and say something along the lines of “HELP! I need somebody! Not just anybody!” (in a text-to-speech voice for added comedic effect).
Now, that only covers PC-to-PC interaction. At any given point, I can enter the network and report my own vitals via the Arduino connected to a PC. Like with the last project, these are Galvanic Skin Response/Resistance (GSR), heart rate, when I went to sleep the previous night, and for how long I slept. If my GSR is too low or heart rate is too high (indicating that I am stressed), then the PCs will see this and offer words of encouragement.
On the other hand, if I went to sleep at an unreasonable hour the previous night, then that will count as a strike. Strikes last until the end of the week. If I accumulate three strikes, then the computers go on-strike and refuse to perform certain actions. Pulling an all-nighter results in an automatic three strikes.
Progress
One of the things that required quite a bit of trial and error was pulling the necessary data from terminal output. I was simply capturing the output of the sensors command (to get the CPU temperature) and nvidia-smi (to get GPU temperature and utilization, since I have Nvidia GPUs in my systems.
Initially, when the GPU was under 100% load, it would read as 00. This was an indexing issue and was easy to fix.
This happened for no apparent reason. It’s possible that the program just received garbage data from nvidia-smi…
This didn’t go over well with my parents… more on that later…
Since there wasn’t a bunch to do on the Arduino side of things and most of my issues were in Python, that’s all I have for in-progress photo documentation, sadly.
Process Reflection
As with everything I’ve done thus far, time management was a major issue. I’m going to save the details on that, though, as I’ve gone into it already in documentation for my previous works.
Getting the PCs to talk to each other over MQTT was far easier than I had anticipated thanks to Garth Zeglin’s work on the MQTT bridge and server for IDeATe. He did the majority of the dirty work, and there was no need to reinvent the wheel, so I borrowed liberally from his original code.
Something much more difficult was getting the system to consistently read the same sensor data. I’m not sure why, but occasionally, nvidia-smi will output garbage for the GPU utilization. This led to crashes that were initially very hard to track down, but after a while, I simply added an empty string check where necessary–the crashes are gone now.
Another difficult part was figuring out what to actually do based on certain scenarios. I wanted to have a somewhat whimsical feel to the PC reactions regardless of what the situation was. Having heard RZach use a somewhat comical text-to-speech voice during class throughout the semester to notify us when our presentation time slot was ending, I decided to follow a similar path and have the machines output responses to me via text-to-speech.
Unfortunately, that did not go over well with my parents when they heard Tars say “Seth, what the hell are you doing? I’m burning over here!” in a robotic voice… right in the middle of their Netflix-watching time. As a result, that code has been omitted for the time being.
Perhaps when I get my own place, I can revisit that. For now, I have to stick with more discrete notifications like plain text messages over MQTT.
Code
Here are the scripts that run on the 3 PC nodes. They are all very similar, but have minor differences to account for Intel vs. AMD CPU (different way of reporting temps), as well as GPU vitals reporting.
For Jarvis…
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if__name__ == "__main__":
main()
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
################################################################
# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu>
# To the extent possible under law, the author has dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
################################################################
# standard Python libraries
from __future__ import print_function
import os, sys, struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html
# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.html
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
# documentation: https://www.eclipse.org/paho/clients/python/docs/
import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')
# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)
# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports = { 8884 : '16-223',
8885 : '16-375',
8886 : '60-223',
8887 : '62-362',
}
mqtt_rc_codes = ['Success', 'Incorrect protocol version', 'Invalid client identifier', 'Server unavailable', 'Bad username or password', 'Not authorized']
counter = 0
################################################################
class MainApp(object):
"""Main application object holding any non-GUI related state."""
def __init__(self):
# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT, self._sigint_handler)
# load any available persistent application settings
QtCore.QCoreApplication.setOrganizationName("IDeATe")
QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")
QtCore.QCoreApplication.setApplicationName('mqtt_monitor')
self.settings = QtCore.QSettings()
# uncomment to restore 'factory defaults'
# self.settings.clear()
# MQTT server settings
self.hostname = self.settings.value('mqtt_host', 'mqtt.ideate.cmu.edu')
self.systemHostname = os.popen('uname -n').read().strip()
self.portnum = self.settings.value('mqtt_port', 8887)
self.portnum = int(self.portnum)
self.username = self.settings.value('mqtt_user', 'students')
self.password = self.settings.value('mqtt_password', 'Arduino')
# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()
self.subscription = self.settings.value('mqtt_subscription', username + '/#')
# default message to send
self.topic = self.settings.value('mqtt_topic', username)
self.payload = self.settings.value('mqtt_payload', 'hello')
# Initialize the MQTT client system
self.client = mqtt.Client()
self.client.enable_logger(mqtt_log)
self.client.on_log = self.on_log
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_message = self.on_message
self.client.tls_set()
return
################################################################
def app_is_exiting(self):
if self.client.is_connected():
self.client.disconnect()
self.client.loop_stop()
def _sigint_handler(self, signal, frame):
print("Keyboard interrupt caught, running close handlers...")
self.app_is_exiting()
sys.exit(0)
################################################################
def set_server_name(self, name):
self.hostname = name
self.settings.setValue('mqtt_host', name)
def set_server_port(self, value):
self.portnum = value
self.settings.setValue('mqtt_port', self.portnum)
def set_username(self, name):
self.username = name
self.settings.setValue('mqtt_user', name)
def set_password(self, name):
self.password = name
self.settings.setValue('mqtt_password', name)
def connect_to_mqtt_server(self):
if self.client.is_connected():
return
else:
if self.portnum is None:
print("Warning: Please specify the server port before attempting connection.")
else:
print("Info: Initiating MQTT connection to %s:%d" % (self.hostname, self.portnum))
self.client.username_pw_set(self.username, self.password)
self.client.connect_async(self.hostname, self.portnum)
self.client.loop_start()
def disconnect_from_mqtt_server(self):
if self.client.is_connected():
self.client.disconnect()
else:
print("Warning: Not connected.")
self.client.loop_stop()
################################################################
# The callback for when the broker responds to our connection request.
def on_connect(self, client, userdata, flags, rc):
print("Info: Connected to server with with flags: %s, result code: %s" % (flags, rc))
if rc == 0:
print("Info: Connection succeeded.")
elif rc > 0:
if rc < len(mqtt_rc_codes):
print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])
else:
print("Warning: Connection failed with unknown error %d", rc)
# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)
return
# The callback for when the broker responds with error messages.
def on_log(client, userdata, level, buf):
print("Info: on_log level %s: %s", level, userdata)
return
def on_disconnect(self, client, userdata, rc):
print("Info: disconnected")
return
# The callback for when a message has been received on a topic to which this
# client is subscribed. The message variable is a MQTTMessage that describes
# all of the message parameters.
# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.
# The payload is a binary string (bytes).
# qos is an integer quality of service indicator (0,1, or 2)
# mid is an integer message ID.
def on_message(self, client, userdata, msg):
global counter
if msg.topic == "leffen/pc_vitals":
if counter == 5:
counter = 0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1] = msgList[-1][0:2]
msgList[-1] = msgList[-1].strip()
if msgList[0][2:] != self.systemHostname:
if (float(msgList[1]) < 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) < 40))):
# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a') # Ring terminal bell
sys.stdout.flush()
self.send_message("leffen/pc_chatter", "Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!" % msgList[0])
elif (float(msgList[1]) >= 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 40))):
# This is the case where the system is under load and getting a bit hot...
self.send_message("leffen/pc_chatter", "%s looks like you're running a little hot... why not take a break? I think you've earned it." % msgList[0])
elif (float(msgList[1]) >= 80.0 and float(msgList[2]) < 85.0) or \
(int(msgList[3]) < 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 80 or \
int(msgList[4]) == 0))):
# This is the case where the system is under load and temps are in-check...
self.send_message("leffen/pc_chatter", "Looks like you're on it %s... keep it up!" % msgList[0])
elif msg.topic == "leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")
if (float(msgList[0]) < 100 or float(msgList[1]) >= 130):
self.send_message("leffen/to_human", "Hey man, looks like you need a break.")
if (int(msgList[2]) == 0):
self.send_message("leffen/to_human", "Alright, if you aren't gonna take care of yourself, then we're going on strike!")
elif (int(msgList[3]) < 7 or int(msgList[3]) >= 12):
self.send_message("leffen/to_human", "Come on, man. Get better sleep!")
counter += 1
print("{%s} %s" % (msg.topic, msg.payload))
return
################################################################
def set_subscription(self, sub):
if self.client.is_connected():
self.client.unsubscribe(self.subscription)
try:
self.client.subscribe(sub)
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
except ValueError:
print("Error: Invalid subscription string, not changed.")
self.client.subscribe(self.subscription)
else:
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
def set_topic(self, sub):
self.topic = sub
self.settings.setValue('mqtt_topic', sub)
def send_message(self, topic, payload):
if self.client.is_connected():
self.client.publish(topic, payload)
else:
print("Error: Not connected.")
self.payload = payload
self.settings.setValue('mqtt_payload', payload)
################################################################
def main():
# Optionally add an additional root log handler to stream messages to the
# terminal console.
if False:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)
# initialize the Qt system itself
app = QtWidgets.QApplication(sys.argv)
# create the main application controller
main = MainApp()
main.connect_to_mqtt_server()
# run the event loop until the user is done
print("Info: Starting event loop.")
while True:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[5].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
gpuUtilLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
cpuTempstr = cpuTempLine[15:19]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
gpuUtilstr = gpuUtilLine[60:63]
cpuTemp = float(cpuTempstr)
gpuTemp = float(gpuTempstr)
main.send_message("leffen/pc_vitals", main.systemHostname + "," +
cpuUtilstr + "," + cpuTempstr + "," + gpuTempstr + "," + gpuUtilstr)
time.sleep(10)
sys.exit(app.exec_())
################################################################
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if __name__ == "__main__":
main()
#!/usr/bin/env python3"""A PyQt5 GUI utility to monitor and send MQTT server messages."""################################################################# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu># To the extent possible under law, the author has dedicated all copyright# and related and neighboring rights to this software to the public domain# worldwide. This software is distributed without any warranty.# You should have received a copy of the CC0 Public Domain Dedication along with this software.# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.################################################################# standard Python librariesfrom __future__ import print_function
import os, sys,struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.htmlfromPyQt5importQtCore,QtGui,QtWidgets,QtNetwork# documentation: https://www.eclipse.org/paho/clients/python/docs/import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports ={8884:'16-223',8885:'16-375',8886:'60-223',8887:'62-362',}
mqtt_rc_codes =['Success','Incorrect protocol version','Invalid client identifier','Server unavailable','Bad username or password','Not authorized']
counter =0################################################################classMainApp(object):"""Main application object holding any non-GUI related state."""def __init__(self):# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT,self._sigint_handler)# load any available persistent application settingsQtCore.QCoreApplication.setOrganizationName("IDeATe")QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")QtCore.QCoreApplication.setApplicationName('mqtt_monitor')self.settings =QtCore.QSettings()# uncomment to restore 'factory defaults'# self.settings.clear()# MQTT server settingsself.hostname =self.settings.value('mqtt_host','mqtt.ideate.cmu.edu')self.systemHostname = os.popen('uname -n').read().strip()self.portnum =self.settings.value('mqtt_port',8887)self.portnum =int(self.portnum)self.username =self.settings.value('mqtt_user','students')self.password =self.settings.value('mqtt_password','Arduino')# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()self.subscription =self.settings.value('mqtt_subscription', username +'/#')# default message to sendself.topic =self.settings.value('mqtt_topic', username)self.payload =self.settings.value('mqtt_payload','hello')# Initialize the MQTT client systemself.client = mqtt.Client()self.client.enable_logger(mqtt_log)self.client.on_log =self.on_log
self.client.on_connect =self.on_connect
self.client.on_disconnect =self.on_disconnect
self.client.on_message =self.on_message
self.client.tls_set()return################################################################def app_is_exiting(self):ifself.client.is_connected():self.client.disconnect()self.client.loop_stop()def _sigint_handler(self, signal, frame):print("Keyboard interrupt caught, running close handlers...")self.app_is_exiting()
sys.exit(0)################################################################def set_server_name(self, name):self.hostname = name
self.settings.setValue('mqtt_host', name)def set_server_port(self,value):self.portnum =valueself.settings.setValue('mqtt_port',self.portnum)def set_username(self, name):self.username = name
self.settings.setValue('mqtt_user', name)def set_password(self, name):self.password = name
self.settings.setValue('mqtt_password', name)def connect_to_mqtt_server(self):ifself.client.is_connected():returnelse:ifself.portnum isNone:print("Warning: Please specify the server port before attempting connection.")else:print("Info: Initiating MQTT connection to %s:%d"%(self.hostname,self.portnum))self.client.username_pw_set(self.username,self.password)self.client.connect_async(self.hostname,self.portnum)self.client.loop_start()def disconnect_from_mqtt_server(self):ifself.client.is_connected():self.client.disconnect()else:print("Warning: Not connected.")self.client.loop_stop()################################################################# The callback for when the broker responds to our connection request.def on_connect(self, client, userdata, flags, rc):print("Info: Connected to server with with flags: %s, result code: %s"%(flags, rc))if rc ==0:print("Info: Connection succeeded.")elif rc >0:if rc < len(mqtt_rc_codes):print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])else:print("Warning: Connection failed with unknown error %d", rc)# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)return# The callback for when the broker responds with error messages.def on_log(client, userdata, level, buf):print("Info: on_log level %s: %s", level, userdata)returndef on_disconnect(self, client, userdata, rc):print("Info: disconnected")return# The callback for when a message has been received on a topic to which this# client is subscribed. The message variable is a MQTTMessage that describes# all of the message parameters.# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.# The payload is a binary string (bytes).# qos is an integer quality of service indicator (0,1, or 2)# mid is an integer message ID.def on_message(self, client, userdata, msg):global counter
if msg.topic =="leffen/pc_vitals":if counter ==5:
counter =0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1]= msgList[-1][0:2]
msgList[-1]= msgList[-1].strip()if msgList[0][2:]!=self.systemHostname:if(float(msgList[1])<40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])<40))):# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a')# Ring terminal bell
sys.stdout.flush()self.send_message("leffen/pc_chatter","Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!"% msgList[0])elif(float(msgList[1])>=40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=40))):# This is the case where the system is under load and getting a bit hot...self.send_message("leffen/pc_chatter","%s looks like you're running a little hot... why not take a break? I think you've earned it."% msgList[0])elif(float(msgList[1])>=80.0andfloat(msgList[2])<85.0)or \
(int(msgList[3])<85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=80or \
int(msgList[4])==0))):# This is the case where the system is under load and temps are in-check...self.send_message("leffen/pc_chatter","Looks like you're on it %s... keep it up!"% msgList[0])elif msg.topic =="leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")if(float(msgList[0])<100orfloat(msgList[1])>=130):self.send_message("leffen/to_human","Hey man, looks like you need a break.")if(int(msgList[2])==0):self.send_message("leffen/to_human","Alright, if you aren't gonna take care of yourself, then we're going on strike!")elif(int(msgList[3])<7orint(msgList[3])>=12):self.send_message("leffen/to_human","Come on, man. Get better sleep!")
counter +=1print("{%s} %s"%(msg.topic, msg.payload))return################################################################def set_subscription(self,sub):ifself.client.is_connected():self.client.unsubscribe(self.subscription)try:self.client.subscribe(sub)self.subscription =subself.settings.setValue('mqtt_subscription',sub)exceptValueError:print("Error: Invalid subscription string, not changed.")self.client.subscribe(self.subscription)else:self.subscription =subself.settings.setValue('mqtt_subscription',sub)def set_topic(self,sub):self.topic =subself.settings.setValue('mqtt_topic',sub)def send_message(self, topic, payload):ifself.client.is_connected():self.client.publish(topic, payload)else:print("Error: Not connected.")self.payload = payload
self.settings.setValue('mqtt_payload', payload)################################################################def main():# Optionally add an additional root log handler to stream messages to the# terminal console.ifFalse:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)# initialize the Qt system itself
app =QtWidgets.QApplication(sys.argv)# create the main application controller
main =MainApp()
main.connect_to_mqtt_server()# run the event loop until the user is doneprint("Info: Starting event loop.")whileTrue:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[5].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
gpuUtilLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
cpuTempstr = cpuTempLine[15:19]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
gpuUtilstr = gpuUtilLine[60:63]
cpuTemp =float(cpuTempstr)
gpuTemp =float(gpuTempstr)
main.send_message("leffen/pc_vitals", main.systemHostname +","+
cpuUtilstr +","+ cpuTempstr +","+ gpuTempstr +","+ gpuUtilstr)
time.sleep(10)
sys.exit(app.exec_())################################################################# Main script follows. This sequence is executed when the script is initiated# from the command line.if __name__ =="__main__":
main()
For Phoenix…
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if__name__ == "__main__":
main()
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
################################################################
# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu>
# To the extent possible under law, the author has dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
################################################################
# standard Python libraries
from __future__ import print_function
import os, sys, struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html
# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.html
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
# documentation: https://www.eclipse.org/paho/clients/python/docs/
import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')
# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)
# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports = { 8884 : '16-223',
8885 : '16-375',
8886 : '60-223',
8887 : '62-362',
}
mqtt_rc_codes = ['Success', 'Incorrect protocol version', 'Invalid client identifier', 'Server unavailable', 'Bad username or password', 'Not authorized']
counter = 0
################################################################
class MainApp(object):
"""Main application object holding any non-GUI related state."""
def __init__(self):
# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT, self._sigint_handler)
# load any available persistent application settings
QtCore.QCoreApplication.setOrganizationName("IDeATe")
QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")
QtCore.QCoreApplication.setApplicationName('mqtt_monitor')
self.settings = QtCore.QSettings()
# uncomment to restore 'factory defaults'
# self.settings.clear()
# MQTT server settings
self.hostname = self.settings.value('mqtt_host', 'mqtt.ideate.cmu.edu')
self.systemHostname = os.popen('uname -n').read().strip()
self.portnum = self.settings.value('mqtt_port', 8887)
self.portnum = int(self.portnum)
self.username = self.settings.value('mqtt_user', 'students')
self.password = self.settings.value('mqtt_password', 'Arduino')
# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()
self.subscription = self.settings.value('mqtt_subscription', username + '/#')
# default message to send
self.topic = self.settings.value('mqtt_topic', username)
self.payload = self.settings.value('mqtt_payload', 'hello')
# Initialize the MQTT client system
self.client = mqtt.Client()
self.client.enable_logger(mqtt_log)
self.client.on_log = self.on_log
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_message = self.on_message
self.client.tls_set()
return
################################################################
def app_is_exiting(self):
if self.client.is_connected():
self.client.disconnect()
self.client.loop_stop()
def _sigint_handler(self, signal, frame):
print("Keyboard interrupt caught, running close handlers...")
self.app_is_exiting()
sys.exit(0)
################################################################
def set_server_name(self, name):
self.hostname = name
self.settings.setValue('mqtt_host', name)
def set_server_port(self, value):
self.portnum = value
self.settings.setValue('mqtt_port', self.portnum)
def set_username(self, name):
self.username = name
self.settings.setValue('mqtt_user', name)
def set_password(self, name):
self.password = name
self.settings.setValue('mqtt_password', name)
def connect_to_mqtt_server(self):
if self.client.is_connected():
return
else:
if self.portnum is None:
print("Warning: Please specify the server port before attempting connection.")
else:
print("Info: Initiating MQTT connection to %s:%d" % (self.hostname, self.portnum))
self.client.username_pw_set(self.username, self.password)
self.client.connect_async(self.hostname, self.portnum)
self.client.loop_start()
def disconnect_from_mqtt_server(self):
if self.client.is_connected():
self.client.disconnect()
else:
print("Warning: Not connected.")
self.client.loop_stop()
################################################################
# The callback for when the broker responds to our connection request.
def on_connect(self, client, userdata, flags, rc):
print("Info: Connected to server with with flags: %s, result code: %s" % (flags, rc))
if rc == 0:
print("Info: Connection succeeded.")
elif rc > 0:
if rc < len(mqtt_rc_codes):
print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])
else:
print("Warning: Connection failed with unknown error %d", rc)
# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)
return
# The callback for when the broker responds with error messages.
def on_log(client, userdata, level, buf):
print("Info: on_log level %s: %s", level, userdata)
return
def on_disconnect(self, client, userdata, rc):
print("Info: disconnected")
return
# The callback for when a message has been received on a topic to which this
# client is subscribed. The message variable is a MQTTMessage that describes
# all of the message parameters.
# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.
# The payload is a binary string (bytes).
# qos is an integer quality of service indicator (0,1, or 2)
# mid is an integer message ID.
def on_message(self, client, userdata, msg):
global counter
if msg.topic == "leffen/pc_vitals":
if counter == 5:
counter = 0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1] = msgList[-1][0:2]
msgList[-1] = msgList[-1].strip()
if msgList[0][2:] != self.systemHostname:
if (float(msgList[1]) < 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) < 40))):
# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a') # Ring terminal bell
sys.stdout.flush()
self.send_message("leffen/pc_chatter", "Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!" % msgList[0])
elif (float(msgList[1]) >= 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 40))):
# This is the case where the system is under load and getting a bit hot...
self.send_message("leffen/pc_chatter", "%s looks like you're running a little hot... why not take a break? I think you've earned it." % msgList[0])
elif (float(msgList[1]) >= 70.0 and float(msgList[2]) < 85.0) or \
(int(msgList[3]) < 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 80))):
# This is the case where the system is under load and temps are in-check...
self.send_message("leffen/pc_chatter", "Looks like you're on it %s... keep it up!" % msgList[0])
elif msg.topic == "leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")
if (float(msgList[0]) < 100 or float(msgList[1]) >= 130):
self.send_message("leffen/to_human", "Hey man, looks like you need a break.")
if (int(msgList[2]) == 0):
self.send_message("leffen/to_human", "Alright, if you aren't gonna take care of yourself, then we're going on strike!")
elif (int(msgList[3]) < 7 or int(msgList[3]) >= 12):
self.send_message("leffen/to_human", "Come on, man. Get better sleep!")
counter += 1
print("{%s} %s" % (msg.topic, msg.payload))
return
################################################################
def set_subscription(self, sub):
if self.client.is_connected():
self.client.unsubscribe(self.subscription)
try:
self.client.subscribe(sub)
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
except ValueError:
print("Error: Invalid subscription string, not changed.")
self.client.subscribe(self.subscription)
else:
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
def set_topic(self, sub):
self.topic = sub
self.settings.setValue('mqtt_topic', sub)
def send_message(self, topic, payload):
if self.client.is_connected():
self.client.publish(topic, payload)
else:
print("Error: Not connected.")
self.payload = payload
self.settings.setValue('mqtt_payload', payload)
################################################################
def main():
# Optionally add an additional root log handler to stream messages to the
# terminal console.
if False:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)
# initialize the Qt system itself
app = QtWidgets.QApplication(sys.argv)
# create the main application controller
main = MainApp()
main.connect_to_mqtt_server()
# run the event loop until the user is done
print("Info: Starting event loop.")
#rawCPU = os.system("sensors"
while True:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[19].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
gpuUtilLine = rawGPU.splitlines()[9].strip()
cpuTempstr = cpuTempLine[16:20]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
gpuUtilstr = gpuUtilLine[60:63]
cpuTemp = float(cpuTempstr)
gpuTemp = float(gpuTempstr)
cpuUtil = float(cpuUtilstr)
main.send_message("leffen/pc_vitals", main.systemHostname + "," +
cpuUtilstr + "," + cpuTempstr + "," + gpuTempstr + "," + gpuUtilstr)
time.sleep(10)
sys.exit(app.exec_())
################################################################
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if __name__ == "__main__":
main()
#!/usr/bin/env python3"""A PyQt5 GUI utility to monitor and send MQTT server messages."""################################################################# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu># To the extent possible under law, the author has dedicated all copyright# and related and neighboring rights to this software to the public domain# worldwide. This software is distributed without any warranty.# You should have received a copy of the CC0 Public Domain Dedication along with this software.# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.################################################################# standard Python librariesfrom __future__ import print_function
import os, sys,struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.htmlfromPyQt5importQtCore,QtGui,QtWidgets,QtNetwork# documentation: https://www.eclipse.org/paho/clients/python/docs/import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports ={8884:'16-223',8885:'16-375',8886:'60-223',8887:'62-362',}
mqtt_rc_codes =['Success','Incorrect protocol version','Invalid client identifier','Server unavailable','Bad username or password','Not authorized']
counter =0################################################################classMainApp(object):"""Main application object holding any non-GUI related state."""def __init__(self):# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT,self._sigint_handler)# load any available persistent application settingsQtCore.QCoreApplication.setOrganizationName("IDeATe")QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")QtCore.QCoreApplication.setApplicationName('mqtt_monitor')self.settings =QtCore.QSettings()# uncomment to restore 'factory defaults'# self.settings.clear()# MQTT server settingsself.hostname =self.settings.value('mqtt_host','mqtt.ideate.cmu.edu')self.systemHostname = os.popen('uname -n').read().strip()self.portnum =self.settings.value('mqtt_port',8887)self.portnum =int(self.portnum)self.username =self.settings.value('mqtt_user','students')self.password =self.settings.value('mqtt_password','Arduino')# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()self.subscription =self.settings.value('mqtt_subscription', username +'/#')# default message to sendself.topic =self.settings.value('mqtt_topic', username)self.payload =self.settings.value('mqtt_payload','hello')# Initialize the MQTT client systemself.client = mqtt.Client()self.client.enable_logger(mqtt_log)self.client.on_log =self.on_log
self.client.on_connect =self.on_connect
self.client.on_disconnect =self.on_disconnect
self.client.on_message =self.on_message
self.client.tls_set()return################################################################def app_is_exiting(self):ifself.client.is_connected():self.client.disconnect()self.client.loop_stop()def _sigint_handler(self, signal, frame):print("Keyboard interrupt caught, running close handlers...")self.app_is_exiting()
sys.exit(0)################################################################def set_server_name(self, name):self.hostname = name
self.settings.setValue('mqtt_host', name)def set_server_port(self,value):self.portnum =valueself.settings.setValue('mqtt_port',self.portnum)def set_username(self, name):self.username = name
self.settings.setValue('mqtt_user', name)def set_password(self, name):self.password = name
self.settings.setValue('mqtt_password', name)def connect_to_mqtt_server(self):ifself.client.is_connected():returnelse:ifself.portnum isNone:print("Warning: Please specify the server port before attempting connection.")else:print("Info: Initiating MQTT connection to %s:%d"%(self.hostname,self.portnum))self.client.username_pw_set(self.username,self.password)self.client.connect_async(self.hostname,self.portnum)self.client.loop_start()def disconnect_from_mqtt_server(self):ifself.client.is_connected():self.client.disconnect()else:print("Warning: Not connected.")self.client.loop_stop()################################################################# The callback for when the broker responds to our connection request.def on_connect(self, client, userdata, flags, rc):print("Info: Connected to server with with flags: %s, result code: %s"%(flags, rc))if rc ==0:print("Info: Connection succeeded.")elif rc >0:if rc < len(mqtt_rc_codes):print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])else:print("Warning: Connection failed with unknown error %d", rc)# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)return# The callback for when the broker responds with error messages.def on_log(client, userdata, level, buf):print("Info: on_log level %s: %s", level, userdata)returndef on_disconnect(self, client, userdata, rc):print("Info: disconnected")return# The callback for when a message has been received on a topic to which this# client is subscribed. The message variable is a MQTTMessage that describes# all of the message parameters.# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.# The payload is a binary string (bytes).# qos is an integer quality of service indicator (0,1, or 2)# mid is an integer message ID.def on_message(self, client, userdata, msg):global counter
if msg.topic =="leffen/pc_vitals":if counter ==5:
counter =0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1]= msgList[-1][0:2]
msgList[-1]= msgList[-1].strip()if msgList[0][2:]!=self.systemHostname:if(float(msgList[1])<40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])<40))):# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a')# Ring terminal bell
sys.stdout.flush()self.send_message("leffen/pc_chatter","Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!"% msgList[0])elif(float(msgList[1])>=40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=40))):# This is the case where the system is under load and getting a bit hot...self.send_message("leffen/pc_chatter","%s looks like you're running a little hot... why not take a break? I think you've earned it."% msgList[0])elif(float(msgList[1])>=70.0andfloat(msgList[2])<85.0)or \
(int(msgList[3])<85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=80))):# This is the case where the system is under load and temps are in-check...self.send_message("leffen/pc_chatter","Looks like you're on it %s... keep it up!"% msgList[0])elif msg.topic =="leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")if(float(msgList[0])<100orfloat(msgList[1])>=130):self.send_message("leffen/to_human","Hey man, looks like you need a break.")if(int(msgList[2])==0):self.send_message("leffen/to_human","Alright, if you aren't gonna take care of yourself, then we're going on strike!")elif(int(msgList[3])<7orint(msgList[3])>=12):self.send_message("leffen/to_human","Come on, man. Get better sleep!")
counter +=1print("{%s} %s"%(msg.topic, msg.payload))return################################################################def set_subscription(self,sub):ifself.client.is_connected():self.client.unsubscribe(self.subscription)try:self.client.subscribe(sub)self.subscription =subself.settings.setValue('mqtt_subscription',sub)exceptValueError:print("Error: Invalid subscription string, not changed.")self.client.subscribe(self.subscription)else:self.subscription =subself.settings.setValue('mqtt_subscription',sub)def set_topic(self,sub):self.topic =subself.settings.setValue('mqtt_topic',sub)def send_message(self, topic, payload):ifself.client.is_connected():self.client.publish(topic, payload)else:print("Error: Not connected.")self.payload = payload
self.settings.setValue('mqtt_payload', payload)################################################################def main():# Optionally add an additional root log handler to stream messages to the# terminal console.ifFalse:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)# initialize the Qt system itself
app =QtWidgets.QApplication(sys.argv)# create the main application controller
main =MainApp()
main.connect_to_mqtt_server()# run the event loop until the user is doneprint("Info: Starting event loop.")#rawCPU = os.system("sensors"whileTrue:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[19].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
gpuUtilLine = rawGPU.splitlines()[9].strip()
cpuTempstr = cpuTempLine[16:20]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
gpuUtilstr = gpuUtilLine[60:63]
cpuTemp =float(cpuTempstr)
gpuTemp =float(gpuTempstr)
cpuUtil =float(cpuUtilstr)
main.send_message("leffen/pc_vitals", main.systemHostname +","+
cpuUtilstr +","+ cpuTempstr +","+ gpuTempstr +","+ gpuUtilstr)
time.sleep(10)
sys.exit(app.exec_())################################################################# Main script follows. This sequence is executed when the script is initiated# from the command line.if __name__ =="__main__":
main()
and for Tars…
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if__name__ == "__main__":
main()
#!/usr/bin/env python3
"""A PyQt5 GUI utility to monitor and send MQTT server messages."""
################################################################
# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu>
# To the extent possible under law, the author has dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
################################################################
# standard Python libraries
from __future__ import print_function
import os, sys, struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html
# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.html
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
# documentation: https://www.eclipse.org/paho/clients/python/docs/
import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')
# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)
# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports = { 8884 : '16-223',
8885 : '16-375',
8886 : '60-223',
8887 : '62-362',
}
mqtt_rc_codes = ['Success', 'Incorrect protocol version', 'Invalid client identifier', 'Server unavailable', 'Bad username or password', 'Not authorized']
counter = 0
################################################################
class MainApp(object):
"""Main application object holding any non-GUI related state."""
def __init__(self):
# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT, self._sigint_handler)
# load any available persistent application settings
QtCore.QCoreApplication.setOrganizationName("IDeATe")
QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")
QtCore.QCoreApplication.setApplicationName('mqtt_monitor')
self.settings = QtCore.QSettings()
# uncomment to restore 'factory defaults'
# self.settings.clear()
# MQTT server settings
self.hostname = self.settings.value('mqtt_host', 'mqtt.ideate.cmu.edu')
self.systemHostname = os.popen('uname -n').read().strip()
self.portnum = self.settings.value('mqtt_port', 8887)
self.portnum = int(self.portnum)
self.username = self.settings.value('mqtt_user', 'students')
self.password = self.settings.value('mqtt_password', 'Arduino')
# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()
self.subscription = self.settings.value('mqtt_subscription', username + '/#')
# default message to send
self.topic = self.settings.value('mqtt_topic', username)
self.payload = self.settings.value('mqtt_payload', 'hello')
# Initialize the MQTT client system
self.client = mqtt.Client()
self.client.enable_logger(mqtt_log)
self.client.on_log = self.on_log
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_message = self.on_message
self.client.tls_set()
return
################################################################
def app_is_exiting(self):
if self.client.is_connected():
self.client.disconnect()
self.client.loop_stop()
def _sigint_handler(self, signal, frame):
print("Keyboard interrupt caught, running close handlers...")
self.app_is_exiting()
sys.exit(0)
################################################################
def set_server_name(self, name):
self.hostname = name
self.settings.setValue('mqtt_host', name)
def set_server_port(self, value):
self.portnum = value
self.settings.setValue('mqtt_port', self.portnum)
def set_username(self, name):
self.username = name
self.settings.setValue('mqtt_user', name)
def set_password(self, name):
self.password = name
self.settings.setValue('mqtt_password', name)
def connect_to_mqtt_server(self):
if self.client.is_connected():
return
else:
if self.portnum is None:
print("Warning: Please specify the server port before attempting connection.")
else:
print("Info: Initiating MQTT connection to %s:%d" % (self.hostname, self.portnum))
self.client.username_pw_set(self.username, self.password)
self.client.connect_async(self.hostname, self.portnum)
self.client.loop_start()
def disconnect_from_mqtt_server(self):
if self.client.is_connected():
self.client.disconnect()
else:
print("Warning: Not connected.")
self.client.loop_stop()
################################################################
# The callback for when the broker responds to our connection request.
def on_connect(self, client, userdata, flags, rc):
print("Info: Connected to server with with flags: %s, result code: %s" % (flags, rc))
if rc == 0:
print("Info: Connection succeeded.")
elif rc > 0:
if rc < len(mqtt_rc_codes):
print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])
else:
print("Warning: Connection failed with unknown error %d", rc)
# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)
return
# The callback for when the broker responds with error messages.
def on_log(client, userdata, level, buf):
print("Info: on_log level %s: %s", level, userdata)
return
def on_disconnect(self, client, userdata, rc):
print("Info: disconnected")
return
# The callback for when a message has been received on a topic to which this
# client is subscribed. The message variable is a MQTTMessage that describes
# all of the message parameters.
# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.
# The payload is a binary string (bytes).
# qos is an integer quality of service indicator (0,1, or 2)
# mid is an integer message ID.
def on_message(self, client, userdata, msg):
global counter
if msg.topic == "leffen/pc_vitals":
if counter == 6:
counter = 0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1] = msgList[-1][0:2]
msgList[-1] = msgList[-1].strip()
if msgList[0][2:] != self.systemHostname:
if (float(msgList[1]) < 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) < 40))):
# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a') # Ring terminal bell
sys.stdout.flush()
self.send_message("leffen/pc_chatter", "Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!" % msgList[0])
elif (float(msgList[1]) >= 40.0 and float(msgList[2]) >= 85.0) or \
(int(msgList[3]) >= 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 40))):
# This is the case where the system is under load and getting a bit hot...
self.send_message("leffen/pc_chatter", "%s looks like you're running a little hot... why not take a break? I think you've earned it." % msgList[0])
elif (float(msgList[1]) >= 80.0 and float(msgList[2]) < 85.0) or \
(int(msgList[3]) < 85 and (len(msgList) < 5 or (msgList[4] != "" and int(msgList[4]) >= 80))):
# This is the case where the system is under load and temps are in-check...
self.send_message("leffen/pc_chatter", "Looks like you're on it %s... keep it up!" % msgList[0])
elif msg.topic == "leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")
if (float(msgList[0]) < 100 or float(msgList[1]) >= 130):
self.send_message("leffen/to_human", "Hey man, looks like you need a break.")
if (int(msgList[2]) == 0):
self.send_message("leffen/to_human", "Alright, if you aren't gonna take care of yourself, then we're going on strike!")
elif (int(msgList[3]) < 7 or int(msgList[3]) >= 12):
self.send_message("leffen/to_human", "Come on, man. Get better sleep!")
counter += 1
print("{%s} %s" % (msg.topic, msg.payload))
return
################################################################
def set_subscription(self, sub):
if self.client.is_connected():
self.client.unsubscribe(self.subscription)
try:
self.client.subscribe(sub)
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
except ValueError:
print("Error: Invalid subscription string, not changed.")
self.client.subscribe(self.subscription)
else:
self.subscription = sub
self.settings.setValue('mqtt_subscription', sub)
def set_topic(self, sub):
self.topic = sub
self.settings.setValue('mqtt_topic', sub)
def send_message(self, topic, payload):
if self.client.is_connected():
self.client.publish(topic, payload)
else:
print("Error: Not connected.")
self.payload = payload
self.settings.setValue('mqtt_payload', payload)
################################################################
def main():
# Optionally add an additional root log handler to stream messages to the
# terminal console.
if False:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)
# initialize the Qt system itself
app = QtWidgets.QApplication(sys.argv)
# create the main application controller
main = MainApp()
main.connect_to_mqtt_server()
# run the event loop until the user is done
print("Info: Starting event loop.")
while True:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[31].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
cpuTempstr = cpuTempLine[15:19]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
cpuTemp = float(cpuTempstr)
gpuTemp = float(gpuTempstr)
main.send_message("leffen/pc_vitals", main.systemHostname + "," +
cpuUtilstr + "," + cpuTempstr + "," + gpuTempstr)
time.sleep(10)
sys.exit(app.exec_())
################################################################
# Main script follows. This sequence is executed when the script is initiated
# from the command line.
if __name__ == "__main__":
main()
#!/usr/bin/env python3"""A PyQt5 GUI utility to monitor and send MQTT server messages."""################################################################# Written in 2018-2020 by Garth Zeglin <garthz@cmu.edu># To the extent possible under law, the author has dedicated all copyright# and related and neighboring rights to this software to the public domain# worldwide. This software is distributed without any warranty.# You should have received a copy of the CC0 Public Domain Dedication along with this software.# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.################################################################# standard Python librariesfrom __future__ import print_function
import os, sys,struct, time, logging, functools, queue, signal, getpass, psutil
# documentation: https://doc.qt.io/qt-5/index.html# documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/index.htmlfromPyQt5importQtCore,QtGui,QtWidgets,QtNetwork# documentation: https://www.eclipse.org/paho/clients/python/docs/import paho.mqtt.client as mqtt
# default logging output
log = logging.getLogger('main')# logger to pass to the MQTT library
mqtt_log = logging.getLogger('mqtt')
mqtt_log.setLevel(logging.WARNING)# IDeATE server instances, as per https://mqtt.ideate.cmu.edu/#ports
ideate_ports ={8884:'16-223',8885:'16-375',8886:'60-223',8887:'62-362',}
mqtt_rc_codes =['Success','Incorrect protocol version','Invalid client identifier','Server unavailable','Bad username or password','Not authorized']
counter =0################################################################classMainApp(object):"""Main application object holding any non-GUI related state."""def __init__(self):# Attach a handler to the keyboard interrupt (control-C).
signal.signal(signal.SIGINT,self._sigint_handler)# load any available persistent application settingsQtCore.QCoreApplication.setOrganizationName("IDeATe")QtCore.QCoreApplication.setOrganizationDomain("ideate.cmu.edu")QtCore.QCoreApplication.setApplicationName('mqtt_monitor')self.settings =QtCore.QSettings()# uncomment to restore 'factory defaults'# self.settings.clear()# MQTT server settingsself.hostname =self.settings.value('mqtt_host','mqtt.ideate.cmu.edu')self.systemHostname = os.popen('uname -n').read().strip()self.portnum =self.settings.value('mqtt_port',8887)self.portnum =int(self.portnum)self.username =self.settings.value('mqtt_user','students')self.password =self.settings.value('mqtt_password','Arduino')# Create a default subscription based on the username. The hash mark is a wildcard.
username = getpass.getuser()self.subscription =self.settings.value('mqtt_subscription', username +'/#')# default message to sendself.topic =self.settings.value('mqtt_topic', username)self.payload =self.settings.value('mqtt_payload','hello')# Initialize the MQTT client systemself.client = mqtt.Client()self.client.enable_logger(mqtt_log)self.client.on_log =self.on_log
self.client.on_connect =self.on_connect
self.client.on_disconnect =self.on_disconnect
self.client.on_message =self.on_message
self.client.tls_set()return################################################################def app_is_exiting(self):ifself.client.is_connected():self.client.disconnect()self.client.loop_stop()def _sigint_handler(self, signal, frame):print("Keyboard interrupt caught, running close handlers...")self.app_is_exiting()
sys.exit(0)################################################################def set_server_name(self, name):self.hostname = name
self.settings.setValue('mqtt_host', name)def set_server_port(self,value):self.portnum =valueself.settings.setValue('mqtt_port',self.portnum)def set_username(self, name):self.username = name
self.settings.setValue('mqtt_user', name)def set_password(self, name):self.password = name
self.settings.setValue('mqtt_password', name)def connect_to_mqtt_server(self):ifself.client.is_connected():returnelse:ifself.portnum isNone:print("Warning: Please specify the server port before attempting connection.")else:print("Info: Initiating MQTT connection to %s:%d"%(self.hostname,self.portnum))self.client.username_pw_set(self.username,self.password)self.client.connect_async(self.hostname,self.portnum)self.client.loop_start()def disconnect_from_mqtt_server(self):ifself.client.is_connected():self.client.disconnect()else:print("Warning: Not connected.")self.client.loop_stop()################################################################# The callback for when the broker responds to our connection request.def on_connect(self, client, userdata, flags, rc):print("Info: Connected to server with with flags: %s, result code: %s"%(flags, rc))if rc ==0:print("Info: Connection succeeded.")elif rc >0:if rc < len(mqtt_rc_codes):print("Warning: Connection failed with error: %s", mqtt_rc_codes[rc])else:print("Warning: Connection failed with unknown error %d", rc)# Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
client.subscribe(self.subscription)return# The callback for when the broker responds with error messages.def on_log(client, userdata, level, buf):print("Info: on_log level %s: %s", level, userdata)returndef on_disconnect(self, client, userdata, rc):print("Info: disconnected")return# The callback for when a message has been received on a topic to which this# client is subscribed. The message variable is a MQTTMessage that describes# all of the message parameters.# Some useful MQTTMessage fields: topic, payload, qos, retain, mid, properties.# The payload is a binary string (bytes).# qos is an integer quality of service indicator (0,1, or 2)# mid is an integer message ID.def on_message(self, client, userdata, msg):global counter
if msg.topic =="leffen/pc_vitals":if counter ==6:
counter =0
msgList = str(msg.payload)
msgList = msgList.split(",")
msgList[-1]= msgList[-1][0:2]
msgList[-1]= msgList[-1].strip()if msgList[0][2:]!=self.systemHostname:if(float(msgList[1])<40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])<40))):# This is the critical case, where something is probably seriously wrong with the system...
sys.stdout.write('\a')# Ring terminal bell
sys.stdout.flush()self.send_message("leffen/pc_chatter","Hey %s you good? No? I'm gonna call the Fire Apartment then... WOO-ooo-WOO-ooo!"% msgList[0])elif(float(msgList[1])>=40.0andfloat(msgList[2])>=85.0)or \
(int(msgList[3])>=85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=40))):# This is the case where the system is under load and getting a bit hot...self.send_message("leffen/pc_chatter","%s looks like you're running a little hot... why not take a break? I think you've earned it."% msgList[0])elif(float(msgList[1])>=80.0andfloat(msgList[2])<85.0)or \
(int(msgList[3])<85and(len(msgList)<5or(msgList[4]!=""andint(msgList[4])>=80))):# This is the case where the system is under load and temps are in-check...self.send_message("leffen/pc_chatter","Looks like you're on it %s... keep it up!"% msgList[0])elif msg.topic =="leffen/human_vitals":
msgList = str(msg.payload)
msgList = msgList.split(",")if(float(msgList[0])<100orfloat(msgList[1])>=130):self.send_message("leffen/to_human","Hey man, looks like you need a break.")if(int(msgList[2])==0):self.send_message("leffen/to_human","Alright, if you aren't gonna take care of yourself, then we're going on strike!")elif(int(msgList[3])<7orint(msgList[3])>=12):self.send_message("leffen/to_human","Come on, man. Get better sleep!")
counter +=1print("{%s} %s"%(msg.topic, msg.payload))return################################################################def set_subscription(self,sub):ifself.client.is_connected():self.client.unsubscribe(self.subscription)try:self.client.subscribe(sub)self.subscription =subself.settings.setValue('mqtt_subscription',sub)exceptValueError:print("Error: Invalid subscription string, not changed.")self.client.subscribe(self.subscription)else:self.subscription =subself.settings.setValue('mqtt_subscription',sub)def set_topic(self,sub):self.topic =subself.settings.setValue('mqtt_topic',sub)def send_message(self, topic, payload):ifself.client.is_connected():self.client.publish(topic, payload)else:print("Error: Not connected.")self.payload = payload
self.settings.setValue('mqtt_payload', payload)################################################################def main():# Optionally add an additional root log handler to stream messages to the# terminal console.ifFalse:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
logging.getLogger().addHandler(console_handler)# initialize the Qt system itself
app =QtWidgets.QApplication(sys.argv)# create the main application controller
main =MainApp()
main.connect_to_mqtt_server()# run the event loop until the user is doneprint("Info: Starting event loop.")whileTrue:
rawCPUTemp = os.popen('sensors').read()
rawGPU = os.popen('nvidia-smi').read()
cpuTempLine = rawCPUTemp.splitlines()[31].strip()
gpuTempLine = rawGPU.splitlines()[9].strip()
cpuUtil = psutil.cpu_percent()
cpuTempstr = cpuTempLine[15:19]
gpuTempstr = gpuTempLine[8:10]
cpuUtilstr = str(cpuUtil)
cpuTemp =float(cpuTempstr)
gpuTemp =float(gpuTempstr)
main.send_message("leffen/pc_vitals", main.systemHostname +","+
cpuUtilstr +","+ cpuTempstr +","+ gpuTempstr)
time.sleep(10)
sys.exit(app.exec_())################################################################# Main script follows. This sequence is executed when the script is initiated# from the command line.if __name__ =="__main__":
main()
Here’s the code that runs on the Arduino. It’s nothing fancy–it’s just sending the relevant data as a comma-separated string over serial, which is then picked up by the computer that the Arduino is connected to.
/*
Human Gateway (62-362 Project 3)
Seth Geiser (sgeiser)
Collaboration: Same as Project 2
GSR Sensor: https://wiki.seeedstudio.com/Grove-GSR_Sensor/
MAX30102 PulseOx: Example 5: Heart-rate
Keypad: Same as Project 1, Workaholic's Clock
DFPlayer Mini: Same as Project 1, Workaholic's Clock
This code initializes a keypad, 20x4 I2C LCD, Pulse
Oximeter, Grove GSR sensor, and a DFRobot DFPlayer
Mini. The keypad is for entering when I went to sleep an
how long I slept. The GSR sensor measures galvanic skin
response, similar to a polygraph test. The Pulse Oximeter
is currently only being used as a heart-rate monitor,
since the SPO2 functionality appears to not be
working right now (tested with the SPO2 example
code).
Pin mapping:
pin | mode | description
---------|------------|------------
SDA/SCL I2C MAX30102 PulseOx + 20x4 I2C LCD
2-8 Drive/Sense Keypad
9 RX DFPlayer Mini TX
10 TX DFPlayer Mini RX
A0 INPUT GSR Sensor (through Grove shield)
*/
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <MAX30105.h>
#include <heartRate.h>
SoftwareSerial mySoftwareSerial(9, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
MAX30105 particleSensor;
LiquidCrystal_I2C lcd(0x27, 20, 4);
// set the LCD address to 0x27 for 20x4 display
char customKey = 0;
const byte ROWS = 4;
const byte COLS = 3;
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
byte rowPins[ROWS] = {5, 3, 2, 7};
byte colPins[COLS] = {4, 8, 6};
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
const byte RATE_SIZE = 4; // Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; // Array of heart rates
byte rateSpot = 0;
long lastBeat = 0; // Time at which the last beat occurred
float beatsPerMinute;
int beatAvg;
const int GSR = A0;
int sensorValue = 0;
int gsr_average = 0;
int j = 0;
int sleepStart;
int sleepAmount;
// For accepting input from the keypad, these get converted
// to the ints above...
char sleep[2];
void(* resetFunc) (void) = 0; // reset the Arduino when done
void setup() {
lcd.init();
lcd.backlight();
mySoftwareSerial.begin(9600);
Serial.begin(115200);
Serial.println("Human,Arduino UNO R3 SMD Connected...");
lcd.setCursor(0, 0);
lcd.print(F("Arduino UNO R3 SMD"));
lcd.setCursor(0, 1);
lcd.print(F("Starting DFPlayer..."));
lcd.setCursor(0, 2);
lcd.print(F("Starting MAX30102..."));
lcd.setCursor(0, 3);
lcd.print(F("Please wait..."));
delay(2000);
// Initialize sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST))
// Use default I2C port, 400kHz speed
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("MAX30102 not found."));
lcd.setCursor(0, 1);
lcd.print(F("Please check wiring."));
while (1);
}
particleSensor.setup();
// Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(0x0A);
// Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeGreen(0);
// Turn off Green LED
if (!myDFPlayer.begin(mySoftwareSerial)) {
// Use softwareSerial to communicate with mp3.
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Unable to begin:"));
lcd.setCursor(0, 1);
lcd.print(F("Please check wiring!"));
lcd.setCursor(0, 2);
lcd.print(F("Please insert SD!"));
}
lcd.setCursor(0, 3);
lcd.print(F("DFPlayerMini online."));
myDFPlayer.volume(20); // Set volume value. From 0 to 30
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("CMU IDeATe PhysComp"));
lcd.setCursor(0, 1);
lcd.print(F("Human Gateway"));
lcd.setCursor(0, 2);
lcd.print(F("Powered by sgeiser"));
lcd.setCursor(0, 3);
lcd.print(F("BOOTING DEVICE..."));
delay(2500);
myDFPlayer.play(1); // 0001_ps2.mp3
delay(2500);
lcd.clear();
customKey = customKeypad.getKey();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Input when you went"));
lcd.setCursor(0, 1);
lcd.print(F("to sleep and how"));
lcd.setCursor(0, 2);
lcd.print(F("long you slept in"));
lcd.setCursor(0, 3);
lcd.print(F("24-hour time."));
delay(5000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("If any value is less"));
lcd.setCursor(0, 1);
lcd.print(F("than 10, then prefix"));
lcd.setCursor(0, 2);
lcd.print(F("with a zero."));
delay(5000);
lcd.clear();
inputSleepPattern();
}
void loop() {
customKey = customKeypad.getKey();
long sum = 0;
for (int i = 0; i < 10; i++) {
sensorValue = analogRead(GSR);
sum += sensorValue;
}
gsr_average = sum / 10;
long irValue = particleSensor.getIR();
if (checkForBeat(irValue) == true)
{
// We sensed a beat!
long delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
if (beatsPerMinute < 255 && beatsPerMinute > 20)
{
rates[rateSpot++] = (byte)beatsPerMinute;
// Store this reading in the array
rateSpot %= RATE_SIZE; // Wrap variable
// Take average of readings
beatAvg = 0;
for (byte x = 0 ; x < RATE_SIZE ; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}
lcd.setCursor(0, 0);
lcd.print((String)"Average GSR: " + gsr_average + " ");
lcd.setCursor(0, 1);
lcd.print((String)"Average BPM: " + beatAvg + " ");
lcd.setCursor(0, 2);
lcd.print(F("#: Assess vitals"));
if (customKey == '#') {
lcd.clear();
assess();
}
}
void inputSleepStart() {
lcd.setCursor(0, 0);
lcd.print(F("Sleep start: "));
while (j < 2) {
char key = customKeypad.getKey();
if (key) {
sleep[j++] = key;
lcd.print(key);
}
key = 0;
}
sleepStart = atoi(sleep);
// Convert the char array to int and store it. Note
// that atoi is deprecated, am going to change it soon.
j = 0;
}
void inputSleepAmount() {
lcd.setCursor(0, 1);
lcd.print(F("Sleep amount: "));
while (j < 2) {
char key = customKeypad.getKey();
if (key) {
sleep[j++] = key;
lcd.print(key);
}
key = 0;
}
sleepAmount = atoi(sleep);
// Convert the char array to int and store it. Note
// that atoi is deprecated, am going to change it soon.
j = 0;
}
// I am not fond of how I wrote these two functions.
// I hope to condense them.
void inputSleepPattern() {
inputSleepStart();
inputSleepAmount();
lcd.clear();
}
void assess() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.setCursor(0, 2);
lcd.print(F("Checking your vitals"));
delay(2500);
if (gsr_average > 500) {
lcd.setCursor(0, 3);
lcd.print(F("Could not read GSR."));
delay(2500);
}
if (beatAvg <= 40) {
lcd.setCursor(0, 3);
lcd.print(F("Could not get pulse."));
delay(2500);
}
if ((gsr_average < 150 || beatAvg >= 130) && (sleepStart <= 6 || (sleepAmount < 7 || sleepAmount > 12))) {
allBad();
}
else if (gsr_average < 150 || beatAvg >= 130) {
stressed();
}
else if (sleepStart <= 6 || (sleepAmount < 7 || sleepAmount > 12)) {
badSleep();
}
else {
allGood();
}
Serial.println(String(gsr_average) + "," + String(beatAvg) + "," + String(sleepStart) + "," + String(sleepAmount));
resetFunc();
}
void badSleep() {
lcd.setCursor(0, 3);
lcd.print(F("Get better sleep! "));
delay(5000);
}
void stressed() {
lcd.setCursor(0, 3);
lcd.print(F("You're stressed..."));
delay(2500);
lcd.setCursor(0, 3);
lcd.print(F("Take a break, dude."));
delay(5000);
}
void allBad() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Oh my God, it's all"));
lcd.setCursor(0, 1);
lcd.print(F("broken! Seriously,"));
lcd.setCursor(0, 2);
lcd.print(F("you need to get on"));
lcd.setCursor(0, 3);
lcd.print(F("top of this..."));
myDFPlayer.play(5); // 0005_battery_low.mp3
delay(5000);
}
void allGood() {
lcd.setCursor(0, 3);
lcd.print(F("Your vitals are OK!"));
delay(2500);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Nicely done! Your"));
lcd.setCursor(0, 1);
lcd.print(F("vitals look alright,"));
lcd.setCursor(0, 2);
lcd.print(F("and your systems are"));
lcd.setCursor(0, 3);
lcd.print(F("running smoothly!"));
myDFPlayer.play(3); // 0003_scatman_world.mp3
delay(13000);
}
/*
Human Gateway (62-362 Project 3)
Seth Geiser (sgeiser)
Collaboration: Same as Project 2
GSR Sensor: https://wiki.seeedstudio.com/Grove-GSR_Sensor/
MAX30102 PulseOx: Example 5: Heart-rate
Keypad: Same as Project 1, Workaholic's Clock
DFPlayer Mini: Same as Project 1, Workaholic's Clock
This code initializes a keypad, 20x4 I2C LCD, Pulse
Oximeter, Grove GSR sensor, and a DFRobot DFPlayer
Mini. The keypad is for entering when I went to sleep an
how long I slept. The GSR sensor measures galvanic skin
response, similar to a polygraph test. The Pulse Oximeter
is currently only being used as a heart-rate monitor,
since the SPO2 functionality appears to not be
working right now (tested with the SPO2 example
code).
Pin mapping:
pin | mode | description
---------|------------|------------
SDA/SCL I2C MAX30102 PulseOx + 20x4 I2C LCD
2-8 Drive/Sense Keypad
9 RX DFPlayer Mini TX
10 TX DFPlayer Mini RX
A0 INPUT GSR Sensor (through Grove shield)
*/#include<SoftwareSerial.h>#include<DFRobotDFPlayerMini.h>#include<Wire.h>#include<LiquidCrystal_I2C.h>#include<Keypad.h>#include<MAX30105.h>#include<heartRate.h>SoftwareSerial mySoftwareSerial(9,10);// RX, TXDFRobotDFPlayerMini myDFPlayer;
MAX30105 particleSensor;LiquidCrystal_I2C lcd(0x27,20,4);// set the LCD address to 0x27 for 20x4 displaychar customKey =0;constbyte ROWS =4;constbyte COLS =3;char hexaKeys[ROWS][COLS]={{'1','2','3'},{'4','5','6'},{'7','8','9'},{'*','0','#'}};byte rowPins[ROWS]={5,3,2,7};byte colPins[COLS]={4,8,6};Keypad customKeypad =Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);constbyte RATE_SIZE =4;// Increase this for more averaging. 4 is good.byte rates[RATE_SIZE];// Array of heart ratesbyte rateSpot =0;long lastBeat =0;// Time at which the last beat occurredfloat beatsPerMinute;int beatAvg;constint GSR = A0;int sensorValue =0;int gsr_average =0;int j =0;int sleepStart;int sleepAmount;// For accepting input from the keypad, these get converted// to the ints above...char sleep[2];void(* resetFunc)(void)=0;// reset the Arduino when donevoid setup(){
lcd.init();
lcd.backlight();
mySoftwareSerial.begin(9600);Serial.begin(115200);Serial.println("Human,Arduino UNO R3 SMD Connected...");
lcd.setCursor(0,0);
lcd.print(F("Arduino UNO R3 SMD"));
lcd.setCursor(0,1);
lcd.print(F("Starting DFPlayer..."));
lcd.setCursor(0,2);
lcd.print(F("Starting MAX30102..."));
lcd.setCursor(0,3);
lcd.print(F("Please wait..."));
delay(2000);// Initialize sensorif(!particleSensor.begin(Wire, I2C_SPEED_FAST))// Use default I2C port, 400kHz speed{
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("MAX30102 not found."));
lcd.setCursor(0,1);
lcd.print(F("Please check wiring."));while(1);}
particleSensor.setup();// Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(0x0A);// Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeGreen(0);// Turn off Green LEDif(!myDFPlayer.begin(mySoftwareSerial)){// Use softwareSerial to communicate with mp3.
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("Unable to begin:"));
lcd.setCursor(0,1);
lcd.print(F("Please check wiring!"));
lcd.setCursor(0,2);
lcd.print(F("Please insert SD!"));}
lcd.setCursor(0,3);
lcd.print(F("DFPlayerMini online."));
myDFPlayer.volume(20);// Set volume value. From 0 to 30
delay(2000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("CMU IDeATe PhysComp"));
lcd.setCursor(0,1);
lcd.print(F("Human Gateway"));
lcd.setCursor(0,2);
lcd.print(F("Powered by sgeiser"));
lcd.setCursor(0,3);
lcd.print(F("BOOTING DEVICE..."));
delay(2500);
myDFPlayer.play(1);// 0001_ps2.mp3
delay(2500);
lcd.clear();
customKey = customKeypad.getKey();
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("Input when you went"));
lcd.setCursor(0,1);
lcd.print(F("to sleep and how"));
lcd.setCursor(0,2);
lcd.print(F("long you slept in"));
lcd.setCursor(0,3);
lcd.print(F("24-hour time."));
delay(5000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("If any value is less"));
lcd.setCursor(0,1);
lcd.print(F("than 10, then prefix"));
lcd.setCursor(0,2);
lcd.print(F("with a zero."));
delay(5000);
lcd.clear();
inputSleepPattern();}void loop(){
customKey = customKeypad.getKey();long sum =0;for(int i =0; i <10; i++){
sensorValue = analogRead(GSR);
sum += sensorValue;}
gsr_average = sum /10;long irValue = particleSensor.getIR();if(checkForBeat(irValue)==true){// We sensed a beat!long delta = millis()- lastBeat;
lastBeat = millis();
beatsPerMinute =60/(delta /1000.0);if(beatsPerMinute <255&& beatsPerMinute >20){
rates[rateSpot++]=(byte)beatsPerMinute;// Store this reading in the array
rateSpot %= RATE_SIZE;// Wrap variable// Take average of readings
beatAvg =0;for(byte x =0; x < RATE_SIZE ; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;}}
lcd.setCursor(0,0);
lcd.print((String)"Average GSR: "+ gsr_average +" ");
lcd.setCursor(0,1);
lcd.print((String)"Average BPM: "+ beatAvg +" ");
lcd.setCursor(0,2);
lcd.print(F("#: Assess vitals"));if(customKey =='#'){
lcd.clear();
assess();}}void inputSleepStart(){
lcd.setCursor(0,0);
lcd.print(F("Sleep start: "));while(j <2){char key = customKeypad.getKey();if(key){
sleep[j++]= key;
lcd.print(key);}
key =0;}
sleepStart = atoi(sleep);// Convert the char array to int and store it. Note// that atoi is deprecated, am going to change it soon.
j =0;}void inputSleepAmount(){
lcd.setCursor(0,1);
lcd.print(F("Sleep amount: "));while(j <2){char key = customKeypad.getKey();if(key){
sleep[j++]= key;
lcd.print(key);}
key =0;}
sleepAmount = atoi(sleep);// Convert the char array to int and store it. Note// that atoi is deprecated, am going to change it soon.
j =0;}// I am not fond of how I wrote these two functions.// I hope to condense them.void inputSleepPattern(){
inputSleepStart();
inputSleepAmount();
lcd.clear();}void assess(){
lcd.clear();
lcd.setCursor(0,0);
lcd.setCursor(0,2);
lcd.print(F("Checking your vitals"));
delay(2500);if(gsr_average >500){
lcd.setCursor(0,3);
lcd.print(F("Could not read GSR."));
delay(2500);}if(beatAvg <=40){
lcd.setCursor(0,3);
lcd.print(F("Could not get pulse."));
delay(2500);}if((gsr_average <150|| beatAvg >=130)&&(sleepStart <=6||(sleepAmount <7|| sleepAmount >12))){
allBad();}elseif(gsr_average <150|| beatAvg >=130){
stressed();}elseif(sleepStart <=6||(sleepAmount <7|| sleepAmount >12)){
badSleep();}else{
allGood();}Serial.println(String(gsr_average)+","+String(beatAvg)+","+String(sleepStart)+","+String(sleepAmount));
resetFunc();}void badSleep(){
lcd.setCursor(0,3);
lcd.print(F("Get better sleep! "));
delay(5000);}void stressed(){
lcd.setCursor(0,3);
lcd.print(F("You're stressed..."));
delay(2500);
lcd.setCursor(0,3);
lcd.print(F("Take a break, dude."));
delay(5000);}void allBad(){
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("Oh my God, it's all"));
lcd.setCursor(0,1);
lcd.print(F("broken! Seriously,"));
lcd.setCursor(0,2);
lcd.print(F("you need to get on"));
lcd.setCursor(0,3);
lcd.print(F("top of this..."));
myDFPlayer.play(5);// 0005_battery_low.mp3
delay(5000);}void allGood(){
lcd.setCursor(0,3);
lcd.print(F("Your vitals are OK!"));
delay(2500);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("Nicely done! Your"));
lcd.setCursor(0,1);
lcd.print(F("vitals look alright,"));
lcd.setCursor(0,2);
lcd.print(F("and your systems are"));
lcd.setCursor(0,3);
lcd.print(F("running smoothly!"));
myDFPlayer.play(3);// 0003_scatman_world.mp3
delay(13000);}
Comments are closed.