- The card opens on a few tiles (room temperature + humidity, feeder portions today / cap, AC, heater, lights) drawn from a new "state" read -- a small JSON from the bot's door -- and redraws them after every press and on click. The status button and the text dump are gone; a press shows the first line of the bot's reply for a few seconds. - feed: a button behind a second tap (window.confirm); the bot keeps its daily cap from the feeder's own counter. - The widget's header icon was a CSS class nothing defined, so the dashboard showed none; css/zigbee_card.css now defines .icon-zigbee-card from img/app-dark.svg, inverted for dark mode like Nextcloud's own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
77 lines
3.4 KiB
PHP
77 lines
3.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\ZigbeeCard\Controller;
|
|
|
|
use OCA\ZigbeeCard\AppInfo\Application;
|
|
use OCA\ZigbeeCard\Buttons;
|
|
use OCP\AppFramework\Controller;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
|
use OCP\AppFramework\Http\JSONResponse;
|
|
use OCP\AppFramework\Services\IAppConfig;
|
|
use OCP\Http\Client\IClientService;
|
|
use OCP\IRequest;
|
|
|
|
/**
|
|
* POST /apps/zigbee_card/cmd {line: "lights day"}
|
|
*
|
|
* The path: this controller -> the house bot's door (an HTTP listener on a private
|
|
* address that only this server reaches) -> the bot's own command handler -> mosquitto
|
|
* -> zigbee2mqtt -> the device -> read back -> the bot's reply text comes back here
|
|
* verbatim and the card prints it.
|
|
*
|
|
* Two app values, set by the admin with occ, never in git:
|
|
* occ config:app:set zigbee_card door_url --value=http://<private address>:<port>/cmd
|
|
* occ config:app:set zigbee_card door_key --value=<the secret the bot holds> --sensitive
|
|
*
|
|
* Any logged-in user may press; the requesttoken check is Nextcloud's default on this
|
|
* route, so only the dashboard page can post here. The user id goes to the bot as "who"
|
|
* for its audit log.
|
|
*/
|
|
class CardController extends Controller {
|
|
|
|
public function __construct(
|
|
IRequest $request,
|
|
private IAppConfig $appConfig,
|
|
private IClientService $clientService,
|
|
private ?string $userId,
|
|
) {
|
|
parent::__construct(Application::APP_ID, $request);
|
|
}
|
|
|
|
#[NoAdminRequired]
|
|
public function cmd(string $line = ''): JSONResponse {
|
|
$line = strtolower(trim(preg_replace('/\s+/', ' ', $line)));
|
|
if (!Buttons::allowed($line)) {
|
|
return new JSONResponse(['ok' => false, 'reply' => 'not a card command'], Http::STATUS_FORBIDDEN);
|
|
}
|
|
$url = $this->appConfig->getAppValueString('door_url', '');
|
|
$key = $this->appConfig->getAppValueString('door_key', '');
|
|
if ($url === '' || $key === '') {
|
|
return new JSONResponse(['ok' => false, 'reply' => 'the card has no door yet (occ config:app:set zigbee_card door_url / door_key)'], Http::STATUS_SERVICE_UNAVAILABLE);
|
|
}
|
|
try {
|
|
$resp = $this->clientService->newClient()->post($url, [
|
|
'headers' => ['X-Zigbee-Key' => $key, 'Content-Type' => 'application/json'],
|
|
'body' => json_encode(['line' => $line, 'who' => (string)$this->userId]),
|
|
// a scene is several publishes and read-backs; give the bot time to answer
|
|
'timeout' => 60,
|
|
// the door is a private address by design; Nextcloud's client refuses those unless told
|
|
'nextcloud' => ['allow_local_address' => true],
|
|
]);
|
|
$data = json_decode((string)$resp->getBody(), true);
|
|
if (!is_array($data) || !isset($data['reply'])) {
|
|
return new JSONResponse(['ok' => false, 'reply' => 'the door answered nonsense'], Http::STATUS_BAD_GATEWAY);
|
|
}
|
|
$out = ['ok' => (bool)($data['ok'] ?? false), 'reply' => (string)$data['reply']];
|
|
if (isset($data['state']) && is_array($data['state'])) {
|
|
$out['state'] = $data['state']; // the "state" read: the tiles' JSON, passed through as-is
|
|
}
|
|
return new JSONResponse($out);
|
|
} catch (\Throwable $e) {
|
|
return new JSONResponse(['ok' => false, 'reply' => 'the door did not answer: ' . $e->getMessage()], Http::STATUS_BAD_GATEWAY);
|
|
}
|
|
}
|
|
}
|