Nik Afiq 94ab7ea42b feat(discord-bot): implement Discord command handler and register commands for light and switch control
- Added command handler for processing Discord interactions related to lights and switches.
- Implemented command registration for light control commands: list, on, off, and toggle.
- Created a gRPC client for communicating with the home automation gateway.
- Developed application logic for handling light and switch commands, including listing, turning on/off, and toggling lights.
- Introduced telemetry setup for OpenTelemetry integration.
- Added configuration loading for Discord token, gateway address, and OpenTelemetry endpoint.
- Defined core driven ports for interacting with the home automation gateway.
2026-04-06 20:13:15 +09:00

117 lines
3.4 KiB
Go

package gateway
import (
"context"
"fmt"
"gitea.nik4nao.com/nik/home-services/discord-bot/internal/core/ports/driven"
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type Client struct {
conn *grpc.ClientConn
lightClient hav1.LightServiceClient
switchClient hav1.SwitchServiceClient
}
func New(ctx context.Context, addr string) (*Client, error) {
conn, err := grpc.NewClient(
addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
if err != nil {
return nil, fmt.Errorf("dial ha-gateway: %w", err)
}
return &Client{
conn: conn,
lightClient: hav1.NewLightServiceClient(conn),
switchClient: hav1.NewSwitchServiceClient(conn),
}, nil
}
func (c *Client) Close() error {
if err := c.conn.Close(); err != nil {
return fmt.Errorf("close ha-gateway client: %w", err)
}
return nil
}
func (c *Client) ListLights(ctx context.Context) ([]driven.Light, error) {
resp, err := c.lightClient.ListLights(ctx, &hav1.ListLightsRequest{})
if err != nil {
return nil, fmt.Errorf("list lights: %w", err)
}
lights := make([]driven.Light, 0, len(resp.GetLights()))
for _, light := range resp.GetLights() {
lights = append(lights, driven.Light{
EntityID: light.GetEntityId(),
FriendlyName: light.GetFriendlyName(),
State: light.GetState(),
SupportedColorModes: append([]string(nil), light.GetSupportedColorModes()...),
MinColorTempKelvin: light.GetMinColorTempKelvin(),
MaxColorTempKelvin: light.GetMaxColorTempKelvin(),
IsHueGroup: light.GetIsHueGroup(),
EffectList: append([]string(nil), light.GetEffectList()...),
})
}
return lights, nil
}
func (c *Client) ListSwitches(ctx context.Context) ([]driven.Switch, error) {
resp, err := c.switchClient.ListSwitches(ctx, &hav1.ListSwitchesRequest{})
if err != nil {
return nil, fmt.Errorf("list switches: %w", err)
}
switches := make([]driven.Switch, 0, len(resp.GetSwitches()))
for _, sw := range resp.GetSwitches() {
switches = append(switches, driven.Switch{
EntityID: sw.GetEntityId(),
FriendlyName: sw.GetFriendlyName(),
State: sw.GetState(),
DeviceClass: sw.GetDeviceClass(),
})
}
return switches, nil
}
func (c *Client) TurnOnLight(ctx context.Context, entityID string, brightnessPct *uint32, colorTempKelvin *uint32) error {
req := &hav1.TurnOnRequest{EntityId: entityID}
if brightnessPct != nil {
req.BrightnessPct = brightnessPct
}
if colorTempKelvin != nil {
req.ColorTempKelvin = colorTempKelvin
}
if _, err := c.lightClient.TurnOn(ctx, req); err != nil {
return fmt.Errorf("turn on light %s: %w", entityID, err)
}
return nil
}
func (c *Client) TurnOffLight(ctx context.Context, entityID string, transition *uint32) error {
req := &hav1.TurnOffRequest{EntityId: entityID}
if transition != nil {
req.Transition = transition
}
if _, err := c.lightClient.TurnOff(ctx, req); err != nil {
return fmt.Errorf("turn off light %s: %w", entityID, err)
}
return nil
}
func (c *Client) ToggleLight(ctx context.Context, entityID string) error {
if _, err := c.lightClient.Toggle(ctx, &hav1.ToggleRequest{EntityId: entityID}); err != nil {
return fmt.Errorf("toggle light %s: %w", entityID, err)
}
return nil
}