1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 <2024> 2025 | Index | 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 <2024> 2025 |
<== Date ==> | <== Thread ==> |
---|
Subject: | Re: [Ext] Re: Room temperature and humidity monitoring with EPICS? |
From: | Jesse Hopkins via Tech-talk <tech-talk at aps.anl.gov> |
To: | Matt Newville <newville at cars.uchicago.edu>, EPICS Tech Talk <tech-talk at aps.anl.gov> |
Date: | Tue, 26 Mar 2024 14:20:50 -0500 |
Hi Jesse,Great - yeah, let me know if you have any questions.On Mon, Nov 13, 2023 at 11:17 AM Jesse Hopkins <jhopkins1 at iit.edu> wrote:Hi Matt,Thanks! Looks pretty straightforward, but if I have questions I may ping you once I have the sensors.All the best.
- Jesse----
Jesse Hopkins, PhD
Deputy Director
BioCAT, Sector 18
Advanced Photon SourceOn Mon, Nov 13, 2023 at 9:53 AM Matt Newville <newville at cars.uchicago.edu> wrote:Hi Jesse,Sorry for the delay. What I do is fairly simple. For each sensor, I load a small record into a softIOC withdbLoadRecords("esensor.db","P=13IDE:,Q=Esensor")in the startup script and with the "esensor.db" of (also attached):##### record used for Esensor temperature, humidity sensors
record(ai,"$(P)$(Q):TempF") {
field(DESC, "Temperature (F)")
field(VAL, "0")
}
record(ai,"$(P)$(Q):TempC") {
field(DESC, "Temperature (C)")
field(VAL, "0")
}
record(ai,"$(P)$(Q):Humid") {
field(DESC, "Humidity (percent)")
field(VAL, "0")
}
record(stringin, "$(P)$(Q):TimeStamp") {
field(DESC, "Timestamp of last update")
field(VAL, "")
}####Then, I run a Python script (attached) to read the sensor (over plain HTTP), parse the results, and push to PVs. The script handles both older Esensors that send data as XML, and newer ones that send JSON data. You could probably put that script inside procServ or something like that but I just have it running as a simple background script.--MattOn Fri, Nov 10, 2023 at 4:15 PM Jesse Hopkins <jhopkins1 at iit.edu> wrote:Hi folks,Thanks to everyone who shared what you're using! I think what Matt or Pierrick suggested are right in line with what I had in mind.The price point on something with temperature and humidity is a bit better from the Esensors that Matt shared, so that's where I'm leaning. Matt, if you'd be willing to share your python script that would be great. I was looking at the EM32-Xe, because the display would be a nice feature to have. Have you used that one?All the best.
- Jesse----
Jesse Hopkins, PhD
Deputy Director
BioCAT, Sector 18
Advanced Photon SourceOn Fri, Nov 10, 2023 at 12:37 PM Matt Newville <newville at cars.uchicago.edu> wrote:Hi Jesse,We have a few of the Esensors temperature/humidity sensors in our hutches, including the EM32-Le https://eesensors.com/products/websensors/environmental-monitor-sensor.htmlThese have a simple magnetic mount and an ethernet port. They provide a simple web app to read the temperature and humidity. But, there is also an HTTP API to get XML or JSON (depending on the generation of the sensor) with the data. Fetching and reading those values and putting them into Epics PVs is pretty straightforward. I'd be happy to share the Python script we use to read multiple meters and put them into PVs so we can monitor temperature and humidity and put those values into metadata.--MattOn Fri, Nov 10, 2023 at 11:47 AM Jesse Hopkins via Tech-talk <tech-talk at aps.anl.gov> wrote:Hi folks,We're looking for a way to monitor humidity and temperature in our experimental enclosures (beamline hutches), with readings that we can access via EPICS. I'd prefer something ethernet based, and low cost is always good. I strongly suspect folks in the EPICS community have already done this in a variety of ways, though I wasn't able to turn anything obvious up in a search.Does anyone have a good solution that you're using?I suspect that if there's nothing else out there I could just buy some analog input boxes (e.g. LabJack or Measurement Computing) and hook up a couple of sensors, but I'm hoping there's a better/cheaper solution.All the best.
- Jesse----
Jesse Hopkins, PhD
Deputy Director
BioCAT, Sector 18
Advanced Photon Source----Matt Newville <newville at cars.uchicago.edu> 630-327-7411----Matt Newville <newville at cars.uchicago.edu> 630-327-7411----Matt Newville <newville at cars.uchicago.edu> 630-327-7411
import asyncio import traceback import time import json import xml.etree.ElementTree as ET from softioc import softioc, builder, asyncio_dispatcher # import cothread import requests class ESensor: """ read Esensor data, push to Epics PVs""" def __init__(self, label, url, tempc_pv, tempf_pv, humid_pv, timestamp_pv): self.label = label self.url = url # self.prefix = prefix self.format = url.split('.')[-1] self.tempf_pv = tempf_pv self.tempc_pv = tempc_pv self.humid_pv = humid_pv self.timestamp_pv = timestamp_pv if self.format == 'xml': self.tattr = "tm0" self.tunit = "tun0" self.hattr = "hu0" self.reader = ET.fromstring elif self.format == "json": self.tattr = "tmp" self.hattr = "hum" self.tunit = "tun" self.reader = json.loads def getval(self, source, attr, force_float=True): if self.format == "xml": try: val = source.find(attr).text except: val = "-1" elif self.format == "json": try: val = source.get(attr, -1.0) except: val = "-1" if force_float: try: val = float(val) except: val = -1.0 return val def read(self): resp = requests.get(self.url, timeout=0.5) tdat = self.reader(resp.content.decode("utf-8")) tunt = self.getval(tdat, self.tunit, force_float=False) tval = self.getval(tdat, self.tattr) hval = self.getval(tdat, self.hattr) return tval, tunt, hval def update(self): tval, tunit, hval = self.read() if tunit in (0, '0', 'F'): tval_f = tval tval_c = (tval - 32)*5.0/9.0 else: tval_c = tval tval_f = (tval_c * 9.0/5) + 32 tval_f = float("%.3f" % tval_f) tval_c = float("%.3f" % tval_c) hval = float("%.3f" % hval) self.tempc_pv.set(tval_c) self.tempf_pv.set(tval_f) self.humid_pv.set(hval) self.timestamp_pv.set(time.ctime()) if __name__ == '__main__': # Create an asyncio dispatcher, the event loop is now running dispatcher = asyncio_dispatcher.AsyncioDispatcher() esensors = [] # Define as prefix, url sensor_defs = [ ['18ID:EnvMon:D', "http://164.54.204.197/status.json"], ['18ID:EnvMon:C', "http://164.54.204.198/status.json"], ['18ID:EnvMon:A', "http://164.54.204.199/status.json"], ] for sensor_def in sensor_defs: prefix = sensor_def[0] url = sensor_def[1] # Set the record prefix builder.SetDeviceName(prefix) # Create some records ai_c = builder.aIn('TempC', initial_value=0, DESC='Temperature (C)') ai_f = builder.aIn('TempF', initial_value=0, DESC='Temperature (F)') ai_h = builder.aIn('Humid', initial_value=0, DESC='Humidity (percent)') si_t = builder.stringIn('TimeStamp', initial_value="", DESC='Timestamp of last update') esensor = ESensor(prefix, url, ai_c, ai_f, ai_h, si_t) esensors.append(esensor) # Boilerplate get the IOC started builder.LoadDatabase() softioc.iocInit(dispatcher) # Start processes required to be run after iocInit async def update(): while True: try: [e.update() for e in esensors] except Exception: # traceback.print_exc() pass await asyncio.sleep(0.5) dispatcher(update) # Finally leave the IOC running with an interactive shell. softioc.interactive_ioc(globals())