package main import ( "log" "os" "path" "errors" "github.com/fsnotify/fsnotify" "github.com/mkideal/cli" "github.com/tarm/serial" ) func stop(exitCode int) { log.Println("Have a nice day.") os.Exit(exitCode) } type message struct { msgType byte data []byte } const ( MSG_TYPE_ANSWER_OK = 0x01 MSG_TYPE_ANSWER_NOOK = 0x02 MSG_TYPE_AGENTID = 0x03 MSG_TYPE_CONFIG = 0x04 ) func decodeByteString(bytes []byte, num int) (msg message, err error) { if bytes[0] == 0x3c && bytes[1] == 0x3e && bytes[num-1] == 0x0A && bytes[num-2] == 0x0D { return message{msgType: bytes[2], data: bytes[3:num-2]}, nil } return message{msgType: 0x00, data: make([]byte, 1)}, errors.New("Can't decode the byte array.") } func waitAndExecuteCommandsFromDevice(serial *serial.Port) { buffer := make([]byte, 40) for { num, err := serial.Read(buffer) if err != nil { log.Fatal(err) } input, err := decodeByteString(buffer, num) if err != nil { log.Fatal(err) } else { switch (input.msgType) { case MSG_TYPE_AGENTID: log.Printf("Key pressed: %d", input.data[0]) default: log.Fatal("Can't recognize the message type.") } } } } func watchForConfigChanges(configFile string, serial *serial.Port) { watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() done := make(chan bool) go func() { for { select { case event := <-watcher.Events: // The file is changed. This is the only event we are interested // in. If the file is renamed, removed or something else, we drop // an error to the user. if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Remove == fsnotify.Remove { if _, err := os.Stat(event.Name); err == nil { log.Println("Reload the config file ...") // TODO: Parse the config file // TODO: Handle errors in the config file // TODO: Write a mapping for the mapping of the keys to the binary format the avr wants. // TODO: Establish a connection to the device // TODO: Transfer the new key mappings // TODO: Close the connection? } } // Some editors have a feature called "swap save" (like vim), which // will trigger a simple WRITE event, instead it wil trigger RENAME // -> CHMOD -> REMOVE in that order. So we lost track of the file. // To prevent this, we add the file again, if it exist after a // REMOVE event. if event.Op&fsnotify.Remove == fsnotify.Remove { if _, err := os.Stat(event.Name); err == nil { watcher.Add(event.Name) } else { log.Fatal("Lost the configuration file. We stop now.") stop(1) } } case err := <-watcher.Errors: log.Println("We have the following problem with the configuration file: ", err) log.Println("Try the fix this problem by yourself and restart the agent.") stop(2) } } }() err = watcher.Add(configFile) if err != nil { log.Fatal("Can't find the configuration file. Please create one.") } <-done } type argT struct { cli.Helper ConfigFile string `cli:"c,config" usage:"specify a keymap config file"` Device string `cli:"d,device" usage:"specify the serial port, the n3rdpad is hooked to"` } func main() { configPath, _ := os.Getwd() configFile := path.Join(configPath, "keymap.conf") cli.Run(&argT{}, func(ctx *cli.Context) error { args := ctx.Argv().(*argT) if args.ConfigFile != "" { configFile = args.ConfigFile } if args.Device == "" { log.Fatal("You need to specify the serial device with the --device parameter.") stop(3) } if !args.Help { // Start up the application log.Println("Using the configuration file", configFile) // Open the serial connection config := &serial.Config{Name: args.Device, Baud: 115200} serial, err := serial.OpenPort(config) if err != nil { log.Fatal(err) return nil } log.Printf("Open the connection to the n3rdpad over %s", args.Device) go watchForConfigChanges(configFile, serial) waitAndExecuteCommandsFromDevice(serial) stop(0) } return nil }) }