Files
controls-web/data/tag-control-update.php
2026-02-17 12:44:37 -06:00

270 lines
8.4 KiB
PHP

<?php // phpcs:ignoreFile
require __DIR__ . '/../session.php';
if (($_SESSION['SESS_MEMBER_LEVEL'] ?? '') !== 'controls') {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Access denied.',
]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
header('Allow: POST');
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Only POST requests are supported.',
]);
exit;
}
$sessionToken = $_SESSION['control_settings_csrf'] ?? '';
$headerToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if ($sessionToken === '' || $headerToken === '' || !hash_equals($sessionToken, $headerToken)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Security token mismatch.',
]);
exit;
}
require __DIR__ . '/../includes/control-tags.php';
require __DIR__ . '/../includes/kepware-rest.php';
require __DIR__ . '/../includes/control-logger.php';
header('Content-Type: application/json');
$payload = json_decode((string) file_get_contents('php://input'), true);
if (!is_array($payload)) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Invalid request payload.',
]);
exit;
}
$idRaw = $payload['id'] ?? '';
$id = is_string($idRaw) ? trim($idRaw) : (is_numeric($idRaw) ? (string) $idRaw : '');
$hasDelta = array_key_exists('delta', $payload);
$hasValue = array_key_exists('value', $payload);
if ($id === '') {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'A valid tag id is required.',
]);
exit;
}
$tag = control_tag_find($id);
if ($tag === null) {
http_response_code(404);
echo json_encode([
'success' => false,
'message' => 'Tag not configured for overrides.',
]);
exit;
}
$mode = $tag['control_mode'];
if (!$hasDelta && !$hasValue) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Provide a delta or an explicit value to apply.',
]);
exit;
}
if ($mode === 'boolean' && $hasDelta) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Boolean tags accept explicit values only.',
]);
exit;
}
$delta = null;
if ($hasDelta) {
$delta = filter_var($payload['delta'], FILTER_VALIDATE_FLOAT);
if ($delta === false || $delta === null) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Delta must be numeric.',
]);
exit;
}
}
$valueInput = null;
if ($hasValue) {
$valueInput = $payload['value'];
}
try {
$currentRead = kepware_read([$tag['id']]);
$currentData = $currentRead[$tag['id']] ?? null;
$currentValue = null;
if ($currentData !== null && isset($currentData['value']) && is_numeric($currentData['value'])) {
$currentValue = (float) $currentData['value'];
}
$targetValue = null;
if ($mode === 'boolean') {
if (!$hasValue) {
throw new RuntimeException('Boolean tag updates must include a value of 0 or 1.');
}
$normalizedValue = null;
if (is_bool($valueInput)) {
$normalizedValue = $valueInput ? 1 : 0;
} elseif (is_numeric($valueInput)) {
$normalizedValue = (float) $valueInput >= 0.5 ? 1 : 0;
} elseif (is_string($valueInput)) {
$lower = strtolower(trim($valueInput));
if (in_array($lower, ['1', 'true', 'on', 'start'], true)) {
$normalizedValue = 1;
} elseif (in_array($lower, ['0', 'false', 'off', 'stop'], true)) {
$normalizedValue = 0;
}
}
if ($normalizedValue === null) {
throw new RuntimeException('Boolean value must be 0 or 1.');
}
$targetValue = $normalizedValue;
} else {
if ($hasValue) {
$filteredValue = filter_var($valueInput, FILTER_VALIDATE_FLOAT);
if ($filteredValue === false || $filteredValue === null) {
throw new RuntimeException('Value must be numeric.');
}
$targetValue = (float) $filteredValue;
} elseif ($delta !== null) {
if ($currentValue === null) {
throw new RuntimeException('Unable to apply a delta without a current value. Refresh and try again.');
}
$targetValue = $currentValue + $delta;
}
if ($targetValue === null) {
throw new RuntimeException('No numeric value provided.');
}
if (isset($tag['min']) && $tag['min'] !== null) {
$targetValue = max($targetValue, (float) $tag['min']);
}
if (isset($tag['max']) && $tag['max'] !== null) {
$targetValue = min($targetValue, (float) $tag['max']);
}
if (isset($tag['precision']) && $tag['precision'] !== null && $tag['precision'] >= 0) {
$targetValue = round($targetValue, (int) $tag['precision']);
}
}
$writePayload = [
[
'id' => $tag['id'],
'v' => $mode === 'boolean' ? (int) $targetValue : $targetValue,
],
];
$writeResults = kepware_write($writePayload);
$writeResult = $writeResults[0] ?? null;
$writeStatus = is_array($writeResult) && isset($writeResult['s']) ? $writeResult['s'] : null;
$writeError = is_array($writeResult) && isset($writeResult['error']) ? $writeResult['error'] : null;
if ($writeError !== null) {
throw new RuntimeException('Kepware write error: ' . $writeError);
}
if (is_string($writeStatus) && stripos($writeStatus, 'good') === false && stripos($writeStatus, 'ok') === false) {
throw new RuntimeException('Kepware write returned status: ' . $writeStatus);
}
$postRead = kepware_read([$tag['id']]);
$postData = $postRead[$tag['id']] ?? null;
$finalValue = $postData['value'] ?? $targetValue;
$finalNumericValue = is_numeric($finalValue) ? (float) $finalValue : null;
$finalTimestamp = isset($postData['timestamp']) && is_string($postData['timestamp']) ? $postData['timestamp'] : null;
$responseValue = $finalNumericValue ?? $finalValue;
$userNameParts = array_filter([
$_SESSION['SESS_FIRST_NAME'] ?? null,
$_SESSION['SESS_LAST_NAME'] ?? null,
]);
$userName = $userNameParts !== [] ? trim(implode(' ', $userNameParts)) : ($_SESSION['SESS_MEMBER_ID'] ?? 'controls-user');
$logEntry = [
'tag_id' => $tag['id'],
'tag_name' => $tag['name'],
'mode' => $mode,
'previous_value' => $currentValue,
'new_value' => $responseValue,
'requested_value' => $targetValue,
'delta' => $mode === 'boolean'
? (($targetValue ?? 0) - ($currentValue ?? 0))
: (($currentValue !== null && is_numeric($targetValue)) ? ($targetValue - $currentValue) : null),
'status' => $writeStatus ?? 'OK',
'message' => $writeError ?? ($writeStatus ?? 'OK'),
'username' => $userName,
'session_id' => session_id(),
'client_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
];
$logResult = control_log_write($logEntry);
if (!$logResult['success']) {
$logMessage = $logResult['error'] ?? 'Unknown logging failure.';
error_log('Control write log persistence failed: ' . $logMessage);
}
echo json_encode([
'success' => true,
'data' => [
'id' => $tag['id'],
'name' => $tag['name'],
'value' => $responseValue,
'control' => 1,
'type' => $tag['type_label'],
'control_mode' => $mode,
'source_idnumber' => $tag['config']['source'] ?? $tag['id'],
'timestamp' => $finalTimestamp,
'displayTimestamp' => control_format_timestamp($finalTimestamp),
'status' => $writeStatus ?? null,
'units' => $tag['units'] ?? null,
'description' => $tag['description'] ?? null,
],
'log' => $logResult,
]);
} catch (Throwable $exception) {
error_log('Tag control update failed: ' . $exception->getMessage());
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Failed to update tag value.',
'details' => $exception->getMessage(),
]);
}