'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 \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; }