"""
Vaisala PTU300 series barometer which reads temperature, relative humidity, and pressure.
Supports models PTU300, PTU301, PTU303, PTU307 and PTU30T.
"""
from __future__ import annotations
import re
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from msl.equipment import EquipmentRecord
from msl.equipment.resources import register
from msl.equipment.exceptions import VaisalaError
from msl.equipment.connection_serial import ConnectionSerial
from msl.equipment.constants import (
Parity,
DataBits,
)
[docs]
@register(manufacturer=r'Vaisala', model=r'^PTU30[0137T]$', flags=re.IGNORECASE)
class PTU300(ConnectionSerial):
def __init__(self, record: EquipmentRecord) -> None:
"""Vaisala Barometer PTU300 series (models PTU300, PTU301, PTU303, PTU307 and PTU30T).
The device manual is available `here <https://docs.vaisala.com/v/u/M210796EN-J/en-US>`_.
.. note::
Ensure the device is in STOP or SEND mode before initiating a connection to a PC.
The default settings for the RS232 connection are:
* Baud rate = 4800
* Data bits = 7
* Stop bits = 1
* Parity = EVEN
* Flow control = None
Do not instantiate this class directly. Use the :meth:`~.EquipmentRecord.connect`
method to connect to the equipment.
:param record: A record from an :ref:`equipment-database`.
"""
props = record.connection.properties
props.setdefault('baud_rate', 4800)
props.setdefault('data_bits', DataBits.SEVEN)
props.setdefault('parity', Parity.EVEN)
super(PTU300, self).__init__(record)
self.set_exception_class(VaisalaError)
self.rstrip = True
self._units = {}
self._pressure_modules = set()
self._info = self._device_info()
# Get the device ID (serial) number and check it agrees with the equipment record.
# can use reply = self.query("*9900SN") but the serial number is also in device_info
sn = self._info["Serial number"]
if not sn == record.serial:
self.raise_exception(f"Inconsistent serial number: expected {record.serial} but received {sn}")
def _device_info(self) -> dict[str, str]:
"""Return a dictionary of information about the Vaisala device.
"""
info = {}
break_keys = {
'PTU30': "module 2",
'PTB33': "module 4",
}
break_key = "TBC"
self.write("?")
while True:
ok = self.read()
try:
key, val = ok.split(': ')
info[key.strip()] = val.strip()
if 'baro' in val.lower():
self._pressure_modules.add(val)
if break_key in key.lower():
break
except ValueError:
model, version = ok.split(" / ")
info['Model'] = model
break_key = break_keys[model[:-1]]
info['Version'] = version
return info
@property
def device_info(self) -> dict[str, str]:
"""Return a dictionary of information about the Vaisala device.
"""
return self._info
[docs]
def set_units(self, desired_units: dict[str, str]) -> None:
"""Set units of specified quantities. Note that only one pressure unit is used at a time for the PTU300 series.
:param desired_units: Dictionary of quantities (as keys) and their unit (their value)
as specified in the instrument manual on pages 22 and 106.
These may include the following (available options depend on the barometer components):
* Pressure quantities: P, P3h, P1, P2, QNH, QFE, HCP, ...
* Pressure units: hPa, psi, inHg, torr, bar, mbar, mmHg, kPa, Pa, mmH2O, inH2O
* Temperature quantity: T
* Temperature units: 'C, 'F (C and F are also supported but are returned as 'C or 'F)
* Humidity quantity: RH
* Humidity unit: %RH
"""
p_units = []
allowed_units = [ # for pressure
'hPa', 'psia', 'inHg', 'torr', 'bara', 'barg', 'psig', 'mbar', 'mmHg', 'kPa', 'Pa', 'mmH2O', 'inH2O'
]
old_units = self.query("UNIT")
if not "Output units" in old_units: # confirming device is of type PTU300
self.raise_exception("Check correct device connected")
for quantity, u in desired_units.items():
if quantity == "RH": # only option is %RH
self._units["RH"] = "%RH"
elif "T" in quantity: # options are 'C, 'F
if "F" in u: # Temperature and humidity setting is done via metric or 'non metric'
r_m = self.query("UNIT N")
if not 'non' in r_m:
self.raise_exception("Error when setting non metric units")
self._units["T"] = "'F"
elif "C" in u:
r_m = self.query("UNIT M")
self._units["T"] = "'C"
else:
self.raise_exception(f"Unit {u} is not supported by this device. Please use 'C or 'F.")
if not 'metric' in r_m:
self._units["T"] = None
self.check_for_errors()
elif u in allowed_units: # assume this is a pressure quantity based on the unit
if p_units and u not in p_units:
self.raise_exception("Only one pressure unit can be set for this barometer")
r_p = self.query(f"UNIT P {u}")
if not r_p.endswith(u):
self.check_for_errors()
self._units[quantity] = u
p_units.append(u)
else: # quantity is not pressure, temperature or humidity, so ask user to set the unit manually
self.raise_exception(f"{u} is not able to be set for {quantity}. Please set this unit manually.")
@property
def units(self) -> dict[str, str]:
"""A dictionary of measured quantities and their associated units as set on the device by :meth:`.set_units`."""
return self._units
[docs]
def get_reading_str(self) -> str:
"""Output the reading once. The returned string follows the format set by :meth:`.set_format`."""
return self.query("SEND")
[docs]
def check_for_errors(self) -> None:
"""Raise an error if present."""
err = self.query("ERRS") # List present transmitter errors
# a PASS or FAIL line is returned from PTB330 modules first
if err == "PASS":
err = self.read()
elif err == "FAIL":
err = self.read()
if err and not err == 'No errors':
self.raise_exception(err)