feat(devices): DeviceModel::V2 for Waveshare 13.3" Spectra-6
CI / test (push) Has been cancelled

Adds the second panel model alongside V1 (800x480, 7.3"). V2 is
1200x1600 panel-native (tall) — the inverse aspect ratio means
its "natural" orientation is portrait, not landscape:
- DeviceModel::nativeOrientation() — V1 returns Landscape, V2 returns
  Portrait. Render rotates the source image 90 CCW only when the user's
  orientation differs from the panel's native, so the .bin stays
  panel-native scan order without per-model branches.
- DeviceModel::panelId() / fromPanelId() — string mapping for the
  firmware's X-Panel-Id header (matches -DPANEL_ID build flag).
- DeviceImageController: on every poll, if X-Panel-Id maps to a known
  model and differs from the device's current model, auto-correct.
  New Devices are created with the V1 default, so a freshly-claimed
  13.3" unit needs this correction before the first image render
  produces a wrong-dimension .bin the firmware would reject.

8 new DeviceModel unit tests, 3 new controller tests cover the
header-correction behaviour (different, same, unknown panel-id).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 15:53:59 -04:00
parent 2adb07518c
commit b286a1f241
5 changed files with 207 additions and 29 deletions
@@ -596,4 +596,61 @@ class DeviceImageControllerTest extends AppWebTestCase
$this->assertResponseStatusCodeSame(204);
}
// Poll with X-Panel-Id matching a different DeviceModel must auto-update
// the device's model. New Devices are created with the V1 default, so a
// 13.3" unit ends up wrongly flagged until the controller corrects it.
public function test_x_panel_id_header_updates_device_model(): void
{
$setup = $this->createTestSetup(true, false);
$this->assertSame(DeviceModel::V1, $setup['device']->getModel());
$this->client->request(
'GET',
'/api/device/' . self::MAC . '/image',
[],
[],
['HTTP_X_PANEL_ID' => 'waveshare-13.3-spectra6'],
);
$this->em()->refresh($setup['device']);
$this->assertSame(DeviceModel::V2, $setup['device']->getModel());
}
// Same-model X-Panel-Id is a no-op — no churn on every poll.
public function test_x_panel_id_header_matching_current_model_does_not_change(): void
{
$setup = $this->createTestSetup(true, false);
$this->client->request(
'GET',
'/api/device/' . self::MAC . '/image',
[],
[],
['HTTP_X_PANEL_ID' => 'waveshare-7.3-spectra6'],
);
$this->em()->refresh($setup['device']);
$this->assertSame(DeviceModel::V1, $setup['device']->getModel());
}
// Unknown panel-id strings must be ignored — never silently drop a known
// device into an unknown state because firmware reported an unrecognised
// panel.
public function test_x_panel_id_header_unknown_leaves_model_alone(): void
{
$setup = $this->createTestSetup(true, false);
$this->client->request(
'GET',
'/api/device/' . self::MAC . '/image',
[],
[],
['HTTP_X_PANEL_ID' => 'totally-fake-panel'],
);
$this->em()->refresh($setup['device']);
$this->assertSame(DeviceModel::V1, $setup['device']->getModel());
}
}