[WIP] dil5 improvements
Change-Id: I2b439bf5898601e10448511479bc67afa3edb4d3
This commit is contained in:
407
frappy_psi/dilution.py
Normal file
407
frappy_psi/dilution.py
Normal file
@ -0,0 +1,407 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import time
|
||||
from frappy.core import Readable, Drivable, Parameter, Attached, FloatRange, \
|
||||
Command, IDLE, BUSY, WARN, ERROR, Property
|
||||
from frappy.datatypes import EnumType, IntRange, BoolType, StructOf, StringType
|
||||
from frappy.states import Retry, Finish, status_code, HasStates
|
||||
from frappy.lib.enum import Enum
|
||||
from frappy.errors import ImpossibleError, HardwareError
|
||||
from frappy.addrparam import AddrParam, AddrMixin
|
||||
from frappy.lib import formatStatusBits
|
||||
from frappy.persistent import PersistentMixin, PersistentParam
|
||||
from frappy_psi.logo import LogoMixin, DigitalActuator
|
||||
|
||||
T = Enum( # target states
|
||||
off = 0,
|
||||
sorbpumped = 2,
|
||||
condense = 5,
|
||||
remove = 7,
|
||||
remove_and_sorbpump = 9,
|
||||
remove_and_condense = 10,
|
||||
manual = 11,
|
||||
test = 12,
|
||||
)
|
||||
|
||||
V = Enum(T, # value status inherits from target status
|
||||
sorbpumping=1,
|
||||
condensing=4,
|
||||
circulating=6,
|
||||
removing=8,
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
value = Parameter('current state', EnumType(T), default=0)
|
||||
target = Parameter('target state', EnumType(T), default=0)
|
||||
|
||||
sorbpumped = Parameter('sorb pump done', BoolType(), default=False)
|
||||
|
||||
#ls372 = Attached()
|
||||
V5 = Attached() #Name noch ändern!!!
|
||||
p1 = Attached() #Name noch ändern!!!
|
||||
|
||||
condensing_p_low = Parameter('Lower limit for condenseline pressure', FloatRange(unit='mbar'))
|
||||
condensing_p_high = Parameter('Higher limit for condenseline pressure', FloatRange(unit='mbar'))
|
||||
dump_target = Parameter('low dump pressure limit indicating end of condensation phase',
|
||||
FloatRange(unit='mbar'), default=20)
|
||||
end_condense_pressure = Parameter('low condense pressure indicating end of condensation phase',
|
||||
FloatRange(unit='mbar'), default=500)
|
||||
turbo_condense_pressure = Parameter('low condense pressure before turbo start',
|
||||
FloatRange(unit='mbar'), default=900)
|
||||
turbo_still_pressure = Parameter('low still pressure before turbo start',
|
||||
FloatRange(unit='mbar'), default=10)
|
||||
turbo_off_delay = Parameter('wait time after switching turbo off',
|
||||
FloatRange(unit='s'), default=300)
|
||||
turbo_off_speed = Parameter('speed to wait for after switching turbo off',
|
||||
FloatRange(unit='s'), default=60)
|
||||
end_remove_still_pressure = Parameter('pressure reached before end of remove',
|
||||
FloatRange(unit='mbar'), default=1e-4)
|
||||
st = StringType()
|
||||
valve_set = StructOf(close=st, open=st, check_open=st, check_closed=st)
|
||||
condense_valves = Parameter('valve to act when condensing', valve_set)
|
||||
valves_after_remove = Parameter('valve to act after remove', valve_set)
|
||||
check_after_remove = Parameter('check for manual valves after remove', valve_set)
|
||||
_start_time = 0
|
||||
init = True
|
||||
_warn_manual_work = None
|
||||
|
||||
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 self.value == self.target:
|
||||
return target # not sure if this is correct. may be a step wants to be repeated?
|
||||
|
||||
self.start_machine(getattr(self, target.name, None))
|
||||
return target
|
||||
|
||||
"""
|
||||
@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, 'start test')
|
||||
def test(self, state):
|
||||
"""Nur zum testen, ob UI funktioniert"""
|
||||
self.init = False
|
||||
if state.init:
|
||||
state._start = state.now
|
||||
return self.wait_test
|
||||
|
||||
@status_code(BUSY)
|
||||
def wait_test(self, state):
|
||||
if state.now < state.start + 20:
|
||||
return Retry
|
||||
return self.final_status(IDLE, 'end test')
|
||||
|
||||
@status_code(BUSY)
|
||||
def condense(self, state):
|
||||
"""Führt das Kondensationsverfahren durch."""
|
||||
if state.init:
|
||||
self.value = V.condensing
|
||||
self.handle_valves(**self.condense_valves)
|
||||
return Retry
|
||||
if self.wait_valves():
|
||||
return Retry
|
||||
self.check_valve_result()
|
||||
return Retry
|
||||
|
||||
@status_code(BUSY)
|
||||
def condensing(self, state):
|
||||
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() > self.dump_target:
|
||||
return Retry
|
||||
|
||||
self.condense_valve.write_target(1)
|
||||
|
||||
if self.turbopump is not None:
|
||||
return self.condense_wait_before_turbo_start
|
||||
|
||||
return self.wait_for_condense_line_pressure
|
||||
|
||||
@status_code(BUSY)
|
||||
def wait_for_condense_line_pressure(self, state):
|
||||
if self.condenseline_pressure.read_value() > self.end_condense_pressure:
|
||||
return Retry
|
||||
self.condense_valve.write_target(0)
|
||||
return self.circulate
|
||||
|
||||
@status_code(BUSY, 'condense (wait before starting turbo)')
|
||||
def condense_wait_before_turbo_start(self, state):
|
||||
if (self.condenseline_pressure.read_value() > self.turbo_condense_pressure
|
||||
and self.still_pressure.read_value() > self.turbo_still_pressure):
|
||||
return Retry
|
||||
self.turbopump.write_target(1)
|
||||
return self.wait_for_condense_line_pressure
|
||||
|
||||
@status_code(BUSY)
|
||||
def circulate(self, state):
|
||||
"""Zirkuliert die Mischung."""
|
||||
if state.init:
|
||||
self.handle_valves(**self.condense_valves)
|
||||
if self.wait_valves():
|
||||
return Retry
|
||||
self.check_valve_result()
|
||||
self.value = V.circulating
|
||||
return Finish
|
||||
|
||||
@status_code(BUSY, 'remove (wait for turbo shut down)')
|
||||
def remove(self, state):
|
||||
"""Entfernt die Mischung."""
|
||||
|
||||
if state.init:
|
||||
self.condenseline_valve.write_target(0)
|
||||
self.dump_valve.write_target(1)
|
||||
if self.turbopump is not None:
|
||||
self._start_time = state.now
|
||||
self.turbopump.write_target(0)
|
||||
return Retry
|
||||
|
||||
if self.turbopump is not None:
|
||||
self.turbopump.write_target(0)
|
||||
|
||||
if (state.now - self._start_time < self.turbo_off_delay
|
||||
or self.turbopump.read_speed() > self.turbo_off_speed):
|
||||
return Retry
|
||||
|
||||
self.circuitshort_valve.write_target(1)
|
||||
|
||||
if self.turbopump is not None:
|
||||
return self.remove_wait_for_still_pressure
|
||||
|
||||
return self.remove_endsequence
|
||||
|
||||
@status_code(BUSY, 'remove (wait for still pressure low)')
|
||||
def remove_wait_for_still_pressure(self, state):
|
||||
if self.still_pressure.read_value() > self.turbo_still_pressure:
|
||||
return Retry
|
||||
self.turbopump.write_target(1)
|
||||
return self.remove_endsequence
|
||||
|
||||
@status_code(BUSY)
|
||||
def remove_endsequence(self, state):
|
||||
if self.still_pressure.read_value() > self.end_remove_still_pressure:
|
||||
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)
|
||||
self.circulate_pump.write_target(0)
|
||||
return self.close_valves_after_remove
|
||||
|
||||
@status_code(BUSY)
|
||||
def close_valves_after_remove(self, state):
|
||||
if state.init:
|
||||
self.handle_valves(**self.valves_after_remove)
|
||||
if self.wait_valves():
|
||||
return Retry
|
||||
self.check_valve_result()
|
||||
self._warn_manual_work = True
|
||||
return self.final_status(WARN, 'please check manual valves')
|
||||
|
||||
def read_status(self):
|
||||
status = super().read_status()
|
||||
if status[0] < ERROR and self._warn_manual_work:
|
||||
try:
|
||||
self.handle_valves(**self.check_after_remove)
|
||||
self._warn_manual_work = False
|
||||
except ImpossibleError:
|
||||
return WARN, f'please close manual valves {",".join(self._valves_failed[False])}'
|
||||
return status
|
||||
|
||||
def handle_valves(self, check_closed=(), check_open=(), close=(), open=()):
|
||||
"""check ot set given valves
|
||||
|
||||
raises ImpossibleError, when checks fails
|
||||
"""
|
||||
self._valves_to_wait_for = {}
|
||||
self._valves_failed = {True: [], False: []}
|
||||
for flag, valves in enumerate([check_closed, check_open]):
|
||||
for vname in valves.split():
|
||||
if self.secNode.modules[vname].read_value() != flag:
|
||||
self._valves_failed[flag].append(vname)
|
||||
for flag, valves in enumerate([close, open]):
|
||||
for vname in valves.split():
|
||||
valve = self.secNode.modules[vname]
|
||||
valve.write_target(flag)
|
||||
if valve.isBusy():
|
||||
self._valves_to_wait_for[vname] = (valve, flag)
|
||||
elif valve.read_value() != flag:
|
||||
self._valves_failed[flag].append(vname)
|
||||
|
||||
def wait_valves(self):
|
||||
busy = False
|
||||
for vname, (valve, flag) in dict(self._valves_to_wait_for.items()):
|
||||
statuscode = valve.read_status()[0]
|
||||
if statuscode == BUSY:
|
||||
busy = True
|
||||
continue
|
||||
if valve.read_value() == flag and statuscode == IDLE:
|
||||
self._valves_to_wait_for.pop(vname)
|
||||
else:
|
||||
self._valves_failed[flag].append(vname)
|
||||
return busy
|
||||
|
||||
def check_valve_result(self):
|
||||
result = []
|
||||
for flag, valves in self._valves_failed.items():
|
||||
if valves:
|
||||
result.append(f"{','.join(valves)} not {'open' if flag else 'closed'}")
|
||||
if result:
|
||||
raise ImpossibleError(f"failed: {', '.join(result)}")
|
||||
|
||||
|
||||
class DIL5(Dilution):
|
||||
condense_valves = {
|
||||
'close': 'V2 V4 V9',
|
||||
'check_closed': 'MV10 MV13 MV8 MVB MV2',
|
||||
'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14',
|
||||
'open': 'V1 V5 compressor pump',
|
||||
}
|
||||
valves_after_remove = {
|
||||
'close': 'V1 V2 V4 V5 V9',
|
||||
'check_closed': 'MV10 MV13 MV8 MVB MV2',
|
||||
'open': '',
|
||||
'check_open': '',
|
||||
}
|
||||
check_after_remove = {
|
||||
'close': '',
|
||||
'check_closed': 'MV1 MV9 MV10 MV11 MV12',
|
||||
'open': '',
|
||||
'check_open': '',
|
||||
}
|
||||
|
||||
|
||||
class Interlock(LogoMixin, AddrMixin, Readable):
|
||||
value = AddrParam('interlock state (bitmap)',
|
||||
IntRange(0, 31), addr='V414', readonly=False)
|
||||
p5lim = AddrParam('safety limit on p5 to protect forepump',
|
||||
FloatRange(), value=1300, addr='VW16 VW18', readonly=False)
|
||||
p2lim = AddrParam('safety limit on p2 to protect compressor',
|
||||
FloatRange(), value=4000, addr='VW8 VW10', readonly=False)
|
||||
p1lim = AddrParam('safety limit to protect dump',
|
||||
FloatRange(), value=1300, addr='VW12 VW14', readonly=False)
|
||||
p2max = AddrParam('limit pn p2 for mechanism to put mix to dump',
|
||||
FloatRange(), value=3000, addr='VW20 VW22', readonly=False)
|
||||
conditions = { # starting with bit 1
|
||||
'off (p5>p5lim)': {'forepump': False},
|
||||
'off (p2>p2lim)': {'compressor': False},
|
||||
'off (p1>p2lim)': {'forepump': False, 'compressor': False},
|
||||
'open (p2>p2max)': {'V4': True}}
|
||||
|
||||
reset_param = Property('addr for reset', StringType(), default='V418.1')
|
||||
_mismatch = None
|
||||
_prefix = ''
|
||||
|
||||
def doPoll(self):
|
||||
self.read_status() # this includes read_value
|
||||
|
||||
def initialReads(self):
|
||||
super().initialReads()
|
||||
self.reset()
|
||||
|
||||
@Command
|
||||
def reset(self):
|
||||
"""reset the interlock"""
|
||||
self._prefix = ''
|
||||
self.set_vm_value(self.reset_param, 1)
|
||||
for actions in self.conditions.values():
|
||||
for mname in actions:
|
||||
self.secNode.modules[mname].reset_fault()
|
||||
if self.read_value() != 0:
|
||||
raise HardwareError('can not clear status byte')
|
||||
self.set_vm_value(self.reset_param, 0)
|
||||
self.read_status() # update status (this may trigger ERROR again)
|
||||
|
||||
def read_status(self):
|
||||
if self._mismatch is None: # init
|
||||
self._mismatch = set()
|
||||
bits = self.read_value()
|
||||
if bits:
|
||||
keys = formatStatusBits(bits, self.conditions, 1)
|
||||
statustext = []
|
||||
for key in keys:
|
||||
actions = self.conditions[key]
|
||||
statustext.append(f"{' and '.join(actions)} {key}")
|
||||
for module, value in actions.items():
|
||||
modobj = self.secNode.modules[module]
|
||||
if modobj.target != value:
|
||||
self._prefix = 'switched '
|
||||
modobj.set_fault(value, f'switched {key}')
|
||||
return ERROR, f"{self._prefix}{', '.join(statustext)}"
|
||||
if self._mismatch:
|
||||
return ERROR, f"mismatch on values for {', '.join(self._mismatch)}"
|
||||
return IDLE, ''
|
||||
|
||||
def addressed_read(self, pobj):
|
||||
values = [self.get_vm_value(a) for a in pobj.addr.split()]
|
||||
if any(v != values[0] for v in values):
|
||||
self._mismatch.add(pobj.name)
|
||||
self.read_status()
|
||||
else:
|
||||
self._mismatch.discard(pobj.name)
|
||||
return values[0]
|
||||
|
||||
def addressed_write(self, pobj, value):
|
||||
for addr in pobj.addr.split():
|
||||
self.set_vm_value(addr, value)
|
||||
self.read_status()
|
||||
|
||||
|
@ -1,320 +0,0 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# 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('Higher limit for condenseline pressure', IntRange())
|
||||
|
||||
target = Parameter('target state', EnumType(Targetstates))
|
||||
|
||||
value = Parameter('current state', EnumType(Targetstates))
|
||||
|
||||
init = True
|
||||
|
||||
|
||||
|
||||
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)
|
||||
def wait_for_condense_line_pressure(self, state):
|
||||
if (self.condenseline_pressure.read_value > 500):
|
||||
return Retry
|
||||
self.condense_valve.write_target(0)
|
||||
return self.circulate
|
||||
|
||||
def initialize_condense_valves(self, state):
|
||||
raise NotImplementedError
|
||||
|
||||
@status_code(BUSY)
|
||||
def condense(self, state):
|
||||
"""Führt das Kondensationsverfahren durch."""
|
||||
if state.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)
|
||||
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):
|
||||
return self.condense_wait_before_turbo_start
|
||||
|
||||
return self.wait_for_condense_line_pressure
|
||||
|
||||
@status_code(BUSY, 'condense (wait before starting turbo)')
|
||||
def condense_wait_before_turbo_start(self, state):
|
||||
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, state):
|
||||
raise NotImplementedError
|
||||
|
||||
@status_code(BUSY)
|
||||
def circulate(self, state):
|
||||
"""Zirkuliert die Mischung."""
|
||||
if state.init:
|
||||
self.initialize_circulation_valves()
|
||||
return Retry
|
||||
|
||||
@status_code(BUSY, 'remove (wait for turbo shut down)')
|
||||
def remove(self, state):
|
||||
"""Entfernt die Mischung."""
|
||||
|
||||
if state.init:
|
||||
self.condenseline_valve.write_target(0)
|
||||
self.dump_valve.write_target(1)
|
||||
self.start_time = self.now
|
||||
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:
|
||||
return self.remove_wait_for_still_pressure
|
||||
|
||||
return remove_endsequence
|
||||
|
||||
@status_code(BUSY, 'remove (wait for still pressure low)')
|
||||
def remove_wait_for_still_pressure(self, state):
|
||||
if self.still_pressure.read_value() > 20:
|
||||
return Retry
|
||||
self.turbopump.write_target(1)
|
||||
return self.remove_endsequence
|
||||
|
||||
@status_code(BUSY)
|
||||
def remove_endsequence(self, state):
|
||||
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)
|
||||
self.remove_check_manual_valves()
|
||||
self.remove_close_valves()
|
||||
self.circulate_pump.write_target(0)
|
||||
return Finish
|
||||
|
||||
def remove_check_manual_valves(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def remove_close_valves(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
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]
|
||||
self.remove_check_closed_valves = [self.MV11, self.MV9, self.MV12, self.MV1]
|
||||
self.remove_closed_valves = [self.condenseline_valve, self.circuitshort_valve, self.V5, self.condense_valve, self.dump_valve]
|
||||
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')
|
||||
|
||||
def remove_check_manual_valves(self):
|
||||
for valve in self.remove_check_closed_valves:
|
||||
if (valve.read_value() == 1):
|
||||
self.final_status(WARN, "manual valve {valve.name} must be closed")
|
||||
|
||||
def remove_close_valves(self):
|
||||
for valve in self.remove_closed_valves:
|
||||
valve.write_target(0)
|
||||
|
@ -17,17 +17,17 @@
|
||||
#
|
||||
#
|
||||
# *****************************************************************************
|
||||
import sys
|
||||
from time import monotonic
|
||||
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 frappy.core import Attached, Command, Readable, Parameter, FloatRange, HasIO, Property, StringType, \
|
||||
IDLE, BUSY, WARN, ERROR, Writable, Drivable, BoolType, IntRange, Communicator, StatusType
|
||||
from frappy.errors import CommunicationFailedError, ConfigError
|
||||
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())
|
||||
@ -37,30 +37,28 @@ class IO(Communicator):
|
||||
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')
|
||||
|
||||
|
||||
|
||||
if monotonic() < self._last_try + 10:
|
||||
raise CommunicationFailedError('logo PLC not reachable')
|
||||
self._plc = snap7.logo.Logo()
|
||||
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 = sys.stdout
|
||||
self._plc = None
|
||||
self._last_try = monotonic()
|
||||
raise CommunicationFailedError('logo PLC not reachable')
|
||||
|
||||
def communicate(self, cmd):
|
||||
with self._lock:
|
||||
self._init()
|
||||
if not self._plc:
|
||||
self._init()
|
||||
cmd = cmd.split(maxsplit=1)
|
||||
if len(cmd) == 2:
|
||||
self.comLog('> %s %s', cmd[0], cmd[1])
|
||||
@ -76,59 +74,203 @@ class IO(Communicator):
|
||||
self.comLog('? %r', e)
|
||||
self.log.exception('error in plc read')
|
||||
self._plc = None
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class Snap7Mixin(HasIO):
|
||||
raise
|
||||
|
||||
|
||||
class LogoMixin(HasIO):
|
||||
ioclass = IO
|
||||
|
||||
|
||||
def get_vm_value(self, vm_address):
|
||||
return literal_eval(self.io.communicate(vm_address))
|
||||
|
||||
|
||||
def set_vm_value(self, vm_address, value):
|
||||
return literal_eval(self.io.communicate(f'{vm_address} {value}'))
|
||||
return literal_eval(self.io.communicate(f'{vm_address} {round(value)}'))
|
||||
|
||||
|
||||
class DigitalActuator(LogoMixin, Writable):
|
||||
"""output with or without feedback"""
|
||||
output_addr = Property('VM address output', datatype=StringType(), default='')
|
||||
target_addr = Property('VM address target', datatype=StringType(), default='')
|
||||
feedback_addr = Property('VM address feedback', datatype=StringType(), default='')
|
||||
target = Parameter('target', datatype=BoolType())
|
||||
value = Parameter('feedback or output', datatype=BoolType())
|
||||
_input = 'output'
|
||||
_value_addr = None
|
||||
_target_addr = None
|
||||
_fault = None
|
||||
|
||||
def doPoll(self):
|
||||
self.read_status() # this calls also read_value
|
||||
|
||||
def checkProperties(self):
|
||||
super().checkProperties()
|
||||
if self.feedback_addr:
|
||||
self._input = 'feedback'
|
||||
self._value_addr = self.feedback_addr
|
||||
else:
|
||||
self._input = 'output'
|
||||
self._value_addr = self.output_addr
|
||||
self._target_addr = self.target_addr or self.output_addr
|
||||
if self._target_addr and self._value_addr:
|
||||
self._check_feedback = self.feedback_addr and self.output_addr
|
||||
return
|
||||
raise ConfigError('need either output_addr or both feedback_addr and target_addr')
|
||||
|
||||
def initialReads(self):
|
||||
super().initialReads()
|
||||
self.target = self.value
|
||||
|
||||
def set_fault(self, value, statustext):
|
||||
"""on a fault condition, set target to value
|
||||
|
||||
and status to statustext
|
||||
"""
|
||||
self.write_target(value)
|
||||
self._fault = statustext
|
||||
self.read_status()
|
||||
|
||||
def reset_fault(self):
|
||||
"""reset fault condition"""
|
||||
self._fault = None
|
||||
self.read_status()
|
||||
|
||||
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)
|
||||
|
||||
return self.get_vm_value(self._value_addr)
|
||||
|
||||
def write_target(self, target):
|
||||
self._fault = None
|
||||
self.set_vm_value(self._target_addr, target)
|
||||
value = self.read_value()
|
||||
if value != target and self.feedback_addr:
|
||||
# retry only if we have a feedback and the feedback did not change yet
|
||||
for i in range(20):
|
||||
if self.read_value() == target:
|
||||
self.log.debug('tried %d times', i)
|
||||
break
|
||||
self.set_vm_value(self._target_addr, target)
|
||||
|
||||
def read_status(self):
|
||||
if self._fault:
|
||||
return ERROR, self._fault
|
||||
value = self.read_value()
|
||||
if value != self.target:
|
||||
return ERROR, 'value and target do not match'
|
||||
if self._check_feedback:
|
||||
if value != self.get_vm_value(self._check_feedback):
|
||||
return ERROR, f'feedback does not match output'
|
||||
if self.feedback_addr:
|
||||
return IDLE, 'feedback confirmed'
|
||||
return IDLE, ''
|
||||
|
||||
|
||||
class DelayedActuator(DigitalActuator, Drivable):
|
||||
delay_addr = Property('address of delay value', StringType())
|
||||
_pulse_start = 0
|
||||
_pulse_end = 0
|
||||
_fault = None
|
||||
|
||||
def read_status(self):
|
||||
if self._fault:
|
||||
return ERROR, self._fault
|
||||
value = self.read_value()
|
||||
fberror = None
|
||||
if self._pulse_start:
|
||||
now = monotonic()
|
||||
if now < self._pulse_start + 1:
|
||||
value = 1
|
||||
elif now < self._pulse_end - 1:
|
||||
if not value:
|
||||
self._pulse_start = 0
|
||||
return WARN, f'{self._input} is not on during pulse - due to interlock?'
|
||||
if value:
|
||||
if now < self._pulse_end + 1:
|
||||
return BUSY, 'pulsing'
|
||||
self.log.warn('pulse timeout')
|
||||
self.set_vm_value(self._target_addr, 0)
|
||||
self._pulse_start = 0
|
||||
self.set_vm_value(self.delay_addr, 0)
|
||||
elif self._check_feedback and value != self.get_vm_value(self._check_feedback):
|
||||
fberror = ERROR, f'feedback does not match output'
|
||||
if value != self.target:
|
||||
return ERROR, 'value does not match target'
|
||||
self.setFastPoll(False)
|
||||
if fberror:
|
||||
return fberror
|
||||
if self.feedback_addr:
|
||||
return IDLE, 'feedback confirmed'
|
||||
return IDLE, ''
|
||||
|
||||
def write_target(self, value):
|
||||
self._pulse_start = 0
|
||||
if not value:
|
||||
self.set_vm_value(self.delay_addr, 0)
|
||||
return super().write_target(value)
|
||||
|
||||
@Command(argument=FloatRange(0))
|
||||
def pulse(self, delay):
|
||||
"""open for delay seconds"""
|
||||
self.set_vm_value(self.delay_addr, delay)
|
||||
self.set_vm_value(self._target_addr, 1)
|
||||
self.set_vm_value(self._target_addr, 0)
|
||||
self.setFastPoll(True, 0.5)
|
||||
self.status = BUSY, 'pulsing'
|
||||
now = monotonic()
|
||||
self._pulse_start = now
|
||||
self._pulse_end = now + delay
|
||||
|
||||
|
||||
class Value(LogoMixin, Readable):
|
||||
addr = Property('VM address', datatype=StringType())
|
||||
|
||||
def read_value(self):
|
||||
return self.get_vm_value(self.addr)
|
||||
|
||||
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
|
||||
|
||||
# TODO: the following classes are too specific, they have to be moved
|
||||
|
||||
class Pressure(LogoMixin, Drivable):
|
||||
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(LogoMixin, 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())
|
||||
def read_status(self):
|
||||
return IDLE, ''
|
||||
|
||||
|
||||
class Valve(LogoMixin, 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
|
||||
@ -142,127 +284,126 @@ class Valve(Snap7Mixin, Drivable):
|
||||
if value != self.target:
|
||||
if self._remaining_tries is None:
|
||||
self.target = self.read_value()
|
||||
return IDLE,''
|
||||
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)
|
||||
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())
|
||||
|
||||
|
||||
class FluidMachines(LogoMixin, Drivable):
|
||||
vm_address_output = Property('VM address output', datatype=StringType())
|
||||
|
||||
target = Parameter('Valve target', datatype=BoolType())
|
||||
value = Parameter('Valve 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())
|
||||
|
||||
|
||||
class TempSensor(LogoMixin, 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(LogoMixin, 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, ''
|
||||
|
||||
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())
|
||||
|
||||
class controlHeater(LogoMixin, 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, ''
|
||||
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())
|
||||
|
||||
|
||||
|
||||
class safetyfeatureState(LogoMixin, 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(LogoMixin, 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, ''
|
||||
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())
|
||||
|
||||
class comparatorgekoppeltParam(LogoMixin, 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, ''
|
||||
|
||||
|
||||
|
||||
|
||||
def read_status(self):
|
||||
return IDLE, ''
|
||||
|
||||
|
Reference in New Issue
Block a user