> I have a time series of slow control measurements in an ASCII format -
> data records in a format (run_number, time, temperature, voltage1, ..., voltageN),
> and, if possible, would like to convert them into a MIDAS history format.
>
> Making MIDAS events out of that data is easy, but is it possible to preserve
> the time stamps? - Logically, this boils down to whether it is possible to have
> the event time set by a user frontend
It looks that the original question was not as naive as I expected and may be pointing to a subtle bug.
I have implemented a python frontend - essentially a clone of
https://bitbucket.org/tmidas/midas/src/develop/python/midas/frontend.py
reading the old slow control data and setting the event.header.timestamp's to some dates from the year of 2022.
When I run MIDAS and read the "old slow control events", one event in 10 seconds,
the MIDAS Event Dump utility shows the data with the correct event timestamps, from the year of 2022.
However the history plots show the event parameters with the timestamps from Feb 01 2025 and the adjacent
data points separated by 10 sec.
Is it possible that the history system uses its own timestamp setting instead of using timestamps from the event headers?
- Under normal circumstances, the two should be very close, and that could've kept the issue hidden...
-- thanks, regards, Pasha
UPDATE: I attached the frontend code and the input data file it is reading. The data file should reside in the local directory
- the frontend code doesn't have everything fully automated for the test,
-- an integer field "/Mu2e/Offline/Ops/LastTime" would need to be created manually
-- the history plots would need to be declared manually |
#!/usr/bin/env python
"""
Example of a basic midas frontend that has one periodic equipment.
See `examples/multi_frontend.py` for an example that uses more
features (frontend index, polled equipment, ODB settings etc).
"""
import midas
import midas.frontend
import midas.event
import time, os, sys
from datetime import datetime
#import TRACE
#TRACE_NAME = 'test_frontend.py'
class MyPeriodicEquipment(midas.frontend.EquipmentBase):
"""
We define an "equipment" for each logically distinct task that this frontend
performs. For example, you may have one equipment for reading data from a
device and sending it to a midas buffer, and another equipment that updates
summary statistics every 10s.
Each equipment class you define should inherit from
`midas.frontend.EquipmentBase`, and should define a `readout_func` function.
If you're creating a "polled" equipment (rather than a periodic one), you
should also define a `poll_func` function in addition to `readout_func`.
"""
def __init__(self, client):
# The name of our equipment. This name will be used on the midas status
# page, and our info will appear in /Equipment/MyPeriodicEquipment in
# the ODB.
equip_name = "MyPeriodicEquipment"
# Define the "common" settings of a frontend. These will appear in
# /Equipment/MyPeriodicEquipment/Common. The values you set here are
# only used the very first time this frontend/equipment runs; after
# that the ODB settings are used.
default_common = midas.frontend.InitialEquipmentCommon()
default_common.equip_type = midas.EQ_PERIODIC
default_common.buffer_name = "SYSTEM"
default_common.trigger_mask = 0
default_common.event_id = 1
default_common.period_ms = 100
default_common.read_when = midas.RO_ALWAYS
default_common.log_history = 1
# You MUST call midas.frontend.EquipmentBase.__init__ in your equipment's __init__ method!
midas.frontend.EquipmentBase.__init__(self, client, equip_name, default_common)
# You can set the status of the equipment (appears in the midas status page)
self.set_status("Initialized")
self._verbose = 1;
# ---------------------------------------------------------------------
def Print(self,Name,level,Message):
if(level>self._verbose): return 0;
now = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime(time.time()))
message = now+' [ GridSubmit::'+Name+' ] '+Message
print(message)
def readout_func(self):
"""
For a periodic equipment, this function will be called periodically
(every 100ms in this case). It should return either a `cdms.event.Event`
or None (if we shouldn't write an event).
"""
last_time = self.client.odb_get('/Mu2e/Offline/Ops/LastTime')
self.Print('readout_func',1,f'last_time:{last_time}')
# cmd = 'dqmTool print-numbers --source 5 --value 6 --expand'
cmd = 'cat val_nightly.csv'
self.Print('readout_func',1,'executing cmd:%s'%cmd)
out=os.popen(cmd).readlines()
event = None;
for line in out:
# print(line)
words = line.strip().split(',')
#------------------------------------------------------------------------------
# ['91', '5228', '0.0', '0 5', 'valNightly', 'reco', 'day', '0 6', 'ops', 'stats', 'CPU 11', '4', '0', '0', '0', '0', '2022-01-11 00:01:00-06:00', '2022-01-11 00:01:00-06:00']
#------------------------------------------------------------------------------
# In this example, we just make a simple event with one bank.
dt = datetime.strptime(words[17], "%Y-%m-%d %H:%M:%S%z");
ts = dt.timestamp();
if (ts > last_time) :
data = []
print(words);
event = midas.event.Event()
data.append(float(words[0]));
data.append(float(words[1]));
data.append(float(words[2]));
event.create_bank("OFLN", midas.TID_FLOAT, data)
event.header.timestamp = int(ts);
self.client.odb_set('/Mu2e/Offline/Ops/LastTime',ts)
print(f'SET_NEW last_time:{ts} dt:{dt}')
# self.Print('readout_func',1,f'SET_NEW last_time:{ts} dt:{dt}')
break;
return event
class MyFrontend(midas.frontend.FrontendBase):
"""
A frontend contains a collection of equipment.
You can access self.client to access the ODB etc (see `midas.client.MidasClient`).
"""
def __init__(self):
# You must call __init__ from the base class.
midas.frontend.FrontendBase.__init__(self, "myfe_name")
# You can add equipment at any time before you call `run()`, but doing
# it in __init__() seems logical.
self.add_equipment(MyPeriodicEquipment(self.client))
self._verbose = 1
def begin_of_run(self, run_number):
"""
This function will be called at the beginning of the run.
You don't have to define it, but you probably should.
You can access individual equipment classes through the `self.equipment`
dict if needed.
"""
self.set_all_equipment_status("Running", "greenLight")
self.client.msg("Frontend has seen start of run number %d" % run_number)
return midas.status_codes["SUCCESS"]
def end_of_run(self, run_number):
self.set_all_equipment_status("Finished", "greenLight")
self.client.msg("Frontend has seen end of run number %d" % run_number)
return midas.status_codes["SUCCESS"]
def frontend_exit(self):
"""
Most people won't need to define this function, but you can use
it for final cleanup if needed.
"""
print("Goodbye from user code!")
if __name__ == "__main__":
# The main executable is very simple - just create the frontend object,
# and call run() on it.
print("EMOE");
# TRACE.TRACE(7,'EMOEM',TRACE_NAME)
with MyFrontend() as my_fe:
my_fe.run()
|