diff --git a/internal/services/alarm_control_panel.go b/internal/services/alarm_control_panel.go new file mode 100644 index 0000000..4dee6c6 --- /dev/null +++ b/internal/services/alarm_control_panel.go @@ -0,0 +1,115 @@ +package services + +import ( + "context" + + "github.com/gorilla/websocket" + ws "saml.dev/gome-assistant/internal/websocket" +) + +/* Structs */ + +type AlarmControlPanel struct { + conn *websocket.Conn + ctx context.Context +} + +/* Public API */ + +// Send the alarm the command for arm away. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) ArmAway(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_arm_away" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for arm away. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) ArmWithCustomBypass(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_arm_custom_bypass" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for arm home. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) ArmHome(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_arm_home" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for arm night. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) ArmNight(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_arm_night" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for arm vacation. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) ArmVacation(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_arm_vacation" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for disarm. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) Disarm(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_disarm" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} + +// Send the alarm the command for trigger. +// Takes an entityId and an optional +// map that is translated into service_data. +func (acp AlarmControlPanel) Trigger(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "alarm_control_panel" + req.Service = "alarm_trigger" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, acp.conn, acp.ctx) +} diff --git a/internal/services/cover.go b/internal/services/cover.go new file mode 100644 index 0000000..c38d599 --- /dev/null +++ b/internal/services/cover.go @@ -0,0 +1,115 @@ +package services + +import ( + "context" + + "github.com/gorilla/websocket" + ws "saml.dev/gome-assistant/internal/websocket" +) + +/* Structs */ + +type Cover struct { + conn *websocket.Conn + ctx context.Context +} + +/* Public API */ + +// Close all or specified cover. Takes an entityId. +func (c Cover) Close(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "close_cover" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Close all or specified cover tilt. Takes an entityId. +func (c Cover) CloseTilt(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "close_cover_tilt" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Open all or specified cover. Takes an entityId. +func (c Cover) Open(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "open_cover" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Open all or specified cover tilt. Takes an entityId. +func (c Cover) OpenTilt(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "open_cover_tilt" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Move to specific position all or specified cover. Takes an entityId and an optional +// map that is translated into service_data. +func (c Cover) SetPosition(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "set_cover_position" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Move to specific position all or specified cover tilt. Takes an entityId and an optional +// map that is translated into service_data. +func (c Cover) SetTiltPosition(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "set_cover_tilt_position" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Stop a cover entity. Takes an entityId. +func (c Cover) Stop(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "stop_cover" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Stop a cover entity tilt. Takes an entityId. +func (c Cover) StopTilt(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "stop_cover_tilt" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Toggle a cover open/closed. Takes an entityId. +func (c Cover) Toggle(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "toggle" + + ws.WriteMessage(req, c.conn, c.ctx) +} + +// Toggle a cover tilt open/closed. Takes an entityId. +func (c Cover) ToggleTilt(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "cover" + req.Service = "toggle_cover_tilt" + + ws.WriteMessage(req, c.conn, c.ctx) +} diff --git a/internal/services/media_player.go b/internal/services/media_player.go new file mode 100644 index 0000000..1a5faba --- /dev/null +++ b/internal/services/media_player.go @@ -0,0 +1,273 @@ +package services + +import ( + "context" + + "github.com/gorilla/websocket" + ws "saml.dev/gome-assistant/internal/websocket" +) + +/* Structs */ + +type MediaPlayer struct { + conn *websocket.Conn + ctx context.Context +} + +/* Public API */ + +// Send the media player the command to clear players playlist. +// Takes an entityId. +func (mp MediaPlayer) ClearPlaylist(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "clear_playlist" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Group players together. Only works on platforms with support for player groups. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) Join(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "join" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command for next track. +// Takes an entityId. +func (mp MediaPlayer) Next(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_next_track" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command for pause. +// Takes an entityId. +func (mp MediaPlayer) Pause(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_pause" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command for play. +// Takes an entityId. +func (mp MediaPlayer) Play(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_play" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Toggle media player play/pause state. +// Takes an entityId. +func (mp MediaPlayer) PlayPause(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_play_pause" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command for previous track. +// Takes an entityId. +func (mp MediaPlayer) Previous(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_previous_track" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command to seek in current playing media. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) Seek(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_seek" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the stop command. +// Takes an entityId. +func (mp MediaPlayer) Stop(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_stop" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command for playing media. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) PlayMedia(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "media_play" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Set repeat mode. Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) RepeatSet(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "repeat_set" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command to change sound mode. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) SelectSoundMode(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "select_sound_mode" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Send the media player the command to change input source. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) SelectSource(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "select_source" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Set shuffling state. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) Shuffle(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "shuffle_set" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Toggles a media player power state. +// Takes an entityId. +func (mp MediaPlayer) Toggle(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "toggle" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Turn a media player power off. +// Takes an entityId. +func (mp MediaPlayer) TurnOff(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "turn_off" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Turn a media player power on. +// Takes an entityId. +func (mp MediaPlayer) TurnOn(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "turn_on" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Unjoin the player from a group. Only works on +// platforms with support for player groups. +// Takes an entityId. +func (mp MediaPlayer) Unjoin(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "unjoin" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Turn a media player volume down. +// Takes an entityId. +func (mp MediaPlayer) VolumeDown(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "volume_down" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Mute a media player's volume. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) VolumeMute(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "volume_mute" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Set a media player's volume level. +// Takes an entityId and an optional +// map that is translated into service_data. +func (mp MediaPlayer) VolumeSet(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "volume_set" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, mp.conn, mp.ctx) +} + +// Turn a media player volume up. +// Takes an entityId. +func (mp MediaPlayer) VolumeUp(entityId string) { + req := NewBaseServiceRequest(entityId) + req.Domain = "media_player" + req.Service = "volume_up" + + ws.WriteMessage(req, mp.conn, mp.ctx) +} diff --git a/internal/services/scene.go b/internal/services/scene.go new file mode 100644 index 0000000..4b5d9b9 --- /dev/null +++ b/internal/services/scene.go @@ -0,0 +1,64 @@ +package services + +import ( + "context" + + "github.com/gorilla/websocket" + ws "saml.dev/gome-assistant/internal/websocket" +) + +/* Structs */ + +type Scene struct { + conn *websocket.Conn + ctx context.Context +} + +/* Public API */ + +// Apply a scene. Takes map that is translated into service_data. +func (s Scene) Apply(serviceData ...map[string]any) { + req := NewBaseServiceRequest("") + req.Domain = "scene" + req.Service = "apply" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, s.conn, s.ctx) +} + +// Create a scene entity. Takes an entityId and an optional +// map that is translated into service_data. +func (s Scene) Create(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "scene" + req.Service = "create" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, s.conn, s.ctx) +} + +// Reload the scenes. +func (s Scene) Reload() { + req := NewBaseServiceRequest("") + req.Domain = "scene" + req.Service = "reload" + + ws.WriteMessage(req, s.conn, s.ctx) +} + +// TurnOn a scene entity. Takes an entityId and an optional +// map that is translated into service_data. +func (s Scene) TurnOn(entityId string, serviceData ...map[string]any) { + req := NewBaseServiceRequest(entityId) + req.Domain = "scene" + req.Service = "turn_on" + if len(serviceData) != 0 { + req.ServiceData = serviceData[0] + } + + ws.WriteMessage(req, s.conn, s.ctx) +} diff --git a/internal/services/services.go b/internal/services/services.go index 4050d4d..75d03b2 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -9,9 +9,12 @@ import ( ) func BuildService[ - T Light | + T AlarmControlPanel | + Cover | + Light | HomeAssistant | Lock | + MediaPlayer | Switch | InputBoolean | InputButton | @@ -19,7 +22,8 @@ func BuildService[ InputText | InputNumber | Notify | - Number, + Number | + Scene, ](conn *websocket.Conn, ctx context.Context) *T { return &T{conn: conn, ctx: ctx} } diff --git a/service.go b/service.go index b299f22..2f3e745 100644 --- a/service.go +++ b/service.go @@ -9,31 +9,39 @@ import ( ) type Service struct { - HomeAssistant *services.HomeAssistant - Light *services.Light - Lock *services.Lock - Switch *services.Switch - InputBoolean *services.InputBoolean - InputButton *services.InputButton - InputText *services.InputText - InputDatetime *services.InputDatetime - InputNumber *services.InputNumber - Notify *services.Notify - Number *services.Number + AlarmControlPanel *services.AlarmControlPanel + Cover *services.Cover + HomeAssistant *services.HomeAssistant + Light *services.Light + Lock *services.Lock + MediaPlayer *services.MediaPlayer + Switch *services.Switch + InputBoolean *services.InputBoolean + InputButton *services.InputButton + InputText *services.InputText + InputDatetime *services.InputDatetime + InputNumber *services.InputNumber + Notify *services.Notify + Number *services.Number + Scene *services.Scene } func newService(conn *websocket.Conn, ctx context.Context, httpClient *http.HttpClient) *Service { return &Service{ - Light: services.BuildService[services.Light](conn, ctx), - HomeAssistant: services.BuildService[services.HomeAssistant](conn, ctx), - Lock: services.BuildService[services.Lock](conn, ctx), - Switch: services.BuildService[services.Switch](conn, ctx), - InputBoolean: services.BuildService[services.InputBoolean](conn, ctx), - InputButton: services.BuildService[services.InputButton](conn, ctx), - InputText: services.BuildService[services.InputText](conn, ctx), - InputDatetime: services.BuildService[services.InputDatetime](conn, ctx), - InputNumber: services.BuildService[services.InputNumber](conn, ctx), - Notify: services.BuildService[services.Notify](conn, ctx), - Number: services.BuildService[services.Number](conn, ctx), + AlarmControlPanel: services.BuildService[services.AlarmControlPanel](conn, ctx), + Cover: services.BuildService[services.Cover](conn, ctx), + Light: services.BuildService[services.Light](conn, ctx), + HomeAssistant: services.BuildService[services.HomeAssistant](conn, ctx), + Lock: services.BuildService[services.Lock](conn, ctx), + MediaPlayer: services.BuildService[services.MediaPlayer](conn, ctx), + Switch: services.BuildService[services.Switch](conn, ctx), + InputBoolean: services.BuildService[services.InputBoolean](conn, ctx), + InputButton: services.BuildService[services.InputButton](conn, ctx), + InputText: services.BuildService[services.InputText](conn, ctx), + InputDatetime: services.BuildService[services.InputDatetime](conn, ctx), + InputNumber: services.BuildService[services.InputNumber](conn, ctx), + Notify: services.BuildService[services.Notify](conn, ctx), + Number: services.BuildService[services.Number](conn, ctx), + Scene: services.BuildService[services.Scene](conn, ctx), } }