drivers.CubicPM3006.CubicPM3006.CubicPM3006¶
- class drivers.CubicPM3006.CubicPM3006.CubicPM3006(cubic_port: str = '/dev/ttyAMA1', baud: int = 9600)¶
UART driver for the Cubic PM3006 laser particulate matter sensor.
Wraps a
pyserialconnection to a Cubic PM3006 device and exposes helpers to request and decode its binary protocol. The sensor reports mass concentrations for PM1, PM2.5, PM4.25, PM10, and TSP (referred to here aspm100) in micrograms per cubic metre, together with particle counts in six size bins (>0.3, >0.5, >1.0, >2.5, >5.0, and >10 micrometres).While this class is self-contained, it is functionally a sibling of
drivers.CubicGeneric.CubicGeneric.CubicGenericand follows the same conventions as other sensor drivers built on top ofSensorBase.SensorBase.GenericSensor.- Variables:
CUBIC_READ_DATA (
list[int]) – Command byte sequence used to request a PM/particle-count measurement frame from the sensor.DEBUG (
bool) – WhenTrue, enables verbose serial-level logging of every TX and RX transaction.data (
dict[str,float]) – Most recent decoded readings. Keys arepm1,pm2_5,pm4_25,pm10,pm100(mass concentrations) andp1throughp6(particle counts).TIMEOUT (
int) – Read timeout in milliseconds for a complete frame.COMMAND_DELAY (
int) – Seconds to wait between consecutive commands.RESPONSE_DELAY (
float) – Seconds to wait after writing before polling the input buffer.MAX_BUFFER_SIZE (
int) – Expected length, in bytes, of a full response frame.
Example
Instantiate and read one sample on a Raspberry Pi:
>>> from drivers.CubicPM3006.CubicPM3006 import CubicPM3006 >>> sensor = CubicPM3006("/dev/ttyAMA1") >>> sensor.getPM(sendCommand=True)["pm10"] 18.0
Initialise the UART connection and runtime state for the sensor.
Opens the underlying
pyserialport with the PM3006’s required 8N1 framing, primes the read-data command, and zero-initialises thedatacache so callers can rely on a fully-populated dictionary even before the first measurement is taken.- Parameters:
cubic_port (
str) – Path to the serial device the PM3006 is attached to. Defaults to"/dev/ttyAMA1"which is the standard UART on Oizom Raspberry Pi based monitors.baud (
int) – Baud rate for the UART link. The PM3006 firmware fixes this at 9600 bps; only override when bridging through hardware that demands a different rate.
- Returns:
The constructor mutates
selfand does not return a value.- Return type:
None
- Raises:
serial.SerialException – If the underlying serial port cannot be opened (for example, the device file does not exist, permissions are missing, or the port is in use).
ValueError – If
baudis not a value accepted bypyserial.
Example
Open the sensor on the default UART:
>>> from drivers.CubicPM3006.CubicPM3006 import CubicPM3006 >>> sensor = CubicPM3006("/dev/ttyAMA1")
Note
The serial port is held open for the lifetime of this instance. Callers that need to share the UART with other drivers should serialise access externally. Unlike sensors built on
SensorBase.SensorBase.GenericSensor, this driver does not register itself with a global sensor manager.- getPM(sendCommand: bool = False) dict[str, float]¶
Read particulate-matter mass concentrations and particle counts.
When
sendCommandisTruethe driver transmitsCUBIC_READ_DATA, waits for the sensor response, and decodes the binary frame into mass concentrations and particle counts. PM fields are reconstructed from 32-bit big-endian integers at fixed offsets in the response payload. WhensendCommandisFalsethe cacheddatadictionary is returned unchanged.- Parameters:
sendCommand (
bool) – IfTrue, issue a read command before returning. IfFalse, return the cached values from the previous successful read.- Returns:
- Dictionary with the following keys.
pm1– PM1.0 mass concentration (ug/m^3).pm2_5– PM2.5 mass concentration (ug/m^3).pm4_25– PM4.25 mass concentration (ug/m^3).pm10– PM10 mass concentration (ug/m^3).pm100– TSP/PM100 mass concentration (ug/m^3).p1– particle count for >0.3 um bin.p2– particle count for >0.5 um bin.p3– particle count for >1.0 um bin.p4– particle count for >2.5 um bin.p5– particle count for >5.0 um bin.p6– particle count for >10 um bin.
- Return type:
- Raises:
Exception – Propagated from
sendCommand()when the sensor fails to respond withinself.TIMEOUTmilliseconds. In practicesendCommand()catches serial errors internally, so a malformed or missing response simply results in zero-valued entries.ValueError – If the response contains non-hexadecimal byte strings that
int(data, 16)cannot parse.
Example
Trigger a fresh measurement and inspect PM2.5:
>>> sensor = CubicPM3006("/dev/ttyAMA1") >>> reading = sensor.getPM(sendCommand=True) >>> reading["pm2_5"] 12.0
Note
Mass-concentration values are decoded from a 32-bit big-endian integer
b3 * 256^3 + b2 * 256^2 + b1 * 256 + b0whereb3..b0are sequential payload bytes. Particle-count decoding uses the same layout but starts at offset 27. A valid frame is at least 16 bytes long and starts with the0x16header byte; shorter or differently-headed frames leave the corresponding entries at their zero defaults. Mirrors the conventions used bydrivers.CubicGeneric.CubicGeneric.CubicGeneric.
- get_serial_number() str¶
Read the factory-programmed serial number from the sensor.
Issues the
SERIAL_NUMBERcommand, waits for the response frame, and reconstructs the device serial number from five consecutive 16-bit big-endian words. Each word is rendered as a zero-padded 4-digit decimal and the segments are concatenated to form a single 20-character string.- Parameters:
None – This method takes no positional or keyword arguments
self. (beyond)
- Returns:
The decoded serial number as a numeric string. Returns an empty string when the response is shorter than expected or does not start with the
0x16header byte.- Return type:
- Raises:
AttributeError – If
self.SERIAL_NUMBERis not defined on the instance. This driver does not declare a default serial-number command; callers are expected to inject one or subclass before invoking this method.Exception – Propagates any underlying serial timeout raised by
sendCommand()if the sensor fails to reply withinself.TIMEOUTmilliseconds.
Example
Read the serial number from a connected sensor:
>>> sensor = CubicPM3006("/dev/ttyAMA1") >>> sensor.SERIAL_NUMBER = [0x11, 0x01, 0x1F, 0xCF] >>> sensor.get_serial_number() '00010002000300040005'
Note
The PM3006 returns its serial number as five 16-bit big-endian fields starting at byte index 3 of the response payload. The 4-digit zero padding preserves leading zeros so that the assembled string is always the same length for a given device family.
- micros() int¶
Return the current wall-clock time in microseconds.
Convenience helper that scales
time.time()to microseconds so that timing logic in this driver reads similarly to the Arduino reference implementations the protocol was first ported from.- Parameters:
None – This method takes no arguments beyond
self.- Returns:
Integer microsecond timestamp since the Unix epoch.
- Return type:
- Raises:
None – This method does not raise under normal operation; it inherits any failure modes of
time.time().
Example
Capture a microsecond timestamp:
>>> sensor = CubicPM3006("/dev/ttyAMA1") >>> isinstance(sensor.micros(), int) True
Note
Resolution is limited to the host clock granularity (typically on the order of a microsecond on Linux). This helper is not monotonic; for elapsed-time measurements that must not be affected by NTP adjustments, prefer
time.monotonic_ns().
- millis() int¶
Return the current wall-clock time in milliseconds.
Used internally by
sendCommand()to implement theTIMEOUTwatchdog while polling for sensor responses.- Parameters:
None – This method takes no arguments beyond
self.- Returns:
Integer millisecond timestamp since the Unix epoch.
- Return type:
- Raises:
None – This method does not raise under normal operation; it inherits any failure modes of
time.time().
Example
Capture a millisecond timestamp:
>>> sensor = CubicPM3006("/dev/ttyAMA1") >>> isinstance(sensor.millis(), int) True
Note
Mirrors the Arduino
millis()API. Not monotonic; prefertime.monotonic()if the timestamps must survive system clock changes.
- sendCommand(command, recieve: bool = True) list[str]¶
Transmit a raw command frame and collect the response bytes.
Re-opens the serial port if it has been closed, writes each command byte individually, waits
RESPONSE_DELAYseconds, and then drains the input buffer one byte at a time. Every received byte is recorded both as a rawbytesobject (for internal debug logging) and as a Python hex string (returned to the caller). The polling loop respectsself.TIMEOUTand raises if the sensor does not start responding in time.- Parameters:
command (
Iterable[int]) – Iterable of byte values (0-255) to transmit to the sensor. Each value is written as a single-bytebytesobject.recieve (
bool) – Legacy flag retained for API compatibility. Currently only controls debug-side behaviour and does not close the port between transactions; preserved to keep downstream callers working unchanged.
- Returns:
Hex-string representations of every byte received from the sensor (for example
["0x16", "0x2d", ...]). Returns whatever was accumulated so far if an exception is caught internally, which may be an empty list.- Return type:
- Raises:
Exception – A generic
Exceptionwith the message"[ERR CUBIC] Timeout CUBIC"is raised internally and caught when the sensor produces no data withinself.TIMEOUTmilliseconds; the caught exception is logged via the context logger.serial.SerialException – Propagated if the underlying serial port fails to (re)open or a write operation errors. The
exceptblock closes the port and returns whatever bytes were captured.
Example
Send the standard read-data command and inspect the response:
>>> sensor = CubicPM3006("/dev/ttyAMA1") >>> sensor.sendCommand(sensor.CUBIC_READ_DATA) ['0x16', '0x2d', '0xb', ...]
Note
The PM3006 expects the entire command frame within a tight window, so this method enforces a 100 ms pre-write delay and a 10 ms inter-byte delay on receive. Enable
self.DEBUGfor a TX/RX trace throughutils.oizom_logger.OizomLogger.
- COMMAND_DELAY = 1¶
- CUBIC_READ_DATA = [b'\x11', b'\x02', b'\x0b', b'\x07', b'\xdb']¶
- DEBUG = False¶
- MAX_BUFFER_SIZE = 51¶
- RESPONSE_DELAY = 0.1¶
- SERIAL_NUMBER = [17, 1, 31, 207]¶
- TIMEOUT = 8000¶
- __cubic_port¶
- _timeout_ = 8¶
- data¶