Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ examples/mock/mock
examples/logging_demo
otto/otto
ottoctl/ottoctl
cmd/ottoctl/ottoctl/ottoctl
mock/mock
gpio/gpio
station/station
Expand Down
12 changes: 12 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ func (c *Client) GetLogConfig() (result utils.LogConfig, err error) {
return result, nil
}

// GetTimers retrieves timer information from the Otto server.
// Returns a slice of TickerInfo containing details about active timers.
//
// This calls the /api/timers endpoint on the server.
func (c *Client) GetTimers() ([]utils.TickerInfo, error) {
var timers []utils.TickerInfo
if err := c.get("/api/timers", &timers); err != nil {
return nil, err
}
return timers, nil
}

// Ping checks if the Otto server is reachable and responding.
// Returns nil if the server is healthy, error otherwise.
func (c *Client) Ping() error {
Expand Down
1 change: 1 addition & 0 deletions cmd/ottoctl/cmd_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func init() {
rootCmd.AddCommand(shutdownCmd)
rootCmd.AddCommand(stationsCmd)
rootCmd.AddCommand(statsCmd)
rootCmd.AddCommand(timersCmd)
rootCmd.AddCommand(versionCmd)
}

Expand Down
42 changes: 42 additions & 0 deletions cmd/ottoctl/cmd_timers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ottoctl

import (
"errors"
"fmt"
"log/slog"
"time"

"github.com/spf13/cobra"
)

var timersCmd = &cobra.Command{
Use: "timers",
Short: "Display timer information",
Long: `Display timer information from local or remote Otto instance`,
RunE: timersRun,
}

func timersRun(cmd *cobra.Command, args []string) error {
// Check if we should connect to a remote server
client := getClient()
if client == nil {
return errors.New("timersRun failed to get a client")
}

// Remote mode: fetch timers from server
slog.Debug("Fetching timers from remote server", "url", client.BaseURL)
timers, err := client.GetTimers()
if err != nil {
fmt.Fprintf(errOutput, "Error fetching remote timers: %v\n", err)
return err
}

fmt.Fprintf(cmdOutput, "%20s %7s %6s %s\n", "ticker", "active", "ticks", "last")
fmt.Fprintln(cmdOutput, "--------------------------------------------------------------")
for _, t := range timers {
last := time.Since(t.LastTick)
last = last.Round(time.Second)
fmt.Fprintf(cmdOutput, "%20s %7t %6d %s\n", t.Name, t.Active, t.Ticks, last)
}
return nil
}
39 changes: 39 additions & 0 deletions cmd/ottoctl/cmd_timers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ottoctl

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestTimers(t *testing.T) {
tstfile := "testdata/timers.json"
response, err := os.ReadFile(tstfile)
assert.NoError(t, err, "failed to open")

tst := newTstInput(timersRun, string(response))
err = httpQuery(t, tst)
assert.NoError(t, err)

output := tst.buffer.String()
assert.Contains(t, output, "heartbeat")
assert.Contains(t, output, "sensor_poll")
}

func TestTimersEmpty(t *testing.T) {
response := ""
tst := newTstInput(timersRun, response)
err := httpQuery(t, tst)
assert.Error(t, err)
}

func TestTimersError(t *testing.T) {
tst := newTstInput(timersRun, "")
tst.statusCode = 500
err := httpQuery(t, tst)
assert.Error(t, err)

output := tst.errbuf.String()
assert.Contains(t, output, "500 - Internal Server Error")
}
2 changes: 1 addition & 1 deletion cmd/ottoctl/ottoctl/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ottoctl: main.go
go build -v . -o ottoctl
go build -v -o ottoctl .
20 changes: 20 additions & 0 deletions cmd/ottoctl/testdata/timers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"name": "heartbeat",
"last_tick": "2026-01-04T16:40:00Z",
"ticks": 120,
"active": true
},
{
"name": "sensor_poll",
"last_tick": "2026-01-04T16:39:55Z",
"ticks": 240,
"active": true
},
{
"name": "cleanup",
"last_tick": "2026-01-04T16:35:00Z",
"ticks": 6,
"active": false
}
]