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(), ]); }