pyobs-core 2.0.0.dev4__py3-none-any.whl → 2.0.0.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyobs/comm/comm.py CHANGED
@@ -4,7 +4,6 @@ import asyncio
4
4
  import functools
5
5
  import inspect
6
6
  import logging
7
- import sys
8
7
  from collections.abc import Callable, Coroutine
9
8
  from typing import TYPE_CHECKING, Any, overload
10
9
 
@@ -132,12 +131,20 @@ class Comm:
132
131
  except IndexError:
133
132
  return None
134
133
 
134
+ # collect capabilities (fixed at proxy construction time)
135
+ capabilities: dict[type[Interface], Any] = {}
136
+ for interface in interfaces:
137
+ if interface.capabilities is not None:
138
+ cap = await self._get_capabilities(client, interface)
139
+ if cap is not None:
140
+ capabilities[interface] = cap
141
+
135
142
  # create new proxy
136
- proxy = Proxy(self, client, interfaces)
143
+ proxy = Proxy(self, client, interfaces, capabilities)
137
144
 
138
145
  # subscribe to state
139
146
  for interface in interfaces:
140
- if getattr(interface, "State", None) is not None:
147
+ if interface.state is not None:
141
148
  await self.subscribe_state(client, interface, functools.partial(proxy.update_state, interface))
142
149
 
143
150
  self._proxies[client] = proxy
@@ -450,22 +457,17 @@ class Comm:
450
457
  async def _set_state(self, interface: type[Interface], state: Any) -> None:
451
458
  pass
452
459
 
453
- @staticmethod
454
- def _interface_from_capabilities(caps_cls: type) -> type:
455
- outer_name = caps_cls.__qualname__.rsplit(".", 1)[0]
456
- return getattr(sys.modules[caps_cls.__module__], outer_name)
457
-
458
- async def set_capabilities(self, capabilities: Any) -> None:
460
+ async def set_capabilities(self, interface: type[Interface], capabilities: Any) -> None:
459
461
  """Publish capabilities for this module.
460
462
 
461
- Called by Module.open() for each interface that defines a Capabilities
463
+ Called by Module.open() for each interface that defines a capabilities
462
464
  dataclass. Not intended to be called directly by module authors after
463
465
  that point — capabilities are fixed for the module lifetime.
464
466
 
465
467
  Args:
468
+ interface: Interface type the capabilities belong to.
466
469
  capabilities: Capabilities dataclass instance.
467
470
  """
468
- interface = Comm._interface_from_capabilities(type(capabilities))
469
471
  await self._set_capabilities(interface, capabilities)
470
472
 
471
473
  async def _set_capabilities(self, interface: type[Interface], capabilities: Any) -> None:
@@ -120,7 +120,7 @@ class LocalComm(Comm):
120
120
 
121
121
  async def _get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
122
122
  """Fetch capabilities from a remote module."""
123
- if not hasattr(interface, "Capabilities"):
123
+ if interface.capabilities is None:
124
124
  return None
125
125
  try:
126
126
  remote = self._network.get_client(module)
pyobs/comm/proxy.py CHANGED
@@ -21,13 +21,20 @@ class Proxy:
21
21
 
22
22
  __module__ = "pyobs.comm"
23
23
 
24
- def __init__(self, comm: Comm, client: str, interfaces: list[type[Interface]]):
24
+ def __init__(
25
+ self,
26
+ comm: Comm,
27
+ client: str,
28
+ interfaces: list[type[Interface]],
29
+ capabilities: dict[type[Interface], Any] | None = None,
30
+ ):
25
31
  """Creates a new proxy.
26
32
 
27
33
  Args:
28
34
  comm: Comm object to use for connection.
29
35
  client: Name of client to connect to.
30
36
  interfaces: List of interfaces supported by client.
37
+ capabilities: Pre-populated capabilities dict, keyed by interface type.
31
38
  """
32
39
 
33
40
  # set client and interfaces
@@ -53,8 +60,9 @@ class Proxy:
53
60
  # create methods
54
61
  self._methods = self._create_methods()
55
62
 
56
- # store state
63
+ # store state and capabilities
57
64
  self._state: dict[type[Interface], Any] = {}
65
+ self._capabilities: dict[type[Interface], Any] = capabilities if capabilities is not None else {}
58
66
 
59
67
  @property
60
68
  def name(self) -> str:
@@ -170,6 +178,10 @@ class Proxy:
170
178
  """Latest known state for the given interface, or None if nothing has arrived yet."""
171
179
  return self._state.get(interface)
172
180
 
181
+ def capabilities(self, interface: type[Interface]) -> Any | None:
182
+ """Capabilities for the given interface, populated once at Proxy construction."""
183
+ return self._capabilities.get(interface)
184
+
173
185
  async def wait_for_state(
174
186
  self,
175
187
  interface: type[Interface],
@@ -828,7 +828,7 @@ class XmppComm(Comm):
828
828
 
829
829
  async def _get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
830
830
  """Fetch and deserialize capabilities for a remote module's interface."""
831
- if not hasattr(interface, "Capabilities"):
831
+ if interface.capabilities is None:
832
832
  return None
833
833
  # Use full JID (with resource) if we know it — bare JID may not route correctly
834
834
  full_jid = next(
@@ -848,7 +848,7 @@ class XmppComm(Comm):
848
848
  for elem in child:
849
849
  tag = elem.tag.split("}")[-1]
850
850
  if tag == "capabilities" and f"{{{ns}}}" in elem.tag:
851
- return _xml_to_dataclass(elem, interface.Capabilities)
851
+ return _xml_to_dataclass(elem, interface.capabilities)
852
852
  return None
853
853
 
854
854
  async def _set_presence(self, state: ModuleState, error_string: str = "") -> None:
@@ -15,16 +15,18 @@ class BinningState:
15
15
  time: Time = field(default_factory=Time.now)
16
16
 
17
17
 
18
+ @dataclass
19
+ class BinningCapabilities:
20
+ binnings: list[BinningState] = field(default_factory=list)
21
+
22
+
18
23
  class IBinning(Interface, metaclass=ABCMeta):
19
24
  """The camera supports binning, to be used together with :class:`~pyobs.interfaces.ICamera`."""
20
25
 
21
26
  __module__ = "pyobs.interfaces"
22
27
 
23
28
  state = BinningState
24
-
25
- @dataclass
26
- class Capabilities:
27
- binnings: list[BinningState] = field(default_factory=list)
29
+ capabilities = BinningCapabilities
28
30
 
29
31
  @abstractmethod
30
32
  async def set_binning(self, x: int, y: int, **kwargs: Any) -> None:
@@ -40,4 +42,4 @@ class IBinning(Interface, metaclass=ABCMeta):
40
42
  ...
41
43
 
42
44
 
43
- __all__ = ["IBinning", "BinningState"]
45
+ __all__ = ["IBinning", "BinningState", "BinningCapabilities"]
@@ -7,16 +7,17 @@ from typing import Any
7
7
  from .interface import Interface
8
8
 
9
9
 
10
+ @dataclass
11
+ class ConfigCapabilities:
12
+ caps: dict[str, tuple[bool, bool, bool]] = field(default_factory=dict)
13
+
14
+
10
15
  class IConfig(Interface, metaclass=ABCMeta):
11
16
  """The module allows access to some of its configuration options."""
12
17
 
13
18
  __module__ = "pyobs.interfaces"
14
19
 
15
- @dataclass
16
- class Capabilities:
17
- readable: list[str] = field(default_factory=list)
18
- writable: list[str] = field(default_factory=list)
19
- options: dict[str, list[str]] = field(default_factory=dict)
20
+ capabilities = ConfigCapabilities
20
21
 
21
22
  @abstractmethod
22
23
  async def get_config_value(self, name: str, **kwargs: Any) -> Any:
@@ -47,4 +48,4 @@ class IConfig(Interface, metaclass=ABCMeta):
47
48
  ...
48
49
 
49
50
 
50
- __all__ = ["IConfig"]
51
+ __all__ = ["IConfig", "ConfigCapabilities"]
@@ -14,16 +14,18 @@ class FilterState:
14
14
  time: Time = field(default_factory=Time.now)
15
15
 
16
16
 
17
+ @dataclass
18
+ class FiltersCapabilities:
19
+ filters: list[str] = field(default_factory=list)
20
+
21
+
17
22
  class IFilters(IMotion, metaclass=ABCMeta):
18
23
  """The module can change filters in a device."""
19
24
 
20
25
  __module__ = "pyobs.interfaces"
21
26
 
22
27
  state = FilterState
23
-
24
- @dataclass
25
- class Capabilities:
26
- filters: list[str] = field(default_factory=list)
28
+ capabilities = FiltersCapabilities
27
29
 
28
30
  @abstractmethod
29
31
  async def set_filter(self, filter_name: str, **kwargs: Any) -> None:
@@ -39,4 +41,4 @@ class IFilters(IMotion, metaclass=ABCMeta):
39
41
  ...
40
42
 
41
43
 
42
- __all__ = ["IFilters", "FilterState"]
44
+ __all__ = ["IFilters", "FilterState", "FiltersCapabilities"]
@@ -16,16 +16,18 @@ class ImageFormatState:
16
16
  time: Time = field(default_factory=Time.now)
17
17
 
18
18
 
19
+ @dataclass
20
+ class ImageFormatCapabilities:
21
+ image_formats: list[str] = field(default_factory=list)
22
+
23
+
19
24
  class IImageFormat(Interface, metaclass=ABCMeta):
20
25
  """The module supports different image formats (e.g. INT16, FLOAT32), mainly used by cameras."""
21
26
 
22
27
  __module__ = "pyobs.interfaces"
23
28
 
24
29
  state = ImageFormatState
25
-
26
- @dataclass
27
- class Capabilities:
28
- image_formats: list[str] = field(default_factory=list)
30
+ capabilities = ImageFormatCapabilities
29
31
 
30
32
  @abstractmethod
31
33
  async def set_image_format(self, fmt: ImageFormat, **kwargs: Any) -> None:
@@ -40,4 +42,4 @@ class IImageFormat(Interface, metaclass=ABCMeta):
40
42
  ...
41
43
 
42
44
 
43
- __all__ = ["IImageFormat", "ImageFormatState"]
45
+ __all__ = ["IImageFormat", "ImageFormatState", "ImageFormatCapabilities"]
pyobs/interfaces/IMode.py CHANGED
@@ -14,16 +14,18 @@ class ModeState:
14
14
  time: Time = field(default_factory=Time.now)
15
15
 
16
16
 
17
+ @dataclass
18
+ class ModeCapabilities:
19
+ modes: dict[str, list[str]] = field(default_factory=dict) # group -> list of modes
20
+
21
+
17
22
  class IMode(Interface, metaclass=ABCMeta):
18
23
  """The module can change modes in a device."""
19
24
 
20
25
  __module__ = "pyobs.interfaces"
21
26
 
22
27
  state = ModeState
23
-
24
- @dataclass
25
- class Capabilities:
26
- modes: dict[str, list[str]] = field(default_factory=dict) # group -> list of modes
28
+ capabilities = ModeCapabilities
27
29
 
28
30
  @abstractmethod
29
31
  async def set_mode(self, mode: str, group: int = 0, **kwargs: Any) -> None:
@@ -40,4 +42,4 @@ class IMode(Interface, metaclass=ABCMeta):
40
42
  ...
41
43
 
42
44
 
43
- __all__ = ["IMode", "ModeState"]
45
+ __all__ = ["IMode", "ModeState", "ModeCapabilities"]
@@ -7,15 +7,18 @@ from typing import Any
7
7
  from .interface import Interface
8
8
 
9
9
 
10
+ @dataclass
11
+ class ModuleCapabilities:
12
+ label: str = ""
13
+ version: str = ""
14
+
15
+
10
16
  class IModule(Interface, metaclass=ABCMeta):
11
17
  """The module is actually a module. Implemented by all modules."""
12
18
 
13
19
  __module__ = "pyobs.interfaces"
14
20
 
15
- @dataclass
16
- class Capabilities:
17
- version: str = ""
18
- label: str = ""
21
+ capabilities = ModuleCapabilities
19
22
 
20
23
  @abstractmethod
21
24
  async def reset_error(self, **kwargs: Any) -> bool:
@@ -23,4 +26,4 @@ class IModule(Interface, metaclass=ABCMeta):
23
26
  ...
24
27
 
25
28
 
26
- __all__ = ["IModule"]
29
+ __all__ = ["IModule", "ModuleCapabilities"]
@@ -17,6 +17,11 @@ class MultiFiberState:
17
17
  time: Time = field(default_factory=Time.now)
18
18
 
19
19
 
20
+ @dataclass
21
+ class MultiFiberCapabilities:
22
+ fiber_count: int = 0
23
+
24
+
20
25
  class IMultiFiber(Interface, metaclass=ABCMeta):
21
26
  """An interface for multi-fiber setups that helps to set/get a fiber and retrieve position and size of the
22
27
  current fiber on the acquisition/guiding image."""
@@ -24,11 +29,7 @@ class IMultiFiber(Interface, metaclass=ABCMeta):
24
29
  __module__ = "pyobs.interfaces"
25
30
 
26
31
  state = MultiFiberState
27
-
28
- @dataclass
29
- class Capabilities:
30
- fiber_count: int = 0
31
- fiber_names: list[str] = field(default_factory=list)
32
+ capabilities = MultiFiberCapabilities
32
33
 
33
34
  @abstractmethod
34
35
  async def abort(self, **kwargs: Any) -> None:
@@ -45,4 +46,4 @@ class IMultiFiber(Interface, metaclass=ABCMeta):
45
46
  ...
46
47
 
47
48
 
48
- __all__ = ["IMultiFiber", "MultiFiberState"]
49
+ __all__ = ["IMultiFiber", "MultiFiberState", "MultiFiberCapabilities"]
@@ -6,14 +6,17 @@ from dataclasses import dataclass
6
6
  from .IData import IData
7
7
 
8
8
 
9
+ @dataclass
10
+ class VideoCapabilities:
11
+ video: str = ""
12
+
13
+
9
14
  class IVideo(IData, metaclass=ABCMeta):
10
15
  """The module controls a video streaming device."""
11
16
 
12
17
  __module__ = "pyobs.interfaces"
13
18
 
14
- @dataclass
15
- class Capabilities:
16
- url: str = ""
19
+ capabilities = VideoCapabilities
17
20
 
18
21
 
19
- __all__ = ["IVideo"]
22
+ __all__ = ["IVideo", "VideoCapabilities"]
@@ -17,16 +17,21 @@ class WindowState:
17
17
  time: Time = field(default_factory=Time.now)
18
18
 
19
19
 
20
+ @dataclass
21
+ class WindowCapabilities:
22
+ full_frame_x: int = 0
23
+ full_frame_y: int = 0
24
+ full_frame_width: int = 0
25
+ full_frame_height: int = 0
26
+
27
+
20
28
  class IWindow(Interface, metaclass=ABCMeta):
21
29
  """The camera supports windows, to be used together with :class:`~pyobs.interfaces.ICamera`."""
22
30
 
23
31
  __module__ = "pyobs.interfaces"
24
32
 
25
33
  state = WindowState
26
-
27
- @dataclass
28
- class Capabilities:
29
- full_frame: WindowState = field(default_factory=lambda: WindowState(0, 0, 0, 0))
34
+ capabilities = WindowCapabilities
30
35
 
31
36
  @abstractmethod
32
37
  async def set_window(self, left: int, top: int, width: int, height: int, **kwargs: Any) -> None:
@@ -44,4 +49,4 @@ class IWindow(Interface, metaclass=ABCMeta):
44
49
  ...
45
50
 
46
51
 
47
- __all__ = ["IWindow", "WindowState"]
52
+ __all__ = ["IWindow", "WindowState", "WindowCapabilities"]
@@ -16,28 +16,29 @@ from .IAcquisition import IAcquisition
16
16
  from .IAutoFocus import IAutoFocus
17
17
  from .IAutoGuiding import IAutoGuiding
18
18
  from .IAutonomous import IAutonomous
19
- from .IBinning import BinningState, IBinning
19
+ from .IBinning import BinningCapabilities, BinningState, IBinning
20
20
  from .ICalibrate import ICalibrate
21
21
  from .ICamera import ICamera
22
- from .IConfig import IConfig
22
+ from .IConfig import ConfigCapabilities, IConfig
23
23
  from .ICooling import CoolingState, ICooling
24
24
  from .IData import IData
25
25
  from .IDome import IDome
26
26
  from .IExposure import ExposureState, IExposure
27
27
  from .IExposureTime import ExposureTimeState, IExposureTime
28
- from .IFilters import FilterState, IFilters
28
+ from .IFilters import FiltersCapabilities, FilterState, IFilters
29
29
  from .IFitsHeaderAfter import IFitsHeaderAfter
30
30
  from .IFitsHeaderBefore import IFitsHeaderBefore
31
31
  from .IFlatField import IFlatField
32
32
  from .IFocuser import FocuserState, IFocuser
33
33
  from .IFocusModel import IFocusModel
34
34
  from .IGain import GainState, IGain
35
- from .IImageFormat import IImageFormat, ImageFormatState
35
+ from .IImageFormat import IImageFormat, ImageFormatCapabilities, ImageFormatState
36
36
  from .IImageType import IImageType, ImageTypeState
37
- from .IMode import IMode, ModeState
38
- from .IModule import IModule
37
+ from .ILatLon import ILatLon, LatLonCapabilities
38
+ from .IMode import IMode, ModeCapabilities, ModeState
39
+ from .IModule import IModule, ModuleCapabilities
39
40
  from .IMotion import DeviceMotionStatus, IMotion, MotionState
40
- from .IMultiFiber import IMultiFiber, MultiFiberState
41
+ from .IMultiFiber import IMultiFiber, MultiFiberCapabilities, MultiFiberState
41
42
  from .interface import Interface
42
43
  from .IOffsetsAltAz import AltAzOffsetState, IOffsetsAltAz
43
44
  from .IOffsetsRaDec import IOffsetsRaDec, RaDecOffsetState
@@ -57,22 +58,25 @@ from .IStartStop import IStartStop
57
58
  from .ISyncTarget import ISyncTarget
58
59
  from .ITelescope import ITelescope
59
60
  from .ITemperatures import ITemperatures, SensorReading, TemperaturesState
60
- from .IVideo import IVideo
61
+ from .IVideo import IVideo, VideoCapabilities
61
62
  from .IWeather import IWeather
62
- from .IWindow import IWindow, WindowState
63
+ from .IWindow import IWindow, WindowCapabilities, WindowState
63
64
 
64
65
  __all__ = [
65
66
  "IAbortable",
66
67
  "IAcquisition",
67
68
  "ILatLon",
69
+ "LatLonCapabilities",
68
70
  "IAutoFocus",
69
71
  "IAutoGuiding",
70
72
  "IAutonomous",
71
73
  "IBinning",
74
+ "BinningCapabilities",
72
75
  "BinningState",
73
76
  "ICalibrate",
74
77
  "ICamera",
75
78
  "IConfig",
79
+ "ConfigCapabilities",
76
80
  "ICooling",
77
81
  "CoolingState",
78
82
  "IDome",
@@ -81,6 +85,7 @@ __all__ = [
81
85
  "IExposureTime",
82
86
  "ExposureTimeState",
83
87
  "IFilters",
88
+ "FiltersCapabilities",
84
89
  "FilterState",
85
90
  "IFitsHeaderAfter",
86
91
  "IFitsHeaderBefore",
@@ -91,17 +96,21 @@ __all__ = [
91
96
  "IGain",
92
97
  "GainState",
93
98
  "IImageFormat",
99
+ "ImageFormatCapabilities",
94
100
  "ImageFormatState",
95
101
  "IData",
96
102
  "IImageType",
97
103
  "ImageTypeState",
98
104
  "IMode",
105
+ "ModeCapabilities",
99
106
  "ModeState",
100
107
  "IModule",
108
+ "ModuleCapabilities",
101
109
  "IMotion",
102
110
  "DeviceMotionStatus",
103
111
  "MotionState",
104
112
  "IMultiFiber",
113
+ "MultiFiberCapabilities",
105
114
  "MultiFiberState",
106
115
  "IPointingAltAz",
107
116
  "AltAzState",
@@ -133,8 +142,10 @@ __all__ = [
133
142
  "RaDecOffsetState",
134
143
  "ISpectrograph",
135
144
  "IVideo",
145
+ "VideoCapabilities",
136
146
  "IWeather",
137
147
  "IWindow",
148
+ "WindowCapabilities",
138
149
  "WindowState",
139
150
  "Interface",
140
151
  ]
@@ -7,6 +7,7 @@ class Interface(metaclass=ABCMeta):
7
7
 
8
8
  version: int = 1
9
9
  state: ClassVar[type | None] = None
10
+ capabilities: ClassVar[type | None] = None
10
11
 
11
12
  __module__ = "pyobs.interfaces"
12
13
  pass
@@ -14,7 +14,7 @@ from numpy.typing import NDArray
14
14
 
15
15
  from pyobs.events import NewImageEvent
16
16
  from pyobs.images import Image
17
- from pyobs.interfaces import IExposureTime, IImageType, IVideo
17
+ from pyobs.interfaces import IExposureTime, IImageType, IVideo, VideoCapabilities
18
18
  from pyobs.mixins.fitsheader import ImageFitsHeaderMixin
19
19
  from pyobs.modules import Module, timeout
20
20
  from pyobs.utils.cache import DataCache
@@ -166,7 +166,7 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
166
166
  self._is_listening = True
167
167
 
168
168
  # publish video URL as capability
169
- await self.comm.set_capabilities(IVideo.Capabilities(url=self._video_path))
169
+ await self.comm.set_capabilities(IVideo, VideoCapabilities(video=self._video_path))
170
170
 
171
171
  async def close(self) -> None:
172
172
  """Close server"""
@@ -18,6 +18,7 @@ from photutils.datasets import make_model_image, make_noise_image
18
18
 
19
19
  from pyobs.images import Image
20
20
  from pyobs.interfaces import (
21
+ BinningCapabilities,
21
22
  BinningState,
22
23
  CoolingState,
23
24
  GainState,
@@ -25,6 +26,7 @@ from pyobs.interfaces import (
25
26
  ICooling,
26
27
  IGain,
27
28
  IImageFormat,
29
+ ImageFormatCapabilities,
28
30
  ImageFormatState,
29
31
  IPointingRaDec,
30
32
  ITemperatures,
@@ -32,6 +34,7 @@ from pyobs.interfaces import (
32
34
  RaDecState,
33
35
  SensorReading,
34
36
  TemperaturesState,
37
+ WindowCapabilities,
35
38
  WindowState,
36
39
  )
37
40
  from pyobs.modules.camera.basecamera import BaseCamera
@@ -121,11 +124,22 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
121
124
  async def open(self) -> None:
122
125
  """Opens camera."""
123
126
  # publish capabilities before super().open()
124
- await self.comm.set_capabilities(IWindow.Capabilities(full_frame=WindowState(*self._full_frame)))
125
127
  await self.comm.set_capabilities(
126
- IBinning.Capabilities(binnings=[BinningState(x=i[0], y=i[1]) for i in [(1, 1), (2, 2), (3, 3)]])
128
+ IWindow,
129
+ WindowCapabilities(
130
+ full_frame_x=self._full_frame[0],
131
+ full_frame_y=self._full_frame[1],
132
+ full_frame_width=self._full_frame[2],
133
+ full_frame_height=self._full_frame[3],
134
+ ),
135
+ )
136
+ await self.comm.set_capabilities(
137
+ IBinning,
138
+ BinningCapabilities(binnings=[BinningState(x=i[0], y=i[1]) for i in [(1, 1), (2, 2), (3, 3)]]),
139
+ )
140
+ await self.comm.set_capabilities(
141
+ IImageFormat, ImageFormatCapabilities(image_formats=[ImageFormat.INT8, ImageFormat.INT16])
127
142
  )
128
- await self.comm.set_capabilities(IImageFormat.Capabilities(image_formats=[ImageFormat.INT8, ImageFormat.INT16]))
129
143
 
130
144
  await BaseCamera.open(self)
131
145
 
pyobs/modules/module.py CHANGED
@@ -11,7 +11,7 @@ import packaging.version
11
11
  from py_expression_eval import Parser
12
12
 
13
13
  from pyobs.events import Event, ModuleOpenedEvent
14
- from pyobs.interfaces import IConfig, IModule, Interface
14
+ from pyobs.interfaces import ConfigCapabilities, IConfig, IModule, Interface, ModuleCapabilities
15
15
  from pyobs.object import Object
16
16
  from pyobs.utils import exceptions as exc
17
17
  from pyobs.utils.enums import ModuleState
@@ -155,17 +155,15 @@ class Module(Object, IModule, IConfig):
155
155
  # publish base capabilities
156
156
  if self._comm is not None:
157
157
  await self._comm.set_capabilities(
158
- IModule.Capabilities(
158
+ IModule,
159
+ ModuleCapabilities(
159
160
  version=await self.get_version(),
160
161
  label=await self.get_label(),
161
- )
162
+ ),
162
163
  )
163
164
  await self._comm.set_capabilities(
164
- IConfig.Capabilities(
165
- readable=[n for n, (r, w, o) in self._config_caps.items() if r],
166
- writable=[n for n, (r, w, o) in self._config_caps.items() if w],
167
- options={n: [] for n, (r, w, o) in self._config_caps.items() if o},
168
- )
165
+ IConfig,
166
+ ConfigCapabilities(caps=self._config_caps),
169
167
  )
170
168
 
171
169
  async def close(self) -> None:
@@ -11,6 +11,7 @@ from astropy.coordinates import SkyCoord
11
11
 
12
12
  from pyobs.events import FilterChangedEvent, OffsetsRaDecEvent
13
13
  from pyobs.interfaces import (
14
+ FiltersCapabilities,
14
15
  FilterState,
15
16
  FocuserState,
16
17
  IFilters,
@@ -183,7 +184,7 @@ class DummyTelescope(
183
184
  await self._change_motion_status(MotionStatus.IDLE)
184
185
 
185
186
  # publish initial states and capabilities
186
- await self.comm.set_capabilities(IFilters.Capabilities(filters=self._filters))
187
+ await self.comm.set_capabilities(IFilters, FiltersCapabilities(filters=self._filters))
187
188
  await self.comm.set_state(IFocuser, FocuserState(focus=self._focus, focus_offset=0.0))
188
189
  await self.comm.set_state(IFilters, FilterState(filter=self._filter_name))
189
190
  await self.comm.set_state(
@@ -5,7 +5,7 @@ import logging
5
5
  from typing import Any
6
6
 
7
7
  from pyobs.events import ModeChangedEvent
8
- from pyobs.interfaces import IMode, IMotion, IReady, ModeState, ReadyState
8
+ from pyobs.interfaces import IMode, IMotion, IReady, ModeCapabilities, ModeState, ReadyState
9
9
  from pyobs.mixins import MotionStatusMixin
10
10
  from pyobs.modules import Module
11
11
  from pyobs.utils.enums import MotionStatus
@@ -45,7 +45,7 @@ class DummyMode(MotionStatusMixin, Module, IMode, IMotion):
45
45
  await self.comm.set_state(IReady, ReadyState(ready=True))
46
46
 
47
47
  # publish capabilities and initial state
48
- await self.comm.set_capabilities(IMode.Capabilities(modes=self._mode_options))
48
+ await self.comm.set_capabilities(IMode, ModeCapabilities(modes=self._mode_options))
49
49
  await self.comm.set_state(IMode, ModeState(modes=dict(self._modes)))
50
50
 
51
51
  def _group_name(self, group: int) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev4
3
+ Version: 2.0.0.dev5
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -10,19 +10,19 @@ pyobs/cli/pyobs.py,sha256=h4XrVrk3kwFu5h-3b7qIpIxYyhdBkwPdhttgGsB7Ztg,3643
10
10
  pyobs/cli/pyobsd.py,sha256=PW2V6ILeDT1wxNtsEku68O3jySPrL4fGyrEm5ZLCoaQ,14151
11
11
  pyobs/cli/pyobsw.py,sha256=uXrJoz8fpGjADPUyf0-MxNcCKqVK2PkI8i9TWS-VS7w,740
12
12
  pyobs/comm/__init__.py,sha256=Pc_hlxXLcVf5sWtf_wCJ9PbC1_sGJHaNsF6qWBEQJzU,1567
13
- pyobs/comm/comm.py,sha256=T_gXCE8XnxPLEm5xqvZX5WZFxUNAjzMXJK9AC2VxWCA,22122
13
+ pyobs/comm/comm.py,sha256=iHEuRaNV0oBUbYHVEnSprrmwnZgtVq7IXjzmR91QyS4,22309
14
14
  pyobs/comm/commlogging.py,sha256=ycilwfEnCfEDY8Ya9_a5y9TqMsk6alFP8QQbWsIwHrU,1273
15
- pyobs/comm/proxy.py,sha256=By-xT9aXr1BPxyCEOXMcCLG-ElbxRQqdbSSh11wBmH8,6749
15
+ pyobs/comm/proxy.py,sha256=luWT1BHJF1o9NzST63k-ydnMNnlhqIbvyCizXvYX_WE,7269
16
16
  pyobs/comm/dummy/__init__.py,sha256=TwLSVExj81QPeIsd-pQR_FRRDWk-ueaCKITywxdW9l4,58
17
17
  pyobs/comm/dummy/dummycomm.py,sha256=qqBnsEdUIjSLhdFrE1aeAIrS-Hz8oOgoucga4qPqt78,1701
18
18
  pyobs/comm/local/__init__.py,sha256=8UwEkp6qBtDWeJjbV0zrFw_YHUT-mLmnB5_sKXIhfJ4,113
19
- pyobs/comm/local/localcomm.py,sha256=vf3bdbSm1vLs3-FyieYeEqjYHCs6zwW86vY8eAuJOz0,6608
19
+ pyobs/comm/local/localcomm.py,sha256=kVBXyzvhXNJWsYGV1VxHhUeJuaWawd_QL1Kq64ckgmk,6600
20
20
  pyobs/comm/local/localnetwork.py,sha256=igFMS_tP5ob-ORnYeA4f6Oacxh4kb8ehGX9EEZcd3uE,796
21
21
  pyobs/comm/xmpp/__init__.py,sha256=U8A_ZAdKS3qH-SjdzZ28tEBzCrnnpAL3qrfoEl92Q_E,55
22
22
  pyobs/comm/xmpp/rpc.py,sha256=cW30kSKu-xcVu-3BfHnWkRS4qzgILn3QTpRezdvBpFI,11898
23
23
  pyobs/comm/xmpp/serializer.py,sha256=cFIU7pIvyZNgzjAzfTUR3WUksTavqdxs16CoXv9WwbY,11666
24
24
  pyobs/comm/xmpp/xmppclient.py,sha256=lf8rs7ige0Y9AeKvUdkrzDZN_ryu_k6oWy-8Jux1vaQ,3866
25
- pyobs/comm/xmpp/xmppcomm.py,sha256=jM_LlZ0Rp1oTz21v7Ty_3M9X39nWtaX_XKULRSsCT0c,35026
25
+ pyobs/comm/xmpp/xmppcomm.py,sha256=YmUDH8a4yKLLuT7BasXGFUZy-TEz9n2gepn-ku8o1S8,35018
26
26
  pyobs/comm/xmpp/xep_0009/__init__.py,sha256=YbgcfeEcRH-M3HQ0IyHEPhPLHIj8xcWYKHSRYIIuX8k,103
27
27
  pyobs/comm/xmpp/xep_0009/binding.py,sha256=VhT1hSMYu8aLXDqraF6YWUzWYrRCBMSbOzmmSVhv3Tg,5970
28
28
  pyobs/comm/xmpp/xep_0009/rpc.py,sha256=4op9TRO2OFOOApRF7kS6MTk_gaUnJgLTMK5OtBRpJVs,1605
@@ -137,28 +137,28 @@ pyobs/interfaces/IAcquisition.py,sha256=OtwECKsauzboyYxCsh0C-wBAIuxgfcgMIgdLCsdY
137
137
  pyobs/interfaces/IAutoFocus.py,sha256=OQ0qrGbaaXUp_NVemU3bQQlKXB0kzaNetnOewkyhXFw,1511
138
138
  pyobs/interfaces/IAutoGuiding.py,sha256=DXK2MHapNmJTtTokm4MtvUy1ckkFVwQAcGLpNRNEt9o,282
139
139
  pyobs/interfaces/IAutonomous.py,sha256=XwBWSOX8WsN5BYnbpCrNezWw9q4bLbydH6_o1BhxDg8,272
140
- pyobs/interfaces/IBinning.py,sha256=nl7ScciINTG7a5KEw26WCfSKdUkAJP5RqKbaz4yM2hM,956
140
+ pyobs/interfaces/IBinning.py,sha256=TUzPZ1FtE8x6OtFZodQP2AwSeJbMeJs1dhOsJiPdTfU,1014
141
141
  pyobs/interfaces/ICalibrate.py,sha256=D8BlHO_7hGTAxSkyYI6ZObXjAsR9-YIRVyyWckMlHtc,379
142
142
  pyobs/interfaces/ICamera.py,sha256=_uPRgsHc360rTA3zaLA78Di-oLAtdHvoi19r4iRwN_Q,247
143
- pyobs/interfaces/IConfig.py,sha256=pNAzrRjoT2Maf9s2t4kzSC0BcNAVcv8D0qm_RMLNai0,1318
143
+ pyobs/interfaces/IConfig.py,sha256=lmEhnSCfBM1h7TTyjqJdP1BxdXDK9h0MlTXzV4PpX64,1269
144
144
  pyobs/interfaces/ICooling.py,sha256=P7p4xcCkYAWpEGfoAb1EoVVz4okuGVKJTHjmiCV1gSg,1072
145
145
  pyobs/interfaces/IData.py,sha256=Uq4t7lkmBsDNhVX25zZAGtW51fnH-CGY56xUcuKEHMY,660
146
146
  pyobs/interfaces/IDome.py,sha256=ZFoSmzL-_IkpsbvmreX8B8qNpzpWCjoJ35Xf0CDK5V0,318
147
147
  pyobs/interfaces/IExposure.py,sha256=T8XGaN2P-HNm3hb7zoLokcItLkMaKK1C1yO6BXCjZRc,658
148
148
  pyobs/interfaces/IExposureTime.py,sha256=LoWB6s3bVxs6Ffq7L_1nWusF2PUoitL3A_VbwkVWk0A,1005
149
- pyobs/interfaces/IFilters.py,sha256=0ccQQX4slQVe4f4aY9BqCsVi0JANL0EX70TkKvy3Ebc,940
149
+ pyobs/interfaces/IFilters.py,sha256=3oygugIuAFIuVaPVaF9SNHDoRz2X4WuZP5ZSTijd8c4,998
150
150
  pyobs/interfaces/IFitsHeaderAfter.py,sha256=rj-id4ReBpVlfFZsJyqhp-BSRIfDOK0PZ68Efz7jKTM,813
151
151
  pyobs/interfaces/IFitsHeaderBefore.py,sha256=cYSDXU4zo0cGyrBglsvfSy0KureYkE1W8VzaYNDpvmg,819
152
152
  pyobs/interfaces/IFlatField.py,sha256=So6fp_jOgh6GwCmFMePFIygE2jAX_2CJxptrmGO76d0,615
153
153
  pyobs/interfaces/IFocusModel.py,sha256=hEkUgwDp_nnltNT7dVa9Ec56Ek4zbqTS2fNS5sPIz_Y,570
154
154
  pyobs/interfaces/IFocuser.py,sha256=qN8O2uNuK6yZUh9bygcm485fT-Z3FhQmbmDMNSM6toQ,1157
155
155
  pyobs/interfaces/IGain.py,sha256=9r-Dm4t3IRLlx3pFxKtBBUJugD0zzfDjW8Cuet6v-sk,1097
156
- pyobs/interfaces/IImageFormat.py,sha256=Qzn6lEm4HprfwNDhfJUSJ1SrQTb6MJUgtz7aMA4Spl8,1018
156
+ pyobs/interfaces/IImageFormat.py,sha256=WYOAkbwIEjbhZ1n7BEV7VE0pxHJQ64fKBYq1_hrv4SM,1088
157
157
  pyobs/interfaces/IImageType.py,sha256=Bl4Alqms8ZhL2m7Ksk_chQ7-zcSWsm4n0-mCLnimuPs,836
158
- pyobs/interfaces/IMode.py,sha256=ZaYWbKZlfdu7wRcLfdcVE2eSwq5oNYox1Sj-eLEq3Us,1059
159
- pyobs/interfaces/IModule.py,sha256=DPMEIJJPwAl-V0Qg9fYzXqbAwcTX3pb9H9bdqh-TeSM,570
158
+ pyobs/interfaces/IMode.py,sha256=uXax87M_qfVCFSHL7qWYkyDppHdv-5mPM8_cmfrrkG4,1108
159
+ pyobs/interfaces/IModule.py,sha256=fprB7l-27xtgzcf1baS_PnBIQKTXvdgEXbXJrt-L9vE,622
160
160
  pyobs/interfaces/IMotion.py,sha256=PLWN9U_iHh8_QlOLu9KAfsXZGJLIxcKvRWGThniWU_w,1336
161
- pyobs/interfaces/IMultiFiber.py,sha256=mFXcTYw2OL1Xy80fItAMr4yMoelx2Qw_SBdt4BpfZoQ,1209
161
+ pyobs/interfaces/IMultiFiber.py,sha256=wO8t5L5YJLhyhcUtnUoh7CiQtGd2Rlzo1HWPDcd5ZZc,1215
162
162
  pyobs/interfaces/IOffsetsAltAz.py,sha256=e2fxJ70K_Oj9PZPUUkU4B2V20lk1uTTKDLSzB0_C02Q,1143
163
163
  pyobs/interfaces/IOffsetsRaDec.py,sha256=gtNdKSf9RWdG2sM_txjtee1WpFwFWKfmAOtbb4RRBi0,1133
164
164
  pyobs/interfaces/IPointingAltAz.py,sha256=eZ4j0Jp9uXthEgWdIyNpBZB1qQ3HyRPy8ylmKMucTSY,1068
@@ -177,11 +177,11 @@ pyobs/interfaces/IStartStop.py,sha256=5wknpRN73kwTIfQpD_ahCU-ok7kuR0Kex4K9cVh6Mt
177
177
  pyobs/interfaces/ISyncTarget.py,sha256=E81pNStzUx1IxjQr0J9-YMPfKutOj0FIzaK7zC-Usz0,493
178
178
  pyobs/interfaces/ITelescope.py,sha256=raaOtP62xAKOrJFU-qXR8ULD4VD1Mqm8GSXVc0Cgoz4,218
179
179
  pyobs/interfaces/ITemperatures.py,sha256=Sz6NK69NYlMH1G2uNpRxswWDaKZRwLT1ZdHuX1-g6EY,725
180
- pyobs/interfaces/IVideo.py,sha256=G5F0GSJi2kMd5x4t3-YhQZv3wK_2eMVN1tbZG4eX5So,340
180
+ pyobs/interfaces/IVideo.py,sha256=Kjd9-3t3tqmm-yOHc4nfmUBV9Xo82NG_fcJ6Zhl73fc,395
181
181
  pyobs/interfaces/IWeather.py,sha256=2Hn9guREymj_aW7PTObOz5q4j1_1MSrxWrqjHBtm-es,1493
182
- pyobs/interfaces/IWindow.py,sha256=CpWA_9MhxYpHY9wvz1p3ZiNhEYt7nVu78fjRmVGMMnU,1128
183
- pyobs/interfaces/__init__.py,sha256=MAQS8-PdjsTZa6RoUTkP8ubGZx2YgsfEN2BViIyrI7Y,3977
184
- pyobs/interfaces/interface.py,sha256=R83JD6-LJcLx-mjH51FYFj5o1JCr6nq9l3laLZiC0sE,274
182
+ pyobs/interfaces/IWindow.py,sha256=ZgM6s5P65Pi7W_WYVbrPSeATjCs-pfkuiDcX6H-KuXc,1211
183
+ pyobs/interfaces/__init__.py,sha256=mK0sypa-qakO0KyEU09f7MZthC1FpbnLUSyyz_pWTcw,4482
184
+ pyobs/interfaces/interface.py,sha256=lw2muMNZ_nR6eFaCmsRmdWmU-L329_fNdJPC8aAOTb4,321
185
185
  pyobs/mixins/__init__.py,sha256=-yFPxCyAzHRdqGXmQMqMy4ITFLZ4dioI2FP4-Y2xmoY,722
186
186
  pyobs/mixins/camerasettings.py,sha256=MeoftNyRp0mczfpi9AVTdwwe012s8W0V5IFclI_jlXs,2283
187
187
  pyobs/mixins/fitsheader.py,sha256=5g-jBM9pLqzhyT59zM8NNOTFrJpaf-hHrog5imlGjK4,15087
@@ -192,13 +192,13 @@ pyobs/mixins/pipeline.py,sha256=2QIe7ihEX2iD07U-6TJ9NoFhEKJflqM_OA5uVyQPPTY,1437
192
192
  pyobs/mixins/waitformotion.py,sha256=PFm51CqPjn23_SZ3Mgi5RPfvVBsbPaP5YF0J8G8PWSo,3002
193
193
  pyobs/mixins/weatheraware.py,sha256=5GkZTY6om1-EsqsWRq_TIdz7OCUgGstcHo8wAJGFmDg,4932
194
194
  pyobs/modules/__init__.py,sha256=3jIJUmO1Vc7PD3rMCzhtYNanOME7f-2z-mot5f8ugwY,2447
195
- pyobs/modules/module.py,sha256=dQVlek3pWlIejynCX_s98dykp2JrxGBvz-2tkHNH9tc,23566
195
+ pyobs/modules/module.py,sha256=49cbCnrTB-lkAhZ48tYrclBSABTVGfdPTwdbwiY8GdI,23401
196
196
  pyobs/modules/camera/__init__.py,sha256=ZlkLRWssKCPi6jIFEw275jXcpv9ORjVCjsT8Ag3fK2E,421
197
197
  pyobs/modules/camera/adaptive.py,sha256=o06lkJSNnLPT19Abql0npi0RtIARKgn_CROwr7556qU,14013
198
198
  pyobs/modules/camera/basecamera.py,sha256=m8rUf_BPcJwGtJaciPIqSwFDVYOJYyxxHopOuVS5hwE,16759
199
199
  pyobs/modules/camera/basespectrograph.py,sha256=uKf8WrlPO9sA_Ad-w4IMy1OOGSO8ABu4-QFDTW1vyeE,6697
200
- pyobs/modules/camera/basevideo.py,sha256=JdJc4lY1R_5WMGQSfsHiy4ghvCj2usJ9IO0dyz76Bpc,15135
201
- pyobs/modules/camera/dummycamera.py,sha256=sZhUFvhyMuYPJG7W72TnwwgjpjVjvDyKvLq880Xo15I,14611
200
+ pyobs/modules/camera/basevideo.py,sha256=HkCgN67R2vK2h6QjK7H_lz6Wz7qw_39ZUyuM_LvwIDo,15162
201
+ pyobs/modules/camera/dummycamera.py,sha256=EPDS87csuotNwxHGfvt9aISEaV-eE-59XNqYSdJnoBI,14967
202
202
  pyobs/modules/camera/dummyspectrograph.py,sha256=JNMgPo_Qq6xNQxFMfBV6oE-592Wbg-l2st2vqxF475g,2335
203
203
  pyobs/modules/camera/dummyvideo.py,sha256=35iabldXQ1ayGooY9Hy2xwZHGQmy9O3I18JDcyqC94k,2367
204
204
  pyobs/modules/camera/pipelinecamera.py,sha256=cyYf9aZIEJQ1PbQyZkuLyLHLeBLbJFefTOipdj2PxwI,1962
@@ -238,12 +238,12 @@ pyobs/modules/roof/baseroof.py,sha256=EUM6IBnW5qRHSi6c-0hPUkRA5tVU2DcsGJuNH77lM3
238
238
  pyobs/modules/roof/dummyroof.py,sha256=K081F9LyydFvgOng_i8b1Uem8ManVluyLoC-DONBmxM,3576
239
239
  pyobs/modules/telescope/__init__.py,sha256=Tz7HHWusnhdobNp81aOHCWTp_bD4B-b5tNYg89uko1U,206
240
240
  pyobs/modules/telescope/basetelescope.py,sha256=6tt16T4Gm_uAaURVQXno9Dx-lfYgUtz8IdI7mN90tbw,12551
241
- pyobs/modules/telescope/dummytelescope.py,sha256=Nurv6qDX8guCpgnKpqFU0NLnulmk2VXD-vCrMSfOC-w,12594
241
+ pyobs/modules/telescope/dummytelescope.py,sha256=sPgpqIxJog75Ix6pQjFhYhvRfHYlotbhOVNMjxBos_4,12627
242
242
  pyobs/modules/test/__init__.py,sha256=jr2YiStF4phPZjb5JigYvn7R3VBw5tkrGFIT3zWgaHI,128
243
243
  pyobs/modules/test/standalone.py,sha256=j7kRdSABQSPwGSoB7Ayn3gCgerN0iqnaVr1-qi23BiI,1049
244
244
  pyobs/modules/utils/__init__.py,sha256=BQVCfNiV9-AWJfzrSKWFqRXdhYhXRVXVLbgL4sSN6os,415
245
245
  pyobs/modules/utils/autonomouswarning.py,sha256=tUtWAkODcZXSnNwWmSvbYkRafBIMQQBYewgNcI8db0g,4978
246
- pyobs/modules/utils/dummymode.py,sha256=dnM_-ArSjRlTzGktngJxzsyMZfHF0Z2NlXv5KeyLV70,2977
246
+ pyobs/modules/utils/dummymode.py,sha256=NSoq301nex7F4fmUzFUUnjzMYANvhoFYsVpxCn2etbE,3000
247
247
  pyobs/modules/utils/fluentlogger.py,sha256=pS1Ej3NRMZQi7698zYkS0PN6s0k--b-10g_wyPdtRGQ,1686
248
248
  pyobs/modules/utils/httpfilecache.py,sha256=KHeMBjF204dv_0YFOfpRRhdOYbWMzE1Iw87chdHpUgA,3385
249
249
  pyobs/modules/utils/kiosk.py,sha256=1BHg2uZPHPdrL1tJUyUxZgqHJvp-hbvQIhxzU-WqGvQ,4871
@@ -425,8 +425,8 @@ pyobs/vfs/vfs.py,sha256=buHTmO6VUjSypkEMVxY8rDKrpMz65AY2OE2hGEsQsLI,10780
425
425
  pyobs/vfs/filelists/__init__.py,sha256=NaTGFjLGh043j_F7kWEx_cXaII7t3CbYguRk2g5hEqw,111
426
426
  pyobs/vfs/filelists/filelist.py,sha256=kCco_5G2gLH-nxWik-wcs6lGuc_-3LeeyR9L4_srf30,201
427
427
  pyobs/vfs/filelists/testing.py,sha256=M8f2BULdjRCc7JFOKhAH7Kwre_uVQC0xLcv9xGTfNFw,425
428
- pyobs_core-2.0.0.dev4.dist-info/METADATA,sha256=k7HrPtO2covszQeYm0qweVKpxwsW7pynylu9nZyOXdk,1927
429
- pyobs_core-2.0.0.dev4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
430
- pyobs_core-2.0.0.dev4.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
431
- pyobs_core-2.0.0.dev4.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
432
- pyobs_core-2.0.0.dev4.dist-info/RECORD,,
428
+ pyobs_core-2.0.0.dev5.dist-info/METADATA,sha256=2_ez6EnaMD-FaSd5VZ8LogWpdvDf72I5LFa5E_F2W1c,1927
429
+ pyobs_core-2.0.0.dev5.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
430
+ pyobs_core-2.0.0.dev5.dist-info/entry_points.txt,sha256=0ceIn3dTDwWdGdJ5VdTvYim7vKkdTe6aqqpoaG065UY,109
431
+ pyobs_core-2.0.0.dev5.dist-info/licenses/LICENSE,sha256=gC8djBliCENDmuRvzCoejpWbsn8eqKx61Hc8bmDZ2LI,1099
432
+ pyobs_core-2.0.0.dev5.dist-info/RECORD,,