From c49f15ce64e4898a82dbc927f2a83147318de0cf Mon Sep 17 00:00:00 2001 From: Andrea Plank Date: Fri, 9 May 2025 10:33:47 +0200 Subject: [PATCH] Add DIL5 Statemachine and LOGO --- cfg/dil5_statemachine_cfg.py | 317 ++++++++++++++++++++++++++++ frappy_psi/dilution_statemachine.py | 300 ++++++++++++++++++++++++++ frappy_psi/logo.py | 262 +++++++++++++++++++++++ frappy_psi/manual_valves.py | 38 ++++ frappy_psi/pfeiffer_new.py | 189 +++++++++++++++++ 5 files changed, 1106 insertions(+) create mode 100644 cfg/dil5_statemachine_cfg.py create mode 100644 frappy_psi/dilution_statemachine.py create mode 100644 frappy_psi/logo.py create mode 100644 frappy_psi/manual_valves.py create mode 100644 frappy_psi/pfeiffer_new.py diff --git a/cfg/dil5_statemachine_cfg.py b/cfg/dil5_statemachine_cfg.py new file mode 100644 index 0000000..ba166a4 --- /dev/null +++ b/cfg/dil5_statemachine_cfg.py @@ -0,0 +1,317 @@ + +Node('LOGO.psi.ch', + 'LOGO', + interface='tcp://5010', + secondary = ['ws://8010'] +) + +Mod('io', + 'frappy_psi.logo.IO', + '', + ip_address = "192.168.0.3", + tcap_client = 0x3000, + tsap_server = 0x2000 +) + +Mod('V1', + 'frappy_psi.logo.Valve', + 'Valves', + io = 'io', + vm_address_input ="V1025.0", + vm_address_output ="V1064.3" +) + +Mod('V2', + 'frappy_psi.logo.Valve', + 'Valves', + io = 'io', + vm_address_input ="V1024.2", + vm_address_output ="V1064.5" +) + +Mod('V4', + 'frappy_psi.logo.Valve', + 'Valves', + io = 'io', + vm_address_input ="V1024.5", + vm_address_output ="V1064.5" +) + +Mod('V5', + 'frappy_psi.logo.Valve', + 'Valves', + io = 'io', + vm_address_input ="V1024.4", + vm_address_output ="V1064.2" +) + +Mod('V9', + 'frappy_psi.logo.Valve', + 'Valves', + io = 'io', + vm_address_input ="V1024.3", + vm_address_output ="V404.1" +) + +Mod('pump', + 'frappy_psi.logo.FluidMachines', + 'Pump', + io = 'io', + vm_address_output ="V414.1" +) + +Mod('compressor', + 'frappy_psi.logo.FluidMachines', + 'Compressor', + io = 'io', + vm_address_output ="V400.1" +) + +Mod('p2', + 'frappy_psi.logo.Pressure', + 'Pressure in mBar', + io = 'io', + vm_address ="VW0", +) + +Mod('p1', + 'frappy_psi.logo.Pressure', + 'Pressure in mBar', + io = 'io', + vm_address ="VW2", +) + +Mod('p5', + 'frappy_psi.logo.Pressure', + 'Pressure in mBar', + io = 'io', + vm_address ="VW4", +) + +Mod('Druckluft', + 'frappy_psi.logo.Airpressure', + 'Airpressure state', + io = 'io', + vm_address ="VW6", +) + + + +Mod('SF1', + 'frappy_psi.logo.safetyfeatureState', + 'Safety Feature', + io = 'io', + vm_address ="V410.1", +) + +Mod('SF2', + 'frappy_psi.logo.safetyfeatureState', + 'Safety Feature', + io = 'io', + vm_address ="V406.1", +) + +Mod('SF3', + 'frappy_psi.logo.safetyfeatureState', + 'Safety Feature', + io = 'io', + vm_address ="V408.1", +) + +Mod('SF4', + 'frappy_psi.logo.safetyfeatureState', + 'Safety Feature', + io = 'io', + vm_address ="V412.1", +) + +Mod('p2max', + 'frappy_psi.logo.safetyfeatureParam', + 'Safety Feature Param', + io = 'io', + vm_address ="VW8", +) + +Mod('pcond', + 'frappy_psi.logo.safetyfeatureParam', + 'Safety Feature Param', + io = 'io', + vm_address ="VW10", +) + +Mod('p5min', + 'frappy_psi.logo.safetyfeatureParam', + 'Safety Feature Param', + io = 'io', + vm_address ="VW12", +) + +Mod('p5max', + 'frappy_psi.logo.safetyfeatureParam', + 'Safety Feature Param', + io = 'io', + vm_address ="VW14", +) + +""" +Mod('io_ls273', + 'frappy_psi.ls372.StringIO', + 'io for Ls372', + uri = 'localhost:2089', + ) +Mod('sw', + 'frappy_psi.ls372.Switcher', + 'channel switcher', + io = 'io_ls273', +) +Mod('res1', + 'frappy_psi.ls372.ResChannel', + 'resistivity chan 1', + vexc = '2mV', + channel = 1, + switcher = 'sw', +) +""" + +Mod('io_pfeiffer', + 'frappy_psi.pfeiffer_new.PfeifferProtocol', + '', + uri='serial:///dev/ttyUSB0?baudrate=9600+parity=none+bytesize=8+stopbits=1', +) + +Mod('io_turbo', + 'frappy_psi.pfeiffer_new.PfeifferProtocol', + '', + uri='serial:///dev/ttyUSB1?baudrate=9600+parity=none+bytesize=8+stopbits=1', +) + +Mod('p3', + 'frappy_psi.pfeiffer_new.RPT200', + 'Pressure in HPa', + io = 'io_pfeiffer', + address= 2, +) + +Mod('p4', + 'frappy_psi.pfeiffer_new.RPT200', + 'Pressure in HPa', + io = 'io_pfeiffer', + address= 4 +) + +Mod('turbopump', + 'frappy_psi.pfeiffer_new.TCP400', + 'Pfeiffer Turbopump', + io = 'io_turbo', + address= 1 +) + +Mod('MV10', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV10' +) + +Mod('MV13', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV13' +) + +Mod('MV8', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV8' +) + +Mod('MVB', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MVB' +) + +Mod('MV2', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV2' +) + +Mod('MV1', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV1' +) + + +Mod('MV3a', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV3a' +) + +Mod('MV3b', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV3b' +) + +Mod('GV1', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve GV1' +) + +Mod('GV2', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve GV2' +) + +Mod('MV14', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV14' +) + +Mod('MV12', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV12' +) + +Mod('MV11', + + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV11' +) + +Mod('MV9', + 'frappy_psi.manual_valves.ManualValve', + 'Manual Valve MV9' +) + +Mod('stateMachine', + 'frappy_psi.dilution_statemachine.DIL5', + 'Statemachine', + + condenseline_pressure = "p2", + condense_valve = "V9", + dump_valve = "V4", + circulate_pump = "pump", + compressor = "compressor", + turbopump = "turbopump", + condenseline_valve = "V1", + circuitshort_valve = "V2", + still_pressure = "p3", + #ls372 = "res1", + V5 = "V5", + p1 = "p1", + + MV10 = 'MV10', + MV13 ='MV13', + MV8 = 'MV8', + MVB = 'MVB', + MV2 = 'MV2', + + MV1 = 'MV1', + MV3a = 'MV3a', + MV3b = 'MV3b', + GV1 = 'GV1', + MV14 = 'MV14', + MV12 = 'MV12', + MV11 = 'MV11', + MV9 = 'MV9', + GV2 = 'GV2', + condensing_p_low = 150, + condensing_p_high = 250 +) + + diff --git a/frappy_psi/dilution_statemachine.py b/frappy_psi/dilution_statemachine.py new file mode 100644 index 0000000..6f150fa --- /dev/null +++ b/frappy_psi/dilution_statemachine.py @@ -0,0 +1,300 @@ +# ***************************************************************************** +# +# 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: +# Andrea Plank +# +# ***************************************************************************** + +from frappy.core import Drivable, Parameter, EnumType, Attached, FloatRange, \ + Command, IDLE, BUSY, WARN, ERROR, Property +from frappy.datatypes import StatusType, EnumType, ArrayOf, BoolType, IntRange +from frappy.states import StateMachine, Retry, Finish, status_code, HasStates +from frappy.lib.enum import Enum +from frappy.errors import ImpossibleError +import time +Targetstates = Enum( + SORBPUMP = 0, + CONDENSE = 1, + CIRCULATE = 2, + REMOVE = 3, + MANUAL = 4, + TEST = 5, + STOP = 6, +) + +class Dilution(HasStates, Drivable): + + condenseline_pressure = Attached() + condense_valve = Attached() + dump_valve = Attached() + + circulate_pump = Attached() + compressor = Attached(mandatory=(False)) + turbopump = Attached(mandatory=(False)) + condenseline_valve = Attached() + circuitshort_valve = Attached() + still_pressure = Attached() + #ls372 = Attached() + V5 = Attached() #Name noch ändern!!! + p1 = Attached() #Name noch ändern!!! + + condensing_p_low = Property('Lower limit for condenseline pressure', IntRange()) + + condensing_p_high = Property('Lower limit for condenseline pressure', IntRange()) + + target = Parameter('target state', EnumType(Targetstates)) + + value = Parameter('target state', EnumType(Targetstates)) + + init = True + + def earlyInit(self): + super().earlyInit() + + def read_value(self): + return self.value + + def write_target(self, target): + """ + if (target == Targetstates.SORBPUMP): + if self.value == target: + return self.target + self.start_machine(self.sorbpump) + self.value = Targetstates.SORBPUMP + return self.value + """ + if (target == Targetstates.TEST): + self.value = Targetstates.TEST + self.init = True + self.start_machine(self.test) + + if (target == Targetstates.REMOVE): + if self.value == target: + return target + if self.value != Teststates.CIRCULATE: + self.final_status(WARN, "state before is not circulate") + return self.value + self.value = Targetstates.REMOVE + self.init = True + self.start_machine(self.remove) + + elif (target == Targetstates.CIRCULATE): + if self.value == target: + return target + self.value = Targetstates.CIRCULATE + self.init = True + self.start_machine(self.circulate) + + elif (target == Targetstates.CONDENSE): + if self.value == target: + return target + self.value = Targetstates.CONDENSE + self.init = True + self.start_machine(self.condense) + + elif(target == Targetstates.MANUAL): + self.value = Targetstates.MANUAL + self.stop_machine() + + elif (target == Targetstates.STOP): + self.value = Targetstates.STOP + self.stop_machine() + return self.value + + """ + @status_code(BUSY, 'sorbpump state') + def sorbpump(self, state): + #Heizt Tsorb auf und wartet ab. + if self.init: + self.ls372.write_target(40) #Setze Tsorb auf 40K + self.start_time = self.now + self.init = false + return Retry + + if self.now - self.start_time < 2400: # 40 Minuten warten + return Retry + + self.ls372.write_target(0) + + if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K + return Retry + + return self.condense + """ + + @status_code(BUSY, 'test mode') + def test(self, state): + "Nur zum testen, ob UI funktioniert" + self.init = False + self.condense_valve.write_target(1) + time.sleep(1) + self.condense_valve.write_target(0) + self.dump_valve.write_target(1) + time.sleep(1) + self.dump_valve.write_target(0) + self.compressor.write_target(1) + return True + + @status_code(BUSY, 'condense mode') + def wait_for_condense_line_pressure(self, state): + if (self.condenseline_pressure.read_value > 500): + return Retry + + return self.circulate + + + def initialize_condense_valves(self): + return True + + @status_code(BUSY, 'condense state') + def condense(self, state): + """Führt das Kondensationsverfahren durch.""" + if self.init: + self.initialize_condense_valves() + self.circuitshort_valve.write_target(0) + self.dump_valve.write_target(0) + self.condense_valve.write_target(0) + + self.condenseline_valve.write_target(1) + self.V5.write_target(1) + + if (self.compressor is not None): + self.compressor.write_target(1) + + self.circulate_pump.write_target(1) + self.init = False + return Retry + + if self.condenseline_pressure.read_value() < self.condensing_p_low: + self.condense_valve.write_target(1) + elif (self.condenseline_pressure.read_value() > self.condensing_p_high): + self.condense_valve.write_target(0) + + if (self.p1.read_value() > 20): + return Retry + + self.condense_valve.write_target(1) + + if (self.turbopump is not None): + if (self.condenseline_pressure.read_value() > 900 and self.still_pressure.read_value() > 10): + return Retry + else: + self.turbopump.write_target(1) + + return self.wait_for_condense_line_pressure + + + def initialize_circulation_valves(self): + return True + + @status_code(BUSY, 'circulate state') + def circulate(self): + """Zirkuliert die Mischung.""" + return self.initialize_circulation_valves() + + + @status_code(BUSY, 'remove state') + def remove(self): + """Entfernt die Mischung.""" + + if self.init: + self.condenseline_valve.write_target(0) + self.dump_valve.write_target(1) + self.start_time = self.now + self.init = False + return Retry + + if self.turbopump is not None: + self.turbopump.write_target(0) + + if (self.now - self.start_time < 300 or self.turbopump.read_speed() > 60): + return Retry + + self.circuitshort_valve.write_target(1) + + if self.turbopump is not None: + if self.still_pressure.read_value() > 20: + return Retry + self.turbopump.write_target(1) + + if self.still_pressure.read_value() > 1e-4: + return Retry + + self.circuitshort_valve.write_target(0) + self.dump_valve.write_target(0) + + if self.compressor is not None: + self.compressor.write_target(0) + + for valve in self.remove_closed_valves: + valve.write_target(0) + + self.circulate_pump.write_target(0) + + return Finish + +class DIL5(Dilution): + + MV10 = Attached() + MV13 = Attached() + MV8 = Attached() + MVB = Attached() + MV2 = Attached() + MV1 = Attached() + MV3a = Attached() + MV3b = Attached() + GV1 = Attached() + MV14 = Attached() + MV12 = Attached() + MV11 = Attached() + MV9 = Attached() + GV2 = Attached() + + def earlyInit(self): + self.circulate_closed_valves = [self.condense_valve, self.dump_valve, self.circuitshort_valve, self.MV10, self.MV13, self.MV8, self.MVB, self.MV2] + self.circulate_open_valves = [self.MV11, self.circulate_pump, self.GV2, self.V5, self.compressor, self.condenseline_valve, self.MV1, self.MV3a, self.MV3b, self.GV1, self.MV9, self.MV14] + self.condense_closed_valves = [self.MV10, self.MV13, self.MV8, self.MVB, self.MV2] + self.condense_open_valves = [self.MV1, self.MV3a, self.MV3b, self.GV1, self.MV9, self.MV14, self.MV12, self.MV11] + super().earlyInit() + + def initialize_condense_valves(self): + #Anfangszustand der Ventile überprüfen + for valve in self.condense_open_valves: + if valve.read_value() == 0: + self.stop_machine() + raise ImpossibleError(f'valve {valve.name} must be open') + + for valve in self.condense_closed_valves: + if valve.read_value == 1: + self.stop_machine() + return ImpossibleError(f'valve {valve.name} must be closed') + + def initialize_circulation_valves(self): + #Anfangszustand der Ventile überprüfen + self.value = Targetstates.CIRCULATE + for valve in self.circulate_closed_valves: + if (valve.read_value() == 1): + self.stop_machine() + raise ImpossibleError(f'valve {valve.name} must be open') + + for valve in self.circulate_open_valves: + if (valve.read_value() == 0): + valve.write_target(1) + self.stop_machine() + raise ImpossibleError(f'valve {valve.name} must be open') + + diff --git a/frappy_psi/logo.py b/frappy_psi/logo.py new file mode 100644 index 0000000..71d9439 --- /dev/null +++ b/frappy_psi/logo.py @@ -0,0 +1,262 @@ +# ***************************************************************************** +# +# 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 +# +# +# +# ***************************************************************************** +from ast import literal_eval +import snap7 +from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType,IDLE, BUSY, WARN, ERROR,Writable, Drivable, BoolType, IntRange, Communicator +from frappy.errors import CommunicationFailedError +from threading import RLock +import sys +import time + +class IO(Communicator): + + + tcap_client = Property('tcap_client', IntRange()) + tsap_server = Property('tcap_server', IntRange()) + ip_address = Property('numeric ip address', StringType()) + _plc = None + _last_try = 0 + + def initModule(self): + self._lock = RLock() + super().initModule() + def _init(self): + if not self._plc: + if time.time() < self._last_try + 10: + raise CommunicationFailedError('logo PLC not reachable') + self._plc = snap7.logo.Logo() + prev_stderr = sys.stdout + sys.stderr = open('/dev/null', 'w') # suppress output of snap7 + try: + self._plc.connect(self.ip_address, self.tcap_client, self.tsap_server) + if self._plc.get_connected(): + return + except Exception: + pass + finally: + sys.stderr = prev_stderr + self._plc = None + self._last_try = time.time() + raise CommunicationFailedError('logo PLC not reachable') + + + + def communicate(self, cmd): + with self._lock: + self._init() + cmd = cmd.split(maxsplit=1) + if len(cmd) == 2: + self._plc.write(cmd[0], literal_eval(cmd[1])) + try: + return self._plc.read(cmd[0]) + except Exception as e: + if self._plc: + self.log.exception('error in plc read') + self._plc = None + raise + + + +class Snap7Mixin(HasIO): + ioclass = IO + + def get_vm_value(self, vm_address): + return self.io.communicate(vm_address) + + + def set_vm_value(self, vm_address, value): + return self.io.communicate(f'{vm_address} {value}') + +class Pressure(Snap7Mixin, Readable): + vm_address = Property('VM address', datatype= StringType()) + value = Parameter('pressure', datatype = FloatRange(unit = 'mbar')) + + #pollinterval = 0.5 + + def read_value(self): + return self.get_vm_value(self.vm_address) + + def read_status(self): + return IDLE, '' + + +class Airpressure(Snap7Mixin, Readable): + vm_address = Property('VM address', datatype= StringType()) + value = Parameter('airpressure state', datatype = BoolType()) + + #pollinterval = 0.5 + + def read_value(self): + if (self.get_vm_value(self.vm_address) > 500): + return 1 + else: + return 0 + + def read_status(self): + return IDLE, '' + +class Valve(Snap7Mixin, Drivable): + vm_address_input = Property('VM address input', datatype= StringType()) + vm_address_output = Property('VM address output', datatype= StringType()) + + target = Parameter('Valve target', datatype = BoolType()) + value = Parameter('Value state', datatype = BoolType()) + _remaining_tries = None + + def read_value(self): + return self.get_vm_value(self.vm_address_input) + + def write_target(self, target): + self.set_vm_value(self.vm_address_output, target) + self._remaining_tries = 5 + self.status = BUSY, 'switching' + self.setFastPoll(True, 0.001) + + def read_status(self): + self.log.info('read_status') + value = self.read_value() + self.log.info('value %d target %d', value, self.target) + if value != self.target: + if self._remaining_tries is None: + self.target = self.read_value() + return IDLE,'' + self._remaining_tries -= 1 + if self._remaining_tries < 0: + self.setFastPoll(False) + return ERROR, 'too many tries to switch' + self.set_vm_value(self.vm_address_output, self.target) + return BUSY, 'switching (try again)' + self.setFastPoll(False) + return IDLE, '' + +class FluidMachines(Snap7Mixin, Drivable): + vm_address_output = Property('VM address output', datatype= StringType()) + + target = Parameter('Valve target', datatype = BoolType()) + value = Parameter('Value state', datatype = BoolType()) + + def read_value(self): + return self.get_vm_value(self.vm_address_output) + + def write_target(self, target): + return self.set_vm_value(self.vm_address_output, target) + + def read_status(self): + return IDLE, '' + +class TempSensor(Snap7Mixin, Readable): + vm_address = Property('VM address', datatype= StringType()) + value = Parameter('resistance', datatype = FloatRange(unit = 'Ohm')) + + + def read_value(self): + return self.get_vm_value(self.vm_address) + + def read_status(self): + return IDLE, '' + +class HeaterParam(Snap7Mixin, Writable): + vm_address = Property('VM address output', datatype= StringType()) + + target = Parameter('Heater target', datatype = IntRange()) + + value = Parameter('Heater Param', datatype = IntRange()) + + def read_value(self): + return self.get_vm_value(self.vm_address) + + def write_target(self, target): + return self.set_vm_value(self.vm_address, target) + + def read_status(self): + return IDLE, '' + + +class controlHeater(Snap7Mixin, Writable): + + vm_address = Property('VM address on switch', datatype= StringType()) + + target = Parameter('Heater state', datatype = BoolType()) + + value = Parameter('Heater state', datatype = BoolType()) + + def read_value(self): + return self.get_vm_value(self.vm_address_on) + + def write_target(self, target): + if (target): + return self.set_vm_value(self.vm_address, True) + else: + return self.set_vm_value(self.vm_address, False) + + def read_status(self): + return IDLE, '' + + +class safetyfeatureState(Snap7Mixin, Readable): + + vm_address = Property('VM address state', datatype= StringType()) + + value = Parameter('safety Feature state', datatype = BoolType()) + + def read_value(self): + return self.get_vm_value(self.vm_address) + + def read_status(self): + return IDLE, '' + + +class safetyfeatureParam(Snap7Mixin, Writable): + vm_address = Property('VM address output', datatype= StringType()) + + target = Parameter('safety Feature target', datatype = IntRange()) + + value = Parameter('safety Feature Param', datatype = IntRange()) + + def read_value(self): + return self.get_vm_value(self.vm_address) + + def write_target(self, target): + return self.set_vm_value(self.vm_address, target) + + def read_status(self): + return IDLE, '' + + +class comparatorgekoppeltParam(Snap7Mixin, Writable): + vm_address_1 = Property('VM address output', datatype= StringType()) + vm_address_2 = Property('VM address output', datatype= StringType()) + + target = Parameter('safety Feature target', datatype = IntRange()) + value = Parameter('safety Feature Param', datatype = IntRange()) + + def read_value(self): + return self.get_vm_value(self.vm_address_1) + + def write_target(self, target): + self.set_vm_value(self.vm_address_1, target) + return self.set_vm_value(self.vm_address_2, target) + + def read_status(self): + return IDLE, '' + + + + diff --git a/frappy_psi/manual_valves.py b/frappy_psi/manual_valves.py new file mode 100644 index 0000000..6624068 --- /dev/null +++ b/frappy_psi/manual_valves.py @@ -0,0 +1,38 @@ +# ***************************************************************************** +# +# 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: +# Andrea Plank +# +# ***************************************************************************** + +from frappy.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO, \ + Property, StringType, Writable, IntRange, IDLE, BUSY, ERROR +from frappy.errors import CommunicationFailedError + +class ManualValve(Writable): + target = Parameter('Valve target', datatype = BoolType()) + value = Parameter('Valve state', datatype = BoolType()) + + def read_value(self): + return self.value + + def write_target(self, target): + self.value = target + return self.value + + def read_status(self): + return IDLE, '' diff --git a/frappy_psi/pfeiffer_new.py b/frappy_psi/pfeiffer_new.py new file mode 100644 index 0000000..2a9e805 --- /dev/null +++ b/frappy_psi/pfeiffer_new.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Apr 29 09:24:07 2024 +@author: andreaplank +""" +from frappy.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO, \ + Property, StringType, Drivable, IntRange, IDLE, BUSY, ERROR, nopoll +from frappy.errors import CommunicationFailedError + + +class PfeifferProtocol(StringIO): + end_of_line = '\r' + +class PfeifferMixin(HasIO): + ioClass = PfeifferProtocol + address= Property('Addresse', datatype= IntRange()) + + def calculate_crc(self, data): + crc = sum(ord(chr) for chr in data) % 256 + return f'{crc:03d}' + + def check_crc(self, data): + if data [-3:] != self.calculate_crc(data[:-3]): + raise CommunicationFailedError('Bad crc') + + def data_request_u_expo_new(self, parameter_nr): + cmd = f'{self.address:03d}00{parameter_nr:03d}02=?' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + + + assert int(reply[5:8]) == parameter_nr + + assert int(reply[0:3]) == self.address + + try: + exponent = int(reply[14:16])-23 + except ValueError: + raise CommunicationFailedError(f'got {reply[10:16]}') + + + return float(f'{reply[10:14]}e{exponent}') + + def data_request_old_boolean(self, parameter_nr): + + + cmd = f'{self.address:03d}00{parameter_nr:03d}02=?' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + assert int(reply[5:8]) == parameter_nr, f"Parameter number mismatch: expected {parameter_nr}, got {int(reply[5:8])}" + + assert int(reply[0:3]) == self.address, f"Address mismatch: expected {self.address}, got {int(reply[0:3])}" + + + if reply[12] == "1": + value = True + elif reply[12] == "0": + value = False + else: + raise CommunicationFailedError(f'got {reply[10:16]}') + + return value + + def data_request_u_real(self, parameter_nr): + cmd = f'{self.address:03d}00{parameter_nr:03d}02=?' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + + assert int(reply[5:8]) == parameter_nr + + assert int(reply[0:3]) == self.address + + try: + value = float(reply[10:16])/100 + except ValueError: + raise CommunicationFailedError(f'got {reply[10:16]}') + + return value + + + def data_request_u_int(self, parameter_nr): + cmd = f'{self.address:03d}00{parameter_nr:03d}02=?' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + + if reply[8] == "0": + reply_length = (int)(reply[9]) + else: + reply_length = (int)(reply[8:10]) + + try: + if reply[10 : 10 + reply_length] == "000000": + value = 0 + else: + value = float(reply[10 : 10 + reply_length].lstrip("0")) + except ValueError: + raise CommunicationFailedError(f'got {reply[10:16]}') + + return value + + def data_request_string(self, parameter_nr): + cmd = f'{self.address:03d}00{parameter_nr:03d}02=?' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + + assert int(reply[5:8]) == parameter_nr + + assert int(reply[0:3]) == self.address + + return str(reply[10:16]) + + + def control_old_boolean(self, parameter_nr, target): + if target: + val = 1 + else: + val = 0 + + cmd = f'{self.address:03d}10{parameter_nr:03d}06{str(val)*6}' + cmd += self.calculate_crc(cmd) + + reply = self.communicate(cmd) + self.check_crc(reply) + + assert cmd == reply, f'got {reply} instead of {cmd} ' + + try: + if reply[11] == "1": + value = 1 + else: + value = 0 + except ValueError: + + raise CommunicationFailedError(f'got {reply[10:16]}') + + return value + +class RPT200(PfeifferMixin, Readable): + value = Parameter('Pressure', FloatRange(unit='hPa')) + + def read_value(self): + return self.data_request_u_expo_new(740) + def read_status(self): + errtxt = self.data_request_string(303) + if errtxt == "000000": + return IDLE, '' + else: + return ERROR, errtxt + +class TCP400(PfeifferMixin, Drivable, Readable): + speed= Parameter('Rotational speed', FloatRange(unit = 'Hz'), readonly = False) + target= Parameter('Pumping station', BoolType()) + current= Parameter('Current consumption', FloatRange(unit = '%')) + value = Parameter('Turbopump state', BoolType()) + temp = Parameter('temp', FloatRange(unit = 'C')) + def read_temp (self): + return self.data_request_u_int(326) + + def read_speed(self): + return self.data_request_u_int(309) + + def read_value(self): + return self.data_request_old_boolean(10) + + def read_current(self): + return self.data_request_u_real(310) + + def write_target(self, target): + return self.control_old_boolean(10, target) + + def read_target(self): + return self.data_request_old_boolean(10) + + def read_status(self): + if not self.data_request_old_boolean(306): + return BUSY, 'ramping up' + else: + return IDLE,'at targetspeed'