Skip to content

Commit bcb8c31

Browse files
committed
Release v0.1.0: Context-first API, linting, and comprehensive tests
Breaking Changes: - All speaker methods now require context.Context as first parameter - Removed *Context variant methods - NewSpeaker returns pointer (*KEFSpeaker, error) - DiscoverSpeakers now takes (context.Context, time.Duration) - Renamed fields: Id→ID, ProfileId→ProfileID, SystemMemberId→SystemMemberID - Renamed KEFGroupingmember→KEFGroupingMember Added: - Functional options pattern for NewSpeaker (WithTimeout, WithHTTPClient) - Custom error types (ErrConnectionRefused, ErrConnectionTimeout, etc.) - TypeEncoder interface for KEF-specific types - golangci-lint configuration with comprehensive linting - 160+ unit tests - CHANGELOG.md following Keep a Changelog format - Comprehensive documentation Changed: - Unified HTTP client per speaker instance - Replaced logrus with slog in library - Improved JSON parsing with safe type assertions - Better error wrapping with %w verb Removed: - cmd/kef-virtual-hub (moved to separate project)
1 parent 0b4d488 commit bcb8c31

38 files changed

Lines changed: 1146 additions & 573 deletions

.golangci.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
version: "2"
2+
3+
run:
4+
timeout: 5m
5+
modules-download-mode: readonly
6+
7+
linters:
8+
default: standard
9+
enable:
10+
- bodyclose
11+
- copyloopvar
12+
- dogsled
13+
- dupl
14+
- errname
15+
- errorlint
16+
- gochecknoinits
17+
- goconst
18+
- gocritic
19+
- gocyclo
20+
- godot
21+
- gosec
22+
- misspell
23+
- nakedret
24+
- nilerr
25+
- noctx
26+
- prealloc
27+
- predeclared
28+
- revive
29+
- unconvert
30+
- unparam
31+
- whitespace
32+
33+
settings:
34+
gocyclo:
35+
min-complexity: 20
36+
goconst:
37+
min-len: 3
38+
min-occurrences: 3
39+
misspell:
40+
locale: US
41+
revive:
42+
rules:
43+
- name: blank-imports
44+
- name: context-as-argument
45+
- name: context-keys-type
46+
- name: dot-imports
47+
- name: error-return
48+
- name: error-strings
49+
- name: error-naming
50+
- name: exported
51+
- name: if-return
52+
- name: increment-decrement
53+
- name: var-naming
54+
- name: var-declaration
55+
- name: package-comments
56+
- name: range
57+
- name: receiver-naming
58+
- name: time-naming
59+
- name: unexported-return
60+
- name: indent-error-flow
61+
- name: errorf
62+
- name: empty-block
63+
- name: superfluous-else
64+
- name: unused-parameter
65+
- name: unreachable-code
66+
- name: redefines-builtin-id
67+
68+
exclusions:
69+
generated: lax
70+
presets:
71+
- comments
72+
- std-error-handling
73+
rules:
74+
# Allow using init functions in cmd packages
75+
- path: cmd/
76+
linters:
77+
- gochecknoinits
78+
# Ignore gosec G104 in cmd packages (printer errors are non-critical)
79+
- path: cmd/
80+
linters:
81+
- gosec
82+
text: "G104"
83+
# Ignore some linters in test files
84+
- path: _test\.go
85+
linters:
86+
- dupl
87+
- gosec
88+
- goconst
89+
# Ignore gosec in examples (example code prioritizes readability)
90+
- path: examples/
91+
linters:
92+
- gosec
93+
# Ignore long lines in generated code
94+
- path: \.pb\.go
95+
linters:
96+
- all
97+
98+
formatters:
99+
enable:
100+
- gofmt
101+
- goimports
102+
settings:
103+
goimports:
104+
local-prefixes:
105+
- github.com/hilli/go-kef-w2
106+
107+
output:
108+
formats:
109+
text:
110+
path: stdout
111+
show-stats: true

CHANGELOG.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.1.0] - 2026-01-30
11+
12+
### Breaking Changes
13+
14+
#### Library API Changes
15+
16+
- **Context-first API**: All speaker methods now require `context.Context` as their first parameter. The `*Context` variant methods have been removed.
17+
18+
```go
19+
// Old
20+
volume, err := speaker.GetVolume()
21+
err = speaker.SetVolume(50)
22+
err = speaker.Mute()
23+
source, err := speaker.Source()
24+
25+
// New
26+
ctx := context.Background()
27+
volume, err := speaker.GetVolume(ctx)
28+
err = speaker.SetVolume(ctx, 50)
29+
err = speaker.Mute(ctx)
30+
source, err := speaker.Source(ctx)
31+
```
32+
33+
Methods updated:
34+
- `GetVolume(ctx)`, `SetVolume(ctx, volume)`, `Mute(ctx)`, `Unmute(ctx)`, `IsMuted(ctx)`
35+
- `Source(ctx)`, `SetSource(ctx, source)`, `PowerOff(ctx)`, `SpeakerState(ctx)`, `IsPoweredOn(ctx)`
36+
- `PlayPause(ctx)`, `NextTrack(ctx)`, `PreviousTrack(ctx)`, `IsPlaying(ctx)`
37+
- `GetMaxVolume(ctx)`, `SetMaxVolume(ctx, volume)`, `SongProgress(ctx)`, `SongProgressMS(ctx)`
38+
- `CanControlPlayback(ctx)`, `NetworkOperationMode(ctx)`, `UpdateInfo(ctx)`
39+
- `PlayerData(ctx)`, `GetEQProfileV2(ctx)`
40+
41+
- **Removed `*Context` variant methods**: Methods like `GetVolumeContext`, `SetVolumeContext`, `MuteContext`, etc. have been removed. Use the standard methods with context instead.
42+
43+
- **`NewSpeaker` now returns a pointer**: `NewSpeaker()` returns `(*KEFSpeaker, error)` instead of `(KEFSpeaker, error)`.
44+
45+
```go
46+
// Old
47+
speaker, err := kefw2.NewSpeaker("192.168.1.100")
48+
49+
// New
50+
speaker, err := kefw2.NewSpeaker("192.168.1.100") // Returns *KEFSpeaker
51+
```
52+
53+
- **`DiscoverSpeakers` signature changed**: Now accepts `context.Context` and `time.Duration` instead of an `int` for timeout seconds.
54+
55+
```go
56+
// Old
57+
speakers, err := kefw2.DiscoverSpeakers(5) // 5 seconds
58+
59+
// New
60+
ctx := context.Background()
61+
speakers, err := kefw2.DiscoverSpeakers(ctx, 5*time.Second)
62+
```
63+
64+
A legacy wrapper `DiscoverSpeakersLegacy(int)` is available for backward compatibility but is deprecated.
65+
66+
- **Renamed `KEFSpeaker.Id` to `KEFSpeaker.ID`**: Following Go naming conventions.
67+
68+
- **Renamed `KEFGroupingmember` to `KEFGroupingMember`**: Fixed casing to follow Go naming conventions.
69+
70+
- **Renamed `EQProfileV2.ProfileId` to `EQProfileV2.ProfileID`**: Following Go naming conventions for acronyms. JSON serialization unchanged (`profileId`).
71+
72+
- **Renamed `PlayerPlayID.SystemMemberId` to `PlayerPlayID.SystemMemberID`**: Following Go naming conventions for acronyms. JSON serialization unchanged (`systemMemberId`).
73+
74+
### Added
75+
76+
#### Functional Options Pattern
77+
78+
`NewSpeaker` now supports functional options for configuration:
79+
80+
```go
81+
speaker, err := kefw2.NewSpeaker("192.168.1.100",
82+
kefw2.WithTimeout(5*time.Second),
83+
kefw2.WithHTTPClient(customClient),
84+
)
85+
```
86+
87+
#### Context Support
88+
89+
All speaker methods now accept `context.Context` for better control over request cancellation and timeouts:
90+
91+
```go
92+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
93+
defer cancel()
94+
95+
volume, err := speaker.GetVolume(ctx)
96+
if err != nil {
97+
// Handle timeout or cancellation
98+
}
99+
```
100+
101+
#### Custom Error Types
102+
103+
New sentinel errors for better error handling:
104+
105+
- `ErrConnectionRefused` - Speaker not responding (powered off or unreachable)
106+
- `ErrConnectionTimeout` - Request timed out
107+
- `ErrHostNotFound` - Invalid IP address or hostname
108+
- `ErrEmptyData` - Empty response from speaker
109+
- `ErrNoValue` - No value in API response
110+
- `ErrUnknownType` - Unknown value type in response
111+
- `ErrInvalidFormat` - Malformed JSON response
112+
113+
#### TypeEncoder Interface
114+
115+
New `TypeEncoder` interface for KEF-specific types, allowing custom types to be used with `setTypedValue`:
116+
117+
```go
118+
type TypeEncoder interface {
119+
KEFTypeInfo() (typeName string, value string)
120+
}
121+
```
122+
123+
Implemented by: `Source`, `SpeakerStatus`, `CableMode`
124+
125+
#### Comprehensive Documentation
126+
127+
- Added package-level documentation with usage examples
128+
- Added godoc comments to all exported types, constants, and functions
129+
- Documented all struct fields
130+
131+
#### Unit Tests
132+
133+
- Added 160+ unit tests covering:
134+
- JSON parsing functions
135+
- HTTP client operations
136+
- Speaker operations (volume, mute, source, power, playback)
137+
- Type encoding and string conversion
138+
- Event types and parsing
139+
- Context cancellation and timeout handling
140+
- Error handling edge cases
141+
142+
### Changed
143+
144+
#### Internal Refactoring
145+
146+
- **Unified HTTP client**: Single shared `*http.Client` per speaker instance
147+
- **Centralized request handling**: All HTTP requests go through `doRequest()` method
148+
- **Replaced `logrus` with `slog`**: Using standard library structured logging in the library (CLI still uses logrus)
149+
- **Improved JSON parsing**: New internal functions with safe type assertions:
150+
- `parseJSONString()`
151+
- `parseJSONInt()`
152+
- `parseJSONBool()`
153+
- `parseJSONValue()`
154+
- **Better error wrapping**: Using `%w` verb consistently for error chains
155+
- **Channel-based discovery**: `DiscoverSpeakers` uses proper channel synchronization instead of `time.Sleep`
156+
157+
### Deprecated
158+
159+
- `JSONStringValue(data []byte, err error)` - Use `parseJSONString()` (internal) or handle errors separately
160+
- `JSONIntValue(data []byte, err error)` - Use `parseJSONInt()` (internal)
161+
- `JSONUnmarshalValue(data []byte, err error)` - Use `parseJSONValue()` (internal)
162+
- `DiscoverSpeakersLegacy(timeout int)` - Use `DiscoverSpeakers(ctx, duration)`
163+
164+
### Fixed
165+
166+
- Fixed go vet warning in `cmd/kef-virtual-hub/kef-virtual-hub.go` (buffered signal channel)
167+
- Fixed inconsistent pointer/value receiver usage on methods
168+
169+
## Migration Guide
170+
171+
### From Previous Version
172+
173+
1. **Add context to all speaker method calls**: All speaker methods now require `context.Context` as the first parameter.
174+
```go
175+
// Before
176+
volume, err := speaker.GetVolume()
177+
err = speaker.SetVolume(50)
178+
err = speaker.Mute()
179+
180+
// After
181+
ctx := context.Background()
182+
volume, err := speaker.GetVolume(ctx)
183+
err = speaker.SetVolume(ctx, 50)
184+
err = speaker.Mute(ctx)
185+
```
186+
187+
2. **Remove `*Context` method calls**: If you were using methods like `GetVolumeContext`, rename them to the standard method name.
188+
```go
189+
// Before
190+
volume, err := speaker.GetVolumeContext(ctx)
191+
192+
// After
193+
volume, err := speaker.GetVolume(ctx)
194+
```
195+
196+
3. **Update `NewSpeaker` calls**: The return type is now a pointer, but if you were already using the result directly, no changes are needed since methods work on pointer receivers.
197+
198+
4. **Update `DiscoverSpeakers` calls**:
199+
```go
200+
// Before
201+
speakers, err := kefw2.DiscoverSpeakers(5)
202+
203+
// After
204+
speakers, err := kefw2.DiscoverSpeakers(context.Background(), 5*time.Second)
205+
```
206+
207+
5. **Update field access**: If you access `speaker.Id`, change it to `speaker.ID`.
208+
209+
6. **Update EQ profile field access**: If you access `eqProfile.ProfileId`, change it to `eqProfile.ProfileID`.
210+
211+
7. **Update player ID field access**: If you access `playId.SystemMemberId`, change it to `playId.SystemMemberID`.
212+
213+
[Unreleased]: https://github.com/hilli/go-kef-w2/compare/v0.1.0...HEAD
214+
[0.1.0]: https://github.com/hilli/go-kef-w2/releases/tag/v0.1.0

0 commit comments

Comments
 (0)