diff --git a/app/Console/Commands/GenerateDefaultImagesCommand.php b/app/Console/Commands/GenerateDefaultImagesCommand.php
new file mode 100644
index 0000000..c326dd6
--- /dev/null
+++ b/app/Console/Commands/GenerateDefaultImagesCommand.php
@@ -0,0 +1,197 @@
+info('Starting generation of default images for all device models...');
+
+ $deviceModels = DeviceModel::all();
+
+ if ($deviceModels->isEmpty()) {
+ $this->warn('No device models found in the database.');
+
+ return self::SUCCESS;
+ }
+
+ $this->info("Found {$deviceModels->count()} device models to process.");
+
+ // Create the target directory
+ $targetDir = 'images/default-screens';
+ if (! Storage::disk('public')->exists($targetDir)) {
+ Storage::disk('public')->makeDirectory($targetDir);
+ $this->info("Created directory: {$targetDir}");
+ }
+
+ $successCount = 0;
+ $skipCount = 0;
+ $errorCount = 0;
+
+ foreach ($deviceModels as $deviceModel) {
+ $this->info("Processing device model: {$deviceModel->label} (ID: {$deviceModel->id})");
+
+ try {
+ // Process setup-logo
+ $setupResult = $this->transformImage('setup-logo', $deviceModel, $targetDir);
+ if ($setupResult) {
+ ++$successCount;
+ } else {
+ ++$skipCount;
+ }
+
+ // Process sleep
+ $sleepResult = $this->transformImage('sleep', $deviceModel, $targetDir);
+ if ($sleepResult) {
+ ++$successCount;
+ } else {
+ ++$skipCount;
+ }
+
+ } catch (Exception $e) {
+ $this->error("Error processing device model {$deviceModel->label}: ".$e->getMessage());
+ ++$errorCount;
+ }
+ }
+
+ $this->info("\nGeneration completed!");
+ $this->info("Successfully processed: {$successCount} images");
+ $this->info("Skipped (already exist): {$skipCount} images");
+ $this->info("Errors: {$errorCount} images");
+
+ return self::SUCCESS;
+ }
+
+ /**
+ * Transform a single image for a device model using Blade templates
+ */
+ private function transformImage(string $imageType, DeviceModel $deviceModel, string $targetDir): bool
+ {
+ // Generate filename: {width}_{height}_{bit_depth}_{rotation}.{extension}
+ $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png';
+ $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}";
+ $targetPath = "{$targetDir}/{$imageType}_{$filename}";
+
+ // Check if target already exists and force is not set
+ if (Storage::disk('public')->exists($targetPath) && ! $this->option('force')) {
+ $this->line(" Skipping {$imageType} - already exists: {$filename}");
+
+ return false;
+ }
+
+ try {
+ // Create custom Browsershot instance if using AWS Lambda
+ $browsershotInstance = null;
+ if (config('app.puppeteer_mode') === 'sidecar-aws') {
+ $browsershotInstance = new BrowsershotLambda();
+ }
+
+ // Generate HTML from Blade template
+ $html = $this->generateHtmlFromTemplate($imageType, $deviceModel);
+ // dump($html);
+
+ $browserStage = new BrowserStage($browsershotInstance);
+ $browserStage->html($html);
+ $browserStage
+ ->width($deviceModel->width)
+ ->height($deviceModel->height);
+
+ $browserStage->setBrowsershotOption('waitUntil', 'networkidle0');
+
+ if (config('app.puppeteer_docker')) {
+ $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']);
+ }
+
+ $outputPath = Storage::disk('public')->path($targetPath);
+
+ $imageStage = new ImageStage();
+ $imageStage->format($extension)
+ ->width($deviceModel->width)
+ ->height($deviceModel->height)
+ ->colors($deviceModel->colors)
+ ->bitDepth($deviceModel->bit_depth)
+ ->rotation($deviceModel->rotation)
+ // ->offsetX($deviceModel->offset_x)
+ // ->offsetY($deviceModel->offset_y)
+ ->outputPath($outputPath);
+
+ (new TrmnlPipeline())->pipe($browserStage)
+ ->pipe($imageStage)
+ ->process();
+
+ if (! file_exists($outputPath)) {
+ throw new RuntimeException('Image file was not created: '.$outputPath);
+ }
+
+ if (filesize($outputPath) === 0) {
+ throw new RuntimeException('Image file is empty: '.$outputPath);
+ }
+
+ $this->line(" ✓ Generated {$imageType}: {$filename}");
+
+ return true;
+
+ } catch (Exception $e) {
+ $this->error(" ✗ Failed to generate {$imageType} for {$deviceModel->label}: ".$e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Generate HTML from Blade template for the given image type and device model
+ */
+ private function generateHtmlFromTemplate(string $imageType, DeviceModel $deviceModel): string
+ {
+ // Map image type to template name
+ $templateName = match ($imageType) {
+ 'setup-logo' => 'default-screens.setup',
+ 'sleep' => 'default-screens.sleep',
+ default => throw new InvalidArgumentException("Invalid image type: {$imageType}")
+ };
+
+ // Determine device properties from DeviceModel
+ $deviceVariant = $deviceModel->name ?? 'og';
+ $colorDepth = $deviceModel->color_depth ?? '1bit'; // Use the accessor method
+ $scaleLevel = $deviceModel->scale_level; // Use the accessor method
+ $darkMode = $imageType === 'sleep'; // Sleep mode uses dark mode, setup uses light mode
+
+ // Render the Blade template
+ return view($templateName, [
+ 'noBleed' => false,
+ 'darkMode' => $darkMode,
+ 'deviceVariant' => $deviceVariant,
+ 'colorDepth' => $colorDepth,
+ 'scaleLevel' => $scaleLevel,
+ ])->render();
+ }
+}
diff --git a/app/Models/DeviceModel.php b/app/Models/DeviceModel.php
index 0d3757b..4dfaf1e 100644
--- a/app/Models/DeviceModel.php
+++ b/app/Models/DeviceModel.php
@@ -31,6 +31,10 @@ final class DeviceModel extends Model
return null;
}
+ if ($this->bit_depth === 3) {
+ return '2bit';
+ }
+
// if higher then 4 return 4bit
if ($this->bit_depth > 4) {
return '4bit';
diff --git a/app/Services/ImageGenerationService.php b/app/Services/ImageGenerationService.php
index 36597d7..f513e05 100644
--- a/app/Services/ImageGenerationService.php
+++ b/app/Services/ImageGenerationService.php
@@ -12,6 +12,7 @@ use Bnussbau\TrmnlPipeline\TrmnlPipeline;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
+use InvalidArgumentException;
use Ramsey\Uuid\Uuid;
use RuntimeException;
use Wnx\SidecarBrowsershot\BrowsershotLambda;
@@ -255,4 +256,151 @@ class ImageGenerationService
}
}
}
+
+ /**
+ * Get device-specific default image path for setup or sleep mode
+ */
+ public static function getDeviceSpecificDefaultImage(Device $device, string $imageType): ?string
+ {
+ // Validate image type
+ if (! in_array($imageType, ['setup-logo', 'sleep'])) {
+ return null;
+ }
+
+ // If device has a DeviceModel, try to find device-specific image
+ if ($device->deviceModel) {
+ $model = $device->deviceModel;
+ $extension = $model->mime_type === 'image/bmp' ? 'bmp' : 'png';
+ $filename = "{$model->width}_{$model->height}_{$model->bit_depth}_{$model->rotation}.{$extension}";
+ $deviceSpecificPath = "images/default-screens/{$imageType}_{$filename}";
+
+ if (Storage::disk('public')->exists($deviceSpecificPath)) {
+ return $deviceSpecificPath;
+ }
+ }
+
+ // Fallback to original hardcoded images
+ $fallbackPath = "images/{$imageType}.bmp";
+ if (Storage::disk('public')->exists($fallbackPath)) {
+ return $fallbackPath;
+ }
+
+ // Try PNG fallback
+ $fallbackPathPng = "images/{$imageType}.png";
+ if (Storage::disk('public')->exists($fallbackPathPng)) {
+ return $fallbackPathPng;
+ }
+
+ return null;
+ }
+
+ /**
+ * Generate a default screen image from Blade template
+ */
+ public static function generateDefaultScreenImage(Device $device, string $imageType): string
+ {
+ // Validate image type
+ if (! in_array($imageType, ['setup-logo', 'sleep'])) {
+ throw new InvalidArgumentException("Invalid image type: {$imageType}");
+ }
+
+ $uuid = Uuid::uuid4()->toString();
+
+ try {
+ // Get image generation settings from DeviceModel if available, otherwise use device settings
+ $imageSettings = self::getImageSettings($device);
+
+ $fileExtension = $imageSettings['mime_type'] === 'image/bmp' ? 'bmp' : 'png';
+ $outputPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.'.$fileExtension);
+
+ // Generate HTML from Blade template
+ $html = self::generateDefaultScreenHtml($device, $imageType);
+
+ // Create custom Browsershot instance if using AWS Lambda
+ $browsershotInstance = null;
+ if (config('app.puppeteer_mode') === 'sidecar-aws') {
+ $browsershotInstance = new BrowsershotLambda();
+ }
+
+ $browserStage = new BrowserStage($browsershotInstance);
+ $browserStage->html($html);
+
+ if (config('app.puppeteer_window_size_strategy') === 'v2') {
+ $browserStage
+ ->width($imageSettings['width'])
+ ->height($imageSettings['height']);
+ } else {
+ $browserStage->useDefaultDimensions();
+ }
+
+ if (config('app.puppeteer_wait_for_network_idle')) {
+ $browserStage->setBrowsershotOption('waitUntil', 'networkidle0');
+ }
+
+ if (config('app.puppeteer_docker')) {
+ $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']);
+ }
+
+ $imageStage = new ImageStage();
+ $imageStage->format($fileExtension)
+ ->width($imageSettings['width'])
+ ->height($imageSettings['height'])
+ ->colors($imageSettings['colors'])
+ ->bitDepth($imageSettings['bit_depth'])
+ ->rotation($imageSettings['rotation'])
+ ->offsetX($imageSettings['offset_x'])
+ ->offsetY($imageSettings['offset_y'])
+ ->outputPath($outputPath);
+
+ (new TrmnlPipeline())->pipe($browserStage)
+ ->pipe($imageStage)
+ ->process();
+
+ if (! file_exists($outputPath)) {
+ throw new RuntimeException('Image file was not created: '.$outputPath);
+ }
+
+ if (filesize($outputPath) === 0) {
+ throw new RuntimeException('Image file is empty: '.$outputPath);
+ }
+
+ Log::info("Device $device->id: generated default screen image: $uuid for type: $imageType");
+
+ return $uuid;
+
+ } catch (Exception $e) {
+ Log::error('Failed to generate default screen image: '.$e->getMessage());
+ throw new RuntimeException('Failed to generate default screen image: '.$e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * Generate HTML from Blade template for default screens
+ */
+ private static function generateDefaultScreenHtml(Device $device, string $imageType): string
+ {
+ // Map image type to template name
+ $templateName = match ($imageType) {
+ 'setup-logo' => 'default-screens.setup',
+ 'sleep' => 'default-screens.sleep',
+ default => throw new InvalidArgumentException("Invalid image type: {$imageType}")
+ };
+
+ // Determine device properties from DeviceModel or device settings
+ $deviceVariant = $device->deviceVariant();
+ $deviceOrientation = $device->rotate > 0 ? 'portrait' : 'landscape';
+ $colorDepth = $device->colorDepth() ?? '1bit';
+ $scaleLevel = $device->scaleLevel();
+ $darkMode = $imageType === 'sleep'; // Sleep mode uses dark mode, setup uses light mode
+
+ // Render the Blade template
+ return view($templateName, [
+ 'noBleed' => false,
+ 'darkMode' => $darkMode,
+ 'deviceVariant' => $deviceVariant,
+ 'deviceOrientation' => $deviceOrientation,
+ 'colorDepth' => $colorDepth,
+ 'scaleLevel' => $scaleLevel,
+ ])->render();
+ }
}
diff --git a/resources/views/default-screens/setup.blade.php b/resources/views/default-screens/setup.blade.php
new file mode 100644
index 0000000..3b0ff05
--- /dev/null
+++ b/resources/views/default-screens/setup.blade.php
@@ -0,0 +1,22 @@
+@props([
+ 'noBleed' => false,
+ 'darkMode' => false,
+ 'deviceVariant' => 'og',
+ 'deviceOrientation' => null,
+ 'colorDepth' => '1bit',
+ 'scaleLevel' => null,
+])
+
+
+
+
+
+ Welcome to BYOS Laravel!
+ Your device is connected.
+
+
+
+
+
diff --git a/resources/views/default-screens/sleep.blade.php b/resources/views/default-screens/sleep.blade.php
new file mode 100644
index 0000000..89d6baa
--- /dev/null
+++ b/resources/views/default-screens/sleep.blade.php
@@ -0,0 +1,28 @@
+@props([
+ 'noBleed' => false,
+ 'darkMode' => true,
+ 'deviceVariant' => 'og',
+ 'deviceOrientation' => null,
+ 'colorDepth' => '1bit',
+ 'scaleLevel' => null,
+])
+
+
+
+
+
+
+ Sleep Mode
+
+
+
+
+
diff --git a/routes/api.php b/routes/api.php
index 8adc404..9721a0f 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -60,12 +60,22 @@ Route::get('/display', function (Request $request) {
}
if ($device->isPauseActive()) {
- $image_path = 'images/sleep.png';
- $filename = 'sleep.png';
+ $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+ if (! $image_path) {
+ // Generate from template if no device-specific image exists
+ $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep');
+ $image_path = 'images/generated/'.$image_uuid.'.png';
+ }
+ $filename = basename($image_path);
$refreshTimeOverride = (int) now()->diffInSeconds($device->pause_until);
} elseif ($device->isSleepModeActive()) {
- $image_path = 'images/sleep.png';
- $filename = 'sleep.png';
+ $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+ if (! $image_path) {
+ // Generate from template if no device-specific image exists
+ $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep');
+ $image_path = 'images/generated/'.$image_uuid.'.png';
+ }
+ $filename = basename($image_path);
$refreshTimeOverride = $device->getSleepModeEndsInSeconds() ?? $device->default_refresh_interval;
} else {
// Get current screen image from a mirror device or continue if not available
@@ -125,8 +135,13 @@ Route::get('/display', function (Request $request) {
$image_uuid = $device->current_screen_image;
}
if (! $image_uuid) {
- $image_path = 'images/setup-logo.bmp';
- $filename = 'setup-logo.bmp';
+ $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ if (! $image_path) {
+ // Generate from template if no device-specific image exists
+ $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo');
+ $image_path = 'images/generated/'.$image_uuid.'.png';
+ }
+ $filename = basename($image_path);
} else {
// Determine image format based on device settings
$preferred_format = 'png'; // Default to PNG for newer firmware
@@ -225,7 +240,7 @@ Route::get('/setup', function (Request $request) {
'status' => 200,
'api_key' => $device->api_key,
'friendly_id' => $device->friendly_id,
- 'image_url' => url('storage/images/setup-logo.png'),
+ 'image_url' => url('storage/'.ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo')),
'message' => 'Welcome to TRMNL BYOS',
]);
});
@@ -444,8 +459,13 @@ Route::get('/current_screen', function (Request $request) {
$image_uuid = $device->current_screen_image;
if (! $image_uuid) {
- $image_path = 'images/setup-logo.bmp';
- $filename = 'setup-logo.bmp';
+ $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ if (! $image_path) {
+ // Generate from template if no device-specific image exists
+ $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo');
+ $image_path = 'images/generated/'.$image_uuid.'.png';
+ }
+ $filename = basename($image_path);
} else {
// Determine image format based on device settings
$preferred_format = 'png'; // Default to PNG for newer firmware
diff --git a/storage/app/.gitignore b/storage/app/.gitignore
deleted file mode 100644
index fedb287..0000000
--- a/storage/app/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-*
-!private/
-!public/
-!.gitignore
diff --git a/storage/app/public/.gitignore b/storage/app/public/firmwares/.gitignore
similarity index 60%
rename from storage/app/public/.gitignore
rename to storage/app/public/firmwares/.gitignore
index 19a4b22..d6b7ef3 100644
--- a/storage/app/public/.gitignore
+++ b/storage/app/public/firmwares/.gitignore
@@ -1,3 +1,2 @@
*
-!images/
!.gitignore
diff --git a/storage/app/public/images/default-screens/.gitkeep b/storage/app/public/images/default-screens/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png b/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png
new file mode 100644
index 0000000..3734da1
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png b/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png
new file mode 100644
index 0000000..17dcf60
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png b/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png
new file mode 100644
index 0000000..71ecd65
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png b/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png
new file mode 100644
index 0000000..a350061
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png b/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png
new file mode 100644
index 0000000..ec22fc8
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png b/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png
new file mode 100644
index 0000000..f080990
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png b/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png
new file mode 100644
index 0000000..c3099d5
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png b/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png
new file mode 100644
index 0000000..5894ab8
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png b/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png
new file mode 100644
index 0000000..1b0d150
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png b/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png
new file mode 100644
index 0000000..031e369
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png differ
diff --git a/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png b/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png
new file mode 100644
index 0000000..61061e7
Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1024_768_8_90.png b/storage/app/public/images/default-screens/sleep_1024_768_8_90.png
new file mode 100644
index 0000000..f8763f3
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1024_768_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1200_820_3_0.png b/storage/app/public/images/default-screens/sleep_1200_820_3_0.png
new file mode 100644
index 0000000..287ec0a
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1200_820_3_0.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1400_840_8_90.png b/storage/app/public/images/default-screens/sleep_1400_840_8_90.png
new file mode 100644
index 0000000..2f2166e
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1400_840_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png b/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png
new file mode 100644
index 0000000..5846cad
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png b/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png
new file mode 100644
index 0000000..a08c008
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png b/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png
new file mode 100644
index 0000000..060da4e
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png b/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png
new file mode 100644
index 0000000..6509a44
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png b/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png
new file mode 100644
index 0000000..792d964
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png differ
diff --git a/storage/app/public/images/default-screens/sleep_800_600_8_90.png b/storage/app/public/images/default-screens/sleep_800_600_8_90.png
new file mode 100644
index 0000000..f066974
Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_800_600_8_90.png differ
diff --git a/storage/app/public/images/setup-logo.png b/storage/app/public/images/setup-logo.png
new file mode 100644
index 0000000..1fc342c
Binary files /dev/null and b/storage/app/public/images/setup-logo.png differ
diff --git a/storage/app/public/images/sleep.bmp b/storage/app/public/images/sleep.bmp
new file mode 100644
index 0000000..a55af63
Binary files /dev/null and b/storage/app/public/images/sleep.bmp differ
diff --git a/storage/app/public/images/sleep.png b/storage/app/public/images/sleep.png
new file mode 100644
index 0000000..49bdabf
Binary files /dev/null and b/storage/app/public/images/sleep.png differ
diff --git a/tests/Feature/Api/DeviceEndpointsTest.php b/tests/Feature/Api/DeviceEndpointsTest.php
index 005e73e..726f313 100644
--- a/tests/Feature/Api/DeviceEndpointsTest.php
+++ b/tests/Feature/Api/DeviceEndpointsTest.php
@@ -812,10 +812,10 @@ test('device in sleep mode returns sleep image and correct refresh rate', functi
'fw-version' => '1.0.0',
])->get('/api/display');
- $response->assertOk()
- ->assertJson([
- 'filename' => 'sleep.png',
- ]);
+ $response->assertOk();
+
+ // The filename should be a UUID-based PNG file since we're generating from template
+ expect($response['filename'])->toMatch('/^[a-f0-9-]+\.png$/');
expect($response['refresh_rate'])->toBeGreaterThan(0);
Carbon\Carbon::setTestNow(); // Clear test time
@@ -867,8 +867,10 @@ test('device returns sleep.png and correct refresh time when paused', function (
$response->assertOk();
$json = $response->json();
- expect($json['filename'])->toBe('sleep.png');
- expect($json['image_url'])->toContain('sleep.png');
+
+ // The filename should be a UUID-based PNG file since we're generating from template
+ expect($json['filename'])->toMatch('/^[a-f0-9-]+\.png$/');
+ expect($json['image_url'])->toContain('images/generated/');
expect($json['refresh_rate'])->toBeLessThanOrEqual(3600); // ~60 min
});
diff --git a/tests/Feature/GenerateDefaultImagesTest.php b/tests/Feature/GenerateDefaultImagesTest.php
new file mode 100644
index 0000000..6c084c9
--- /dev/null
+++ b/tests/Feature/GenerateDefaultImagesTest.php
@@ -0,0 +1,89 @@
+not->toBeEmpty();
+
+ // Run the command
+ $this->artisan('images:generate-defaults')
+ ->assertExitCode(0);
+
+ // Check that the default-screens directory was created
+ expect(Storage::disk('public')->exists('images/default-screens'))->toBeTrue();
+
+ // Check that images were generated for each device model
+ foreach ($deviceModels as $deviceModel) {
+ $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png';
+ $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}";
+
+ $setupPath = "images/default-screens/setup-logo_{$filename}";
+ $sleepPath = "images/default-screens/sleep_{$filename}";
+
+ expect(Storage::disk('public')->exists($setupPath))->toBeTrue();
+ expect(Storage::disk('public')->exists($sleepPath))->toBeTrue();
+ }
+});
+
+test('getDeviceSpecificDefaultImage returns correct path for device with model', function () {
+ $deviceModel = DeviceModel::first();
+ expect($deviceModel)->not->toBeNull();
+
+ $device = new Device();
+ $device->deviceModel = $deviceModel;
+
+ $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+
+ expect($setupImage)->toContain('images/default-screens/setup-logo_');
+ expect($sleepImage)->toContain('images/default-screens/sleep_');
+});
+
+test('getDeviceSpecificDefaultImage falls back to original images for device without model', function () {
+ $device = new Device();
+ $device->deviceModel = null;
+
+ $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+
+ expect($setupImage)->toBe('images/setup-logo.bmp');
+ expect($sleepImage)->toBe('images/sleep.bmp');
+});
+
+test('generateDefaultScreenImage creates images from Blade templates', function () {
+ $device = Device::factory()->create();
+
+ $setupUuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo');
+ $sleepUuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep');
+
+ expect($setupUuid)->not->toBeEmpty();
+ expect($sleepUuid)->not->toBeEmpty();
+ expect($setupUuid)->not->toBe($sleepUuid);
+
+ // Check that the generated images exist
+ $setupPath = "images/generated/{$setupUuid}.png";
+ $sleepPath = "images/generated/{$sleepUuid}.png";
+
+ expect(Storage::disk('public')->exists($setupPath))->toBeTrue();
+ expect(Storage::disk('public')->exists($sleepPath))->toBeTrue();
+});
+
+test('generateDefaultScreenImage throws exception for invalid image type', function () {
+ $device = Device::factory()->create();
+
+ expect(fn () => ImageGenerationService::generateDefaultScreenImage($device, 'invalid-type'))
+ ->toThrow(InvalidArgumentException::class);
+});
+
+test('getDeviceSpecificDefaultImage returns null for invalid image type', function () {
+ $device = new Device();
+ $device->deviceModel = DeviceModel::first();
+
+ $result = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'invalid-type');
+ expect($result)->toBeNull();
+});
diff --git a/tests/Feature/TransformDefaultImagesTest.php b/tests/Feature/TransformDefaultImagesTest.php
new file mode 100644
index 0000000..041c708
--- /dev/null
+++ b/tests/Feature/TransformDefaultImagesTest.php
@@ -0,0 +1,89 @@
+not->toBeEmpty();
+
+ // Run the command
+ $this->artisan('images:generate-defaults')
+ ->assertExitCode(0);
+
+ // Check that the default-screens directory was created
+ expect(Storage::disk('public')->exists('images/default-screens'))->toBeTrue();
+
+ // Check that images were generated for each device model
+ foreach ($deviceModels as $deviceModel) {
+ $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png';
+ $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}";
+
+ $setupPath = "images/default-screens/setup-logo_{$filename}";
+ $sleepPath = "images/default-screens/sleep_{$filename}";
+
+ expect(Storage::disk('public')->exists($setupPath))->toBeTrue();
+ expect(Storage::disk('public')->exists($sleepPath))->toBeTrue();
+ }
+});
+
+test('getDeviceSpecificDefaultImage returns correct path for device with model', function () {
+ $deviceModel = DeviceModel::first();
+ expect($deviceModel)->not->toBeNull();
+
+ $device = new Device();
+ $device->deviceModel = $deviceModel;
+
+ $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+
+ expect($setupImage)->toContain('images/default-screens/setup-logo_');
+ expect($sleepImage)->toContain('images/default-screens/sleep_');
+});
+
+test('getDeviceSpecificDefaultImage falls back to original images for device without model', function () {
+ $device = new Device();
+ $device->deviceModel = null;
+
+ $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo');
+ $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep');
+
+ expect($setupImage)->toBe('images/setup-logo.bmp');
+ expect($sleepImage)->toBe('images/sleep.bmp');
+});
+
+test('generateDefaultScreenImage creates images from Blade templates', function () {
+ $device = Device::factory()->create();
+
+ $setupUuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo');
+ $sleepUuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep');
+
+ expect($setupUuid)->not->toBeEmpty();
+ expect($sleepUuid)->not->toBeEmpty();
+ expect($setupUuid)->not->toBe($sleepUuid);
+
+ // Check that the generated images exist
+ $setupPath = "images/generated/{$setupUuid}.png";
+ $sleepPath = "images/generated/{$sleepUuid}.png";
+
+ expect(Storage::disk('public')->exists($setupPath))->toBeTrue();
+ expect(Storage::disk('public')->exists($sleepPath))->toBeTrue();
+})->skipOnCI();
+
+test('generateDefaultScreenImage throws exception for invalid image type', function () {
+ $device = Device::factory()->create();
+
+ expect(fn () => ImageGenerationService::generateDefaultScreenImage($device, 'invalid-type'))
+ ->toThrow(InvalidArgumentException::class);
+});
+
+test('getDeviceSpecificDefaultImage returns null for invalid image type', function () {
+ $device = new Device();
+ $device->deviceModel = DeviceModel::first();
+
+ $result = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'invalid-type');
+ expect($result)->toBeNull();
+});