# ***************************************************************************** # 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: # Anik Stark # Markus Zolliker # ***************************************************************************** """Bronkhorst flow or pressure regulators. Communication via ProPar protocol. write command: :LnAd01PrTpData\r\n read command: :LnAd04CopyPrTp\r\n (Ln = 06) answer: :LnAd02CopyData\r\n Ln: number of bytes (hex digits pairs) following Ad: node address (3...120, always a reply if message is sent to node address 128) Copy: values just to be copied by the reply (first and third digit < 8) recommended practice: Use PrTp for Copy Pr: Process number (<80, manual page 24pp) Tp: Type + parameter number. Type: 00 byte, 20 int, 40 long/float, 60 string for strings either 00 (for nul terminated) or the max. number of chars has to be appended to the type Data: length depending on type. read command for direct communication: :06800401210120 Send values on a scale from 0-32000 (0-100%). """ from frappy.core import StringIO, HasIO, Readable, Writable, Drivable, Parameter, Property, \ FloatRange, BoolType, EnumType, IntRange, IDLE, BUSY from frappy.errors import CommunicationFailedError class IO(StringIO): end_of_line = '\r\n' # hex: 0D0A adr = 128 identification = [(f':07{adr:02X}047163716300', f':10{adr:02X}02716300.*')] # serial number default_settings = {'baudrate': 38400} def intpar(process, parameter): return '06', f'{process:02X}{parameter|0x20:02X}' def longpar(process, parameter): return '08', f'{process:02X}{parameter|0x40:04X}' MEASURE = intpar(1, 0) SETPOINT = intpar(1, 1) RAMP = intpar(1, 2) CONTROL = longpar(114, 1) class Sensor(HasIO, Readable): ioClass = IO value = Parameter('pressure', FloatRange()) scale = Property('scale factor', FloatRange(), default=1) adr = Property('node adress', IntRange(0, 255)) def get_par(self, length, param, scale): reply = self.communicate(f':{length}{self.adr:02X}04{param}{param}') if reply[:11] != f':{length}{self.adr:02X}02{param}': return CommunicationFailedError(f'bad reply: {reply}') val = int(reply[11:14], 16) / 32000 * scale return val def read_value(self): return self.get_par(*MEASURE, self.scale) class Controller(Sensor, Writable): def set_par(self, length, param, scale, value): reply = self.communicate(f':{length}{self.adr:02X}01{param}{round(value/scale):04X}') if reply[:8] != f':04{self.adr:02X}0000': raise CommunicationFailedError(f'bad reply: {reply}') return self.get_par(length, param, scale) def read_target(self): return self.get_par(*SETPOINT, self.scale) def write_target(self, value): val = value / self.scale * 32000 return self.set_par(*SETPOINT, self.scale, val) class HasRamp(Drivable): setpoint = Parameter('running setpoint', FloatRange()) ramp_enable = Parameter('enable ramp mode', BoolType()) ramp = Parameter('slope of ramp', FloatRange(1e-6, unit='mbar/min')) tolerance = Property('tolerance for target vs. running setpoint', FloatRange(), default=1) def read_target(self): # overwrite Controller.read_target() as setpoint is running return self.read_target def write_target(self, target): super().write_target(target) self.status = BUSY, 'ramping' def read_setpoint(self): return super().read_target() def read_ramp(self): if abs(self.read_setpoint() - self.target) < self.tolerance: self.status = IDLE, '' def write_ramp(self, ramp): if self.ramp_enable: time = min(self.scale / ramp, 3000) return self.set_par(*RAMP, (60 / 0.1), time) def write_ramp_enable(self, flag): if flag: self.write_ramp(self.ramp) else: self.set_par(*RAMP, (60 / 0.1), 0) class HasControlMode(): control_active = Parameter('control mode active', BoolType()) control = Property('control mode', EnumType(manual=4, loop=11)) output = Parameter('valve output', FloatRange(), readonly=False) def write_control(self, value): if self.control_active: val = self.control.get(value, 4) return self.set_par(*CONTROL, 1, val) def write_output(self, value): scale = (2**24 - 1) / 100 self.set_par(*CONTROL, scale, value) class ControllerRamp(HasRamp, Controller): pass class ControllerControlMode(HasControlMode, Controller): pass class ControllerRampControlMode(HasRamp, HasControlMode, Controller): pass