Skip to content

Commit

Permalink
[feat] command for blink, execute, play (#6)
Browse files Browse the repository at this point in the history
* cmd blink

* Blink for 1s

* Pattern preview

* One loader for pattern file

* Execute the pattern

* Set repeat times

* play pattern

* One place for preargs

* Resolve comments
  • Loading branch information
hyorigo authored Oct 23, 2023
1 parent 75d9e1a commit b880a82
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 86 deletions.
125 changes: 125 additions & 0 deletions cmd/blink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cmd

import (
"fmt"
"image/color"
"time"

"github.com/1set/gut/ystring"
b1 "github.com/b1ug/blink1-go"
"github.com/b1ug/nb1/exchange"
"github.com/b1ug/nb1/hdwr"
"github.com/b1ug/nb1/util"
"github.com/spf13/cobra"
)

// blinkCmd represents the turn command
var blinkCmd = &cobra.Command{
Use: "blink <color>...",
Aliases: aliasesBlink,
Short: "Blink a blink(1) device to given color",
Long: hdocf(`
Blink a blink(1) device to given color (or random color) for a given times.
The colors should be given as arguments, and can be specified as a hex color code, a color name, or a preset color name.
For multiple colors, the blink(1) will blink to each color in order.
e.g. <color1> <off> <color2> <off> <color3> <off> ... <color1> <off> <color2> <off>
Special colors:
%s
Supported preset colors:
%s
`,
util.JoinWrapSlice([]string{"random", "on"}, ", ", 100),
util.JoinWrapSlice(b1.GetColorNames(), ", ", 100)),
PersistentPreRunE: openBlink1Device,
RunE: func(cmd *cobra.Command, args []string) error {
// clean up args
var colorRaw []string
for _, arg := range args {
if ystring.IsNotBlank(arg) {
colorRaw = append(colorRaw, arg)
}
}
if len(colorRaw) == 0 {
// default blink to white
colorRaw = []string{"white"}
}
if blinkTimeDur < 10*time.Millisecond {
// 10ms is the minimum
blinkTimeDur = 10 * time.Millisecond
}
if blinkTimes == 0 {
blinkTimes = 5
}
log.Debugw("raw colors to blink blink(1)", "count", len(colorRaw), "colors", colorRaw, "interval", blinkTimeDur, "led", blinkLedNum, "times", blinkTimes)

// set color now
performColorChange := func(index int, cl color.Color) error {
led := b1.LEDIndex(blinkLedNum)
st := b1.NewLightState(cl, 0, led)
log.Debugw("set blink(1) state now", "index", index, "state", st)

// set state
if err := hdwr.PlayState(st); err != nil {
return err
}

// print state
fmt.Printf("#%d: Set %s to Color: %s\n", index+1, led, util.FormatNamedColor(cl))

// wait for next blink
time.Sleep(blinkTimeDur)
return nil
}

// let blink(1) blink
for i := 0; i < int(blinkTimes); i++ {
// parse color
raw := colorRaw[i%len(colorRaw)]
cl, err := exchange.ParseColor(raw)
if err != nil {
return fmt.Errorf("%w: %q", err, raw)
}

// can't be off
if cl == b1.ColorBlack {
return fmt.Errorf("blink(1) can't blink to off")
}

// blink on
if err := performColorChange(i, cl); err != nil {
return err
}

// blink off
if err := performColorChange(i, b1.ColorBlack); err != nil {
return err
}
}

// stop playing
// TODO: handle Ctrl+C
return hdwr.StopPlaying()
},
}

var (
blinkTimeDur time.Duration
blinkLedNum uint
blinkTimes uint
)

func init() {
rootCmd.AddCommand(blinkCmd)

// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command and all subcommands, e.g.:
// blinkCmd.PersistentFlags().String("foo", "", "A help for foo")
blinkCmd.PersistentFlags().DurationVarP(&blinkTimeDur, "blink-time", "m", 200*time.Millisecond, "duration of blink on/off")
blinkCmd.PersistentFlags().UintVarP(&blinkLedNum, "led", "l", 0, "which led number to blink, 0=all/1=top/2=bottom (mk2+)")
blinkCmd.PersistentFlags().UintVarP(&blinkTimes, "times", "t", 5, "how many times to blink")

// Cobra supports local flags which will only run when this command is called directly, e.g.:
// blinkCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
35 changes: 7 additions & 28 deletions cmd/convert.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cmd

import (
"errors"

"github.com/b1ug/nb1/exchange"
"github.com/b1ug/nb1/schema"
"github.com/b1ug/nb1/util"
Expand All @@ -20,7 +18,6 @@ var convertCmd = &cobra.Command{
Supported formats:
- Play Text (e.g. "red blink 3 times")
- Pattern JSON (e.g. '{"repeat":1,"seq":"#FF0000L0T1500;#FF0000L0T3500"...}')
- Starlark Script (e.g. 'play(red, blue, green)')
`),
}

Expand All @@ -42,28 +39,8 @@ func init() {
// Subcommands
convertCmd.AddCommand(convertText2JSONCmd)
convertCmd.AddCommand(convertJSON2TextCmd)
convertCmd.AddCommand(convertText2ScriptCmd)
convertCmd.AddCommand(convertJSON2ScriptCmd)
}

var (
inputPath string
outputPath string
)

// getInOutPathArgs returns a PersistentPreRunE function that sets inputPath and outputPath from args.
func getInOutPathArgs(msg, extName string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
// input and output path
inputPath = args[0]
if len(args) >= 2 {
outputPath = args[1]
} else {
outputPath = util.ChangeFileExt(args[0], extName)
}
log.Infow(msg, "input_path", inputPath, "output_path", outputPath)
return nil
}
//convertCmd.AddCommand(convertText2ScriptCmd)
//convertCmd.AddCommand(convertJSON2ScriptCmd)
}

// convertText2JSONCmd represents the text2json command
Expand All @@ -89,7 +66,7 @@ var convertText2JSONCmd = &cobra.Command{

// output
if convertPreviewPattern {
util.PrintStateSequence(ps.Sequence)
_ = util.PrintPatternSet(ps)
}
return exchange.SaveAsJSON(ps, outputPath)
},
Expand All @@ -111,17 +88,18 @@ var convertJSON2TextCmd = &cobra.Command{
if err := exchange.LoadFromJSON(&ps, inputPath); err != nil {
return err
}
ps.Length = uint(len(ps.Sequence)) // TODO: may auto calculate length with helper methods
ps.AutoFill()

// output
if convertPreviewPattern {
util.PrintStateSequence(ps.Sequence)
_ = util.PrintPatternSet(&ps)
}
ls := exchange.EncodePlayText(ps)
return exchange.SaveAsLine(ls, outputPath)
},
}

/*
// convertText2ScriptCmd represents the text2script command
var convertText2ScriptCmd = &cobra.Command{
Use: "text2script",
Expand Down Expand Up @@ -151,3 +129,4 @@ var convertJSON2ScriptCmd = &cobra.Command{
return errors.New("not implemented")
},
}
*/
72 changes: 49 additions & 23 deletions cmd/execute.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,82 @@
package cmd

import (
"fmt"

"bitbucket.org/ai69/amoy"
"github.com/b1ug/nb1/exchange"
"github.com/b1ug/nb1/hdwr"
"github.com/b1ug/nb1/util"
"github.com/spf13/cobra"
)

// executeCmd represents the execute command
var executeCmd = &cobra.Command{
Use: "execute",
Aliases: aliasesExecute,
Short: "Execute blink(1) into a given color",
Short: "Execute pattern files",
Long: hdoc(`
Perform a specific color changing action on a blink(1) device.
// TODO:
Load pattern files and execute them by playing the patterns.
Supported formats:
- Play Text (e.g. "red blink 3 times")
- Pattern JSON (e.g. '{"repeat":1,"seq":"#FF0000L0T1500;#FF0000L0T3500"...}')
`),
Args: cobra.MinimumNArgs(1),
Args: cobra.ExactArgs(1),
PersistentPreRunE: openBlink1Device,
RunE: func(cmd *cobra.Command, args []string) error {
// read file content
fp := args[0]

// TODO: check file types, and THEN read

// read
ls, err := amoy.ReadFileLines(fp)
// load and parse pattern file
ps, err := exchange.LoadPatternFile(args[0])
if err != nil {
return err
}

// parsed
ps, err := exchange.ParsePlayText(ls)
if err != nil {
return err
// override repeat times
if cmd.Flag("times").Changed {
ps.RepeatTimes = execRepeatTimes
}
fmt.Println("Play", ps)
amoy.PrintOneLineJSON(ps)
amoy.PrintJSON(ps)

// TODO:
return fmt.Errorf("not implemented")
// preview
if execPreviewPattern {
_ = util.PrintPatternSet(ps)
}

// execute
if times := int(ps.RepeatTimes); times == 0 {
log.Debugw("executing pattern set forever", "pattern", ps, "times", times)
idx := 0
for {
log.Debugw("playing state sequence", "index", idx, "length", len(ps.Sequence))
if err := hdwr.PlayStateSequence(ps.Sequence); err != nil {
return err
}
idx++
}
} else {
log.Debugw("executing pattern set for limited times", "pattern", ps, "times", times)
for idx := 0; idx < times; idx++ {
log.Debugw("playing state sequence", "index", idx, "length", len(ps.Sequence))
if err := hdwr.PlayStateSequence(ps.Sequence); err != nil {
return err
}
}
}

// TODO: handle Ctrl+C
return hdwr.StopPlaying()
},
}

var (
execPreviewPattern bool
execRepeatTimes uint
)

func init() {
rootCmd.AddCommand(executeCmd)

// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command and all subcommands, e.g.:
// executeCmd.PersistentFlags().String("foo", "", "A help for foo")
executeCmd.PersistentFlags().BoolVarP(&execPreviewPattern, "preview", "p", false, "Preview the pattern to be executed")
executeCmd.PersistentFlags().UintVarP(&execRepeatTimes, "times", "t", 1, "Override the pattern repeat times (0 means forever)")

// Cobra supports local flags which will only run when this command is called directly, e.g.:
// executeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
Expand Down
54 changes: 45 additions & 9 deletions cmd/play.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,71 @@
package cmd

import (
"fmt"

"github.com/b1ug/nb1/hdwr"
"github.com/b1ug/nb1/util"
"github.com/spf13/cobra"
)

// playCmd represents the play command
var playCmd = &cobra.Command{
Use: "play",
Use: "play <start-end>",
Aliases: aliasesPlay,
Short: "Play blink(1) into a given color",
Short: "Play pattern stored in blink(1)",
Long: hdoc(`
Perform a specific color changing action on a blink(1) device.
// TODO:
Send a command to blink(1) to play a pattern stored on a blink(1) device.
Default start-end is 0-0, which means play the whole pattern stored on the device.
You can specify a start-end range to play a sub-pattern, and you can preview the pattern by using the --preview flag.
The following pattern ranges are supported:
- 0 or 0-0: play the whole pattern stored on the device
- 5: play the sub-pattern at index 5 until the end
- 5-10: play the sub-pattern from index 5 to 10
`),
Args: cobra.MinimumNArgs(1),
PersistentPreRunE: openBlink1Device,
RunE: func(cmd *cobra.Command, args []string) error {
// parse sub-pattern range
if err := getPatternPosArgs(cmd, args); err != nil {
return err
}

// preview full pattern
if playPreviewPattern {
seq, err := hdwr.ReadOnChipSequence()
if err != nil {
return err
}
_ = util.PrintStateSequence(seq)
}

// TODO: handle Ctrl+C to stop playing

// TODO:
return fmt.Errorf("not implemented")
// play and wait
if waitComplete {
log.Infow("start playing pattern and wait", "start", patternStartPos, "end", patternEndPos, "repeat", playRepeatTimes)
if err := hdwr.PlayOnChipPattern(patternStartPos, patternEndPos, playRepeatTimes, true); err != nil {
return err
}
return hdwr.StopPlaying()
}

log.Infow("start playing pattern", "start", patternStartPos, "end", patternEndPos, "repeat", playRepeatTimes)
return hdwr.PlayOnChipPattern(patternStartPos, patternEndPos, playRepeatTimes, false)
},
}

var (
playPreviewPattern bool
playRepeatTimes int
)

func init() {
rootCmd.AddCommand(playCmd)

// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command and all subcommands, e.g.:
// playCmd.PersistentFlags().String("foo", "", "A help for foo")
playCmd.PersistentFlags().BoolVarP(&playPreviewPattern, "preview", "p", false, "Load and preview the pattern stored on the blink(1) device")
playCmd.PersistentFlags().IntVarP(&playRepeatTimes, "times", "t", 1, "Pattern repeat times (0 means forever)")

// Cobra supports local flags which will only run when this command is called directly, e.g.:
// playCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
Expand Down
Loading

0 comments on commit b880a82

Please sign in to comment.