Add DIL5 Statemachine and LOGO
This commit is contained in:
parent
3bae6f8d7f
commit
c49f15ce64
317
cfg/dil5_statemachine_cfg.py
Normal file
317
cfg/dil5_statemachine_cfg.py
Normal file
@ -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
|
||||
)
|
||||
|
||||
|
300
frappy_psi/dilution_statemachine.py
Normal file
300
frappy_psi/dilution_statemachine.py
Normal file
@ -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 <andrea.plank@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
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')
|
||||
|
||||
|
262
frappy_psi/logo.py
Normal file
262
frappy_psi/logo.py
Normal file
@ -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, ''
|
||||
|
||||
|
||||
|
||||
|
38
frappy_psi/manual_valves.py
Normal file
38
frappy_psi/manual_valves.py
Normal file
@ -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 <andrea.plank@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
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, ''
|
189
frappy_psi/pfeiffer_new.py
Normal file
189
frappy_psi/pfeiffer_new.py
Normal file
@ -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'
|
Loading…
x
Reference in New Issue
Block a user