298 lines
13 KiB
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;
|
|
}
|