moat-api-bosch 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moat_api_bosch-0.1.1/LICENSE.txt +26 -0
- moat_api_bosch-0.1.1/Makefile +14 -0
- moat_api_bosch-0.1.1/PKG-INFO +47 -0
- moat_api_bosch-0.1.1/README.md +29 -0
- moat_api_bosch-0.1.1/debian/.gitignore +7 -0
- moat_api_bosch-0.1.1/debian/changelog +11 -0
- moat_api_bosch-0.1.1/debian/control +19 -0
- moat_api_bosch-0.1.1/debian/rules +5 -0
- moat_api_bosch-0.1.1/pyproject.toml +35 -0
- moat_api_bosch-0.1.1/setup.cfg +4 -0
- moat_api_bosch-0.1.1/src/moat/api/bosch/__init__.py +5 -0
- moat_api_bosch-0.1.1/src/moat/api/bosch/bmv080.py +712 -0
- moat_api_bosch-0.1.1/src/moat_api_bosch.egg-info/PKG-INFO +47 -0
- moat_api_bosch-0.1.1/src/moat_api_bosch.egg-info/SOURCES.txt +15 -0
- moat_api_bosch-0.1.1/src/moat_api_bosch.egg-info/dependency_links.txt +1 -0
- moat_api_bosch-0.1.1/src/moat_api_bosch.egg-info/requires.txt +1 -0
- moat_api_bosch-0.1.1/src/moat_api_bosch.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
**
|
|
2
|
+
** This license only applies to the Python wrapper.
|
|
3
|
+
** Any binaries included in this package (or not)
|
|
4
|
+
** are covered by their own license.
|
|
5
|
+
**
|
|
6
|
+
|
|
7
|
+
The MIT License (MIT)
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
10
|
+
a copy of this software and associated documentation files (the
|
|
11
|
+
"Software"), to deal in the Software without restriction, including
|
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
15
|
+
the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be
|
|
18
|
+
included in all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
25
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
26
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/make -f
|
|
2
|
+
|
|
3
|
+
PACKAGE = moat-api-bosch
|
|
4
|
+
MAKEINCL ?= $(shell python3 -mmoat src path)/make/py
|
|
5
|
+
|
|
6
|
+
ifneq ($(wildcard $(MAKEINCL)),)
|
|
7
|
+
include $(MAKEINCL)
|
|
8
|
+
# availabe via http://github.com/smurfix/sourcemgr
|
|
9
|
+
|
|
10
|
+
else
|
|
11
|
+
%:
|
|
12
|
+
@echo "Please fix 'python3 -mmoat src path'."
|
|
13
|
+
@exit 1
|
|
14
|
+
endif
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-api-bosch
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: MoaT API wrappers for Bosch Sensortec chips
|
|
5
|
+
Author-email: Matthias Urlichs <matthias@urlichs.de>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://m-o-a-t.org
|
|
8
|
+
Project-URL: repository, https://github.com/M-o-a-T/moat
|
|
9
|
+
Keywords: MoaT,Bosch,BMV080,sensor
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: cffi
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# MoaT API: Bosch Sensortec
|
|
20
|
+
|
|
21
|
+
% start synopsis
|
|
22
|
+
% start main
|
|
23
|
+
|
|
24
|
+
This module collects CFFI-based Python wrappers for Bosch Sensortec sensors.
|
|
25
|
+
|
|
26
|
+
- BMV080 Particulate Matter Sensor
|
|
27
|
+
|
|
28
|
+
% end synopsis
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- BMV080: the shared libraries (`libbmv080.so` / `bmv080.dll`) must be
|
|
33
|
+
obtained from Bosch Sensortec.
|
|
34
|
+
|
|
35
|
+
## Availability
|
|
36
|
+
|
|
37
|
+
The Bosch libraries may or may not be available for your OS and architecture.
|
|
38
|
+
If they are not, acquire a suitable device (e.g. a Raspberry Pi 4) and use
|
|
39
|
+
MoaT-Link to connect to the library remotely.
|
|
40
|
+
|
|
41
|
+
% end main
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
This library is licensed under the MIT license. The license of the Bosch
|
|
46
|
+
binares unfortunately does not explicitly allow redistribution, thus they
|
|
47
|
+
cannot be included here.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# MoaT API: Bosch Sensortec
|
|
2
|
+
|
|
3
|
+
% start synopsis
|
|
4
|
+
% start main
|
|
5
|
+
|
|
6
|
+
This module collects CFFI-based Python wrappers for Bosch Sensortec sensors.
|
|
7
|
+
|
|
8
|
+
- BMV080 Particulate Matter Sensor
|
|
9
|
+
|
|
10
|
+
% end synopsis
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- BMV080: the shared libraries (`libbmv080.so` / `bmv080.dll`) must be
|
|
15
|
+
obtained from Bosch Sensortec.
|
|
16
|
+
|
|
17
|
+
## Availability
|
|
18
|
+
|
|
19
|
+
The Bosch libraries may or may not be available for your OS and architecture.
|
|
20
|
+
If they are not, acquire a suitable device (e.g. a Raspberry Pi 4) and use
|
|
21
|
+
MoaT-Link to connect to the library remotely.
|
|
22
|
+
|
|
23
|
+
% end main
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
This library is licensed under the MIT license. The license of the Bosch
|
|
28
|
+
binares unfortunately does not explicitly allow redistribution, thus they
|
|
29
|
+
cannot be included here.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
moat-api-bosch (0.1.1-3) unstable; urgency=medium
|
|
2
|
+
|
|
3
|
+
* New release for 26.0.0
|
|
4
|
+
|
|
5
|
+
-- Matthias Urlichs <matthias@urlichs.de> Sun, 08 Feb 2026 11:05:50 +0100
|
|
6
|
+
|
|
7
|
+
moat-api-bosch (0.1.1-3) unstable; urgency=medium
|
|
8
|
+
|
|
9
|
+
* Initial release for 26.0.0
|
|
10
|
+
|
|
11
|
+
-- Matthias Urlichs <matthias@urlichs.de> Sun, 08 Feb 2026 11:01:04 +0100
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Source: moat-api-bosch
|
|
2
|
+
Maintainer: "Matthias Urlichs" <matthias@urlichs.de>
|
|
3
|
+
Section: python
|
|
4
|
+
Priority: optional
|
|
5
|
+
Build-Depends: dh-python, python3-all, debhelper (>= 13),
|
|
6
|
+
python3-setuptools,
|
|
7
|
+
python3-wheel,
|
|
8
|
+
python3-anyio (>= 4.0),
|
|
9
|
+
python3-cffi,
|
|
10
|
+
Standards-Version: 3.9.6
|
|
11
|
+
Homepage: https://m-o-a-t.org
|
|
12
|
+
X-DH-Compat: 13
|
|
13
|
+
|
|
14
|
+
Package: python3-moat-api-bosch
|
|
15
|
+
Architecture: all
|
|
16
|
+
Depends: ${misc:Depends}, ${python3:Depends},
|
|
17
|
+
python3-anyio (>= 4.0),
|
|
18
|
+
Description: APIs for Bosch sensors
|
|
19
|
+
This package wraps various Bosch Sensortec libraries.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [ "setuptools", "wheel",]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
classifiers = [
|
|
7
|
+
"Intended Audience :: Developers",
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"Development Status :: 3 - Alpha",
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"cffi",
|
|
13
|
+
]
|
|
14
|
+
version = "0.1.1"
|
|
15
|
+
keywords = [ "MoaT", "Bosch", "BMV080", "sensor",]
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
|
+
name = "moat-api-bosch"
|
|
18
|
+
description = "MoaT API wrappers for Bosch Sensortec chips"
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
license = "MIT"
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
email = "matthias@urlichs.de"
|
|
24
|
+
name = "Matthias Urlichs"
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
homepage = "https://m-o-a-t.org"
|
|
28
|
+
repository = "https://github.com/M-o-a-T/moat"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
"*" = ["*.yaml"]
|
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFFI-based interface to the Bosch BMV080 particulate matter sensor.
|
|
3
|
+
|
|
4
|
+
This module provides a Python wrapper around the BMV080 C library,
|
|
5
|
+
allowing measurement of PM1, PM2.5, and PM10 concentrations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from anyio import ContextManagerMixin
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
|
|
16
|
+
from moat.api._dll import DLL
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from cffi import FFI
|
|
22
|
+
|
|
23
|
+
from collections.abc import Generator
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"BMV080",
|
|
27
|
+
"BMV080Error",
|
|
28
|
+
"BMV080Output",
|
|
29
|
+
"BMV080_Link",
|
|
30
|
+
"DutyCyclingMode",
|
|
31
|
+
"MeasurementAlgorithm",
|
|
32
|
+
"StatusCode",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Required library version: >= 11.2.0, < 12.0.0
|
|
36
|
+
_MIN_VERSION = (24, 2, 0)
|
|
37
|
+
_MAX_VERSION = (25, 0, 0)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StatusCode(IntEnum):
|
|
41
|
+
"""Status codes returned by the BMV080 sensor driver."""
|
|
42
|
+
|
|
43
|
+
OK = 0
|
|
44
|
+
|
|
45
|
+
# Warnings
|
|
46
|
+
WARNING_INVALID_REG_READ = 1
|
|
47
|
+
WARNING_INVALID_REG_WRITE = 2
|
|
48
|
+
WARNING_FIFO_READ = 3
|
|
49
|
+
WARNING_FIFO_EVENTS_OVERFLOW = 4
|
|
50
|
+
WARNING_FIFO_SW_BUFFER_SIZE = 208
|
|
51
|
+
WARNING_FIFO_HW_BUFFER_SIZE = 209
|
|
52
|
+
|
|
53
|
+
# Errors
|
|
54
|
+
ERROR_NULLPTR = 100
|
|
55
|
+
ERROR_REG_ADDR = 101
|
|
56
|
+
ERROR_PARAM_LOCKED = 179
|
|
57
|
+
ERROR_PARAM_INVALID = 115
|
|
58
|
+
ERROR_PARAM_INVALID_CHANNEL = 102
|
|
59
|
+
ERROR_PARAM_INVALID_VALUE = 123
|
|
60
|
+
ERROR_PARAM_INVALID_INTERNAL_CONFIG = 104
|
|
61
|
+
ERROR_PRECONDITION_UNSATISFIED = 180
|
|
62
|
+
ERROR_HW_READ = 105
|
|
63
|
+
ERROR_HW_WRITE = 106
|
|
64
|
+
ERROR_MISMATCH_CHIP_ID = 107
|
|
65
|
+
ERROR_MISMATCH_REG_VALUE = 160
|
|
66
|
+
ERROR_OPERATION_MODE_INVALID = 116
|
|
67
|
+
ERROR_OPERATION_MODE_CHANGE = 113
|
|
68
|
+
ERROR_OPERATION_MODE_CHANNELS_OUT_OF_SYNC = 114
|
|
69
|
+
ERROR_ASIC_NOT_CONFIGURED = 157
|
|
70
|
+
ERROR_MEM_READ = 133
|
|
71
|
+
ERROR_MEM_ADDRESS = 135
|
|
72
|
+
ERROR_MEM_CMD = 136
|
|
73
|
+
ERROR_MEM_TIMEOUT = 137
|
|
74
|
+
ERROR_MEM_INVALID = 138
|
|
75
|
+
ERROR_MEM_OBSOLETE = 139
|
|
76
|
+
ERROR_MEM_OPERATION_MODE = 140
|
|
77
|
+
ERROR_MEM_DATA_INTEGRITY = 153
|
|
78
|
+
ERROR_MEM_DATA_INTEGRITY_INTERNAL_TEST_1 = 154
|
|
79
|
+
ERROR_MEM_INTERNAL_TEST_1 = 156
|
|
80
|
+
ERROR_MEM_DATA_INTEGRITY_INTERNAL_TEST_2 = 159
|
|
81
|
+
ERROR_MEM_INTERNAL_TEST_2 = 181
|
|
82
|
+
ERROR_FIFO_FORMAT = 210
|
|
83
|
+
ERROR_FIFO_EVENTS_COUNT_SATURATED = 213
|
|
84
|
+
ERROR_FIFO_UNAVAILABLE = 174
|
|
85
|
+
ERROR_FIFO_EVENTS_COUNT_DIFF = 214
|
|
86
|
+
ERROR_SYNC_COMM = 161
|
|
87
|
+
ERROR_SYNC_CTRL = 162
|
|
88
|
+
ERROR_SYNC_MEAS = 163
|
|
89
|
+
ERROR_SYNC_LOCKED = 164
|
|
90
|
+
ERROR_DC_CANCEL_RANGE = 165
|
|
91
|
+
ERROR_DC_ESTIM_RANGE = 166
|
|
92
|
+
ERROR_LPWR_T = 167
|
|
93
|
+
ERROR_LPWR_RANGE = 168
|
|
94
|
+
ERROR_POWER_DOMAIN = 169
|
|
95
|
+
ERROR_HEADROOM_VDDL = 170
|
|
96
|
+
ERROR_HEADROOM_LDV_OUTPUT = 171
|
|
97
|
+
ERROR_HEADROOM_LDV_REF = 172
|
|
98
|
+
ERROR_HEADROOM_INTERNAL = 173
|
|
99
|
+
ERROR_SAFETY_PRECAUTION = 120
|
|
100
|
+
ERROR_TIMESTAMP_DIFFERENCE = 211
|
|
101
|
+
ERROR_TIMESTAMP_OVERFLOW = 212
|
|
102
|
+
ERROR_LIB_VERSION_INCOMPATIBLE = 300
|
|
103
|
+
ERROR_INTERNAL_PARAMETER_VERSION_INVALID = 301
|
|
104
|
+
ERROR_INTERNAL_PARAMETER_INDEX_INVALID = 302
|
|
105
|
+
ERROR_MEMORY_ALLOCATION = 403
|
|
106
|
+
ERROR_CALLBACK_DELAY = 303
|
|
107
|
+
ERROR_INCOMPATIBLE_SENSOR_HW = 418
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class MeasurementAlgorithm(IntEnum):
|
|
111
|
+
"""Measurement algorithm choices."""
|
|
112
|
+
|
|
113
|
+
FAST_RESPONSE = 1
|
|
114
|
+
BALANCED = 2
|
|
115
|
+
HIGH_PRECISION = 3
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class DutyCyclingMode(IntEnum):
|
|
119
|
+
"""Modes of performing a duty cycling measurement."""
|
|
120
|
+
|
|
121
|
+
MODE_0 = 0 # Fixed duty cycle, ON time = integration_time, OFF time = sleep time
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class BMV080Error(Exception):
|
|
125
|
+
"""Exception raised for BMV080 errors."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, status: StatusCode, message: str = "") -> None:
|
|
128
|
+
self.status = status
|
|
129
|
+
name_val = f"{status.name} ({status.value})"
|
|
130
|
+
super().__init__(f"{name_val}: {message}" if message else name_val)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class BMV080Output:
|
|
135
|
+
"""Output data from BMV080 sensor measurement.
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
runtime_in_sec: Estimate of time passed since measurement start, in seconds.
|
|
139
|
+
pm2_5_mass_concentration: PM2.5 value in µg/m³.
|
|
140
|
+
pm1_mass_concentration: PM1 value in µg/m³.
|
|
141
|
+
pm10_mass_concentration: PM10 value in µg/m³.
|
|
142
|
+
pm2_5_number_concentration: PM2.5 value in particles/cm³.
|
|
143
|
+
pm1_number_concentration: PM1 value in particles/cm³.
|
|
144
|
+
pm10_number_concentration: PM10 value in particles/cm³.
|
|
145
|
+
is_obstructed: Whether the sensor is obstructed.
|
|
146
|
+
is_outside_measurement_range: Whether PM2.5 is outside 0..1000 µg/m³.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
runtime_in_sec: float
|
|
150
|
+
pm2_5_mass_concentration: float
|
|
151
|
+
pm1_mass_concentration: float
|
|
152
|
+
pm10_mass_concentration: float
|
|
153
|
+
pm2_5_number_concentration: float
|
|
154
|
+
pm1_number_concentration: float
|
|
155
|
+
pm10_number_concentration: float
|
|
156
|
+
is_obstructed: bool
|
|
157
|
+
is_outside_measurement_range: bool
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@runtime_checkable
|
|
161
|
+
class BMV080_Link(Protocol):
|
|
162
|
+
"""Protocol for serial communication interface to BMV080.
|
|
163
|
+
|
|
164
|
+
The caller must provide an object implementing these methods.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def read(self, header: int, length: int) -> list[int]:
|
|
168
|
+
"""Read data from the sensor.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
header: 16-bit header information.
|
|
172
|
+
length: Number of 16-bit words to read.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of 16-bit words read from sensor.
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
IOError: If read fails.
|
|
179
|
+
"""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
def write(self, header: int, payload: list[int]) -> None:
|
|
183
|
+
"""Write data to the sensor.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
header: 16-bit header information.
|
|
187
|
+
payload: List of 16-bit words to write.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
IOError: If write fails.
|
|
191
|
+
"""
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
def delay_ms(self, duration_ms: int) -> None:
|
|
195
|
+
"""Delay for specified milliseconds.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
duration_ms: Duration in milliseconds.
|
|
199
|
+
"""
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
def time_ms(self) -> int:
|
|
203
|
+
"""Get current tick value in milliseconds.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Current tick value for timing purposes.
|
|
207
|
+
"""
|
|
208
|
+
...
|
|
209
|
+
|
|
210
|
+
def process(self, output: BMV080Output) -> None:
|
|
211
|
+
"""Process a measurement output.
|
|
212
|
+
|
|
213
|
+
Override this method to handle measurements. Called for each
|
|
214
|
+
output from serve_interrupt().
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
output: Measurement data from the sensor.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# C declarations for CFFI
|
|
222
|
+
_CDEF = """
|
|
223
|
+
typedef int8_t bmv080_status_code_t;
|
|
224
|
+
typedef void* bmv080_handle_t;
|
|
225
|
+
typedef void* bmv080_sercom_handle_t;
|
|
226
|
+
|
|
227
|
+
typedef enum {
|
|
228
|
+
E_BMV080_DUTY_CYCLING_MODE_0 = 0
|
|
229
|
+
} bmv080_duty_cycling_mode_t;
|
|
230
|
+
|
|
231
|
+
typedef enum {
|
|
232
|
+
E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE = 1,
|
|
233
|
+
E_BMV080_MEASUREMENT_ALGORITHM_BALANCED = 2,
|
|
234
|
+
E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION = 3
|
|
235
|
+
} bmv080_measurement_algorithm_t;
|
|
236
|
+
|
|
237
|
+
struct bmv080_extended_info_s;
|
|
238
|
+
|
|
239
|
+
typedef struct {
|
|
240
|
+
float runtime_in_sec;
|
|
241
|
+
float pm2_5_mass_concentration;
|
|
242
|
+
float pm1_mass_concentration;
|
|
243
|
+
float pm10_mass_concentration;
|
|
244
|
+
float pm2_5_number_concentration;
|
|
245
|
+
float pm1_number_concentration;
|
|
246
|
+
float pm10_number_concentration;
|
|
247
|
+
bool is_obstructed;
|
|
248
|
+
bool is_outside_measurement_range;
|
|
249
|
+
float reserved_0;
|
|
250
|
+
float reserved_1;
|
|
251
|
+
float reserved_2;
|
|
252
|
+
struct bmv080_extended_info_s *extended_info;
|
|
253
|
+
} bmv080_output_t;
|
|
254
|
+
|
|
255
|
+
typedef int8_t(*bmv080_callback_read_t)(bmv080_sercom_handle_t sercom_handle, uint16_t header,
|
|
256
|
+
uint16_t* payload, uint16_t payload_length);
|
|
257
|
+
typedef int8_t(*bmv080_callback_write_t)(bmv080_sercom_handle_t sercom_handle, uint16_t header,
|
|
258
|
+
const uint16_t* payload, uint16_t payload_length);
|
|
259
|
+
typedef int8_t(*bmv080_callback_delay_t)(uint32_t duration_in_ms);
|
|
260
|
+
typedef uint32_t(*bmv080_callback_tick_t)(void);
|
|
261
|
+
typedef void(*bmv080_callback_data_ready_t)(bmv080_output_t bmv080_output,
|
|
262
|
+
void* callback_parameters);
|
|
263
|
+
|
|
264
|
+
bmv080_status_code_t bmv080_open(bmv080_handle_t* handle,
|
|
265
|
+
const bmv080_sercom_handle_t sercom_handle, const bmv080_callback_read_t read,
|
|
266
|
+
const bmv080_callback_write_t write, const bmv080_callback_delay_t delay_ms);
|
|
267
|
+
|
|
268
|
+
bmv080_status_code_t bmv080_get_driver_version(uint16_t* major, uint16_t* minor, uint16_t* patch,
|
|
269
|
+
char git_hash[12], int32_t* num_commits_ahead);
|
|
270
|
+
|
|
271
|
+
bmv080_status_code_t bmv080_reset(const bmv080_handle_t handle);
|
|
272
|
+
|
|
273
|
+
bmv080_status_code_t bmv080_get_parameter(const bmv080_handle_t handle,
|
|
274
|
+
const char* key, void* value);
|
|
275
|
+
|
|
276
|
+
bmv080_status_code_t bmv080_set_parameter(const bmv080_handle_t handle, const char* key,
|
|
277
|
+
const void* value);
|
|
278
|
+
|
|
279
|
+
bmv080_status_code_t bmv080_get_sensor_id(const bmv080_handle_t handle, char id[13]);
|
|
280
|
+
|
|
281
|
+
bmv080_status_code_t bmv080_start_continuous_measurement(const bmv080_handle_t handle);
|
|
282
|
+
|
|
283
|
+
bmv080_status_code_t bmv080_start_duty_cycling_measurement(const bmv080_handle_t handle,
|
|
284
|
+
const bmv080_callback_tick_t get_tick_ms, bmv080_duty_cycling_mode_t duty_cycling_mode);
|
|
285
|
+
|
|
286
|
+
bmv080_status_code_t bmv080_serve_interrupt(const bmv080_handle_t handle,
|
|
287
|
+
bmv080_callback_data_ready_t data_ready, void* callback_parameters);
|
|
288
|
+
|
|
289
|
+
bmv080_status_code_t bmv080_stop_measurement(const bmv080_handle_t handle);
|
|
290
|
+
|
|
291
|
+
bmv080_status_code_t bmv080_close(bmv080_handle_t* handle);
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class BMV080(ContextManagerMixin):
|
|
296
|
+
"""CFFI-based interface to the BMV080 particulate matter sensor.
|
|
297
|
+
|
|
298
|
+
This class wraps the BMV080 C library, providing a Pythonic interface
|
|
299
|
+
for measuring particulate matter concentrations.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
link: Serial communication interface implementing read/write/delay_ms/time_ms.
|
|
303
|
+
libs_path: Path to the BMV080 shared libraries (.so/.dll). The lib_postProcessor.so
|
|
304
|
+
library must be loaded first.
|
|
305
|
+
|
|
306
|
+
Example:
|
|
307
|
+
>>> class MySPI:
|
|
308
|
+
... def read(self, header, length): ...
|
|
309
|
+
... def write(self, header, payload): ...
|
|
310
|
+
... def delay_ms(self, ms): ...
|
|
311
|
+
... def time_ms(self): ...
|
|
312
|
+
>>> with BMV080(MySPI(), "/path/to/libbmv080.so") as sensor:
|
|
313
|
+
... sensor.start_continuous_measurement()
|
|
314
|
+
... sensor.serve_interrupt()
|
|
315
|
+
... sensor.stop_measurement()
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def __init__(self, link: BMV080_Link, libs_path: str) -> None:
|
|
319
|
+
"""Initialize BMV080 interface.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
link: Object with read/write/delay_ms/time_ms methods.
|
|
323
|
+
libs_path: Paths to the BMV080 shared libraries, colon-separated
|
|
324
|
+
(use semicolon on Windows).
|
|
325
|
+
"""
|
|
326
|
+
self._link = link
|
|
327
|
+
self._libs_path = libs_path
|
|
328
|
+
|
|
329
|
+
self._ffi: FFI | None = None
|
|
330
|
+
self._lib = None
|
|
331
|
+
self._handle = None
|
|
332
|
+
self._handle_ptr = None
|
|
333
|
+
|
|
334
|
+
# Store callbacks to prevent garbage collection
|
|
335
|
+
self._read_cb = None
|
|
336
|
+
self._write_cb = None
|
|
337
|
+
self._delay_cb = None
|
|
338
|
+
self._tick_cb = None
|
|
339
|
+
self._data_ready_cb = None
|
|
340
|
+
|
|
341
|
+
# Store exception from callback for re-raising
|
|
342
|
+
self._logged_exc: BaseException | None = None
|
|
343
|
+
|
|
344
|
+
@contextmanager
|
|
345
|
+
def __contextmanager__(self) -> Generator[Self]:
|
|
346
|
+
with DLL("moat.api.bosch.bmv080", _CDEF, *self._libs_path.split(os.pathsep)) as (
|
|
347
|
+
self._ffi,
|
|
348
|
+
self._lib,
|
|
349
|
+
):
|
|
350
|
+
try:
|
|
351
|
+
self._open()
|
|
352
|
+
yield self
|
|
353
|
+
finally:
|
|
354
|
+
self._close()
|
|
355
|
+
|
|
356
|
+
def _log_exc(self, exc: BaseException) -> None:
|
|
357
|
+
"""Store an exception from a callback for later re-raising.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
exc: The exception to store.
|
|
361
|
+
"""
|
|
362
|
+
if self._logged_exc is None:
|
|
363
|
+
self._logged_exc = exc
|
|
364
|
+
|
|
365
|
+
def _check_status(self, status: int) -> None:
|
|
366
|
+
"""Check status code and raise exception if error.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
status: Status code from C library.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
BMV080Error: If status indicates an error (>= 100).
|
|
373
|
+
BaseException: Re-raises stored callback exception if status is -1.
|
|
374
|
+
"""
|
|
375
|
+
if status == -1 and self._logged_exc is not None:
|
|
376
|
+
exc = self._logged_exc
|
|
377
|
+
self._logged_exc = None
|
|
378
|
+
raise exc
|
|
379
|
+
if status >= 100:
|
|
380
|
+
raise BMV080Error(StatusCode(status))
|
|
381
|
+
|
|
382
|
+
def _open(self) -> None:
|
|
383
|
+
"""Open connection to the BMV080 sensor.
|
|
384
|
+
|
|
385
|
+
Initializes the C library and creates a sensor handle.
|
|
386
|
+
Verifies that the library version is compatible (>= 11.2, < 12).
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
BMV080Error: If opening fails.
|
|
390
|
+
FileNotFoundError: If library file not found.
|
|
391
|
+
RuntimeError: If library version is incompatible.
|
|
392
|
+
"""
|
|
393
|
+
if self._handle is not None:
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
# Check library version before proceeding
|
|
397
|
+
version = self._get_driver_version_raw()
|
|
398
|
+
if version < _MIN_VERSION or version >= _MAX_VERSION:
|
|
399
|
+
min_str = ".".join(str(x) for x in _MIN_VERSION)
|
|
400
|
+
max_str = ".".join(str(x) for x in _MAX_VERSION)
|
|
401
|
+
ver_str = ".".join(str(x) for x in version)
|
|
402
|
+
raise RuntimeError(
|
|
403
|
+
f"BMV080 library version {ver_str} not supported "
|
|
404
|
+
f"(requires >= {min_str}, < {max_str})"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Create callbacks
|
|
408
|
+
# sercom_handle is passed by the C library but unused; we use self._link
|
|
409
|
+
@self._ffi.callback("bmv080_callback_read_t")
|
|
410
|
+
def read_cb(
|
|
411
|
+
sercom_handle: object, # noqa: ARG001
|
|
412
|
+
header: int,
|
|
413
|
+
payload: object,
|
|
414
|
+
length: int,
|
|
415
|
+
) -> int:
|
|
416
|
+
try:
|
|
417
|
+
data = self._link.read(header, length)
|
|
418
|
+
for i, val in enumerate(data[:length]):
|
|
419
|
+
payload[i] = val
|
|
420
|
+
return 0
|
|
421
|
+
except BaseException as exc:
|
|
422
|
+
self._log_exc(exc)
|
|
423
|
+
return -1
|
|
424
|
+
|
|
425
|
+
@self._ffi.callback("bmv080_callback_write_t")
|
|
426
|
+
def write_cb(
|
|
427
|
+
sercom_handle: object, # noqa: ARG001
|
|
428
|
+
header: int,
|
|
429
|
+
payload: object,
|
|
430
|
+
length: int,
|
|
431
|
+
) -> int:
|
|
432
|
+
try:
|
|
433
|
+
data = [payload[i] for i in range(length)]
|
|
434
|
+
self._link.write(header, data)
|
|
435
|
+
return 0
|
|
436
|
+
except BaseException as exc:
|
|
437
|
+
self._log_exc(exc)
|
|
438
|
+
return -1
|
|
439
|
+
|
|
440
|
+
@self._ffi.callback("bmv080_callback_delay_t")
|
|
441
|
+
def delay_cb(duration_ms: int) -> int:
|
|
442
|
+
try:
|
|
443
|
+
self._link.delay_ms(duration_ms)
|
|
444
|
+
return 0
|
|
445
|
+
except BaseException as exc:
|
|
446
|
+
self._log_exc(exc)
|
|
447
|
+
return -1
|
|
448
|
+
|
|
449
|
+
# params is passed by the C library but unused
|
|
450
|
+
@self._ffi.callback("bmv080_callback_data_ready_t")
|
|
451
|
+
def data_ready_cb(output: object, _params: object) -> None:
|
|
452
|
+
out = BMV080Output(
|
|
453
|
+
runtime_in_sec=output.runtime_in_sec,
|
|
454
|
+
pm2_5_mass_concentration=output.pm2_5_mass_concentration,
|
|
455
|
+
pm1_mass_concentration=output.pm1_mass_concentration,
|
|
456
|
+
pm10_mass_concentration=output.pm10_mass_concentration,
|
|
457
|
+
pm2_5_number_concentration=output.pm2_5_number_concentration,
|
|
458
|
+
pm1_number_concentration=output.pm1_number_concentration,
|
|
459
|
+
pm10_number_concentration=output.pm10_number_concentration,
|
|
460
|
+
is_obstructed=output.is_obstructed,
|
|
461
|
+
is_outside_measurement_range=output.is_outside_measurement_range,
|
|
462
|
+
)
|
|
463
|
+
self._link.process(out)
|
|
464
|
+
|
|
465
|
+
self._read_cb = read_cb
|
|
466
|
+
self._write_cb = write_cb
|
|
467
|
+
self._delay_cb = delay_cb
|
|
468
|
+
self._data_ready_cb = data_ready_cb
|
|
469
|
+
|
|
470
|
+
# Create handle
|
|
471
|
+
self._handle_ptr = self._ffi.new("bmv080_handle_t*")
|
|
472
|
+
status = self._lib.bmv080_open(
|
|
473
|
+
self._handle_ptr,
|
|
474
|
+
self._handle_ptr, # self._ffi.NULL -- not used but lib complains if NULL
|
|
475
|
+
self._read_cb,
|
|
476
|
+
self._write_cb,
|
|
477
|
+
self._delay_cb,
|
|
478
|
+
)
|
|
479
|
+
self._check_status(status)
|
|
480
|
+
self._handle = self._handle_ptr[0]
|
|
481
|
+
|
|
482
|
+
def _get_driver_version_raw(self) -> tuple[int, int, int]:
|
|
483
|
+
"""Get the driver version without requiring an open handle.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Tuple of (major, minor, patch) version numbers.
|
|
487
|
+
|
|
488
|
+
Raises:
|
|
489
|
+
BMV080Error: If getting version fails.
|
|
490
|
+
"""
|
|
491
|
+
major = self._ffi.new("uint16_t*")
|
|
492
|
+
minor = self._ffi.new("uint16_t*")
|
|
493
|
+
patch = self._ffi.new("uint16_t*")
|
|
494
|
+
git_hash = self._ffi.new("char[12]")
|
|
495
|
+
commits = self._ffi.new("int32_t*")
|
|
496
|
+
|
|
497
|
+
status = self._lib.bmv080_get_driver_version(major, minor, patch, git_hash, commits)
|
|
498
|
+
self._check_status(status)
|
|
499
|
+
return (major[0], minor[0], patch[0])
|
|
500
|
+
|
|
501
|
+
def _close(self) -> None:
|
|
502
|
+
"""Close connection to the BMV080 sensor.
|
|
503
|
+
|
|
504
|
+
Releases the sensor handle and resources.
|
|
505
|
+
"""
|
|
506
|
+
if self._handle is not None:
|
|
507
|
+
self._lib.bmv080_close(self._handle_ptr)
|
|
508
|
+
self._handle = None
|
|
509
|
+
|
|
510
|
+
self._handle_ptr = None
|
|
511
|
+
self._read_cb = None
|
|
512
|
+
self._write_cb = None
|
|
513
|
+
self._delay_cb = None
|
|
514
|
+
self._tick_cb = None
|
|
515
|
+
self._data_ready_cb = None
|
|
516
|
+
self._lib = None
|
|
517
|
+
self._ffi = None
|
|
518
|
+
self._logged_exc = None
|
|
519
|
+
|
|
520
|
+
def reset(self) -> None:
|
|
521
|
+
"""Reset the sensor including hardware and software.
|
|
522
|
+
|
|
523
|
+
All parameters revert to defaults.
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
BMV080Error: If reset fails.
|
|
527
|
+
"""
|
|
528
|
+
status = self._lib.bmv080_reset(self._handle)
|
|
529
|
+
self._check_status(status)
|
|
530
|
+
|
|
531
|
+
def get_driver_version(self) -> tuple[int, int, int]:
|
|
532
|
+
"""Get the driver version.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Tuple of (major, minor, patch) version numbers.
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
BMV080Error: If getting version fails.
|
|
539
|
+
"""
|
|
540
|
+
return self._get_driver_version_raw()
|
|
541
|
+
|
|
542
|
+
def get_sensor_id(self) -> str:
|
|
543
|
+
"""Get the sensor ID.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
13-character sensor ID string.
|
|
547
|
+
|
|
548
|
+
Raises:
|
|
549
|
+
BMV080Error: If getting ID fails.
|
|
550
|
+
"""
|
|
551
|
+
id_buf = self._ffi.new("char[13]")
|
|
552
|
+
status = self._lib.bmv080_get_sensor_id(self._handle, id_buf)
|
|
553
|
+
self._check_status(status)
|
|
554
|
+
return self._ffi.string(id_buf).decode("ascii")
|
|
555
|
+
|
|
556
|
+
def set_parameter(self, key: str, value: bool | float | str) -> None:
|
|
557
|
+
"""Set a sensor parameter.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
key: Parameter key (e.g., "integration_time", "do_vibration_filtering").
|
|
561
|
+
value: Parameter value of appropriate type. Use int for integer params.
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
BMV080Error: If setting parameter fails.
|
|
565
|
+
TypeError: If value type is not supported.
|
|
566
|
+
"""
|
|
567
|
+
key_bytes = key.encode("utf-8")
|
|
568
|
+
|
|
569
|
+
if isinstance(value, bool):
|
|
570
|
+
val_ptr = self._ffi.new("bool*", value)
|
|
571
|
+
elif isinstance(value, int):
|
|
572
|
+
# int is a subtype of float in type hints but not at runtime
|
|
573
|
+
val_ptr = self._ffi.new("uint16_t*", value)
|
|
574
|
+
elif isinstance(value, float):
|
|
575
|
+
val_ptr = self._ffi.new("float*", value)
|
|
576
|
+
elif isinstance(value, str):
|
|
577
|
+
val_ptr = self._ffi.new("char[]", value.encode("utf-8"))
|
|
578
|
+
else:
|
|
579
|
+
raise TypeError(f"Unsupported parameter type: {type(value)}")
|
|
580
|
+
|
|
581
|
+
status = self._lib.bmv080_set_parameter(self._handle, key_bytes, val_ptr)
|
|
582
|
+
self._check_status(status)
|
|
583
|
+
|
|
584
|
+
def get_parameter_bool(self, key: str) -> bool:
|
|
585
|
+
"""Get a boolean parameter.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
key: Parameter key.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Parameter value.
|
|
592
|
+
|
|
593
|
+
Raises:
|
|
594
|
+
BMV080Error: If getting parameter fails.
|
|
595
|
+
"""
|
|
596
|
+
key_bytes = key.encode("utf-8")
|
|
597
|
+
val_ptr = self._ffi.new("bool*")
|
|
598
|
+
status = self._lib.bmv080_get_parameter(self._handle, key_bytes, val_ptr)
|
|
599
|
+
self._check_status(status)
|
|
600
|
+
return val_ptr[0]
|
|
601
|
+
|
|
602
|
+
def get_parameter_int(self, key: str) -> int:
|
|
603
|
+
"""Get an integer parameter.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
key: Parameter key.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
Parameter value.
|
|
610
|
+
|
|
611
|
+
Raises:
|
|
612
|
+
BMV080Error: If getting parameter fails.
|
|
613
|
+
"""
|
|
614
|
+
key_bytes = key.encode("utf-8")
|
|
615
|
+
val_ptr = self._ffi.new("uint16_t*")
|
|
616
|
+
status = self._lib.bmv080_get_parameter(self._handle, key_bytes, val_ptr)
|
|
617
|
+
self._check_status(status)
|
|
618
|
+
return val_ptr[0]
|
|
619
|
+
|
|
620
|
+
def get_parameter_float(self, key: str) -> float:
|
|
621
|
+
"""Get a float parameter.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
key: Parameter key.
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
Parameter value.
|
|
628
|
+
|
|
629
|
+
Raises:
|
|
630
|
+
BMV080Error: If getting parameter fails.
|
|
631
|
+
"""
|
|
632
|
+
key_bytes = key.encode("utf-8")
|
|
633
|
+
val_ptr = self._ffi.new("float*")
|
|
634
|
+
status = self._lib.bmv080_get_parameter(self._handle, key_bytes, val_ptr)
|
|
635
|
+
self._check_status(status)
|
|
636
|
+
return val_ptr[0]
|
|
637
|
+
|
|
638
|
+
def get_parameter_str(self, key: str) -> str:
|
|
639
|
+
"""Get a string parameter.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
key: Parameter key.
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
Parameter value (max 256 characters).
|
|
646
|
+
|
|
647
|
+
Raises:
|
|
648
|
+
BMV080Error: If getting parameter fails.
|
|
649
|
+
"""
|
|
650
|
+
key_bytes = key.encode("utf-8")
|
|
651
|
+
val_ptr = self._ffi.new("char[256]")
|
|
652
|
+
status = self._lib.bmv080_get_parameter(self._handle, key_bytes, val_ptr)
|
|
653
|
+
self._check_status(status)
|
|
654
|
+
return self._ffi.string(val_ptr).decode("utf-8")
|
|
655
|
+
|
|
656
|
+
def start_continuous_measurement(self) -> None:
|
|
657
|
+
"""Start particle measurement in continuous mode.
|
|
658
|
+
|
|
659
|
+
The sensor stays in measurement mode until stop_measurement() is called.
|
|
660
|
+
Call serve_interrupt() regularly to get data.
|
|
661
|
+
|
|
662
|
+
Raises:
|
|
663
|
+
BMV080Error: If starting measurement fails.
|
|
664
|
+
"""
|
|
665
|
+
status = self._lib.bmv080_start_continuous_measurement(self._handle)
|
|
666
|
+
self._check_status(status)
|
|
667
|
+
|
|
668
|
+
def start_duty_cycling_measurement(
|
|
669
|
+
self,
|
|
670
|
+
mode: DutyCyclingMode = DutyCyclingMode.MODE_0,
|
|
671
|
+
) -> None:
|
|
672
|
+
"""Start particle measurement in duty cycling mode.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
mode: Duty cycling mode (currently only MODE_0).
|
|
676
|
+
|
|
677
|
+
Raises:
|
|
678
|
+
BMV080Error: If starting measurement fails.
|
|
679
|
+
"""
|
|
680
|
+
|
|
681
|
+
@self._ffi.callback("bmv080_callback_tick_t")
|
|
682
|
+
def tick_cb() -> int:
|
|
683
|
+
return self._link.time_ms() & 0xFFFFFFFF
|
|
684
|
+
|
|
685
|
+
self._tick_cb = tick_cb
|
|
686
|
+
|
|
687
|
+
status = self._lib.bmv080_start_duty_cycling_measurement(self._handle, self._tick_cb, mode)
|
|
688
|
+
self._check_status(status)
|
|
689
|
+
|
|
690
|
+
def stop_measurement(self) -> None:
|
|
691
|
+
"""Stop particle measurement.
|
|
692
|
+
|
|
693
|
+
Raises:
|
|
694
|
+
BMV080Error: If stopping measurement fails.
|
|
695
|
+
"""
|
|
696
|
+
status = self._lib.bmv080_stop_measurement(self._handle)
|
|
697
|
+
self._check_status(status)
|
|
698
|
+
|
|
699
|
+
def serve_interrupt(self) -> None:
|
|
700
|
+
"""Service an interrupt.
|
|
701
|
+
|
|
702
|
+
Should be called regularly (at least once per second in duty cycling mode).
|
|
703
|
+
Calls the process() hook for each available output.
|
|
704
|
+
|
|
705
|
+
Raises:
|
|
706
|
+
BMV080Error: If serving interrupt fails.
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
status = self._lib.bmv080_serve_interrupt(
|
|
710
|
+
self._handle, self._data_ready_cb, self._ffi.NULL
|
|
711
|
+
)
|
|
712
|
+
self._check_status(status)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-api-bosch
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: MoaT API wrappers for Bosch Sensortec chips
|
|
5
|
+
Author-email: Matthias Urlichs <matthias@urlichs.de>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://m-o-a-t.org
|
|
8
|
+
Project-URL: repository, https://github.com/M-o-a-T/moat
|
|
9
|
+
Keywords: MoaT,Bosch,BMV080,sensor
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: cffi
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# MoaT API: Bosch Sensortec
|
|
20
|
+
|
|
21
|
+
% start synopsis
|
|
22
|
+
% start main
|
|
23
|
+
|
|
24
|
+
This module collects CFFI-based Python wrappers for Bosch Sensortec sensors.
|
|
25
|
+
|
|
26
|
+
- BMV080 Particulate Matter Sensor
|
|
27
|
+
|
|
28
|
+
% end synopsis
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- BMV080: the shared libraries (`libbmv080.so` / `bmv080.dll`) must be
|
|
33
|
+
obtained from Bosch Sensortec.
|
|
34
|
+
|
|
35
|
+
## Availability
|
|
36
|
+
|
|
37
|
+
The Bosch libraries may or may not be available for your OS and architecture.
|
|
38
|
+
If they are not, acquire a suitable device (e.g. a Raspberry Pi 4) and use
|
|
39
|
+
MoaT-Link to connect to the library remotely.
|
|
40
|
+
|
|
41
|
+
% end main
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
This library is licensed under the MIT license. The license of the Bosch
|
|
46
|
+
binares unfortunately does not explicitly allow redistribution, thus they
|
|
47
|
+
cannot be included here.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
Makefile
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
debian/.gitignore
|
|
6
|
+
debian/changelog
|
|
7
|
+
debian/control
|
|
8
|
+
debian/rules
|
|
9
|
+
src/moat/api/bosch/__init__.py
|
|
10
|
+
src/moat/api/bosch/bmv080.py
|
|
11
|
+
src/moat_api_bosch.egg-info/PKG-INFO
|
|
12
|
+
src/moat_api_bosch.egg-info/SOURCES.txt
|
|
13
|
+
src/moat_api_bosch.egg-info/dependency_links.txt
|
|
14
|
+
src/moat_api_bosch.egg-info/requires.txt
|
|
15
|
+
src/moat_api_bosch.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cffi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moat
|