Commit 9be6df01 authored by Perceval Guillou's avatar Perceval Guillou

Merge branch '1250-counters-registration-to-the-map' into 'master'

Resolve "counters registration to the map"

Closes #1250 and #1199

See merge request bliss/bliss!1870
parents 94f912d2 df6eed31
......@@ -60,14 +60,15 @@ class Counter:
def __init__(self, name, controller, conversion_function=None, unit=None):
self._name = name
self.__counter_controller = controller
self.__counter_controller._counters[self.name] = self
self._conversion_function = (
conversion_function if conversion_function is not None else lambda x: x
)
assert callable(self._conversion_function)
self._unit = unit
parents_list = ["counters"] + ([controller] if controller is not None else [])
global_map.register(self, parents_list, tag=self.name)
@property
def name(self):
......
......@@ -197,9 +197,7 @@ class Input(SamplingCounterController):
# useful attribute for a temperature controller writer
self._attr_dict = {}
self.add_counter(
SamplingCounter(self.name, self, unit=config.get("unit", "N/A"))
)
self.create_counter(SamplingCounter, self.name, unit=config.get("unit", "N/A"))
def read_all(self, *counters):
return [self.read()]
......@@ -322,9 +320,7 @@ class Output(SamplingCounterController):
# useful attribute for a temperature controller writer
self._attr_dict = {}
self.add_counter(
SamplingCounter(self.name, self, unit=config.get("unit", "N/A"))
)
self.create_counter(SamplingCounter, self.name, unit=config.get("unit", "N/A"))
def read_all(self, *counters):
return [self.read()]
......@@ -665,9 +661,7 @@ class Loop(SamplingCounterController):
self.reg_plot = None
self.add_counter(
SamplingCounter(self.name, self, unit=config.get("unit", "N/A"))
)
self.create_counter(SamplingCounter, self.name, unit=config.get("unit", "N/A"))
# ----------- BASE METHODS -----------------------------------------
......
......@@ -20,15 +20,14 @@ from bliss.common.protocols import counter_namespace
class CounterController:
def __init__(self, name, master_controller=None): # , hw_ctrl=None):
def __init__(self, name, master_controller=None, register_counters=True):
self.__name = name
self.__master_controller = master_controller
self._counters = {}
# self._hw_controller = hw_ctrl
global_map.register(self, parents_list=["counters"])
if register_counters:
global_map.register(self, parents_list=["counters"])
@property
def name(self):
......@@ -45,16 +44,13 @@ class CounterController:
def _master_controller(self):
return self.__master_controller
# @property
# def hw_controller(self):
# return self._hw_controller
@property
def counters(self):
return counter_namespace(self._counters)
def add_counter(self, counter):
self._counters[counter.name] = counter
def create_counter(self, counter_class, *args, **kwargs):
counter = counter_class(*args, controller=self, **kwargs)
return counter
# ---------------------POTENTIALLY OVERLOAD METHODS ------------------------------------
def create_chain_node(self):
......@@ -96,8 +92,12 @@ class CounterController:
class SamplingCounterController(CounterController):
def __init__(self, name="samp_cc", master_controller=None):
super().__init__(name, master_controller=master_controller)
def __init__(self, name, master_controller=None, register_counters=True):
super().__init__(
name,
master_controller=master_controller,
register_counters=register_counters,
)
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
return SamplingCounterAcquisitionSlave(
......@@ -135,8 +135,12 @@ class SamplingCounterController(CounterController):
class IntegratingCounterController(CounterController):
def __init__(self, name="integ_cc", master_controller=None):
super().__init__(name, master_controller=master_controller)
def __init__(self, name="integ_cc", master_controller=None, register_counters=True):
super().__init__(
name,
master_controller=master_controller,
register_counters=register_counters,
)
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
return IntegratingCounterAcquisitionSlave(
......@@ -295,8 +299,8 @@ class CalcCounterController(CounterController):
class SoftCounterController(SamplingCounterController):
def __init__(self, name="soft_counter_controller"):
super().__init__(name)
def __init__(self, name="soft_counter_controller", register_counters=True):
super().__init__(name, register_counters=True)
def read(self, counter):
return counter.apply(counter.get_value())
......@@ -65,7 +65,7 @@ class CT2Controller(Proxy, CounterController):
self, functools.partial(Client, address, **kwargs), init_once=True
)
CounterController.__init__(self, name=name)
CounterController.__init__(self, name=name, register_counters=False)
# Remote call
self.configure(device_config)
......@@ -77,15 +77,13 @@ class CT2Controller(Proxy, CounterController):
ct_name = channel.get("counter name", None)
if ct_name:
address = int(channel["address"])
slave._counters[ct_name] = CT2Counter(
ct_name, address, controller=slave
)
slave.create_counter(CT2Counter, ct_name, address)
# Add ct2 counter timer
timer = device_config.get("timer", None)
if timer is not None:
ct_name = timer.get("counter name", None)
if ct_name:
slave._counters[ct_name] = CT2CounterTimer(ct_name, controller=slave)
slave.create_counter(CT2CounterTimer, ct_name)
self._counters = slave._counters
......
......@@ -52,14 +52,14 @@ class EBVDiodeRange:
class EBVCounterController(SamplingCounterController):
def __init__(self, ebv_master, diode_name):
super().__init__(ebv_master.name)
self._ebv_master = ebv_master
def __init__(self, ebv_device, diode_name):
super().__init__(ebv_device.name)
self._ebv_device = ebv_device
self._diode_name = diode_name
def read(self, counter):
if counter.name == self._diode_name:
return self._ebv_master.current
return self._ebv_device.current
class EBVCounter(SamplingCounter):
......@@ -116,11 +116,10 @@ class EBV:
self.initialize()
# --- counter interface
self.master_controller = None
self.controller = EBVCounterController(self, self._cnt_name)
diode_counter = EBVCounter(self._cnt_name, self.controller, unit="mA")
self.controller.add_counter(diode_counter)
add_property(self, self._cnt_name, diode_counter)
self._counter_controller = EBVCounterController(self, self._cnt_name)
self._diode_counter = EBVCounter(
self._cnt_name, self._counter_controller, unit="mA"
)
global_map.register(
self,
......@@ -133,6 +132,13 @@ class EBV:
self._wago.connect()
self.__update()
@property
def diode(self):
return self._diode_counter
def counters(self):
return self._counter_controller.counters
def __led_status_changed(self, state):
event.send(self, "led_status", state)
......
......@@ -65,7 +65,7 @@ class EmhCounter(SamplingCounter):
"""EMH counter class
"""
def __init__(self, name, controller, channel, unit=None):
def __init__(self, name, channel, controller, unit=None):
SamplingCounter.__init__(self, name, controller)
# ref to the controller
......@@ -98,10 +98,9 @@ class EMH(CounterController):
# BPM COUNTERS
for counter_conf in config.get("counters", list()):
unit = counter_conf.get_inherited("unit")
counter = EmhCounter(
counter_conf["counter_name"], self, counter_conf["channel"], unit
self.create_counter(
EmhCounter, counter_conf["counter_name"], counter_conf["channel"], unit
)
self._counters[counter.name] = counter
self.bpm_values = {"bpmx": -1, "bpmy": -1, "bpmi": -1}
......@@ -113,7 +112,6 @@ class EMH(CounterController):
from bliss.scanning.acquisition.emh import EmhAcquisitionSlave
return EmhAcquisitionSlave(self, ctrl_params=ctrl_params, **acq_params)
else:
return SamplingCounterAcquisitionSlave(
self, ctrl_params=ctrl_params, **acq_params
......
......@@ -27,7 +27,10 @@ class LimaBpmCounter(IntegratingCounter):
class Bpm(IntegratingCounterController):
def __init__(self, name, bpm_proxy, acquisition_proxy):
super().__init__("bpm", master_controller=acquisition_proxy)
# leave counters registration to the parent object
super().__init__(
"bpm", master_controller=acquisition_proxy, register_counters=False
)
self._proxy = bpm_proxy
self._counters.update(
{
......
......@@ -157,7 +157,10 @@ class RoiCounters(IntegratingCounterController):
"""
def __init__(self, proxy, acquisition_proxy):
super().__init__("roi_counters", master_controller=acquisition_proxy)
# leave counters registration to the parent object
super().__init__(
"roi_counters", master_controller=acquisition_proxy, register_counters=False
)
self._proxy = proxy
self._current_config = settings.SimpleSetting(
self.fullname, default_value="default"
......
......@@ -327,14 +327,10 @@ class BaseMCA(CounterController):
@property
def counters(self):
# from bliss.scanning.acquisition.mca import mca_counters
return mca_counters(self)
@property
def counter_groups(self):
# from bliss.scanning.acquisition.mca import mca_counter_groups
return mca_counter_groups(self)
# Roi handling
......
......@@ -63,7 +63,7 @@ class Mythen(CounterController):
self._hostname = config["hostname"]
self._interface = MythenInterface(self._hostname)
self._apply_configuration()
self._counters["spectrum"] = MythenCounter(self)
self.create_counter(MythenCounter)
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
return MythenAcquistionSlave(self, ctrl_params=ctrl_params, **acq_params)
......@@ -234,18 +234,8 @@ class MythenCounter(Counter):
# Initialization
def __init__(self, controller):
# self._name = "spectrum"
self._controller = controller
super().__init__("spectrum", controller)
# @property
# def name(self):
# return self._name
@property
def controller(self):
return self._controller
# Data properties
@property
......@@ -254,7 +244,7 @@ class MythenCounter(Counter):
@property
def shape(self):
return (self.controller.get_nchannels(),)
return (self._counter_controller.get_nchannels(),)
class MythenAcquistionSlave(AcquisitionSlave):
......
......@@ -66,14 +66,14 @@ def lazy_init(func):
class MusstSamplingCounter(SamplingCounter):
def __init__(self, controller, name, channel, channel_config):
def __init__(self, name, channel, channel_config, controller):
SamplingCounter.__init__(self, name, controller)
self.channel = channel
self.channel_config = channel_config
class MusstIntegratingCounter(IntegratingCounter):
def __init__(self, controller, name, channel, channel_config):
def __init__(self, name, channel, channel_config, controller):
IntegratingCounter.__init__(self, name, controller)
self.channel = channel
self.channel_config = channel_config
......@@ -81,7 +81,9 @@ class MusstIntegratingCounter(IntegratingCounter):
class MusstSamplingCounterController(SamplingCounterController):
def __init__(self, name, master_controller):
super().__init__(name, master_controller=master_controller)
super().__init__(
name, master_controller=master_controller, register_counters=False
)
def read_all(self, *counters):
""" return the values of the given counters as a list.
......@@ -93,7 +95,10 @@ class MusstSamplingCounterController(SamplingCounterController):
class MusstIntegratingCounterController(IntegratingCounterController):
def __init__(self, name, master_controller):
IntegratingCounterController.__init__(
self, name=name, master_controller=master_controller
self,
name=name,
master_controller=master_controller,
register_counters=False,
)
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
......@@ -381,22 +386,17 @@ class musst(CounterController):
)
if channel_type in ("cnt",):
cnt = MusstIntegratingCounter(
self.integrating_counters, cnt_name, cnt_channel, channel_config
self.integrating_counters.create_counter(
MusstIntegratingCounter, cnt_name, cnt_channel, channel_config
)
self.integrating_counters.add_counter(cnt)
elif channel_type in ("encoder", "ssi", "adc5", "adc10"):
cnt = MusstSamplingCounter(
self.sampling_counters, cnt_name, cnt_channel, channel_config
cnt = self.sampling_counters.create_counter(
MusstSamplingCounter, cnt_name, cnt_channel, channel_config
)
cnt_mode = channel_config.get("counter_mode")
if cnt_mode:
cnt.mode = cnt_mode
self.sampling_counters.add_counter(cnt)
def _channels_init(self, config_tree):
""" Handle configured channels """
......@@ -779,7 +779,6 @@ class musst(CounterController):
@autocomplete_property
def counters(self):
all_counters = {}
# all_counters.update(self._counters)
all_counters.update(self.integrating_counters._counters)
all_counters.update(self.sampling_counters._counters)
return counter_namespace(all_counters)
......
......@@ -86,7 +86,7 @@ import enum
import logging
import weakref
import collections
import itertools
import numpy
from bliss.comm.util import get_comm, TCP
......@@ -606,7 +606,6 @@ class PEPU(CounterController):
super().__init__(name, master_controller=master_controller)
# self.name = name
self.bliss_config = config
self.streams = dict()
......@@ -625,6 +624,13 @@ class PEPU(CounterController):
[(i, ChannelCALC(self, i)) for i in self.CALC_CHANNELS]
)
for channel in itertools.chain(
self.in_channels.values(),
self.out_channels.values(),
self.calc_channels.values(),
):
self.create_counter(PepuCounter, channel.name)
if "template" in config:
template_name = "TEMPLATE_" + config["template"].upper()
template = globals()[template_name]
......@@ -746,48 +752,15 @@ class PEPU(CounterController):
def __info__(self):
return "{0}(name={1!r})".format(type(self).__name__, self.name)
@property
def counters(self):
# --- add counters
channels = list(self.in_channels.values()) + list(self.calc_channels.values())
counters = []
for channel in channels:
counters.append(PepuCounter(channel))
self._counters = {cnt.name: cnt for cnt in counters}
return counter_namespace(self._counters)
class PepuCounter(Counter):
def __init__(self, channel):
self.channel = channel
self.acquisition_device = None
super().__init__(self.channel.name, self.channel.pepu)
def __init__(self, name, controller):
super().__init__(name, controller)
# Standard interface
# @property
# def controller(self):
# return self.channel.pepu
# @property
# def name(self):
# return self.channel.name
@property
def dtype(self):
return float
# @property
# def shape(self):
# return ()
# Extra logic
def feed_point(self, stream_data):
self.emit_data_point(stream_data[self.name])
def emit_data_point(self, data_point):
pepu = self.acquisition_device
pepu.channels.update({f"{pepu.name}:{self.name}": data_point})
......@@ -380,4 +380,3 @@ class SimulationCounter(Counter):
def __init__(self, name, config):
super().__init__(name, SimulationCounterController())
self.config = config
self._counter_controller.add_counter(self)
......@@ -104,5 +104,4 @@ def simulation_diode(name, config):
)
else:
diode = SimulationDiodeSamplingCounter(name, controller)
controller._counters[diode.name] = diode
return diode
......@@ -608,10 +608,9 @@ class SpeedgoatCountersController(CounterController):
par_cnt.pop(cnt)
# create counters
self.available_counters = {}
for cnt_name in sig_cnt.keys():
self.available_counters[cnt_name] = SimpleCounter(
self, cnt_name, sig_cnt[cnt_name], par_cnt[cnt_name]
self.create_counter(
SimpleCounter, cnt_name, sig_cnt[cnt_name], par_cnt[cnt_name]
)
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
......@@ -658,7 +657,7 @@ class SpeedgoatCountersController(CounterController):
# return list of values
idx_list = []
for name in counter_list_name:
idx_list.append(self.available_counters[name].read_index())
idx_list.append(getattr(self.counters, name).read_index())
value_list = self.speedgoat.get_signal_value_from_idxs(idx_list)
......@@ -670,7 +669,7 @@ class SpeedgoatCountersController(CounterController):
class SimpleCounter(Counter):
def __init__(self, controller, counter_name, signal_node, param_node):
def __init__(self, counter_name, signal_node, param_node, controller):
super().__init__(counter_name, controller)
self.signal_node = signal_node
......@@ -715,8 +714,6 @@ class SpeedgoatCounter(SamplingCounter):
'speedgoat: Counter "%s" not configured in speedgoat' % name
)
self._counter_controller._counters[name] = self
##########################################################################
########## ##########
......@@ -1359,7 +1356,7 @@ class Speedgoat(object):
self._cache["counters_controller"] = ctrl = SpeedgoatCountersController(
self, signal_node[0], param_node[0]
)
self._cache["counters"] = ctrl.available_counters
self._cache["counters"] = ctrl.counters
return self._cache
......
......@@ -174,8 +174,6 @@ class tango_attr_as_counter(SamplingCounter):
self.tango_uri, TangoCounterController(self.tango_uri)
)
controller._counters[name] = self
log_debug(
controller, f" to read '{self.attribute}' tango attribute."
)
......
......@@ -143,14 +143,11 @@ class PepuAcquisitionSlave(AcquisitionSlave):
# Counter management
def _do_add_counter(self, counter):
assert self.device == counter.channel.pepu
super()._do_add_counter(counter)
counter.acquisition_device = self
def publish(self, data):
for counter in self._counters:
counter.feed_point(data)
self.channels.update(
{f"{self.device.name}:{counter.name}": data[counter.name]}
)
# Standard methods
......
......@@ -149,7 +149,7 @@ bliss % more setup.cfg
[tool:pytest]
addopts = -v --ignore=tests/images --ignore=tests/test_configuration --ignore=tests/controllers_hw
usefixtures = clean_louie clean_gevent clean_session
usefixtures = clean_louie clean_gevent clean_globals
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
......
[tool:pytest]
addopts = -v --ignore=tests/images --ignore=tests/test_configuration --ignore=tests/controllers_hw --ignore=tests/emulators
usefixtures = clean_louie clean_gevent clean_session
usefixtures = clean_louie clean_gevent clean_globals
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
......
......@@ -306,10 +306,8 @@ def test_global_map(beacon, s1hg, roby):
},
"counters": {"thermo_sample", "sample_regulation", "heater"},
"global": {"controllers", "comms", "counters", "axes"},
"heater": {"heater"},
"motion_hooks": {"hook0"},
"sample_regulation": {"thermo_sample", "heater"},
"thermo_sample": {"thermo_sample"},
}
assert links == expected_links
......
......@@ -134,7 +134,7 @@ def test_multiple_greenlets(ports):
assert len(conductor_conn._redis_connection) == 1
def test_single_bus_for_channels(ports):
def test_single_bus_for_channels(beacon):
key = "multi_green"
value = "hello"
......
......@@ -27,6 +27,7 @@ from bliss.config.conductor import connection
from bliss.config.conductor.client import get_default_connection
from bliss.controllers.lima.roi import Roi
from bliss.controllers.wago.wago import ModulesConfig
from bliss.controllers import simulation_diode
from bliss.common import plot
from bliss.common.tango import DeviceProxy, DevFailed
from bliss import logging_startup
......@@ -123,10 +124,12 @@ def clean_gevent():
@pytest.fixture
def clean_session():
# assert main._GLOBAL_DICT['session'] is None
def clean_globals():
yield
global_map.clear()
# reset module-level globals
simulation_diode.DEFAULT_CONTROLLER = None
simulation_diode.DEFAULT_INTEGRATING_CONTROLLER = None
@pytest.fixture(scope="session")
......
......@@ -130,7 +130,7 @@ def test_mythen_counter(beacon, run_command):
assert counter.name == "spectrum"
assert counter.dtype == np.int32
assert counter.shape == (1280,)
assert counter.controller == m
assert counter._counter_controller == m
def test_mythen_from_config(run_command, beacon):
......
......@@ -70,6 +70,9 @@ class DummyDevice(AcquisitionSlave):
class CustomSimulationDiodeController(SamplingCounterController):
def __init__(self):
super().__init__("custom_simulation_diode_controller")
def read(self, counter):
if counter.read_exception:
raise RuntimeError("Diode reading exception")
......
......@@ -10,6 +10,9 @@ def test_exception_in_reading(session):
event = gevent.event.Event()
class CntController(SamplingCounterController):
def __init__(self):
super().__init__("cnt_controller")
def read(self, counter):
try:
if counter.nbpoints > 5:
......
......@@ -6,6 +6,7 @@ from unittest import mock
import pytest
import numpy as np
import gevent.queue
import functools
from bliss import global_map
from bliss.common import scans
......@@ -13,7 +14,7 @@ from bliss.scanning.scan import Scan
from bliss.scanning.chain import AcquisitionChain
from bliss.common.measurementgroup import MeasurementGroup
from bliss.controllers.pepu import PEPU as PepuClass
from bliss.controllers.pepu import PEPU
from bliss.controllers.pepu import ChannelIN, ChannelOUT, ChannelCALC, Signal
from bliss.scanning.acquisition.pepu import PepuAcquisitionSlave
......@@ -27,59 +28,42 @@ def pepu():
trigger = gevent.queue.Queue()
def idata(n):
nb_points = pepu.create_stream.call_args[1]["nb_points"]
def idata(n, create_stream_call_kwargs=None, pepu_counters=None):
nb_points = create_stream_call_kwargs["nb_points"]
assert n == nb_points
points = [[y + x / 10. for y in range(1, 15)] for x in range(n)]
points = [
[y + x / 10. for y in range(1, len(pepu_counters) + 1)] for x in range(n)
]
for point in points:
data = np.array(point)
data.dtype = [(counter.name, float) for counter in pepu.counters]
mode = pepu.create_stream.call_args[1]["trigger"]
data.dtype = [(counter.name, float) for counter in pepu_counters]
mode = create_stream_call_kwargs["trigger"]
if mode.clock == Signal.SOFT:
trigger.get()
yield data
def assert_data(data, n):
for i, counter in enumerate(pepu.counters, 1):
expecting = [i + x / 10. for x in range(n)]
assert list(data[counter.name]) == expecting
with mock.patch("bliss.controllers.pepu.PEPU", autospec=True) as PEPU:
pepu = PEPU.return_value
pepu.name = "pepu1"
pepu.in_channels = OrderedDict(
[(i, ChannelIN(pepu, i)) for i in PepuClass.IN_CHANNELS]
)
pepu.out_channels = OrderedDict(
[(i, ChannelOUT(pepu, i)) for i in PepuClass.OUT_CHANNELS]