feat: implement SwitchService with ListSwitches method
- Added ListSwitches method to SwitchService in switch_grpc.pb.go. - Implemented SwitchGRPC adapter for ListSwitches in switch.go. - Created SwitchApp for managing switch states and added ListSwitches logic. - Updated core domain with Switch struct and associated methods. - Enhanced LightApp to include ListLights functionality. - Updated protobuf definitions for Switch and Light services to include new request and response messages. - Introduced error handling for unimplemented methods in the gRPC server.
This commit is contained in:
parent
2e99c464ff
commit
abb6774b77
185
README.md
185
README.md
@ -0,0 +1,185 @@
|
|||||||
|
# home-service
|
||||||
|
|
||||||
|
This workspace contains a Home Assistant gRPC gateway and the protobuf contract
|
||||||
|
it serves.
|
||||||
|
|
||||||
|
The current implementation is centered on `ha-gateway`, a Go service that:
|
||||||
|
|
||||||
|
- exposes Home Assistant operations over gRPC
|
||||||
|
- translates gRPC requests into Home Assistant REST API calls
|
||||||
|
- emits OpenTelemetry traces and metrics when configured
|
||||||
|
- keeps protobuf definitions and generated Go stubs in-repo
|
||||||
|
|
||||||
|
`EntityService` and `LightService` are implemented. `SwitchService` and
|
||||||
|
`EventService` are scaffolded but currently return `Unimplemented`.
|
||||||
|
|
||||||
|
## Workspace Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── proto/ # Source protobuf definitions
|
||||||
|
├── gen/ # Generated Go protobuf/grpc code (committed)
|
||||||
|
├── ha-gateway/ # Go gRPC server
|
||||||
|
├── buf.yaml # Buf module config
|
||||||
|
├── buf.gen.yaml # Buf codegen config
|
||||||
|
└── go.work # Go workspace linking gen + ha-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
`ha-gateway` follows a ports-and-adapters structure:
|
||||||
|
|
||||||
|
- `internal/core/domain`: pure domain types
|
||||||
|
- `internal/core/ports/driving`: interfaces exposed to primary adapters
|
||||||
|
- `internal/core/ports/driven`: interfaces the app layer depends on
|
||||||
|
- `internal/app`: application logic for entity and light operations
|
||||||
|
- `internal/adapters/primary/grpc`: gRPC handlers and proto/domain mapping
|
||||||
|
- `internal/adapters/secondary/ha`: Home Assistant REST client
|
||||||
|
- `internal/telemetry`: OpenTelemetry setup
|
||||||
|
|
||||||
|
The runtime flow is:
|
||||||
|
|
||||||
|
1. A gRPC client calls `EntityService` or `LightService`.
|
||||||
|
2. The gRPC adapter maps protobuf messages into domain parameters.
|
||||||
|
3. The app layer orchestrates the use case.
|
||||||
|
4. The HA adapter calls Home Assistant's REST API.
|
||||||
|
5. The response is mapped back into the protobuf response.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
### Implemented
|
||||||
|
|
||||||
|
- `ha.v1.EntityService`
|
||||||
|
- `GetState`
|
||||||
|
- `ListStates`
|
||||||
|
- `ha.v1.LightService`
|
||||||
|
- `TurnOn`
|
||||||
|
- `TurnOff`
|
||||||
|
- `Toggle`
|
||||||
|
|
||||||
|
### Stubbed
|
||||||
|
|
||||||
|
- `ha.v1.SwitchService`
|
||||||
|
- `ha.v1.EventService`
|
||||||
|
|
||||||
|
The event path is planned around Home Assistant WebSocket subscriptions, but the
|
||||||
|
WebSocket adapter and fan-out broker are not implemented yet.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`ha-gateway` reads configuration from environment variables. A sample file lives
|
||||||
|
at [ha-gateway/.env.example](/Users/nik-macbookair/repo/home-service/ha-gateway/.env.example).
|
||||||
|
|
||||||
|
| Variable | Required | Default | Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `HA_TOKEN` | yes | none | Home Assistant long-lived access token |
|
||||||
|
| `GRPC_PORT` | no | `50051` | gRPC listen port |
|
||||||
|
| `HA_BASE_URL` | effectively yes | empty | Example: `http://ha.home.arpa:8123` |
|
||||||
|
| `OTEL_ENDPOINT` | no | empty | OTLP gRPC endpoint; empty disables telemetry |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- startup fails if `HA_TOKEN` is missing
|
||||||
|
- `HA_BASE_URL` is not validated on load, but the gateway cannot reach Home
|
||||||
|
Assistant without it
|
||||||
|
- if `OTEL_ENDPOINT` is empty, the service installs no-op telemetry providers
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go `1.26`
|
||||||
|
- `buf` for protobuf generation
|
||||||
|
- a reachable Home Assistant instance
|
||||||
|
- a valid Home Assistant long-lived access token
|
||||||
|
|
||||||
|
## Generate Protobuf Code
|
||||||
|
|
||||||
|
Run from the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
buf generate
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated files are written to `gen/ha/v1`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Sync the workspace, then build the gateway:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go work sync
|
||||||
|
cd ha-gateway
|
||||||
|
go build ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
Create a local env file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp ha-gateway/.env.example ha-gateway/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill in `HA_TOKEN` and `HA_BASE_URL`, then start the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ha-gateway
|
||||||
|
go run ./cmd/gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
The gateway listens on `:50051` by default.
|
||||||
|
|
||||||
|
## Smoke Test With grpcurl
|
||||||
|
|
||||||
|
Examples against a locally running gateway:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all light entities
|
||||||
|
grpcurl -plaintext -d '{"domain":"light"}' \
|
||||||
|
localhost:50051 ha.v1.EntityService/ListStates
|
||||||
|
|
||||||
|
# Get one entity
|
||||||
|
grpcurl -plaintext -d '{"entity_id":"light.living_room"}' \
|
||||||
|
localhost:50051 ha.v1.EntityService/GetState
|
||||||
|
|
||||||
|
# Turn on a light at 80% brightness
|
||||||
|
grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \
|
||||||
|
localhost:50051 ha.v1.LightService/TurnOn
|
||||||
|
|
||||||
|
# Toggle a light
|
||||||
|
grpcurl -plaintext -d '{"entity_id":"light.living_room"}' \
|
||||||
|
localhost:50051 ha.v1.LightService/Toggle
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Build from the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f ha-gateway/Dockerfile -t ha-gateway:dev .
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it with the same env file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --env-file ha-gateway/.env -p 50051:50051 ha-gateway:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
When `OTEL_ENDPOINT` is set, the gateway exports:
|
||||||
|
|
||||||
|
- traces via OTLP/gRPC
|
||||||
|
- metrics via OTLP/gRPC
|
||||||
|
|
||||||
|
The service name is `ha-gateway`. When `OTEL_ENDPOINT` is unset, telemetry is
|
||||||
|
disabled for local development.
|
||||||
|
|
||||||
|
## Current Limitations
|
||||||
|
|
||||||
|
- no authentication/authorization on inbound gRPC requests yet
|
||||||
|
- `SwitchService` is not implemented
|
||||||
|
- `EventService` is not implemented
|
||||||
|
- Home Assistant event streaming over WebSocket is not implemented
|
||||||
|
- there are currently no unit tests in the repo
|
||||||
|
|
||||||
|
The auth note in [ha-gateway/cmd/gateway/main.go](/Users/nik-macbookair/repo/home-service/ha-gateway/cmd/gateway/main.go) explicitly calls out API-key and mTLS as future options before exposing the gateway outside a trusted network.
|
||||||
@ -240,6 +240,186 @@ func (x *LightResponse) GetState() *EntityState {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LightEntity struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
|
||||||
|
FriendlyName string `protobuf:"bytes,2,opt,name=friendly_name,json=friendlyName,proto3" json:"friendly_name,omitempty"`
|
||||||
|
State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"`
|
||||||
|
SupportedColorModes []string `protobuf:"bytes,4,rep,name=supported_color_modes,json=supportedColorModes,proto3" json:"supported_color_modes,omitempty"`
|
||||||
|
MinColorTempKelvin uint32 `protobuf:"varint,5,opt,name=min_color_temp_kelvin,json=minColorTempKelvin,proto3" json:"min_color_temp_kelvin,omitempty"`
|
||||||
|
MaxColorTempKelvin uint32 `protobuf:"varint,6,opt,name=max_color_temp_kelvin,json=maxColorTempKelvin,proto3" json:"max_color_temp_kelvin,omitempty"`
|
||||||
|
IsHueGroup bool `protobuf:"varint,7,opt,name=is_hue_group,json=isHueGroup,proto3" json:"is_hue_group,omitempty"`
|
||||||
|
EffectList []string `protobuf:"bytes,8,rep,name=effect_list,json=effectList,proto3" json:"effect_list,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) Reset() {
|
||||||
|
*x = LightEntity{}
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LightEntity) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LightEntity) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LightEntity.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LightEntity) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_light_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetEntityId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EntityId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetFriendlyName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.FriendlyName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetState() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.State
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetSupportedColorModes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SupportedColorModes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetMinColorTempKelvin() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MinColorTempKelvin
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetMaxColorTempKelvin() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MaxColorTempKelvin
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetIsHueGroup() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsHueGroup
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LightEntity) GetEffectList() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EffectList
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListLightsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListLightsRequest) Reset() {
|
||||||
|
*x = ListLightsRequest{}
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListLightsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListLightsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListLightsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListLightsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListLightsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_light_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListLightsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Lights []*LightEntity `protobuf:"bytes,1,rep,name=lights,proto3" json:"lights,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListLightsResponse) Reset() {
|
||||||
|
*x = ListLightsResponse{}
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListLightsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListLightsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListLightsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_light_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListLightsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListLightsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_light_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListLightsResponse) GetLights() []*LightEntity {
|
||||||
|
if x != nil {
|
||||||
|
return x.Lights
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_ha_v1_light_proto protoreflect.FileDescriptor
|
var File_ha_v1_light_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_ha_v1_light_proto_rawDesc = "" +
|
const file_ha_v1_light_proto_rawDesc = "" +
|
||||||
@ -267,11 +447,27 @@ const file_ha_v1_light_proto_rawDesc = "" +
|
|||||||
"\rToggleRequest\x12\x1b\n" +
|
"\rToggleRequest\x12\x1b\n" +
|
||||||
"\tentity_id\x18\x01 \x01(\tR\bentityId\"9\n" +
|
"\tentity_id\x18\x01 \x01(\tR\bentityId\"9\n" +
|
||||||
"\rLightResponse\x12(\n" +
|
"\rLightResponse\x12(\n" +
|
||||||
"\x05state\x18\x01 \x01(\v2\x12.ha.v1.EntityStateR\x05state2\xb2\x01\n" +
|
"\x05state\x18\x01 \x01(\v2\x12.ha.v1.EntityStateR\x05state\"\xc2\x02\n" +
|
||||||
|
"\vLightEntity\x12\x1b\n" +
|
||||||
|
"\tentity_id\x18\x01 \x01(\tR\bentityId\x12#\n" +
|
||||||
|
"\rfriendly_name\x18\x02 \x01(\tR\ffriendlyName\x12\x14\n" +
|
||||||
|
"\x05state\x18\x03 \x01(\tR\x05state\x122\n" +
|
||||||
|
"\x15supported_color_modes\x18\x04 \x03(\tR\x13supportedColorModes\x121\n" +
|
||||||
|
"\x15min_color_temp_kelvin\x18\x05 \x01(\rR\x12minColorTempKelvin\x121\n" +
|
||||||
|
"\x15max_color_temp_kelvin\x18\x06 \x01(\rR\x12maxColorTempKelvin\x12 \n" +
|
||||||
|
"\fis_hue_group\x18\a \x01(\bR\n" +
|
||||||
|
"isHueGroup\x12\x1f\n" +
|
||||||
|
"\veffect_list\x18\b \x03(\tR\n" +
|
||||||
|
"effectList\"\x13\n" +
|
||||||
|
"\x11ListLightsRequest\"@\n" +
|
||||||
|
"\x12ListLightsResponse\x12*\n" +
|
||||||
|
"\x06lights\x18\x01 \x03(\v2\x12.ha.v1.LightEntityR\x06lights2\xf5\x01\n" +
|
||||||
"\fLightService\x124\n" +
|
"\fLightService\x124\n" +
|
||||||
"\x06TurnOn\x12\x14.ha.v1.TurnOnRequest\x1a\x14.ha.v1.LightResponse\x126\n" +
|
"\x06TurnOn\x12\x14.ha.v1.TurnOnRequest\x1a\x14.ha.v1.LightResponse\x126\n" +
|
||||||
"\aTurnOff\x12\x15.ha.v1.TurnOffRequest\x1a\x14.ha.v1.LightResponse\x124\n" +
|
"\aTurnOff\x12\x15.ha.v1.TurnOffRequest\x1a\x14.ha.v1.LightResponse\x124\n" +
|
||||||
"\x06Toggle\x12\x14.ha.v1.ToggleRequest\x1a\x14.ha.v1.LightResponseB4Z2gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1b\x06proto3"
|
"\x06Toggle\x12\x14.ha.v1.ToggleRequest\x1a\x14.ha.v1.LightResponse\x12A\n" +
|
||||||
|
"\n" +
|
||||||
|
"ListLights\x12\x18.ha.v1.ListLightsRequest\x1a\x19.ha.v1.ListLightsResponseB4Z2gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1b\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_ha_v1_light_proto_rawDescOnce sync.Once
|
file_ha_v1_light_proto_rawDescOnce sync.Once
|
||||||
@ -285,29 +481,35 @@ func file_ha_v1_light_proto_rawDescGZIP() []byte {
|
|||||||
return file_ha_v1_light_proto_rawDescData
|
return file_ha_v1_light_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_ha_v1_light_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
var file_ha_v1_light_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
var file_ha_v1_light_proto_goTypes = []any{
|
var file_ha_v1_light_proto_goTypes = []any{
|
||||||
(*TurnOnRequest)(nil), // 0: ha.v1.TurnOnRequest
|
(*TurnOnRequest)(nil), // 0: ha.v1.TurnOnRequest
|
||||||
(*TurnOffRequest)(nil), // 1: ha.v1.TurnOffRequest
|
(*TurnOffRequest)(nil), // 1: ha.v1.TurnOffRequest
|
||||||
(*ToggleRequest)(nil), // 2: ha.v1.ToggleRequest
|
(*ToggleRequest)(nil), // 2: ha.v1.ToggleRequest
|
||||||
(*LightResponse)(nil), // 3: ha.v1.LightResponse
|
(*LightResponse)(nil), // 3: ha.v1.LightResponse
|
||||||
(*RGBColor)(nil), // 4: ha.v1.RGBColor
|
(*LightEntity)(nil), // 4: ha.v1.LightEntity
|
||||||
(*EntityState)(nil), // 5: ha.v1.EntityState
|
(*ListLightsRequest)(nil), // 5: ha.v1.ListLightsRequest
|
||||||
|
(*ListLightsResponse)(nil), // 6: ha.v1.ListLightsResponse
|
||||||
|
(*RGBColor)(nil), // 7: ha.v1.RGBColor
|
||||||
|
(*EntityState)(nil), // 8: ha.v1.EntityState
|
||||||
}
|
}
|
||||||
var file_ha_v1_light_proto_depIdxs = []int32{
|
var file_ha_v1_light_proto_depIdxs = []int32{
|
||||||
4, // 0: ha.v1.TurnOnRequest.rgb_color:type_name -> ha.v1.RGBColor
|
7, // 0: ha.v1.TurnOnRequest.rgb_color:type_name -> ha.v1.RGBColor
|
||||||
5, // 1: ha.v1.LightResponse.state:type_name -> ha.v1.EntityState
|
8, // 1: ha.v1.LightResponse.state:type_name -> ha.v1.EntityState
|
||||||
0, // 2: ha.v1.LightService.TurnOn:input_type -> ha.v1.TurnOnRequest
|
4, // 2: ha.v1.ListLightsResponse.lights:type_name -> ha.v1.LightEntity
|
||||||
1, // 3: ha.v1.LightService.TurnOff:input_type -> ha.v1.TurnOffRequest
|
0, // 3: ha.v1.LightService.TurnOn:input_type -> ha.v1.TurnOnRequest
|
||||||
2, // 4: ha.v1.LightService.Toggle:input_type -> ha.v1.ToggleRequest
|
1, // 4: ha.v1.LightService.TurnOff:input_type -> ha.v1.TurnOffRequest
|
||||||
3, // 5: ha.v1.LightService.TurnOn:output_type -> ha.v1.LightResponse
|
2, // 5: ha.v1.LightService.Toggle:input_type -> ha.v1.ToggleRequest
|
||||||
3, // 6: ha.v1.LightService.TurnOff:output_type -> ha.v1.LightResponse
|
5, // 6: ha.v1.LightService.ListLights:input_type -> ha.v1.ListLightsRequest
|
||||||
3, // 7: ha.v1.LightService.Toggle:output_type -> ha.v1.LightResponse
|
3, // 7: ha.v1.LightService.TurnOn:output_type -> ha.v1.LightResponse
|
||||||
5, // [5:8] is the sub-list for method output_type
|
3, // 8: ha.v1.LightService.TurnOff:output_type -> ha.v1.LightResponse
|
||||||
2, // [2:5] is the sub-list for method input_type
|
3, // 9: ha.v1.LightService.Toggle:output_type -> ha.v1.LightResponse
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
6, // 10: ha.v1.LightService.ListLights:output_type -> ha.v1.ListLightsResponse
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
7, // [7:11] is the sub-list for method output_type
|
||||||
0, // [0:2] is the sub-list for field type_name
|
3, // [3:7] is the sub-list for method input_type
|
||||||
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_ha_v1_light_proto_init() }
|
func init() { file_ha_v1_light_proto_init() }
|
||||||
@ -324,7 +526,7 @@ func file_ha_v1_light_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ha_v1_light_proto_rawDesc), len(file_ha_v1_light_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ha_v1_light_proto_rawDesc), len(file_ha_v1_light_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 4,
|
NumMessages: 7,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const (
|
|||||||
LightService_TurnOn_FullMethodName = "/ha.v1.LightService/TurnOn"
|
LightService_TurnOn_FullMethodName = "/ha.v1.LightService/TurnOn"
|
||||||
LightService_TurnOff_FullMethodName = "/ha.v1.LightService/TurnOff"
|
LightService_TurnOff_FullMethodName = "/ha.v1.LightService/TurnOff"
|
||||||
LightService_Toggle_FullMethodName = "/ha.v1.LightService/Toggle"
|
LightService_Toggle_FullMethodName = "/ha.v1.LightService/Toggle"
|
||||||
|
LightService_ListLights_FullMethodName = "/ha.v1.LightService/ListLights"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LightServiceClient is the client API for LightService service.
|
// LightServiceClient is the client API for LightService service.
|
||||||
@ -31,6 +32,7 @@ type LightServiceClient interface {
|
|||||||
TurnOn(ctx context.Context, in *TurnOnRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
TurnOn(ctx context.Context, in *TurnOnRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
||||||
TurnOff(ctx context.Context, in *TurnOffRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
TurnOff(ctx context.Context, in *TurnOffRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
||||||
Toggle(ctx context.Context, in *ToggleRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
Toggle(ctx context.Context, in *ToggleRequest, opts ...grpc.CallOption) (*LightResponse, error)
|
||||||
|
ListLights(ctx context.Context, in *ListLightsRequest, opts ...grpc.CallOption) (*ListLightsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type lightServiceClient struct {
|
type lightServiceClient struct {
|
||||||
@ -71,6 +73,16 @@ func (c *lightServiceClient) Toggle(ctx context.Context, in *ToggleRequest, opts
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *lightServiceClient) ListLights(ctx context.Context, in *ListLightsRequest, opts ...grpc.CallOption) (*ListLightsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListLightsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, LightService_ListLights_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LightServiceServer is the server API for LightService service.
|
// LightServiceServer is the server API for LightService service.
|
||||||
// All implementations must embed UnimplementedLightServiceServer
|
// All implementations must embed UnimplementedLightServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@ -78,6 +90,7 @@ type LightServiceServer interface {
|
|||||||
TurnOn(context.Context, *TurnOnRequest) (*LightResponse, error)
|
TurnOn(context.Context, *TurnOnRequest) (*LightResponse, error)
|
||||||
TurnOff(context.Context, *TurnOffRequest) (*LightResponse, error)
|
TurnOff(context.Context, *TurnOffRequest) (*LightResponse, error)
|
||||||
Toggle(context.Context, *ToggleRequest) (*LightResponse, error)
|
Toggle(context.Context, *ToggleRequest) (*LightResponse, error)
|
||||||
|
ListLights(context.Context, *ListLightsRequest) (*ListLightsResponse, error)
|
||||||
mustEmbedUnimplementedLightServiceServer()
|
mustEmbedUnimplementedLightServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +110,9 @@ func (UnimplementedLightServiceServer) TurnOff(context.Context, *TurnOffRequest)
|
|||||||
func (UnimplementedLightServiceServer) Toggle(context.Context, *ToggleRequest) (*LightResponse, error) {
|
func (UnimplementedLightServiceServer) Toggle(context.Context, *ToggleRequest) (*LightResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method Toggle not implemented")
|
return nil, status.Error(codes.Unimplemented, "method Toggle not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedLightServiceServer) ListLights(context.Context, *ListLightsRequest) (*ListLightsResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListLights not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedLightServiceServer) mustEmbedUnimplementedLightServiceServer() {}
|
func (UnimplementedLightServiceServer) mustEmbedUnimplementedLightServiceServer() {}
|
||||||
func (UnimplementedLightServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedLightServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -172,6 +188,24 @@ func _LightService_Toggle_Handler(srv interface{}, ctx context.Context, dec func
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _LightService_ListLights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListLightsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(LightServiceServer).ListLights(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: LightService_ListLights_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(LightServiceServer).ListLights(ctx, req.(*ListLightsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// LightService_ServiceDesc is the grpc.ServiceDesc for LightService service.
|
// LightService_ServiceDesc is the grpc.ServiceDesc for LightService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -191,6 +225,10 @@ var LightService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Toggle",
|
MethodName: "Toggle",
|
||||||
Handler: _LightService_Toggle_Handler,
|
Handler: _LightService_Toggle_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListLights",
|
||||||
|
Handler: _LightService_ListLights_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "ha/v1/light.proto",
|
Metadata: "ha/v1/light.proto",
|
||||||
|
|||||||
@ -109,6 +109,154 @@ func (x *SwitchResponse) GetState() *EntityState {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SwitchEntity struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
|
||||||
|
FriendlyName string `protobuf:"bytes,2,opt,name=friendly_name,json=friendlyName,proto3" json:"friendly_name,omitempty"`
|
||||||
|
State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"`
|
||||||
|
DeviceClass string `protobuf:"bytes,4,opt,name=device_class,json=deviceClass,proto3" json:"device_class,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) Reset() {
|
||||||
|
*x = SwitchEntity{}
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SwitchEntity) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SwitchEntity.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SwitchEntity) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_switch_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) GetEntityId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EntityId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) GetFriendlyName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.FriendlyName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) GetState() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.State
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SwitchEntity) GetDeviceClass() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.DeviceClass
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListSwitchesRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListSwitchesRequest) Reset() {
|
||||||
|
*x = ListSwitchesRequest{}
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListSwitchesRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListSwitchesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListSwitchesRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListSwitchesRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListSwitchesRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_switch_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListSwitchesResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Switches []*SwitchEntity `protobuf:"bytes,1,rep,name=switches,proto3" json:"switches,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListSwitchesResponse) Reset() {
|
||||||
|
*x = ListSwitchesResponse{}
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListSwitchesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListSwitchesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListSwitchesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_ha_v1_switch_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListSwitchesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListSwitchesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_ha_v1_switch_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListSwitchesResponse) GetSwitches() []*SwitchEntity {
|
||||||
|
if x != nil {
|
||||||
|
return x.Switches
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_ha_v1_switch_proto protoreflect.FileDescriptor
|
var File_ha_v1_switch_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_ha_v1_switch_proto_rawDesc = "" +
|
const file_ha_v1_switch_proto_rawDesc = "" +
|
||||||
@ -117,11 +265,20 @@ const file_ha_v1_switch_proto_rawDesc = "" +
|
|||||||
"\rSwitchRequest\x12\x1b\n" +
|
"\rSwitchRequest\x12\x1b\n" +
|
||||||
"\tentity_id\x18\x01 \x01(\tR\bentityId\":\n" +
|
"\tentity_id\x18\x01 \x01(\tR\bentityId\":\n" +
|
||||||
"\x0eSwitchResponse\x12(\n" +
|
"\x0eSwitchResponse\x12(\n" +
|
||||||
"\x05state\x18\x01 \x01(\v2\x12.ha.v1.EntityStateR\x05state2\xb5\x01\n" +
|
"\x05state\x18\x01 \x01(\v2\x12.ha.v1.EntityStateR\x05state\"\x89\x01\n" +
|
||||||
|
"\fSwitchEntity\x12\x1b\n" +
|
||||||
|
"\tentity_id\x18\x01 \x01(\tR\bentityId\x12#\n" +
|
||||||
|
"\rfriendly_name\x18\x02 \x01(\tR\ffriendlyName\x12\x14\n" +
|
||||||
|
"\x05state\x18\x03 \x01(\tR\x05state\x12!\n" +
|
||||||
|
"\fdevice_class\x18\x04 \x01(\tR\vdeviceClass\"\x15\n" +
|
||||||
|
"\x13ListSwitchesRequest\"G\n" +
|
||||||
|
"\x14ListSwitchesResponse\x12/\n" +
|
||||||
|
"\bswitches\x18\x01 \x03(\v2\x13.ha.v1.SwitchEntityR\bswitches2\xfe\x01\n" +
|
||||||
"\rSwitchService\x125\n" +
|
"\rSwitchService\x125\n" +
|
||||||
"\x06TurnOn\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponse\x126\n" +
|
"\x06TurnOn\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponse\x126\n" +
|
||||||
"\aTurnOff\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponse\x125\n" +
|
"\aTurnOff\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponse\x125\n" +
|
||||||
"\x06Toggle\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponseB4Z2gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1b\x06proto3"
|
"\x06Toggle\x12\x14.ha.v1.SwitchRequest\x1a\x15.ha.v1.SwitchResponse\x12G\n" +
|
||||||
|
"\fListSwitches\x12\x1a.ha.v1.ListSwitchesRequest\x1a\x1b.ha.v1.ListSwitchesResponseB4Z2gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1b\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_ha_v1_switch_proto_rawDescOnce sync.Once
|
file_ha_v1_switch_proto_rawDescOnce sync.Once
|
||||||
@ -135,25 +292,31 @@ func file_ha_v1_switch_proto_rawDescGZIP() []byte {
|
|||||||
return file_ha_v1_switch_proto_rawDescData
|
return file_ha_v1_switch_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_ha_v1_switch_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
var file_ha_v1_switch_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_ha_v1_switch_proto_goTypes = []any{
|
var file_ha_v1_switch_proto_goTypes = []any{
|
||||||
(*SwitchRequest)(nil), // 0: ha.v1.SwitchRequest
|
(*SwitchRequest)(nil), // 0: ha.v1.SwitchRequest
|
||||||
(*SwitchResponse)(nil), // 1: ha.v1.SwitchResponse
|
(*SwitchResponse)(nil), // 1: ha.v1.SwitchResponse
|
||||||
(*EntityState)(nil), // 2: ha.v1.EntityState
|
(*SwitchEntity)(nil), // 2: ha.v1.SwitchEntity
|
||||||
|
(*ListSwitchesRequest)(nil), // 3: ha.v1.ListSwitchesRequest
|
||||||
|
(*ListSwitchesResponse)(nil), // 4: ha.v1.ListSwitchesResponse
|
||||||
|
(*EntityState)(nil), // 5: ha.v1.EntityState
|
||||||
}
|
}
|
||||||
var file_ha_v1_switch_proto_depIdxs = []int32{
|
var file_ha_v1_switch_proto_depIdxs = []int32{
|
||||||
2, // 0: ha.v1.SwitchResponse.state:type_name -> ha.v1.EntityState
|
5, // 0: ha.v1.SwitchResponse.state:type_name -> ha.v1.EntityState
|
||||||
0, // 1: ha.v1.SwitchService.TurnOn:input_type -> ha.v1.SwitchRequest
|
2, // 1: ha.v1.ListSwitchesResponse.switches:type_name -> ha.v1.SwitchEntity
|
||||||
0, // 2: ha.v1.SwitchService.TurnOff:input_type -> ha.v1.SwitchRequest
|
0, // 2: ha.v1.SwitchService.TurnOn:input_type -> ha.v1.SwitchRequest
|
||||||
0, // 3: ha.v1.SwitchService.Toggle:input_type -> ha.v1.SwitchRequest
|
0, // 3: ha.v1.SwitchService.TurnOff:input_type -> ha.v1.SwitchRequest
|
||||||
1, // 4: ha.v1.SwitchService.TurnOn:output_type -> ha.v1.SwitchResponse
|
0, // 4: ha.v1.SwitchService.Toggle:input_type -> ha.v1.SwitchRequest
|
||||||
1, // 5: ha.v1.SwitchService.TurnOff:output_type -> ha.v1.SwitchResponse
|
3, // 5: ha.v1.SwitchService.ListSwitches:input_type -> ha.v1.ListSwitchesRequest
|
||||||
1, // 6: ha.v1.SwitchService.Toggle:output_type -> ha.v1.SwitchResponse
|
1, // 6: ha.v1.SwitchService.TurnOn:output_type -> ha.v1.SwitchResponse
|
||||||
4, // [4:7] is the sub-list for method output_type
|
1, // 7: ha.v1.SwitchService.TurnOff:output_type -> ha.v1.SwitchResponse
|
||||||
1, // [1:4] is the sub-list for method input_type
|
1, // 8: ha.v1.SwitchService.Toggle:output_type -> ha.v1.SwitchResponse
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
4, // 9: ha.v1.SwitchService.ListSwitches:output_type -> ha.v1.ListSwitchesResponse
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
6, // [6:10] is the sub-list for method output_type
|
||||||
0, // [0:1] is the sub-list for field type_name
|
2, // [2:6] is the sub-list for method input_type
|
||||||
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_ha_v1_switch_proto_init() }
|
func init() { file_ha_v1_switch_proto_init() }
|
||||||
@ -168,7 +331,7 @@ func file_ha_v1_switch_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ha_v1_switch_proto_rawDesc), len(file_ha_v1_switch_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ha_v1_switch_proto_rawDesc), len(file_ha_v1_switch_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 2,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,21 +22,17 @@ const (
|
|||||||
SwitchService_TurnOn_FullMethodName = "/ha.v1.SwitchService/TurnOn"
|
SwitchService_TurnOn_FullMethodName = "/ha.v1.SwitchService/TurnOn"
|
||||||
SwitchService_TurnOff_FullMethodName = "/ha.v1.SwitchService/TurnOff"
|
SwitchService_TurnOff_FullMethodName = "/ha.v1.SwitchService/TurnOff"
|
||||||
SwitchService_Toggle_FullMethodName = "/ha.v1.SwitchService/Toggle"
|
SwitchService_Toggle_FullMethodName = "/ha.v1.SwitchService/Toggle"
|
||||||
|
SwitchService_ListSwitches_FullMethodName = "/ha.v1.SwitchService/ListSwitches"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SwitchServiceClient is the client API for SwitchService service.
|
// SwitchServiceClient is the client API for SwitchService service.
|
||||||
//
|
//
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
//
|
|
||||||
// TODO: implement SwitchService. Follow the same pattern as LightService:
|
|
||||||
// - domain type in core/domain/switch.go
|
|
||||||
// - port interface in core/ports/driving/switch.go
|
|
||||||
// - application logic in app/switch.go
|
|
||||||
// - gRPC adapter in adapters/primary/grpc/switch.go
|
|
||||||
type SwitchServiceClient interface {
|
type SwitchServiceClient interface {
|
||||||
TurnOn(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
TurnOn(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
||||||
TurnOff(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
TurnOff(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
||||||
Toggle(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
Toggle(ctx context.Context, in *SwitchRequest, opts ...grpc.CallOption) (*SwitchResponse, error)
|
||||||
|
ListSwitches(ctx context.Context, in *ListSwitchesRequest, opts ...grpc.CallOption) (*ListSwitchesResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type switchServiceClient struct {
|
type switchServiceClient struct {
|
||||||
@ -77,19 +73,24 @@ func (c *switchServiceClient) Toggle(ctx context.Context, in *SwitchRequest, opt
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *switchServiceClient) ListSwitches(ctx context.Context, in *ListSwitchesRequest, opts ...grpc.CallOption) (*ListSwitchesResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListSwitchesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, SwitchService_ListSwitches_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SwitchServiceServer is the server API for SwitchService service.
|
// SwitchServiceServer is the server API for SwitchService service.
|
||||||
// All implementations must embed UnimplementedSwitchServiceServer
|
// All implementations must embed UnimplementedSwitchServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
//
|
|
||||||
// TODO: implement SwitchService. Follow the same pattern as LightService:
|
|
||||||
// - domain type in core/domain/switch.go
|
|
||||||
// - port interface in core/ports/driving/switch.go
|
|
||||||
// - application logic in app/switch.go
|
|
||||||
// - gRPC adapter in adapters/primary/grpc/switch.go
|
|
||||||
type SwitchServiceServer interface {
|
type SwitchServiceServer interface {
|
||||||
TurnOn(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
TurnOn(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
||||||
TurnOff(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
TurnOff(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
||||||
Toggle(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
Toggle(context.Context, *SwitchRequest) (*SwitchResponse, error)
|
||||||
|
ListSwitches(context.Context, *ListSwitchesRequest) (*ListSwitchesResponse, error)
|
||||||
mustEmbedUnimplementedSwitchServiceServer()
|
mustEmbedUnimplementedSwitchServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +110,9 @@ func (UnimplementedSwitchServiceServer) TurnOff(context.Context, *SwitchRequest)
|
|||||||
func (UnimplementedSwitchServiceServer) Toggle(context.Context, *SwitchRequest) (*SwitchResponse, error) {
|
func (UnimplementedSwitchServiceServer) Toggle(context.Context, *SwitchRequest) (*SwitchResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method Toggle not implemented")
|
return nil, status.Error(codes.Unimplemented, "method Toggle not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedSwitchServiceServer) ListSwitches(context.Context, *ListSwitchesRequest) (*ListSwitchesResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListSwitches not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedSwitchServiceServer) mustEmbedUnimplementedSwitchServiceServer() {}
|
func (UnimplementedSwitchServiceServer) mustEmbedUnimplementedSwitchServiceServer() {}
|
||||||
func (UnimplementedSwitchServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedSwitchServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -184,6 +188,24 @@ func _SwitchService_Toggle_Handler(srv interface{}, ctx context.Context, dec fun
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _SwitchService_ListSwitches_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListSwitchesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(SwitchServiceServer).ListSwitches(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: SwitchService_ListSwitches_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(SwitchServiceServer).ListSwitches(ctx, req.(*ListSwitchesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// SwitchService_ServiceDesc is the grpc.ServiceDesc for SwitchService service.
|
// SwitchService_ServiceDesc is the grpc.ServiceDesc for SwitchService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -203,6 +225,10 @@ var SwitchService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Toggle",
|
MethodName: "Toggle",
|
||||||
Handler: _SwitchService_Toggle_Handler,
|
Handler: _SwitchService_Toggle_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListSwitches",
|
||||||
|
Handler: _SwitchService_ListSwitches_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "ha/v1/switch.proto",
|
Metadata: "ha/v1/switch.proto",
|
||||||
|
|||||||
33
go.work.sum
33
go.work.sum
@ -4,12 +4,10 @@ cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1F
|
|||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||||
@ -19,38 +17,26 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP
|
|||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||||
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
|
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
|
||||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
@ -59,11 +45,10 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
@ -72,13 +57,13 @@ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||||
@ -89,4 +74,4 @@ google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwl
|
|||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/peer"
|
"google.golang.org/grpc/peer"
|
||||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
|
||||||
|
|
||||||
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/secondary/ha"
|
|
||||||
grpcadapter "gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/primary/grpc"
|
grpcadapter "gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/primary/grpc"
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/secondary/ha"
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/app"
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/app"
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/config"
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/config"
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/telemetry"
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/telemetry"
|
||||||
@ -61,6 +61,16 @@ func main() {
|
|||||||
// 5. Build application services.
|
// 5. Build application services.
|
||||||
entityApp := app.NewEntityApp(haClient)
|
entityApp := app.NewEntityApp(haClient)
|
||||||
lightApp := app.NewLightApp(haClient)
|
lightApp := app.NewLightApp(haClient)
|
||||||
|
switchApp := app.NewSwitchApp(haClient)
|
||||||
|
|
||||||
|
// 5a. Prime the discovery cache. Non-fatal — ListLights/ListSwitches will
|
||||||
|
// retry lazily on the first call if this fails (e.g. HA not yet ready).
|
||||||
|
if err := lightApp.Refresh(ctx); err != nil {
|
||||||
|
slog.Warn("initial light discovery failed, will retry on first request", "err", err)
|
||||||
|
}
|
||||||
|
if err := switchApp.Refresh(ctx); err != nil {
|
||||||
|
slog.Warn("initial switch discovery failed, will retry on first request", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Build the gRPC server with OTel stats handler and logging interceptors.
|
// 6. Build the gRPC server with OTel stats handler and logging interceptors.
|
||||||
srv := grpc.NewServer(
|
srv := grpc.NewServer(
|
||||||
@ -72,7 +82,7 @@ func main() {
|
|||||||
// 7. Register services.
|
// 7. Register services.
|
||||||
hav1.RegisterEntityServiceServer(srv, grpcadapter.NewEntityGRPC(entityApp))
|
hav1.RegisterEntityServiceServer(srv, grpcadapter.NewEntityGRPC(entityApp))
|
||||||
hav1.RegisterLightServiceServer(srv, grpcadapter.NewLightGRPC(lightApp))
|
hav1.RegisterLightServiceServer(srv, grpcadapter.NewLightGRPC(lightApp))
|
||||||
hav1.RegisterSwitchServiceServer(srv, &grpcadapter.SwitchGRPC{})
|
hav1.RegisterSwitchServiceServer(srv, grpcadapter.NewSwitchGRPC(switchApp))
|
||||||
hav1.RegisterEventServiceServer(srv, &grpcadapter.EventGRPC{})
|
hav1.RegisterEventServiceServer(srv, &grpcadapter.EventGRPC{})
|
||||||
|
|
||||||
// 8. Start listener.
|
// 8. Start listener.
|
||||||
|
|||||||
@ -52,8 +52,14 @@ func grpcError(err error) error {
|
|||||||
if errors.Is(err, ErrNotFound) {
|
if errors.Is(err, ErrNotFound) {
|
||||||
return status.Errorf(codes.NotFound, "%v", err)
|
return status.Errorf(codes.NotFound, "%v", err)
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, ErrNotImplemented) {
|
||||||
|
return status.Errorf(codes.Unimplemented, "%v", err)
|
||||||
|
}
|
||||||
return status.Errorf(codes.Internal, "%v", err)
|
return status.Errorf(codes.Internal, "%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNotFound is returned by the app layer when an entity does not exist.
|
// ErrNotFound is returned by the app layer when an entity does not exist.
|
||||||
var ErrNotFound = errors.New("not found")
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
// ErrNotImplemented is returned by handlers that are stubbed pending full implementation.
|
||||||
|
var ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|||||||
@ -40,3 +40,15 @@ func (h *LightGRPC) Toggle(ctx context.Context, req *hav1.ToggleRequest) (*hav1.
|
|||||||
}
|
}
|
||||||
return &hav1.LightResponse{State: domainStateToProto(s)}, nil
|
return &hav1.LightResponse{State: domainStateToProto(s)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *LightGRPC) ListLights(ctx context.Context, req *hav1.ListLightsRequest) (*hav1.ListLightsResponse, error) {
|
||||||
|
lights, err := h.svc.ListLights(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, grpcError(err)
|
||||||
|
}
|
||||||
|
out := make([]*hav1.LightEntity, 0, len(lights))
|
||||||
|
for _, l := range lights {
|
||||||
|
out = append(out, domainLightToProto(l))
|
||||||
|
}
|
||||||
|
return &hav1.ListLightsResponse{Lights: out}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
|
||||||
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func domainStateToProto(s *domain.EntityState) *hav1.EntityState {
|
func domainStateToProto(s *domain.EntityState) *hav1.EntityState {
|
||||||
@ -51,3 +51,29 @@ func protoTurnOffToParams(r *hav1.TurnOffRequest) domain.TurnOffParams {
|
|||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func domainLightToProto(l domain.Light) *hav1.LightEntity {
|
||||||
|
modes := make([]string, len(l.SupportedColorModes))
|
||||||
|
for i, m := range l.SupportedColorModes {
|
||||||
|
modes[i] = string(m)
|
||||||
|
}
|
||||||
|
return &hav1.LightEntity{
|
||||||
|
EntityId: string(l.EntityID),
|
||||||
|
FriendlyName: l.FriendlyName,
|
||||||
|
State: l.State,
|
||||||
|
SupportedColorModes: modes,
|
||||||
|
MinColorTempKelvin: l.MinColorTempKelvin,
|
||||||
|
MaxColorTempKelvin: l.MaxColorTempKelvin,
|
||||||
|
IsHueGroup: l.IsHueGroup,
|
||||||
|
EffectList: l.EffectList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainSwitchToProto(s domain.Switch) *hav1.SwitchEntity {
|
||||||
|
return &hav1.SwitchEntity{
|
||||||
|
EntityId: string(s.EntityID),
|
||||||
|
FriendlyName: s.FriendlyName,
|
||||||
|
State: s.State,
|
||||||
|
DeviceClass: s.DeviceClass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,44 @@
|
|||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driving"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SwitchGRPC struct {
|
type SwitchGRPC struct {
|
||||||
hav1.UnimplementedSwitchServiceServer
|
hav1.UnimplementedSwitchServiceServer
|
||||||
|
svc driving.SwitchService
|
||||||
}
|
}
|
||||||
|
|
||||||
// All methods return codes.Unimplemented via the embedded UnimplementedSwitchServiceServer.
|
func NewSwitchGRPC(svc driving.SwitchService) *SwitchGRPC {
|
||||||
// TODO: follow the same pattern as LightGRPC once app/switch.go is implemented.
|
return &SwitchGRPC{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwitchGRPC) ListSwitches(ctx context.Context, req *hav1.ListSwitchesRequest) (*hav1.ListSwitchesResponse, error) {
|
||||||
|
switches, err := h.svc.ListSwitches(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, grpcError(err)
|
||||||
|
}
|
||||||
|
out := make([]*hav1.SwitchEntity, 0, len(switches))
|
||||||
|
for _, s := range switches {
|
||||||
|
out = append(out, domainSwitchToProto(s))
|
||||||
|
}
|
||||||
|
return &hav1.ListSwitchesResponse{Switches: out}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TurnOn, TurnOff, Toggle — left as Unimplemented for now.
|
||||||
|
// TODO: implement once app/switch.go has callService support.
|
||||||
|
// Follow the same pattern as LightGRPC: payload{"entity_id": ...} → ha.CallService.
|
||||||
|
func (h *SwitchGRPC) TurnOn(ctx context.Context, req *hav1.SwitchRequest) (*hav1.SwitchResponse, error) {
|
||||||
|
return nil, grpcError(ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwitchGRPC) TurnOff(ctx context.Context, req *hav1.SwitchRequest) (*hav1.SwitchResponse, error) {
|
||||||
|
return nil, grpcError(ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwitchGRPC) Toggle(ctx context.Context, req *hav1.SwitchRequest) (*hav1.SwitchResponse, error) {
|
||||||
|
return nil, grpcError(ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
||||||
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driven"
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driven"
|
||||||
@ -9,12 +11,49 @@ import (
|
|||||||
|
|
||||||
type LightApp struct {
|
type LightApp struct {
|
||||||
ha driven.HAClient
|
ha driven.HAClient
|
||||||
|
mu sync.RWMutex
|
||||||
|
cache []domain.Light
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightApp(ha driven.HAClient) *LightApp {
|
func NewLightApp(ha driven.HAClient) *LightApp {
|
||||||
return &LightApp{ha: ha}
|
return &LightApp{ha: ha}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *LightApp) Refresh(ctx context.Context) error {
|
||||||
|
all, err := a.ha.ListStates(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lights []domain.Light
|
||||||
|
for _, s := range all {
|
||||||
|
if !strings.HasPrefix(s.EntityID, "light.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lights = append(lights, haStateToLight(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
a.mu.Lock()
|
||||||
|
a.cache = lights
|
||||||
|
a.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LightApp) ListLights(ctx context.Context) ([]domain.Light, error) {
|
||||||
|
a.mu.RLock()
|
||||||
|
c := a.cache
|
||||||
|
a.mu.RUnlock()
|
||||||
|
if c == nil {
|
||||||
|
if err := a.Refresh(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.mu.RLock()
|
||||||
|
c = a.cache
|
||||||
|
a.mu.RUnlock()
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *LightApp) TurnOn(ctx context.Context, p domain.TurnOnParams) (*domain.EntityState, error) {
|
func (a *LightApp) TurnOn(ctx context.Context, p domain.TurnOnParams) (*domain.EntityState, error) {
|
||||||
payload := map[string]any{"entity_id": string(p.EntityID)}
|
payload := map[string]any{"entity_id": string(p.EntityID)}
|
||||||
if p.BrightnessPct != nil {
|
if p.BrightnessPct != nil {
|
||||||
@ -63,3 +102,39 @@ func (a *LightApp) callService(ctx context.Context, svcDomain, service string, p
|
|||||||
}
|
}
|
||||||
return haStateToDomain(s), nil
|
return haStateToDomain(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func haStateToLight(s *driven.HAState) domain.Light {
|
||||||
|
l := domain.Light{
|
||||||
|
EntityID: domain.EntityID(s.EntityID),
|
||||||
|
State: s.State,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := s.Attributes["friendly_name"].(string); ok {
|
||||||
|
l.FriendlyName = v
|
||||||
|
}
|
||||||
|
if v, ok := s.Attributes["is_hue_group"].(bool); ok {
|
||||||
|
l.IsHueGroup = v
|
||||||
|
}
|
||||||
|
if v, ok := s.Attributes["min_color_temp_kelvin"].(float64); ok {
|
||||||
|
l.MinColorTempKelvin = uint32(v)
|
||||||
|
}
|
||||||
|
if v, ok := s.Attributes["max_color_temp_kelvin"].(float64); ok {
|
||||||
|
l.MaxColorTempKelvin = uint32(v)
|
||||||
|
}
|
||||||
|
if modes, ok := s.Attributes["supported_color_modes"].([]any); ok {
|
||||||
|
for _, m := range modes {
|
||||||
|
if ms, ok := m.(string); ok {
|
||||||
|
l.SupportedColorModes = append(l.SupportedColorModes, domain.ColorMode(ms))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if effects, ok := s.Attributes["effect_list"].([]any); ok {
|
||||||
|
for _, e := range effects {
|
||||||
|
if es, ok := e.(string); ok {
|
||||||
|
l.EffectList = append(l.EffectList, es)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|||||||
69
ha-gateway/internal/app/switch.go
Normal file
69
ha-gateway/internal/app/switch.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driven"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SwitchApp struct {
|
||||||
|
ha driven.HAClient
|
||||||
|
mu sync.RWMutex
|
||||||
|
cache []domain.Switch
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSwitchApp(ha driven.HAClient) *SwitchApp {
|
||||||
|
return &SwitchApp{ha: ha}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SwitchApp) Refresh(ctx context.Context) error {
|
||||||
|
all, err := a.ha.ListStates(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var switches []domain.Switch
|
||||||
|
for _, s := range all {
|
||||||
|
if !strings.HasPrefix(s.EntityID, "switch.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switches = append(switches, haStateToSwitch(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
a.mu.Lock()
|
||||||
|
a.cache = switches
|
||||||
|
a.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SwitchApp) ListSwitches(ctx context.Context) ([]domain.Switch, error) {
|
||||||
|
a.mu.RLock()
|
||||||
|
c := a.cache
|
||||||
|
a.mu.RUnlock()
|
||||||
|
if c == nil {
|
||||||
|
if err := a.Refresh(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.mu.RLock()
|
||||||
|
c = a.cache
|
||||||
|
a.mu.RUnlock()
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func haStateToSwitch(s *driven.HAState) domain.Switch {
|
||||||
|
sw := domain.Switch{
|
||||||
|
EntityID: domain.EntityID(s.EntityID),
|
||||||
|
State: s.State,
|
||||||
|
}
|
||||||
|
if v, ok := s.Attributes["friendly_name"].(string); ok {
|
||||||
|
sw.FriendlyName = v
|
||||||
|
}
|
||||||
|
if v, ok := s.Attributes["device_class"].(string); ok {
|
||||||
|
sw.DeviceClass = v
|
||||||
|
}
|
||||||
|
return sw
|
||||||
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type EntityID string
|
type EntityID string
|
||||||
|
|
||||||
@ -11,3 +14,5 @@ type EntityState struct {
|
|||||||
LastChanged time.Time
|
LastChanged time.Time
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|||||||
@ -1,8 +1,28 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
type ColorMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorModeColorTemp ColorMode = "color_temp"
|
||||||
|
ColorModeHS ColorMode = "hs"
|
||||||
|
ColorModeXY ColorMode = "xy"
|
||||||
|
ColorModeBrightness ColorMode = "brightness"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Light struct {
|
||||||
|
EntityID EntityID
|
||||||
|
FriendlyName string
|
||||||
|
State string // "on" | "off" | "unavailable"
|
||||||
|
SupportedColorModes []ColorMode
|
||||||
|
MinColorTempKelvin uint32
|
||||||
|
MaxColorTempKelvin uint32
|
||||||
|
IsHueGroup bool
|
||||||
|
EffectList []string
|
||||||
|
}
|
||||||
|
|
||||||
type TurnOnParams struct {
|
type TurnOnParams struct {
|
||||||
EntityID EntityID
|
EntityID EntityID
|
||||||
BrightnessPct *uint32 // nil = not set
|
BrightnessPct *uint32
|
||||||
ColorTempKelvin *uint32
|
ColorTempKelvin *uint32
|
||||||
RGBColor *RGBColor
|
RGBColor *RGBColor
|
||||||
Transition *uint32
|
Transition *uint32
|
||||||
|
|||||||
8
ha-gateway/internal/core/domain/switch.go
Normal file
8
ha-gateway/internal/core/domain/switch.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type Switch struct {
|
||||||
|
EntityID EntityID
|
||||||
|
FriendlyName string
|
||||||
|
State string // "on" | "off" | "unavailable"
|
||||||
|
DeviceClass string // e.g. "switch"
|
||||||
|
}
|
||||||
@ -10,4 +10,6 @@ type LightService interface {
|
|||||||
TurnOn(ctx context.Context, p domain.TurnOnParams) (*domain.EntityState, error)
|
TurnOn(ctx context.Context, p domain.TurnOnParams) (*domain.EntityState, error)
|
||||||
TurnOff(ctx context.Context, p domain.TurnOffParams) (*domain.EntityState, error)
|
TurnOff(ctx context.Context, p domain.TurnOffParams) (*domain.EntityState, error)
|
||||||
Toggle(ctx context.Context, id domain.EntityID) (*domain.EntityState, error)
|
Toggle(ctx context.Context, id domain.EntityID) (*domain.EntityState, error)
|
||||||
|
ListLights(ctx context.Context) ([]domain.Light, error)
|
||||||
|
Refresh(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|||||||
12
ha-gateway/internal/core/ports/driving/switch.go
Normal file
12
ha-gateway/internal/core/ports/driving/switch.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package driving
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SwitchService interface {
|
||||||
|
ListSwitches(ctx context.Context) ([]domain.Switch, error)
|
||||||
|
Refresh(ctx context.Context) error
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ service LightService {
|
|||||||
rpc TurnOn(TurnOnRequest) returns (LightResponse);
|
rpc TurnOn(TurnOnRequest) returns (LightResponse);
|
||||||
rpc TurnOff(TurnOffRequest) returns (LightResponse);
|
rpc TurnOff(TurnOffRequest) returns (LightResponse);
|
||||||
rpc Toggle(ToggleRequest) returns (LightResponse);
|
rpc Toggle(ToggleRequest) returns (LightResponse);
|
||||||
|
rpc ListLights(ListLightsRequest) returns (ListLightsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional fields require protobuf 3.15+ / buf >= 1.0. They generate
|
// optional fields require protobuf 3.15+ / buf >= 1.0. They generate
|
||||||
@ -25,3 +26,19 @@ message TurnOffRequest {
|
|||||||
}
|
}
|
||||||
message ToggleRequest { string entity_id = 1; }
|
message ToggleRequest { string entity_id = 1; }
|
||||||
message LightResponse { EntityState state = 1; }
|
message LightResponse { EntityState state = 1; }
|
||||||
|
message LightEntity {
|
||||||
|
string entity_id = 1;
|
||||||
|
string friendly_name = 2;
|
||||||
|
string state = 3;
|
||||||
|
repeated string supported_color_modes = 4;
|
||||||
|
uint32 min_color_temp_kelvin = 5;
|
||||||
|
uint32 max_color_temp_kelvin = 6;
|
||||||
|
bool is_hue_group = 7;
|
||||||
|
repeated string effect_list = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListLightsRequest {}
|
||||||
|
|
||||||
|
message ListLightsResponse {
|
||||||
|
repeated LightEntity lights = 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,16 +3,25 @@ package ha.v1;
|
|||||||
option go_package = "gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1";
|
option go_package = "gitea.nik4nao.com/nik/home-services/gen/ha/v1;hav1";
|
||||||
import "ha/v1/common.proto";
|
import "ha/v1/common.proto";
|
||||||
|
|
||||||
// TODO: implement SwitchService. Follow the same pattern as LightService:
|
|
||||||
// - domain type in core/domain/switch.go
|
|
||||||
// - port interface in core/ports/driving/switch.go
|
|
||||||
// - application logic in app/switch.go
|
|
||||||
// - gRPC adapter in adapters/primary/grpc/switch.go
|
|
||||||
service SwitchService {
|
service SwitchService {
|
||||||
rpc TurnOn(SwitchRequest) returns (SwitchResponse);
|
rpc TurnOn(SwitchRequest) returns (SwitchResponse);
|
||||||
rpc TurnOff(SwitchRequest) returns (SwitchResponse);
|
rpc TurnOff(SwitchRequest) returns (SwitchResponse);
|
||||||
rpc Toggle(SwitchRequest) returns (SwitchResponse);
|
rpc Toggle(SwitchRequest) returns (SwitchResponse);
|
||||||
|
rpc ListSwitches(ListSwitchesRequest) returns (ListSwitchesResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message SwitchRequest { string entity_id = 1; }
|
message SwitchRequest { string entity_id = 1; }
|
||||||
message SwitchResponse { EntityState state = 1; }
|
message SwitchResponse { EntityState state = 1; }
|
||||||
|
|
||||||
|
message SwitchEntity {
|
||||||
|
string entity_id = 1;
|
||||||
|
string friendly_name = 2;
|
||||||
|
string state = 3;
|
||||||
|
string device_class = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListSwitchesRequest {}
|
||||||
|
|
||||||
|
message ListSwitchesResponse {
|
||||||
|
repeated SwitchEntity switches = 1;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user