Source code for dripline.implementations.entity_endpoints
'''
A Entity is an enhanced implementation of a Dripline Endpoint with simple logging capabilities.
The Entitys defined here are more broad-ranging than a single service, obviating the need to define new Entitys for each new service or provider.
When implementing a Entity, please remember:
- All communication must be configured to return a response. If no useful get is possible, consider a \*OPC?
- set_and_check is a generally desirable functionality
Generic Entity catalog (in order of ease-of-use):
- SimpleSCPIEntity: quick and simple minimal Entity
- SimpleSCPIGetEntity/SimpleSCPISetEntity: limited instance of above with disabled Get/Set
- FormatEntity: utility Entity with expanded functionality
'''
import asteval # used for FormatEntity
import re # used for FormatEntity
from dripline.core import Entity, calibrate, ThrowReply
import logging
logger = logging.getLogger(__name__)
__all__ = []
__all__.append('SimpleSCPIEntity')
[docs]class SimpleSCPIEntity(Entity):
'''
Convenience Entity for interacting with SCPI endpoints that support basic assignment and query syntax.
(That is, assignments of the form "base_string <new_value>" and queries of the form "base_string?")
Commands requiring a more complex structure, such as specifying channels or other arguments, should use FormatEntity or a custom Entity.
'''
def __init__(self,
base_str=None,
**kwargs):
'''
Args:
base_str (str): string used to generate SCPI commands; get will be of the form "base_str?"; set will be of the form "base_str <value>;base_str?"
'''
if base_str is None:
raise ValueError('<base_str> is required to __init__ SimpleSCPIEntity instance')
else:
self.cmd_base = base_str
Entity.__init__(self, **kwargs)
@calibrate()
def on_get(self):
to_send = [self.cmd_base + '?']
result = self.service.send_to_device(to_send)
logger.debug(f'raw result is: {result}')
return result
[docs] def on_set(self, value):
to_send = [f'{self.cmd_base} {value};{self.cmd_base}?']
return self.service.send_to_device(to_send)
__all__.append('SimpleSCPIGetEntity')
[docs]class SimpleSCPIGetEntity(SimpleSCPIEntity):
'''
Identical to SimpleSCPIEntity, but with an explicit exception if on_set is attempted
'''
def __init__(self, **kwargs):
SimpleSCPIEntity.__init__(self, **kwargs)
[docs] def on_set(self, value):
raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support set")
__all__.append('SimpleSCPISetEntity')
[docs]class SimpleSCPISetEntity(SimpleSCPIEntity):
'''
Modelled on SimpleSCPIEntity, but with an explicit exception if on_get is attempted.
Uses \*OPC? to ensure a response is generated when making an assignment.
'''
def __init__(self, **kwargs):
SimpleSCPIEntity.__init__(self, **kwargs)
[docs] def on_get(self):
# exceptions.DriplineMethodNotSupportedError
raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support get")
[docs] def on_set(self, value):
to_send = [f'{self.cmd_base} {value};*OPC?']
return self.service.send_to_device(to_send)
__all__.append('FormatEntity')
[docs]class FormatEntity(Entity):
'''
Utility Entity allowing arbitrary set and query syntax and formatting for more complicated usage cases
No assumption about SCPI communication syntax.
'''
def __init__(self,
get_str=None,
get_reply_float=False,
set_str=None,
set_value_lowercase=True,
set_value_map=None,
extract_raw_regex=None,
**kwargs):
'''
Args:
get_str (str): sent verbatim in the event of on_get; if None, getting of endpoint is disabled
get_reply_float (bool): apply special formatting to get return
set_str (str): sent as set_str.format(value) in the event of on_set; if None, setting of endpoint is disabled
set_value_lowercase (bool): default option to map all string set value to .lower()
**WARNING**: never set to False if using a set_value_map dict
set_value_map (str||dict): inverse of calibration to map raw set value to value sent; either a dictionary or an asteval-interpretable string
extract_raw_regex (str): regular expression search pattern applied to get return. Must be constructed with an extraction group keyed with the name "value_raw" (ie r'(?P<value_raw>)' )
'''
Entity.__init__(self, **kwargs)
self._get_reply_float = get_reply_float
self._get_str = get_str
self._set_str = set_str
self._set_value_map = set_value_map
self._extract_raw_regex = extract_raw_regex
self.evaluator = asteval.Interpreter()
if set_value_map is not None and not isinstance(set_value_map, (dict,str)):
raise ValueError(f"Invalid set_value_map config for {self.name}; type is {type(set_value_map)} not dict")
self._set_value_lowercase = set_value_lowercase
if isinstance(set_value_map, dict) and not set_value_lowercase:
raise ValueError(f"Invalid config option for {self.name} with set_value_map and set_value_lowercase=False")
@calibrate()
def on_get(self):
if self._get_str is None:
# exceptions.DriplineMethodNotSupportedError
raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support get")
result = self.service.send_to_device([self._get_str])
logger.debug(f'result is: {result}')
if self._extract_raw_regex is not None:
first_result = result
matches = re.search(self._extract_raw_regex, first_result)
if matches is None:
logger.error('matching returned none')
# exceptions.DriplineValueError
raise ThrowReply('resource_error', 'device returned unparsable result, [{}] has no match to input regex [{}]'.format(first_result, self._extract_raw_regex))
logger.debug(f"matches are: {matches.groupdict()}")
result = matches.groupdict()['value_raw']
return result
[docs] def on_set(self, value):
if self._set_str is None:
# exceptions.DriplineMethodNotSupportedError
raise ThrowReply('service_error', f"endpoint '{self.name}' does not support set")
if isinstance(value, str) and self._set_value_lowercase:
value = value.lower()
if self._set_value_map is None:
mapped_value = value
elif isinstance(self._set_value_map, dict):
mapped_value = self._set_value_map[value]
elif isinstance(self._set_value_map, str):
mapped_value = self.evaluator(self._set_value_map.format(value))
logger.debug(f'value is {value}; mapped value is: {mapped_value}')
return self.service.send_to_device([self._set_str.format(mapped_value)])