@@ -2,6 +2,7 @@ package ink.trmnl.android.data
22
33import com.slack.eithernet.ApiResult
44import com.slack.eithernet.InternalEitherNetApi
5+ import com.slack.eithernet.exceptionOrNull
56import com.squareup.anvil.annotations.optional.SingleIn
67import ink.trmnl.android.BuildConfig.USE_FAKE_API
78import ink.trmnl.android.di.AppScope
@@ -10,6 +11,8 @@ import ink.trmnl.android.model.TrmnlDeviceType
1011import ink.trmnl.android.network.TrmnlApiService
1112import ink.trmnl.android.network.TrmnlApiService.Companion.CURRENT_PLAYLIST_SCREEN_API_PATH
1213import ink.trmnl.android.network.TrmnlApiService.Companion.NEXT_PLAYLIST_SCREEN_API_PATH
14+ import ink.trmnl.android.network.model.TrmnlDisplayResponse
15+ import ink.trmnl.android.util.ERROR_TYPE_DEVICE_SETUP_REQUIRED
1316import ink.trmnl.android.util.HTTP_200
1417import ink.trmnl.android.util.HTTP_500
1518import ink.trmnl.android.util.isHttpOk
@@ -57,7 +60,7 @@ class TrmnlDisplayRepository
5760 deviceMacId = trmnlDeviceConfig.deviceMacId,
5861 // TEMP FIX: Use Base64 encoding to avoid relative path issue
5962 // See https://github.com/usetrmnl/trmnl-android/issues/76#issuecomment-2980018109
60- useBase64 = trmnlDeviceConfig.type == TrmnlDeviceType .BYOS ,
63+ // useBase64 = trmnlDeviceConfig.type == TrmnlDeviceType.BYOS, // Disabled for now
6164 )
6265
6366 when (result) {
@@ -66,13 +69,18 @@ class TrmnlDisplayRepository
6669 }
6770 is ApiResult .Success -> {
6871 // Map the response to the display info
69- val response = result.value
72+ val response: TrmnlDisplayResponse = result.value
73+
74+ if (isDeviceSetupRequired(trmnlDeviceConfig, response)) {
75+ return setupRequiredTrmnlDisplayInfo(trmnlDeviceConfig)
76+ }
77+
7078 val displayInfo =
7179 TrmnlDisplayInfo (
7280 status = response.status,
7381 trmnlDeviceType = trmnlDeviceConfig.type,
7482 imageUrl = response.imageUrl ? : " " ,
75- imageName = response.imageName ? : " " ,
83+ imageFileName = response.imageFileName ? : " " ,
7684 error = response.error,
7785 refreshIntervalSeconds = response.refreshRate,
7886 httpResponseMetadata = extractHttpResponseMetadata(result),
@@ -132,7 +140,7 @@ class TrmnlDisplayRepository
132140 status = response.status,
133141 trmnlDeviceType = trmnlDeviceConfig.type,
134142 imageUrl = response.imageUrl ? : " " ,
135- imageName = response.filename ? : " " ,
143+ imageFileName = response.filename ? : " " ,
136144 error = response.error,
137145 refreshIntervalSeconds = response.refreshRateSec,
138146 httpResponseMetadata = extractHttpResponseMetadata(result),
@@ -151,6 +159,46 @@ class TrmnlDisplayRepository
151159 }
152160 }
153161
162+ /* *
163+ * Sets up a new device by calling the setup API endpoint.
164+ *
165+ * This is only applicable for BYOS devices, as other device types do not require setup.
166+ *
167+ * @param trmnlDeviceConfig The configuration for the device to be set up.
168+ * @return A [DeviceSetupInfo] object containing the result of the setup operation.
169+ */
170+ suspend fun setupNewDevice (trmnlDeviceConfig : TrmnlDeviceConfig ): DeviceSetupInfo {
171+ if (trmnlDeviceConfig.type != TrmnlDeviceType .BYOS ) {
172+ Timber .w(" Device setup is only applicable for BYOS devices." )
173+ }
174+
175+ val result =
176+ apiService.setupNewDevice(
177+ fullApiUrl = constructApiUrl(trmnlDeviceConfig.apiBaseUrl, TrmnlApiService .SETUP_API_PATH ),
178+ deviceMacId = requireNotNull(trmnlDeviceConfig.deviceMacId) { " Device MAC ID is required for setup" },
179+ )
180+ when (result) {
181+ is ApiResult .Failure -> {
182+ Timber .e(" Failed to setup device: ${result.exceptionOrNull()} " )
183+ return DeviceSetupInfo (
184+ success = false ,
185+ deviceMacId = trmnlDeviceConfig.deviceMacId,
186+ apiKey = " " ,
187+ message = " Failed to setup device with ID (${trmnlDeviceConfig.deviceMacId} ). Reason: $result " ,
188+ )
189+ }
190+ is ApiResult .Success -> {
191+ Timber .i(" Device setup successful: ${result.value} " )
192+ return DeviceSetupInfo (
193+ success = true ,
194+ deviceMacId = trmnlDeviceConfig.deviceMacId,
195+ apiKey = result.value.apiKey,
196+ message = result.value.message,
197+ )
198+ }
199+ }
200+ }
201+
154202 /* *
155203 * Generates fake display info for debugging purposes without wasting an API request.
156204 *
@@ -169,7 +217,7 @@ class TrmnlDisplayRepository
169217 status = HTTP_200 ,
170218 trmnlDeviceType = TrmnlDeviceType .TRMNL ,
171219 imageUrl = mockImageUrl,
172- imageName = " mocked-image-" + mockImageUrl.substringAfterLast(' ?' ),
220+ imageFileName = " mocked-image-" + mockImageUrl.substringAfterLast(' ?' ),
173221 error = null ,
174222 refreshIntervalSeconds = mockRefreshRate,
175223 )
@@ -208,7 +256,7 @@ class TrmnlDisplayRepository
208256 status = HTTP_500 ,
209257 trmnlDeviceType = trmnlDeviceConfig.type,
210258 imageUrl = " " ,
211- imageName = " " ,
259+ imageFileName = " " ,
212260 error = " API failure" ,
213261 refreshIntervalSeconds = 0L ,
214262 )
@@ -222,7 +270,7 @@ class TrmnlDisplayRepository
222270 status = HTTP_500 ,
223271 trmnlDeviceType = trmnlDeviceConfig.type,
224272 imageUrl = " " ,
225- imageName = " " ,
273+ imageFileName = " " ,
226274 error = " HTTP failure: ${failure.code} , error: ${failure.error} " ,
227275 refreshIntervalSeconds = 0L ,
228276 )
@@ -236,7 +284,7 @@ class TrmnlDisplayRepository
236284 status = HTTP_500 ,
237285 trmnlDeviceType = trmnlDeviceConfig.type,
238286 imageUrl = " " ,
239- imageName = " " ,
287+ imageFileName = " " ,
240288 error = " Network failure: ${failure.error.localizedMessage} " ,
241289 refreshIntervalSeconds = 0L ,
242290 )
@@ -250,7 +298,7 @@ class TrmnlDisplayRepository
250298 status = HTTP_500 ,
251299 trmnlDeviceType = trmnlDeviceConfig.type,
252300 imageUrl = " " ,
253- imageName = " " ,
301+ imageFileName = " " ,
254302 error = " Unknown failure: ${failure.error.localizedMessage} " ,
255303 refreshIntervalSeconds = 0L ,
256304 )
@@ -285,4 +333,38 @@ class TrmnlDisplayRepository
285333 timestamp = System .currentTimeMillis(),
286334 )
287335 }
336+
337+ /* *
338+ * Right now there is no good known way to determine if a device requires setup.
339+ * The logic here is based on sample responses from the Terminus server API.
340+ *
341+ * See
342+ * - https://discord.com/channels/1281055965508141100/1331360842809348106/1384605617456545904
343+ * - https://discord.com/channels/1281055965508141100/1384605617456545904/1384613229086511135
344+ */
345+ private fun isDeviceSetupRequired (
346+ trmnlDeviceConfig : TrmnlDeviceConfig ,
347+ response : TrmnlDisplayResponse ,
348+ ): Boolean =
349+ trmnlDeviceConfig.type == TrmnlDeviceType .BYOS &&
350+ response.imageFileName?.startsWith(" setup" , ignoreCase = true ) == true &&
351+ // This ensures that no screen is generated yet for the device
352+ response.imageUrl?.contains(" screens" , ignoreCase = true ) == false
353+
354+ /* *
355+ * Creates a [TrmnlDisplayInfo] indicating that the device requires setup.
356+ *
357+ * This is used when the device is not yet configured and needs to be set up before it can display content.
358+ * @see ERROR_TYPE_DEVICE_SETUP_REQUIRED
359+ * @see [isDeviceSetupRequired]
360+ */
361+ private fun setupRequiredTrmnlDisplayInfo (trmnlDeviceConfig : TrmnlDeviceConfig ): TrmnlDisplayInfo =
362+ TrmnlDisplayInfo (
363+ status = HTTP_500 ,
364+ trmnlDeviceType = trmnlDeviceConfig.type,
365+ imageUrl = " " ,
366+ imageFileName = ERROR_TYPE_DEVICE_SETUP_REQUIRED ,
367+ error = " Device setup required" ,
368+ refreshIntervalSeconds = 0L ,
369+ )
288370 }
0 commit comments