Skip to content

Commit 2c54db4

Browse files
author
fwmone
committed
## [0.1.17] - 2026-07-19
### Added - "X-Server-Timestamp" header sends the server time as Unix timestamp so that the frame can use it
1 parent 000a246 commit 2c54db4

4 files changed

Lines changed: 42 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/).
77

8+
## [0.1.17] - 2026-07-19
9+
10+
### Added
11+
- "X-Server-Timestamp" header sends the server time as Unix timestamp so that the frame can use it
12+
813
## [0.1.16] - 2026-06-21
914

1015
### Changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,13 @@ Content-Type: application/json
100100
}
101101
```
102102

103-
For IP-AND-PORT-OF-HOME-ASSISTANT, for example, http://192.168.0.1:8123. The framework then automatically appends the path to the pull service.
103+
On Windows cmd it looks like this:
104+
105+
```bash
106+
curl -X PUT "http://{device_ip}/upstream/pull_settings" -H "Content-Type: application/json" -d "{\"upstream_on\":true,\"upstream_url\":\"http://<IP-AND-PORT-OF-HOME-ASSISTANT>\",\"token\":\"<YOUR_TOKEN_HERE>\",\"cron_time\":\"2025-11-01T08:30:00Z\"}"
107+
```
108+
109+
Use for IP-AND-PORT-OF-HOME-ASSISTANT, for example, http://192.168.0.1:8123. The framework then automatically appends the path to the pull service.
104110

105111
## Testing
106112

custom_components/bloomin8_pull/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "bloomin8_pull",
33
"name": "BLOOMIN8 Pull Endpoint",
4-
"version": "0.1.16",
4+
"version": "0.1.17",
55
"documentation": "https://github.com/fwmone/bloomin8_pull",
66
"issue_tracker": "https://github.com/fwmone/bloomin8_pull/issues",
77
"requirements": [],

custom_components/bloomin8_pull/view.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,29 @@ async def choose_varied(hass, files: list[str], store_key: str = "recent_images"
136136
ALLOWED_EXT = (".jpg",)
137137

138138

139+
def _server_timestamp() -> str:
140+
"""Return the current UTC time as a Unix timestamp in seconds."""
141+
return str(int(dt.now(timezone.utc).timestamp()))
142+
143+
144+
def json_response_with_timestamp(
145+
data: dict,
146+
*,
147+
status: int = HTTPStatus.OK,
148+
**kwargs,
149+
) -> web.Response:
150+
"""Create a JSON response including the BLOOMIN8 clock-sync header."""
151+
headers = dict(kwargs.pop("headers", {}) or {})
152+
headers["X-Server-Timestamp"] = _server_timestamp()
153+
154+
return web.json_response(
155+
data,
156+
status=status,
157+
headers=headers,
158+
**kwargs,
159+
)
160+
161+
139162
class Bloomin8PullView(HomeAssistantView):
140163
"""Implements GET /eink_pull for BLOOMIN8 devices."""
141164

@@ -152,7 +175,7 @@ async def get(self, request: web.Request) -> web.Response:
152175
token = request.headers.get("X-Access-Token", "")
153176
expected = self.cfg["access_token"]
154177
if not expected or token != expected:
155-
return web.json_response(
178+
return json_response_with_timestamp(
156179
{"status": 401, "type": "ERROR", "message": "Unauthorized"},
157180
status=HTTPStatus.UNAUTHORIZED,
158181
)
@@ -213,7 +236,7 @@ async def get(self, request: web.Request) -> web.Response:
213236
files = []
214237

215238
if not files:
216-
return web.json_response(
239+
return json_response_with_timestamp(
217240
{
218241
"status": 204,
219242
"message": "No image available",
@@ -253,7 +276,7 @@ async def get(self, request: web.Request) -> web.Response:
253276
)
254277
except Exception as err:
255278
_LOGGER.exception("Failed to publish image %s -> %s: %s", src_path, dst_path, err)
256-
return web.json_response(
279+
return json_response_with_timestamp(
257280
{"status": 500, "type": "ERROR", "message": "Failed to publish image"},
258281
status=HTTPStatus.INTERNAL_SERVER_ERROR,
259282
)
@@ -283,7 +306,7 @@ async def get(self, request: web.Request) -> web.Response:
283306
except Exception:
284307
pass
285308

286-
return web.json_response(
309+
return json_response_with_timestamp(
287310
{
288311
"status": 200,
289312
"type": "SHOW",
@@ -312,7 +335,7 @@ async def get(self, request: web.Request) -> web.Response:
312335
token = request.headers.get("X-Access-Token", "")
313336
expected = self.cfg["access_token"]
314337
if not expected or token != expected:
315-
return web.json_response(
338+
return json_response_with_timestamp(
316339
{"status": 401, "type": "ERROR", "message": "Unauthorized"},
317340
status=HTTPStatus.UNAUTHORIZED,
318341
)
@@ -352,7 +375,7 @@ async def get(self, request: web.Request) -> web.Response:
352375
pull_id, success, request.remote
353376
)
354377

355-
return web.json_response(
378+
return json_response_with_timestamp(
356379
{
357380
"status": 200,
358381
"message": "Feedback recorded"

0 commit comments

Comments
 (0)