naneos
naneos-devices: talk to naneos particle solutions devices over serial and BLE.
ConnectionType
Bases: StrEnum
How a data point reached us. Ordered by trust: serial > connected.
Source code in src/naneos/data_point.py
26 27 28 29 30 | |
DeviceType
Bases: IntEnum
Numeric device type as used by the upload backend. Do not renumber.
Source code in src/naneos/data_point.py
8 9 10 11 12 13 14 15 16 17 18 19 | |
from_name(name)
classmethod
The type a device names itself in answer to "name?" (same over USB and BLE).
Source code in src/naneos/data_point.py
16 17 18 19 | |
NaneosDeviceDataPoint
dataclass
One measurement of one device. Every field is optional; None means "not reported".
Source code in src/naneos/data_point.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
to_dict(remove_nan=True)
Field values by name; with remove_nan only the fields that are set.
Source code in src/naneos/data_point.py
99 100 101 102 103 104 | |
NaneosDeviceManager
Bases: Thread
Connects to every Partector on USB and BLE, gathers their data in snapshots and hands each snapshot to the output queue and / or the naneos upload.
Source code in src/naneos/manager.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 | |
diagnostics_in_progress
property
True while the manager is reading the diagnostics of its devices.
diagnostics_interval_hours
property
writable
How often the UI curve and the pulse form of every connected device are read and uploaded, 0.5 to 24 hours, None for never. The readouts happen at the wall clock multiples of the interval (with 1 h: on the hour), one device after the other, so a change takes effect at the next multiple; request_diagnostics() reads now.
A UI curve sweep disturbs the measurement: the data points of the device are held back for about 15 s (USB) to 30 s (BLE) per readout.
gathering_interval_seconds
property
writable
Snapshot interval; values are clamped to 10-600 s.
live_points_dropped
property
Points dropped from the live queue because it was full.
pending_diagnostics_count
property
Number of UI curves and pulse forms waiting to be uploaded, including retries.
pending_upload_bytes
property
RAM the data waiting to be uploaded takes, of upload_buffer_mb.
pending_upload_count
property
Number of snapshots waiting to be uploaded, including retries.
The request that is being sent right now is not counted.
pending_upload_seconds
property
Seconds of data waiting to be uploaded (per chunk, devices in parallel).
sample_rate_hz
property
writable
The data rate of every USB device: 1, 10 or 100 Hz, or None for the default of each device (1 Hz, size distribution mode on a P2 Pro). Takes effect within a second, also while running; BLE stays at 1 Hz. The upload to naneos stays at 1 Hz whatever is set here.
seconds_until_next_snapshot
property
Time until the next snapshot is put on the output queue and uploaded.
upload_active
property
writable
Upload of the snapshots to the naneos IoT service on / off.
use_ble
property
writable
BLE devices on / off. Takes effect within a second, also while running; switching off waits for the links to disconnect.
use_serial
property
writable
USB devices on / off. Takes effect within a second, also while running.
__init__(use_serial=True, use_ble=True, upload_active=True, gathering_interval_seconds=30, ble_serial_numbers=None, ble_max_links=PartectorBleManager.DEFAULT_MAX_LINKS, serial_gain_test=True, serial_pulse_diagnostics=True, sample_rate_hz=None, diagnostics_interval_hours=1.0, upload_buffer_mb=100, ble_p2pro_mode=True)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
use_serial
|
bool
|
connect to Partectors on USB. |
True
|
use_ble
|
bool
|
connect to Partectors over Bluetooth (data comes from the links only). |
True
|
upload_active
|
bool
|
upload every snapshot to the naneos IoT service. |
True
|
gathering_interval_seconds
|
int
|
snapshot interval, clamped to 10-600 s. |
30
|
ble_serial_numbers
|
Iterable[int] | None
|
only link to these devices over BLE; None means any in reach. |
None
|
ble_max_links
|
int
|
upper bound of simultaneous BLE links. |
DEFAULT_MAX_LINKS
|
serial_gain_test
|
bool
|
run the electrometer gain test on USB devices. Their data is held back for at least 10 s after every connect while it settles. |
True
|
serial_pulse_diagnostics
|
bool
|
let USB devices report the pulse diagnostics. |
True
|
sample_rate_hz
|
int | None
|
the data rate of the USB devices, see the property. |
None
|
diagnostics_interval_hours
|
float | None
|
read the UI curve and the pulse form of every connected device this often, 0.5 to 24 hours, see the property. None switches it off. |
1.0
|
upload_buffer_mb
|
float
|
what is kept in RAM while the upload does not work, in MB (a P2 at 1 Hz needs about 16 MB per day). The oldest data is dropped when it is full. It is lost when the process ends. |
100
|
ble_p2pro_mode
|
bool
|
put a P2 Pro into size distribution mode after every BLE
connect, as the USB connect does. Skipped for a device that is connected
over USB, and for all devices while |
True
|
Source code in src/naneos/manager.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
get_device(serial_number)
The handle of one connected device.
Raises:
| Type | Description |
|---|---|
KeyError
|
no device with this serial number is connected. |
Source code in src/naneos/manager.py
370 371 372 373 374 375 376 377 378 379 | |
get_devices()
One handle per connected device, to write to it, query it and set its rate.
A device that is reachable both ways is listed with its USB connection, like its data: USB is faster and the only way to change the data rate.
Source code in src/naneos/manager.py
358 359 360 361 362 363 364 365 366 367 368 | |
query(serial_number, command, timeout=None)
Send a command to a device and return its answer, see PartectorDevice.query().
Source code in src/naneos/manager.py
385 386 387 | |
read_pulse_form(serial_number, timeout=None)
Read the pulse form of one device, see PartectorDevice.read_pulse_form(). The result is returned, not uploaded.
Source code in src/naneos/manager.py
402 403 404 405 | |
read_ui_curve(serial_number, timeout=None)
Read the UI curve of one device, see PartectorDevice.read_ui_curve(). Blocks for 15 s or more; the result is returned, not uploaded.
Source code in src/naneos/manager.py
397 398 399 400 | |
register_diagnostics_queue(diagnostics_queue)
Every UiCurve and PulseForm the manager reads is put on this queue, whether it was read on the interval or with request_diagnostics(). Only complete ones (100 U + 100 I values, 200 I values) get here; see DIAGNOSTICS_ATTEMPTS.
Source code in src/naneos/manager.py
244 245 246 247 248 | |
register_live_queue(live_queue)
Every NaneosDeviceDataPoint is put on this queue the moment it arrives.
Independent of the snapshots and the upload, at the rate of the device (see set_sample_rate). A device that is connected over USB and BLE delivers its USB points only.
Give the queue a maxsize: when it is full the oldest point is dropped to make room (see live_points_dropped), so a consumer that falls behind loses old data instead of stalling the devices. An unbounded queue grows without limit when nobody reads it.
Source code in src/naneos/manager.py
277 278 279 280 281 282 283 284 285 286 287 288 289 | |
register_output_queue(out_queue)
Every snapshot (dict[int, pandas.DataFrame]) is put on this queue.
Source code in src/naneos/manager.py
270 271 272 | |
request_diagnostics()
Read the UI curve and the pulse form of every connected device now, one device after the other, on a thread of the manager. Returns at once; the results go to the diagnostics queue and the upload like the periodic ones.
Source code in src/naneos/manager.py
253 254 255 256 257 | |
set_sample_rate(serial_number, hz)
Set the data rate of one device on USB, see PartectorDevice.set_sample_rate(). For all devices at once, and for the ones that connect later, set sample_rate_hz.
The output queue gets the data at this rate; the upload stays at 1 Hz.
Source code in src/naneos/manager.py
389 390 391 392 393 394 395 | |
write(serial_number, command)
Send a command without an answer to a device, see PartectorDevice.write().
Source code in src/naneos/manager.py
381 382 383 | |
NotSupportedError
Bases: Exception
The device or the transport it is reached over cannot do this.
Source code in src/naneos/device.py
19 20 | |
PartectorBleManager
Bases: Thread
Connects to the Partectors in reach and collects the data they send over the link.
Only connected devices deliver data. The scanner is used to find devices, to gate connects by signal strength and to refresh stale device handles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serial_numbers
|
Iterable[int] | None
|
Only connect to these devices. None connects to every Partector in reach, first come first served. |
None
|
max_links
|
int
|
Upper bound of simultaneous links (including ones that are still retrying). BlueZ handles about seven reliably. |
DEFAULT_MAX_LINKS
|
point_listener
|
PointListener | None
|
Called with every data point as it arrives, in addition to get_data(). Runs on the BLE event loop: must be quick and must not block. |
None
|
p2pro_mode
|
bool
|
Put a P2 Pro into size distribution mode after every connect, like the USB connect does. False leaves the mode of the device alone. |
True
|
p2pro_mode_guard
|
Callable[[int], bool] | None
|
Called with the serial number before that switch, on the BLE thread; returning False skips it for this connect. |
None
|
Source code in src/naneos/ble/partector/manager.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | |
get_connected_serial_numbers()
Serial numbers of the devices with a live BLE link.
Source code in src/naneos/ble/partector/manager.py
148 149 150 | |
get_data()
Returns the collected data as DataFrames and clears the buffer.
Source code in src/naneos/ble/partector/manager.py
125 126 127 128 129 130 131 132 133 134 135 136 | |
get_devices()
Handles to write to and query the devices with a live BLE link.
Source code in src/naneos/ble/partector/manager.py
152 153 154 | |
PartectorDevice
Bases: ABC
One Partector, whatever it is connected with.
Source code in src/naneos/device.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
device_type
abstractmethod
property
None until the device family is known.
firmware_version
abstractmethod
property
None until the device has told it.
sample_rate_hz
abstractmethod
property
Data points per second; 0 when the output is off, None when the device paces itself (P2 Pro size distribution mode).
serial_number
abstractmethod
property
None until the device has told it.
query(command, timeout=None)
abstractmethod
Send a command and return the tab separated fields of its answer.
One command is in flight per device; calls from several threads queue up.
Raises:
| Type | Description |
|---|---|
ConnectionError
|
the device is not connected. |
TimeoutError
|
no answer within timeout (default: what suits the transport). |
Source code in src/naneos/device.py
65 66 67 68 69 70 71 72 73 74 | |
read_pulse_form(timeout=None)
abstractmethod
Read the form of the last charging pulse, 200 samples. Does not disturb the measurement.
Over BLE a lost packet leaves the form short: check PulseForm.is_complete. The manager does that and reads again on its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float | None
|
for the readout; default 30 s over USB, 90 s over BLE. |
None
|
Raises:
| Type | Description |
|---|---|
NotSupportedError
|
a P1, or firmware older than 418. |
TimeoutError
|
the form did not arrive complete within the timeout. |
ConnectionError
|
the device is not connected. |
Source code in src/naneos/device.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
read_ui_curve(timeout=None)
abstractmethod
Sweep the corona voltage and read the resulting UI curve, 100 points.
Blocks for the sweep (10 s) plus the readout, so 15 s or more. The measurement is disturbed by the sweep: the data points of the device are held back until it has settled again.
Over BLE a lost packet leaves the curve short: check UiCurve.is_complete. The manager does that and reads again on its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float | None
|
for the readout that follows the sweep; default 30 s over USB, 90 s over BLE (the device sends one packet every 2 s). |
None
|
Raises:
| Type | Description |
|---|---|
NotSupportedError
|
a P1, or firmware older than 418. |
TimeoutError
|
the curve did not arrive complete within the timeout. |
ConnectionError
|
the device is not connected. |
Source code in src/naneos/device.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
set_sample_rate(hz)
abstractmethod
Set the data rate to 0 (off), 1, 10 or 100 Hz, or None for the default of the device: 1 Hz, or the size distribution mode of a P2 Pro, where the device sets the pace itself.
The upload to naneos is limited to 1 Hz whatever is set here.
Raises:
| Type | Description |
|---|---|
NotSupportedError
|
over BLE, where the rate is fixed at 1 Hz (None is fine). |
ValueError
|
for a rate the device does not offer. |
Source code in src/naneos/device.py
76 77 78 79 80 81 82 83 84 85 86 87 | |
write(command)
abstractmethod
Send a command that has no answer, for example "X0001!".
Raises:
| Type | Description |
|---|---|
ConnectionError
|
the device is not connected. |
Source code in src/naneos/device.py
57 58 59 60 61 62 63 | |
PartectorSerialManager
Bases: Thread
Connects to every Partector on USB, keeps the links alive and collects their data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gain_test_active
|
bool
|
run the electrometer gain test on P2 / P2 Pro. It holds the data of a device back for at least 10 s after every connect. |
True
|
output_pulse_diagnostics
|
bool
|
let P2 / P2 Pro append the pulse diagnostics columns. |
True
|
point_listener
|
PointListener | None
|
called with every data point as it arrives, in addition to get_data(). Runs on the reader threads: must be quick and must not block. |
None
|
sample_rate_hz
|
int | None
|
the data rate of every device, see the property. |
None
|
Source code in src/naneos/usb/partector/manager.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | |
sample_rate_hz
property
writable
The data rate of every device: 1, 10 or 100 Hz, or None for the default of each device (1 Hz, size distribution mode on a P2 Pro). Setting it changes the connected devices within a second and applies to the ones that connect later.
get_data()
Returns the data the manager loop collected since the last call.
The devices are read by the manager thread only (see _fetch_data), so this can be called from any thread.
Source code in src/naneos/usb/partector/manager.py
61 62 63 64 65 66 67 68 69 | |
get_devices()
Handles to write to and query the connected devices and to set their rate.
Source code in src/naneos/usb/partector/manager.py
109 110 111 | |
get_settling_serial_numbers()
Serial numbers of devices still warming up after a gain test was started.
Source code in src/naneos/usb/partector/manager.py
102 103 104 | |
PulseForm
dataclass
The electrometer current along one charging pulse, 200 samples in time order.
Source code in src/naneos/diagnostics.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
entries
property
What the form holds, for the log: "200 I values".
UiCurve
dataclass
The electrometer current over the corona voltage, 100 points sorted by voltage.
Source code in src/naneos/diagnostics.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
entries
property
What the curve holds, for the log: "100 U + 100 I values".
enable_console_logging(level=logging.INFO, colored=True)
Print naneos log messages of at least level to stderr.
Calling it again replaces the previous console handler, so the output is never duplicated. Returns the "naneos" logger.
Source code in src/naneos/logger.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
enable_file_logging(path, level=logging.INFO, max_bytes=0, backup_count=0)
Append naneos log messages of at least level to a file.
path may be a directory, in which case naneos-devices.log is created in
it. Calling it again replaces the previous file handler. Returns the
"naneos" logger.
The file grows without limit unless max_bytes is set: then it is moved to
naneos-devices.log.1 (and so on, up to backup_count files) once it would
exceed that size. A long-running application should set both.
Source code in src/naneos/logger.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
upload_snapshot(data)
Upload the frames of a snapshot, keyed by device serial number.
Blocks for up to TIMEOUT_SECONDS. Raises requests.RequestException on network problems; HTTP errors are reported by the returned response.
Source code in src/naneos/cloud/upload.py
96 97 98 99 100 101 102 | |