spiderwire 0.1.0a1__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.
- spiderwire-0.1.0a1/.gitignore +37 -0
- spiderwire-0.1.0a1/CHANGELOG.md +27 -0
- spiderwire-0.1.0a1/LICENSE +21 -0
- spiderwire-0.1.0a1/PKG-INFO +169 -0
- spiderwire-0.1.0a1/README.md +138 -0
- spiderwire-0.1.0a1/docs/device-map.md +212 -0
- spiderwire-0.1.0a1/docs/hw-notes.md +37 -0
- spiderwire-0.1.0a1/docs/protocol-analysis.md +141 -0
- spiderwire-0.1.0a1/pyproject.toml +84 -0
- spiderwire-0.1.0a1/src/gss_ctrl_pc/__init__.py +1 -0
- spiderwire-0.1.0a1/src/gss_ctrl_pc/__main__.py +481 -0
- spiderwire-0.1.0a1/src/spiderwire/__init__.py +60 -0
- spiderwire-0.1.0a1/src/spiderwire/bus.py +280 -0
- spiderwire-0.1.0a1/src/spiderwire/protocol.py +143 -0
- spiderwire-0.1.0a1/src/spiderwire/py.typed +0 -0
- spiderwire-0.1.0a1/src/spiderwire/registers.py +307 -0
- spiderwire-0.1.0a1/src/spiderwire/transport.py +230 -0
- spiderwire-0.1.0a1/tests/__init__.py +0 -0
- spiderwire-0.1.0a1/tests/test_protocol.py +117 -0
- spiderwire-0.1.0a1/tests/test_registers.py +165 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
wheels/
|
|
12
|
+
|
|
13
|
+
# Virtual envs
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Tooling caches
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.tox/
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
|
|
26
|
+
# uv
|
|
27
|
+
uv.lock
|
|
28
|
+
uv.lock.local
|
|
29
|
+
|
|
30
|
+
# Local secrets / env
|
|
31
|
+
.env
|
|
32
|
+
.env.local
|
|
33
|
+
|
|
34
|
+
# IDE / OS
|
|
35
|
+
.idea/
|
|
36
|
+
.vscode/
|
|
37
|
+
.DS_Store
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0a1] - 2026-05-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- First pre-release on PyPI / TestPyPI for the publishing trial run.
|
|
15
|
+
Pre-release versions are skipped by default `pip install spiderwire`;
|
|
16
|
+
use `pip install --pre spiderwire` to opt in.
|
|
17
|
+
- `spiderwire` library: Modbus RTU protocol (`protocol`), RS-485 transport
|
|
18
|
+
(`transport`), per-device register map and decoders (`registers`), and
|
|
19
|
+
tiered bus master with setpoint heartbeat (`bus`).
|
|
20
|
+
- `gss-ctrl` CLI: `scan`, `poll`, `read`, `write`, `fan`, `blower`,
|
|
21
|
+
`light` commands. Acts as a stand-in master for the OEM SpiderFarmer
|
|
22
|
+
GSS hub on a USB-RS485 adapter.
|
|
23
|
+
- Reference docs: `docs/protocol-analysis.md`, `docs/device-map.md`,
|
|
24
|
+
`docs/hw-notes.md`.
|
|
25
|
+
|
|
26
|
+
[Unreleased]: https://github.com/1am/spiderwire/compare/v0.1.0a1...HEAD
|
|
27
|
+
[0.1.0a1]: https://github.com/1am/spiderwire/releases/tag/v0.1.0a1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 1AM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spiderwire
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: SpiderWire — SpiderFarmer GSS Modbus protocol library + gss-ctrl CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/1am/spiderwire
|
|
6
|
+
Project-URL: Source, https://github.com/1am/spiderwire
|
|
7
|
+
Project-URL: Issues, https://github.com/1am/spiderwire/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/1am/spiderwire/blob/main/CHANGELOG.md
|
|
9
|
+
Author: 1AM
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: grow,gss,home-assistant,horticulture,modbus,modbus-rtu,rs485,spiderfarmer
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Home Automation
|
|
25
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
26
|
+
Classifier: Topic :: Terminals :: Serial
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: pyserial>=3.5
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# SpiderWire
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/spiderwire/)
|
|
35
|
+
[](https://pypi.org/project/spiderwire/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://github.com/1am/spiderwire/actions/workflows/ci.yml)
|
|
38
|
+
|
|
39
|
+
Open Modbus RTU library and `gss-ctrl` CLI for the **SpiderFarmer GSS**
|
|
40
|
+
peripheral bus (inline fans, CO₂ sensor, sensor hub, light driver). The
|
|
41
|
+
OEM GSS hub is a Modbus RTU master over a proprietary RJ12 wire layout;
|
|
42
|
+
SpiderWire replaces that hub so you can drive the bus from any host —
|
|
43
|
+
locally, no cloud account.
|
|
44
|
+
|
|
45
|
+
> **Unofficial and experimental.** This is an independent project with no
|
|
46
|
+
> affiliation, endorsement, or relationship with SpiderFarmer. It works
|
|
47
|
+
> on my hardware, but the GSS ecosystem ships in many hardware and
|
|
48
|
+
> firmware revisions — yours may behave differently or not work at all.
|
|
49
|
+
> Expect rough edges and verify behavior on your own bus before relying
|
|
50
|
+
> on it.
|
|
51
|
+
|
|
52
|
+
Two surfaces, one library:
|
|
53
|
+
|
|
54
|
+
| Surface | Role | What ships with it |
|
|
55
|
+
| --------------------------- | ------------------------------------------ | ------------------------------------------------------------ |
|
|
56
|
+
| `spiderwire` Python package | Transport, register map, tiered bus master | `bus.py`, `protocol.py`, `registers.py`, `transport.py` |
|
|
57
|
+
| `gss-ctrl` CLI | Test / ops / manual control over USB-RS485 | `gss-ctrl scan / poll / read / write / fan / blower / light` |
|
|
58
|
+
|
|
59
|
+
A companion Home Assistant integration is planned — it will depend on
|
|
60
|
+
the published `spiderwire` wheel and expose the bus as HA entities.
|
|
61
|
+
|
|
62
|
+
- [PCB preview](https://www.youtube.com/watch?v=0Yn37gflFO0)
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
From PyPI (recommended):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install spiderwire
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
From source (for development):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/1am/spiderwire.git
|
|
76
|
+
cd spiderwire
|
|
77
|
+
pip install -e ".[dev]" # or: uv sync --extra dev
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Python 3.10+ and a USB-RS485 adapter (`pyserial` is the only
|
|
81
|
+
runtime dependency).
|
|
82
|
+
|
|
83
|
+
## CLI quickstart
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
make scan PORT=/dev/ttyUSB0 # discover devices on the bus
|
|
87
|
+
make poll PORT=/dev/ttyUSB0 # master mode: tiered poll + heartbeat
|
|
88
|
+
make read PORT=... ADDR=0x0A QTY=28
|
|
89
|
+
make fan PORT=... ADDR=0x04 SPEED=15
|
|
90
|
+
make light PORT=... PCT=50
|
|
91
|
+
make blower PORT=... PCT=40
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Without the OEM GSS hub on the bus, **`gss-ctrl poll`** takes over
|
|
95
|
+
master duties: tiered polling (~1 s fast, ~2.5 s actuators, ~7 s scan)
|
|
96
|
+
plus the setpoint heartbeat broadcast (~3.5 s), matching the OEM
|
|
97
|
+
cadence peripherals expect.
|
|
98
|
+
|
|
99
|
+
Full CLI: `gss-ctrl --help`. Commands: `scan`, `poll`, `read`, `write`,
|
|
100
|
+
`fan`, `blower`, `light`.
|
|
101
|
+
|
|
102
|
+
## Library use
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from spiderwire import BusMaster, RS485Transport
|
|
106
|
+
|
|
107
|
+
with RS485Transport("/dev/ttyUSB0", baudrate=115200) as tx:
|
|
108
|
+
bus = BusMaster(tx)
|
|
109
|
+
bus.poll_loop(interval=1.0, callback=print)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See `src/spiderwire/bus.py` for the tiered scheduler,
|
|
113
|
+
`src/spiderwire/registers.py` for the per-device register map, and
|
|
114
|
+
`src/spiderwire/transport.py` for the RS-485 framer.
|
|
115
|
+
|
|
116
|
+
## Layout
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
spiderwire/
|
|
120
|
+
├── src/
|
|
121
|
+
│ ├── spiderwire/ Python package (lib)
|
|
122
|
+
│ │ ├── bus.py tiered master + heartbeat scheduler
|
|
123
|
+
│ │ ├── protocol.py Modbus RTU framing + CRC
|
|
124
|
+
│ │ ├── registers.py per-device register map + decoders
|
|
125
|
+
│ │ └── transport.py RS-485 framer over pyserial
|
|
126
|
+
│ └── gss_ctrl_pc/ gss-ctrl CLI (stand-in for the OEM master)
|
|
127
|
+
├── tests/ pytest suite (no hardware required)
|
|
128
|
+
├── docs/ protocol + device map reference
|
|
129
|
+
├── pyproject.toml builds the `spiderwire` wheel + `gss-ctrl` script
|
|
130
|
+
└── Makefile dev shortcuts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Docs
|
|
134
|
+
|
|
135
|
+
- [`docs/device-map.md`](docs/device-map.md) — per-device register map
|
|
136
|
+
for every address observed on the bus.
|
|
137
|
+
- [`docs/protocol-analysis.md`](docs/protocol-analysis.md) — protocol
|
|
138
|
+
reference: physical layer, function codes, tiered polling, heartbeat.
|
|
139
|
+
- [`docs/hw-notes.md`](docs/hw-notes.md) — OEM hub board notes and RJ12
|
|
140
|
+
pinout.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
Copyright (c) 2026 [1AM](https://1am.pl)
|
|
145
|
+
|
|
146
|
+
Released under the [MIT License](LICENSE) — free to use, modify, and
|
|
147
|
+
distribute, including in commercial and closed-source products. The
|
|
148
|
+
only requirement is that the copyright notice and license text are
|
|
149
|
+
preserved in copies or substantial portions of the software.
|
|
150
|
+
|
|
151
|
+
## Disclaimer
|
|
152
|
+
|
|
153
|
+
This software is provided "as is", without warranty of any kind, express
|
|
154
|
+
or implied, including but not limited to the warranties of
|
|
155
|
+
merchantability, fitness for a particular purpose, and non-infringement.
|
|
156
|
+
|
|
157
|
+
This project interacts with mains-powered grow equipment over an RS-485
|
|
158
|
+
bus. Incorrect wiring, miswired connectors, unsupported devices, or
|
|
159
|
+
misuse of the protocol can damage hardware, void manufacturer warranties,
|
|
160
|
+
cause fire, or result in personal injury. You are solely responsible for
|
|
161
|
+
verifying the correctness of your wiring, your device configuration, and
|
|
162
|
+
the commands you send.
|
|
163
|
+
|
|
164
|
+
In no event shall the author or contributors be liable for any direct,
|
|
165
|
+
indirect, incidental, special, exemplary, or consequential damages —
|
|
166
|
+
including but not limited to damage to equipment, crops, property, or
|
|
167
|
+
persons — arising from the use of, or inability to use, this software.
|
|
168
|
+
|
|
169
|
+
Use at your own risk.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SpiderWire
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/spiderwire/)
|
|
4
|
+
[](https://pypi.org/project/spiderwire/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/1am/spiderwire/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
Open Modbus RTU library and `gss-ctrl` CLI for the **SpiderFarmer GSS**
|
|
9
|
+
peripheral bus (inline fans, CO₂ sensor, sensor hub, light driver). The
|
|
10
|
+
OEM GSS hub is a Modbus RTU master over a proprietary RJ12 wire layout;
|
|
11
|
+
SpiderWire replaces that hub so you can drive the bus from any host —
|
|
12
|
+
locally, no cloud account.
|
|
13
|
+
|
|
14
|
+
> **Unofficial and experimental.** This is an independent project with no
|
|
15
|
+
> affiliation, endorsement, or relationship with SpiderFarmer. It works
|
|
16
|
+
> on my hardware, but the GSS ecosystem ships in many hardware and
|
|
17
|
+
> firmware revisions — yours may behave differently or not work at all.
|
|
18
|
+
> Expect rough edges and verify behavior on your own bus before relying
|
|
19
|
+
> on it.
|
|
20
|
+
|
|
21
|
+
Two surfaces, one library:
|
|
22
|
+
|
|
23
|
+
| Surface | Role | What ships with it |
|
|
24
|
+
| --------------------------- | ------------------------------------------ | ------------------------------------------------------------ |
|
|
25
|
+
| `spiderwire` Python package | Transport, register map, tiered bus master | `bus.py`, `protocol.py`, `registers.py`, `transport.py` |
|
|
26
|
+
| `gss-ctrl` CLI | Test / ops / manual control over USB-RS485 | `gss-ctrl scan / poll / read / write / fan / blower / light` |
|
|
27
|
+
|
|
28
|
+
A companion Home Assistant integration is planned — it will depend on
|
|
29
|
+
the published `spiderwire` wheel and expose the bus as HA entities.
|
|
30
|
+
|
|
31
|
+
- [PCB preview](https://www.youtube.com/watch?v=0Yn37gflFO0)
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
From PyPI (recommended):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install spiderwire
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
From source (for development):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/1am/spiderwire.git
|
|
45
|
+
cd spiderwire
|
|
46
|
+
pip install -e ".[dev]" # or: uv sync --extra dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Requires Python 3.10+ and a USB-RS485 adapter (`pyserial` is the only
|
|
50
|
+
runtime dependency).
|
|
51
|
+
|
|
52
|
+
## CLI quickstart
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
make scan PORT=/dev/ttyUSB0 # discover devices on the bus
|
|
56
|
+
make poll PORT=/dev/ttyUSB0 # master mode: tiered poll + heartbeat
|
|
57
|
+
make read PORT=... ADDR=0x0A QTY=28
|
|
58
|
+
make fan PORT=... ADDR=0x04 SPEED=15
|
|
59
|
+
make light PORT=... PCT=50
|
|
60
|
+
make blower PORT=... PCT=40
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Without the OEM GSS hub on the bus, **`gss-ctrl poll`** takes over
|
|
64
|
+
master duties: tiered polling (~1 s fast, ~2.5 s actuators, ~7 s scan)
|
|
65
|
+
plus the setpoint heartbeat broadcast (~3.5 s), matching the OEM
|
|
66
|
+
cadence peripherals expect.
|
|
67
|
+
|
|
68
|
+
Full CLI: `gss-ctrl --help`. Commands: `scan`, `poll`, `read`, `write`,
|
|
69
|
+
`fan`, `blower`, `light`.
|
|
70
|
+
|
|
71
|
+
## Library use
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from spiderwire import BusMaster, RS485Transport
|
|
75
|
+
|
|
76
|
+
with RS485Transport("/dev/ttyUSB0", baudrate=115200) as tx:
|
|
77
|
+
bus = BusMaster(tx)
|
|
78
|
+
bus.poll_loop(interval=1.0, callback=print)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See `src/spiderwire/bus.py` for the tiered scheduler,
|
|
82
|
+
`src/spiderwire/registers.py` for the per-device register map, and
|
|
83
|
+
`src/spiderwire/transport.py` for the RS-485 framer.
|
|
84
|
+
|
|
85
|
+
## Layout
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
spiderwire/
|
|
89
|
+
├── src/
|
|
90
|
+
│ ├── spiderwire/ Python package (lib)
|
|
91
|
+
│ │ ├── bus.py tiered master + heartbeat scheduler
|
|
92
|
+
│ │ ├── protocol.py Modbus RTU framing + CRC
|
|
93
|
+
│ │ ├── registers.py per-device register map + decoders
|
|
94
|
+
│ │ └── transport.py RS-485 framer over pyserial
|
|
95
|
+
│ └── gss_ctrl_pc/ gss-ctrl CLI (stand-in for the OEM master)
|
|
96
|
+
├── tests/ pytest suite (no hardware required)
|
|
97
|
+
├── docs/ protocol + device map reference
|
|
98
|
+
├── pyproject.toml builds the `spiderwire` wheel + `gss-ctrl` script
|
|
99
|
+
└── Makefile dev shortcuts
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Docs
|
|
103
|
+
|
|
104
|
+
- [`docs/device-map.md`](docs/device-map.md) — per-device register map
|
|
105
|
+
for every address observed on the bus.
|
|
106
|
+
- [`docs/protocol-analysis.md`](docs/protocol-analysis.md) — protocol
|
|
107
|
+
reference: physical layer, function codes, tiered polling, heartbeat.
|
|
108
|
+
- [`docs/hw-notes.md`](docs/hw-notes.md) — OEM hub board notes and RJ12
|
|
109
|
+
pinout.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
Copyright (c) 2026 [1AM](https://1am.pl)
|
|
114
|
+
|
|
115
|
+
Released under the [MIT License](LICENSE) — free to use, modify, and
|
|
116
|
+
distribute, including in commercial and closed-source products. The
|
|
117
|
+
only requirement is that the copyright notice and license text are
|
|
118
|
+
preserved in copies or substantial portions of the software.
|
|
119
|
+
|
|
120
|
+
## Disclaimer
|
|
121
|
+
|
|
122
|
+
This software is provided "as is", without warranty of any kind, express
|
|
123
|
+
or implied, including but not limited to the warranties of
|
|
124
|
+
merchantability, fitness for a particular purpose, and non-infringement.
|
|
125
|
+
|
|
126
|
+
This project interacts with mains-powered grow equipment over an RS-485
|
|
127
|
+
bus. Incorrect wiring, miswired connectors, unsupported devices, or
|
|
128
|
+
misuse of the protocol can damage hardware, void manufacturer warranties,
|
|
129
|
+
cause fire, or result in personal injury. You are solely responsible for
|
|
130
|
+
verifying the correctness of your wiring, your device configuration, and
|
|
131
|
+
the commands you send.
|
|
132
|
+
|
|
133
|
+
In no event shall the author or contributors be liable for any direct,
|
|
134
|
+
indirect, incidental, special, exemplary, or consequential damages —
|
|
135
|
+
including but not limited to damage to equipment, crops, property, or
|
|
136
|
+
persons — arising from the use of, or inability to use, this software.
|
|
137
|
+
|
|
138
|
+
Use at your own risk.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Device Map — Current Bus State
|
|
2
|
+
|
|
3
|
+
Snapshot of what actually lives on the RS-485 bus as captured by the
|
|
4
|
+
`gss-ctrl` CLI (`gss-ctrl <serial> scan` / `read`, or `make scan` /
|
|
5
|
+
`make read` from the repo root). Example port:
|
|
6
|
+
`/dev/cu.usbserial-130` (macOS).
|
|
7
|
+
See [`protocol-analysis.md`](./protocol-analysis.md) for the full
|
|
8
|
+
protocol write-up and historical observations; this file is a concise,
|
|
9
|
+
per-device reference grouped by role.
|
|
10
|
+
|
|
11
|
+
**Code:** parsing, tier lists, and `DEFAULT_QTY` live in
|
|
12
|
+
[`registers.py`](../registers.py);
|
|
13
|
+
orchestration in
|
|
14
|
+
[`bus.py`](../bus.py).
|
|
15
|
+
The Home Assistant custom component
|
|
16
|
+
[`custom_components/spiderfarmer/`](https://github.com/1am/spiderfarmer-ha/tree/main/custom_components/spiderfarmer/)
|
|
17
|
+
drives the same `BusMaster` + `RS485Transport` stack (see below).
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
| Addr | Role | Device | Header type (reg 6) | Model (reg 4) | FW (reg 2–3) | HW (reg 7) | Reliable register qty |
|
|
22
|
+
|------|------|--------|---------------------|---------------|--------------|------------|-----------------------|
|
|
23
|
+
| 0x03 | **Sensor** | CO₂ sensor | `0x0308` | `0x0330` | `"05.12"` (ASCII) | `0x0202` | 13 |
|
|
24
|
+
| 0x0A | **Sensor** + actuator | Sensor hub (air T/RH, soil T, PPFD); `reg 19` is the *reported* light value, not a setpoint | `0x0400` | `0x0330` | `"05.12"` (ASCII) | `0x0202` | 28 |
|
|
25
|
+
| 0x04 | **Actuator** | **Light dimmer (primary)** — accepts FC06 → reg 10 in **percent (0-100)**, never echoes writes | `0x0301` | `0x0426` | `0xF784C96D` (binary) | `0x0105` | 24 — silent to FC03 in current rig, but acts on FC06 writes |
|
|
26
|
+
| 0x06 | **Actuator** | **Blower / ventilation** (OEM UI calls it *"Light 2"*) — FC06 → **reg 14** in percent (0-100), *does* echo | `0x0204` | `0x043D` | `0xF784C90F` (binary) | `0x0102` | 16 |
|
|
27
|
+
|
|
28
|
+
`DeviceHeader.type_name` in `registers.py` (`LIGHT=0x02`, `FAN=0x03`,
|
|
29
|
+
`SENSOR_HUB=0x04`) maps **only the high byte** of register 6. That is
|
|
30
|
+
why the CLI prints `fan` for the CO₂ sensor at `0x03` (header type high
|
|
31
|
+
byte is `0x03`). The subtype in the low byte is what actually
|
|
32
|
+
distinguishes CO₂ (`0x08`) from the duct fan (`0x01`).
|
|
33
|
+
|
|
34
|
+
## Tier placement (OEM orchestration)
|
|
35
|
+
|
|
36
|
+
`BusMaster.tick()` (see `bus.py`) interleaves the same four schedules the
|
|
37
|
+
OEM GSS hub uses (see `protocol-analysis.md` §"Bus Timing"). On each call,
|
|
38
|
+
actuators run **when their ~2.5 s deadline has elapsed** (before the fast
|
|
39
|
+
pair), the fast sensor pair **always** runs, then heartbeat / silent scan
|
|
40
|
+
fire on their own timers:
|
|
41
|
+
|
|
42
|
+
| Tier | Cadence | Addresses / action |
|
|
43
|
+
|------|---------|---------------------|
|
|
44
|
+
| B — actuators | ~2.5 s | `0x04`, `0x06` (FC03 read when due) |
|
|
45
|
+
| A — fast sensors | every tick (~1.0 s outer loop) | `0x03`, `0x0A` |
|
|
46
|
+
| D — heartbeat broadcast | ~3.5 s | FC 0x10 → 0x00, 26 regs @ 1001 |
|
|
47
|
+
| C — silent-slot scan | ~7.0 s | `0x10, 0x02, 0x01, 0x0C, 0x05, 0x07, 0x0B, 0x0D, 0x0E, 0x08, 0x0F, 0x13` |
|
|
48
|
+
|
|
49
|
+
Moving a device between tiers is editing `FAST_ADDRS`,
|
|
50
|
+
`ACTUATOR_ADDRS`, or `SCAN_ADDRS` in `spiderwire/registers.py` (or
|
|
51
|
+
mutating `bus.fast_addrs` / `actuator_addrs` / `scan_addrs` at runtime).
|
|
52
|
+
|
|
53
|
+
### Home Assistant (`spiderfarmer`)
|
|
54
|
+
|
|
55
|
+
The integration's coordinator calls `BusMaster.tick()` once per HA
|
|
56
|
+
update (`DEFAULT_SCAN_INTERVAL` = 1 s in `custom_components/spiderfarmer/const.py`), so the Python
|
|
57
|
+
master tracks the OEM cadence internally.
|
|
58
|
+
|
|
59
|
+
| HA platform | What appears | Bus mapping |
|
|
60
|
+
|-------------|--------------|-------------|
|
|
61
|
+
| **Sensor** | Air temp, humidity, VPD, soil temp, PPFD on the hub; CO₂ ppm on `0x03` | Typed `SensorHubData` / `CO2SensorData` from coordinator data; unique ids `sf_0x0a_<key>` / `sf_0x03_co2` (hex lower case). |
|
|
62
|
+
| **Light** | One **Light 1** entity, attached to the **dimmer device (`0x04`)** so the user sees it under "Light Driver" rather than under the sensor hub | State from hub regs **18** (on/off) and **19** (reported %). `turn_on` writes hub reg 18 → 1, dimmer `0x04` reg **16** → 1 and reg **10** → % — **all FC06 blind**, neither the hub nor the dimmer echoes writes on this firmware. `turn_off` writes hub reg 18 → 0 and dimmer reg 10 → 0. Unique id stays `sf_0x0a_light1` (legacy, hub-scoped) so existing installs don't lose their entity. |
|
|
63
|
+
| **Fan** | **Blower** for each `BlowerData` (today: `0x06`) | HA fan entity with `translation_key` blower; unique id `sf_0x06_blower`. Writes FC06 → reg **14** (0–100 %), waits for echo. |
|
|
64
|
+
| **Number** | **Light brightness** (under "Light Driver") and **Blower speed** (under "Blower") — direct 0–100 % sliders visible on the dashboard / tile cards, complementing the on/off light + fan entities | Brightness writes the same dimmer reg 10 (with reg 16 = 1 latch) as the light entity but does **not** touch the hub gate. Blower speed writes blower reg 14, identical to the fan entity's `set_percentage`. Unique ids `sf_<dimmer>_brightness` / `sf_<blower>_blower_percent`. |
|
|
65
|
+
|
|
66
|
+
All four platforms use a **dynamic discovery** loop (`setup_dynamic_entities` in `entity.py`): entities are added both at first refresh **and** on subsequent coordinator updates as new addresses appear. This is what makes the CO₂ sensor at `0x03` show up reliably even when it misses the very first poll burst — without it, anything not on the bus during HA's startup tick would stay invisible until a full reload.
|
|
67
|
+
|
|
68
|
+
Device names in the HA UI are derived from the *parsed* device class
|
|
69
|
+
(`SensorHubData` → "Sensor Hub", `CO2SensorData` → "CO₂ Sensor",
|
|
70
|
+
`FanControllerData` → "Light Driver", `BlowerData` → "Blower" — see
|
|
71
|
+
`_ROLE_NAMES` in `entity.py`), **not** from `DeviceHeader.type_name`.
|
|
72
|
+
The OEM SKUs on this rig all mis-identify themselves in register 6
|
|
73
|
+
(0x03 CO₂ reports `fan`, 0x04 dimmer reports `fan`, 0x06 blower reports
|
|
74
|
+
`light`); using `type_name` for the device label is what produced the
|
|
75
|
+
"Blower device with a CO₂ sensor inside" misnaming on first install.
|
|
76
|
+
|
|
77
|
+
There is no separate switch platform: light enable is folded into the
|
|
78
|
+
light entity. Legacy `BusMaster` helpers (`set_fan_speed` /
|
|
79
|
+
`set_fan_enable` on reg 10 / 16) remain for a true fan-class actuator if
|
|
80
|
+
one is wired at `0x04` on another rig.
|
|
81
|
+
|
|
82
|
+
## Common Header (all devices, regs 0–9)
|
|
83
|
+
|
|
84
|
+
| Reg | Meaning | Notes |
|
|
85
|
+
|-----|---------|-------|
|
|
86
|
+
| 0 | Self-reported Modbus address | matches polling addr |
|
|
87
|
+
| 1 | `0xAA` magic byte << 8 \| addr | e.g. `0xAA0A` for `0x0A` |
|
|
88
|
+
| 2–3 | Firmware version | ASCII `"HH.LL"` on SF-made units, binary `0xF784xxxx` on OEM‑branded LED/fan drivers |
|
|
89
|
+
| 4 | Model / product code | |
|
|
90
|
+
| 5 | Serial number fragment | |
|
|
91
|
+
| 6 | Device type (hi) : subtype (lo) | |
|
|
92
|
+
| 7 | Hardware version | |
|
|
93
|
+
| 8–9 | Reserved | always `0x0000` |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Sensors
|
|
98
|
+
|
|
99
|
+
### `0x03` — CO₂ sensor *(13 registers)*
|
|
100
|
+
|
|
101
|
+
Example FC03 read (`qty` ≥ 13; values illustrative):
|
|
102
|
+
|
|
103
|
+
| Reg | Hex | Decimal | Interpretation |
|
|
104
|
+
|-----|-----|---------|----------------|
|
|
105
|
+
| 0 | `0x0003` | 3 | self-addr |
|
|
106
|
+
| 1 | `0xAA03` | 43523 | magic + addr |
|
|
107
|
+
| 2 | `0x3035` | — | FW hi = `"05"` |
|
|
108
|
+
| 3 | `0x3132` | — | FW lo = `"12"` |
|
|
109
|
+
| 4 | `0x0330` | 816 | model |
|
|
110
|
+
| 5 | `0x7518` | 29976 | serial frag |
|
|
111
|
+
| 6 | `0x0308` | 776 | type / subtype (sensor-class, CO₂) |
|
|
112
|
+
| 7 | `0x0202` | 514 | hw |
|
|
113
|
+
| 8–9 | `0x0000` | 0 | reserved |
|
|
114
|
+
| **10** | `0x01B5` | **437** | **CO₂ concentration [ppm]** |
|
|
115
|
+
| 11–12 | `0x0000` | 0 | reserved (outside the 13-reg spec) |
|
|
116
|
+
|
|
117
|
+
The scan uses `DEFAULT_QTY[0x03]=13`, the response parses to `CO2SensorData`, and `co2_ppm = reg[10]`. Observed range across recent runs: **430–466 ppm**, consistent with an occupied indoor room. The value is confirmed real sensor data (not stale, not from another device).
|
|
118
|
+
|
|
119
|
+
### `0x0A` — Sensor hub (air T / RH / soil T / PPFD) + Light 1 enable *(28 registers)*
|
|
120
|
+
|
|
121
|
+
Full 28-register reads work reliably since `RS485Transport` switched
|
|
122
|
+
to deterministic, fixed-size frame reads (`spiderwire.transport`);
|
|
123
|
+
the old silence-timing heuristic truncated long frames. The
|
|
124
|
+
authoritative snapshot below comes from
|
|
125
|
+
`docs/capture-20260418-1135.sal` (Saleae, Session 9), where the OEM
|
|
126
|
+
GSS hub pulls the full 61-byte response with CRC OK.
|
|
127
|
+
|
|
128
|
+
| Reg | Hex | Decimal | Interpretation |
|
|
129
|
+
|-----|-----|---------|----------------|
|
|
130
|
+
| 0–9 | — | — | header (self-addr `0x000A`, magic `0xAA0A`, FW `"05.12"`, model `0x0330`, type `0x0400`, hw `0x0202`) |
|
|
131
|
+
| **10** | `0x00F5` | **245** | **Air temperature × 10 → 24.5 °C** |
|
|
132
|
+
| **11** | `0x01B0` | **432** | **Air humidity × 10 → 43.2 %RH** |
|
|
133
|
+
| **12** | `0xFC18` | −1000 (signed) | **Soil temperature × 10** — `-1000` means probe not connected (`soil_temp_c = None`) |
|
|
134
|
+
| **13** | `0x0146` | **326** | **PPFD in µmol/m²/s** (matches in-app display 325–326) |
|
|
135
|
+
| 14 | `0x0163` | 355 | co-tracking light channel — peak / IR / DLI TBD |
|
|
136
|
+
| 15 | `0x23F8` | 9208 | constant — calibration / device config |
|
|
137
|
+
| 16–17 | `0x0000` | 0 | reserved |
|
|
138
|
+
| **18** | `0x0001` | 1 | **Light 1 enable flag** — written by master via **blind** FC 0x06 (the hub never echoes the write — `gss-ctrl light` and HA both timed out waiting for an echo before we switched to blind writes; reads of reg 18 still confirm the new value on the next poll) |
|
|
139
|
+
| **19** | `0x0064` | 100 | **Light 1 *reported* value** (read-only status echo of the dimmer at `0x04`; writing it does not change brightness — confirmed absent from all FC06 traffic in `capture-20260418-1152.sal`) |
|
|
140
|
+
| 20–21 | `0x0023`, `0x000A` | 35, 10 | zone / group (reg 21 matches device addr = 10) |
|
|
141
|
+
| 22–27 | `0x0000` | 0 | reserved / status flags |
|
|
142
|
+
|
|
143
|
+
`BusMaster.set_light_enable(addr=0x0A, enable)` writes reg 18. VPD is
|
|
144
|
+
computed client-side from reg 10 + reg 11 via the Tetens SVP formula in
|
|
145
|
+
`SensorHubData.vpd_kpa`. PPFD from reg 13 is exposed as a sensor entity
|
|
146
|
+
with translation key `ppfd` on the hub device in Home Assistant.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Actuators
|
|
151
|
+
|
|
152
|
+
### `0x04` — Light dimmer *(24 registers)*
|
|
153
|
+
|
|
154
|
+
Header type `0x0301` reads as "fan subtype" via `registers.py`, but the actual device wired at `0x04` in the current rig is the **main grow-light dimmer** — confirmed in `docs/capture-20260418-1152.sal`: every time the user moved the OEM app's brightness slider (0 → 53 → 30 → 53 → 0 %), the hub emitted FC06 writes to `0x04 reg 10` carrying the percent value directly. Whether this is a re-flashed fan MCU or the OEM assigning the fan product code to their light driver is unresolved; treat `reg 10` here as a 0-100 % brightness setpoint.
|
|
155
|
+
|
|
156
|
+
| Reg | Interpretation | Observed |
|
|
157
|
+
|-----|----------------|----------|
|
|
158
|
+
| 0–9 | header | type `0x0301`, model `0x0426`, FW `0xF784C96D`, hw `0x0105` |
|
|
159
|
+
| **10** | **Brightness 0-100 %** (direct percent) | written by master via FC 0x06; OEM writes it blind (see below) |
|
|
160
|
+
| 11–15 | reserved | `0` |
|
|
161
|
+
| **16** | **Enable flag** — must be `1` for reg 10 writes to take effect | Throughout `capture-20260418-1152.sal` the FC03 responses show `reg[16] = 1`; the OEM sets it during an earlier session and the value sticks. If you start a fresh master without setting this bit, reg 10 writes land on the dimmer but produce no visible change. `gss-ctrl light` (and the HA light entity) writes `reg 16 ← 1` blind alongside the brightness. |
|
|
162
|
+
| 17–23 | reserved | `0` |
|
|
163
|
+
|
|
164
|
+
**No Modbus echo on FC06 writes.** The device acts on the command but never sends the 8-byte echo back — `gss-ctrl light` and the HA integration use `write_register(..., wait_for_response=False)` for this address so they don't stall 300 ms per write. FC03 reads also time out most of the time in the current rig.
|
|
165
|
+
|
|
166
|
+
The legacy fan helpers — `BusMaster.set_fan_speed(addr, 0..25)` / `set_fan_enable(addr, bool)` — still target `reg 10` / `reg 16`. If you genuinely have a duct fan wired here (different rig), speed is 0-25 and speeds below ~11 stall the motor; see `protocol-analysis.md` §"Fan Speed Control".
|
|
167
|
+
|
|
168
|
+
### `0x06` — Blower / ventilation *(16 registers)*
|
|
169
|
+
|
|
170
|
+
The OEM app labels this "Light 2" but the 0x06 SKU is physically wired
|
|
171
|
+
to the blower / ventilation fan. Confirmed in
|
|
172
|
+
`docs/capture-20260418-1452.sal`: the user drove the OEM slider
|
|
173
|
+
**0 → 74 → 60 → 25 → OFF** and every change produced an FC06 write to
|
|
174
|
+
`0x06 reg 14` carrying the percent directly; reg 10 stays `0` the entire
|
|
175
|
+
capture. The device's own 16-register broadcast echoes the live
|
|
176
|
+
setpoint back in reg 14 and latches reg 12 to 1 whenever the blower is
|
|
177
|
+
running.
|
|
178
|
+
|
|
179
|
+
| Reg | Interpretation | Notes |
|
|
180
|
+
|-----|----------------|-------|
|
|
181
|
+
| 0–9 | header | type `0x0204`, model `0x043D`, FW `0xF784C90F`, hw `0x0102` |
|
|
182
|
+
| 10 | unused | `0x0000` throughout every capture |
|
|
183
|
+
| 11 | paired flag | `0x0001` once the controller has linked, latches |
|
|
184
|
+
| **12** | **running flag** | `1` whenever the blower is actively driven, `0` when off |
|
|
185
|
+
| 13 | reserved | `0x0000` |
|
|
186
|
+
| **14** | **Blower % (0-100)** | master writes via FC 0x06; slave echoes |
|
|
187
|
+
| 15 | reserved | `0x0000` |
|
|
188
|
+
|
|
189
|
+
`BusMaster.set_blower(addr, percent)` issues `write_register` on reg 14
|
|
190
|
+
(`BLOWER_SETPOINT_REG` in `registers.py`) and waits for the standard FC06
|
|
191
|
+
response echo. The OEM UI enforces a 25 % floor above 0 (it refuses to
|
|
192
|
+
slide lower without going to OFF), but the device itself accepts any
|
|
193
|
+
0–100 value — `set_blower` and the HA blower entity allow lower values
|
|
194
|
+
if desired.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Addresses polled but silent
|
|
199
|
+
|
|
200
|
+
The OEM master polls `0x01, 0x02, 0x05, 0x07, 0x08, 0x0B–0x10, 0x13` every poll cycle. None of these respond in the current rig. The per-address register-count table in `registers.py` (`DEFAULT_QTY`) encodes the OEM's expected device class at each slot; nothing is physically installed there today.
|
|
201
|
+
|
|
202
|
+
> During `gss-ctrl poll`, the top-of-screen "N online" counter sometimes shows phantom devices at those addresses with data identical to `0x03`'s. Those are garbled frames that happened to pass CRC — not real devices. Only the clean `scan` output above should be trusted for device discovery.
|
|
203
|
+
|
|
204
|
+
## Broadcast heartbeat (master → 0x00)
|
|
205
|
+
|
|
206
|
+
FC `0x10` @ register 1001, 26 words. `BusMaster.setpoints` holds the
|
|
207
|
+
live payload; `broadcast_setpoints()` ships it, and `tick()` fires one
|
|
208
|
+
every `heartbeat_interval` seconds (default 3.5 s, matching the OEM
|
|
209
|
+
hub). Peripherals enter a master-missing fail-safe if the heartbeat
|
|
210
|
+
stops. See `protocol-analysis.md` §"Broadcast Writes" for the
|
|
211
|
+
per-register layout; default non-zero values are `reg[1009]=7` and
|
|
212
|
+
`reg[1011]=1112` (both observed constant in every capture).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Hardware Notes
|
|
2
|
+
|
|
3
|
+
Raw observations of the OEM SpiderFarmer GSS hub, kept separate from
|
|
4
|
+
the protocol reference.
|
|
5
|
+
|
|
6
|
+
## OEM hub board
|
|
7
|
+
|
|
8
|
+
- MCU: **ESP32-S3-WROOM-1**
|
|
9
|
+
- Ethernet PHY: likely [Davicom DM9051](https://www.davicom.com.tw/production-item.php?lang_id=en)
|
|
10
|
+
(SPI MAC+PHY; S3 has no internal EMAC, and the pin-compatible DM9051
|
|
11
|
+
is what fits the 25 MHz crystal next to U6)
|
|
12
|
+
- RS-485 transceiver: TBD (on-board, DE-driven)
|
|
13
|
+
- LCD: parallel TFT (driver chip not yet identified — candidates
|
|
14
|
+
include ILI9341, ST7789V, ST7796, ILI9488)
|
|
15
|
+
|
|
16
|
+
See [`firmware/sf-gss.yaml`](../firmware/sf-gss.yaml) for a draft
|
|
17
|
+
ESPHome firmware that can flash this hardware as a drop-in replacement
|
|
18
|
+
for the OEM firmware — bring-up still needs the LCD pin mapping and
|
|
19
|
+
driver chip verified.
|
|
20
|
+
|
|
21
|
+
## RJ12 pinout (peripheral-side)
|
|
22
|
+
|
|
23
|
+
| Pin colour | Signal |
|
|
24
|
+
|------------|--------|
|
|
25
|
+
| green | GND |
|
|
26
|
+
| blue | +12 V |
|
|
27
|
+
| white | +12 V |
|
|
28
|
+
| black | RS-485 |
|
|
29
|
+
| yellow | RS-485 |
|
|
30
|
+
| red | (unused / TBD) |
|
|
31
|
+
|
|
32
|
+
## Saleae Logic probe wiring (reference)
|
|
33
|
+
|
|
34
|
+
| Logic signal | Wire colour path |
|
|
35
|
+
|--------------|------------------|
|
|
36
|
+
| RS-485 A | yellow → grey → blue at Saleae |
|
|
37
|
+
| RS-485 B | green → purple → brown at Saleae |
|