diff --git a/build/cli/makefile b/build/cli/makefile index 5db16ab..cd8e069 100755 --- a/build/cli/makefile +++ b/build/cli/makefile @@ -88,7 +88,7 @@ $(MAIN_OBJECT): %.o: %.c $(CXX) $(CFLAGS) -o $@ -c $< $(PROGRAM): - go build -o $(PROGRAM) $(ONEWIFI_EM_SRC)/cli/main.go + go build -o $(PROGRAM) $(ONEWIFI_EM_SRC)/cli/*.go # Clean target: "make -f Makefile.Linux clean" to remove unwanted objects and executables. # diff --git a/src/cli/em_cmd_cli.cpp b/src/cli/em_cmd_cli.cpp index c6d10ba..be87c4e 100644 --- a/src/cli/em_cmd_cli.cpp +++ b/src/cli/em_cmd_cli.cpp @@ -66,7 +66,7 @@ em_cmd_params_t spec_params[] = { {.u = {.args = {2, {"", "", "", "", ""}, "STASteer.json"}}}, {.u = {.args = {2, {"", "", "", "", ""}, "STADisassoc.json"}}}, {.u = {.args = {2, {"", "", "", "", ""}, "STABtm.json"}}}, - {.u = {.args = {1, {"", "", "", "", ""}, "DPPURI.json"}}}, + {.u = {.args = {1, {"", "", "", "", ""}, "DPPURI_sendable.json"}}}, {.u = {.args = {1, {"", "", "", "", ""}, "Clientcap.json"}}}, {.u = {.args = {2, {"", "", "", "", ""}, "Policy"}}}, {.u = {.args = {2, {"", "", "", "", ""}, "Policy.json"}}}, diff --git a/src/cli/main.go b/src/cli/main.go index f078a93..2ee6e67 100644 --- a/src/cli/main.go +++ b/src/cli/main.go @@ -11,18 +11,19 @@ package main import "C" import ( - "github.com/rdkcentral/unified-wifi-mesh/src/cli/etree" - "unsafe" "fmt" "os" + "strings" "time" - "strings" + "unsafe" + + "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/davecgh/go-spew/spew" + "github.com/rdkcentral/unified-wifi-mesh/src/cli/etree" "golang.org/x/term" - "github.com/davecgh/go-spew/spew" ) const ( @@ -128,7 +129,7 @@ var easyMeshCommands = map[string]EasyMeshCmd { ClientDevicesCmd: {ClientDevicesCmd, 6, "get_sta OneWifiMesh", "", "", ""}, SteerDevicesCmd: {SteerDevicesCmd, 7, "get_sta OneWifiMesh", "get_sta OneWifiMesh 1", "steer_sta OneWifiMesh", ""}, NetworkMetricsCmd: {NetworkMetricsCmd, 8, "", "", "", ""}, - DeviceOnboardingCmd: {DeviceOnboardingCmd, 9, "", "", "", ""}, + DeviceOnboardingCmd: {DeviceOnboardingCmd, 9, "", "", "start_dpp OneWifiMesh", ""}, WiFiEventsCmd: {WiFiEventsCmd, 10, "", "", "", ""}, WiFiResetCmd: {WiFiResetCmd, 11, "get_network OneWifiMesh", "", "reset OneWifiMesh", ""}, DebugCmd: {DebugCmd, 12, "dev_test OneWifiMesh", "", "", ""}, @@ -213,9 +214,7 @@ func newModel(platform string) model { } } -func splitIntoLines(content string) []string { - return strings.Split(content, "\n") -} + func (m model) Init() tea.Cmd { var params *C.em_cli_params_t @@ -349,7 +348,17 @@ func (m *model) execSelectedCommand(cmdStr string, cmdType int) { if cmdStr == value.Title { switch cmdType { case GET: + if value.Title == DeviceOnboardingCmd { + nodes, err := readJSONFile("DPPURI.json") + if err != nil { + spew.Fprintf(m.dump, "Error reading JSON file: %v\n", err) + return + } + m.tree.SetNodes(nodes) + return + } if value.GetCommand == "" { + m.tree.SetNodes([]etree.Node{}) return } m.currentNetNode = C.exec(C.CString(value.GetCommand), C.strlen(C.CString(value.GetCommand)), nil) @@ -389,6 +398,18 @@ func (m *model) execSelectedCommand(cmdStr string, cmdType int) { if value.SetCommand == "" { return } + if value.Title == DeviceOnboardingCmd { + // Write current nodes to a JSON file (could be modified or unmodified) + if err := writeJSONFile(m.tree.Nodes(), "DPPURI_sendable.json"); err != nil { + spew.Fprintf(m.dump, "Error writing JSON: %v\n", err) + return + } + spew.Fdump(m.dump, "Sending DPPURI JSON file") + // Network nodes not needed for DPPURI + C.exec(C.CString(value.SetCommand), C.strlen(C.CString(value.SetCommand)), nil) + return + } + root := m.tree.Nodes() C.exec(C.CString(value.SetCommand), C.strlen(C.CString(value.SetCommand)), m.treeToNodes(&root[0])) } diff --git a/src/cli/utils.go b/src/cli/utils.go new file mode 100644 index 0000000..b7de985 --- /dev/null +++ b/src/cli/utils.go @@ -0,0 +1,224 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/charmbracelet/bubbles/textinput" + "github.com/rdkcentral/unified-wifi-mesh/src/cli/etree" +) + +func splitIntoLines(content string) []string { + return strings.Split(content, "\n") +} + +// convertJSONToNodes recursively converts a JSON data structure into etree.Node slice. +// It handles objects, arrays, strings, numbers, and boolean values. +// +// Parameters: +// - data: interface{} containing unmarshaled JSON data +// +// Returns: +// - []etree.Node: slice of nodes representing the JSON structure +// +// Example JSON input: +// +// { +// "key1": "value1", +// "key2": { +// "nested": "value" +// }, +// "key3": [1,2,3] +// } +func convertJSONToNodes(data interface{}) []etree.Node { + var nodes []etree.Node + + switch v := data.(type) { + case map[string]interface{}: + // Handle object + for key, value := range v { + node := etree.Node{ + Key: key, + Value: textinput.New(), + } + + switch val := value.(type) { + case map[string]interface{}: + node.Type = etree.NodeTypeObject + node.Children = convertJSONToNodes(val) + case []interface{}: + node.Type = etree.NodeTypeArrayObj + node.Children = convertJSONToNodes(val) + case string: + node.Type = etree.NodeTypeString + node.Value.Placeholder = val + case float64: + node.Type = etree.NodeTypeNumber + node.Value.Placeholder = fmt.Sprintf("%v", val) + case bool: + if val { + node.Type = etree.NodeTypeTrue + } else { + node.Type = etree.NodeTypeFalse + } + node.Value.Placeholder = fmt.Sprintf("%v", val) + } + nodes = append(nodes, node) + } + + case []interface{}: + // Handle array + for _, value := range v { + childNodes := convertJSONToNodes(value) + nodes = append(nodes, childNodes...) + } + } + + return nodes +} + +// readJSONFile reads a JSON file and converts its contents into an etree.Node structure. +// The function can handle any valid JSON file with nested objects and arrays. +// +// Parameters: +// - filePath: string path to the JSON file +// +// Returns: +// - []etree.Node: slice of nodes representing the JSON structure +// - error: any error encountered during file reading or JSON parsing +// +// Example: +// +// nodes, err := readJSONFile("config.json") +// if err != nil { +// log.Fatal(err) +// } +func readJSONFile(filePath string) ([]etree.Node, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + var jsonData interface{} + if err := json.Unmarshal(data, &jsonData); err != nil { + return nil, err + } + + nodes := convertJSONToNodes(jsonData) + if len(nodes) == 0 { + return nil, fmt.Errorf("no nodes created from JSON") + } + + return nodes, nil +} + +// convertNodesToJSON converts an etree.Node structure back into a generic interface{} +// that can be marshaled to JSON. It handles objects, arrays, strings, numbers and booleans. +// +// Parameters: +// - nodes: []etree.Node slice containing the tree structure to convert +// +// Returns: +// - interface{}: generic data structure ready for JSON marshaling +// +// Example input: +// nodes := []etree.Node{ +// { +// Key: "root", +// Type: etree.NodeTypeObject, +// Children: []etree.Node{ +// {Key: "string", Type: etree.NodeTypeString, Value: "value"}, +// {Key: "number", Type: etree.NodeTypeNumber, Value: "42"}, +// }, +// }, +// } +func convertNodesToJSON(nodes []etree.Node) interface{} { + if len(nodes) == 0 { + return nil + } + + // Handle single root node + node := nodes[0] + + switch node.Type { + case etree.NodeTypeObject: + result := make(map[string]interface{}) + for _, child := range node.Children { + value := child.Value.Value() + if value == "" { + value = child.Value.Placeholder + } + + switch child.Type { + case etree.NodeTypeObject, etree.NodeTypeArrayObj: + result[child.Key] = convertNodesToJSON([]etree.Node{child}) + case etree.NodeTypeString: + result[child.Key] = value + case etree.NodeTypeNumber: + if f, err := strconv.ParseFloat(value, 64); err == nil { + result[child.Key] = f + } + case etree.NodeTypeTrue: + result[child.Key] = true + case etree.NodeTypeFalse: + result[child.Key] = false + } + } + return result + + case etree.NodeTypeArrayObj: + var result []interface{} + for _, child := range node.Children { + result = append(result, convertNodesToJSON([]etree.Node{child})) + } + return result + } + + return nil +} + +// writeJSONFile writes an etree.Node structure to a JSON file. +// The nodes are first converted to a generic interface{} and then marshaled to JSON +// with proper indentation. The file is created with 0644 permissions. +// +// Parameters: +// - nodes: []etree.Node slice containing the tree structure to write +// - filePath: string path where to write the JSON file +// +// Returns: +// - error: any error encountered during JSON conversion, marshaling or file writing +// +// Example: +// nodes := []etree.Node{ +// { +// Key: "config", +// Type: etree.NodeTypeObject, +// Children: []etree.Node{ +// {Key: "setting", Type: etree.NodeTypeString, Value: "value"}, +// }, +// }, +// } +// err := writeJSONFile(nodes, "config.json") +func writeJSONFile(nodes []etree.Node, filePath string) error { + data := convertNodesToJSON(nodes) + if data == nil { + return fmt.Errorf("invalid node structure") + } + + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("error marshaling JSON: %v", err) + } + + return os.WriteFile(filePath, jsonData, 0644) +} \ No newline at end of file