drivers.Semeatech.Semeatech.Semeatech

class drivers.Semeatech.Semeatech.Semeatech(sm_port: str = '/dev/ttyAMA1', baud: int = 115200)

Driver for the Semeatech electrochemical gas sensor over UART.

Sends pre-computed binary commands to a Semeatech OGS module and parses the multi-field response into gas concentration (ug/m^3 and ppb), on-board temperature (degrees Celsius), relative humidity (percent), and a raw ADC reading. The protocol uses a 0x3A header with 10-byte command frames; responses are variable-length packets whose payload bytes are decoded as big-endian signed integers.

What it measures:

Concentration of a single target gas (the species depends on the cell installed - e.g. O3, NO2, SO2, CO, H2S). The primary reading is in parts per billion (ppb); ug/m^3 is also reported by the sensor and cached for downstream conversion.

Variables:
  • SM_GET (ClassVar[list[int]]) – 10-byte command frame that requests a full multi-field readout (gas + temperature + humidity + raw).

  • SM_RAW (ClassVar[list[int]]) – 10-byte command frame for the raw ADC channel only.

  • SM_TEMP (ClassVar[list[int]]) – 10-byte command frame for the on-board temperature channel.

  • SM_HUM (ClassVar[list[int]]) – 10-byte command frame for the on-board humidity channel.

  • DEBUG (bool) – When True, verbose TX/RX traces are emitted via utils.oizom_logger.OizomLogger.

  • TIMEOUT (int) – Per-transaction response timeout in milliseconds (derived from _timeout_ seconds).

  • COMMAND_DELAY (int) – Inter-command idle period in seconds.

  • RESPONSE_DELAY (float) – Mandatory post-transmit settle delay in seconds. Do not change - the bus is timing-sensitive.

  • MAX_BUFFER_SIZE (int) – Upper bound on bytes read in a single response.

  • data (ClassVar[dict]) – Cache of the most recent decoded values with keys ug, ppb, temp, hum, and raw.

Example

Read the gas concentration and cached environmental fields:

sm = Semeatech("/dev/ttyAMA1")  # doctest: +SKIP
ppb = sm.getSensorData(0)  # doctest: +SKIP
raw_adc = sm.getSensorData(1)  # doctest: +SKIP
temp_c = sm.getSensorData(2)  # doctest: +SKIP
hum_rh = sm.getSensorData(3)  # doctest: +SKIP

See also

SensorBase.SensorBase.GenericSensor: Reference base contract

for Oizom sensor drivers. Semeatech does not inherit from it but mirrors the getSensorData(channel) access pattern so it can be used interchangeably by higher-level acquisition loops.

Configure the UART port used to talk to the Semeatech module.

Creates the serial.Serial instance with 8N1 framing and a 5-second read/write timeout, then immediately closes the handle so the port is only held while a transaction is in flight.

Parameters:
  • sm_port (str) – Serial device path on the host (default "/dev/ttyAMA1" on Raspberry Pi).

  • baud (int) – UART baud rate. Must match the sensor’s configured rate (default 115200).

Returns:

The constructor has no return value.

Return type:

None

Raises:

serial.SerialException – If the underlying pyserial driver fails to acquire the requested device (e.g. permissions, missing device node, or port already in use).

Example

Construct a driver bound to the default UART:

sm = Semeatech()  # doctest: +SKIP
sm2 = Semeatech("/dev/ttyUSB0", baud=115200)  # doctest: +SKIP

Note

The port is left closed after construction. Subsequent calls to sendCommand() will open and close it on demand.

getSensorData(channel: int = 0) float

Return one decoded measurement from the sensor.

Channel 0 issues a fresh SM_GET transaction, parses the full response into the data cache, and returns the gas concentration in ppb. Channels 1-3 are non-blocking and return the cached value populated by the most recent channel-0 read:

  • 1 -> raw ADC count (divided by 10.0)

  • 2 -> on-board temperature in degrees Celsius

  • 3 -> on-board relative humidity in percent

Parameters:

channel (int) – Logical data channel. 0 for ppb (full read), 1 for raw ADC, 2 for temperature, 3 for humidity.

Returns:

The requested measurement, or None if a hardware or parsing error occurred (the exception is logged, not re-raised).

Return type:

float

Raises:

Exception – Any error raised inside the read path is caught and logged via context_logger.error_with_context; this method does not propagate exceptions to the caller.

Example

Sample a full frame and then fetch cached environmental fields:

sm = Semeatech("/dev/ttyAMA1")  # doctest: +SKIP
ppb = sm.getSensorData(0)  # doctest: +SKIP
temp_c = sm.getSensorData(2)  # doctest: +SKIP

Note

Channels 1-3 will return 0 until at least one channel-0 read has succeeded, because the cache is seeded with zeros at class definition time.

micros() int

Return the current wall-clock time in microseconds.

Thin wrapper around time.time() provided so the rest of the driver can match the microsecond-based timing idiom used elsewhere in the Oizom firmware.

Parameters:

None – This method takes no arguments other than self.

Returns:

Microseconds since the Unix epoch.

Return type:

int

Raises:

None – This method does not raise.

Example

Measure how long a transaction takes in microseconds:

sm = Semeatech("/dev/ttyAMA1")  # doctest: +SKIP
t0 = sm.micros()  # doctest: +SKIP
sm.getSensorData(0)  # doctest: +SKIP
elapsed_us = sm.micros() - t0  # doctest: +SKIP

Note

Resolution is bounded by the underlying OS clock; on Linux this is typically nanosecond-accurate but is reported here as an integer microsecond value.

millis() int

Return the current wall-clock time in milliseconds.

Used internally by sendCommand() to enforce the TIMEOUT budget while waiting for a sensor response.

Parameters:

None – This method takes no arguments other than self.

Returns:

Milliseconds since the Unix epoch.

Return type:

int

Raises:

None – This method does not raise.

Example

Build a simple millisecond stopwatch:

sm = Semeatech("/dev/ttyAMA1")  # doctest: +SKIP
start = sm.millis()  # doctest: +SKIP
sm.getSensorData(0)  # doctest: +SKIP
duration_ms = sm.millis() - start  # doctest: +SKIP

Note

The returned value is a monotonic-looking integer but it is actually wall-clock time, so it can step backwards if the system clock is adjusted (e.g. by NTP).

sendCommand(command: list[int], recieve: bool = False) list

Send a binary command frame and return the raw response.

Opens the serial port (if not already open), writes each byte of command individually, waits RESPONSE_DELAY seconds for the sensor to begin replying, then polls inWaiting until either data is available or TIMEOUT milliseconds elapse. Up to three retry passes are performed when an empty response is received. The port is closed at the end of the transaction unless recieve is True.

Parameters:
  • command (list[int]) – Sequence of byte values (0-255) constituting a full Semeatech command frame, typically one of the SM_* class constants.

  • recieve (bool) – If True, the serial port is left open after reading so a follow-up call can re-use the same handle. If False (default) the port is closed before returning.

Returns:

Response bytes as hex strings (e.g. ["0x3a", "0x10", ...]). An empty list is returned if every retry pass observed no data and no exception was raised.

Return type:

list

Raises:

Exception – If all three retry passes time out with no bytes received, an Exception("Timeout Semeatech") is raised inside the method, caught locally, logged, and an empty list is returned to the caller.

Example

Issue the full-readout frame and inspect the raw payload:

sm = Semeatech("/dev/ttyAMA1")  # doctest: +SKIP
rx = sm.sendCommand(Semeatech.SM_GET)  # doctest: +SKIP
print(rx[:4])  # doctest: +SKIP

Note

The method is byte-oriented and intentionally writes one byte at a time to remain compatible with the sensor’s input buffer handling. Do not parallelize calls on the same port.

COMMAND_DELAY = 1
DEBUG = True
MAX_BUFFER_SIZE = 51
RESPONSE_DELAY = 0.001
SM_GET: ClassVar[list[int]] = [58, 16, 3, 0, 0, 8, 0, 0, 83, 80]
SM_HUM: ClassVar[list[int]] = [58, 16, 3, 0, 5, 1, 0, 0, 50, 146]
SM_RAW: ClassVar[list[int]] = [58, 16, 3, 0, 6, 2, 0, 0, 115, 218]
SM_TEMP: ClassVar[list[int]] = [58, 16, 3, 0, 4, 1, 0, 0, 130, 98]
TIMEOUT = 8000
__sm_port
_timeout_ = 8
data: ClassVar[dict]