added drivers for small furnace
This commit is contained in:
parent
9e6699dd1e
commit
4a2ce62dd8
67
frappy_psi/bkpower.py
Normal file
67
frappy_psi/bkpower.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
|
||||||
|
# *****************************************************************************
|
||||||
|
"""Powersupply B&K Precision BK168xB"""
|
||||||
|
|
||||||
|
from frappy.core import StringIO, Readable, Parameter, FloatRange, Writable, HasIO, BoolType
|
||||||
|
|
||||||
|
class IO(StringIO):
|
||||||
|
end_of_line = ('OK\r', '\r')
|
||||||
|
default_settings = {'baudrate': 9600}
|
||||||
|
|
||||||
|
|
||||||
|
class Power(HasIO, Readable):
|
||||||
|
value = Parameter(datatype=FloatRange(0,300,unit='W'))
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
reply = self.communicate('GETD')
|
||||||
|
volt = float(reply[0:4])/100
|
||||||
|
current = float(reply[4:8])/100
|
||||||
|
return volt*current
|
||||||
|
|
||||||
|
|
||||||
|
class Output(HasIO, Writable):
|
||||||
|
value = Parameter(datatype=FloatRange(0,100,unit='%'))
|
||||||
|
target = Parameter(datatype=FloatRange(0,100,unit='%'))
|
||||||
|
maxvolt = Parameter('voltage at 100%',datatype=FloatRange(0,60,unit='V'),default=50,readonly=False)
|
||||||
|
maxcurrent = Parameter('current at 100%',datatype=FloatRange(0,5,unit='A'),default=2,readonly=False)
|
||||||
|
output_enable = Parameter('control on/off', BoolType(), readonly=False)
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.write_output_enable(False)
|
||||||
|
|
||||||
|
def write_target(self, target):
|
||||||
|
self.write_output_enable(target != 0)
|
||||||
|
self.communicate(f'VOLT{round(max(8,target*self.maxvolt/10)):03d}')
|
||||||
|
self.communicate(f'CURR{round(target*self.maxcurrent):03d}')
|
||||||
|
self.value = target
|
||||||
|
|
||||||
|
def write_output_enable(self, value):
|
||||||
|
self.communicate(f'SOUT{int(not value)}')
|
||||||
|
|
||||||
|
def write_maxvolt(self, maxvolt):
|
||||||
|
self.communicate(f'SOVP{round(maxvolt*10):03d}')
|
||||||
|
|
||||||
|
def write_maxcurrent(self, maxcurrent):
|
||||||
|
self.communicate(f'SOCP{round(maxcurrent*100):03d}')
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.write_target(0)
|
||||||
|
|
49
frappy_psi/furnace.py
Normal file
49
frappy_psi/furnace.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
"""interlocks for furnance"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable, BoolType, ERROR, IDLE
|
||||||
|
|
||||||
|
class Interlocks(Module):
|
||||||
|
input = Attached(Readable, 'the input module')
|
||||||
|
vacuum = Attached (Readable, 'the vacuum pressure')
|
||||||
|
wall_T = Attached (Readable, 'the wall temperature')
|
||||||
|
control = Attached(Module, 'the control module')
|
||||||
|
relais = Attached(Writable, 'the interlock relais')
|
||||||
|
wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'),
|
||||||
|
default = 50, readonly = False)
|
||||||
|
vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'),
|
||||||
|
default = 0.1, readonly = False)
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
if self.input.status[0] >= ERROR:
|
||||||
|
self.control.status = self.input.status
|
||||||
|
elif self.vacuum.value > self.vacuum_limit:
|
||||||
|
self.control.status = ERROR, 'bad vacuum'
|
||||||
|
elif self.wall_T.value > self.wall_limit:
|
||||||
|
self.control.status = ERROR, 'wall overheat'
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
self.control.write_control_active(False)
|
||||||
|
self.relais.write_target(False)
|
||||||
|
|
||||||
|
|
116
frappy_psi/ionopimax.py
Normal file
116
frappy_psi/ionopimax.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from frappy.core import Readable, Writable, Parameter, BoolType, StringType,\
|
||||||
|
FloatRange, Property, TupleOf, ERROR, IDLE
|
||||||
|
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
addr = Property('address', StringType())
|
||||||
|
def read(self, addr, scale=None):
|
||||||
|
with open(f'/sys/class/ionopimax/{self.devclass}/{addr}') as f:
|
||||||
|
result = f.read()
|
||||||
|
if scale:
|
||||||
|
return float(result) / scale
|
||||||
|
return result
|
||||||
|
|
||||||
|
def write(self, addr, value, scale=None):
|
||||||
|
value = str(round(value * scale)) if scale else str(value)
|
||||||
|
with open(f'/sys/class/ionopimax/{self.devclass}/{addr}', 'w') as f:
|
||||||
|
f.write(value)
|
||||||
|
|
||||||
|
|
||||||
|
class DigitalInput(Base, Readable):
|
||||||
|
value = Parameter('input state', BoolType())
|
||||||
|
devclass = 'digital_in'
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
return self.read(self.addr, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DigitalOutput(DigitalInput, Writable):
|
||||||
|
target = Parameter('output state', BoolType(), readonly=False)
|
||||||
|
devclass = 'digital_out'
|
||||||
|
|
||||||
|
def write_target(self, value):
|
||||||
|
self.write(self.addr, value, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class AnalogInput(Base, Readable):
|
||||||
|
value = Parameter('analog value', FloatRange())
|
||||||
|
rawrange = Property('raw range(electronic)', TupleOf(FloatRange(),FloatRange()))
|
||||||
|
valuerange = Property('value range(physical)', TupleOf(FloatRange(),FloatRange()))
|
||||||
|
devclass = 'analog_in'
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
x0, x1 = self.rawrange
|
||||||
|
y0, y1 = self.valuerange
|
||||||
|
self.x = self.read(self.addr, self.scale)
|
||||||
|
return y0 + (y1 - y0) * (self.x - x0) / (x1 - x0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VoltageInput(AnalogInput):
|
||||||
|
scale = 1e5
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.write(f'{self.addr}_mode','U')
|
||||||
|
|
||||||
|
|
||||||
|
class CurrentInput(AnalogInput):
|
||||||
|
scale = 1e6
|
||||||
|
rawrange = (0.004,0.02)
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.write(f'{self.addr}_mode','U')
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
result = super().read_value()
|
||||||
|
if self.x > 0.021:
|
||||||
|
self.status = ERROR, 'sensor broken'
|
||||||
|
else:
|
||||||
|
self.status = IDLE, ''
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class AnalogOutput(AnalogInput, Writable):
|
||||||
|
target = Parameter('outputvalue', FloatRange())
|
||||||
|
devclass = 'analog_out'
|
||||||
|
|
||||||
|
def write_target(self, value):
|
||||||
|
x0, x1 = self.rawrange
|
||||||
|
y0, y1 = self.valuerange
|
||||||
|
self.write(self.addr, x0 + (x1 - x0) * (value - y0) / (y1 - y0),self.scale)
|
||||||
|
|
||||||
|
|
||||||
|
class VoltageOutput(AnalogOutput):
|
||||||
|
rawrange = (0,10)
|
||||||
|
scale = 1e3
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.write(f'{self.addr}_enabled', '0')
|
||||||
|
self.write(f'{self.addr}_mode', 'V')
|
||||||
|
self.write(f'{self.addr}', '0')
|
||||||
|
self.write(f'{self.addr}_enabled', '1')
|
||||||
|
|
||||||
|
|
64
frappy_psi/pfeiffer.py
Normal file
64
frappy_psi/pfeiffer.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# *****************************************************************************
|
||||||
|
"""pfeiffer TPG vacuum pressure reading"""
|
||||||
|
|
||||||
|
from frappy.core import StringIO, HasIO, Command, StringType, IntRange, \
|
||||||
|
IDLE, WARN, ERROR, Readable, Parameter, Property
|
||||||
|
from frappy.errors import CommunicationFailedError
|
||||||
|
|
||||||
|
ACK = '\x06'
|
||||||
|
ENQ = '\x05'
|
||||||
|
|
||||||
|
|
||||||
|
class IO(StringIO):
|
||||||
|
end_of_line = '\r\n'
|
||||||
|
default_settings = {'baudrate': 9600}
|
||||||
|
|
||||||
|
def communicate(self, command, noreply=False):
|
||||||
|
with self._lock:
|
||||||
|
ack = super().communicate(command)
|
||||||
|
if ack != ACK:
|
||||||
|
raise CommunicationFailedError('no ack received')
|
||||||
|
if noreply:
|
||||||
|
# to be used for changing parameters when needed
|
||||||
|
return None
|
||||||
|
return super().communicate(ENQ)
|
||||||
|
|
||||||
|
|
||||||
|
class Pressure(HasIO, Readable):
|
||||||
|
value = Parameter(unit='mbar')
|
||||||
|
channel = Property('channel number', IntRange(1,2), default=1)
|
||||||
|
|
||||||
|
STATUS_MAP = {
|
||||||
|
'0': (IDLE, ''),
|
||||||
|
'1': (WARN, 'underrange'),
|
||||||
|
'2': (WARN, 'overrange'),
|
||||||
|
'3': (ERROR, 'sensor error'),
|
||||||
|
'4': (ERROR, 'sensor off'),
|
||||||
|
'5': (ERROR, 'no sensor'),
|
||||||
|
'6': (ERROR, 'identification error'),
|
||||||
|
}
|
||||||
|
|
||||||
|
ioClass = IO
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
reply = self.communicate(f'PR{self.channel}')
|
||||||
|
status, strvalue = reply.split(',')
|
||||||
|
self.status = self.STATUS_MAP.get(status, (ERROR, 'bad status'))
|
||||||
|
return float(strvalue)
|
70
frappy_psi/picontrol.py
Normal file
70
frappy_psi/picontrol.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
"""PI control for furnance"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from frappy.core import Writable, Attached, Parameter, FloatRange, Readable, BoolType, ERROR, IDLE
|
||||||
|
|
||||||
|
class PI(Writable):
|
||||||
|
input = Attached(Readable, 'the input module')
|
||||||
|
output = Attached(Writable, 'the output module')
|
||||||
|
relais = Attached(Writable, 'the interlock relais')
|
||||||
|
p = Parameter('proportional term', FloatRange(0), readonly=False)
|
||||||
|
i = Parameter('integral term', FloatRange(0), readonly=False)
|
||||||
|
control_active = Parameter('control flag', BoolType(), readonly=False, default=False)
|
||||||
|
value = Parameter(unit='degC')
|
||||||
|
_lastdiff = None
|
||||||
|
_lasttime = 0
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
if not self.control_active:
|
||||||
|
return
|
||||||
|
self.value = self.input.value
|
||||||
|
self.status = IDLE, 'controlling'
|
||||||
|
now = time.time()
|
||||||
|
deltat = min(10, now-self._lasttime)
|
||||||
|
self._lasttime = now
|
||||||
|
diff = self.target - self.value
|
||||||
|
if self.value > 300:
|
||||||
|
self.write_control_active(False)
|
||||||
|
return
|
||||||
|
if self._lastdiff is None:
|
||||||
|
self._lastdiff = diff
|
||||||
|
deltadiff = diff - self._lastdiff
|
||||||
|
self._lastdiff = diff
|
||||||
|
output = self.output.target
|
||||||
|
output += self.p * deltadiff + self.i * deltat * diff
|
||||||
|
if output > 100:
|
||||||
|
output = 100
|
||||||
|
elif output < 0:
|
||||||
|
output = 0
|
||||||
|
self.output.write_target(output)
|
||||||
|
|
||||||
|
def write_control_active(self, value):
|
||||||
|
if not value:
|
||||||
|
self.output.write_target(0)
|
||||||
|
|
||||||
|
def write_target(self, value):
|
||||||
|
self.control_active = True
|
||||||
|
self.relais.write_target(1)
|
||||||
|
|
||||||
|
|
62
frappy_psi/tdkpower.py
Normal file
62
frappy_psi/tdkpower.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# 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>
|
||||||
|
# Leon Zimmermann <leon.zimmermann@psi.ch>
|
||||||
|
# *****************************************************************************
|
||||||
|
"""Powersupply TDK-Lambda GEN8-400-1P230"""
|
||||||
|
|
||||||
|
from frappy.core import StringIO, Readable, Parameter, FloatRange, Writable, HasIO, BoolType
|
||||||
|
|
||||||
|
class IO(StringIO):
|
||||||
|
end_of_line = ('OK\r', '\r')
|
||||||
|
default_settings = {'baudrate': 9600}
|
||||||
|
|
||||||
|
|
||||||
|
class Power(HasIO, Readable):
|
||||||
|
value = Parameter(datatype=FloatRange(0,3300,unit='W'))
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
reply_volt = self.communicate('MV?')
|
||||||
|
reply_current = self.communicate('MC?')
|
||||||
|
volt = float(reply_volt)
|
||||||
|
current = float(reply_current)
|
||||||
|
return volt*current
|
||||||
|
|
||||||
|
|
||||||
|
class Output(HasIO, Writable):
|
||||||
|
value = Parameter(datatype=FloatRange(0,100,unit='%'))
|
||||||
|
target = Parameter(datatype=FloatRange(0,100,unit='%'))
|
||||||
|
maxvolt = Parameter('voltage at 100%',datatype=FloatRange(0,8,unit='V'),readonly=False)
|
||||||
|
maxcurrent = Parameter('current at 100%',datatype=FloatRange(0,400,unit='A'),readonly=False)
|
||||||
|
output_enable = Parameter('control on/off', BoolType(), readonly=False)
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.write_output_enable(False)
|
||||||
|
|
||||||
|
def write_target(self, target):
|
||||||
|
self.write_output_enable(target != 0)
|
||||||
|
self.communicate(f'PV {target*self.maxvolt:.5f}')
|
||||||
|
self.communicate(f'PC {target*self.maxcurrent:.5f}')
|
||||||
|
self.value = target
|
||||||
|
|
||||||
|
def write_output_enable(self, value):
|
||||||
|
self.communicate(f'OUT {int(value)}')
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.write_target(0)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user