From 4a2ce62dd88f07c7875bf3a3ab5b5e520b5b79e7 Mon Sep 17 00:00:00 2001 From: LIN SE rpi1 Date: Thu, 11 Jan 2024 16:28:06 +0100 Subject: [PATCH] added drivers for small furnace --- frappy_psi/bkpower.py | 67 +++++++++++++++++++++++ frappy_psi/furnace.py | 49 +++++++++++++++++ frappy_psi/ionopimax.py | 116 ++++++++++++++++++++++++++++++++++++++++ frappy_psi/pfeiffer.py | 64 ++++++++++++++++++++++ frappy_psi/picontrol.py | 70 ++++++++++++++++++++++++ frappy_psi/tdkpower.py | 62 +++++++++++++++++++++ 6 files changed, 428 insertions(+) create mode 100644 frappy_psi/bkpower.py create mode 100644 frappy_psi/furnace.py create mode 100644 frappy_psi/ionopimax.py create mode 100644 frappy_psi/pfeiffer.py create mode 100644 frappy_psi/picontrol.py create mode 100644 frappy_psi/tdkpower.py diff --git a/frappy_psi/bkpower.py b/frappy_psi/bkpower.py new file mode 100644 index 0000000..2171ab8 --- /dev/null +++ b/frappy_psi/bkpower.py @@ -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 +# Jael Celia Lorenzana +# ***************************************************************************** +"""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) + diff --git a/frappy_psi/furnace.py b/frappy_psi/furnace.py new file mode 100644 index 0000000..6db7e3f --- /dev/null +++ b/frappy_psi/furnace.py @@ -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 +# ***************************************************************************** + +"""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) + + \ No newline at end of file diff --git a/frappy_psi/ionopimax.py b/frappy_psi/ionopimax.py new file mode 100644 index 0000000..b3d8bbf --- /dev/null +++ b/frappy_psi/ionopimax.py @@ -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 +# Jael Celia Lorenzana +# ***************************************************************************** + +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') + + diff --git a/frappy_psi/pfeiffer.py b/frappy_psi/pfeiffer.py new file mode 100644 index 0000000..2511ef4 --- /dev/null +++ b/frappy_psi/pfeiffer.py @@ -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 +# ***************************************************************************** +"""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) diff --git a/frappy_psi/picontrol.py b/frappy_psi/picontrol.py new file mode 100644 index 0000000..f754a98 --- /dev/null +++ b/frappy_psi/picontrol.py @@ -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 +# Jael Celia Lorenzana +# ***************************************************************************** + +"""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) + + \ No newline at end of file diff --git a/frappy_psi/tdkpower.py b/frappy_psi/tdkpower.py new file mode 100644 index 0000000..64cad5c --- /dev/null +++ b/frappy_psi/tdkpower.py @@ -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 +# Leon Zimmermann +# ***************************************************************************** +"""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) +