From bf5c9bb47afccd09a3d92edd1354bfbb7d373253 Mon Sep 17 00:00:00 2001 From: Brooke Kuhlmann Date: Tue, 31 Mar 2026 11:35:49 -0600 Subject: [PATCH] Added Devices API wake reason and missing attributes Ensures the wake reason is allowed to be included in all API requests and responses. This includes allowing the following attributes to be updated via the API: - `firmware_version` - `battery_charge` - `battery_voltage` - `wifi` - `width` - `height` The above allows BYODs to providde accurate infromation that wasn't originally supported. Milestone: minor --- app/schemas/devices/patch.rb | 9 ++- app/schemas/devices/upsert.rb | 9 ++- app/serializers/device.rb | 1 + doc/api.adoc | 19 +++++- spec/app/schemas/devices/upsert_spec.rb | 13 +++- spec/app/serializers/device_spec.rb | 1 + spec/requests/devices_spec.rb | 91 ++++++++++++++----------- 7 files changed, 96 insertions(+), 47 deletions(-) diff --git a/app/schemas/devices/patch.rb b/app/schemas/devices/patch.rb index 6121833f..174cbdc3 100644 --- a/app/schemas/devices/patch.rb +++ b/app/schemas/devices/patch.rb @@ -8,14 +8,21 @@ module Terminus Patch = Dry::Schema.Params do optional(:model_id).filled :integer optional(:playlist_id).filled :integer - optional(:friendly_id).filled :string optional(:label).filled :string + optional(:friendly_id).filled :string optional(:mac_address).filled Types::MACAddress optional(:api_key).filled :string optional(:refresh_rate).filled :integer, gt?: 0 optional(:image_timeout).filled :integer, gteq?: 0 optional(:proxy).filled :bool optional(:firmware_update).filled :bool + optional(:firmware_version).filled Types::Version + optional(:battery_charge).filled :float, gteq?: 0 + optional(:battery_voltage).filled :float, gteq?: 0 + optional(:wifi).filled :integer + optional(:width).filled :integer + optional(:height).filled :integer + optional(:wake_reason).filled :string optional(:sleep_start_at).maybe :string optional(:sleep_stop_at).maybe :string end diff --git a/app/schemas/devices/upsert.rb b/app/schemas/devices/upsert.rb index 591bc274..d7682d37 100644 --- a/app/schemas/devices/upsert.rb +++ b/app/schemas/devices/upsert.rb @@ -12,12 +12,17 @@ module Terminus optional(:friendly_id).filled :string required(:mac_address).filled Types::MACAddress optional(:api_key).filled :string - optional(:battery_charge).filled :integer, gteq?: 0 - optional(:battery_voltage).filled :float optional(:refresh_rate).filled :integer, gt?: 0 optional(:image_timeout).filled :integer, gteq?: 0 optional(:proxy).filled :bool optional(:firmware_update).filled :bool + optional(:firmware_version).filled Types::Version + optional(:battery_charge).filled :float, gteq?: 0 + optional(:battery_voltage).filled :float + optional(:wifi).filled :integer + optional(:width).filled :integer + optional(:height).filled :integer + optional(:wake_reason).filled :string optional(:sleep_start_at).maybe :string optional(:sleep_stop_at).maybe :string diff --git a/app/serializers/device.rb b/app/serializers/device.rb index fc226661..a3ad2a1e 100644 --- a/app/serializers/device.rb +++ b/app/serializers/device.rb @@ -19,6 +19,7 @@ module Terminus battery_voltage refresh_rate image_timeout + wake_reason width height proxy diff --git a/doc/api.adoc b/doc/api.adoc index 83324db5..6c0670b0 100644 --- a/doc/api.adoc +++ b/doc/api.adoc @@ -44,8 +44,11 @@ The {trmnl_firmware_link} will include the following headers in each HTTP reques * `HOST`: The host (usually the IP address). * `ID`: The MAC address of the device. * `MODEL`: The generic device model (doesn't match {trmnl_api_gem_link} model names but can be used as a fuzzy match). +* `PERCENT_CHARGED`: The remaining charge of the device battery. When present, prefer this value over `BATTERY_VOLTAGE` because this will give you the exact percent of battery charge left. * `REFRESH_RATE`: The current refresh rate as saved on the device. Example: 100. * `RSSI`: The WiFi signal strength (usually -100 to 100). +* `SENSORS`: The sensor reasons for any sensors connected to the device. +* `UPDATE_SOURCE`: The reason for why the device was woken from sleep. * `USER_AGENT`: The device name (usually the ESP32 board name). * `WIDTH`: The device width. Example: 800. @@ -259,6 +262,7 @@ curl "https://localhost:2443/api/devices/1" \ "battery_voltage": 4.0, "refresh_rate": 500, "image_timeout": 0, + "wake_reason": null, "width": 800, "height": 480, "proxy": false, @@ -287,6 +291,7 @@ curl "https://localhost:2443/api/devices/1" \ "battery_voltage": 4.0, "refresh_rate": 500, "image_timeout": 0, + "wake_reason": null, "width": 800, "height": 480, "proxy": false, @@ -335,12 +340,17 @@ curl -X "POST" "https://localhost:2443/api/devices" \ "friendly_id": "DEMO11", "mac_address": "A1:B2:C3:D4:E5:F6", "api_key": "OScdcN0kFbKjFcid9Kz6Cx", - "battery_charge": 85.0, - "battery_voltage": -70, - "firmware_update": true, "refresh_rate": 500, "image_timeout": 0, "proxy": false, + "firmware_update": true, + "firmware_version": "1.2.3", + "battery_charge": 85.0, + "battery_voltage": -70, + "wifi": -70, + "width": 800, + "height": 480, + "wake_reason": "Awoken from API.", "sleep_start_at": "18:00:00", "sleep_stop_at": "06:00:00" } @@ -368,6 +378,7 @@ curl -X "POST" "https://localhost:2443/api/devices" \ "battery_voltage": 0.0, "refresh_rate": 900, "image_timeout": 0, + "wake_reason": null, "width": 0, "height": 0, "proxy": false, @@ -421,6 +432,7 @@ You can change a single attribute or multiple attributes at once. All attributes "battery_voltage": 4.0, "refresh_rate": 250, "image_timeout": 0, + "wake_reason": null, "width": 800, "height": 480, "proxy": false, @@ -466,6 +478,7 @@ curl -X "DELETE" "https://localhost:2443/api/devices/1" \ "battery_voltage": 4.0, "refresh_rate": 250, "image_timeout": 0, + "wake_reason": null, "width": 800, "height": 480, "proxy": false, diff --git a/spec/app/schemas/devices/upsert_spec.rb b/spec/app/schemas/devices/upsert_spec.rb index ce68de92..ee15ddb9 100644 --- a/spec/app/schemas/devices/upsert_spec.rb +++ b/spec/app/schemas/devices/upsert_spec.rb @@ -14,12 +14,19 @@ RSpec.describe Terminus::Schemas::Devices::Upsert do friendly_id: "ABC123", mac_address: "AA:BB:CC:11:22:33", api_key: "secret", - battery_charge: 85.0, - battery_voltage: 3.5, refresh_rate: 100, image_timeout: 0, proxy: "on", - firmware_update: "on" + firmware_update: "on", + firmware_version: "1.2.3", + battery_charge: 85.0, + battery_voltage: 3.5, + wifi: -75, + width: 800, + height: 480, + wake_reason: "Awoken from test.", + sleep_start_at: "18:00:00", + sleep_end_at: "06:00:00" } end diff --git a/spec/app/serializers/device_spec.rb b/spec/app/serializers/device_spec.rb index 69b1f836..c3180a75 100644 --- a/spec/app/serializers/device_spec.rb +++ b/spec/app/serializers/device_spec.rb @@ -21,6 +21,7 @@ RSpec.describe Terminus::Serializers::Device do mac_address: "A1:B2:C3:D4:E5:F6", api_key: "abc123", firmware_version: "1.2.3", + wake_reason: nil, wifi: -40, battery_charge: 0.0, battery_voltage: 3.0, diff --git a/spec/requests/devices_spec.rb b/spec/requests/devices_spec.rb index aa3f82ed..9ea998e9 100644 --- a/spec/requests/devices_spec.rb +++ b/spec/requests/devices_spec.rb @@ -12,17 +12,24 @@ RSpec.describe "/api/devices", :db do let :attributes do { model_id: model.id, - playlist_id: nil, + playlist_id: playlist.id, + label: "Test", friendly_id: "ABC123", - label: "Request Test", mac_address: "A1:B2:C3:D4:E5:F6", - api_key: "abc123", - refresh_rate: 500, + api_key: "secret", + refresh_rate: 100, image_timeout: 5, - proxy: true, - firmware_update: false, - sleep_start_at: "05:00:00+0000", - sleep_stop_at: "10:00:00+0000" + proxy: "on", + firmware_update: "on", + firmware_version: "1.2.3", + battery_charge: 85.0, + battery_voltage: 3.5, + wifi: -75, + width: 800, + height: 480, + wake_reason: "Awoken from test.", + sleep_start_at: "18:00:00+0000", + sleep_stop_at: "06:00:00+0000" } end @@ -40,19 +47,21 @@ RSpec.describe "/api/devices", :db do id: device.id, model_id: model.id, playlist_id: playlist.id, - friendly_id: "ABC123", label: "Test", + friendly_id: "ABC123", mac_address: "A1:B2:C3:D4:E5:F6", api_key: "abc123", - firmware_version: "1.2.3", - wifi: -44, - battery_voltage: 3.0, refresh_rate: 900, image_timeout: 0, - width: 0, - height: 0, proxy: false, firmware_update: true, + firmware_version: "1.2.3", + battery_charge: 0.0, + battery_voltage: 3.0, + wifi: -44, + width: 0, + height: 0, + wake_reason: nil, sleep_start_at: nil, sleep_stop_at: nil, created_at: match_rfc_3339, @@ -92,6 +101,7 @@ RSpec.describe "/api/devices", :db do battery_voltage: 3.0, refresh_rate: 900, image_timeout: 0, + wake_reason: nil, width: 0, height: 0, proxy: false, @@ -124,21 +134,23 @@ RSpec.describe "/api/devices", :db do id: kind_of(Integer), model_id: model.id, playlist_id: kind_of(Integer), + label: "Test", friendly_id: match_device_friendly_id, - label: "Request Test", mac_address: "A1:B2:C3:D4:E5:F6", api_key: kind_of(String), - firmware_version: nil, - wifi: 0, - battery_voltage: 0.0, - refresh_rate: 500, + refresh_rate: 100, image_timeout: 5, - width: 0, - height: 0, proxy: true, - firmware_update: false, - sleep_start_at: "05:00:00", - sleep_stop_at: "10:00:00", + firmware_update: true, + firmware_version: "1.2.3", + battery_voltage: 3.5, + battery_charge: 85.0, + wifi: -75, + width: 800, + height: 480, + wake_reason: "Awoken from test.", + sleep_start_at: "18:00:00", + sleep_stop_at: "06:00:00", created_at: match_rfc_3339, updated_at: match_rfc_3339 ) @@ -163,19 +175,21 @@ RSpec.describe "/api/devices", :db do id: kind_of(Integer), model_id: model.id, playlist_id: kind_of(Integer), - friendly_id: match_device_friendly_id, label: "Test", + friendly_id: match_device_friendly_id, mac_address: "A1:B2:C3:D4:E5:F6", api_key: kind_of(String), - firmware_version: nil, - wifi: 0, - battery_voltage: 0.0, refresh_rate: 900, image_timeout: 0, - width: 0, - height: 0, proxy: false, firmware_update: false, + firmware_version: nil, + battery_voltage: 0.0, + battery_charge: 0.0, + wifi: 0, + width: 0, + height: 0, + wake_reason: nil, sleep_start_at: nil, sleep_stop_at: nil, created_at: match_rfc_3339, @@ -236,7 +250,7 @@ RSpec.describe "/api/devices", :db do expect(json_payload.dig(:data, :label)).to eq("Test Patch") end - it "answers original record when there is nothing to patch" do + it "answers problem details with empty attributes" do patch routes.path(:api_device_patch, id: device.id), {device: {}}.to_json, "HTTP_AUTHORIZATION" => access_token, @@ -292,20 +306,21 @@ RSpec.describe "/api/devices", :db do id: device.id, model_id: model.id, playlist_id: playlist.id, - friendly_id: "ABC123", label: "Test", + friendly_id: "ABC123", mac_address: "A1:B2:C3:D4:E5:F6", api_key: "abc123", - firmware_version: "1.2.3", - wifi: -44, - battery_charge: 0, - battery_voltage: 3.0, refresh_rate: 900, image_timeout: 0, - width: 0, - height: 0, proxy: false, firmware_update: true, + firmware_version: "1.2.3", + battery_charge: 0, + battery_voltage: 3.0, + wifi: -44, + width: 0, + height: 0, + wake_reason: nil, sleep_start_at: nil, sleep_stop_at: nil, created_at: match_rfc_3339,