18 Commits

Author SHA1 Message Date
daa4c72133 fixes on secop gui
- do not display a dialog when a command returns None
- apply fmtstr, if available

Change-Id: I74da3e86e4eae9000608581e0a0f6e8c72cef715
2021-06-04 12:27:46 +02:00
96f4133a4b changes defaults in ls370sim
Change-Id: I6388358c42a4b115fc5aec4df0d4588457140744
2021-06-04 12:27:30 +02:00
1409959f53 remove debug print statement 2021-05-06 16:27:09 +02:00
10147b0db5 first tries with uniax stick 2021-04-30 17:28:05 +02:00
e2f258658c add cfg/deleop directory
Change-Id: Ia9f15af8bb68a8306a9d966ef2b217d587f0ab28
2021-04-29 11:12:21 +02:00
3d67fef557 treat raised error message in modules.py correctly when arg is not a list
Change-Id: I4082a65849c71b3f212f2c8c345a6881f972d107
2021-04-29 11:12:21 +02:00
eb3d35e1a6 fixed doule entry tcoil1 in ma7.cfg
Change-Id: I547e7dcb6cca72004dd3ea8e56a0601e7bf8771a
2021-04-29 11:12:21 +02:00
2863c2bca0 added cfg files for SINQ Frappy
Change-Id: Icb1ea5e2e1f9aedd77a6bac7ed6b14458aea4aa7
2021-04-29 11:12:21 +02:00
390c955a8a improve comments (poller.py) 2021-04-29 11:12:20 +02:00
539d97a733 updated README.md
the procedure may still need to be discussed

Change-Id: Iddf60ac93309ef2131d55f3858e11d6111a976b4
2021-04-29 11:12:20 +02:00
1fd16bbc43 pollOneParam has no return value (avoid pylint complaint)
Change-Id: Iab75c970e106617ffd4e612d358da491312ca54b
2021-04-29 11:12:20 +02:00
5e77a43f6c fix 'ts' value 2021-04-29 11:12:20 +02:00
3e7b008a59 fix handling of unknown sea messages 2021-04-29 11:10:09 +02:00
2f96a2db92 added trinamic.cfg 2021-04-27 17:33:14 +02:00
fab950550d trinamic driver and bytesio module
Change-Id: Id634e7514fecab6fd6bc3edf81e25ad41c2bb12f
2021-04-27 16:42:29 +02:00
801eab4b13 fix issue with a module based on a SEA subobject
Change-Id: I15102ef0e17cd4414e8699e2967292b3853d2ac0
2021-04-27 16:00:45 +02:00
06551b17e2 improve more errors during startup
errors from a module a combined with a header and intended
details

Change-Id: I4be7bc2f8455fb0e3c9346f3bb23ac88e7589604
2021-04-27 16:00:26 +02:00
4a0796a1bd fix poller issue with dynamic interval
calculating next due must also be done when current poll was not due

Change-Id: I18d9cbc61aa6ca66f3fc2dc4cdfa1fce29a87705
2021-04-27 15:02:13 +02:00
23 changed files with 448 additions and 1114 deletions

View File

@ -1,8 +1,35 @@
[NODE] [node seatest.psi.ch]
description = sea client (communication only) description = SEA test
id = comm.sea.psi.ch
[seaconn] [interface tcp]
type = tcp
bindto = 0.0.0.0
bindport = 5002
[module seaconn]
class = secop_psi.sea.SeaClient class = secop_psi.sea.SeaClient
description = a SEA connection description = a SEA connection
visibility = 1 uri = tcp://samenv.psi.ch:8645
#[module t1]
#class = secop_psi.sea.SeaDrivable
#iodev = seaconn
#json_descr = tt.flamp.config
#remote_paths = . t1
#[module tw1]
#class = secop_psi.sea.SeaReadable
#iodev = seaconn
#json_descr = wall.lampovenwall.addon
#remote_paths = tw1
#[module pv]
#class = secop_psi.sea.SeaReadable
#iodev = seaconn
#json_descr = pv.flamp.config
#[module mf]
#class = secop.Proxy
#remote_class = secop_psi.ppms.Field
#description = magnetic field
#iodev = secnode

View File

@ -1,8 +0,0 @@
[NODE]
description = sea client (communication only)
id = comm.sea.psi.ch
[seaconn]
class = secop_psi.sea.SeaConfigCreator
description = a SEA connection. will shut down after getting the description
visibility = 1

View File

@ -1,17 +0,0 @@
[NODE]
id = ls240.psi.ch
description = ls240 test
[INTERFACE]
uri = tcp://5000
[T]
description = temperature on uniax stick
class = secop_psi.ls240.Ls240
iodev = T_iodev
[T_iodev]
class = secop.bytesio.BytesIO
description = IO device for LS240
uri = serial:///dev/ttyUSB0?baudrate=9600+parity=EVEN
timeout = 0.2

View File

@ -1,11 +1,11 @@
[NODE] [NODE]
description = thin film oven for AMOR description = thin film oven for AMOR
id = fftf.config.sea.psi.ch id = mbe.config.sea.psi.ch
[sea_main] [sea_main]
class = secop_psi.sea.SeaClient class = secop_psi.sea.SeaClient
description = main sea connection for fftf.config description = SEA connection to mbe
config = fftf.config config = mbe.config
service = main service = main
[tt] [tt]
@ -13,24 +13,23 @@ class = secop_psi.sea.SeaDrivable
iodev = sea_main iodev = sea_main
sea_object = tt sea_object = tt
[cc]
class = secop_psi.sea.SeaReadable
iodev = sea_main
sea_object = cc
[p] [p]
class = secop_psi.sea.SeaReadable class = secop_psi.sea.SeaReadable
iodev = sea_main iodev = sea_main
sea_object = p sea_object = p
extra_modules = vacuumpump gasflow tlimit tlimit_without_vacuum extra_modules = vacuumpump, gasflow
[vacuumpump]
class = secop_psi.sea.SeaWritable
iodev = sea_main
single_module = p.vacuumpump
[gasflow] [gasflow]
class = secop_psi.sea.SeaWritable class = secop_psi.sea.SeaWritable
iodev = sea_main iodev = sea_main
single_module = p.gasflow single_module = p.gasflow
[vacuumpump]
class = secop_psi.sea.SeaWritable
iodev = sea_main
sea_object = p
rel_paths = vacuumpump tlimit tlimit_without_vacuum
[table]
class = secop_psi.sea.SeaModule
iodev = sea_main
sea_object = table

View File

@ -1,24 +0,0 @@
[NODE]
description = Keithley 2450 sourcemeter
id = smamor.config.sea.psi.ch
[sea_main]
class = secop_psi.sea.SeaClient
description = main sea connection for smamor.config
config = smamor.config
service = main
[smi]
class = secop_psi.sea.SeaDrivable
iodev = sea_main
sea_object = smi
[smv]
class = secop_psi.sea.SeaDrivable
iodev = sea_main
sea_object = smv
[r]
class = secop_psi.sea.SeaReadable
iodev = sea_main
sea_object = r

View File

@ -1,8 +1,35 @@
[NODE] [node seatest.psi.ch]
description = sea client (communication only) description = SEA test
id = comm.sea.psi.ch
[seaconn] [interface tcp]
type = tcp
bindto = 0.0.0.0
bindport = 5002
[module seaconn]
class = secop_psi.sea.SeaClient class = secop_psi.sea.SeaClient
description = a SEA connection description = a SEA connection
visibility = 1 uri = tcp://samenv.psi.ch:8645
#[module t1]
#class = secop_psi.sea.SeaDrivable
#iodev = seaconn
#json_descr = tt.flamp.config
#remote_paths = . t1
#[module tw1]
#class = secop_psi.sea.SeaReadable
#iodev = seaconn
#json_descr = wall.lampovenwall.addon
#remote_paths = tw1
#[module pv]
#class = secop_psi.sea.SeaReadable
#iodev = seaconn
#json_descr = pv.flamp.config
#[module mf]
#class = secop.Proxy
#remote_class = secop_psi.ppms.Field
#description = magnetic field
#iodev = secnode

View File

@ -1,5 +1,4 @@
{"tt": {"base": "/tt", "params": [ {"tt": {"base": "/tt", "params": [{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 14},
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 14},
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3}, {"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
@ -49,10 +48,7 @@
{"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"}, {"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"},
{"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"}, {"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"}, {"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
{"path": "remote", "type": "bool"}]}, {"path": "remote", "type": "bool"}]}, "cc": {"base": "/cc", "params": [{"path": "", "type": "bool", "kids": 96},
"cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96},
{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"}, {"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
@ -148,24 +144,10 @@
{"path": "tm", "type": "float", "visibility": 3}, {"path": "tm", "type": "float", "visibility": 3},
{"path": "tv", "type": "float", "visibility": 3}, {"path": "tv", "type": "float", "visibility": 3},
{"path": "tq", "type": "float", "visibility": 3}, {"path": "tq", "type": "float", "visibility": 3},
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}, {"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}, "p": {"base": "/p", "params": [{"path": "", "type": "float", "kids": 6},
"p": {"base": "/p", "params": [
{"path": "", "type": "float", "kids": 6},
{"path": "send", "type": "text", "readonly": false, "cmd": "p send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "p send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "vacuumpump", "type": "bool", "readonly": false, "cmd": "p vacuumpump"}, {"path": "vacuumpump", "type": "bool", "readonly": false, "cmd": "p vacuumpump"},
{"path": "gasflow", "type": "bool", "readonly": false, "cmd": "p gasflow"}, {"path": "gasflow", "type": "bool", "readonly": false, "cmd": "p gasflow"},
{"path": "tlimit", "type": "float", "readonly": false, "cmd": "p tlimit"}, {"path": "tlimit", "type": "float", "readonly": false, "cmd": "p tlimit"},
{"path": "tlimit_without_vacuum", "type": "float", "readonly": false, "cmd": "p tlimit_without_vacuum"}]}, {"path": "tlimit_without_vacuum", "type": "float", "readonly": false, "cmd": "p tlimit_without_vacuum"}]}}
"table": {"base": "/table", "params": [
{"path": "", "type": "none", "kids": 8},
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "fix_tt_set_prop", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_prop"},
{"path": "val_tt_set_prop", "type": "float"},
{"path": "tbl_tt_set_prop", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_prop", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_set_integ", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_integ"},
{"path": "val_tt_set_integ", "type": "float"},
{"path": "tbl_tt_set_integ", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_integ", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."}]}}

View File

@ -1,36 +0,0 @@
{"smi": {"base": "/smi", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run smi", "kids": 13},
{"path": "send", "type": "text", "readonly": false, "cmd": "smi send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "smi is_running", "visibility": 3},
{"path": "target", "type": "float"},
{"path": "output", "type": "bool", "readonly": false, "cmd": "smi output"},
{"path": "set", "type": "float", "readonly": false, "cmd": "smi set"},
{"path": "limited", "type": "float"},
{"path": "limit", "type": "float", "readonly": false, "cmd": "smi limit"},
{"path": "lastmsg", "type": "text"},
{"path": "script", "type": "text", "readonly": false, "cmd": "smi script", "description": "scriptfile containing 'while' and 'halt' scripts"},
{"path": "delay", "type": "float", "readonly": false, "cmd": "smi delay"},
{"path": "step", "type": "float", "readonly": false, "cmd": "smi step"},
{"path": "codeversion", "type": "text", "visibility": 3}]},
"smv": {"base": "/smv", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run smv", "kids": 13},
{"path": "send", "type": "text", "readonly": false, "cmd": "smv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "smv is_running", "visibility": 3},
{"path": "target", "type": "float"},
{"path": "output", "type": "bool", "readonly": false, "cmd": "smv output"},
{"path": "set", "type": "float", "readonly": false, "cmd": "smv set"},
{"path": "limited", "type": "float"},
{"path": "limit", "type": "float", "readonly": false, "cmd": "smv limit"},
{"path": "lastmsg", "type": "text"},
{"path": "script", "type": "text", "readonly": false, "cmd": "smv script", "description": "scriptfile containing 'while' and 'halt' scripts"},
{"path": "delay", "type": "float", "readonly": false, "cmd": "smv delay"},
{"path": "step", "type": "float", "readonly": false, "cmd": "smv step"},
{"path": "codeversion", "type": "text", "visibility": 3}]},
"r": {"base": "/r", "params": [
{"path": "", "type": "float", "kids": 2},
{"path": "send", "type": "text", "readonly": false, "cmd": "r send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}]}}

View File

@ -8,8 +8,8 @@ uri = tcp://5000
[drv_iodev] [drv_iodev]
description = description =
class = secop.bytesio.BytesIO class = secop.bytesio.BytesIO
uri = serial:///dev/ttyUSB0?baudrate=57600 # uri = serial:///dev/ttyUSB0?baudrate=57600
# uri = serial:///dev/ttyUSB0?baudrate=9600 uri = serial:///dev/ttyUSB0?baudrate=9600
[drv] [drv]
description = trinamic motor test description = trinamic motor test
@ -17,9 +17,9 @@ class = secop_psi.trinamic.Motor
iodev = drv_iodev iodev = drv_iodev
standby_current=0.1 standby_current=0.1
maxcurrent=1.4 maxcurrent=1.4
acceleration=150. acceleration=50
movelimit=360 maxspeed=200
speed=40 zero=-36
encoder_tolerance=3.6 enc_tolerance=3.6
free_wheeling=0.1 free_wheeling=0.001
power_down_delay=0.1 pull_up=1

View File

@ -8,39 +8,25 @@ uri = tcp://5000
[drv_iodev] [drv_iodev]
description = description =
class = secop.bytesio.BytesIO class = secop.bytesio.BytesIO
# uri = serial:///dev/ttyUSB1?baudrate=57600 uri = serial:///dev/ttyUSB0?baudrate=57600
uri = tcp://192.168.127.254:3002 # uri = serial:///dev/ttyUSB0?baudrate=9600
[drv] [drv]
description = trinamic motor test description = trinamic motor test
class = secop_psi.trinamic.Motor class = secop_psi.trinamic.Motor
iodev = drv_iodev iodev = drv_iodev
standby_current=0.1 standby_current=0.1
maxcurrent=0.2 maxcurrent=1.4
acceleration=150. acceleration=50
movelimit=360 maxspeed=200
speed=40 zero=-36
encoder_tolerance=3.6 enc_tolerance=3.6
free_wheeling=0.1 free_wheeling=0.001
power_down_delay=0.1 pull_up=1
[force] [force]
description = DPM driver to read out the transducer value, write and read the offset and scale factor description = DPM driver to read out the transducer value, write and read the offset and scale factor
class = secop_psi.dpm.DPM3 class = secop_psi.dpm.DPM3
#uri = serial:///dev/ttyUSB0?baudrate=9600 uri = serial:///dev/ttyUSB1?baudrate=9600
uri = tcp://192.168.127.254:3001
digits = 2 digits = 2
scale_factor = 0.0156 scale_factor = 0.0156
motor = drv
[res]
description = temperature on uniax stick
class = secop_psi.ls340res.ResChannel
uri = tcp://192.168.127.254:3003
channel = C
[T]
class = secop_psi.softcal.Sensor
rawsensor = res
calib = /home/l_samenv/frappy/secop_psi/calcurves/X132254.340
value.unit = K

View File

@ -37,5 +37,3 @@ from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
from secop.properties import Property from secop.properties import Property
from secop.proxy import Proxy, SecNode, proxy_class from secop.proxy import Proxy, SecNode, proxy_class
from secop.stringio import HasIodev, StringIO from secop.stringio import HasIodev, StringIO
from secop.bytesio import BytesIO
from secop.persistent import PersistentMixin, PersistentParam

View File

@ -251,9 +251,7 @@ class AsynSerial(AsynConn):
if not fullname.startswith(name): if not fullname.startswith(name):
raise ConfigError('illegal parity: %s' % parity) raise ConfigError('illegal parity: %s' % parity)
options['parity'] = name[0] options['parity'] = name[0]
if 'timeout' in options: if 'timeout' not in options:
options['timeout'] = float(self.timeout)
else:
options['timeout'] = self.timeout options['timeout'] = self.timeout
try: try:
self.connection = Serial(dev, **options) self.connection = Serial(dev, **options)

View File

@ -30,7 +30,7 @@ from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
IntRange, StatusType, StringType, TextType, TupleOf, get_datatype IntRange, StatusType, StringType, TextType, TupleOf, get_datatype
from secop.errors import BadValueError, ConfigError, InternalError, \ from secop.errors import BadValueError, ConfigError, InternalError, \
ProgrammingError, SECoPError, SilentError, secop_error ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import formatException, getGeneralConfig, mkthread from secop.lib import formatException, mkthread
from secop.lib.enum import Enum from secop.lib.enum import Enum
from secop.params import Accessible, Command, Parameter from secop.params import Accessible, Command, Parameter
from secop.poller import BasicPoller, Poller from secop.poller import BasicPoller, Poller
@ -385,7 +385,7 @@ class Module(HasAccessibles):
if k in self.parameters or k in self.propertyDict: if k in self.parameters or k in self.propertyDict:
setattr(self, k, v) setattr(self, k, v)
cfgdict.pop(k) cfgdict.pop(k)
except (ValueError, TypeError) as e: except (ValueError, TypeError):
# self.log.exception(formatExtendedStack()) # self.log.exception(formatExtendedStack())
errors.append('module %s, parameter %s: %s' % (self.name, k, e)) errors.append('module %s, parameter %s: %s' % (self.name, k, e))

View File

@ -39,6 +39,10 @@ class Accessible(HasProperties):
kwds = None # is a dict if it might be used as Override kwds = None # is a dict if it might be used as Override
def __init__(self, **kwds):
super().__init__()
self.init(kwds)
def init(self, kwds): def init(self, kwds):
# do not use self.propertyValues.update here, as no invalid values should be # do not use self.propertyValues.update here, as no invalid values should be
# assigned to properties, even not before checkProperties # assigned to properties, even not before checkProperties
@ -149,9 +153,6 @@ class Parameter(Accessible):
handler = Property( handler = Property(
'[internal] overload the standard read and write functions', ValueType(), '[internal] overload the standard read and write functions', ValueType(),
export=False, default=None, settable=False) export=False, default=None, settable=False)
persistent = Property(
'[internal] persistent setting', BoolType(),
export=False, default=False)
initwrite = Property( initwrite = Property(
'''[internal] write this parameter on initialization '''[internal] write this parameter on initialization
@ -164,7 +165,7 @@ class Parameter(Accessible):
readerror = None readerror = None
def __init__(self, description=None, datatype=None, inherit=True, *, unit=None, constant=None, **kwds): def __init__(self, description=None, datatype=None, inherit=True, *, unit=None, constant=None, **kwds):
super().__init__() super().__init__(**kwds)
if datatype is not None: if datatype is not None:
if not isinstance(datatype, DataType): if not isinstance(datatype, DataType):
if isinstance(datatype, type) and issubclass(datatype, DataType): if isinstance(datatype, type) and issubclass(datatype, DataType):
@ -177,8 +178,6 @@ class Parameter(Accessible):
if 'default' in kwds: if 'default' in kwds:
self.default = datatype(kwds['default']) self.default = datatype(kwds['default'])
self.init(kwds) # datatype must be defined before we can treat dataset properties like fmtstr or unit
if description is not None: if description is not None:
self.description = inspect.cleandoc(description) self.description = inspect.cleandoc(description)
@ -314,8 +313,7 @@ class Command(Accessible):
func = None func = None
def __init__(self, argument=False, *, result=None, inherit=True, **kwds): def __init__(self, argument=False, *, result=None, inherit=True, **kwds):
super().__init__() super().__init__(**kwds)
self.init(kwds)
if result or kwds or isinstance(argument, DataType) or not callable(argument): if result or kwds or isinstance(argument, DataType) or not callable(argument):
# normal case # normal case
if argument is False and result: if argument is False and result:

View File

@ -1,147 +0,0 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""Mixin for keeping parameters persistent
For hardware not keeping parameters persistent, we might want to store them in Frappy.
The following example will make 'param1' and 'param2' persistent, i.e. whenever
one of the parameters is changed, either by a change command or by access from
an other interface to the hardware, it is saved to a file, and reloaded after
a power down / power up cycle. In order to make this work properly, there is a
mechanism needed to detect power down (i.e. a reading a hardware parameter
taking a special value on power up).
An additional use might be the example of a motor with cyclic reading of an
encoder value, which looses the counts of how many turns already happened on
power down.
This can be solved by comparing the loaded encoder value self.encoder with a
fresh value from the hardware and then adjusting the zero point accordingly.
class MyClass(PersistentMixin, ...):
param1 = PersistentParam(...)
param2 = PersistentParam(...)
encoder = PersistentParam(...)
...
def read_encoder(self):
encoder = <get encoder from hardware>
if <power down/power up cycle detected>:
self.loadParameters()
<fix encoder turns by comparing loaded self.encoder with encoder from hw>
else:
self.saveParameters()
"""
import os
import json
from secop.errors import BadValueError, ConfigError, InternalError, \
ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import getGeneralConfig
from secop.params import Parameter, Property, BoolType, Command
from secop.modules import HasAccessibles
class PersistentParam(Parameter):
persistent = Property('persistence flag', BoolType(), default=True)
class PersistentMixin(HasAccessibles):
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
# write=False: write will happen later
self.initData = {}
for pname in self.parameters:
pobj = self.parameters[pname]
if not pobj.readonly and getattr(pobj, 'persistent', False):
self.initData[pname] = pobj.value
self.writeDict.update(self.loadParameters(write=False))
print('initData', self.initData)
def loadParameters(self, write=True):
"""load persistent parameters
:return: persistent parameters which have to be written
is called upon startup and may be called from a module
when a hardware powerdown is detected
"""
persistentdir = os.path.join(getGeneralConfig()['logdir'], 'persistent')
self.persistentFile = os.path.join(persistentdir, '%s.%s.json' % (self.DISPATCHER.equipment_id, self.name))
try:
with open(self.persistentFile, 'r') as f:
self.persistentData = json.load(f)
except Exception:
self.persistentData = {}
writeDict = {}
for pname in self.parameters:
pobj = self.parameters[pname]
if getattr(pobj, 'persistent', False) and pname in self.persistentData:
try:
value = pobj.datatype.import_value(self.persistentData[pname])
pobj.value = value
if not pobj.readonly:
writeDict[pname] = value
except Exception as e:
self.log.warning('can not restore %r to %r (%r)' % (pname, value, e))
if write:
self.writeDict.update(writeDict)
self.writeInitParams()
return writeDict
def saveParameters(self):
"""save persistent parameters
- to be called regularely explicitly by the module
- the caller has to make sure that this is not called after
a power down of the connected hardware before loadParameters
"""
if self.writeDict:
# do not save before all values are written to the hw, as potentially
# factory default values were read in the mean time
return
data = {k: v.export_value() for k, v in self.parameters.items()
if getattr(v, 'persistent', False)}
if data != self.persistentData:
self.persistentData = data
persistentdir = os.path.basename(self.persistentFile)
tmpfile = self.persistentFile + '.tmp'
if not os.path.isdir(persistentdir):
os.makedirs(persistentdir, exist_ok=True)
try:
with open(tmpfile, 'w') as f:
json.dump(self.persistentData, f, indent=2)
f.write('\n')
os.rename(tmpfile, self.persistentFile)
finally:
try:
os.remove(tmpfile)
except FileNotFoundError:
pass
@Command()
def factory_reset(self):
self.writeDict.update(self.initData)
self.writeInitParams()

View File

@ -260,7 +260,7 @@ class Server:
self.modules[modname] = modobj self.modules[modname] = modobj
except ConfigError as e: except ConfigError as e:
errors.append('error creating module %s:' % modname) errors.append('error creating module %s:' % modname)
for errtxt in e.args[0] if isinstance(e.args[0], list) else [e.args[0]]: for errtxt in e.args[0] if isinstance(e.args, list) else [e.args[0]]:
errors.append(' ' + errtxt) errors.append(' ' + errtxt)
except Exception: except Exception:
failure_traceback = traceback.format_exc() failure_traceback = traceback.format_exc()

View File

@ -1,208 +0,0 @@
Sensor Model: CX-1050-SD 2018-11-20
Serial Number: X132254
Data Format: 4 (Log Ohms/Kelvin)
SetPoint Limit: 330.0 (Kelvin)
Temperature coefficient: 1 (Negative)
Number of Breakpoints: 199
No. Units Temperature (K)
1 1.865581 330.0000
2 1.889780 310.0000
3 1.900613 301.5330
4 1.911510 293.2973
5 1.922466 285.2865
6 1.933476 277.4945
7 1.944536 269.9153
8 1.955638 262.5431
9 1.966779 255.3723
10 1.977950 248.3974
11 1.989148 241.6129
12 2.000369 235.0138
13 2.011610 228.5949
14 2.022870 222.3513
15 2.034145 216.2782
16 2.045433 210.3710
17 2.056730 204.6252
18 2.068035 199.0363
19 2.079343 193.6000
20 2.090652 188.3123
21 2.101960 183.1689
22 2.113263 178.1660
23 2.124559 173.2998
24 2.135845 168.5665
25 2.147117 163.9624
26 2.158374 159.4842
27 2.169612 155.1282
28 2.180830 150.8912
29 2.192023 146.7699
30 2.203192 142.7612
31 2.214333 138.8620
32 2.225444 135.0693
33 2.236525 131.3801
34 2.247573 127.7918
35 2.258586 124.3014
36 2.269564 120.9064
37 2.280509 117.6041
38 2.291423 114.3920
39 2.302308 111.2676
40 2.313167 108.2285
41 2.324003 105.2725
42 2.334817 102.3972
43 2.345609 99.60045
44 2.356380 96.88007
45 2.367129 94.23400
46 2.377857 91.66019
47 2.388562 89.15669
48 2.399245 86.72156
49 2.409907 84.35294
50 2.420547 82.04902
51 2.431166 79.80803
52 2.441762 77.62824
53 2.452338 75.50799
54 2.462894 73.44565
55 2.473431 71.43963
56 2.483949 69.48841
57 2.494449 67.59048
58 2.504932 65.74439
59 2.515397 63.94872
60 2.525846 62.20210
61 2.536278 60.50318
62 2.546695 58.85066
63 2.557096 57.24328
64 2.567482 55.67980
65 2.577854 54.15902
66 2.588212 52.67978
67 2.598555 51.24095
68 2.608882 49.84141
69 2.619195 48.48010
70 2.629492 47.15596
71 2.639773 45.86800
72 2.650037 44.61521
73 2.660285 43.39664
74 2.670517 42.21135
75 2.680731 41.05844
76 2.690928 39.93701
77 2.701107 38.84622
78 2.711268 37.78522
79 2.721411 36.75319
80 2.731536 35.74936
81 2.741644 34.77294
82 2.751735 33.82319
83 2.761808 32.89938
84 2.771865 32.00080
85 2.781907 31.12677
86 2.791933 30.27661
87 2.801945 29.44966
88 2.811944 28.64531
89 2.821930 27.86292
90 2.831903 27.10191
91 2.841865 26.36167
92 2.851817 25.64166
93 2.861758 24.94131
94 2.871690 24.26009
95 2.881614 23.59748
96 2.891532 22.95297
97 2.901446 22.32605
98 2.911361 21.71626
99 2.921277 21.12313
100 2.931198 20.54620
101 2.941127 19.98502
102 2.951066 19.43917
103 2.961018 18.90823
104 2.970985 18.39179
105 2.980970 17.88946
106 2.990974 17.40085
107 3.001002 16.92558
108 3.011054 16.46329
109 3.021133 16.01363
110 3.031243 15.57626
111 3.041384 15.15082
112 3.051561 14.73701
113 3.061776 14.33450
114 3.072032 13.94298
115 3.082331 13.56216
116 3.092677 13.19174
117 3.103073 12.83143
118 3.113522 12.48097
119 3.124027 12.14008
120 3.134592 11.80850
121 3.145221 11.48597
122 3.155918 11.17226
123 3.166687 10.86711
124 3.177532 10.57030
125 3.188458 10.28159
126 3.199469 10.00077
127 3.210570 9.727624
128 3.221764 9.461935
129 3.233053 9.203502
130 3.244440 8.952128
131 3.255927 8.707619
132 3.267518 8.469789
133 3.279216 8.238455
134 3.291024 8.013439
135 3.302945 7.794568
136 3.314984 7.581676
137 3.327143 7.374599
138 3.339428 7.173177
139 3.351843 6.977257
140 3.364390 6.786688
141 3.377076 6.601324
142 3.389904 6.421023
143 3.402879 6.245646
144 3.416005 6.075059
145 3.429288 5.909132
146 3.442733 5.747736
147 3.456347 5.590749
148 3.470134 5.438050
149 3.484101 5.289521
150 3.498250 5.145049
151 3.512586 5.004522
152 3.527113 4.867834
153 3.541835 4.734880
154 3.556757 4.605557
155 3.571882 4.479766
156 3.587215 4.357410
157 3.602759 4.238397
158 3.618520 4.122634
159 3.634504 4.010033
160 3.650715 3.900507
161 3.667161 3.793973
162 3.683849 3.690349
163 3.700786 3.589555
164 3.717980 3.491514
165 3.735437 3.396150
166 3.753166 3.303392
167 3.771176 3.213166
168 3.789476 3.125406
169 3.808071 3.040042
170 3.826971 2.957009
171 3.846182 2.876245
172 3.865712 2.797686
173 3.885567 2.721273
174 3.905755 2.646948
175 3.926284 2.574652
176 3.947160 2.504331
177 3.968390 2.435930
178 3.989980 2.369398
179 4.011939 2.304683
180 4.034269 2.241735
181 4.056978 2.180507
182 4.080072 2.120951
183 4.103563 2.063022
184 4.127465 2.006675
185 4.151790 1.951866
186 4.176551 1.898555
187 4.201764 1.846700
188 4.227442 1.796262
189 4.253600 1.747201
190 4.280254 1.699479
191 4.307419 1.653062
192 4.335111 1.607912
193 4.363347 1.563995
194 4.392142 1.521278
195 4.421513 1.479727
196 4.451476 1.439312
197 4.482048 1.400000
198 4.664046 1.200000
199 4.907708 1.000000

View File

@ -16,13 +16,11 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# Module authors: # Module authors:
# M. Zolliker <markus.zolliker@psi.ch> # ...
# #
# ***************************************************************************** # *****************************************************************************
"""transducer DPM3 read out"""
from secop.core import Drivable, Parameter, FloatRange, BoolType, StringIO,\ from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIodev, IntRange, Done
HasIodev, IntRange, Done, Attached, Command
class DPM3IO(StringIO): class DPM3IO(StringIO):
@ -45,7 +43,7 @@ def float2hex(value, digits):
return '%06X' % intvalue return '%06X' % intvalue
class DPM3(HasIodev, Drivable): class DPM3(HasIodev, Readable):
OFFSET = 0x8f OFFSET = 0x8f
SCALE = 0x8c SCALE = 0x8c
@ -54,11 +52,8 @@ class DPM3(HasIodev, Drivable):
iodevClass = DPM3IO iodevClass = DPM3IO
motor = Attached()
digits = Parameter('number of digits for value', IntRange(0, 5), initwrite=True, readonly=False) digits = Parameter('number of digits for value', IntRange(0, 5), initwrite=True, readonly=False)
value = Parameter(unit='N') value = Parameter(unit='N')
target = Parameter(unit='N')
step = Parameter('maximum motor step', FloatRange(unit='deg'), default=5, readonly=False)
offset = Parameter('', FloatRange(-1e5, 1e5), readonly=False, poll=True) offset = Parameter('', FloatRange(-1e5, 1e5), readonly=False, poll=True)
@ -67,8 +62,6 @@ class DPM3(HasIodev, Drivable):
thus a maximl output of 1500. 10=150/f thus a maximl output of 1500. 10=150/f
""" """
scale_factor = Parameter('', FloatRange(-1e5, 1e5, unit='input_units/N'), readonly=False, poll=True) scale_factor = Parameter('', FloatRange(-1e5, 1e5, unit='input_units/N'), readonly=False, poll=True)
_target = None
fast_pollfactor = 0.01
def query(self, adr, value=None): def query(self, adr, value=None):
if value is not None: if value is not None:
@ -115,36 +108,8 @@ class DPM3(HasIodev, Drivable):
return int(back_value,16) - 1 return int(back_value,16) - 1
def read_value(self): def read_value(self):
value = float(self._iodev.communicate('*1B1')) value = self._iodev.communicate('*1B1')
if self._target is not None: return float(value)
mot = self._motor
if self._direction * (self._target - value) > 0:
if not mot.isBusy():
step = self.step * self._direction
mot.write_target(mot.value + step)
else:
print(value)
self.stop()
self.status = self.Status.IDLE, 'target reached'
return value
def write_target(self, target):
self._target = target
if target - self.value > 0:
self._direction = 1
else:
self._direction = -1
print('direction', self._direction)
self.status = self.Status.BUSY, 'moving motor'
if self._motor.status[0] == self.Status.ERROR:
self._motor.reset()
return target
@Command()
def stop(self):
self._target = None
self._motor.stop()
self.status = self.Status.IDLE, 'stopped'
def read_offset(self): def read_offset(self):
reply = self.query(self.OFFSET) reply = self.query(self.OFFSET)

View File

@ -1,102 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
"""LakeShore 240 temperature monitor"""
import struct
from secop.core import FloatRange, HasIodev, Readable, Parameter, BytesIO
from secop.errors import CommunicationFailedError
SD1= 0x10
SD2= 0x68
FC = 0x49
ED = 0x16
STATUS_REQ = 0x49
def dehex(msg):
return bytes(int(b, 16) for b in msg.split())
def hexify(msg):
return ' '.join('%2.2X' % b for b in msg)
class Ls240(HasIodev, Readable):
value = Parameter('sensor reading', FloatRange(unit='Ohm'))
iodevClass = BytesIO
def request(self, replylen, data='', ext=None, dst_adr=3, src_adr=2):
data = dehex(data)
if ext is None:
ext = len(data) > 1
if ext:
dst_adr |= 0x80
src_adr |= 0x80
if len(data) > 1:
length = len(data) + 2
hdr = [SD2, length, length, SD2]
else:
hdr = [SD1]
mid = [dst_adr, src_adr] + list(data)
checksum = sum(mid) % 256
msg = bytes(hdr + mid + [checksum, ED])
for i in range(10):
try:
# print('>', hexify(msg))
reply = self._iodev.communicate(msg, replylen)
# print('<', hexify(reply))
except (TimeoutError, CommunicationFailedError):
continue
return reply
return None
def read_value(self):
# check connection
self.request(6, '49')
# get diag
# 3C: slave diag, (what means 3E?)
reply = self.request(17, '6D 3C 3E')
assert reply[13:15] == b'\x0f\x84' # LS240 ident
# set parameters
# 3D set param (what means 3E?)
# B0 FF FF: no watchdog, 00: min wait, 0F 84: ident, 01: group
assert b'\xe5' == self.request(1, '5D 3D 3E B0 FF FF 00 0F 84 01')
# set config
# 3E set config (what means 2nd 3E?)
# 93: input only, 4 == 3+1 bytes
assert b'\xe5' == self.request(1, '7D 3E 3E 93')
# get diag
# 3C: slave diag, (what means 3E?)
reply = self.request(17, '5D 3C 3E')
assert reply[13:15] == b'\x0f\x84' # LS240 ident
# get data
# do not know what 42 24 means
reply = self.request(13, '7D 42 24', ext=0)
print('DATA', reply)
value = struct.unpack('>f', reply[7:11])[0]
print('VALUE', value)
return value

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
"""very simple LakeShore Model 340 driver, resistivity only"""
import time
from secop.datatypes import StringType, FloatRange
from secop.modules import Parameter, Property, Readable
from secop.stringio import HasIodev, StringIO
class LscIO(StringIO):
identification = [('*IDN?', 'LSCI,MODEL340,.*')]
end_of_line = '\r'
wait_before = 0.05
class ResChannel(HasIodev, Readable):
"""temperature channel on Lakeshore 340"""
iodevClass = LscIO
value = Parameter(datatype=FloatRange(unit='Ohm'))
channel = Property('the channel A,B,C or D', StringType())
def read_value(self):
return self._iodev.communicate('SRDG?%s' % self.channel)

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ***************************************************************************** # *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under # This program is free software; you can redistribute it and/or modify it under
@ -78,7 +79,7 @@ for confdir in getGeneralConfig()['confdir'].split(os.pathsep):
if exists(seaconfdir): if exists(seaconfdir):
break break
else: else:
seaconfdir = os.environ.get('FRAPPY_SEA_DIR') seaconfdir = None
def get_sea_port(instance): def get_sea_port(instance):
@ -262,37 +263,18 @@ class SeaClient(ProxyClient, Module):
if value is None: if value is None:
value = oldv value = oldv
if value != oldv or str(readerror) != str(oldr) or abs(now - oldt) > 60: if value != oldv or str(readerror) != str(oldr) or abs(now - oldt) > 60:
# do not update unchanged values within 60 sec # do not update unchanged values within 0.1 sec
self.updateValue(module, param, value, now, readerror) self.updateValue(module, param, value, now, readerror)
@Command(StringType(), result=StringType()) @Command(StringType(), result=StringType())
def communicate(self, command): def communicate(self, command):
"""send a command to SEA""" """send a command to SEA"""
reply = self.request(command) reply = self.request(command)
return reply return reply
@Command(StringType(), result=StringType()) @Command(result=StringType())
def query(self, cmd): def describe(self):
"""a request checking for errors and accepting 0 or 1 line as result""" """save objects (and sub-objects) description"""
errors = []
reply = None
for line in self.request(cmd).split('\n'):
if line.strip().startswith('ERROR:'):
errors.append(line[6:].strip())
elif reply is None:
reply = line.strip()
else:
self.log.info('SEA: superfluous reply %r to %r', reply, cmd)
if errors:
raise HardwareError('; '.join(errors))
return reply
class SeaConfigCreator(SeaClient):
def startModule(self, started_callback):
"""save objects (and sub-objects) description and exit"""
self._connect(lambda: None)
reply = self.request('describe_all') reply = self.request('describe_all')
reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n')) reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n'))
description, reply = json.loads(reply) description, reply = json.loads(reply)
@ -319,11 +301,11 @@ class SeaConfigCreator(SeaClient):
nodedescr=description.get(filename, filename))) nodedescr=description.get(filename, filename)))
for obj in descr: for obj in descr:
fp.write(CFG_MODULE % dict(modcls=modcls[obj], module=obj, seaconn=seaconn)) fp.write(CFG_MODULE % dict(modcls=modcls[obj], module=obj, seaconn=seaconn))
content = json.dumps(descr).replace('}, {', '},\n{').replace('[{', '[\n{').replace('}]}, ', '}]},\n\n') content = json.dumps(descr).replace('}, {', '},\n{')
with open(join(seaconfdir, filename + '.json'), 'w') as fp: with open(join(seaconfdir, filename + '.json'), 'w') as fp:
fp.write(content + '\n') fp.write(content + '\n')
result.append('%s: %s' % (filename, ','.join(n for n in descr))) result.append('%s: %s' % (filename, ','.join(n for n in descr)))
raise SystemExit('; '.join(result)) return '; '.join(result)
@Command(StringType(), result=StringType()) @Command(StringType(), result=StringType())
def query(self, cmd): def query(self, cmd):
@ -387,9 +369,11 @@ class SeaModule(Module):
paramdesc['key'] = 'value' paramdesc['key'] = 'value'
if issubclass(cls, SeaWritable): if issubclass(cls, SeaWritable):
if paramdesc.get('readonly', True): if paramdesc.get('readonly', True):
raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path'])) raise ConfigError('%s is not writable' % sea_object)
paramdesc['key'] = 'target' targetdesc = dict(paramdesc, key='target')
paramdesc['readonly'] = False params.append(targetdesc)
paramdesc['readonly'] = True
# print('SINGLE %s/%s %s %r' % (base, paramdesc['path'], cls.__name__, params))
extra_module_set = () extra_module_set = ()
if 'description' not in cfgdict: if 'description' not in cfgdict:
cfgdict['description'] = '%s@%s' % (single_module, json_file) cfgdict['description'] = '%s@%s' % (single_module, json_file)
@ -407,11 +391,8 @@ class SeaModule(Module):
if rel_paths == '*' or not rel_paths: if rel_paths == '*' or not rel_paths:
# take all # take all
main = descr['params'][0] main = descr['params'][0]
if issubclass(cls, Readable): # assert main['path'] == '' # TODO: check cases where this fails
# assert main['path'] == '' # TODO: check cases where this fails main['key'] = 'value'
main['key'] = 'value'
else:
descr['params'].pop(0)
else: else:
# filter by relative paths # filter by relative paths
rel_paths = rel_paths.split() rel_paths = rel_paths.split()
@ -420,11 +401,7 @@ class SeaModule(Module):
include = True include = True
for paramdesc in descr['params']: for paramdesc in descr['params']:
path = paramdesc['path'] path = paramdesc['path']
if path.endswith('is_running'): if paramdesc.get('visibility', 1) > visibility_level and not path.endswith('is_running'):
# take this always
result.append(paramdesc)
continue
if paramdesc.get('visibility', 1) > visibility_level:
continue continue
sub = path.split('/', 1) sub = path.split('/', 1)
if rpath == '.': # take all except subpaths with readonly node at top if rpath == '.': # take all except subpaths with readonly node at top
@ -435,22 +412,14 @@ class SeaModule(Module):
elif sub[0] == rpath: elif sub[0] == rpath:
result.append(paramdesc) result.append(paramdesc)
descr['params'] = result descr['params'] = result
for valuedesc in result: rel0 = '' if rel_paths[0] == '.' else rel_paths[0]
if valuedesc['path'] == '': if result[0]['path'] == rel0:
valuedesc['key'] = 'value' result[0]['key'] = 'value'
break
else: else:
logger.error('%s: no value found', name) logger.error('%s: no value found', name)
# logger.info('PARAMS %s %r', name, result) # logger.info('PARAMS %s %r', name, result)
base = descr['base'] base = descr['base']
params = descr['params'] params = descr['params']
if issubclass(cls, SeaWritable):
paramdesc = params[0]
assert paramdesc['key'] == 'value'
if paramdesc.get('readonly', True):
raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path']))
paramdesc['key'] = 'target'
paramdesc['readonly'] = False
extra_module_set = cfgdict.pop('extra_modules', ()) extra_module_set = cfgdict.pop('extra_modules', ())
if extra_module_set: if extra_module_set:
extra_module_set = set(extra_module_set.replace(',', ' ').split()) extra_module_set = set(extra_module_set.replace(',', ' ').split())
@ -473,23 +442,21 @@ class SeaModule(Module):
if kwds['datatype'] is None: if kwds['datatype'] is None:
kwds.update(visibility=3, default='', datatype=StringType()) kwds.update(visibility=3, default='', datatype=StringType())
pathlist = path.split('/') if path else [] pathlist = path.split('/') if path else []
key = paramdesc.get('key') # None, 'value' or 'target' key = paramdesc.get('key') # will be None, 'value' or 'target'
if key is None: if key is None:
if len(pathlist) > 0: if len(pathlist) > 0:
if len(pathlist) == 1: if len(pathlist) == 1:
if issubclass(cls, Readable): kwds['group'] = 'more'
kwds['group'] = 'more'
else: else:
kwds['group'] = pathlist[-2] kwds['group'] = pathlist[-2]
# flatten path to parameter name # flatten path to parameter name
key = None
for i in reversed(range(len(pathlist))): for i in reversed(range(len(pathlist))):
key = '_'.join(pathlist[i:]) key = '_'.join(pathlist[i:])
if not key in cls.accessibles: if not key in cls.accessibles:
break break
if key == 'is_running': if key == 'is_running':
kwds['export'] = False kwds['export'] = False
if key == 'target' and kwds.get('group') == 'more':
kwds.pop('group')
if key in cls.accessibles: if key in cls.accessibles:
if key == 'target': if key == 'target':
kwds['readonly'] = False kwds['readonly'] = False
@ -498,21 +465,20 @@ class SeaModule(Module):
else: else:
pobj = Parameter(**kwds) pobj = Parameter(**kwds)
datatype = pobj.datatype datatype = pobj.datatype
if issubclass(cls, SeaWritable) and key == 'target':
kwds['readonly'] = False
attributes['value'] = Parameter(**kwds)
hdbpath = '/'.join([base] + pathlist) hdbpath = '/'.join([base] + pathlist)
if key in extra_module_set: if key in extra_module_set:
extra_modules[name + '.' + key] = sea_object, base, paramdesc extra_modules[name + '.' + key] = sea_object, base, paramdesc
continue # skip this parameter continue # skip this parameter
path2param[hdbpath] = (name, key) path2param[hdbpath] = (name, key)
# logger.info('PARAM %s %s %s', hdbpath, name, key)
attributes[key] = pobj attributes[key] = pobj
# if hasattr(cls, 'read_' + key): # if hasattr(cls, 'read_' + key):
# print('override %s.read_%s' % (cls.__name__, key)) # print('override %s.read_%s' % (cls.__name__, key))
def rfunc(self, cmd='hval %s/%s' % (base, path)): def rfunc(self, cmd='hval %s/%s' % (base, path)):
print('READ', cmd)
reply = self._iodev.query(cmd) reply = self._iodev.query(cmd)
print('REPLY', reply)
try: try:
reply = float(reply) reply = float(reply)
except ValueError: except ValueError:
@ -523,8 +489,8 @@ class SeaModule(Module):
attributes['read_' + key] = rfunc attributes['read_' + key] = rfunc
if not readonly: if not readonly:
# if hasattr(cls, 'write_' + key): if hasattr(cls, 'write_' + key):
# print('override %s.write_%s' % (cls.__name__, key)) print('override %s.write_%s' % (cls.__name__, key))
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']): def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
value = datatype.export_value(value) value = datatype.export_value(value)
@ -533,6 +499,7 @@ class SeaModule(Module):
# TODO: check if more has to be done for valid tcl data (strings?) # TODO: check if more has to be done for valid tcl data (strings?)
cmd = "%s %s" % (command, value) cmd = "%s %s" % (command, value)
self._iodev.query(cmd) self._iodev.query(cmd)
print('WRITE %s' % cmd)
return Done return Done
attributes['write_' + key] = wfunc attributes['write_' + key] = wfunc
@ -596,7 +563,6 @@ class SeaWritable(SeaModule, Writable):
return self.target return self.target
def update_target(self, value, timestamp, readerror): def update_target(self, value, timestamp, readerror):
self.target = value
if not readerror: if not readerror:
self.value = value self.value = value

View File

@ -198,7 +198,7 @@ class Sensor(Readable):
def update_value(self, value): def update_value(self, value):
if self.abs: if self.abs:
value = abs(float(value)) value = abs(value)
self.value = self._calib(value) self.value = self._calib(value)
self._value_error = None self._value_error = None

View File

@ -23,121 +23,221 @@
"""drivers for trinamic PD-1161 motors""" """drivers for trinamic PD-1161 motors"""
import time import time
import os
import struct import struct
from math import log10 import json
from secop.core import BoolType, Command, EnumType, FloatRange, IntRange, \ from secop.core import BoolType, Command, EnumType, FloatRange, IntRange, \
HasIodev, Parameter, Property, Drivable, TupleOf, Done, PersistentMixin, PersistentParam HasIodev, Parameter, Property, Drivable, TupleOf, Done
from secop.bytesio import BytesIO from secop.bytesio import BytesIO
from secop.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError from secop.lib import getGeneralConfig
from secop.errors import CommunicationFailedError, HardwareError
MOTOR_STOP = 3 MOTOR_STOP = 3
MOVE = 4 MOVE = 4
SET_AXIS_PAR = 5 SET_AXIS_PAR = 5
GET_AXIS_PAR = 6 GET_AXIS_PAR = 6
SAVE_AXIS_PAR = 7
LOAD_AXIS_PAR = 8
SET_GLOB_PAR = 9 SET_GLOB_PAR = 9
GET_GLOB_PAR = 10 GET_GLOB_PAR = 10
# STORE_GLOB_PAR = 11 SAVE_GLOB_PAR = 11
LOAD_GLOB_PAR = 12
SET_IO = 14
GET_IO = 15
MOT_FACT = object()
CONFIG = getGeneralConfig()
class AxisPar(Parameter):
SET = SET_AXIS_PAR
GET = GET_AXIS_PAR
def __init__(self, adr, min=None, max=None, unit=None, scale=1, readonly=False, poll=True):
if unit:
datatype = FloatRange(min, max, unit=unit)
else:
datatype = IntRange(min, max)
super().__init__('_', datatype, readonly=readonly, poll=poll)
self.adr = adr
self.scale = scale
def to_raw(self, motor, value):
return round(value / self.scale)
def from_raw(self, motor, value):
return value * self.scale
def read(self, motor):
result = round(self.from_raw(motor, motor.comm(self.GET, self.adr)), 3)
# print('read', self.adr, result)
return result
def write(self, motor, value):
rvalue = self.to_raw(motor, value)
motor.comm(self.SET, self.adr, rvalue)
reply = motor.comm(self.GET, self.adr)
if rvalue != reply:
raise HardwareError('%s: reply does not match: %r != %r' % (self.name, rvalue, reply))
return round(self.from_raw(motor,reply), 3)
def __set_name__(self, owner, name):
super().__set_name__(owner, name)
setattr(owner, 'read_' + name, lambda self, pobj=self: pobj.read(self))
if not self.readonly:
setattr(owner, 'write_' + name, lambda self, value, pobj=self: pobj.write(self, value))
class IoPar(AxisPar):
SET = SET_IO
GET = GET_IO
def __init__(self, adr, bank, datatype=EnumType(CLOSED=0, OPEN=1), scale=1, readonly=False, poll=True):
Parameter.__init__(self, '_', datatype, readonly=readonly, poll=poll)
self.adr = adr + 1000 * bank
self.scale = scale
def read(self, motor):
return motor.comm(self.GET, self.adr)
def write(self, motor, value):
motor.comm(self.SET, self.adr, int(value))
return value
class AxisDiffPar(AxisPar):
@staticmethod
def to_raw(motor, value):
return round(value / motor.fact)
@staticmethod
def from_raw(motor, value):
return round(value * motor.fact, 3)
class AxisZeroPar(AxisPar):
@staticmethod
def to_raw(motor, value):
return round(value / motor.fact - motor.zero)
@staticmethod
def from_raw(motor, value):
return round(value * motor.fact + motor.zero, 3)
BAUDRATES = [9600, 0, 19200, 0, 38400, 57600, 0, 115200] BAUDRATES = [9600, 0, 19200, 0, 38400, 57600, 0, 115200]
FULL_STEP = 1.8
ANGLE_SCALE = FULL_STEP/256
# assume factory settings for pulse and ramp divisors:
SPEED_SCALE = 1E6 / 2 ** 15 * ANGLE_SCALE
MAX_SPEED = 2047 * SPEED_SCALE
ACCEL_SCALE = 1E12 / 2 ** 31 * ANGLE_SCALE
MAX_ACCEL = 2047 * ACCEL_SCALE
CURRENT_SCALE = 2.8/250
ENCODER_RESOLUTION = 0.4 # 365 / 1024, rounded up
class Motor(HasIodev, Drivable):
class HwParam(PersistentParam):
adr = Property('parameter address', IntRange(0, 255), export=False)
scale = Property('scale factor (physical value / unit)', FloatRange(), export=False)
def __init__(self, description, datatype, adr, scale=1, poll=True,
readonly=True, persistent=None, **kwds):
"""hardware parameter"""
if persistent is None:
persistent = not readonly
if isinstance(datatype, FloatRange) and datatype.fmtstr == '%g':
datatype.fmtstr = '%%.%df' % max(0, 1 - int(log10(scale) + 0.01))
super().__init__(description, datatype, poll=poll, adr=adr, scale=scale,
persistent=persistent, readonly=readonly, **kwds)
def copy(self):
res = HwParam(self.description, self.datatype.copy(), self.adr)
res.name = self.name
res.init(self.propertyValues)
return res
class Motor(PersistentMixin, HasIodev, Drivable):
address = Property('module address', IntRange(0, 255), default=1) address = Property('module address', IntRange(0, 255), default=1)
fact = Property('gear factor', FloatRange(unit='deg/step'), default=1.8/256)
# limit_pin_mask = Property('input pin mask for lower/upper limit switch', limit_pin_mask = Property('input pin mask for lower/upper limit switch',
# TupleOf(IntRange(0, 15), IntRange(0, 15)), TupleOf(IntRange(0, 15), IntRange(0, 15)),
# default=(8, 0)) default=(8, 0))
value = Parameter('motor position', FloatRange(unit='deg'))
value = Parameter('motor position', FloatRange(unit='deg', fmtstr='%.3f'))
zero = PersistentParam('zero point', FloatRange(unit='$'), readonly=False, default=0)
encoder = HwParam('encoder reading', FloatRange(unit='$', fmtstr='%.1f'),
209, ANGLE_SCALE, readonly=True, initwrite=False, persistent=True)
steppos = HwParam('position from motor steps', FloatRange(unit='$'),
1, ANGLE_SCALE, readonly=True, initwrite=False)
target = Parameter('_', FloatRange(unit='$'), default=0) target = Parameter('_', FloatRange(unit='$'), default=0)
movelimit = Parameter('max. angle to drive in one go', FloatRange(unit='$'), default=400, readonly=False)
zero = Parameter('zero point', FloatRange(unit='$'), readonly=False, default=0)
tolerance = Parameter('positioning tolerance', FloatRange(unit='$'), readonly=False, default=0.9)
encoder = Parameter('encoder value', FloatRange(unit='$'), needscfg=False)
steppos = Parameter('motor steps position', FloatRange(unit='$'), needscfg=False)
# at_upper_limit = Parameter('upper limit switch touched', BoolType(), needscfg=False)
# at_lower_limit = Parameter('lower limit switch touched', BoolType(), needscfg=False)
pull_up = Parameter('activate pull up resistors', BoolType(), needscfg=False)
baudrate = Parameter('baud rate code', EnumType({'%d' % v: i for i, v in enumerate(BAUDRATES)}),
readonly=False, default=0, poll=True)
movelimit = Parameter('max. angle to drive in one go', FloatRange(unit='$'), temperature = IoPar(9, 1, IntRange(), readonly=True)
readonly=False, default=360, group='more') operating_voltage = IoPar(8, 1, IntRange(), readonly=True)
tolerance = Parameter('positioning tolerance', FloatRange(unit='$'),
readonly=False, default=0.9)
encoder_tolerance = HwParam('the allowed deviation between steppos and encoder\n\nmust be > tolerance',
FloatRange(0, 360., unit='$'),
212, ANGLE_SCALE, readonly=False, group='more')
speed = HwParam('max. speed', FloatRange(0, MAX_SPEED, unit='$/sec'),
4, SPEED_SCALE, readonly=False, group='motorparam')
minspeed = HwParam('min. speed', FloatRange(0, MAX_SPEED, unit='$/sec'),
130, SPEED_SCALE, readonly=False, default=SPEED_SCALE, group='motorparam')
currentspeed = HwParam('current speed', FloatRange(-MAX_SPEED, MAX_SPEED, unit='$/sec'),
3, SPEED_SCALE, readonly=True, group='motorparam')
maxcurrent = HwParam('_', FloatRange(0, 2.8, unit='A'),
6, CURRENT_SCALE, readonly=False, group='motorparam')
standby_current = HwParam('_', FloatRange(0, 2.8, unit='A'),
7, CURRENT_SCALE, readonly=False, group='motorparam')
acceleration = HwParam('_', FloatRange(4.6 * ACCEL_SCALE, MAX_ACCEL, unit='deg/s^2'),
5, ACCEL_SCALE, readonly=False, group='motorparam')
target_reached = HwParam('_', BoolType(), 8, group='hwstatus')
move_status = HwParam('_', IntRange(0, 3),
207, readonly=True, group='hwstatus')
error_bits = HwParam('_', IntRange(0, 255),
208, readonly=True, group='hwstatus')
# the doc says msec, but I believe the scale is 10 msec
free_wheeling = HwParam('_', FloatRange(0, 60., unit='sec'),
204, 0.01, default=0.1, readonly=False, group='motorparam')
power_down_delay = HwParam('_', FloatRange(0, 60., unit='sec'),
214, 0.01, default=0.1, readonly=False, group='motorparam')
baudrate = Parameter('_', EnumType({'%d' % v: i for i, v in enumerate(BAUDRATES)}),
readonly=False, default=0, poll=True, visibility=3, group='more')
pollinterval = Parameter(group='more')
enc_tolerance = AxisDiffPar(212, 0, 360., '$')
maxspeed = AxisPar(4, 0, 2047)
minspeed = AxisPar(130, 0, 2047)
maxcurrent = AxisPar(6, 0, 2.8, 'A', 2.8/250)
standby_current = AxisPar(7, 0, 2.8, 'A', 2.8/250)
acceleration = AxisPar(5, 0, 2047)
internal_target = AxisZeroPar(0, unit='$')
target_reached = AxisPar(8, readonly=True)
move_status = AxisPar(207, readonly=True)
error_bits = AxisPar(208, readonly=True)
free_wheeling = AxisPar(204, 0, 60, 'sec', scale=0.001)
fast_pollfactor = 0.001
_targettime = None
_prevconn = None
iodevClass = BytesIO iodevClass = BytesIO
fast_pollfactor = 0.001 # poll as fast as possible when busy
_started = 0
_calcTimeout = True _calcTimeout = True
_need_reset = None _poserror = None
save_filename = None
def comm(self, cmd, adr, value=0, bank=0): def earlyInit(self):
"""set or get a parameter self.writeDict.update(self.loadParams())
:param adr: parameter number def loadParams(self):
:param cmd: SET command (in the GET case, 1 is added to this) # the following should be moved to core functionality
:param bank: the bank savedir = os.path.join(CONFIG['logdir'], 'persistent')
:param value: if given, the parameter is written, else it is returned os.makedirs(savedir, exist_ok=True)
:return: the returned value self._save_filename = os.path.join(savedir, '%s.%s.json' % (self.DISPATCHER.equipment_id, self.name))
""" try:
with open(self._save_filename, 'r') as f:
save_dict = json.load(f)
except FileNotFoundError:
save_dict = {}
writeDict = {}
for pname, value in save_dict.items():
pobj = self.parameters[pname]
val = pobj.datatype.import_value(value)
if pobj.readonly:
try:
pobj.value = val
except Exception as e:
self.log.warning('can not restore %r to %r' % (pname, val))
else:
writeDict[pname] = val
return writeDict
def saveParams(self):
save_dict = {}
for pname, pobj in self.parameters.items():
if pname not in ('target', 'internal_target'):
save_dict[pname] = pobj.export_value()
tmpfile = self._save_filename + '.tmp'
try:
with open(tmpfile, 'w') as f:
json.dump(save_dict, f, indent=2)
f.write('\n')
os.rename(tmpfile, self._save_filename)
finally:
try:
os.remove(tmpfile)
except FileNotFoundError:
pass
def startModule(self, started_callback):
self._initialize = True
def cb(self=self, started_callback=started_callback, encoder=self.encoder, zero=self.zero):
encoder += self.zero - zero
diff = encoder - self.encoder + 180
if abs(diff % 360 - 180) < self.enc_tolerance:
diff = round((diff - 180) / 360) * 360
if diff:
self.comm(SET_AXIS_PAR, 209, AxisZeroPar.to_raw(self, encoder + diff))
self._poserror = 'reset'
else:
self.log.error('encoder: saved value (%g) does not match reading (%g)' % (encoder, self.encoder))
self._poserror = 'need_reset'
self.comm(SET_GLOB_PAR, 2255, 1)
self._initialize = False
self._prevconn = self._iodev._conn
started_callback()
super().startModule(cb)
def comm(self, cmd, adr=0, value=0):
if self._calcTimeout: if self._calcTimeout:
self._calcTimeout = False self._calcTimeout = False
baudrate = getattr(self._iodev._conn.connection, 'baudrate', None) baudrate = getattr(self._iodev._conn.connection, 'baudrate', None)
@ -146,21 +246,19 @@ class Motor(PersistentMixin, HasIodev, Drivable):
raise CommunicationFailedError('unsupported baud rate: %d' % baudrate) raise CommunicationFailedError('unsupported baud rate: %d' % baudrate)
self._iodev.timeout = 0.03 + 200 / baudrate self._iodev.timeout = 0.03 + 200 / baudrate
exc = None bank = adr // 1000
for itry in range(3,0,-1): iadr = adr % 1000
byt = struct.pack('>BBBBi', self.address, cmd, adr, bank, round(value)) for itry in range(3):
byt = struct.pack('>BBBBi', self.address, cmd, iadr, bank, round(value))
try: try:
reply = self._iodev.communicate(byt + bytes([sum(byt) & 0xff]), 9) reply = self._iodev.communicate(byt + bytes([sum(byt) & 0xff]), 9)
if sum(reply[:-1]) & 0xff != reply[-1]:
raise CommunicationFailedError('checksum error')
except Exception as e: except Exception as e:
if itry == 1: print(repr(e), adr, bank)
raise
exc = e
continue continue
break if sum(reply[:-1]) & 0xff == reply[-1]:
if exc: break
self.log.warning('tried %d times after %s', itry, exc) else:
raise CommunicationFailedError('checksum error')
radr, modadr, status, rcmd, result = struct.unpack('>BBBBix', reply) radr, modadr, status, rcmd, result = struct.unpack('>BBBBix', reply)
if status != 100: if status != 100:
self.log.warning('bad status from cmd %r %s: %d', cmd, adr, status) self.log.warning('bad status from cmd %r %s: %d', cmd, adr, status)
@ -168,252 +266,129 @@ class Motor(PersistentMixin, HasIodev, Drivable):
raise CommunicationFailedError('bad reply %r to command %s %d' % (reply, cmd, adr)) raise CommunicationFailedError('bad reply %r to command %s %d' % (reply, cmd, adr))
return result return result
def get(self, pname, **kwds):
"""get parameter"""
pobj = self.parameters[pname]
value = self.comm(GET_AXIS_PAR, pobj.adr, **kwds)
# do not apply scale when 1 (datatype might not be float)
return value if pobj.scale == 1 else value * pobj.scale
def set(self, pname, value, check=True, **kwds):
"""set parameter and check result"""
pobj = self.parameters[pname]
scale = pobj.scale
rawvalue = round(value / scale)
self.comm(SET_AXIS_PAR, pobj.adr, rawvalue, **kwds)
if check:
result = self.comm(GET_AXIS_PAR, pobj.adr, **kwds)
if result != rawvalue:
raise HardwareError('result does not match %d != %d' % (result, rawvalue))
value = result * scale
return value
def startModule(self, started_callback):
# get encoder value from motor. at this stage self.encoder contains the persistent value
encoder = self.get('encoder')
encoder += self.zero
self.fix_encoder(encoder)
super().startModule(started_callback)
def fix_encoder(self, encoder_from_hw):
"""fix encoder value
:param encoder_from_hw: the encoder value read from the HW
self.encoder is assumed to contain the last known (persistent) value
if encoder has not changed modulo 360, adjust by an integer multiple of 360
set status to error when encoder has changed be more than encoder_tolerance
"""
# calculate nearest, most probable value
adjusted_encoder = encoder_from_hw + round((self.encoder - encoder_from_hw) / 360.) * 360
if abs(self.encoder - adjusted_encoder) >= self.encoder_tolerance:
# encoder module0 360 has changed
self.log.error('saved encoder value (%.2f) does not match reading (%.2f %.2f)', self.encoder, encoder_from_hw, adjusted_encoder)
if adjusted_encoder != encoder_from_hw:
self.log.info('take next closest encoder value (%.2f)' % adjusted_encoder)
self._need_reset = True
self.status = self.Status.ERROR, 'saved encoder value does not match reading'
self.set('encoder', adjusted_encoder - self.zero, check=False)
def read_value(self): def read_value(self):
encoder = self.read_encoder() rawenc = self.comm(GET_AXIS_PAR, 209)
steppos = self.read_steppos() rawpos = self.comm(GET_AXIS_PAR, 1)
initialized = self.comm(GET_GLOB_PAR, 255, bank=2) self.encoder = AxisZeroPar.from_raw(self, rawenc)
if initialized: # no power loss self.steppos = AxisZeroPar.from_raw(self, rawpos)
self.saveParameters()
else: # just powered up
# get persistent values
writeDict = self.loadParameters()
self.log.info('set to previous saved values %r', writeDict)
# self.encoder now contains the last known (persistent) value
if self._need_reset is None:
if self.status[0] == self.Status.IDLE:
# server started, power cycled and encoder value matches last one
self.reset()
else:
self.fix_encoder(encoder)
self._need_reset = True
self.status = self.Status.ERROR, 'power loss'
# or should we just fix instead of error status?
# self.set('steppos', self.steppos - self.zero, check=False)
self.comm(SET_GLOB_PAR, 255, 1, bank=2) # set initialized flag
self._started = 0
return encoder if abs(encoder - steppos) > self.tolerance else steppos initialized = self._initialize or self.comm(GET_GLOB_PAR, 2255) # bank 2 adr 255
if initialized and self._prevconn == self._iodev._conn: # no power loss or connection interrupt
self.saveParams()
else:
self.log.info('set to previous saved values')
for pname, value in self.loadParams().items():
try:
getattr(self, 'write_' + pname)(value)
except Exception as e:
self.log.warning('can not write %r to %r' % (value, pname))
self.comm(SET_GLOB_PAR, 2255, 1)
self._prevconn = self._iodev._conn
if abs(rawenc - rawpos) > 128:
return self.encoder
return self.steppos
def read_status(self): def read_status(self):
oldpos = self.steppos if not self._targettime:
self.read_value() # make sure encoder and steppos are fresh if self._poserror:
if not self._started: return self.Status.ERROR, 'encoder does not match internal pos'
if abs(self.encoder - self.steppos) > self.encoder_tolerance:
self._need_reset = True
if self.status[0] != self.Status.ERROR:
self.log.error('encoder (%.2f) does not match internal pos (%.2f)', self.encoder, self.steppos)
return self.Status.ERROR, 'encoder does not match internal pos'
return self.status return self.status
now = time.time() # self.get_limits()
if oldpos != self.steppos or not (self.read_target_reached() or self.read_move_status() # if self.at_lower_limit or self.at_upper_limit:
or self.read_error_bits()): # self.stop()
return self.Status.BUSY, 'moving' # return self.Status.ERROR, 'at limit'
diff = self.target - self.encoder target_reached = self.read_target_reached()
if abs(diff) <= self.tolerance: if target_reached or self.read_move_status():
self._started = 0 self._targettime = None
return self.Status.IDLE, '' if abs(self.target - self.value) < self.tolerance:
#if (abs(self.target - self.steppos) < self.tolerance and return self.Status.IDLE, ''
# abs(self.encoder - self.steppos) < self.encoder_tolerance): self.log.warning('out of tolerance (%d)', self.move_status)
# self._try_count += 1 return self.Status.WARN, 'out of tolerance (%d)' % self.move_status
# if self._try_count < 3: if time.time() > self._targettime:
# # occasionaly, two attempts are needed, as steppos and encoder might have been self.log.warning('move timeout')
# # off by 1-2 full steps before moving return self.Status.WARN, 'move timeout'
# self.fix_steppos(self.tolerance, self.target) return self.Status.BUSY, 'moving'
# self.log.warning('try move again')
# return self.Status.BUSY, 'try again'
self.log.error('out of tolerance by %.3g', diff)
self._started = 0
return self.Status.ERROR, 'out of tolerance'
def write_target(self, target): def write_target(self, target):
self.read_value() # make sure encoder and steppos are fresh rawenc = self.comm(GET_AXIS_PAR, 209)
rawpos = self.comm(GET_AXIS_PAR, 1)
if abs(rawenc - rawpos) > 128:
# adjust missing steps
missing_steps = (rawenc - rawpos) / 256.0
#if self._poserror == 'reset':
print(missing_steps, self._poserror)
if abs(missing_steps) < 10 or self._poserror == 'reset':
self.log.warning('correct missing steps (%.1f deg)', AxisDiffPar.from_raw(self, missing_steps))
self._poserror = None
self.status = self.Status.IDLE, ''
else:
self._poserror = 'need_reset'
raise ValueError('encoder does not match internal pos')
rawpos += round(missing_steps) * 256
self.comm(SET_AXIS_PAR, 1, rawpos)
if abs(target - self.encoder) > self.movelimit: if abs(target - self.encoder) > self.movelimit:
raise BadValueError('can not move more than %s deg' % self.movelimit) raise ValueError('can not move more than %s deg' % self.movelimit)
diff = self.encoder - self.steppos rawtarget = round((target - self.zero) / self.fact)
if self._need_reset: delay = (abs(rawtarget - rawpos) / 30.5 / self.maxspeed
raise HardwareError('need reset (%s)' % self.status[1]) + self.maxspeed / self.acceleration / 15.25 + 0.5)
if abs(diff) > self.tolerance: self._targettime = time.time() + delay
if abs(diff) > self.encoder_tolerance:
self._need_reset = True
self.status = self.Status.ERROR, 'encoder does not match internal pos'
raise HardwareError('need reset (encoder does not match internal pos)')
self.set('steppos', self.encoder - self.zero)
# self.fix_steppos(self.encoder_tolerance)
self._started = time.time()
# self._try_count = 0
self.log.info('move to %.1f', target) self.log.info('move to %.1f', target)
self.comm(MOVE, 0, (target - self.zero) / ANGLE_SCALE) self.comm(MOVE, 0, rawtarget)
print('write_target')
self.target_reached = 0
self.status = self.Status.BUSY, 'changed target' self.status = self.Status.BUSY, 'changed target'
return target return target
def write_zero(self, value): # def get_limits(self):
diff = value - self.zero # self.input_bits = self.comm(GET_IO, 255)
self.encoder += diff # bits = (~ self.input_bits) & 0xf
self.steppos += diff # self.at_lower_limit = bool(bits & self.limit_pin_mask[0])
self.value += diff # self.at_upper_limit = bool(bits & self.limit_pin_mask[1])
#
# def read_at_upper_limit(self):
# self.get_limits()
# return Done
#
# def read_at_lower_limit(self):
# self.get_limits()
# return Done
def write_pull_up(self, value):
self.comm(SET_IO, 0, int(value))
return value return value
def read_encoder(self):
return self.get('encoder') + self.zero
def read_steppos(self):
return self.get('steppos') + self.zero
def read_encoder_tolerance(self):
return self.get('encoder_tolerance')
def write_encoder_tolerance(self, value):
return self.set('encoder_tolerance', value)
def read_target_reached(self):
return self.get('target_reached')
def read_speed(self):
return self.get('speed')
def write_speed(self, value):
return self.set('speed', value)
def read_minspeed(self):
return self.get('minspeed')
def write_minspeed(self, value):
return self.set('minspeed', value)
def read_currentspeed(self):
return self.get('currentspeed')
def read_acceleration(self):
return self.get('acceleration')
def write_acceleration(self, value):
return self.set('acceleration', value)
def read_maxcurrent(self):
return self.get('maxcurrent')
def write_maxcurrent(self, value):
return self.set('maxcurrent', value)
def read_standby_current(self):
return self.get('standby_current')
def write_standby_current(self, value):
return self.set('standby_current', value)
def read_free_wheeling(self):
return self.get('free_wheeling')
def write_free_wheeling(self, value):
return self.set('free_wheeling', value)
def read_power_down_delay(self):
return self.get('power_down_delay')
def write_power_down_delay(self, value):
return self.set('power_down_delay', value)
def read_move_status(self):
return self.get('move_status')
def read_error_bits(self):
return self.get('error_bits')
@Command(FloatRange())
def set_zero(self, value):
self.write_zero(value - self.read_value())
def read_baudrate(self):
return self.comm(GET_GLOB_PAR, 65)
def write_baudrate(self, value): def write_baudrate(self, value):
self.comm(SET_GLOB_PAR, 65, int(value)) self.comm(SET_GLOB_PAR, 65, int(value))
def read_baudrate(self):
reply = self.comm(GET_GLOB_PAR, 65)
return reply
@Command(FloatRange())
def set_zero(self, value):
self.zero += value - self.read_value()
@Command() @Command()
def reset(self): def reset(self):
"""set steppos to encoder value, if not within tolerance""" if self._poserror:
if self._started: self._poserror = 'reset'
raise IsBusyError('can not reset while moving') self.write_target(self.encoder)
tol = ENCODER_RESOLUTION
for itry in range(10):
diff = self.read_encoder() - self.read_steppos()
if abs(diff) <= tol:
self._need_reset = False
self.status = self.Status.IDLE, 'ok'
return
self.set('steppos', self.encoder - self.zero, check=False)
self.comm(MOVE, 0, (self.encoder - self.zero) / ANGLE_SCALE)
time.sleep(0.1)
if itry > 5:
tol = self.tolerance
self.status = self.Status.ERROR, 'reset failed'
return
@Command() @Command
def stop(self): def stop(self):
self.comm(MOTOR_STOP, 0) self.comm(MOTOR_STOP)
self.status = self.Status.IDLE, 'stopped' self.status = self.Status.IDLE, 'stopped'
self._started = 0 self._targettime = None
#@Command() @Command(FloatRange())
#def step(self): def step(self, value):
# self.comm(MOVE, 1, FULL_STEP / ANGLE_SCALE) self.comm(MOVE, 1, value / self.fact)
#@Command() @Command(IntRange(), result=IntRange())
#def back(self): def get_axis_par(self, adr):
# self.comm(MOVE, 1, - FULL_STEP / ANGLE_SCALE) return self.comm(GET_AXIS_PAR, adr)
#@Command(IntRange(), result=IntRange()) @Command(IntRange())
#def get_axis_par(self, adr): def set_axis_par(self, adr, value):
# return self.comm(GET_AXIS_PAR, adr) return self.comm(SET_AXIS_PAR, adr, value)
#@Command((IntRange(), FloatRange()), result=IntRange())
#def set_axis_par(self, adr, value):
# return self.comm(SET_AXIS_PAR, adr, value)