Closed puterboy closed 1 year ago
It would be fairly easy to create a single file plugin to run a specific command like you mention and make it available to the plugin. I'd be happy to program up an example of this and provide in the repository as an example like I have done for a couple of other use cases. Do you have an example command that I could use for that?
It would be fairly easy to create a single file plugin to run a specific command like you mention and make it available to the plugin. I'd be happy to program up an example of this and provide in the repository as an example like I have done for a couple of other use cases. Do you have an example command that I could use for that?
Thanks. I wrote a python script (embedded in a bash script that sets up a virtual environment) to read temperatures from my temperature sensor chip with a few command line switches to get Fahrenheit vs. Celsius.
The command is just the name of the script plus the Celsius flag
PrusaTemperature.py -c
Wouldn't it be easier to just have a text box in your plugin that allows one to enter an arbitrary command string rather than having to create a single file plugin for each use case? Or am I missing something...
Thanks for all your contributions!
I don't necessarily want to add an open command line box. Because then someone's going to ask for two command line options, etc. and it snowballs into feature creep. Since your running python script it would actually be smarter just to wrap it in a plugin.
Actually, it's a shell script wrapper for a python script whose first couple of lines launch a venv before 'exec'ing the python script so technically it's a shell script.
The first lines are:
#!/usr/bin/sh
"exec" "$(dirname $(readlink -f $0))/.venv/bin/python3" "$0" "$@"
Then the python code follows...
I'm sure there is a way to do this elegantly in pure python, but I am a python-beginner. Do you know how to do this natively in python?
(Also, there are other sensors that I may want access from a bash script that reads from GPIO's... so would be good to have a general framework too)
Yeah, so it's just executing python command line and piping the python code through. Can you share the whole thing possibly?
Here is my code (though I don't really know Python - I much prefer Perl) It's rough so no argument or error checking.
If you just want to get a Celsius number run with arguments "2 1"
#!/usr/bin/sh
"exec" "$(dirname $(readlink -f $0))/.venv/bin/python3" "$0" "$@"
################################################################################
###Read temperature of Prusa Enclosure
################################################################################
import sys
import board
import adafruit_mcp9808
i2c = board.I2C() # uses board.SCL and board.SDA
addr = 0x18 #Default address with all set to low
#addr = i2c.scan()[0] #Get first i2c
mcp = adafruit_mcp9808.MCP9808(i2c, address=addr)
tempC = mcp.temperature
tempF = tempC * 1.8 + 32
if len(sys.argv) > 2 :
degF = ""
degC = ""
else: #\xb0 is the unicode degree sign
degF = "\xb0F"
degC = "\xb0C"
if len(sys.argv) > 1 and sys.argv[1] == "1" :
print("%.1f%s" % (tempF, degF))
elif len(sys.argv) > 1 and sys.argv[1] == "2" :
print("%.1f%s" % (tempC, degC))
else :
print("%.1f%s (%.1f%s)" % (tempF, degF, tempC, degC))
thanks. this is actually trickier than I thought using the adafruit circuitpython modules. do you still have the steps you used to install this in the venv? Not sure if my error is because I don't have a device or the modules aren't installed properly in my pi.
Install was simple for me:
python3 -m venv .venv
source .venv/bin/activate
pip3 install pip --upgrade
pip3 install adafruit-circuitpython-mcp9808
As an alternate "kludge" could you create a stripped-down plugin that allowed you to do a system call on an arbitrary script, using something like os.system()
I actually just realized I was installing the modules on a different pi than I was using the plugin on...lol. But now I can't get to either of them from work, so will play with it a bit tonight after getting back home.
ok, definitely works when you install the modules in the right spot. of course, I get i2c errors since I don't have the device. SSH to your pi and run these commands one at a time. Adjust the pi
username to match your installation. These command assume default octopi install.
sudo apt-get install -y i2c-tools libgpiod-dev
/home/pi/oprint/bin/pip install adafruit-blinka
/home/pi/oprint/bin/pip install adafruit-circuitpython-mcp9808
This will install the required dependencies into OctoPrint's venv.
Then in OctoPrint's plugin manager > get more, copy/paste the URL below into ...from URL and click install.
https://github.com/jneilliii/OctoPrint-PlotlyTempGraph/raw/rc/MCP9808Graph.py
Once installed it will default to Celsius. If you want to have in Fahrenheit run this command in SSH.
/home/pi/oprint/bin/octoprint config set --bool plugins.MCP9808Graph.use_fahrenheit True
Thanks. And what do I need to do to configure the plugin - and specifically to get PlotlyTempGraph to recognize it (i.e what name should I use?)
And is it sufficient to download the plugin manually and put it the Octoprint venv plugin directory?
once it's installed it should automatically pick-up the new temperature, assuming it is reporting. there is nothing to change outside of that one setting via commandline if you want to use fahrenheit values instead of celsius. if you want to download and install, you would actually put it into the basedir for octoprint under the plugins subfolder. the default for that is /home/pi/.octoprint/plugins
, but like I mentioned in my last post you can just copy/paste the URL into plugin manager > get more > ...fromURL and that will install it where it needs to be automatically.
Thanks. I actually ended up writing my own script since I didn't like the idea of the venv dependency and the need to duplicate all the Adafruit python libraries in Octoprint.
So based on the method used to access the MPC9808 in the OctoPrint Enclosure plugin and some code cleanup from reading the manpages from the chip, I was able to get the following "native" code to work based on your framework that doesn't require any additional python library install for me.
# coding=utf-8
################################################################################
### MCP9808 Sensor Plugin for Plotty Temp Graph plugin
### Based on plugin structure provided by jneilliii
### Based on MCP9808 code provided by Adafruit and from Octoprint-Enclosure
### Plus reference to datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf
###
### Jeffrey J. Kosowsky
### August 7, 2022
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
from octoprint.util import RepeatedTimer
import smbus
# default I2C address for device.
MCP9808_I2CADDR_DEFAULT = 0x18
# register addresses.
MCP9808_REG_CONFIG = 0x01
MCP9808_REG_UPPER_TEMP = 0x02
MCP9808_REG_LOWER_TEMP = 0x03
MCP9808_REG_CRIT_TEMP = 0x04
MCP9808_REG_AMBIENT_TEMP = 0x05
MCP9808_REG_MANUF_ID = 0x06
MCP9808_REG_DEVICE_ID = 0x07
MCP9808_REG_RESOLUTION = 0x08
# configuration register values.
MCP9808_REG_CONFIG_CONTCONV = 0x0000
MCP9808_REG_CONFIG_SHUTDOWN = 0x0100
MCP9808_REG_CONFIG_CRITLOCKED = 0x0080
MCP9808_REG_CONFIG_WINLOCKED = 0x0040
MCP9808_REG_CONFIG_INTCLR = 0x0020
MCP9808_REG_CONFIG_ALERTSTAT = 0x0010
MCP9808_REG_CONFIG_ALERTCTRL = 0x0008
MCP9808_REG_CONFIG_ALERTSEL = 0x0002
MCP9808_REG_CONFIG_ALERTPOL = 0x0002
MCP9808_REG_CONFIG_ALERTMODE = 0x0001
# other config values (JJK)
MCP9808_RES_MEDIUM = 0x01
MCP9808_RES_HIGH = 0x02
MCP9808_RES_PRECISION = 0x03
DEFAULT_SMBUS_I2C_NUMBER = 0x01 #Default (first) i2c bus
DEFAULT_UPDATE_PERIOD = 2
class MCP9808Graph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.bus = smbus.SMBus(DEFAULT_SMBUS_I2C_NUMBER) #Use default (first) SMBus
self.addr = MCP9808_I2CADDR_DEFAULT #Default address with all set to low (0x18)
config = [MCP9808_REG_CONFIG_CONTCONV, 0x00] #Continuous conversion mode, power-up default
self.bus.write_i2c_block_data(self.addr, MCP9808_REG_CONFIG, config)
self.bus.write_byte_data(self.addr, MCP9808_REG_RESOLUTION, MCP9808_RES_PRECISION) #High precision +0.0625 degC, 0x03(03)
def get_settings_defaults(self):
return {"convertTo_celcius": False,
"convertTo_fahrenheit": False,}
def on_after_startup(self):
# start repeated timer for checking temp from sensor, runs every 5 seconds
self.poll_temps = RepeatedTimer(DEFAULT_UPDATE_PERIOD, self.read_temp)
self.poll_temps.start()
def read_temp(self):
currtemp = None
try:
data = self.bus.read_i2c_block_data(self.addr, MCP9808_REG_AMBIENT_TEMP, 2) #2-byte temperature (temp-MSB, temp-LSB) from reg 0x05
#Note 16 bits of data are of form: AAASMMMM LLLLLLLL (where A=alert, M=MSB, L=LSB)
#i.e data is stored in 12-bits plus sign
#See https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf
currtemp = (data[0] & 0x0F) * 16 + data[1] * .0625 #Multiply MSB by 16, divide LSB by 16
if data[0] & 0x10 : #Sign bit
currtemp = 256 - currtemp
if currtemp:
if self._settings.get_boolean(["convertTo_fahrenheit"]):
currtemp = currtemp * 1.8 + 32
elif self._settings.get_boolean(["convertTo_celcius"]):
currtemp = (currtemp - 32) * 5/9
self.last_temps["MCP9808"] = (round(currtemp,1), None)
except:
self._logger.debug("There was an error getting temperature from the MCP9808 temperature sensor")
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "MCP9808 Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = MCP9808Graph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback
}
Addendum: I would like to extend the code to allow for more configuration variables -- including the i2C bus, the i2C address, and potentially the temp resolution.
I tried the following for the i2C address:
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.addr = self._settings.get_int(["i2c_addr"])
...
def get_settings_defaults(self):
return {"use_fahrenheit": False, "i2c_addr": MCP9808_I2CADDR_DEFAULT}
But it seems like the default settings are not accessible that way from the init portion... (or else perhaps I have some other Python error given that I no almost no Python)
So any suggestions how to make default configs work in the init section.
Yeah, you'd need to either add a super class to your init (not quite sure how that works) or initialize as none and then set it in on_after_startup or in the worker function read_temp altogether. Putting it in worker function would probably be better so that if it gets changed it picks up on the next repeated cycle.
Here is a a version that allows you to call an arbitrary shell script that provides a temperature reading. The default config uses a shell command to read the RPi system temperature which seems like a generically useful thing to graph. However everything is configurable via the command line including: name of sensor, command to run, update frequency, output precision and convert to celsius/fahrenheit. So users should generally not need to touch the code itself.
# coding=utf-8
################################################################################
### SystemCmd Plugin for Plotty Temp Graph plugin
### Based on plugin structure provided by jneilliii
###
### Jeffrey J. Kosowsky
### August 5, 2022
###
### Note: configure from cli using:
### /home/kosowsky/octoprint/venv/bin/octoprint config set [-bool|-int] plugins.SystemCmdGraph-.<config_variable> <config_value>
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
from octoprint.util import RepeatedTimer
import subprocess
# Config default values
SENSOR_NAME = "RPi"
SENSOR_CMD = "PATH='/usr/bin';vcgencmd measure_temp | sed 's/^temp=\([0-9.]*\).*/\\1/'"
OUTPUT_PRECISION = 1 #Output precision in digits
UPDATE_PERIOD = 5 #Seconds
class SystemCmdGraph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.sensor_name = None
self.sensor_cmd = None
self.output_precision = None
self.update_period = None
self.convertTo_celsius = None
self.convertTo_fahrenheit = None
def get_settings_defaults(self):
return {
"sensor_name": SENSOR_NAME,
"sensor_cmd": SENSOR_CMD,
"output_precision": 1,
"convertTo_celsius": False,
"convertTo_fahrenheit": False,
"update_period": UPDATE_PERIOD,
}
def on_after_startup(self):
self.sensor_name = self._settings.get(["sensor_name"])
self.sensor_cmd = self._settings.get(["sensor_cmd"])
self.output_precision = self._settings.get_int(["output_precision"])
self.convertTo_celsius = self._settings.get_boolean(["convertTo_celsius"])
self.convertTo_fahrenheit = self._settings.get_boolean(["convertTo_fahrenheit"])
self.update_period = self._settings.get_int(["update_period"])
# start repeated timer for checking temp from sensor, runs every 5 seconds
self.poll_temps = RepeatedTimer(self.update_period, self.read_temp)
self.poll_temps.start()
def read_temp(self):
currtemp = None
try:
currtemp = float(subprocess.check_output(self.sensor_cmd, shell=True))
if currtemp:
if self.convertTo_fahrenheit:
currtemp = currtemp * 1.8 + 32
elif self.convertTo_celsius:
currtemp = (currtemp - 32) * 5/9
currtemp = round(currtemp, self.output_precision)
self.last_temps[self.sensor_name] = (currtemp, None)
# self._logger.warning("JJK: {} : {}".format(self.sensor_name, currtemp)) #JJKDEBUG
except:
self._logger.debug("There was an error getting temperature from the SystemCmd temperature plugin")
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "SystemCmd Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = SystemCmdGraph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback
}
And here is an updated version of the python-native MCP9808 plugin with similar cli configurability:
# coding=utf-8
################################################################################
### MCP9808 Sensor Plugin for Plotty Temp Graph plugin
### Based on plugin structure provided by jneilliii
### Based on MCP9808 code provided by Adafruit and from Octoprint-Enclosure
### Plus reference to datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf
###
### Jeffrey J. Kosowsky
### August 7, 2022
###
### Note: configure from cli using:
### /home/kosowsky/octoprint/venv/bin/octoprint config set [-bool|-int] plugins.MCP9808Graph.<config_variable> <config_value>
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
from octoprint.util import RepeatedTimer
import smbus
# register addresses.
MCP9808_REG_CONFIG = 0x01
MCP9808_REG_UPPER_TEMP = 0x02
MCP9808_REG_LOWER_TEMP = 0x03
MCP9808_REG_CRIT_TEMP = 0x04
MCP9808_REG_AMBIENT_TEMP = 0x05
MCP9808_REG_MANUF_ID = 0x06
MCP9808_REG_DEVICE_ID = 0x07
MCP9808_REG_RESOLUTION = 0x08
# configuration register values.
MCP9808_REG_CONFIG_CONTCONV = 0x0000
MCP9808_REG_CONFIG_SHUTDOWN = 0x0100
MCP9808_REG_CONFIG_CRITLOCKED = 0x0080
MCP9808_REG_CONFIG_WINLOCKED = 0x0040
MCP9808_REG_CONFIG_INTCLR = 0x0020
MCP9808_REG_CONFIG_ALERTSTAT = 0x0010
MCP9808_REG_CONFIG_ALERTCTRL = 0x0008
MCP9808_REG_CONFIG_ALERTSEL = 0x0002
MCP9808_REG_CONFIG_ALERTPOL = 0x0002
MCP9808_REG_CONFIG_ALERTMODE = 0x0001
#Other chip defs (JJK)
MCP9808_RES_MEDIUM = 0x01
MCP9808_RES_HIGH = 0x02
MCP9808_RES_PRECISION = 0x03
# Config default values
SMBUS_NUMBER = 0x01 #Default (first) i2c bus
I2C_ADDR = 0x18 #Default i2c address with all set to low (0x18)
SENSOR_PRECISION = MCP9808_RES_PRECISION #Precision of the sensor itself
SENSOR_NAME = "MCP9808"
OUTPUT_PRECISION = 1 #Digits of output format resolution
UPDATE_PERIOD = 5 #Seconds
class MCP9808Graph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.smbus = None
self.i2c_addr = None
self.sensor_precision = None
self.sensor_name = None
self.output_precision = None
self.convertTo_celsius = None
self.convertTo_fahrenheit = None
self.update_period = None
def get_settings_defaults(self):
return {
"smbus_number": SMBUS_NUMBER,
"i2c_addr": I2C_ADDR,
"sensor_precision": SENSOR_PRECISION,
"sensor_name": SENSOR_NAME,
"output_precision": OUTPUT_PRECISION,
"convertTo_celsius": False,
"convertTo_fahrenheit": False,
"update_period" : UPDATE_PERIOD,
}
def on_after_startup(self):
self.smbus = smbus.SMBus(self._settings.get(["smbus_number"]))
self.i2c_addr = self._settings.get(["i2c_addr"])
self.sensor_precision = self._settings.get(["sensor_precision"])
self.sensor_name = self._settings.get(["sensor_name"])
self.output_precision = self._settings.get_int(["output_precision"])
self.convertTo_celsius = self._settings.get_boolean(["convertTo_celsius"])
self.convertTo_fahrenheit = self._settings.get_boolean(["convertTo_fahrenheit"])
self.update_period = self._settings.get_int(["update_period"])
mcp9808_config = [MCP9808_REG_CONFIG_CONTCONV, 0x00] #Continuous conversion mode, power-up default
self.smbus.write_i2c_block_data(self.i2c_addr, MCP9808_REG_CONFIG, mcp9808_config)
self.smbus.write_byte_data(self.i2c_addr, MCP9808_REG_RESOLUTION, self.sensor_precision) #High precision +0.0625 degC, 0x03(03)
# start repeated timer for checking temp from sensor, runs every 5 seconds
self.poll_temps = RepeatedTimer(self.update_period, self.read_temp)
self.poll_temps.start()
def read_temp(self):
currtemp = None
try:
data = self.smbus.read_i2c_block_data(self.i2c_addr, MCP9808_REG_AMBIENT_TEMP, 2) #2-byte temperature (temp-MSB, temp-LSB) from reg 0x05
#Note 16 bits of data are of form: AAASMMMM LLLLLLLL (where A=alert, M=MSB, L=LSB)
#i.e data is stored in 12-bits plus sign
#See https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf
currtemp = (data[0] & 0x0F) * 16 + data[1] * .0625 #Multiply MSB by 16, divide LSB by 16
if data[0] & 0x10: #Sign bit
currtemp = 256 - currtemp
if currtemp:
if self.convertTo_fahrenheit:
currtemp = currtemp * 1.8 + 32
elif self.convertTo_celsius:
currtemp = (currtemp - 32) * 5/9
currtemp = round(currtemp, self.output_precision)
self.last_temps[self.sensor_name] = (currtemp, None)
# self._logger.warning("JJK: {} : {}".format(self.sensor_name, currtemp)) #JJKDEBUG
except:
self._logger.debug("There was an error getting temperature from the MCP9808 temperature sensor")
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "MCP9808 Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = MCP9808Graph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback
}
And here is a version that accesses the RPi temp via the sysfs.
# coding=utf-8
################################################################################
### RPi sysfs Plugin for Plotty Temp Graph plugin
### Based on plugin structure provided by jneilliii
###
### Jeffrey J. Kosowsky
### August 7, 2022
###
### Note: configure from cli using:
### /home/kosowsky/octoprint/venv/bin/octoprint config set [-bool|-int] plugins.RPiSysGraph.<config_variable> <config_value>
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
from octoprint.util import RepeatedTimer
# Config default values
SENSOR_NAME = "RPiSys"
SENSOR_PATH = "/sys/class/thermal/thermal_zone0/temp"
SENSOR_DIVISOR = 1000
OUTPUT_PRECISION = 1 #Output precision in digits
UPDATE_PERIOD = 5 #Seconds
class RPiSysGraph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.sensor_name = None
self.sensor_path = None
self.sensor_divisor = None
self.output_precision = None
self.update_period = None
self.convertTo_celsius = None
self.convertTo_fahrenheit = None
def get_settings_defaults(self):
return {
"sensor_name": SENSOR_NAME,
"sensor_path": SENSOR_PATH,
"sensor_divisor": SENSOR_DIVISOR,
"output_precision": 1,
"convertTo_celsius": False,
"convertTo_fahrenheit": False,
"update_period": UPDATE_PERIOD,
}
def on_after_startup(self):
self.sensor_name = self._settings.get(["sensor_name"])
self.sensor_path = self._settings.get(["sensor_path"])
self.sensor_divisor = self._settings.get(["sensor_divisor"])
self.output_precision = self._settings.get_int(["output_precision"])
self.convertTo_celsius = self._settings.get_boolean(["convertTo_celsius"])
self.convertTo_fahrenheit = self._settings.get_boolean(["convertTo_fahrenheit"])
self.update_period = self._settings.get_int(["update_period"])
# start repeated timer for checking temp from sensor, runs every 5 seconds
self.poll_temps = RepeatedTimer(self.update_period, self.read_temp)
self.poll_temps.start()
def read_temp(self):
currtemp = None
try:
with open(r"/sys/class/thermal/thermal_zone0/temp") as File:
currtemp = (float(File.readline()))/self.sensor_divisor
if currtemp:
if self.convertTo_fahrenheit:
currtemp = currtemp * 1.8 + 32
elif self.convertTo_celsius:
currtemp = (currtemp - 32) * 5/9
currtemp = round(currtemp, self.output_precision)
self.last_temps[self.sensor_name] = (currtemp, None)
# self._logger.warning("JJK: {} : {}".format(self.sensor_name, currtemp)) #JJKDEBUG
except:
self._logger.debug("There was an error getting temperature from the SystemCmd temperature plugin")
# self._logger.warning ("There was an error getting temperature from the SystemCmd temperature plugin") #JJKDEBUG
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "RPiSys Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = RPiSysGraph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback
}
Hope the above variations are helpful to people :)
I'm stripping them out and adding to an examples sub folder to release with the next version. I appreciate the contributions.
Not sure how much different the RPiSys one is different than my plotly_temp_graph_cpu_reporting.py example.
@jneilliii, I would have liked each plugin to be able to host an array of like sensors. For example, if you have multiple MC9808 sensors, then it would have been nice to manage an array of such sensors in one plugin. Similarly, for the command version, it would be nice to allow an array of command accessing a bunch of different sensors potentially through very different commands. However, I couldn't figure out how to do that since temp_callback needs to return a pointer to a single parsed_temps [not sure if I am using the right python terminology].
Am I missing something simple? Because it would be really great if multiple "like" sensors could be hosted on a single plugin.
self.last_temps is a dictionary, so you can stuff as much data you want into it with each having their own unique "key". So something like this would add both temperature lines.
self.last_temps["sensor1"] = (currtemp, None)
self.last_temps["sensor2"] = (currtemp, None)
etc....
Not sure how much different the RPiSys one is different than my plotly_temp_graph_cpu_reporting.py example.
Ahhh... I didn't see that example (it doesn't seem to be in the github heirarchy) so very possible that not very different. I did however facilitate changing parameters via configs by moving the setup to the on_startup section (which you had suggested and which seems to be the way others handle the situation).
Ah yeah I see now, it's in my rc branch (what I typically dev in), waiting for next release. BTW, running any of my plugins I recommend running in release candidate mode to have the latest and greatest, albeit possibly buggy (not usually).
https://github.com/jneilliii/OctoPrint-PlotlyTempGraph/blob/rc/plotly_temp_graph_cpu_reporting.py
self.last_temps is a dictionary, so you can stuff as much data you want into it with each having their own unique "key". So something like this would add both temperature lines.
self.last_temps["sensor1"] = (currtemp, None) self.last_temps["sensor2"] = (currtemp, None) etc....
I tried something like that by my Python skills must have been lacking :( I will probably code an example using system commands since that could be a "poor man's" ones stop solution to monitoring multiple parameters with a single plugin...
Ah yeah I see now, it's in my rc branch (what I typically dev in), waiting for next release. BTW, running any of my plugins I recommend running in release candidate mode to have the latest and greatest, albeit possibly buggy (not usually).
https://github.com/jneilliii/OctoPrint-PlotlyTempGraph/blob/rc/plotly_temp_graph_cpu_reporting.py
Yours is more structured and high level using 'psutil' lib. (and thus better :) Mine is more hacky by directly reading /sys/class/ etc.
Ah yeah I see now, it's in my rc branch (what I typically dev in), waiting for next release. BTW, running any of my plugins I recommend running in release candidate mode to have the latest and greatest, albeit possibly buggy (not usually).
https://github.com/jneilliii/OctoPrint-PlotlyTempGraph/blob/rc/plotly_temp_graph_cpu_reporting.py
BTW, if I am reading your code correctly, you have duplicated the cpu_thermal line
if "cpu-thermal" in temp:
cpu_temp = temp["cpu-thermal"][0].current
if "cpu_thermal" in temp:
cpu_temp = temp["cpu_thermal"][0].current
Couple of finesse questions:
BTW, if I am reading your code correctly, you have duplicated the cpu_thermal line
ah, thanks.
Actually, that's not a duplication in the CPU example. dash vs underscore.
Actually, that's not a duplication in the CPU example. dash vs underscore.
I have old eyes I guess :)
Here is one (hopefully) final example using multiple system commands in one plugin:
# coding=utf-8
################################################################################
### SystemCmdMulti Plugin for Plotty Temp Graph plugin
### Allows multiple system commands to be used to query multiple temperature variables
### Based on plugin structure provided by jneilliii
###
### Jeffrey J. Kosowsky
### August 8, 2022
###
### Note: configure from cli using:
### /home/kosowsky/octoprint/venv/bin/octoprint config set [-bool|-int] plugins.SystemCmdMultiGraph.<config_variable> <config_value>
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
from octoprint.util import RepeatedTimer
import subprocess
# Config default values
POLL_INTERVAL = 5 #Seconds
SYSTEM_CMDS = {
'RPiCmd': {
'cmd': "PATH='/usr/bin';vcgencmd measure_temp | sed 's/^temp=\([0-9.]*\).*/\\1/'",
'precision': 1,
'convertTo_celsius': False,
'convertTo_fahrenheit': False,
},
'RPiCmd2': {
'cmd': "printf '%.1f' $(cat /sys/class/thermal/thermal_zone0/temp)e-3",
},
}
#NOTE: attributes: 'precision, 'convertTo_celsius', 'convertTo_fahrenheit' are *optional*
DEFAULT_PRECISION = 1
class SystemCmdMultiGraph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.poll_temps = None
self.poll_interval = None
self.system_cmds = None
def get_settings_defaults(self):
return {
"poll_interval": POLL_INTERVAL,
"system_cmds": SYSTEM_CMDS,
}
def on_after_startup(self):
self.poll_interval = self._settings.get_int(["poll_interval"])
self.system_cmds = self._settings.get(["system_cmds"])
for sensor in self.system_cmds :
sensorval = self.system_cmds[sensor]
if not hasattr(sensorval, 'precision'):
sensorval['precision'] = DEFAULT_PRECISION
if not hasattr(sensorval, 'convertTo_fahrenheit'):
sensorval['convertTo_fahrenheit'] = False
if not hasattr(sensorval, 'convertTo_celsius'):
sensorval['convertTo_fahrenheit'] = False
if not hasattr(sensorval, 'convertTo_celsius'):
sensorval['convertTo_celsius'] = False
# start repeated timer for checking temp from sensor, runs every 5 seconds
self.poll_temps = RepeatedTimer(self.poll_interval, self.read_temp)
self.poll_temps.start()
def read_temp(self):
for sensor in self.system_cmds :
sensorval = self.system_cmds[sensor]
current_temp = None
try:
current_temp = float(subprocess.check_output(sensorval['cmd'], shell=True))
if current_temp:
if sensorval['convertTo_fahrenheit']:
current_temp = current_temp * 1.8 + 32
elif sensorval['convertTo_celsius']:
current_temp = (current_temp - 32) * 5/9
current_temp = round(current_temp, sensorval['precision'])
self.last_temps[sensor] = (current_temp, None)
# self._logger.warning("JJK: {} : {}".format(sensor, current_temp)) #JJKDEBUG
except:
self._logger.debug("There was an error getting temperature from the SystemCmdMulti temperature plugin: {}".format(sensor))
# self._logger.warning ("Error getting temperature: {} : {}".format(sensor, sensorval)) #JJKDEBUG
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "SystemCmdMulti Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = SystemCmdMultiGraph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback
}
In case, people are looking for more examples, here is one more for Prusa MK3 that reads in the temperature from the Einsy board. (based on the code you suggested for Klipper: https://raw.githubusercontent.com/jneilliii/OctoPrint-PlotlyTempGraph/master/klipper_additional_temp.py)
# coding=utf-8
################################################################################
### MK3 Einsy Temperature Plugin for Plotty Temp Graph plugin
### Reads temperature from M105 command sent from MK3 while printing
### Based on plugin structure provided by jneilliii
###
### Jeffrey J. Kosowsky
### August 8, 2022
###
### To activate, store (copy) in: ~/.octoprint/plugins
### Note: configure from cli using:
### ~/octoprint/venv/bin/octoprint config set [-bool|-int] plugins.MK3TempGraph.<config_variable> <config_value>
### e.g.,
### ~/octoprint/venv/bin/octoprint config set -bool plugins.MK3TempGraph.convertTo_fahrenheit True
###
################################################################################
from __future__ import absolute_import
import octoprint.plugin
import re
# Config default values
SENSOR_NAME = "Einsy"
OUTPUT_PRECISION = 1 #Output precision in digits
TEMP_REGEX = ".+\sA:(?P<temp>\d+\.\d)"
#Note: M105 response is of form:
# T:240.1 /240.0 B:80.1 /85.0 T0:240.1 /240.0 @:28 B@:127 P:0.0 A:40.0
class MK3TempGraph(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin):
def __init__(self):
self.last_temps = dict()
self.sensor_name = None
self.output_precision = None
self.convertTo_celsius = None
self.convertTo_fahrenheit = None
self.temp_regex = None
def get_settings_defaults(self):
return {
"sensor_name": SENSOR_NAME,
"output_precision": 1,
"convertTo_celsius": False,
"convertTo_fahrenheit": False,
"temp_regex": TEMP_REGEX,
}
def on_after_startup(self):
self.sensor_name = self._settings.get(["sensor_name"])
self.output_precision = self._settings.get_int(["output_precision"])
self.convertTo_celsius = self._settings.get_boolean(["convertTo_celsius"])
self.convertTo_fahrenheit = self._settings.get_boolean(["convertTo_fahrenheit"])
self.temp_regex = re.compile(self._settings.get(["temp_regex"]))
def gcode_callback(self, comm, line, *args, **kwargs):
if not line.startswith("T:"):
return line
# self._logger.warning("JJK: {} : {}".format(self.sensor_name,line)) #JJKDEBUG
try:
match = self.temp_regex.match(line)
current_temp = float(match.group("temp"))
if self.convertTo_fahrenheit:
current_temp = current_temp * 1.8 + 32
elif self.convertTo_celsius:
current_temp = (current_temp - 32) * 5/9
self.last_temps[self.sensor_name] = (current_temp, None)
# self._logger.warning("JJK: {} : {}".format(self.sensor_name, current_temp)) #JJKDEBUG
except:
self._logger.debug("Error getting temperature from the MK3TempGraph temperature plugin")
# self._logger.warning ("Error getting temperature from the MK3TempGraph temperature plugin") #JJKDEBUG
return line
def temp_callback(self, comm, parsed_temps):
parsed_temps.update(self.last_temps)
return parsed_temps
__plugin_name__ = "MK3TempGraph Plotly Temp Graph Integration"
__plugin_pythoncompat__ = ">=3,<4"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = MK3TempGraph()
__plugin_hooks__ = {
"octoprint.comm.protocol.temperatures.received": __plugin_implementation__.temp_callback,
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.gcode_callback
}
Is this Prusa MK3 example even needed with the latest OctoPrint 1.8.0+ versions? I think OctoPrint will natively pull the A and P values won't it?
You are correct. Still a good example but unnecessary.
BTW, I also noticed sensor names 'E actual' and 'W actual'. Though they both always display zero (as well as 'P actual' 'W actual' starts displaying when printing starts 'E actual' starts displayin 'W actual' doesn't display
Do you know what 'E' and 'W' are?
If I remember correctly those are associated with time to reach temperature (at least W is). E may be time to cool down to temperature, but unsure.
The fact that those numbers (as well as P) seem to be always 0, does that mean that the functionality is not implemented (or at least not engaged) on my MK3S+?
I cannot say as I do not have one of those printers or use the Prusa firmware personally. I do know that W tends to stay 0 a lot, but does come in occasionally with numbers so not sure how the actual feature works.
It would be great if you could allow the addition of custom commands to retrieve a custom-named temperature variable without having to use another plugin such as octoprint-enclosure (which is semi-deprecated) to input the temperature value.
This could be done similar to the Navbar Temperature Plugin that allows you to give an arbitrary command whose output is displayed in the Navbar. In this case of course the output would need to be an integer in a rational temperature range.