| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package cli |
| |
| import ( |
| "encoding/hex" |
| "fmt" |
| "sort" |
| "strings" |
| "sync" |
| |
| "github.com/runtimeco/go-coap" |
| "github.com/spf13/cast" |
| "github.com/spf13/cobra" |
| "gopkg.in/abiosoft/ishell.v2" |
| "mynewt.apache.org/newtmgr/newtmgr/nmutil" |
| "mynewt.apache.org/newtmgr/nmxact/nmcoap" |
| "mynewt.apache.org/newtmgr/nmxact/nmxutil" |
| "mynewt.apache.org/newtmgr/nmxact/sesn" |
| "mynewt.apache.org/newtmgr/nmxact/xact" |
| ) |
| |
| var ObserverId int |
| |
| type observeElem struct { |
| Id int |
| Path string |
| Listener *nmcoap.Listener |
| } |
| |
| var ResourcePath string |
| var observerId int |
| var observers = map[int]*observeElem{} |
| var observerMtx sync.Mutex |
| |
| func addObserver(path string, cl *nmcoap.Listener) *observeElem { |
| observerMtx.Lock() |
| defer observerMtx.Unlock() |
| |
| o := &observeElem{ |
| Id: ObserverId, |
| Path: path, |
| Listener: cl, |
| } |
| ObserverId++ |
| observers[o.Id] = o |
| |
| go func() { |
| for { |
| msg, err := sesn.RxCoap(o.Listener, 0) |
| if err != nil { |
| fmt.Printf("notification error: %s", err.Error()) |
| return |
| } |
| |
| if msg == nil { |
| // No longer listening. |
| return |
| } |
| |
| fmt.Println("Notification received:") |
| fmt.Println("Code:", msg.Code()) |
| fmt.Println("Path:", o.Path) |
| fmt.Println("Token: [", msg.Token(), "]") |
| fmt.Printf("%s\n", resResponseStr(msg.PathString(), msg.Payload())) |
| fmt.Println() |
| } |
| }() |
| |
| return o |
| } |
| |
| func removeObserver(id int) *observeElem { |
| observerMtx.Lock() |
| defer observerMtx.Unlock() |
| |
| o := observers[id] |
| if o != nil { |
| delete(observers, id) |
| } |
| |
| return o |
| } |
| |
| func copyFromMap(m map[string]interface{}, key string) (value string) { |
| |
| if m[key] == nil { |
| return "" |
| } |
| |
| v, ok := m[key].(string) |
| if ok { |
| return v |
| } else { |
| return "" |
| } |
| } |
| |
| func hasStoredParams() bool { |
| |
| if strings.Compare(ResourcePath, "") == 0 { |
| return false |
| } |
| |
| return true |
| } |
| |
| func getPath(m map[string]interface{}) { |
| |
| rpath := copyFromMap(m, "path") |
| |
| if strings.Compare(rpath, "") != 0 { |
| ResourcePath = rpath |
| } |
| } |
| |
| func coapTxRx(c *ishell.Context, mp nmcoap.MsgParams) error { |
| s, err := GetSesn() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| cmd := xact.NewResCmd() |
| cmd.SetTxOptions(nmutil.TxOptions()) |
| cmd.MsgParams = mp |
| |
| res, err := cmd.Run(s) |
| if err != nil { |
| c.Println("Error:", err) |
| return err |
| } |
| |
| sres := res.(*xact.ResResult) |
| if sres.Status() != 0 { |
| fmt.Printf("Error: %s (%d)\n", sres.Rsp.Code(), sres.Status()) |
| return err |
| } |
| |
| if len(sres.Rsp.Payload()) > 0 { |
| fmt.Printf("%s\n", |
| resResponseStr(mp.Uri, sres.Rsp.Payload())) |
| } |
| |
| return nil |
| } |
| |
| func getCmd(c *ishell.Context) { |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| c.Println(m) |
| |
| c.Println("command: ", c.Cmd.Name) |
| c.Println("path: ", ResourcePath) |
| c.Println() |
| |
| mp := nmcoap.MsgParams{ |
| Code: coap.GET, |
| Uri: ResourcePath, |
| } |
| if err := coapTxRx(c, mp); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| } |
| } |
| |
| func registerCmd(c *ishell.Context) { |
| s, err := GetSesn() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| c.Println(m) |
| |
| c.Println("Register for notifications") |
| c.Println("path: ", ResourcePath) |
| c.Println() |
| |
| mc := nmcoap.MsgCriteria{Token: nmxutil.NextToken()} |
| cl, err := s.ListenCoap(mc) |
| if err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| return |
| } |
| |
| cmd := xact.NewResNoRxCmd() |
| cmd.MsgParams = nmcoap.MsgParams{ |
| Code: coap.GET, |
| Uri: ResourcePath, |
| Observe: nmcoap.OBSERVE_START, |
| Token: mc.Token, |
| } |
| if _, err := cmd.Run(s); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| return |
| } |
| |
| if _, err := sesn.RxCoap(cl, nmutil.TxOptions().Timeout); err != nil { |
| s.StopListenCoap(mc) |
| fmt.Printf("error: %s\n", err.Error()) |
| return |
| } |
| |
| o := addObserver(ResourcePath, cl) |
| c.Println("Observer added:") |
| c.Println("id:", o.Id, "path:", ResourcePath, "token:", mc.Token) |
| c.Println() |
| } |
| |
| func unregisterCmd(c *ishell.Context) { |
| s, err := GetSesn() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| if m["id"] == nil { |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| id, err := cast.ToIntE(m["id"]) |
| if err != nil { |
| c.Printf("Invalid ID: %v\n", m["id"]) |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| o := removeObserver(id) |
| if o == nil { |
| c.Println("Observer id:", id, "not found") |
| return |
| } |
| s.StopListenCoap(o.Listener.Criteria) |
| |
| mp := nmcoap.MsgParams{ |
| Code: coap.GET, |
| Uri: ResourcePath, |
| Observe: nmcoap.OBSERVE_STOP, |
| } |
| if err := coapTxRx(c, mp); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| return |
| } |
| |
| c.Println("Unregister for notifications") |
| c.Println("id: ", o.Id) |
| c.Println("path: ", o.Path) |
| c.Println("token: ", o.Listener.Criteria.Token) |
| c.Println() |
| } |
| |
| func getUriParams(c *ishell.Context) ([]byte, error) { |
| |
| c.ShowPrompt(false) |
| defer c.ShowPrompt(true) |
| |
| c.Println("provide ", c.Cmd.Name, " parameters in format key=value [key=value]") |
| pstr := c.ReadLine() |
| params := strings.Split(pstr, " ") |
| |
| m, err := extractResKv(params) |
| if err != nil { |
| return nil, err |
| } |
| |
| b, err := nmxutil.EncodeCborMap(m) |
| if err != nil { |
| return nil, err |
| } |
| |
| return b, nil |
| } |
| |
| func putCmd(c *ishell.Context) { |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| b, err := getUriParams(c) |
| if err != nil { |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| mp := nmcoap.MsgParams{ |
| Code: coap.PUT, |
| Uri: ResourcePath, |
| Payload: b, |
| } |
| if err := coapTxRx(c, mp); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| } |
| } |
| |
| func postCmd(c *ishell.Context) { |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| b, err := getUriParams(c) |
| if err != nil { |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| mp := nmcoap.MsgParams{ |
| Code: coap.POST, |
| Uri: ResourcePath, |
| Payload: b, |
| } |
| if err := coapTxRx(c, mp); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| } |
| } |
| |
| func deleteCmd(c *ishell.Context) { |
| m, err := extractResKv(c.Args) |
| if err != nil || len(c.Args) == 0 { |
| c.Println("Incorrect or no parameters provided ... using cached ones") |
| } else { |
| getPath(m) |
| } |
| |
| if hasStoredParams() == false { |
| c.Println("Missing resource path") |
| c.Println(c.HelpText()) |
| return |
| } |
| |
| mp := nmcoap.MsgParams{ |
| Code: coap.DELETE, |
| Uri: ResourcePath, |
| } |
| if err := coapTxRx(c, mp); err != nil { |
| fmt.Printf("error: %s\n", err.Error()) |
| } |
| } |
| |
| func printObservers(c *ishell.Context) { |
| observerMtx.Lock() |
| defer observerMtx.Unlock() |
| |
| var ids []int |
| for id, _ := range observers { |
| ids = append(ids, id) |
| } |
| sort.Ints(ids) |
| |
| for _, id := range ids { |
| o := observers[id] |
| c.Printf("id=%d path=%s token=%s\n", |
| o.Id, o.Path, hex.EncodeToString(o.Listener.Criteria.Token)) |
| } |
| } |
| |
| func startInteractive(cmd *cobra.Command, args []string) { |
| |
| // create new shell. |
| // by default, new shell includes 'exit', 'help' and 'clear' commands. |
| shell := ishell.New() |
| shell.SetPrompt("> ") |
| |
| // display welcome info. |
| shell.Println() |
| shell.Println(" Newtmgr shell mode for COAP:") |
| shell.Println(" Connection profile: ", nmutil.ConnProfile) |
| shell.Println() |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "get", |
| Help: "Send a CoAP GET request: get path=v", |
| Func: getCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "put", |
| Help: "Send a CoAP PUT request: path=v <you will be asked for params>", |
| Func: putCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "post", |
| Help: "Send a CoAP POST request: post path=v <you will be asked for params>", |
| Func: postCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "delete", |
| Help: "Send a CoAP POST request: delete path=v", |
| Func: deleteCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "reg", |
| Help: "Register for notifications: req path=v", |
| Func: registerCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "unreg", |
| Help: "Unregister from notifications (id means observer id): unreq id=v", |
| Func: unregisterCmd, |
| }) |
| |
| shell.AddCmd(&ishell.Cmd{ |
| Name: "observers", |
| Help: "Print registered observers: observers", |
| Func: printObservers, |
| }) |
| |
| shell.Run() |
| shell.Close() |
| } |
| |
| func interactiveCmd() *cobra.Command { |
| |
| shellCmd := &cobra.Command{ |
| Use: "interactive", |
| Short: "Run " + nmutil.ToolInfo.ShortName + |
| " interactive mode (used for COAP only)", |
| Run: startInteractive, |
| } |
| |
| return shellCmd |
| } |