package main import ( "encoding/csv" "encoding/xml" "flag" "fmt" "io" "os" "sort" "github.com/BurntSushi/toml" ) type Project struct { XMLName xml.Name `xml:"Project"` ChannelList []Channel `xml:"ChannelList>Channel"` } type Channel struct { Name string `xml:"Name"` DeviceList []Device `xml:"DeviceList>Device"` } type Device struct { Name string `xml:"Name"` TagList []Tag `xml:"TagList>Tag"` } type Tag struct { Name string `xml:"Name"` Description string `xml:"Description"` DataType string `xml:"DataType"` } type TelegrafConfig struct { Inputs struct { OPCUA []struct { Nodes []map[string]interface{} `toml:"nodes"` } `toml:"inputs.opcua"` } `toml:"inputs"` } func printStatistics(project Project) { groupSet := make(map[string]bool) roomSet := make(map[string]bool) for _, channel := range project.ChannelList { groupSet[channel.Name] = true for _, device := range channel.DeviceList { roomSet[fmt.Sprintf("%s.%s", channel.Name, device.Name)] = true } } fmt.Printf("Statistics:\n") fmt.Printf(" Number of groups: %d\n", len(groupSet)) fmt.Printf(" Number of rooms: %d\n", len(roomSet)) } func main() { // Parse CLI args inputPath := flag.String("telegraf_template", "", "Input telegraf.conf file") outputPath := flag.String("telegraf_out", "", "Output telegraf.conf file") csvPath := flag.String("csv_out", "", "Write documentation to CSV file") flag.Parse() if flag.NArg() != 1 { fmt.Println("Usage: program ") flag.PrintDefaults() return } // Need either both or neither of telegraf input/output if *inputPath != "" && *outputPath == "" || *inputPath == "" && *outputPath != "" { fmt.Println("Error: Either both or neither of -telegraf_template and -output_file must be provided.") return } kepwareXML := flag.Arg(0) // Read the XML file xmlFile, err := os.Open(kepwareXML) if err != nil { fmt.Println("Error opening file:", err) return } defer xmlFile.Close() byteValue, _ := io.ReadAll(xmlFile) var project Project xml.Unmarshal(byteValue, &project) /* Telegraf config generation */ if *inputPath != "" { // Read the base Telegraf config var config map[string]interface{} if _, err := toml.DecodeFile(*inputPath, &config); err != nil { fmt.Println("Error reading base config:", err) return } // Print statistics printStatistics(project) // Generate node definitions var nodes []map[string]interface{} for _, channel := range project.ChannelList { for _, device := range channel.DeviceList { for _, tag := range device.TagList { // Group "Razno" are general PLCs, not bound to specific rooms, so we use "plc" instead of "room" plc_tag := "room" if channel.Name == "Razno" { plc_tag = "plc" } node := map[string]interface{}{ "name": tag.Name, "identifier_type": "s", "namespace": "2", "identifier": fmt.Sprintf("%s.%s.%s", channel.Name, device.Name, tag.Name), "tags": [][]string{ {"group", channel.Name}, {plc_tag, device.Name}, }, } nodes = append(nodes, node) } } } fmt.Printf(" Total number of nodes: %d\n", len(nodes)) // Update the config inputs, ok := config["inputs"].(map[string]interface{}) if !ok { fmt.Println("Error: 'inputs' section not found in config") return } opcuaInputs, ok := inputs["opcua_listener"].([]map[string]interface{}) if !ok || len(opcuaInputs) == 0 { fmt.Println("Warn: 'inputs.opcua_listener' section not found in config") // return } else { opcuaInputs[0]["nodes"] = nodes } opcuaListenerInputs, ok := inputs["opcua"].([]map[string]interface{}) if !ok || len(opcuaListenerInputs) == 0 { fmt.Println("Warn: 'inputs.opcua' section not found in config") // return } else { opcuaListenerInputs[0]["nodes"] = nodes } // Write the updated config to a new file f, err := os.Create(*outputPath) if err != nil { fmt.Println("Error creating output file:", err) return } defer f.Close() encoder := toml.NewEncoder(f) encoder.Indent = " " if err := encoder.Encode(config); err != nil { fmt.Println("Error encoding TOML:", err) return } fmt.Printf("Config written to %s\n", *outputPath) } /* Generate CSV documentation (optional) */ if *csvPath != "" { uniqueTags := make(map[string]Tag) for _, channel := range project.ChannelList { for _, device := range channel.DeviceList { for _, tag := range device.TagList { uniqueTags[fmt.Sprintf("%s.%s.%s", tag.Name, tag.Description, tag.DataType)] = tag } } } fmt.Printf(" Total number of unique tags: %d\n", len(uniqueTags)) // Sort the tags alphabetically by name keys := make([]string, 0, len(uniqueTags)) for k := range uniqueTags { keys = append(keys, k) } sort.Strings(keys) // Write the CSV file f, err := os.Create(*csvPath) if err != nil { fmt.Println("Error creating CSV file:", err) return } defer f.Close() writer := csv.NewWriter(f) defer writer.Flush() writer.Write([]string{"DataType", "Name", "Description"}) for _, k := range keys { writer.Write([]string{uniqueTags[k].DataType, uniqueTags[k].Name, uniqueTags[k].Description}) } fmt.Printf("CSV documentation written to %s\n", *csvPath) } }