Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented DPP URI viewer and submitter to Go TUI #125

Merged
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
2 changes: 1 addition & 1 deletion build/cli/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
2 changes: 1 addition & 1 deletion src/cli/em_cmd_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}},
Expand Down
43 changes: 32 additions & 11 deletions src/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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", "", "", ""},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]))
}
Expand Down
224 changes: 224 additions & 0 deletions src/cli/utils.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading