frostbay: BLE-Steuerung produktiv + Doku + Repo-Aufräumung
- tools/frostbay_ble.py: Vollständige Frostbay-Steuerung via BlueZ/D-Bus (Fan/Pumpe drosseln, Status lesen, RGB vorbereitet) - docs/frostbay-ble-protocol.md: Protokoll-Doku aus Live-Reverse-Engineering - README.md: Projektstand aktualisiert (niri/DMS, Frostbay, Roadmap) - tools/wmi/: Build-Artefakte entfernt (Makefile-Output)
This commit is contained in:
parent
e9132cc9e4
commit
a7b23f0eea
4 changed files with 397 additions and 34 deletions
93
README.md
93
README.md
|
|
@ -1,32 +1,75 @@
|
|||
# SuperX-goes-Arch
|
||||
|
||||
Arbeitsrepo für Linux-/Arch-/COSMIC-Support auf dem OneXplayer Super X.
|
||||
Arbeitsrepo für Linux-/Arch-Support auf dem OneXplayer Super X.
|
||||
|
||||
Fokus:
|
||||
- RGB-Stripes / interne LED-Steuerung
|
||||
- Keyboard-/Controller-/HID-Pfade
|
||||
- Frostbay-Wasserkühlung (BLE/HID)
|
||||
- Reverse Engineering von OEM-Tools und Windows-Artefakten
|
||||
- später: echte Linux-Tools / evtl. hhd-Plugin / udev+hwdb / Arch-Pakete
|
||||
## Aktueller Stand (2026-06-05)
|
||||
|
||||
Aktueller Stand
|
||||
- Für Super X gibt es schon brauchbare Linux-Basisarbeit im OXP/hhd-Umfeld.
|
||||
- Fan-/EC-Themen sind teils schon im Kernel/hhd-Land sichtbar.
|
||||
- Für Frostbay gibt es sehr frische Community-Arbeit.
|
||||
- Für Super-X-RGB/Keyboard ist noch viel Nebel da. Da müssen wir ziemlich sicher selbst reverse engineeren.
|
||||
- Das alte Repo `onexplayer-superxcontrol` wurde inzwischen als Referenzquelle gegengeprüft; relevante Legacy-HID-Pakete und Statusnotizen wurden in dieses Repo übernommen.
|
||||
- In `~/Downloads` liegen zusätzlich OEM-Artefakte wie `frostbay firmware.zip` und `HH-GA25-SUPERX-Devices_V1.0.zip`, die wir weiter ausschlachten können.
|
||||
### Fertig
|
||||
|
||||
Wichtige Hinweise
|
||||
- Deine ersetzten RGB-Stripes sind WS2812B. Das ist hilfreich, aber nicht die eigentliche Linux-Baustelle.
|
||||
- Entscheidend ist, welches Board/EC/MCU das Datensignal erzeugt und über welchen Transport das Gerät angesteuert wird.
|
||||
- Frostbay wirkt deutlich greifbarer als die interne RGB-Leiste, weil es bereits konkrete BLE-/GATT-Hinweise gibt.
|
||||
| Bereich | Status |
|
||||
|---|---|
|
||||
| **Frostbay BLE (Drosselung)** | Produktiv — `tools/frostbay_ble.py` steuert Fan/Pumpe via BlueZ/D-Bus |
|
||||
| **Frostbay Status** | Live-Read von Temp, RPM, Flow, Alerts via BLE GATT |
|
||||
| **Niri + DMS** | Installiert & konfiguriert — niri 26.04 + DankMaterialShell 1.4.6 |
|
||||
| **Protokoll-Doku** | `docs/frostbay-ble-protocol.md` — vollständig dokumentiert |
|
||||
|
||||
Ordner
|
||||
- `docs/` Research, Protokollnotizen, Roadmap
|
||||
- `tools/` kleine Hilfsskripte für Analyse/Probing
|
||||
### In Arbeit / Blockiert
|
||||
|
||||
Nächste sinnvolle Stoßrichtung
|
||||
1. Frostbay unter Linux/BlueZ auf Arch reproduzierbar sichtbar machen.
|
||||
2. Parallel Super-X-HID/EC-/RGB-Pfad weiter eingrenzen.
|
||||
3. Danach kleine Linux-Tools bauen statt blind Bytes zu würfeln.
|
||||
| Bereich | Status |
|
||||
|---|---|
|
||||
| Frostbay RGB | Funktional via BLE, aber LED-Strips physisch nicht angeschlossen |
|
||||
| Mainboard RGB (WS2812B) | Hardware-Reverse läuft — Logic Analyzer bestellt, `tools/superx_signal_decode.py` vorbereitet |
|
||||
| WMI/EC-Kernel-Module | Erste Module kompilieren, aber noch nicht funktional |
|
||||
|
||||
## Ordner
|
||||
|
||||
- `docs/` — Protokolldokumentation, Research-Notizen
|
||||
- `tools/` — Python-Skripte und WMI-Kernel-Module
|
||||
- `config/` — Niri/Wayland-Konfigurationen
|
||||
|
||||
## Frostbay-Steuerung
|
||||
|
||||
```bash
|
||||
# Status check
|
||||
sudo python3 tools/frostbay_ble.py status
|
||||
|
||||
# Drosseln (custom mode)
|
||||
sudo python3 tools/frostbay_ble.py mode custom --fan 25 --pump 25
|
||||
|
||||
# Ausschalten
|
||||
sudo python3 tools/frostbay_ble.py off
|
||||
```
|
||||
|
||||
Details: `docs/frostbay-ble-protocol.md`
|
||||
|
||||
## Niri + DankMaterialShell
|
||||
|
||||
Niri ist ein scrollend-kachelnder Wayland-Compositor, DMS liefert
|
||||
Material-3-Shell mit Panel, Launcher, Notification-Center und mehr.
|
||||
Konfiguration liegt unter `config/niri/`.
|
||||
|
||||
```bash
|
||||
# Installation (bereits erfolgt)
|
||||
sudo pacman -S dms-shell dms-shell-niri matugen
|
||||
```
|
||||
|
||||
## Hardware
|
||||
|
||||
- OneXplayer Super X (Intel Meteor Lake)
|
||||
- Eingelötete WS2812B-Stripes am Mainboard-Anschluss RGB2
|
||||
- SYSTEK ZT5571VG-Platine (GA25-RGB_FPC_VA_5522) als RGB-Controller
|
||||
- Frostbay ONEC1 Wasserkühlung (BLE GATT)
|
||||
|
||||
## Git Remote
|
||||
|
||||
```bash
|
||||
git remote add origin ssh://git@git.nepharius.at:2222/nepharius/superx-goes-arch.git
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. ~~Frostbay unter Linux/BlueZ auf Arch reproduzierbar steuern~~
|
||||
2. Logic Analyzer an Mainboard-RGB2-Pin → OEM-Protokoll decodieren
|
||||
3. Entweder kompatible Ersatz-LEDs oder Software-Übersetzer (MCU→WS2812B)
|
||||
4. Niri/DMS-Integration mit Frostbay-Daemon (Fan-Kurven + Quick-Toggles)
|
||||
5. Optional: hhd-Plugin oder Standalone-Daemon für vollständige Systemsteuerung
|
||||
|
|
|
|||
104
docs/frostbay-ble-protocol.md
Normal file
104
docs/frostbay-ble-protocol.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Frostbay ONEC1 — BLE GATT Protocol (Getestet & Funktionierend)
|
||||
|
||||
Stand: 2026-06-05 — Live getestet an CoolingSystem_ONEC1 via BlueZ/D-Bus.
|
||||
|
||||
## TL;DR
|
||||
|
||||
Frostbay verwendet **BLE GATT** (nicht HID!) für Steuerung. Fan/Pumpe-Drosselung
|
||||
funktioniert, RGB ist aktuell nicht nutzbar (LED-Strips nach Reparatur nicht
|
||||
angeschlossen).
|
||||
|
||||
```
|
||||
sudo python3 tools/frostbay_ble.py status # Live-Werte
|
||||
sudo python3 tools/frostbay_ble.py mode custom --fan 25 --pump 25 # Drosseln
|
||||
sudo python3 tools/frostbay_ble.py off # Ausschalten
|
||||
```
|
||||
|
||||
## Verbindung
|
||||
|
||||
| Eigenschaft | Wert |
|
||||
|---|---|
|
||||
| Gerätename | `CoolingSystem_ONEC1` |
|
||||
| BT-Adresse | variabel (z.B. `C8:17:17:F5:C8:62`) |
|
||||
| Service | `0000ffe0-0000-1000-8000-00805f9b34fb` |
|
||||
| Char (R/W) | `0000ffe1-0000-1000-8000-00805f9b34fb` (64 Byte Blocks) |
|
||||
|
||||
Der Service erscheint als `service003f` im BlueZ-Objektbaum unter
|
||||
`/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/`.
|
||||
|
||||
## Protokoll
|
||||
|
||||
### Lesen (Status abfragen)
|
||||
|
||||
Einfach die `ffe1`-Characteristic per D-Bus `ReadValue()` auslesen.
|
||||
Antwort ist ein 64-Byte-Block:
|
||||
|
||||
```
|
||||
Byte 0: 0xC1 (Echo)
|
||||
Byte 1: 0x10 (Read-Flag — nie ändern!)
|
||||
Byte 2: 0x08 oder 0x00
|
||||
Byte 3: Module Switch: 0x01=follow, 0x02=save, 0x04=reconnect
|
||||
Byte 4: Betriebsmodus
|
||||
Byte 5: Fan PWM (0-100%)
|
||||
Byte 6-7: Fan RPM (uint16)
|
||||
Byte 8: Pump PWM (0-100%)
|
||||
Byte 9-10: Pump RPM (uint16)
|
||||
Byte 11-12: Water Flow mL/min (uint16)
|
||||
Byte 13: Inlet Temp (°C)
|
||||
Byte 14: Outlet Temp (°C)
|
||||
Byte 15: Status-Bitmask
|
||||
Byte 16: RGB Mode (0x00-0x13=preset, 0xFD=Helligkeit, 0xFE=Custom)
|
||||
Byte 17: RGB Switch (0-7 Bitmask: Strip1/2/3)
|
||||
Byte 18: RGB Speed (0-9)
|
||||
Byte 19: RGB Brightness (0-4)
|
||||
Byte 20-22: RGB Custom R/G/B (Readback)
|
||||
...
|
||||
Byte 57: Auto-RGB-Off Timeout
|
||||
```
|
||||
|
||||
### Schreiben (Steuerung)
|
||||
|
||||
**Write-Flag = 0x02** in Byte 1 setzen (kritisch!). Ohne 0x02 wird der
|
||||
Write ignoriert. Dann kompletter 64-Byte-Block schreiben.
|
||||
|
||||
Betriebsmodi:
|
||||
| Code | Name | Beschreibung |
|
||||
|---|---|---|
|
||||
| 0x00 | off | Aus |
|
||||
| 0x01 | silent | Leise (25% Fan, 30% Pump) |
|
||||
| 0x02 | gentle | Sanft |
|
||||
| 0x03 | strong | Stark |
|
||||
| 0x04 | extreme | Extrem |
|
||||
| 0xFE | smart | Intelligent (automatisch) |
|
||||
| 0xFF | custom | Manuelle Fan/Pump-Steuerung |
|
||||
|
||||
Nur im custom-Mode (0xFF) werden Fan- und Pump-PWM vom User akzeptiert.
|
||||
Die anderen Modi haben feste Kurven.
|
||||
|
||||
### RGB
|
||||
|
||||
**Theoretisch unterstützt, praktisch nicht nutzbar** (LED-Strips nach
|
||||
Reparatur physisch nicht angeschlossen).
|
||||
|
||||
Workflow (falls LEDs vorhanden):
|
||||
1. `buff[16]=0xFD, buff[17]=0x07, buff[18]=5, buff[19]=4` → Helligkeit max
|
||||
2. `buff[16]=0xFE, buff[17..19]=R,G,B` → Farbe setzen
|
||||
3. Zwei separate 64-Byte-Writes, nicht ein kombinierter!
|
||||
|
||||
## Implementierung
|
||||
|
||||
`tools/frostbay_ble.py`:
|
||||
- Python + D-Bus (BlueZ)
|
||||
- `status` — Live-Werte anzeigen
|
||||
- `mode <mode> [--fan N] [--pump N]` — Modus setzen
|
||||
- `fan <pwm>` / `pump <pwm>` — Einzelwerte (implizit custom mode)
|
||||
- `rgb <r> <g> <b>` — RGB setzen (nur wenn LEDs angeschlossen)
|
||||
- `off` — Frostbay ausschalten
|
||||
|
||||
Benötigt Root-Rechte für D-Bus-Zugriff auf BlueZ.
|
||||
|
||||
## Quellen
|
||||
|
||||
- `接口.C` aus OEM-Firmware-ZIP (Protokoll-Logik)
|
||||
- `coolingsystem_debugger.html` (Web-Bluetooth-Debugger, bestätigt 64-Byte-BLE)
|
||||
- Reverse-Engineering durch SuperX-goes-Arch, 2026-06-05
|
||||
225
tools/frostbay_ble.py
Normal file
225
tools/frostbay_ble.py
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Frostbay BLE GATT reader/controller."""
|
||||
|
||||
import dbus
|
||||
import time
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
BUS = dbus.SystemBus()
|
||||
BASE = "/org/bluez/hci0/dev_C8_17_17_F5_C8_62"
|
||||
CHAR_FFE1_RW = f"{BASE}/service003f/char0040" # read+write
|
||||
CHAR_FFE2_R = f"{BASE}/service003f/char0043" # read
|
||||
CHAR_FFE3_W = f"{BASE}/service003f/char0046" # write
|
||||
|
||||
MODE_MAP = {
|
||||
0x00: "off",
|
||||
0x01: "silent",
|
||||
0x02: "gentle",
|
||||
0x03: "strong",
|
||||
0x04: "extreme",
|
||||
0xFE: "smart",
|
||||
0xFF: "custom",
|
||||
}
|
||||
MODE_REV = {v: k for k, v in MODE_MAP.items()}
|
||||
|
||||
STATUS_BITS = {
|
||||
0x01: "pump_error",
|
||||
0x02: "flow_error",
|
||||
0x04: "fan_error",
|
||||
0x08: "outlet_temp_error",
|
||||
0x10: "inlet_temp_error",
|
||||
0x20: "flow_rising",
|
||||
0x40: "flow_dropping",
|
||||
}
|
||||
|
||||
|
||||
def read_char(path):
|
||||
char = dbus.Interface(BUS.get_object("org.bluez", path),
|
||||
"org.bluez.GattCharacteristic1")
|
||||
return bytes(char.ReadValue({}))
|
||||
|
||||
|
||||
def write_char(path, data):
|
||||
char = dbus.Interface(BUS.get_object("org.bluez", path),
|
||||
"org.bluez.GattCharacteristic1")
|
||||
char.WriteValue(dbus.Array(data, signature="y"), {})
|
||||
|
||||
|
||||
def read_state() -> bytes:
|
||||
"""Read full 64-byte state from ffe1."""
|
||||
return read_char(CHAR_FFE1_RW)
|
||||
|
||||
|
||||
def parse_state(data: bytes):
|
||||
b = data
|
||||
if len(b) < 58:
|
||||
return {"error": f"too short: {len(b)} bytes, need >=58"}
|
||||
|
||||
state = {
|
||||
"cmd_echo": b[0],
|
||||
"module_switch": b[3],
|
||||
"mode_raw": b[4],
|
||||
"mode_name": MODE_MAP.get(b[4], f"0x{b[4]:02X}"),
|
||||
"fan_pwm": b[5],
|
||||
"fan_rpm": (b[6] << 8) | b[7],
|
||||
"pump_pwm": b[8],
|
||||
"pump_rpm": (b[9] << 8) | b[10],
|
||||
"water_flow": (b[11] << 8) | b[12],
|
||||
"inlet_temp": b[13],
|
||||
"outlet_temp": b[14],
|
||||
"status_raw": b[15],
|
||||
"status_alerts": [n for bit, n in STATUS_BITS.items() if b[15] & bit],
|
||||
"rgb_mode": b[16],
|
||||
"rgb_switch": b[17],
|
||||
"rgb_speed": b[18],
|
||||
"rgb_brightness": b[19],
|
||||
"rgb_r": b[20],
|
||||
"rgb_g": b[21],
|
||||
"rgb_b": b[22],
|
||||
"auto_rgb_off": b[57],
|
||||
}
|
||||
state["is_healthy"] = b[15] == 0
|
||||
return state
|
||||
|
||||
|
||||
def print_state(s):
|
||||
if "error" in s:
|
||||
print(f"ERROR: {s['error']}")
|
||||
return
|
||||
|
||||
health = "OK" if s["is_healthy"] else "ALARM: " + ", ".join(s["status_alerts"])
|
||||
|
||||
print(f"""
|
||||
Modus: {s['mode_name']} (0x{s['mode_raw']:02X})
|
||||
Fan: PWM={s['fan_pwm']}% RPM={s['fan_rpm']}
|
||||
Pump: PWM={s['pump_pwm']}% RPM={s['pump_rpm']}
|
||||
Flow: {s['water_flow']} mL/min
|
||||
Temp In: {s['inlet_temp']} C Temp Out: {s['outlet_temp']} C
|
||||
Status: {health}
|
||||
RGB Mode: 0x{s['rgb_mode']:02X} Switch={s['rgb_switch']} Speed={s['rgb_speed']} Brightness={s['rgb_brightness']}
|
||||
RGB Color: R={s['rgb_r']} G={s['rgb_g']} B={s['rgb_b']}
|
||||
""")
|
||||
|
||||
# Raw dump
|
||||
raw = s.get("_raw") or b""
|
||||
if raw:
|
||||
print(" Raw (hex):")
|
||||
for i in range(0, len(raw), 20):
|
||||
line = raw[i:i+20]
|
||||
print(f" {i:4d}: {line.hex(' ')}")
|
||||
|
||||
|
||||
def write_state(mode_code: int, fan_pwm: int = None, pump_pwm: int = None,
|
||||
rgb_mode: int = None, rgb_r: int = None, rgb_g: int = None,
|
||||
rgb_b: int = None):
|
||||
"""Write configuration and read back."""
|
||||
# Read current state as baseline
|
||||
cur = read_state()
|
||||
cur_raw = bytes.fromhex(cur.hex())
|
||||
|
||||
buff = bytearray(64)
|
||||
buff[:] = cur_raw[:64] # copy current state
|
||||
|
||||
buff[0] = 0xC1 # command
|
||||
buff[1] = 0x02 # WRITE flag (0x02 = write, 0x10 = read)
|
||||
buff[3] = 0x01 # module: follow
|
||||
buff[4] = mode_code
|
||||
|
||||
if fan_pwm is not None:
|
||||
buff[5] = fan_pwm & 0xFF
|
||||
if pump_pwm is not None:
|
||||
buff[8] = pump_pwm & 0xFF
|
||||
|
||||
if rgb_mode is not None:
|
||||
buff[16] = rgb_mode & 0xFF
|
||||
if rgb_mode == 0xFE or rgb_mode == 0xFF:
|
||||
# RGB values go to write_buff[17-19] (→ Ram_op_params_buff[20-22])
|
||||
if rgb_r is not None:
|
||||
buff[17] = rgb_r & 0xFF
|
||||
if rgb_g is not None:
|
||||
buff[18] = rgb_g & 0xFF
|
||||
if rgb_b is not None:
|
||||
buff[19] = rgb_b & 0xFF
|
||||
# Note: RGB readback is at buff[20-22], write positions are 17-19
|
||||
# per 接口.C line 59-61
|
||||
|
||||
write_char(CHAR_FFE1_RW, bytes(buff))
|
||||
time.sleep(0.3)
|
||||
|
||||
# Read back
|
||||
resp = read_state()
|
||||
return parse_state(resp)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="cmd")
|
||||
|
||||
sub.add_parser("status", help="Read Frostbay state")
|
||||
|
||||
p_mode = sub.add_parser("mode", help="Set mode")
|
||||
p_mode.add_argument("mode", choices=list(MODE_REV.keys()))
|
||||
p_mode.add_argument("--fan", type=int, default=None, help="Fan PWM 0-100")
|
||||
p_mode.add_argument("--pump", type=int, default=None, help="Pump PWM 0-100")
|
||||
|
||||
p_fan = sub.add_parser("fan", help="Set fan PWM")
|
||||
p_fan.add_argument("pwm", type=int)
|
||||
|
||||
p_pump = sub.add_parser("pump", help="Set pump PWM")
|
||||
p_pump.add_argument("pwm", type=int)
|
||||
|
||||
p_rgb = sub.add_parser("rgb", help="Set custom RGB color")
|
||||
p_rgb.add_argument("r", type=int)
|
||||
p_rgb.add_argument("g", type=int)
|
||||
p_rgb.add_argument("b", type=int)
|
||||
|
||||
sub.add_parser("off", help="Turn Frostbay off")
|
||||
sub.add_parser("raw", help="Raw hex dump of ffe1")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.cmd == "status" or args.cmd is None:
|
||||
raw = read_state()
|
||||
s = parse_state(raw)
|
||||
s["_raw"] = raw[:58]
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "raw":
|
||||
raw = read_state()
|
||||
print(raw[:64].hex(" "))
|
||||
print()
|
||||
s = parse_state(raw)
|
||||
s["_raw"] = raw[:64]
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "mode":
|
||||
code = MODE_REV[args.mode]
|
||||
s = write_state(code, fan_pwm=args.fan, pump_pwm=args.pump)
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "fan":
|
||||
cur = parse_state(read_state())
|
||||
s = write_state(0xFF, fan_pwm=args.pwm, pump_pwm=cur["pump_pwm"])
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "pump":
|
||||
cur = parse_state(read_state())
|
||||
s = write_state(0xFF, pump_pwm=args.pwm, fan_pwm=cur["fan_pwm"])
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "rgb":
|
||||
cur = parse_state(read_state())
|
||||
s = write_state(0xFF, rgb_mode=0xFE,
|
||||
fan_pwm=cur["fan_pwm"],
|
||||
pump_pwm=cur["pump_pwm"],
|
||||
rgb_r=args.r, rgb_g=args.g, rgb_b=args.b)
|
||||
print_state(s)
|
||||
|
||||
elif args.cmd == "off":
|
||||
s = write_state(0x00)
|
||||
print_state(s)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
tools/wmi/.gitignore
vendored
9
tools/wmi/.gitignore
vendored
|
|
@ -1,9 +0,0 @@
|
|||
*.o
|
||||
*.ko
|
||||
*.mod
|
||||
*.mod.c
|
||||
*.mod.o
|
||||
.modules.order
|
||||
Module.symvers
|
||||
modules.order
|
||||
*.cmd
|
||||
Loading…
Add table
Reference in a new issue