fix(#71): device specific sleep and setup images
Some checks failed
tests / ci (push) Has been cancelled
197
app/Console/Commands/GenerateDefaultImagesCommand.php
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\DeviceModel;
|
||||
use Bnussbau\TrmnlPipeline\Stages\BrowserStage;
|
||||
use Bnussbau\TrmnlPipeline\Stages\ImageStage;
|
||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Wnx\SidecarBrowsershot\BrowsershotLambda;
|
||||
|
||||
class GenerateDefaultImagesCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'images:generate-defaults {--force : Force regeneration of existing images}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate default images (setup-logo and sleep) for all device models from Blade templates';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
resources/views/default-screens/setup.blade.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
@props([
|
||||
'noBleed' => false,
|
||||
'darkMode' => false,
|
||||
'deviceVariant' => 'og',
|
||||
'deviceOrientation' => null,
|
||||
'colorDepth' => '1bit',
|
||||
'scaleLevel' => null,
|
||||
])
|
||||
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}" no-bleed="{{$noBleed}}" dark-mode="{{$darkMode}}"
|
||||
device-variant="{{$deviceVariant}}" device-orientation="{{$deviceOrientation}}"
|
||||
scale-level="{{$scaleLevel}}">
|
||||
<x-trmnl::view>
|
||||
<x-trmnl::layout>
|
||||
<x-trmnl::richtext gapSize="large" align="center">
|
||||
<x-trmnl::title>Welcome to BYOS Laravel!</x-trmnl::title>
|
||||
<x-trmnl::content>Your device is connected.</x-trmnl::content>
|
||||
</x-trmnl::richtext>
|
||||
</x-trmnl::layout>
|
||||
<x-trmnl::title-bar title="byos_laravel"/>
|
||||
</x-trmnl::view>
|
||||
</x-trmnl::screen>
|
||||
28
resources/views/default-screens/sleep.blade.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@props([
|
||||
'noBleed' => false,
|
||||
'darkMode' => true,
|
||||
'deviceVariant' => 'og',
|
||||
'deviceOrientation' => null,
|
||||
'colorDepth' => '1bit',
|
||||
'scaleLevel' => null,
|
||||
])
|
||||
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}" no-bleed="{{$noBleed}}" dark-mode="{{$darkMode}}"
|
||||
device-variant="{{$deviceVariant}}" device-orientation="{{$deviceOrientation}}"
|
||||
scale-level="{{$scaleLevel}}">
|
||||
<x-trmnl::view>
|
||||
<x-trmnl::layout>
|
||||
<x-trmnl::richtext gapSize="large" align="center">
|
||||
<div class="image image-dither" alt="sleep">
|
||||
<svg class="w-64 h-64" fill="#000" xmlns="http://www.w3.org/2000/svg" id="mdi-sleep"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<x-trmnl::title>Sleep Mode</x-trmnl::title>
|
||||
</x-trmnl::richtext>
|
||||
</x-trmnl::layout>
|
||||
<x-trmnl::title-bar title="byos_laravel"/>
|
||||
</x-trmnl::view>
|
||||
</x-trmnl::screen>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
4
storage/app/.gitignore
vendored
|
|
@ -1,4 +0,0 @@
|
|||
*
|
||||
!private/
|
||||
!public/
|
||||
!.gitignore
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
*
|
||||
!images/
|
||||
!.gitignore
|
||||
0
storage/app/public/images/default-screens/.gitkeep
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 9 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
BIN
storage/app/public/images/default-screens/sleep_1200_820_3_0.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 963 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
storage/app/public/images/default-screens/sleep_800_600_8_90.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
storage/app/public/images/setup-logo.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
storage/app/public/images/sleep.bmp
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
storage/app/public/images/sleep.png
Normal file
|
After Width: | Height: | Size: 522 B |
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
|||
89
tests/Feature/GenerateDefaultImagesTest.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Services\ImageGenerationService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
test('command transforms default images for all device models', function () {
|
||||
// Ensure we have device models
|
||||
$deviceModels = DeviceModel::all();
|
||||
expect($deviceModels)->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();
|
||||
});
|
||||
89
tests/Feature/TransformDefaultImagesTest.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Services\ImageGenerationService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
test('command transforms default images for all device models', function () {
|
||||
// Ensure we have device models
|
||||
$deviceModels = DeviceModel::all();
|
||||
expect($deviceModels)->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();
|
||||
});
|
||||