Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
daa4c72133 | |||
96f4133a4b | |||
1409959f53 | |||
10147b0db5 | |||
e2f258658c | |||
3d67fef557 | |||
eb3d35e1a6 | |||
2863c2bca0 | |||
390c955a8a | |||
539d97a733 | |||
1fd16bbc43 | |||
5e77a43f6c | |||
3e7b008a59 | |||
2f96a2db92 | |||
fab950550d | |||
801eab4b13 | |||
06551b17e2 | |||
4a0796a1bd |
@ -1,8 +1,35 @@
|
||||
[NODE]
|
||||
description = sea client (communication only)
|
||||
id = comm.sea.psi.ch
|
||||
[node seatest.psi.ch]
|
||||
description = SEA test
|
||||
|
||||
[seaconn]
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5002
|
||||
|
||||
[module seaconn]
|
||||
class = secop_psi.sea.SeaClient
|
||||
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
|
||||
|
@ -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
|
@ -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
|
@ -1,11 +1,11 @@
|
||||
[NODE]
|
||||
description = thin film oven for AMOR
|
||||
id = fftf.config.sea.psi.ch
|
||||
id = mbe.config.sea.psi.ch
|
||||
|
||||
[sea_main]
|
||||
class = secop_psi.sea.SeaClient
|
||||
description = main sea connection for fftf.config
|
||||
config = fftf.config
|
||||
description = SEA connection to mbe
|
||||
config = mbe.config
|
||||
service = main
|
||||
|
||||
[tt]
|
||||
@ -13,24 +13,23 @@ class = secop_psi.sea.SeaDrivable
|
||||
iodev = sea_main
|
||||
sea_object = tt
|
||||
|
||||
[cc]
|
||||
class = secop_psi.sea.SeaReadable
|
||||
iodev = sea_main
|
||||
sea_object = cc
|
||||
|
||||
[p]
|
||||
class = secop_psi.sea.SeaReadable
|
||||
iodev = sea_main
|
||||
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]
|
||||
class = secop_psi.sea.SeaWritable
|
||||
iodev = sea_main
|
||||
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
|
@ -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
|
37
cfg/sea.cfg
37
cfg/sea.cfg
@ -1,8 +1,35 @@
|
||||
[NODE]
|
||||
description = sea client (communication only)
|
||||
id = comm.sea.psi.ch
|
||||
[node seatest.psi.ch]
|
||||
description = SEA test
|
||||
|
||||
[seaconn]
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5002
|
||||
|
||||
[module seaconn]
|
||||
class = secop_psi.sea.SeaClient
|
||||
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
|
||||
|
@ -1,5 +1,4 @@
|
||||
{"tt": {"base": "/tt", "params": [
|
||||
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 14},
|
||||
{"tt": {"base": "/tt", "params": [{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 14},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
|
||||
{"path": "status", "type": "text", "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/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
|
||||
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
|
||||
{"path": "remote", "type": "bool"}]},
|
||||
|
||||
"cc": {"base": "/cc", "params": [
|
||||
{"path": "", "type": "bool", "kids": 96},
|
||||
{"path": "remote", "type": "bool"}]}, "cc": {"base": "/cc", "params": [{"path": "", "type": "bool", "kids": 96},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
|
||||
{"path": "status", "type": "text", "visibility": 3},
|
||||
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
|
||||
@ -148,24 +144,10 @@
|
||||
{"path": "tm", "type": "float", "visibility": 3},
|
||||
{"path": "tv", "type": "float", "visibility": 3},
|
||||
{"path": "tq", "type": "float", "visibility": 3},
|
||||
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]},
|
||||
|
||||
"p": {"base": "/p", "params": [
|
||||
{"path": "", "type": "float", "kids": 6},
|
||||
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}, "p": {"base": "/p", "params": [{"path": "", "type": "float", "kids": 6},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "p send", "visibility": 3},
|
||||
{"path": "status", "type": "text", "visibility": 3},
|
||||
{"path": "vacuumpump", "type": "bool", "readonly": false, "cmd": "p vacuumpump"},
|
||||
{"path": "gasflow", "type": "bool", "readonly": false, "cmd": "p gasflow"},
|
||||
{"path": "tlimit", "type": "float", "readonly": false, "cmd": "p tlimit"},
|
||||
{"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 ..."}]}}
|
||||
{"path": "tlimit_without_vacuum", "type": "float", "readonly": false, "cmd": "p tlimit_without_vacuum"}]}}
|
@ -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}]}}
|
@ -8,8 +8,8 @@ uri = tcp://5000
|
||||
[drv_iodev]
|
||||
description =
|
||||
class = secop.bytesio.BytesIO
|
||||
uri = serial:///dev/ttyUSB0?baudrate=57600
|
||||
# uri = serial:///dev/ttyUSB0?baudrate=9600
|
||||
# uri = serial:///dev/ttyUSB0?baudrate=57600
|
||||
uri = serial:///dev/ttyUSB0?baudrate=9600
|
||||
|
||||
[drv]
|
||||
description = trinamic motor test
|
||||
@ -17,9 +17,9 @@ class = secop_psi.trinamic.Motor
|
||||
iodev = drv_iodev
|
||||
standby_current=0.1
|
||||
maxcurrent=1.4
|
||||
acceleration=150.
|
||||
movelimit=360
|
||||
speed=40
|
||||
encoder_tolerance=3.6
|
||||
free_wheeling=0.1
|
||||
power_down_delay=0.1
|
||||
acceleration=50
|
||||
maxspeed=200
|
||||
zero=-36
|
||||
enc_tolerance=3.6
|
||||
free_wheeling=0.001
|
||||
pull_up=1
|
||||
|
@ -8,39 +8,25 @@ uri = tcp://5000
|
||||
[drv_iodev]
|
||||
description =
|
||||
class = secop.bytesio.BytesIO
|
||||
# uri = serial:///dev/ttyUSB1?baudrate=57600
|
||||
uri = tcp://192.168.127.254:3002
|
||||
uri = serial:///dev/ttyUSB0?baudrate=57600
|
||||
# uri = serial:///dev/ttyUSB0?baudrate=9600
|
||||
|
||||
[drv]
|
||||
description = trinamic motor test
|
||||
class = secop_psi.trinamic.Motor
|
||||
iodev = drv_iodev
|
||||
standby_current=0.1
|
||||
maxcurrent=0.2
|
||||
acceleration=150.
|
||||
movelimit=360
|
||||
speed=40
|
||||
encoder_tolerance=3.6
|
||||
free_wheeling=0.1
|
||||
power_down_delay=0.1
|
||||
maxcurrent=1.4
|
||||
acceleration=50
|
||||
maxspeed=200
|
||||
zero=-36
|
||||
enc_tolerance=3.6
|
||||
free_wheeling=0.001
|
||||
pull_up=1
|
||||
|
||||
[force]
|
||||
description = DPM driver to read out the transducer value, write and read the offset and scale factor
|
||||
class = secop_psi.dpm.DPM3
|
||||
#uri = serial:///dev/ttyUSB0?baudrate=9600
|
||||
uri = tcp://192.168.127.254:3001
|
||||
uri = serial:///dev/ttyUSB1?baudrate=9600
|
||||
digits = 2
|
||||
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
|
||||
|
@ -37,5 +37,3 @@ from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
|
||||
from secop.properties import Property
|
||||
from secop.proxy import Proxy, SecNode, proxy_class
|
||||
from secop.stringio import HasIodev, StringIO
|
||||
from secop.bytesio import BytesIO
|
||||
from secop.persistent import PersistentMixin, PersistentParam
|
||||
|
@ -251,9 +251,7 @@ class AsynSerial(AsynConn):
|
||||
if not fullname.startswith(name):
|
||||
raise ConfigError('illegal parity: %s' % parity)
|
||||
options['parity'] = name[0]
|
||||
if 'timeout' in options:
|
||||
options['timeout'] = float(self.timeout)
|
||||
else:
|
||||
if 'timeout' not in options:
|
||||
options['timeout'] = self.timeout
|
||||
try:
|
||||
self.connection = Serial(dev, **options)
|
||||
|
@ -30,7 +30,7 @@ from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
|
||||
IntRange, StatusType, StringType, TextType, TupleOf, get_datatype
|
||||
from secop.errors import BadValueError, ConfigError, InternalError, \
|
||||
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.params import Accessible, Command, Parameter
|
||||
from secop.poller import BasicPoller, Poller
|
||||
@ -385,7 +385,7 @@ class Module(HasAccessibles):
|
||||
if k in self.parameters or k in self.propertyDict:
|
||||
setattr(self, k, v)
|
||||
cfgdict.pop(k)
|
||||
except (ValueError, TypeError) as e:
|
||||
except (ValueError, TypeError):
|
||||
# self.log.exception(formatExtendedStack())
|
||||
errors.append('module %s, parameter %s: %s' % (self.name, k, e))
|
||||
|
||||
|
@ -39,6 +39,10 @@ class Accessible(HasProperties):
|
||||
|
||||
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):
|
||||
# do not use self.propertyValues.update here, as no invalid values should be
|
||||
# assigned to properties, even not before checkProperties
|
||||
@ -149,9 +153,6 @@ class Parameter(Accessible):
|
||||
handler = Property(
|
||||
'[internal] overload the standard read and write functions', ValueType(),
|
||||
export=False, default=None, settable=False)
|
||||
persistent = Property(
|
||||
'[internal] persistent setting', BoolType(),
|
||||
export=False, default=False)
|
||||
initwrite = Property(
|
||||
'''[internal] write this parameter on initialization
|
||||
|
||||
@ -164,7 +165,7 @@ class Parameter(Accessible):
|
||||
readerror = None
|
||||
|
||||
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 not isinstance(datatype, DataType):
|
||||
if isinstance(datatype, type) and issubclass(datatype, DataType):
|
||||
@ -177,8 +178,6 @@ class Parameter(Accessible):
|
||||
if 'default' in kwds:
|
||||
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:
|
||||
self.description = inspect.cleandoc(description)
|
||||
|
||||
@ -314,8 +313,7 @@ class Command(Accessible):
|
||||
func = None
|
||||
|
||||
def __init__(self, argument=False, *, result=None, inherit=True, **kwds):
|
||||
super().__init__()
|
||||
self.init(kwds)
|
||||
super().__init__(**kwds)
|
||||
if result or kwds or isinstance(argument, DataType) or not callable(argument):
|
||||
# normal case
|
||||
if argument is False and result:
|
||||
|
@ -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()
|
@ -260,7 +260,7 @@ class Server:
|
||||
self.modules[modname] = modobj
|
||||
except ConfigError as e:
|
||||
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)
|
||||
except Exception:
|
||||
failure_traceback = traceback.format_exc()
|
||||
|
@ -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
|
@ -16,13 +16,11 @@
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# M. Zolliker <markus.zolliker@psi.ch>
|
||||
# ...
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""transducer DPM3 read out"""
|
||||
|
||||
from secop.core import Drivable, Parameter, FloatRange, BoolType, StringIO,\
|
||||
HasIodev, IntRange, Done, Attached, Command
|
||||
from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIodev, IntRange, Done
|
||||
|
||||
|
||||
class DPM3IO(StringIO):
|
||||
@ -45,7 +43,7 @@ def float2hex(value, digits):
|
||||
return '%06X' % intvalue
|
||||
|
||||
|
||||
class DPM3(HasIodev, Drivable):
|
||||
class DPM3(HasIodev, Readable):
|
||||
OFFSET = 0x8f
|
||||
SCALE = 0x8c
|
||||
|
||||
@ -54,11 +52,8 @@ class DPM3(HasIodev, Drivable):
|
||||
|
||||
iodevClass = DPM3IO
|
||||
|
||||
motor = Attached()
|
||||
digits = Parameter('number of digits for value', IntRange(0, 5), initwrite=True, readonly=False)
|
||||
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)
|
||||
|
||||
@ -67,8 +62,6 @@ class DPM3(HasIodev, Drivable):
|
||||
thus a maximl output of 1500. 10=150/f
|
||||
"""
|
||||
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):
|
||||
if value is not None:
|
||||
@ -100,7 +93,7 @@ class DPM3(HasIodev, Drivable):
|
||||
return value/mag
|
||||
else:
|
||||
return hex2float(hexvalue, self.digits)
|
||||
|
||||
|
||||
def write_digits(self, value):
|
||||
# value defines the number of digits
|
||||
back_value=self._iodev.communicate('*1F135%02X\r*1G135' % (value + 1))
|
||||
@ -115,37 +108,9 @@ class DPM3(HasIodev, Drivable):
|
||||
return int(back_value,16) - 1
|
||||
|
||||
def read_value(self):
|
||||
value = float(self._iodev.communicate('*1B1'))
|
||||
if self._target is not None:
|
||||
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
|
||||
value = self._iodev.communicate('*1B1')
|
||||
return float(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):
|
||||
reply = self.query(self.OFFSET)
|
||||
return reply
|
||||
|
@ -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
|
@ -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)
|
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# 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):
|
||||
break
|
||||
else:
|
||||
seaconfdir = os.environ.get('FRAPPY_SEA_DIR')
|
||||
seaconfdir = None
|
||||
|
||||
|
||||
def get_sea_port(instance):
|
||||
@ -262,37 +263,18 @@ class SeaClient(ProxyClient, Module):
|
||||
if value is None:
|
||||
value = oldv
|
||||
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)
|
||||
|
||||
|
||||
@Command(StringType(), result=StringType())
|
||||
def communicate(self, command):
|
||||
"""send a command to SEA"""
|
||||
reply = self.request(command)
|
||||
return reply
|
||||
|
||||
@Command(StringType(), result=StringType())
|
||||
def query(self, cmd):
|
||||
"""a request checking for errors and accepting 0 or 1 line as result"""
|
||||
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)
|
||||
@Command(result=StringType())
|
||||
def describe(self):
|
||||
"""save objects (and sub-objects) description"""
|
||||
reply = self.request('describe_all')
|
||||
reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n'))
|
||||
description, reply = json.loads(reply)
|
||||
@ -319,11 +301,11 @@ class SeaConfigCreator(SeaClient):
|
||||
nodedescr=description.get(filename, filename)))
|
||||
for obj in descr:
|
||||
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:
|
||||
fp.write(content + '\n')
|
||||
result.append('%s: %s' % (filename, ','.join(n for n in descr)))
|
||||
raise SystemExit('; '.join(result))
|
||||
return '; '.join(result)
|
||||
|
||||
@Command(StringType(), result=StringType())
|
||||
def query(self, cmd):
|
||||
@ -387,9 +369,11 @@ class SeaModule(Module):
|
||||
paramdesc['key'] = 'value'
|
||||
if issubclass(cls, SeaWritable):
|
||||
if paramdesc.get('readonly', True):
|
||||
raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path']))
|
||||
paramdesc['key'] = 'target'
|
||||
paramdesc['readonly'] = False
|
||||
raise ConfigError('%s is not writable' % sea_object)
|
||||
targetdesc = dict(paramdesc, key='target')
|
||||
params.append(targetdesc)
|
||||
paramdesc['readonly'] = True
|
||||
# print('SINGLE %s/%s %s %r' % (base, paramdesc['path'], cls.__name__, params))
|
||||
extra_module_set = ()
|
||||
if 'description' not in cfgdict:
|
||||
cfgdict['description'] = '%s@%s' % (single_module, json_file)
|
||||
@ -407,11 +391,8 @@ class SeaModule(Module):
|
||||
if rel_paths == '*' or not rel_paths:
|
||||
# take all
|
||||
main = descr['params'][0]
|
||||
if issubclass(cls, Readable):
|
||||
# assert main['path'] == '' # TODO: check cases where this fails
|
||||
main['key'] = 'value'
|
||||
else:
|
||||
descr['params'].pop(0)
|
||||
# assert main['path'] == '' # TODO: check cases where this fails
|
||||
main['key'] = 'value'
|
||||
else:
|
||||
# filter by relative paths
|
||||
rel_paths = rel_paths.split()
|
||||
@ -420,11 +401,7 @@ class SeaModule(Module):
|
||||
include = True
|
||||
for paramdesc in descr['params']:
|
||||
path = paramdesc['path']
|
||||
if path.endswith('is_running'):
|
||||
# take this always
|
||||
result.append(paramdesc)
|
||||
continue
|
||||
if paramdesc.get('visibility', 1) > visibility_level:
|
||||
if paramdesc.get('visibility', 1) > visibility_level and not path.endswith('is_running'):
|
||||
continue
|
||||
sub = path.split('/', 1)
|
||||
if rpath == '.': # take all except subpaths with readonly node at top
|
||||
@ -435,22 +412,14 @@ class SeaModule(Module):
|
||||
elif sub[0] == rpath:
|
||||
result.append(paramdesc)
|
||||
descr['params'] = result
|
||||
for valuedesc in result:
|
||||
if valuedesc['path'] == '':
|
||||
valuedesc['key'] = 'value'
|
||||
break
|
||||
rel0 = '' if rel_paths[0] == '.' else rel_paths[0]
|
||||
if result[0]['path'] == rel0:
|
||||
result[0]['key'] = 'value'
|
||||
else:
|
||||
logger.error('%s: no value found', name)
|
||||
# logger.info('PARAMS %s %r', name, result)
|
||||
base = descr['base']
|
||||
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', ())
|
||||
if extra_module_set:
|
||||
extra_module_set = set(extra_module_set.replace(',', ' ').split())
|
||||
@ -473,23 +442,21 @@ class SeaModule(Module):
|
||||
if kwds['datatype'] is None:
|
||||
kwds.update(visibility=3, default='', datatype=StringType())
|
||||
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 len(pathlist) > 0:
|
||||
if len(pathlist) == 1:
|
||||
if issubclass(cls, Readable):
|
||||
kwds['group'] = 'more'
|
||||
kwds['group'] = 'more'
|
||||
else:
|
||||
kwds['group'] = pathlist[-2]
|
||||
# flatten path to parameter name
|
||||
key = None
|
||||
for i in reversed(range(len(pathlist))):
|
||||
key = '_'.join(pathlist[i:])
|
||||
if not key in cls.accessibles:
|
||||
break
|
||||
if key == 'is_running':
|
||||
kwds['export'] = False
|
||||
if key == 'target' and kwds.get('group') == 'more':
|
||||
kwds.pop('group')
|
||||
if key in cls.accessibles:
|
||||
if key == 'target':
|
||||
kwds['readonly'] = False
|
||||
@ -498,21 +465,20 @@ class SeaModule(Module):
|
||||
else:
|
||||
pobj = Parameter(**kwds)
|
||||
datatype = pobj.datatype
|
||||
if issubclass(cls, SeaWritable) and key == 'target':
|
||||
kwds['readonly'] = False
|
||||
attributes['value'] = Parameter(**kwds)
|
||||
|
||||
hdbpath = '/'.join([base] + pathlist)
|
||||
if key in extra_module_set:
|
||||
extra_modules[name + '.' + key] = sea_object, base, paramdesc
|
||||
continue # skip this parameter
|
||||
path2param[hdbpath] = (name, key)
|
||||
# logger.info('PARAM %s %s %s', hdbpath, name, key)
|
||||
attributes[key] = pobj
|
||||
# if hasattr(cls, 'read_' + key):
|
||||
# print('override %s.read_%s' % (cls.__name__, key))
|
||||
|
||||
def rfunc(self, cmd='hval %s/%s' % (base, path)):
|
||||
print('READ', cmd)
|
||||
reply = self._iodev.query(cmd)
|
||||
print('REPLY', reply)
|
||||
try:
|
||||
reply = float(reply)
|
||||
except ValueError:
|
||||
@ -523,8 +489,8 @@ class SeaModule(Module):
|
||||
attributes['read_' + key] = rfunc
|
||||
|
||||
if not readonly:
|
||||
# if hasattr(cls, 'write_' + key):
|
||||
# print('override %s.write_%s' % (cls.__name__, key))
|
||||
if hasattr(cls, 'write_' + key):
|
||||
print('override %s.write_%s' % (cls.__name__, key))
|
||||
|
||||
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
|
||||
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?)
|
||||
cmd = "%s %s" % (command, value)
|
||||
self._iodev.query(cmd)
|
||||
print('WRITE %s' % cmd)
|
||||
return Done
|
||||
|
||||
attributes['write_' + key] = wfunc
|
||||
@ -596,7 +563,6 @@ class SeaWritable(SeaModule, Writable):
|
||||
return self.target
|
||||
|
||||
def update_target(self, value, timestamp, readerror):
|
||||
self.target = value
|
||||
if not readerror:
|
||||
self.value = value
|
||||
|
||||
|
@ -198,7 +198,7 @@ class Sensor(Readable):
|
||||
|
||||
def update_value(self, value):
|
||||
if self.abs:
|
||||
value = abs(float(value))
|
||||
value = abs(value)
|
||||
self.value = self._calib(value)
|
||||
self._value_error = None
|
||||
|
||||
|
@ -23,121 +23,221 @@
|
||||
"""drivers for trinamic PD-1161 motors"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import struct
|
||||
from math import log10
|
||||
|
||||
import json
|
||||
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.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError
|
||||
|
||||
from secop.lib import getGeneralConfig
|
||||
from secop.errors import CommunicationFailedError, HardwareError
|
||||
|
||||
MOTOR_STOP = 3
|
||||
MOVE = 4
|
||||
SET_AXIS_PAR = 5
|
||||
GET_AXIS_PAR = 6
|
||||
SAVE_AXIS_PAR = 7
|
||||
LOAD_AXIS_PAR = 8
|
||||
SET_GLOB_PAR = 9
|
||||
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]
|
||||
|
||||
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 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):
|
||||
class Motor(HasIodev, Drivable):
|
||||
address = Property('module address', IntRange(0, 255), default=1)
|
||||
|
||||
# limit_pin_mask = Property('input pin mask for lower/upper limit switch',
|
||||
# TupleOf(IntRange(0, 15), IntRange(0, 15)),
|
||||
# default=(8, 0))
|
||||
|
||||
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)
|
||||
fact = Property('gear factor', FloatRange(unit='deg/step'), default=1.8/256)
|
||||
limit_pin_mask = Property('input pin mask for lower/upper limit switch',
|
||||
TupleOf(IntRange(0, 15), IntRange(0, 15)),
|
||||
default=(8, 0))
|
||||
value = Parameter('motor position', FloatRange(unit='deg'))
|
||||
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='$'),
|
||||
readonly=False, default=360, group='more')
|
||||
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')
|
||||
|
||||
temperature = IoPar(9, 1, IntRange(), readonly=True)
|
||||
operating_voltage = IoPar(8, 1, IntRange(), readonly=True)
|
||||
|
||||
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
|
||||
fast_pollfactor = 0.001 # poll as fast as possible when busy
|
||||
_started = 0
|
||||
_calcTimeout = True
|
||||
_need_reset = None
|
||||
_poserror = None
|
||||
save_filename = None
|
||||
|
||||
def comm(self, cmd, adr, value=0, bank=0):
|
||||
"""set or get a parameter
|
||||
def earlyInit(self):
|
||||
self.writeDict.update(self.loadParams())
|
||||
|
||||
:param adr: parameter number
|
||||
:param cmd: SET command (in the GET case, 1 is added to this)
|
||||
:param bank: the bank
|
||||
:param value: if given, the parameter is written, else it is returned
|
||||
:return: the returned value
|
||||
"""
|
||||
def loadParams(self):
|
||||
# the following should be moved to core functionality
|
||||
savedir = os.path.join(CONFIG['logdir'], 'persistent')
|
||||
os.makedirs(savedir, exist_ok=True)
|
||||
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:
|
||||
self._calcTimeout = False
|
||||
baudrate = getattr(self._iodev._conn.connection, 'baudrate', None)
|
||||
@ -146,21 +246,19 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
||||
raise CommunicationFailedError('unsupported baud rate: %d' % baudrate)
|
||||
self._iodev.timeout = 0.03 + 200 / baudrate
|
||||
|
||||
exc = None
|
||||
for itry in range(3,0,-1):
|
||||
byt = struct.pack('>BBBBi', self.address, cmd, adr, bank, round(value))
|
||||
bank = adr // 1000
|
||||
iadr = adr % 1000
|
||||
for itry in range(3):
|
||||
byt = struct.pack('>BBBBi', self.address, cmd, iadr, bank, round(value))
|
||||
try:
|
||||
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:
|
||||
if itry == 1:
|
||||
raise
|
||||
exc = e
|
||||
print(repr(e), adr, bank)
|
||||
continue
|
||||
break
|
||||
if exc:
|
||||
self.log.warning('tried %d times after %s', itry, exc)
|
||||
if sum(reply[:-1]) & 0xff == reply[-1]:
|
||||
break
|
||||
else:
|
||||
raise CommunicationFailedError('checksum error')
|
||||
radr, modadr, status, rcmd, result = struct.unpack('>BBBBix', reply)
|
||||
if status != 100:
|
||||
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))
|
||||
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):
|
||||
encoder = self.read_encoder()
|
||||
steppos = self.read_steppos()
|
||||
initialized = self.comm(GET_GLOB_PAR, 255, bank=2)
|
||||
if initialized: # no power loss
|
||||
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
|
||||
rawenc = self.comm(GET_AXIS_PAR, 209)
|
||||
rawpos = self.comm(GET_AXIS_PAR, 1)
|
||||
self.encoder = AxisZeroPar.from_raw(self, rawenc)
|
||||
self.steppos = AxisZeroPar.from_raw(self, rawpos)
|
||||
|
||||
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):
|
||||
oldpos = self.steppos
|
||||
self.read_value() # make sure encoder and steppos are fresh
|
||||
if not self._started:
|
||||
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'
|
||||
if not self._targettime:
|
||||
if self._poserror:
|
||||
return self.Status.ERROR, 'encoder does not match internal pos'
|
||||
return self.status
|
||||
now = time.time()
|
||||
if oldpos != self.steppos or not (self.read_target_reached() or self.read_move_status()
|
||||
or self.read_error_bits()):
|
||||
return self.Status.BUSY, 'moving'
|
||||
diff = self.target - self.encoder
|
||||
if abs(diff) <= self.tolerance:
|
||||
self._started = 0
|
||||
return self.Status.IDLE, ''
|
||||
#if (abs(self.target - self.steppos) < self.tolerance and
|
||||
# abs(self.encoder - self.steppos) < self.encoder_tolerance):
|
||||
# self._try_count += 1
|
||||
# if self._try_count < 3:
|
||||
# # occasionaly, two attempts are needed, as steppos and encoder might have been
|
||||
# # off by 1-2 full steps before moving
|
||||
# self.fix_steppos(self.tolerance, self.target)
|
||||
# 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'
|
||||
# self.get_limits()
|
||||
# if self.at_lower_limit or self.at_upper_limit:
|
||||
# self.stop()
|
||||
# return self.Status.ERROR, 'at limit'
|
||||
target_reached = self.read_target_reached()
|
||||
if target_reached or self.read_move_status():
|
||||
self._targettime = None
|
||||
if abs(self.target - self.value) < self.tolerance:
|
||||
return self.Status.IDLE, ''
|
||||
self.log.warning('out of tolerance (%d)', self.move_status)
|
||||
return self.Status.WARN, 'out of tolerance (%d)' % self.move_status
|
||||
if time.time() > self._targettime:
|
||||
self.log.warning('move timeout')
|
||||
return self.Status.WARN, 'move timeout'
|
||||
return self.Status.BUSY, 'moving'
|
||||
|
||||
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:
|
||||
raise BadValueError('can not move more than %s deg' % self.movelimit)
|
||||
diff = self.encoder - self.steppos
|
||||
if self._need_reset:
|
||||
raise HardwareError('need reset (%s)' % self.status[1])
|
||||
if abs(diff) > self.tolerance:
|
||||
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
|
||||
raise ValueError('can not move more than %s deg' % self.movelimit)
|
||||
rawtarget = round((target - self.zero) / self.fact)
|
||||
delay = (abs(rawtarget - rawpos) / 30.5 / self.maxspeed
|
||||
+ self.maxspeed / self.acceleration / 15.25 + 0.5)
|
||||
self._targettime = time.time() + delay
|
||||
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'
|
||||
return target
|
||||
|
||||
def write_zero(self, value):
|
||||
diff = value - self.zero
|
||||
self.encoder += diff
|
||||
self.steppos += diff
|
||||
self.value += diff
|
||||
# def get_limits(self):
|
||||
# self.input_bits = self.comm(GET_IO, 255)
|
||||
# bits = (~ self.input_bits) & 0xf
|
||||
# self.at_lower_limit = bool(bits & self.limit_pin_mask[0])
|
||||
# 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
|
||||
|
||||
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):
|
||||
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()
|
||||
def reset(self):
|
||||
"""set steppos to encoder value, if not within tolerance"""
|
||||
if self._started:
|
||||
raise IsBusyError('can not reset while moving')
|
||||
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
|
||||
if self._poserror:
|
||||
self._poserror = 'reset'
|
||||
self.write_target(self.encoder)
|
||||
|
||||
@Command()
|
||||
@Command
|
||||
def stop(self):
|
||||
self.comm(MOTOR_STOP, 0)
|
||||
self.comm(MOTOR_STOP)
|
||||
self.status = self.Status.IDLE, 'stopped'
|
||||
self._started = 0
|
||||
self._targettime = None
|
||||
|
||||
#@Command()
|
||||
#def step(self):
|
||||
# self.comm(MOVE, 1, FULL_STEP / ANGLE_SCALE)
|
||||
@Command(FloatRange())
|
||||
def step(self, value):
|
||||
self.comm(MOVE, 1, value / self.fact)
|
||||
|
||||
#@Command()
|
||||
#def back(self):
|
||||
# self.comm(MOVE, 1, - FULL_STEP / ANGLE_SCALE)
|
||||
@Command(IntRange(), result=IntRange())
|
||||
def get_axis_par(self, adr):
|
||||
return self.comm(GET_AXIS_PAR, adr)
|
||||
|
||||
#@Command(IntRange(), result=IntRange())
|
||||
#def get_axis_par(self, adr):
|
||||
# return self.comm(GET_AXIS_PAR, adr)
|
||||
|
||||
#@Command((IntRange(), FloatRange()), result=IntRange())
|
||||
#def set_axis_par(self, adr, value):
|
||||
# return self.comm(SET_AXIS_PAR, adr, value)
|
||||
@Command(IntRange())
|
||||
def set_axis_par(self, adr, value):
|
||||
return self.comm(SET_AXIS_PAR, adr, value)
|
||||
|
Reference in New Issue
Block a user