Skip to content

Commit

Permalink
Implementing Client.SendAction (#25)
Browse files Browse the repository at this point in the history
* implementing Client.SendAction

messages.go: add new `statustouches` event

* resolve more non obvious conflicts

* [fhome config list --glance]: fix
  • Loading branch information
bartekpacia authored Dec 19, 2024
1 parent 4cd69b5 commit a3cfd9e
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 10 deletions.
13 changes: 13 additions & 0 deletions .run/fhome config list --glance.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="fhome config list --glance" type="GoApplicationRunConfiguration" factoryName="Go Application" focusToolWindowBeforeRun="true">
<module name="fhome" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="config list --glance" />
<kind value="PACKAGE" />
<package value="github.com/bartekpacia/fhome/cmd/fhome" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$" />
<pty_enabled value="true" />
<method v="2" />
</configuration>
</component>
19 changes: 19 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,25 @@ func (c *Client) ReadAnyMessage() (*Message, error) {
return &msg, nil
}

// SendAction sends an action to the server.
func (c *Client) SendAction(ctx context.Context, actionName string) (*Message, error) {
token := generateRequestToken()

action := Action{
ActionName: actionName,
Login: *c.email,
PasswordHash: *c.resourcePasswordHash,
RequestToken: token,
}

err := c.mainConn.WriteJSON(action)
if err != nil {
return nil, fmt.Errorf("failed to write action %s: %v", action.ActionName, err)
}

return c.ReadMessage(ctx, action.ActionName, token)
}

// SendEvent sends an event containing value to the cell.
//
// Events are named "Xevents" in F&Home's terminology.
Expand Down
8 changes: 6 additions & 2 deletions api/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ const (
ActionGetSystemConfig = "touches"
ActionGetUserConfig = "get_user_config"
ActionEvent = "xevent"
// ActionStatusTouches can be sent to get real values of resources.
ActionStatusTouches = "statustouches"

// ActionStatusTouches returns real values of objects.
ActionStatusTouches = "statustouches"

// ActionStatusTouchesChanged returns mostly the same response as ActionStatusTouches,
// but only for a specific (usually single) changed object.
ActionStatusTouchesChanged = "statustoucheschanged"
)

Expand Down
4 changes: 4 additions & 0 deletions api/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ type MobileDisplayCell struct {
MaxValue string `json:"Max"`
// Step (aka current value). Display Type TEMP always has this set to
// 0xa005.
//
// Update 25/07/2024: This is literally the *step*, not current value.
//
// To obtain current value, send "touches".
Step string `json:"Sp"`
// Display Type.
DisplayType DisplayType `json:"DT"`
Expand Down
88 changes: 84 additions & 4 deletions cmd/fhome/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"
"os"
"strconv"
"strings"
"text/tabwriter"

"github.com/adrg/strutil"
Expand Down Expand Up @@ -54,6 +55,14 @@ var configCommand = cli.Command{
Name: "user",
Usage: "Print config set in the client apps",
},
&cli.BoolFlag{
Name: "merged",
Usage: "Print merged config (system + user)",
},
&cli.BoolFlag{
Name: "glance",
Usage: "Print nice, at glance status of system",
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Bool("system") && cmd.Bool("user") {
Expand All @@ -79,6 +88,12 @@ var configCommand = cli.Command{
}
log.Println("got user config")

apiConfig, err := api.MergeConfigs(userConfig, sysConfig)
if err != nil {
return fmt.Errorf("failed to merge configs: %v", err)
}
_ = apiConfig

if cmd.Bool("system") {
w := tabwriter.NewWriter(os.Stdout, 8, 8, 0, ' ', 0)
defer w.Flush()
Expand Down Expand Up @@ -110,7 +125,7 @@ var configCommand = cli.Command{

fmt.Fprintf(w, "%3d\t%s\t%s\t%s\n", cell.ObjectID, cell.IconName(), cell.Name, p)
}
} else {
} else if cmd.Bool("merged") {
config, err := api.MergeConfigs(userConfig, sysConfig)
if err != nil {
return fmt.Errorf("failed to merge configs: %v", err)
Expand All @@ -124,6 +139,72 @@ var configCommand = cli.Command{
}
log.Println()
}
} else if cmd.Bool("glance") {
// We want to see real values of the system resources.
// To do that, we need to send the "statustouches" action and
// wait for its response.

msg, err := client.SendAction(ctx, api.ActionStatusTouches)
if err != nil {
return fmt.Errorf("failed to send action: %v", err)
}

var touchesResponse api.StatusTouchesChangedResponse

err = json.Unmarshal(msg.Raw, &touchesResponse)
if err != nil {
slog.Error("failed to unmarshal message", slog.Any("error", err))
return err
}

cells := make([]struct {
Name string
Value int
}, 0)

mdCells := sysConfig.Response.MobileDisplayProperties.Cells
for _, cell := range mdCells {
if cell.DisplayType != api.Percentage {
continue
}
if !strings.HasPrefix(cell.Step, "0x60") {
continue
}

var cellValue *api.CellValue
for _, cv := range touchesResponse.Response.CellValues {
if cv.ID == cell.ID {
cellValue = &cv
break
}
}
if cellValue == nil {
slog.Error("failed to find corresponding cell value", slog.String("cell", cell.ID))
continue
}

slog.Info("remapping lighting value", slog.String("cell", cell.Desc), slog.String("value", cellValue.Value), slog.String("step", cell.Step))
val, err := api.RemapLighting(cellValue.Value)
if err != nil {
slog.Error(
"error remapping lighting value",
slog.Group("cell", slog.String("id", cell.ID), slog.String("desc", cell.Desc)),
slog.Any("error", err),
)
continue
}

cells = append(cells, struct {
Name string
Value int
}{Name: cell.Desc, Value: val})
}

text := "Oto status oświetlenia:\n"
for _, cell := range cells {
text += fmt.Sprintf("• %s: %d%%\n", cell.Name, cell.Value)
}
fmt.Print(text)
}

return nil
Expand Down Expand Up @@ -303,10 +384,9 @@ var objectCommand = cli.Command{
return fmt.Errorf("no matching object found, confidence is %d%%", int(bestScore*100))
}

slog.Info("selected object",
slog.String("name", bestObject.Name),
slog.Int("id", bestObject.ID),
slog.Info("found best match",
slog.Int("confidence", int(bestScore*100)),
slog.Group("object", slog.String("name", bestObject.Name), slog.Int("id", bestObject.ID)),
)

value := api.MapLighting(value)
Expand Down
6 changes: 5 additions & 1 deletion cmd/fhomed/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ func daemon(ctx context.Context, config *highlevel.Config, name, pin string) err
}

cellValue := resp.Response.CellValues[0]
printCellData(&cellValue, apiConfig)
err = highlevel.PrintCellData(&cellValue, apiConfig)
if err != nil {
slog.Error("failed to print cell data", slog.Any("error", err))
return err
}

// handle lightbulb
{
Expand Down
6 changes: 3 additions & 3 deletions cmd/fhomed/utils.go → highlevel/utils.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package highlevel

import (
"fmt"
Expand All @@ -7,8 +7,8 @@ import (
"github.com/bartekpacia/fhome/api"
)

// printCellData prints the values of its arguments into a JSON object.
func printCellData(cellValue *api.CellValue, cfg *api.Config) error {
// PrintCellData prints the values of its arguments into a JSON object.
func PrintCellData(cellValue *api.CellValue, cfg *api.Config) error {
cell, err := cfg.GetCellByID(cellValue.IntID())
if err != nil {
return fmt.Errorf("failed to get cell with ID %d: %v", cellValue.IntID(), err)
Expand Down

0 comments on commit a3cfd9e

Please sign in to comment.