From a7fd90cd6df5d6017877a192c1a5beab96faf17f Mon Sep 17 00:00:00 2001 From: l_samenv Date: Mon, 14 Apr 2025 11:40:12 +0200 Subject: [PATCH] flowsas project as of 2025-04-14 --- cfg/flowsas_cfg.py | 18 ++- cfg/peristaltic_pump_cfg.py | 12 ++ cfg/pressureTest_cfg.py | 13 ++ cfg/rheotrigger_cfg.py | 11 ++ frappy_psi/cetoni_pump.py | 245 ++++++++++++++++++++++++++++++++---- frappy_psi/gilsonpump.py | 104 +++++++++++++++ frappy_psi/phytron.py | 20 ++- frappy_psi/rheo_trigger.py | 70 +++++++++++ 8 files changed, 462 insertions(+), 31 deletions(-) create mode 100644 cfg/peristaltic_pump_cfg.py create mode 100644 cfg/pressureTest_cfg.py create mode 100644 cfg/rheotrigger_cfg.py create mode 100644 frappy_psi/gilsonpump.py create mode 100644 frappy_psi/rheo_trigger.py diff --git a/cfg/flowsas_cfg.py b/cfg/flowsas_cfg.py index 195e9c0..675d55d 100644 --- a/cfg/flowsas_cfg.py +++ b/cfg/flowsas_cfg.py @@ -1,6 +1,6 @@ Node('flowsas.psi.ch', 'flowsas test motors', - 'tcp://5000', + 'tcp://3000', ) #Mod('mot_io', @@ -14,7 +14,7 @@ Node('flowsas.psi.ch', # 'horizontal axis', # axis = 'X', # io = 'mot_io', -# encoder_mode= 'NO', +# encoder_mode = 'NO', # ) #Mod('vmot', @@ -28,7 +28,7 @@ Node('flowsas.psi.ch', Mod('syr_io', 'frappy_psi.cetoni_pump.LabCannBus', 'Module for bus', - deviceconfig = "/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/config/dual_pumps", + deviceconfig = "/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/config/conti_flow", ) Mod('syr1', @@ -37,7 +37,7 @@ Mod('syr1', io='syr_io', pump_name = "Nemesys_S_1_Pump", valve_name = "Nemesys_S_1_Valve", - inner_diameter_set = 10, + inner_diameter_set = 14.5673, piston_stroke_set = 60, ) @@ -47,6 +47,14 @@ Mod('syr2', io='syr_io', pump_name = "Nemesys_S_2_Pump", valve_name = "Nemesys_S_2_Valve", - inner_diameter_set = 1, + inner_diameter_set = 14.5673, piston_stroke_set = 60, ) + +Mod('contiflow', + 'frappy_psi.cetoni_pump.ContiFlowPump', + 'Continuous flow pump', + io='syr_io', + inner_diameter_set = 14.5673, + piston_stroke_set = 60, + ) \ No newline at end of file diff --git a/cfg/peristaltic_pump_cfg.py b/cfg/peristaltic_pump_cfg.py new file mode 100644 index 0000000..d6e4f85 --- /dev/null +++ b/cfg/peristaltic_pump_cfg.py @@ -0,0 +1,12 @@ +Node('flowsas.psi.ch', + 'peristaltic pump', + 'tcp://3000', +) + +Mod('peripump', + 'frappy_psi.gilsonpump.PeristalticPump', + 'Peristaltic pump', + addr_AO = 'ao1', + addr_dir_relay = 'o1', + addr_run_relay = 'o2', +) \ No newline at end of file diff --git a/cfg/pressureTest_cfg.py b/cfg/pressureTest_cfg.py new file mode 100644 index 0000000..3a6441c --- /dev/null +++ b/cfg/pressureTest_cfg.py @@ -0,0 +1,13 @@ +Node('vf.psi.ch', + 'small vacuum furnace', + 'tcp://5000', +) + +Mod('p', + 'frappy_psi.ionopimax.VoltageInput', + 'Vacuum pressure', + addr = 'av2', + rawrange = (0, 10), + valuerange = (0, 10), + value = Param(unit='V'), + ) diff --git a/cfg/rheotrigger_cfg.py b/cfg/rheotrigger_cfg.py new file mode 100644 index 0000000..c00fe71 --- /dev/null +++ b/cfg/rheotrigger_cfg.py @@ -0,0 +1,11 @@ +Node('flowsas.psi.ch', + 'rheometer triggering', + 'tcp://3000', +) + +Mod('rheo', + 'frappy_psi.rheo_trigger.RheoTrigger', + 'Trigger for the rheometer', + addr='dt1', + doBeep = False, + ) \ No newline at end of file diff --git a/frappy_psi/cetoni_pump.py b/frappy_psi/cetoni_pump.py index c45774c..97ba11a 100644 --- a/frappy_psi/cetoni_pump.py +++ b/frappy_psi/cetoni_pump.py @@ -4,13 +4,15 @@ if libpath not in sys.path: sys.path.append(libpath) from frappy.core import Drivable, Readable, StringIO, HasIO, FloatRange, IntRange, StringType, BoolType, EnumType, \ - Parameter, Property, PersistentParam, Command, IDLE, BUSY, ERROR, Attached + Parameter, Property, PersistentParam, Command, IDLE, BUSY, ERROR, WARN, Attached, Module from qmixsdk import qmixbus from qmixsdk import qmixpump from qmixsdk import qmixvalve +from qmixsdk.qmixpump import ContiFlowProperty, ContiFlowSwitchingMode from qmixsdk.qmixbus import UnitPrefix, TimeUnit +import time -class LabCannBus(Readable): +class LabCannBus(Module): deviceconfig = Property('config files', StringType(),default="/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/config/dual_pumps") def earlyInit(self): @@ -22,11 +24,15 @@ class LabCannBus(Readable): super().initModule() self.bus.start() + with open('/sys/class/ionopimax/buzzer/beep', 'w') as f : + f.write('200 50 3') + def shutdownModule(self): - """Not so gracefully close the connection""" + """Close the connection""" self.bus.stop() self.bus.close() + class SyringePump(Drivable): io = Attached() pump_name = Property('name of pump', StringType(),default="Nemesys_S_1_Pump") @@ -35,18 +41,26 @@ class SyringePump(Drivable): inner_diameter_set = Property('inner diameter', FloatRange(), default=1) piston_stroke_set = Property('piston stroke', FloatRange(), default=60) - value = PersistentParam('volume', FloatRange(unit='mL')) - status = PersistentParam() + value = Parameter('volume', FloatRange(unit='uL')) + status = Parameter() + + max_flow_rate = Parameter('max flow rate', FloatRange(0,100000, unit='uL/s',), readonly=True) + max_volume = Parameter('max volume', FloatRange(0,100000, unit='uL',), readonly=True) - max_flow_rate = Parameter('max flow rate', FloatRange(0,100000, unit='mL/min',), readonly=True) - max_volume = Parameter('max volume', FloatRange(0,100000, unit='mL',), readonly=True) - target_flow_rate = Parameter('target flow rate', FloatRange(unit='mL/min'), readonly=False) - real_flow_rate = Parameter('actual flow rate', FloatRange(unit='mL/min'), readonly=True) - target = Parameter('target volume', FloatRange(unit='mL'), readonly=False) + target_flow_rate = Parameter('target flow rate', FloatRange(unit='uL/s'), readonly=False) + real_flow_rate = Parameter('actual flow rate', FloatRange(unit='uL/s'), readonly=True) + + target = Parameter('target volume', FloatRange(unit='uL'), readonly=False) no_of_valve_pos = Property('number of valve positions', IntRange(0,10), default=1) valve_pos = Parameter('valve position', EnumType('valve', CLOSED=0, APP=1, RES=2, OPEN=3), readonly=False) + force = Parameter('syringe force', FloatRange(unit='kN'), readonly=True) + max_force = Parameter('max device force', FloatRange(unit='kN'), readonly=True) + force_limit = Parameter('user force limit', FloatRange(unit='kN'), readonly=False) + + _resolving_force_overload = False + def initModule(self): super().initModule() @@ -66,32 +80,41 @@ class SyringePump(Drivable): self.pump.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set) - self.pump.set_volume_unit(qmixpump.UnitPrefix.milli, qmixpump.VolumeUnit.litres) + self.pump.set_volume_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres) - self.pump.set_flow_unit(qmixpump.UnitPrefix.milli, qmixpump.VolumeUnit.litres, qmixpump.TimeUnit.per_minute) + self.pump.set_flow_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres, qmixpump.TimeUnit.per_second) - self.max_flow_rate = self.pump.get_flow_rate_max() - self.max_volume = self.pump.get_volume_max() - self.no_of_valve_pos = self.valve.number_of_valve_positions() + self.max_flow_rate = round(self.pump.get_flow_rate_max(),2) + self.max_volume = round(self.pump.get_volume_max(),2) self.valve_pos = self.valve.actual_valve_position() - self.target_flow_rate = self.max_flow_rate * 0.5 - self.target = self.pump.get_fill_level() + self.target_flow_rate = round(self.max_flow_rate * 0.5,2) + self.target = max(0, round(self.pump.get_fill_level(),2)) + + self.pump.enable_force_monitoring(True) + self.max_force = self.pump.get_max_device_force() + self.force_limit = self.max_force def read_value(self): - return self.pump.get_fill_level() + return round(self.pump.get_fill_level(),2) def write_target(self, target): - self.pump.set_fill_level(target, self.target_flow_rate) - self.status = BUSY, 'Target changed' - return target + if self.read_valve_pos() == 0 : + self.status = ERROR, 'Cannot pump if valve is closed' + self.log.warn('Cannot pump if valve is closed') + return target + else: + self.pump.set_fill_level(target, self.target_flow_rate) + self.status = BUSY, 'Target changed' + self.log.info(f'Started pumping at {self.target_flow_rate} ul/s') + return target def write_target_flow_rate(self, rate): - self.pump.target_flow_rate = rate + self.target_flow_rate = rate return rate def read_real_flow_rate(self): - return self.pump.get_flow_is() + return round(self.pump.get_flow_is(),2) def read_valve_pos(self): return self.valve.actual_valve_position() @@ -100,11 +123,165 @@ class SyringePump(Drivable): self.valve.switch_valve_to_position(target_pos) return target_pos + def read_force(self): + return round(self.pump.read_force_sensor(),3) + + def read_force_limit(self): + return self.pump.get_force_limit() + + def write_force_limit(self, limit): + self.pump.write_force_limit(limit) + return limit + def read_status(self): fault_state = self.pump.is_in_fault_state() pumping = self.pump.is_pumping() + pump_enabled = self.pump.is_enabled() + safety_stop_active = self.pump.is_force_safety_stop_active() + if fault_state == True: return ERROR, 'Pump in fault state' + elif self._resolving_force_overload : + return BUSY, 'Resolving force overload' + elif safety_stop_active: + return ERROR, 'Pressure safety stop' + elif not pump_enabled: + return ERROR, 'Pump not enabled' + elif pumping == True: + return BUSY, f'Pumping {self.real_flow_rate} ul/s' + elif self.read_valve_pos() == 0: + return IDLE, 'Valve closed' + else: + return IDLE, '' + + @Command + def stop(self): + self.pump.stop_pumping() + self.target = self.pump.get_fill_level() + self.status = BUSY, 'Stopping' + + @Command + def clear_errors(self): + """Clear fault state and enable pump""" + if self.pump.is_in_fault_state(): + self.pump.clear_fault() + self.log.info('Cleared faults') + + if not self.pump.is_enabled(): + self.pump.enable(True) + self.log.info('Pump was disabled, re-enabling') + + self.target = max(0,round(self.value,2)) + self.status = IDLE, '' + + @Command + def resolve_force_overload(self): + """Resolve a force overload situation""" + if not self.pump.is_force_safety_stop_active(): + self.status = ERROR, 'No force overload detected' + self.log.warn('No force overload to be resolved') + return + + self._resolving_force_overload = True + self.status = BUSY, 'Resolving force overload' + self.pump.enable_force_monitoring(False) + + flow = 0 - self.pump.get_flow_rate_max() / 100 + self.pump.generate_flow(flow) + + safety_stop_active = False + while not safety_stop_active: + time.sleep(0.1) + safety_stop_active = self.pump.is_force_safety_stop_active() + self.pump.stop_pumping() + self.pump.enable_force_monitoring(True) + + time.sleep(0.3) + self._resolving_force_overload = False + self.status = self.read_status() + +class ContiFlowPump(Drivable): + io = Attached() + + inner_diameter_set = Property('inner diameter', FloatRange(), default=1) + piston_stroke_set = Property('piston stroke', FloatRange(), default=60) + + crossflow_seconds = Property('crossflow duration', FloatRange(unit='s'),default=2) + + value = PersistentParam('flow rate', FloatRange(unit='uL/s')) + status = PersistentParam() + + max_refill_flow = Parameter('max refill flow', FloatRange(unit='uL/s'), readonly=True) + refill_flow = Parameter('refill flow', FloatRange(unit='uL/s'), readonly=False) + + max_flow_rate = Parameter('max flow rate', FloatRange(0,100000, unit='uL/s',), readonly=True) + target = Parameter('target flow rate', FloatRange(unit='uL/s'), readonly=False) + + def initModule(self): + super().initModule() + + self.pump = qmixpump.ContiFlowPump() + self.pump.lookup_by_name("ContiFlowPump_1") + + def initialReads(self): + if self.pump.is_in_fault_state(): + self.pump.clear_fault() + + if not self.pump.is_enabled(): + self.pump.enable(True) + + self.syringe_pump1 = self.pump.get_syringe_pump(0) + self.syringe_pump1.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set) + self.syringe_pump2 = self.pump.get_syringe_pump(1) + self.syringe_pump2.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set) + self.pump.set_volume_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres) + self.pump.set_flow_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres, qmixpump.TimeUnit.per_second) + + self.pump.set_device_property(ContiFlowProperty.SWITCHING_MODE, ContiFlowSwitchingMode.CROSS_FLOW) + self.max_refill_flow = self.pump.get_device_property(ContiFlowProperty.MAX_REFILL_FLOW) + self.pump.set_device_property(ContiFlowProperty.REFILL_FLOW, self.max_refill_flow / 2.0) + self.pump.set_device_property(ContiFlowProperty.CROSSFLOW_DURATION_S, self.crossflow_seconds) + self.pump.set_device_property(ContiFlowProperty.OVERLAP_DURATION_S, 0) + + self.max_flow_rate = self.pump.get_flow_rate_max() + self.target = 0 + + def read_value(self): + return round(self.pump.get_flow_is(),3) + + def write_target(self, target): + if target <= 0: + self.pump.stop_pumping() + self.status = self.read_status() + return 0 + else: + self.pump.generate_flow(target) + self.status = BUSY, 'Target changed' + return target + + def read_refill_flow(self): + return round(self.pump.get_device_property(ContiFlowProperty.REFILL_FLOW),3) + + def write_refill_flow(self, refill_flow): + self.pump.set_device_property(ContiFlowProperty.REFILL_FLOW, refill_flow) + self.max_flow_rate = self.pump.get_flow_rate_max() + return refill_flow + + def read_status(self): + fault_state = self.pump.is_in_fault_state() + pumping = self.pump.is_pumping() + pump_enabled = self.pump.is_enabled() + pump_initialised = self.pump.is_initialized() + pump_initialising = self.pump.is_initializing() + + if fault_state == True: + return ERROR, 'Pump in fault state' + elif not pump_enabled: + return ERROR, 'Pump not enabled' + elif not pump_initialised: + return WARN, 'Pump not initialised' + elif pump_initialising: + return BUSY, 'Pump initialising' elif pumping == True: return BUSY, 'Pumping' else: @@ -113,4 +290,24 @@ class SyringePump(Drivable): @Command def stop(self): self.pump.stop_pumping() - self.target = self.pump.get_fill_level() + self.target = 0 + self.status = BUSY, 'Stopping' + + @Command + def clear_errors(self): + """Clear fault state and enable pump""" + if self.pump.is_in_fault_state(): + self.pump.clear_fault() + self.log.info('Cleared faults') + + if not self.pump.is_enabled(): + self.pump.enable(True) + self.log.info('Pump was disabled, re-enabling') + + self.target = 0 + self.status = IDLE, '' + + @Command + def initialise(self): + """Initialise the ConfiFlow pump""" + self.pump.initialize() \ No newline at end of file diff --git a/frappy_psi/gilsonpump.py b/frappy_psi/gilsonpump.py new file mode 100644 index 0000000..06aa99c --- /dev/null +++ b/frappy_psi/gilsonpump.py @@ -0,0 +1,104 @@ +# Author: Wouter Gruenewald + +from frappy.core import StringType, BoolType, EnumType, FloatRange, Parameter, Property, PersistentParam, Command, IDLE, ERROR, WARN, BUSY, Drivable + + +class PeristalticPump(Drivable): + value = Parameter('Pump speed', FloatRange(0,100,unit="%"), default=0) + target = Parameter('Target pump speed', FloatRange(0,100,unit="%"), default=0) + status = Parameter() + + addr_AO = Property('Address of the analog out', StringType()) + addr_dir_relay = Property('Address of the direction relay', StringType()) + addr_run_relay = Property('Address of the running relay', StringType()) + + direction = Parameter('pump direction', EnumType('direction', CLOCKWISE=0, ANTICLOCKWISE=1), default=0, readonly=False) + active = Parameter('pump running', BoolType(), default=False, readonly=False) + + def initModule(self): + super().initModule() + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f : + f.write('0') + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_mode', 'w') as f : + f.write('V') + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f : + f.write('0') + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f : + f.write('1') + + def shutdownModule(self): + '''Disable analog output''' + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f : + f.write('0') + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f : + f.write('0') + + def read_value(self): + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'r') as f : + raw_value = f.read().strip('\n') + value = (int(raw_value) / 5000) * 100 + return value + + def write_target(self, target): + raw_value = (target / 100)*5000 + with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f : + f.write(str(int(raw_value))) + return target + + def read_direction(self): + with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'r') as f : + raw_direction = f.read().strip('\n') + if raw_direction == '0' or raw_direction == 'F': + return 0 + if raw_direction == '1' or raw_direction == 'S': + return 1 + else: + return None + + def write_direction(self, direction): + if direction == 0: + raw_direction = '0' + elif direction == 1: + raw_direction = '1' + with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'w') as f : + f.write(raw_direction) + return direction + + def read_active(self): + with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'r') as f : + raw_active = f.read().strip('\n') + if raw_active == '0' or raw_active == 'F': + return False + elif raw_active == '1' or raw_active == 'S': + return True + else: + return None + + def write_active(self, active): + if active == False: + raw_active = '0' + elif active == True: + raw_active = '1' + with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'w') as f : + f.write(raw_active) + return active + + + def read_status(self): + with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'r') as f : + raw_direction = f.read().strip('\n') + with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'r') as f : + raw_active = f.read().strip('\n') + + if raw_direction == 'F' or raw_direction == 'S': + return ERROR, 'Fault on direction relay' + elif raw_active == 'F' or raw_active == 'S': + return ERROR, 'Fault on pump activation relay' + elif self.active == True: + return BUSY, 'Pump running' + else: + return IDLE, '' + + @Command + def stop(self): + self.write_active(False) diff --git a/frappy_psi/phytron.py b/frappy_psi/phytron.py index 9f484bf..9cc090b 100644 --- a/frappy_psi/phytron.py +++ b/frappy_psi/phytron.py @@ -62,7 +62,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): encoder_mode = Parameter('how to treat the encoder', EnumType('encoder', NO=0, READ=1, CHECK=2), default=1, readonly=False) - check_limit_switches = Parameter('whethter limit switches are checked',BoolType(), + check_limit_switches = Parameter('whether limit switches are checked',BoolType(), default=0, readonly=False) value = PersistentParam('angle', FloatRange(unit='deg')) status = PersistentParam() @@ -90,6 +90,8 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): status_bits = ['power stage error', 'undervoltage', 'overtemperature', 'active', 'lower switch active', 'upper switch active', 'step failure', 'encoder error'] + _doing_reference = False + def get(self, cmd): return self.communicate(f'{self.address:x}{self.axis}{cmd}') @@ -178,10 +180,14 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): def doPoll(self): super().doPoll() - if self._running and not self.isBusy(): + if self._running and not self.isBusy() and not self._doing_reference: if time.time() > self._stopped_at + 5: self.log.warning('stop motor not started by us') self.hw_stop() + if self._doing_reference and self.get('=H') == 'E' : + self.status = IDLE, '' + self.target = 0 + self._doing_reference = False def read_status(self): hexstatus = 0x100 @@ -207,6 +213,9 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): if status[0] == ERROR: self._blocking_error = status[1] return status + if self._doing_reference and self.get('=H') == 'N': + status = BUSY, 'Doing reference run' + return status return super().read_status() # status from state machine def check_moving(self): @@ -346,3 +355,10 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): self.status = 'IDLE', 'after error reset' self._blocking_error = None self.target = self.value # clear error in target + + @Command + def make_ref_run(self): + '''Do reference run''' + self._doing_reference = True + self.status = BUSY, 'Doing reference run' + self.communicate(f'{self.address:x}{self.axis}0-') diff --git a/frappy_psi/rheo_trigger.py b/frappy_psi/rheo_trigger.py new file mode 100644 index 0000000..0016499 --- /dev/null +++ b/frappy_psi/rheo_trigger.py @@ -0,0 +1,70 @@ +from frappy.core import StringType, BoolType, Parameter, Property, PersistentParam, Command, IDLE, ERROR, WARN, Writable +import time + +class RheoTrigger(Writable): + addr = Property('Port address', StringType()) + value = Parameter('Output state', BoolType(), default=0) + target = Parameter('target', BoolType(), default=0, readonly=False) + status = Parameter() + doBeep = Property('Make noise', BoolType(), default=0) + + _status = 0 + + def initModule(self): + super().initModule() + with open('/sys/class/ionopimax/digital_io/'+self.addr+'_mode', 'w') as f : + f.write('out') + + if self.doBeep: + with open('/sys/class/ionopimax/buzzer/beep', 'w') as f : + f.write('200 50 3') + + def read_value(self): + with open('/sys/class/ionopimax/digital_io/'+self.addr, 'r') as f : + file_value = f.read() + if file_value == '0\n': + value = False + self._status = 0 + elif file_value == '1\n': + value = True + self._status = 1 + else: + self._status = -1 + value = False + return value + + + def write_target(self,target): + if target == self.value: + return target + else: + with open('/sys/class/ionopimax/digital_io/'+self.addr, 'w') as f : + if target == True: + f.write('1') + elif target == False: + f.write('0') + time.sleep(0.05) + if self.doBeep: + with open('/sys/class/ionopimax/buzzer/beep', 'w') as f : + f.write('200') + self.status = self.read_status() + return target + + def read_status(self): + self.value = self.read_value() + if self._status == 0: + return IDLE, 'Signal low' + elif self._status == 1: + return IDLE, 'Signal high' + else: + return ERROR, 'Cannot read status' + + + @Command + def toggle(self): + """Toggle output""" + value = self.read_value() + if value == True: + self.write_target(False) + else: + self.write_target(True) \ No newline at end of file