Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A small web page to adjust the heating per room from a phone on the home network

## Airing

"Airing 15/30/60" sends `system_mode: off`. When the time is up, or on "Heat again", the room gets back the mode and target from before.
"Air 5/15/30/60" sends `system_mode: off` for that many minutes, with 5 for a quick Stoßlüften. When the time is up, or on "Heat again", the room gets back the mode and target from before.

The service keeps no saved state, so a restart during airing forgets it: **that room stays off until someone taps "Heat again"**. A target waiting to be sent is lost too. The thermostat's own frost protection still applies.

Expand Down
3 changes: 3 additions & 0 deletions internal/api/api.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ components:
properties:
minutes:
type: integer
enum: [15, 30, 60]
enum: [5, 15, 30, 60]
description: How long to air the room.
SetTargetRequest:
type: object
Expand Down
6 changes: 3 additions & 3 deletions internal/web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
.note { color: var(--muted); font-size: .9rem; margin-top: 8px; }
.note:empty { display: none; }
.note.problem { color: var(--error); }
.airing { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 8px; }
.airing { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 8px; }
.airing button.stop { grid-column: 1 / -1; background: var(--accent); color: var(--card); font-weight: 600; }
[hidden] { display: none !important; }
</style>
Expand Down Expand Up @@ -78,7 +78,7 @@ <h1>Heating</h1>
errorEl.textContent = commandError || connectionError;
}

const airingMinutes = [15, 30, 60];
const airingMinutes = [5, 15, 30, 60];

const clock = iso => new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const degrees = v => v == null ? '–' : v.toFixed(1) + ' °C';
Expand Down Expand Up @@ -140,7 +140,7 @@ <h1>Heating</h1>
airing.className = 'airing';
const start = airingMinutes.map(minutes => {
const b = document.createElement('button');
b.textContent = 'Airing ' + minutes;
b.textContent = 'Air ' + minutes;
b.setAttribute('aria-label', 'Air ' + room.label + ' for ' + minutes + ' minutes');
b.addEventListener('click', () => startAiring(room.id, minutes));
airing.append(b);
Expand Down
2 changes: 1 addition & 1 deletion internal/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (s server) SetRoomTarget(_ context.Context, r api.SetRoomTargetRequestObjec

func (s server) StartRoomAiring(_ context.Context, r api.StartRoomAiringRequestObject) (api.StartRoomAiringResponseObject, error) {
if !r.Body.Minutes.Valid() {
return api.StartRoomAiring400JSONResponse{Message: "minutes must be 15, 30 or 60"}, nil
return api.StartRoomAiring400JSONResponse{Message: "minutes must be 5, 15, 30 or 60"}, nil
}
switch err := s.rooms.StartAiring(r.Id, time.Duration(r.Body.Minutes)*time.Minute); {
case err == nil:
Expand Down
8 changes: 8 additions & 0 deletions internal/web/web_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ func TestStartAiring(t *testing.T) {
}
}

func TestStartAiringForFiveMinutes(t *testing.T) {
f := &fakeRooms{}
rec := post(t, handler(f), "/api/rooms/office/airing", `{"minutes":5}`, nil)
if rec.Code != http.StatusAccepted || f.airing != 5*time.Minute {
t.Fatalf("status %d, airing %v", rec.Code, f.airing)
}
}

func TestStartAiringErrors(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading