w1-therm-api 0.1.0__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.
- w1_therm_api-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- w1_therm_api-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- w1_therm_api-0.1.0/.github/pull_request_template.md +28 -0
- w1_therm_api-0.1.0/.github/release.yml +25 -0
- w1_therm_api-0.1.0/.github/workflows/publish.yml +41 -0
- w1_therm_api-0.1.0/.gitignore +22 -0
- w1_therm_api-0.1.0/PKG-INFO +119 -0
- w1_therm_api-0.1.0/README.md +102 -0
- w1_therm_api-0.1.0/pyproject.toml +28 -0
- w1_therm_api-0.1.0/src/w1_therm_api/__init__.py +5 -0
- w1_therm_api-0.1.0/src/w1_therm_api/api.py +171 -0
- w1_therm_api-0.1.0/src/w1_therm_api/w1_therm_proto.txt +35 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 🐛 Bug Report
|
|
3
|
+
about: Create a report to help us improve the package
|
|
4
|
+
title: "[BUG] "
|
|
5
|
+
labels: ["bug"]
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
### Describe the Bug
|
|
10
|
+
A clear and concise description of what the bug is.
|
|
11
|
+
|
|
12
|
+
### Reproduction Steps
|
|
13
|
+
Steps to reproduce the behavior:
|
|
14
|
+
1. Run code '...'
|
|
15
|
+
2. Pass arguments '...'
|
|
16
|
+
3. See error
|
|
17
|
+
|
|
18
|
+
### Expected Behavior
|
|
19
|
+
A clear and concise description of what you expected to happen.
|
|
20
|
+
|
|
21
|
+
### Code Snippet & Error Logs
|
|
22
|
+
If applicable, add a minimal Python code snippet and the full terminal traceback error logs to help explain your problem.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# Paste your code here
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Environment Information
|
|
29
|
+
- **OS:** [e.g. macOS, Ubuntu, Windows]
|
|
30
|
+
- **Python Version:** [e.g. 3.11.4]
|
|
31
|
+
- **Package Version:** [e.g. 0.1.2]
|
|
32
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 🚀 Feature Request
|
|
3
|
+
about: Suggest an idea or new functionality for this package
|
|
4
|
+
title: "[FEATURE] "
|
|
5
|
+
labels: ["enhancement", "feature"]
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
### Is your feature request related to a problem? Please describe.
|
|
10
|
+
A clear and concise description of what the problem is. (e.g., "I'm frustrated when trying to parse XYZ files because...")
|
|
11
|
+
|
|
12
|
+
### Describe the solution you'd like
|
|
13
|
+
A clear and concise description of what you want to happen and how you envision the new Python API/syntax looking.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# If applicable, show an ideal code example of how you'd like to use it:
|
|
17
|
+
import my_package
|
|
18
|
+
|
|
19
|
+
my_package.new_feature(option="value")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Describe alternatives you've considered
|
|
23
|
+
A clear and concise description of any alternative solutions or workarounds you've tried.
|
|
24
|
+
|
|
25
|
+
### Additional context
|
|
26
|
+
Add any other context, screenshots, or examples about the feature request here.
|
|
27
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
Provide a brief summary of the changes introduced by this pull request and the problems they solve.
|
|
3
|
+
|
|
4
|
+
Fixes # (issue number)
|
|
5
|
+
|
|
6
|
+
## Type of Change
|
|
7
|
+
Please delete options that are not relevant.
|
|
8
|
+
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
|
|
9
|
+
- [ ] 🚀 New feature (non-breaking change which adds functionality)
|
|
10
|
+
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
11
|
+
- [ ] 📝 Documentation update
|
|
12
|
+
|
|
13
|
+
## How Has This Been Tested?
|
|
14
|
+
Please describe the tests that you ran to verify your changes.
|
|
15
|
+
- [ ] **Unit Tests:** Run `pytest` locally to ensure no regressions.
|
|
16
|
+
- [ ] **Manual Verification:** Tested locally using the following script/input:
|
|
17
|
+
```python
|
|
18
|
+
# Paste manual testing snippet if applicable
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Checklist
|
|
22
|
+
- [ ] My code follows the code style and formatting guidelines of this project.
|
|
23
|
+
- [ ] I have performed a self-review of my own code.
|
|
24
|
+
- [ ] I have commented my code, particularly in hard-to-understand areas.
|
|
25
|
+
- [ ] I have made corresponding changes to the documentation.
|
|
26
|
+
- [ ] My changes generate no new warnings or console errors.
|
|
27
|
+
- [ ] New and existing unit tests pass locally with my changes.
|
|
28
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
changelog:
|
|
2
|
+
exclude:
|
|
3
|
+
labels:
|
|
4
|
+
- ignore
|
|
5
|
+
- duplicate
|
|
6
|
+
authors:
|
|
7
|
+
- dependabot[bot]
|
|
8
|
+
|
|
9
|
+
categories:
|
|
10
|
+
- title: "🚀 New Features"
|
|
11
|
+
labels:
|
|
12
|
+
- enhancement
|
|
13
|
+
- feature
|
|
14
|
+
- title: "🐛 Bug Fixes"
|
|
15
|
+
labels:
|
|
16
|
+
- bug
|
|
17
|
+
- fix
|
|
18
|
+
- title: "📝 Documentation"
|
|
19
|
+
labels:
|
|
20
|
+
- documentation
|
|
21
|
+
- docs
|
|
22
|
+
- title: "⚙️ Dependency Updates"
|
|
23
|
+
labels:
|
|
24
|
+
- dependencies
|
|
25
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
deploy:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # Critical for OIDC Trusted Publishing
|
|
13
|
+
contents: write # Crucial to let the action attach binaries to your release
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout Source
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: '3.11' # Fixed syntax from 'python-numeric-version'
|
|
23
|
+
|
|
24
|
+
- name: Install Build Tools
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install build
|
|
28
|
+
|
|
29
|
+
- name: Build Package Distributions
|
|
30
|
+
run: python -m build
|
|
31
|
+
|
|
32
|
+
- name: Upload Binaries to GitHub Release
|
|
33
|
+
uses: softprops/action-gh-release@v2
|
|
34
|
+
with:
|
|
35
|
+
files: dist/* # Automatically grabs both the .whl and .tar.gz files
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Built-in token, no setup required
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: w1-therm-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fast Python API wrapper for the Linux w1_therm kernel driver supporting simultaneous conversions.
|
|
5
|
+
Project-URL: Homepage, https://github.com/troxel/w1-therm-api
|
|
6
|
+
Project-URL: Bug-Tracker, https://github.com/issues
|
|
7
|
+
Author-email: Steven Troxel <troxel@perlworks.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# w1-therm-api
|
|
19
|
+
|
|
20
|
+
A Python script to support the Linux `w1_therm` kernel driver. It discovers
|
|
21
|
+
multiple 1-Wire buses, 1wire temperature sensors, triggers simultaneous conversions on supported buses, and returns temperatures in Fahrenheit by default.
|
|
22
|
+
|
|
23
|
+
The package reads the driver's sysfs interface directly and has no runtime
|
|
24
|
+
dependencies.
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Linux with the kernel `w1_therm` driver loaded.
|
|
29
|
+
- Read/write access to `/sys/bus/w1/devices` (or an alternate sysfs root).
|
|
30
|
+
- Python 3.7 or later.
|
|
31
|
+
|
|
32
|
+
`W1Bus` recognizes the common `w1_therm` temperature families: `28-`, `10-`,
|
|
33
|
+
`22-`, and `3b-`.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install w1-therm-api
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from w1_therm_api import W1Bus
|
|
45
|
+
|
|
46
|
+
bus = W1Bus()
|
|
47
|
+
|
|
48
|
+
# Send Convert T to each master that exposes therm_bulk_read, then wait for
|
|
49
|
+
# the driver's cached results to become available.
|
|
50
|
+
if bus.convert():
|
|
51
|
+
for sensor_id, temperature_f in bus.read_temperatures():
|
|
52
|
+
if temperature_f is None:
|
|
53
|
+
print(f"{sensor_id}: unavailable")
|
|
54
|
+
else:
|
|
55
|
+
print(f"{sensor_id}: {temperature_f:.2f} F")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To receive Celsius values, select it when creating the bus:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
bus = W1Bus(unit="C")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Sensors are discovered when `W1Bus` is created. To ask each master to search
|
|
65
|
+
for new devices and refresh that list:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
bus.rescan()
|
|
69
|
+
print(bus.sensors)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For systems without `therm_bulk_read`, individual reads still work:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
for sensor_id, temperature_f in bus.read_temperatures():
|
|
76
|
+
print(sensor_id, temperature_f)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## How It Uses `w1_therm`
|
|
80
|
+
|
|
81
|
+
When available, `convert()` writes `trigger` to each master's
|
|
82
|
+
`therm_bulk_read` entry. It then polls until the driver reports that the
|
|
83
|
+
conversion result is ready before reading every sensor's `temperature` entry.
|
|
84
|
+
This lets devices on a bus convert in parallel, including parasite-powered
|
|
85
|
+
devices when the driver and adapter support a strong pullup.
|
|
86
|
+
|
|
87
|
+
`read_temperature()` first reads the cached `temperature` value. If that file
|
|
88
|
+
is missing, empty, invalid, or reports the DS18x20 power-on value of 85 C, it
|
|
89
|
+
falls back to `w1_slave` and validates its CRC status. Failed reads and
|
|
90
|
+
power-on values are returned as `(sensor_id, None)`; valid values are returned
|
|
91
|
+
as `(sensor_id, temperature_f)`.
|
|
92
|
+
|
|
93
|
+
The underlying sysfs entries and their behavior are documented in the Linux
|
|
94
|
+
kernel's `w1_therm` driver ABI. This package does not configure resolution,
|
|
95
|
+
EEPROM, alarm, power, or driver feature settings.
|
|
96
|
+
|
|
97
|
+
## API
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
W1Bus(
|
|
101
|
+
devices_dir="/sys/bus/w1/devices",
|
|
102
|
+
known_families=("28-", "10-", "22-", "3b-"),
|
|
103
|
+
unit="F",
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `find_masters()` returns discovered `w1_bus_master*` directories.
|
|
108
|
+
- `find_sensors()` returns recognized sensor directories.
|
|
109
|
+
- `rescan(timeout=2.0)` requests a bus search and refreshes `sensors`.
|
|
110
|
+
- `start_conversion()` triggers bulk conversion and returns whether any master was triggered.
|
|
111
|
+
- `wait_for_conversion(timeout=2.0)` waits for all triggered bulk conversions.
|
|
112
|
+
- `convert(timeout=2.0)` performs both steps.
|
|
113
|
+
- `read_temperature(sensor_path)` returns `(sensor_id, temperature_f | None)`.
|
|
114
|
+
- `read_temperatures()` reads every currently discovered sensor.
|
|
115
|
+
- `unit` accepts `"F"` (default) or `"C"`; unit names are case-insensitive.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# w1-therm-api
|
|
2
|
+
|
|
3
|
+
A Python script to support the Linux `w1_therm` kernel driver. It discovers
|
|
4
|
+
multiple 1-Wire buses, 1wire temperature sensors, triggers simultaneous conversions on supported buses, and returns temperatures in Fahrenheit by default.
|
|
5
|
+
|
|
6
|
+
The package reads the driver's sysfs interface directly and has no runtime
|
|
7
|
+
dependencies.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Linux with the kernel `w1_therm` driver loaded.
|
|
12
|
+
- Read/write access to `/sys/bus/w1/devices` (or an alternate sysfs root).
|
|
13
|
+
- Python 3.7 or later.
|
|
14
|
+
|
|
15
|
+
`W1Bus` recognizes the common `w1_therm` temperature families: `28-`, `10-`,
|
|
16
|
+
`22-`, and `3b-`.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python -m pip install w1-therm-api
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from w1_therm_api import W1Bus
|
|
28
|
+
|
|
29
|
+
bus = W1Bus()
|
|
30
|
+
|
|
31
|
+
# Send Convert T to each master that exposes therm_bulk_read, then wait for
|
|
32
|
+
# the driver's cached results to become available.
|
|
33
|
+
if bus.convert():
|
|
34
|
+
for sensor_id, temperature_f in bus.read_temperatures():
|
|
35
|
+
if temperature_f is None:
|
|
36
|
+
print(f"{sensor_id}: unavailable")
|
|
37
|
+
else:
|
|
38
|
+
print(f"{sensor_id}: {temperature_f:.2f} F")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
To receive Celsius values, select it when creating the bus:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
bus = W1Bus(unit="C")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Sensors are discovered when `W1Bus` is created. To ask each master to search
|
|
48
|
+
for new devices and refresh that list:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
bus.rescan()
|
|
52
|
+
print(bus.sensors)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For systems without `therm_bulk_read`, individual reads still work:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
for sensor_id, temperature_f in bus.read_temperatures():
|
|
59
|
+
print(sensor_id, temperature_f)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## How It Uses `w1_therm`
|
|
63
|
+
|
|
64
|
+
When available, `convert()` writes `trigger` to each master's
|
|
65
|
+
`therm_bulk_read` entry. It then polls until the driver reports that the
|
|
66
|
+
conversion result is ready before reading every sensor's `temperature` entry.
|
|
67
|
+
This lets devices on a bus convert in parallel, including parasite-powered
|
|
68
|
+
devices when the driver and adapter support a strong pullup.
|
|
69
|
+
|
|
70
|
+
`read_temperature()` first reads the cached `temperature` value. If that file
|
|
71
|
+
is missing, empty, invalid, or reports the DS18x20 power-on value of 85 C, it
|
|
72
|
+
falls back to `w1_slave` and validates its CRC status. Failed reads and
|
|
73
|
+
power-on values are returned as `(sensor_id, None)`; valid values are returned
|
|
74
|
+
as `(sensor_id, temperature_f)`.
|
|
75
|
+
|
|
76
|
+
The underlying sysfs entries and their behavior are documented in the Linux
|
|
77
|
+
kernel's `w1_therm` driver ABI. This package does not configure resolution,
|
|
78
|
+
EEPROM, alarm, power, or driver feature settings.
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
W1Bus(
|
|
84
|
+
devices_dir="/sys/bus/w1/devices",
|
|
85
|
+
known_families=("28-", "10-", "22-", "3b-"),
|
|
86
|
+
unit="F",
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `find_masters()` returns discovered `w1_bus_master*` directories.
|
|
91
|
+
- `find_sensors()` returns recognized sensor directories.
|
|
92
|
+
- `rescan(timeout=2.0)` requests a bus search and refreshes `sensors`.
|
|
93
|
+
- `start_conversion()` triggers bulk conversion and returns whether any master was triggered.
|
|
94
|
+
- `wait_for_conversion(timeout=2.0)` waits for all triggered bulk conversions.
|
|
95
|
+
- `convert(timeout=2.0)` performs both steps.
|
|
96
|
+
- `read_temperature(sensor_path)` returns `(sensor_id, temperature_f | None)`.
|
|
97
|
+
- `read_temperatures()` reads every currently discovered sensor.
|
|
98
|
+
- `unit` accepts `"F"` (default) or `"C"`; unit names are case-insensitive.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "w1-therm-api"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A fast Python API wrapper for the Linux w1_therm kernel driver supporting simultaneous conversions."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Steven Troxel", email = "troxel@perlworks.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: System :: Hardware :: Hardware Drivers",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/troxel/w1-therm-api"
|
|
27
|
+
Bug-Tracker = "https://github.com/issues"
|
|
28
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
W1_DEVICES_DIR = "/sys/bus/w1/devices"
|
|
7
|
+
KNOWN_FAMILIES = ("28-", "10-", "22-", "3b-")
|
|
8
|
+
POWER_ON_RESET_MDEG_C = 85000
|
|
9
|
+
|
|
10
|
+
def mdeg_to_f(mdeg_c: int) -> float:
|
|
11
|
+
return (mdeg_c / 1000.0) * 9.0 / 5.0 + 32.0
|
|
12
|
+
|
|
13
|
+
class W1Bus:
|
|
14
|
+
"""Discover, convert, and read sensors on Linux 1-Wire masters using the w1_therm interface."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
devices_dir: str = W1_DEVICES_DIR,
|
|
19
|
+
known_families: tuple[str, ...] = KNOWN_FAMILIES,
|
|
20
|
+
unit: str = "F",
|
|
21
|
+
) -> None:
|
|
22
|
+
unit = unit.upper()
|
|
23
|
+
if unit not in ("C", "F"):
|
|
24
|
+
raise ValueError("unit must be 'C' or 'F'")
|
|
25
|
+
self.devices_dir = devices_dir
|
|
26
|
+
self.known_families = known_families
|
|
27
|
+
self.unit = unit
|
|
28
|
+
self.masters = self.find_masters()
|
|
29
|
+
self.sensors = self.find_sensors()
|
|
30
|
+
|
|
31
|
+
def find_masters(self) -> list[str]:
|
|
32
|
+
"""Find all 1-Wire master buses."""
|
|
33
|
+
return glob.glob(os.path.join(self.devices_dir, "w1_bus_master*"))
|
|
34
|
+
|
|
35
|
+
def find_sensors(self) -> list[str]:
|
|
36
|
+
"""Find all currently registered temperature sensor devices."""
|
|
37
|
+
if not os.path.exists(self.devices_dir):
|
|
38
|
+
return []
|
|
39
|
+
return sorted(
|
|
40
|
+
os.path.join(self.devices_dir, entry)
|
|
41
|
+
for entry in os.listdir(self.devices_dir)
|
|
42
|
+
if any(entry.startswith(prefix) for prefix in self.known_families)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def rescan(self, timeout: float = 2.0) -> None:
|
|
46
|
+
"""Force masters to add new sensors and remove unresponsive ones."""
|
|
47
|
+
search_files = []
|
|
48
|
+
for master in self.masters:
|
|
49
|
+
search_file = os.path.join(master, "w1_master_search")
|
|
50
|
+
if not os.path.exists(search_file):
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
with open(search_file, "w") as file:
|
|
54
|
+
file.write("1")
|
|
55
|
+
search_files.append(search_file)
|
|
56
|
+
except OSError as error:
|
|
57
|
+
print(f"Warning: failed to trigger bus search on {master}: {error}")
|
|
58
|
+
|
|
59
|
+
deadline = time.monotonic() + timeout
|
|
60
|
+
pending = search_files
|
|
61
|
+
while pending and time.monotonic() < deadline:
|
|
62
|
+
remaining = []
|
|
63
|
+
for search_file in pending:
|
|
64
|
+
try:
|
|
65
|
+
with open(search_file) as file:
|
|
66
|
+
count = file.read().strip()
|
|
67
|
+
except OSError:
|
|
68
|
+
continue
|
|
69
|
+
if count != "0":
|
|
70
|
+
remaining.append(search_file)
|
|
71
|
+
if remaining:
|
|
72
|
+
time.sleep(0.05)
|
|
73
|
+
pending = remaining
|
|
74
|
+
|
|
75
|
+
self.sensors = self.find_sensors()
|
|
76
|
+
|
|
77
|
+
def start_conversion(self) -> bool:
|
|
78
|
+
"""Start one simultaneous Convert T on each supported master."""
|
|
79
|
+
triggered = False
|
|
80
|
+
for bulk_file in self._bulk_files():
|
|
81
|
+
try:
|
|
82
|
+
with open(bulk_file, "w") as file:
|
|
83
|
+
file.write("trigger\n")
|
|
84
|
+
triggered = True
|
|
85
|
+
except OSError as error:
|
|
86
|
+
print(f"Warning: failed to trigger bulk conversion: {error}")
|
|
87
|
+
return triggered
|
|
88
|
+
|
|
89
|
+
def wait_for_conversion(self, timeout: float = 2.0) -> bool:
|
|
90
|
+
"""Wait until every supported master has a cached result ready."""
|
|
91
|
+
deadline = time.monotonic() + timeout
|
|
92
|
+
pending = self._bulk_files()
|
|
93
|
+
|
|
94
|
+
while pending and time.monotonic() < deadline:
|
|
95
|
+
remaining = []
|
|
96
|
+
for bulk_file in pending:
|
|
97
|
+
try:
|
|
98
|
+
with open(bulk_file) as file:
|
|
99
|
+
state = file.read().strip()
|
|
100
|
+
except OSError:
|
|
101
|
+
return False
|
|
102
|
+
if state == "1":
|
|
103
|
+
continue
|
|
104
|
+
if state == "-1":
|
|
105
|
+
remaining.append(bulk_file)
|
|
106
|
+
continue
|
|
107
|
+
return False
|
|
108
|
+
if remaining:
|
|
109
|
+
time.sleep(0.005)
|
|
110
|
+
pending = remaining
|
|
111
|
+
|
|
112
|
+
return not pending
|
|
113
|
+
|
|
114
|
+
def convert(self, timeout: float = 2.0) -> bool:
|
|
115
|
+
"""Start conversion and wait to finish a bulk temperature conversion."""
|
|
116
|
+
return self.start_conversion() and self.wait_for_conversion(timeout)
|
|
117
|
+
|
|
118
|
+
def read_temperature(self, sensor_path: str) -> tuple[str, float | None]:
|
|
119
|
+
|
|
120
|
+
"""Read one cached temperature in the configured unit."""
|
|
121
|
+
sensor_id = os.path.basename(sensor_path)
|
|
122
|
+
temp_file = os.path.join(sensor_path, "temperature")
|
|
123
|
+
if os.path.exists(temp_file):
|
|
124
|
+
try:
|
|
125
|
+
with open(temp_file) as file:
|
|
126
|
+
temp_mdeg_c = int(file.read().strip())
|
|
127
|
+
if temp_mdeg_c != POWER_ON_RESET_MDEG_C:
|
|
128
|
+
return sensor_id, self._convert_temperature(temp_mdeg_c)
|
|
129
|
+
except (OSError, ValueError):
|
|
130
|
+
pass
|
|
131
|
+
# Fallback to parsing the w1_slave file if the temperature file is not available or invalid.
|
|
132
|
+
return self.parse_w1_slave(sensor_path)
|
|
133
|
+
|
|
134
|
+
def read_temperatures(self) -> list[tuple[str, float | None]]:
|
|
135
|
+
"""Read all currently registered sensor temperatures."""
|
|
136
|
+
return [self.read_temperature(sensor) for sensor in self.sensors]
|
|
137
|
+
|
|
138
|
+
def _bulk_files(self) -> list[str]:
|
|
139
|
+
return [
|
|
140
|
+
bulk_file
|
|
141
|
+
for bulk_file in (
|
|
142
|
+
os.path.join(master, "therm_bulk_read") for master in self.masters
|
|
143
|
+
)
|
|
144
|
+
if os.path.exists(bulk_file)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
def _convert_temperature(self, mdeg_c: int) -> float:
|
|
148
|
+
if self.unit == "C":
|
|
149
|
+
return mdeg_c / 1000.0
|
|
150
|
+
return mdeg_to_f(mdeg_c)
|
|
151
|
+
|
|
152
|
+
def parse_w1_slave(self, sensor_path: str) -> tuple[str, float | None]:
|
|
153
|
+
sensor_id = os.path.basename(sensor_path)
|
|
154
|
+
slave_file = os.path.join(sensor_path, "w1_slave")
|
|
155
|
+
if not os.path.exists(slave_file):
|
|
156
|
+
return sensor_id, None
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
with open(slave_file) as file:
|
|
160
|
+
lines = file.readlines()
|
|
161
|
+
if len(lines) < 2 or "YES" not in lines[0]:
|
|
162
|
+
return sensor_id, None
|
|
163
|
+
match = re.search(r"t=(-?\d+)", lines[1])
|
|
164
|
+
if match is None:
|
|
165
|
+
return sensor_id, None
|
|
166
|
+
temp_mdeg_c = int(match.group(1))
|
|
167
|
+
if temp_mdeg_c == POWER_ON_RESET_MDEG_C:
|
|
168
|
+
return sensor_id, None
|
|
169
|
+
return sensor_id, self._convert_temperature(temp_mdeg_c)
|
|
170
|
+
except (OSError, ValueError):
|
|
171
|
+
return sensor_id, None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Support is provided through the sysfs entry w1_slave. Each open and read sequence will initiate a temperature conversion, then provide two lines of ASCII output. The first line contains the nine hex bytes read along with a calculated crc value and YES or NO if it matched. If the crc matched the returned values are retained. The second line displays the retained values along with a temperature in millidegrees Centigrade after t=.
|
|
2
|
+
|
|
3
|
+
Alternatively, temperature can be read using temperature sysfs, it returns only the temperature in millidegrees Centigrade.
|
|
4
|
+
|
|
5
|
+
A bulk read of all devices on the bus could be done writing trigger to therm_bulk_read entry at w1_bus_master level. This will send the convert command to all devices on the bus, and if parasite powered devices are detected on the bus (and strong pullup is enabled in the module), it will drive the line high during the longer conversion time required by parasited powered device on the line. Reading therm_bulk_read will return 0 if no bulk conversion pending, -1 if at least one sensor still in conversion, 1 if conversion is complete but at least one sensor value has not been read yet. Result temperature is then accessed by reading the temperature entry of each device, which may return empty if conversion is still in progress. Note that if a bulk read is sent but one sensor is not read immediately, the next access to temperature on this device will return the temperature measured at the time of issue of the bulk read command (not the current temperature).
|
|
6
|
+
|
|
7
|
+
A strong pullup will be applied during the conversion if required.
|
|
8
|
+
|
|
9
|
+
conv_time is used to get current conversion time (read), and adjust it (write). A temperature conversion time depends on the device type and its current resolution. Default conversion time is set by the driver according to the device datasheet. A conversion time for many original device clones deviate from datasheet specs. There are three options: 1) manually set the correct conversion time by writing a value in milliseconds to conv_time; 2) auto measure and set a conversion time by writing 1 to conv_time; 3) use features to enable poll for conversion completion. Options 2, 3 can’t be used in parasite power mode. To get back to the default conversion time write 0 to conv_time.
|
|
10
|
+
|
|
11
|
+
Writing a resolution value (in bits) to w1_slave will change the precision of the sensor for the next readings. Allowed resolutions are defined by the sensor. Resolution is reset when the sensor gets power-cycled.
|
|
12
|
+
|
|
13
|
+
To store the current resolution in EEPROM, write 0 to w1_slave. Since the EEPROM has a limited amount of writes (>50k), this command should be used wisely.
|
|
14
|
+
|
|
15
|
+
Alternatively, resolution can be read or written using the dedicated resolution entry on each device, if supported by the sensor.
|
|
16
|
+
|
|
17
|
+
Some non-genuine DS18B20 chips are fixed in 12-bit mode only, so the actual resolution is read back from the chip and verified.
|
|
18
|
+
|
|
19
|
+
Note: Changing the resolution reverts the conversion time to default.
|
|
20
|
+
|
|
21
|
+
The write-only sysfs entry eeprom_cmd is an alternative for EEPROM operations. Write save to save device RAM to EEPROM. Write restore to restore EEPROM data in device RAM.
|
|
22
|
+
|
|
23
|
+
ext_power entry allows checking the power state of each device. Reads 0 if the device is parasite powered, 1 if the device is externally powered.
|
|
24
|
+
|
|
25
|
+
Sysfs alarms allow read or write TH and TL (Temperature High an Low) alarms. Values shall be space separated and in the device range (typical -55 degC to 125 degC). Values are integer as they are store in a 8bit register in the device. Lowest value is automatically put to TL. Once set, alarms could be search at master level.
|
|
26
|
+
|
|
27
|
+
The module parameter strong_pullup can be set to 0 to disable the strong pullup, 1 to enable autodetection or 2 to force strong pullup. In case of autodetection, the driver will use the “READ POWER SUPPLY” command to check if there are pariste powered devices on the bus. If so, it will activate the master’s strong pullup. In case the detection of parasite devices using this command fails (seems to be the case with some DS18S20) the strong pullup can be force-enabled.
|
|
28
|
+
|
|
29
|
+
If the strong pullup is enabled, the master’s strong pullup will be driven when the conversion is taking place, provided the master driver does support the strong pullup (or it falls back to a pullup resistor). The DS18b20 temperature sensor specification lists a maximum current draw of 1.5mA and that a 5k pullup resistor is not sufficient. The strong pullup is designed to provide the additional current required.
|
|
30
|
+
|
|
31
|
+
The DS28EA00 provides an additional two pins for implementing a sequence detection algorithm. This feature allows you to determine the physical location of the chip in the 1-wire bus without needing pre-existing knowledge of the bus ordering. Support is provided through the sysfs w1_seq. The file will contain a single line with an integer value representing the device index in the bus starting at 0.
|
|
32
|
+
|
|
33
|
+
features sysfs entry controls optional driver settings per device. Insufficient power in parasite mode, line noise and insufficient conversion time may lead to conversion failure. Original DS18B20 and some clones allow for detection of invalid conversion. Write bit mask 1 to features to enable checking the conversion success. If byte 6 of scratchpad memory is 0xC after conversion and temperature reads 85.00 (powerup value) or 127.94 (insufficient power), the driver returns a conversion error. Bit mask 2 enables poll for conversion completion (normal power only) by generating read cycles on the bus after conversion starts. In parasite power mode this feature is not available. Feature bit masks may be combined (OR). More details in ABI file testing/sysfs-driver-w1_therm
|
|
34
|
+
|
|
35
|
+
GX20MH01 device shares family number 0x28 with DS18*20. The device is generally compatible with DS18B20. Added are lowest 2-5, 2-6 temperature bits in Config register; R2 bit in Config register enabling 13 and 14 bit resolutions. The device is powered up in 14-bit resolution mode. The conversion times specified in the datasheet are too low and have to be increased. The device supports driver features 1 and 2.
|