Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .SRCINFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pkgbase = coolerdash
pkgdesc = Plug-in for CoolerControl that extends the LCD functionality with additional features
pkgver = 3.0.2
pkgver = 3.0.3
pkgrel = 1
url = https://github.com/damachine/coolerdash
install = coolerdash.install
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.2
3.0.3
23 changes: 10 additions & 13 deletions docs/config-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,16 @@ Connection to the CoolerControl daemon.
```json
"daemon": {
"address": "http://localhost:11987",
"access_token": "",
"password": "coolAdmin"
"access_token": ""
}
```

| Key | Default | Description |
|-----|---------|-------------|
| `address` | `http://localhost:11987` | API endpoint |
| `access_token` | `""` | **CC4:** Bearer token from CoolerControl UI → Access Protection. Format: `cc_<uuid>`. Takes precedence over `password`. |
| `password` | `coolAdmin` | **CC3 / fallback.** Ignored when `access_token` is set. |
| `access_token` | `""` | Bearer token from CoolerControl UI → Access Protection. Format: `cc_<uuid>`. Required for authenticated API access. |

> CC4: generate a token in CoolerControl UI under **Access Protection** and paste it into `access_token`.
> CC3: leave `access_token` empty, set `password`.
Generate a token in CoolerControl UI under **Access Protection** and paste it into `access_token`.

---

Expand Down Expand Up @@ -92,9 +89,9 @@ LCD display configuration tested with NZXT Kraken 2023.
- **`circle_switch_interval`**: Slot switch interval for circle mode in seconds (1–60, default: `5`)
- **`content_scale_factor`**: Safe area percentage (0.5–1.0, default: `0.98`)
- **`inscribe_factor`**: Inscribe factor for circular displays (default: `0.70710678` = 1/√2)
- **`sensor_slot_up`**: Sensor shown in top slot: `cpu`, `gpu`, or `liquid` (default: `cpu`)
- **`sensor_slot_mid`**: Sensor shown in middle slot (default: `liquid`)
- **`sensor_slot_down`**: Sensor shown in bottom slot (default: `gpu`)
- **`sensor_slot_1`**: Sensor for slot 1: `cpu`, `gpu`, or `liquid` (default: `cpu`)
- **`sensor_slot_2`**: Sensor for slot 2 (default: `liquid`)
- **`sensor_slot_3`**: Sensor for slot 3 (default: `gpu`)

Sensor slots control which sensor appears in each display position for both dual and circle mode.

Expand Down Expand Up @@ -197,9 +194,9 @@ All values are in pixels unless noted. Positions are calculated dynamically from
"bar_border_enabled": 1,
"label_margin_left": 1,
"label_margin_bar": 1,
"bar_height_up": 0,
"bar_height_mid": 0,
"bar_height_down": 0
"bar_height_1": 0,
"bar_height_2": 0,
"bar_height_3": 0
}
```

Expand All @@ -212,7 +209,7 @@ All values are in pixels unless noted. Positions are calculated dynamically from
| `bar_border_enabled` | `1` | Enable bar border (`1`/`0`) |
| `label_margin_left` | `1` | Left label margin multiplier |
| `label_margin_bar` | `1` | Margin between label and bar |
| `bar_height_up/mid/down` | `0` | Per-slot bar height override. `0` = use `bar_height` |
| `bar_height_1/2/3` | `0` | Per-slot bar height override. `0` = use `bar_height` |

---

Expand Down
122 changes: 35 additions & 87 deletions docs/coolercontrol-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ CoolerDash acts as a specialized LCD client for CoolerControl, fetching temperat
### API Endpoints Used

```
POST /login - CC3 authentication (Basic Auth cookie)
GET /devices - Device enumeration
POST /status - Temperature sensor data
PUT /devices/{uid}/settings/lcd/lcd/images - LCD image upload
Expand Down Expand Up @@ -147,50 +146,32 @@ cc_cleanup_response_buffer(&response);

#### 2. Session State

**Purpose**: Maintain persistent CURL session with cookie-based authentication
**Purpose**: Maintain persistent CURL session with Bearer-token authentication

```c
typedef struct {
CURL *curl_handle;
char cookie_jar[CC_COOKIE_SIZE];
char access_token[CC_BEARER_HEADER_SIZE];
int session_initialized;
} CoolerControlSession;

static CoolerControlSession cc_session = {
.curl_handle = NULL,
.cookie_jar = {0},
.access_token = {0},
.session_initialized = 0
};
```

**Cookie Jar**: `/tmp/coolerdash_cookie_{PID}.txt`
- Stores session cookies for authenticated requests
- Cleaned up on program exit
- Per-process isolation via PID

#### 3. Authentication Flow

**Function**: `init_coolercontrol_session(const Config *config)`

**CC4 — Bearer Token (primary):**
- If `access_token` is set in config, all requests use `Authorization: Bearer cc_<uuid>`
- No cookie jar, no `/login` call needed
- Token is generated in CoolerControl UI under **Access Protection**
CoolerDash uses one authentication mode only:

**CC3 — Basic Auth cookie (fallback):**
- Used when `access_token` is empty
- Sends `POST /login` with HTTP Basic Auth (`CCAdmin:{password}`)
- Cookie jar stored at `/tmp/coolerdash_cookie_{PID}.txt` (in-memory, written only on cleanup, deleted immediately after)

**Steps (CC3 path)**:
1. Initialize libcurl global state
2. Create CURL easy handle
3. Configure cookie jar for session persistence
4. Build login URL: `{daemon_address}/login`
5. Set HTTP Basic Auth: `CCAdmin:{password}`
6. Send POST request with empty body
7. Validate response (200/204)
8. Store cookies for subsequent requests
- `access_token` must be set in config
- All requests use `Authorization: Bearer cc_<uuid>`
- Token is generated in CoolerControl UI under **Access Protection**
- No legacy login or session fallback is used

---

Expand All @@ -203,55 +184,28 @@ Called **once at startup** after device initialization. Registers `shutdown.png`
- Endpoint: `PUT {address}/devices/{uid}/settings/lcd/lcd/shutdown-image`
- Same multipart form as live image upload
- CC4 stores image server-side; displays it when CoolerControl stops
- Returns gracefully on 404 (CC3 — endpoint does not exist)
- Returns gracefully on 404 when the endpoint is unavailable

**Implementation**:
```c
int init_coolercontrol_session(const Config *config)
{
if (!config || config->access_token[0] == '\0')
return 0;

curl_global_init(CURL_GLOBAL_DEFAULT);
cc_session.curl_handle = curl_easy_init();

// Cookie jar: /tmp/coolerdash_cookie_{PID}.txt
snprintf(cc_session.cookie_jar, sizeof(cc_session.cookie_jar),
"/tmp/coolerdash_cookie_%d.txt", getpid());

curl_easy_setopt(cc_session.curl_handle, CURLOPT_COOKIEJAR, cc_session.cookie_jar);
curl_easy_setopt(cc_session.curl_handle, CURLOPT_COOKIEFILE, cc_session.cookie_jar);

// Login request
char login_url[CC_URL_SIZE];
snprintf(login_url, sizeof(login_url), "%s/login", config->daemon_address);

char userpwd[CC_USERPWD_SIZE];
snprintf(userpwd, sizeof(userpwd), "CCAdmin:%s", config->daemon_password);

curl_easy_setopt(cc_session.curl_handle, CURLOPT_URL, login_url);
curl_easy_setopt(cc_session.curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(cc_session.curl_handle, CURLOPT_USERPWD, userpwd);
curl_easy_setopt(cc_session.curl_handle, CURLOPT_POST, 1L);
curl_easy_setopt(cc_session.curl_handle, CURLOPT_POSTFIELDS, "");

CURLcode res = curl_easy_perform(cc_session.curl_handle);
long response_code = 0;
curl_easy_getinfo(cc_session.curl_handle, CURLINFO_RESPONSE_CODE, &response_code);

// Secure password cleanup
memset(userpwd, 0, sizeof(userpwd));

if (res == CURLE_OK && (response_code == 200 || response_code == 204)) {
cc_session.session_initialized = 1;
return 1;
}

return 0;

snprintf(cc_session.access_token, sizeof(cc_session.access_token),
"Authorization: Bearer %s", config->access_token);
cc_session.session_initialized = 1;
return 1;
}
```

**Security Notes**:
- Password immediately zeroed after use (`memset`)
- HTTPS support with SSL verification
- Cookie-based session prevents password re-transmission
- Bearer token is attached explicitly to every authenticated request
- No fallback authentication path exists

#### 4. LCD Image Upload

Expand Down Expand Up @@ -353,8 +307,8 @@ int send_image_to_lcd(const Config *config, const char *image_path, const char *
**Steps**:
1. Cleanup CURL easy handle
2. Cleanup libcurl global state
3. Remove cookie jar file
4. Mark session as uninitialized
3. Mark session as uninitialized
4. Clear the cached Bearer header
5. Set cleanup flag to prevent double-cleanup

**Implementation**:
Expand All @@ -373,12 +327,10 @@ void cleanup_coolercontrol_session(void)

// Global CURL cleanup
curl_global_cleanup();

// Remove cookie jar
unlink(cc_session.cookie_jar);


// Mark as uninitialized
cc_session.session_initialized = 0;
cc_session.access_token[0] = '\0';
cleanup_done = 1;
}
```
Expand Down Expand Up @@ -981,11 +933,7 @@ main() startup
2. init_coolercontrol_session(&config)
├─→ POST /login
│ (HTTP Basic Auth: CCAdmin:{password})
│ Response: 200 OK + session cookie
└─→ Save cookie to /tmp/coolerdash_cookie_{PID}.txt
└─→ Build Authorization: Bearer cc_<uuid>
3. init_device_cache(&config)
Expand Down Expand Up @@ -1074,7 +1022,7 @@ Program exit
struct Config {
// CoolerControl connection
char daemon_address[256]; // e.g., "http://127.0.0.1:11987"
char daemon_password[128]; // CCAdmin password
char access_token[64]; // Bearer token

// Display settings
uint16_t display_width; // e.g., 640 (auto-detected if 0)
Expand Down Expand Up @@ -1111,7 +1059,7 @@ static struct {
```c
typedef struct {
CURL *curl_handle; // Persistent CURL handle
char cookie_jar[CC_COOKIE_SIZE]; // Cookie file path
char access_token[CC_BEARER_HEADER_SIZE]; // Authorization header
int session_initialized; // 0 = not initialized, 1 = ready
} CoolerControlSession;

Expand Down Expand Up @@ -1163,18 +1111,19 @@ LOG_STATUS - Normal operation status (upload success)
**Symptom**: `init_coolercontrol_session()` returns 0

**Causes**:
- Wrong password in config
- Missing or invalid access token
- CoolerControl daemon not running
- Daemon address unreachable

**Debugging**:
```c
log_message(LOG_ERROR, "Login failed: CURL code %d, HTTP code %ld", res, response_code);
log_message(LOG_ERROR,
"CoolerControl access token missing; token-only authentication is required");
```

**Resolution**:
- Verify daemon is running: `systemctl status coolercontrol`
- Check password in `config.json`
- Check `access_token` in `config.json`
- Test connection: `curl http://127.0.0.1:11987/devices`

#### 2. Device Not Found
Expand Down Expand Up @@ -1411,10 +1360,9 @@ if (response.data && response.size > 0) {

**Test authentication**:
```bash
curl -X POST http://127.0.0.1:11987/login \
-u CCAdmin:your_password \
-c cookies.txt \
-v
curl http://127.0.0.1:11987/devices \
-H "Authorization: Bearer cc_<your-uuid-here>" \
-v
```

**Test device list**:
Expand Down Expand Up @@ -1506,7 +1454,7 @@ cat /tmp/coolerdash_cookie_*.txt
**Optimization**:
- Device cache eliminates repeated /devices calls
- Persistent CURL session reuses HTTP connection
- Cookie-based auth avoids re-login
- Shared Bearer header avoids rebuilding auth state on every request

### Memory Usage

Expand Down Expand Up @@ -1560,7 +1508,7 @@ cat /tmp/coolerdash_cookie_*.txt
## Conclusion

The CoolerControl API integration provides a robust, efficient interface for:
- **Authentication**: Cookie-based session with HTTP Basic Auth
- **Authentication**: Bearer token via CoolerControl Access Protection
- **Device Detection**: Automatic CoolerControl LCD device discovery and caching
- **Temperature Monitoring**: Real-time CPU/GPU data retrieval
- **LCD Upload**: Multipart image upload with brightness/orientation control
Expand Down
Loading
Loading