TKK_E32230273/api_sync.php

298 lines
13 KiB
PHP

<?php
/**
* API Sync for TFT Design Editor
* Handles save, load, list, delete, latest, and export_cpp actions
*/
require_once 'koneksi.php';
if (session_status() === PHP_SESSION_NONE) session_start();
header('Content-Type: application/json');
$action = $_GET['action'] ?? $_POST['action'] ?? '';
switch ($action) {
// ========== SAVE / UPDATE DESIGN ==========
case 'save':
$id = intval($_POST['id'] ?? 0);
$name = trim($_POST['name'] ?? 'Untitled');
$json = $_POST['design_json'] ?? '';
if (empty($json)) {
echo json_encode(['status' => 'error', 'message' => 'design_json is required']);
exit;
}
$hash = md5($json);
if ($id > 0) {
// Update existing
$stmt = $pdo->prepare("UPDATE tft_designs SET name = ?, design_json = ?, version_hash = ? WHERE id = ?");
$stmt->execute([$name, $json, $hash, $id]);
echo json_encode(['status' => 'success', 'message' => 'Design updated', 'id' => $id, 'version_hash' => $hash]);
} else {
// Insert new
$stmt = $pdo->prepare("INSERT INTO tft_designs (name, design_json, version_hash) VALUES (?, ?, ?)");
$stmt->execute([$name, $json, $hash]);
$newId = $pdo->lastInsertId();
echo json_encode(['status' => 'success', 'message' => 'Design saved', 'id' => intval($newId), 'version_hash' => $hash]);
}
break;
// ========== LOAD DESIGN BY ID ==========
case 'load':
$id = intval($_GET['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'Invalid ID']);
exit;
}
$stmt = $pdo->prepare("SELECT * FROM tft_designs WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if ($row) {
echo json_encode(['status' => 'success', 'data' => $row]);
} else {
echo json_encode(['status' => 'error', 'message' => 'Design not found']);
}
break;
// ========== LIST ALL DESIGNS ==========
case 'list':
$stmt = $pdo->query("SELECT id, name, version_hash, created_at, updated_at FROM tft_designs ORDER BY updated_at DESC");
$rows = $stmt->fetchAll();
echo json_encode(['status' => 'success', 'data' => $rows]);
break;
// ========== DELETE DESIGN ==========
case 'delete':
$id = intval($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'Invalid ID']);
exit;
}
$stmt = $pdo->prepare("DELETE FROM tft_designs WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['status' => 'success', 'message' => 'Design deleted']);
break;
// ========== LATEST DESIGN (for ESP32 polling) ==========
case 'latest':
$stmt = $pdo->query("SELECT id, name, design_json, version_hash, updated_at FROM tft_designs ORDER BY updated_at DESC LIMIT 1");
$row = $stmt->fetch();
if ($row) {
// Parse the design_json to get the active screen elements
$designData = json_decode($row['design_json'], true);
$activeScreen = $_GET['screen'] ?? '0';
$elements = [];
if (isset($designData['screens'][$activeScreen]['objects'])) {
$elements = $designData['screens'][$activeScreen]['objects'];
} elseif (isset($designData['screens']) && count($designData['screens']) > 0) {
$firstScreen = reset($designData['screens']);
$elements = $firstScreen['objects'] ?? [];
}
echo json_encode([
'status' => 'success',
'version_hash' => $row['version_hash'],
'screen_count' => isset($designData['screens']) ? count($designData['screens']) : 0,
'elements' => $elements
]);
} else {
echo json_encode(['status' => 'empty', 'message' => 'No designs found']);
}
break;
// ========== EXPORT C++ CODE (Component-Based) ==========
case 'export_cpp':
$json = $_POST['design_json'] ?? '';
if (empty($json)) {
echo json_encode(['status' => 'error', 'message' => 'design_json is required']);
exit;
}
$designData = json_decode($json, true);
if (!$designData || !isset($designData['screens'])) {
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON structure']);
exit;
}
$orientation = $designData['orientation'] ?? 'landscape';
$rotation = $orientation === 'landscape' ? 1 : 0;
$cpp = "// Auto-generated TFT UI Code (Component-Based)\n";
$cpp .= "// Generated: " . date('Y-m-d H:i:s') . "\n";
$cpp .= "// Library: TFT_eSPI\n\n";
$cpp .= "#include <TFT_eSPI.h>\n";
$cpp .= "TFT_eSPI tft = TFT_eSPI();\n\n";
$cpp .= "uint16_t hexToRGB565(uint32_t hex) {\n";
$cpp .= " uint8_t r = (hex >> 16) & 0xFF;\n";
$cpp .= " uint8_t g = (hex >> 8) & 0xFF;\n";
$cpp .= " uint8_t b = hex & 0xFF;\n";
$cpp .= " return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);\n";
$cpp .= "}\n\n";
// Recursive function to generate C++ for components
function generateCompCpp($comp, $offX = 0, $offY = 0) {
$code = '';
$type = $comp['type'] ?? '';
$x = intval($comp['x'] ?? 0) + $offX;
$y = intval($comp['y'] ?? 0) + $offY;
$w = intval($comp['w'] ?? 0);
$h = intval($comp['h'] ?? 0);
$bg = str_replace('#', '0x', $comp['bg'] ?? '#1e2223');
$r = intval($comp['radius'] ?? 0);
$border = intval($comp['border'] ?? 0);
$borderColor = str_replace('#', '0x', $comp['borderColor'] ?? '#444444');
$label = $comp['label'] ?? $type;
$code .= " // [{$label}] {$type}\n";
// Draw background (skip transparent)
if (($comp['bg'] ?? '') !== 'transparent' && ($comp['bg'] ?? '') !== '') {
if ($r > 0) {
$code .= " tft.fillRoundRect({$x}, {$y}, {$w}, {$h}, {$r}, hexToRGB565({$bg}));\n";
} else {
$code .= " tft.fillRect({$x}, {$y}, {$w}, {$h}, hexToRGB565({$bg}));\n";
}
}
// Draw border
if ($border > 0) {
if ($r > 0) {
$code .= " tft.drawRoundRect({$x}, {$y}, {$w}, {$h}, {$r}, hexToRGB565({$borderColor}));\n";
} else {
$code .= " tft.drawRect({$x}, {$y}, {$w}, {$h}, hexToRGB565({$borderColor}));\n";
}
}
// Type-specific rendering
switch ($type) {
case 'button':
case 'label':
case 'datavalue':
if (!empty($comp['text'])) {
$text = addcslashes($comp['text'], '"\\');
$txtColor = str_replace('#', '0x', $comp['textColor'] ?? '#FFFFFF');
$fontSize = intval($comp['fontSize'] ?? 12);
$tftSize = max(1, intval($fontSize / 8));
$align = $comp['textAlign'] ?? 'center';
$code .= " tft.setTextSize({$tftSize});\n";
$code .= " tft.setTextColor(hexToRGB565({$txtColor}));\n";
if ($align === 'center') {
$code .= " tft.setTextDatum(MC_DATUM);\n";
$tx = $x + intval($w / 2);
$ty = $y + intval($h / 2);
} elseif ($align === 'right') {
$code .= " tft.setTextDatum(MR_DATUM);\n";
$tx = $x + $w - 4;
$ty = $y + intval($h / 2);
} else {
$code .= " tft.setTextDatum(ML_DATUM);\n";
$tx = $x + 4;
$ty = $y + intval($h / 2);
}
$code .= " tft.drawString(\"{$text}\", {$tx}, {$ty});\n";
$code .= " tft.setTextDatum(TL_DATUM);\n";
}
break;
case 'icon':
if (!empty($comp['icon'])) {
$icon = addcslashes($comp['icon'], '"\\');
$txtColor = str_replace('#', '0x', $comp['textColor'] ?? '#FFD700');
$fontSize = intval($comp['fontSize'] ?? 18);
$tftSize = max(1, intval($fontSize / 8));
$code .= " tft.setTextSize({$tftSize});\n";
$code .= " tft.setTextColor(hexToRGB565({$txtColor}));\n";
$code .= " tft.setTextDatum(MC_DATUM);\n";
$code .= " tft.drawString(\"{$icon}\", " . ($x + intval($w/2)) . ", " . ($y + intval($h/2)) . ");\n";
$code .= " tft.setTextDatum(TL_DATUM);\n";
}
break;
case 'toggle':
$on = ($comp['toggleState'] ?? true) ? 'true' : 'false';
$knobR = intval($h / 2) - 2;
$knobX = ($comp['toggleState'] ?? true) ? ($x + $w - intval($h/2)) : ($x + intval($h/2));
$code .= " tft.fillCircle({$knobX}, " . ($y + intval($h/2)) . ", {$knobR}, hexToRGB565(0xFFFFFF));\n";
break;
case 'progressbar':
$pct = intval($comp['value'] ?? 60);
$pw = intval($w * $pct / 100);
$barColor = str_replace('#', '0x', $comp['barColor'] ?? '#4B49AC');
$code .= " tft.fillRoundRect({$x}, {$y}, {$pw}, {$h}, {$r}, hexToRGB565({$barColor}));\n";
break;
}
// Recursively render children
if (!empty($comp['children'])) {
foreach ($comp['children'] as $child) {
$code .= generateCompCpp($child, $x, $y);
}
}
return $code;
}
foreach ($designData['screens'] as $idx => $screen) {
$screenName = preg_replace('/[^a-zA-Z0-9_]/', '_', $screen['name'] ?? 'Screen_' . $idx);
$cpp .= "void draw_{$screenName}() {\n";
$cpp .= " tft.fillScreen(TFT_BLACK);\n\n";
if (isset($screen['components'])) {
foreach ($screen['components'] as $comp) {
$cpp .= generateCompCpp($comp);
$cpp .= "\n";
}
}
$cpp .= "}\n\n";
}
$cpp .= "void setup() {\n";
$cpp .= " tft.init();\n";
$cpp .= " tft.setRotation({$rotation});\n";
$cpp .= " draw_" . preg_replace('/[^a-zA-Z0-9_]/', '_', $designData['screens'][0]['name'] ?? 'Screen_0') . "();\n";
$cpp .= "}\n\n";
$cpp .= "void loop() {\n";
$cpp .= " // Touch or interaction handling here\n";
$cpp .= "}\n";
echo json_encode(['status' => 'success', 'code' => $cpp]);
break;
// ========== PUSH STATE FROM ESP32 ==========
case 'push':
$rawInput = file_get_contents('php://input');
$data = json_decode($rawInput, true);
if (!$data) {
// Try POST fallback
$data = json_decode($_POST['design_json'] ?? '', true);
}
if (!$data || !isset($data['screens'])) {
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON. Must contain screens array.']);
exit;
}
$name = $data['projectName'] ?? 'ESP32 Design';
$json = json_encode($data);
$hash = md5($json);
// Check if there is an existing design to update
$existing = $pdo->query("SELECT id FROM tft_designs ORDER BY updated_at DESC LIMIT 1")->fetch();
if ($existing) {
$stmt = $pdo->prepare("UPDATE tft_designs SET name = ?, design_json = ?, version_hash = ? WHERE id = ?");
$stmt->execute([$name, $json, $hash, $existing['id']]);
echo json_encode(['status' => 'success', 'message' => 'Design updated from ESP32', 'id' => $existing['id'], 'version_hash' => $hash]);
} else {
$stmt = $pdo->prepare("INSERT INTO tft_designs (name, design_json, version_hash) VALUES (?, ?, ?)");
$stmt->execute([$name, $json, $hash]);
$newId = $pdo->lastInsertId();
echo json_encode(['status' => 'success', 'message' => 'Design pushed from ESP32', 'id' => intval($newId), 'version_hash' => $hash]);
}
break;
default:
echo json_encode(['status' => 'error', 'message' => 'Unknown action: ' . $action]);
break;
}