initial commit lets start

This commit is contained in:
Adrian Zuercher
2026-08-28 17:59:35 +02:00
commit 3ba5a189f3
48 changed files with 100075 additions and 0 deletions
View File
+43
View File
@@ -0,0 +1,43 @@
package builders
import (
"encoding/xml"
"plcopenxmlbuilder/models"
"schema"
)
type Twincat3Builder struct {
}
func NewTwincat3Builder() *Twincat3Builder {
return &Twincat3Builder{}
}
func (tc *Twincat3Builder) Marshal(s *schema.Project) ([]byte, error) {
plcopenxml := models.NewPLCopenXML()
proj := plcopenxml.AddProject()
proj.AddFileHeader(s.Information.CompanyName, s.Information.ProductName, s.Information.Version)
proj.AddContentHeader(s.ProjectName)
app := proj.AddApplication(models.URL_PLCOPENXML, models.HANDLE_IMPLEMENTATION)
switch s.GenerateType {
//PLC
case 0:
err := app.AddResources(s)
if err != nil {
return nil, err
}
//Application
case 1:
//Files
case 2:
}
content, err := xml.MarshalIndent(plcopenxml.Project, " ", " ")
content = append([]byte(xml.Header), content...)
if err != nil {
return nil, err
}
return append([]byte(xml.Header), content...), nil
}
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
package facades
import (
"fmt"
"plcopenxmlbuilder/builders"
"schema"
)
type GenerateType int
const (
PLC GenerateType = iota
Application
Files
)
type Builder interface {
Marshal(s *schema.Project) ([]byte, error)
}
func NewBuilder(platform string, generateType GenerateType) (Builder, error) {
var builder Builder
switch platform {
case "twincat3":
builder = builders.NewTwincat3Builder()
default:
return nil, fmt.Errorf("no builder for %s found", platform)
}
return builder, nil
}
+10
View File
@@ -0,0 +1,10 @@
module plcopenxmlbuilder
go 1.26.4
require (
github.com/google/uuid v1.6.0
schema v0.0.0
)
replace schema => ../schema
+2
View File
@@ -0,0 +1,2 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+44
View File
@@ -0,0 +1,44 @@
package models
import (
"encoding/xml"
"fmt"
)
type AddData struct {
XMLName xml.Name `xml:"addData"`
Data []*Data `xml:"data,omitempty"`
}
func NewAddData() *AddData {
return &AddData{}
}
func (a *AddData) AddProjectInformation(name, handleUnknown string) *ProjectInformation {
d := a.addNewData(fmt.Sprintf("%s/%s", name, XML_PROJECTINFORMATION), handleUnknown)
d.ProjectInformation = &ProjectInformation{}
return d.ProjectInformation
}
func (a *AddData) AddApplication(name, handleUnknown string) *Data {
return a.addNewData(fmt.Sprintf("%s/%s", name, XML_APPLICATION), handleUnknown)
}
func (a *AddData) AddTasksettings(name, handleUnknown, kindOfTask, intervalUnit string, interval int) (*TaskSettings, error) {
data := a.addNewData(fmt.Sprintf("%s/%s", name, XML_TASKSETTINGS), handleUnknown)
data.TaskSettings = &TaskSettings{
KindOfTask: kindOfTask,
Interval: interval,
IntervalUnit: intervalUnit,
}
return data.TaskSettings, nil
}
func (a *AddData) addNewData(name, handleUnknown string) *Data {
d := NewData(name, handleUnknown)
a.Data = append(a.Data, d)
return d
}
+10
View File
@@ -0,0 +1,10 @@
package models
import "encoding/xml"
type Array struct {
XMLName xml.Name `xml:"array"`
Lower int `xml:"lower,attr"`
Upper int `xml:"upper,attr"`
BaseType BaseType `xml:"baseType"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type ArrayValue struct {
XMLName xml.Name `xml:"arrayValue"`
Value *Value `xml:"value"`
}
+11
View File
@@ -0,0 +1,11 @@
package models
import (
"encoding/xml"
)
type Attribute struct {
XMLName xml.Name `xml:"Attribute"`
Name string `xml:"Name"`
Value string `xml:"Value"`
}
+10
View File
@@ -0,0 +1,10 @@
package models
import (
"encoding/xml"
)
type Attributes struct {
XMLName xml.Name `xml:"Attributes"`
Attribute []*Attribute `xml:"Attribute"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type BaseType struct {
XMLName xml.Name `xml:"baseType"`
Derived *Derived `xml:"derived,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
package models
import (
"encoding/xml"
)
type Configurations struct {
XMLName xml.Name `xml:"configurations"`
}
+12
View File
@@ -0,0 +1,12 @@
package models
const (
URL_PLCOPENXML = "http://www.3s-software.com/plcopenxml"
XML_PROJECTINFORMATION = "projectinformation"
XML_APPLICATION = "application"
XML_TASKSETTINGS = "tasksettings"
XMLNS_Project = "http://www.plcopen.org/xml/tc6_0200"
HANDLE_IMPLEMENTATION = "implementation"
)
+30
View File
@@ -0,0 +1,30 @@
package models
import (
"encoding/xml"
"time"
)
type ContentHeader struct {
XMLName xml.Name `xml:"contentHeader"`
Name string `xml:"name,attr"`
ModificationDateTime string `xml:"modificationDateTime,attr"`
CoordinateInfo CoordinateInfo `xml:"coordinateInfo"`
AddData *AddData `xml:"addData"`
}
func (c *ContentHeader) SetCurrentCreationDateTime() {
//format "2026-08-25T21:35:38.4736792"
c.ModificationDateTime = time.Now().Format("2006-01-02T15:04:05.0000000")
}
func (c *ContentHeader) SetCoordinateDefault() {
c.CoordinateInfo.Default()
}
func (c *ContentHeader) AddAddData() *AddData {
if c.AddData == nil {
c.AddData = NewAddData()
}
return c.AddData
}
+19
View File
@@ -0,0 +1,19 @@
package models
import (
"encoding/xml"
)
type CoordinateInfo struct {
XMLName xml.Name `xml:"coordinateInfo"`
Fbd Fbd `xml:"fbd"`
Ld Ld `xml:"ld"`
Sfc Sfc `xml:"sfc"`
CreationDateTime string `xml:"creationDateTime,attr"`
}
func (c *CoordinateInfo) Default() {
c.Fbd.Default()
c.Ld.Default()
c.Sfc.Default()
}
+43
View File
@@ -0,0 +1,43 @@
package models
import (
"encoding/xml"
"schema"
)
type Data struct {
XMLName xml.Name `xml:"data"`
Name string `xml:"name,attr"`
HandleUnknown string `xml:"handleUnknown,attr"`
ProjectInformation *ProjectInformation `xml:"ProjectInformation,omitempty"`
Resource *Resourse `xml:"resource,omitempty"`
TaskSettings *TaskSettings `xml:"TaskSettings,omitempty"`
Attributes *Attributes `xml:"Attributes,omitempty"`
InterfaceAsPlainText *InterfaceAsPlainText `xml:"InterfaceAsPlainText,omitempty"`
ObjectId *ObjectId `xml:"ObjectId,omitempty"`
}
func NewData(name, handleUnknown string) *Data {
return &Data{
Name: name,
HandleUnknown: handleUnknown,
}
}
func (d *Data) AddResources(proj *schema.Project) error {
for _, device := range proj.Devices {
res := d.AddResource(device.Name)
err := res.AddTasks(device)
if err != nil {
return err
}
}
return nil
}
func (d *Data) AddResource(name string) *Resourse {
d.Resource = &Resourse{
Name: name,
}
return d.Resource
}
+9
View File
@@ -0,0 +1,9 @@
package models
import (
"encoding/xml"
)
type DataTypes struct {
XMLName xml.Name `xml:"dataTypes"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type Derived struct {
XMLName xml.Name `xml:"derived"`
Name string `xml:"name,attr"`
}
+10
View File
@@ -0,0 +1,10 @@
package models
import (
"encoding/xml"
)
type Documentation struct {
XMLName xml.Name `xml:"documentation"`
Xhtml Xhtml `xml:"xhtml"`
}
+14
View File
@@ -0,0 +1,14 @@
package models
import (
"encoding/xml"
)
type Fbd struct {
XMLName xml.Name `xml:"fbd"`
Scaling Scaling `xml:"scaling"`
}
func (fbd *Fbd) Default() {
fbd.Scaling.Default()
}
+19
View File
@@ -0,0 +1,19 @@
package models
import (
"encoding/xml"
"time"
)
type FileHeader struct {
XMLName xml.Name `xml:"fileHeader"`
CompanyName string `xml:"companyName,attr"`
ProductName string `xml:"productName,attr"`
ProductVersion string `xml:"productVersion,attr"`
CreationDateTime string `xml:"creationDateTime,attr"`
}
func (f *FileHeader) SetCurrentCreationDateTime() {
//format "2026-08-25T21:35:38.4736792"
f.CreationDateTime = time.Now().Format("2006-01-02T15:04:05.0000000")
}
+9
View File
@@ -0,0 +1,9 @@
package models
import "encoding/xml"
type GlobalVars struct {
XMLName xml.Name `xml:"globalVars"`
Variable []*Variable `xml:"variable"`
AddData []*AddData `xml:"addData"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type InitialValue struct {
XMLName xml.Name `xml:"initialValue"`
ArrayValue *ArrayValue `xml:"arrayValue,omitempty"`
}
+10
View File
@@ -0,0 +1,10 @@
package models
import (
"encoding/xml"
)
type Instances struct {
XMLName xml.Name `xml:"instances"`
Configurations Configurations `xml:"configurations"`
}
+11
View File
@@ -0,0 +1,11 @@
package models
import (
"encoding/xml"
)
type InterfaceAsPlainText struct {
XMLName xml.Name `xml:"InterfaceAsPlainText"`
Xmlns string `xml:"xmlns,attr"`
Text string `xml:",chardata"`
}
+14
View File
@@ -0,0 +1,14 @@
package models
import (
"encoding/xml"
)
type Ld struct {
XMLName xml.Name `xml:"ld"`
Scaling Scaling `xml:"scaling"`
}
func (ld *Ld) Default() {
ld.Scaling.Default()
}
+18
View File
@@ -0,0 +1,18 @@
package models
import (
"encoding/xml"
"github.com/google/uuid"
)
type ObjectId struct {
XMLName xml.Name `xml:"ObjectId"`
Value string `xml:",chardata"`
}
func NewObjectId() *ObjectId {
id := uuid.New()
return &ObjectId{Value: id.String()}
}
+18
View File
@@ -0,0 +1,18 @@
package models
type PLCopenXML struct {
Project *Project `xml:"project"`
}
func NewPLCopenXML() *PLCopenXML {
plcopenxml := &PLCopenXML{}
plcopenxml.AddProject()
return plcopenxml
}
func (openXML *PLCopenXML) AddProject() *Project {
openXML.Project = &Project{
Xmlns: XMLNS_Project,
}
return openXML.Project
}
+12
View File
@@ -0,0 +1,12 @@
package models
import (
"encoding/xml"
)
type PouInstance struct {
XMLName xml.Name `xml:"pouInstance"`
Name string `xml:"name,attr"`
TypeName string `xml:"typeName,attr"`
Documentation *Documentation `xml:"documentation,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
package models
import (
"encoding/xml"
)
type Pous struct {
XMLName xml.Name `xml:"pous"`
}
+40
View File
@@ -0,0 +1,40 @@
package models
import (
"encoding/xml"
)
type Project struct {
XMLName xml.Name `xml:"project"`
Xmlns string `xml:"xmlns,attr"`
FileHeader FileHeader `xml:"fileHeader"`
ContentHeader ContentHeader `xml:"contentHeader"`
Types Types `xml:"types"`
Instances Instances `xml:"instances"`
AddData *AddData `xml:"addData"`
}
func (p *Project) AddFileHeader(companyName, productName, productversion string) {
p.FileHeader = FileHeader{
CompanyName: companyName,
ProductName: productName,
ProductVersion: productversion,
}
p.FileHeader.SetCurrentCreationDateTime()
}
func (p *Project) AddContentHeader(projectName string) {
p.ContentHeader = ContentHeader{
Name: projectName,
}
p.FileHeader.SetCurrentCreationDateTime()
p.ContentHeader.SetCoordinateDefault()
p.ContentHeader.AddAddData().AddProjectInformation(URL_PLCOPENXML, HANDLE_IMPLEMENTATION)
}
func (p *Project) AddApplication(url, handleUnknown string) *Data {
if p.AddData == nil {
p.AddData = NewAddData()
}
return p.AddData.AddApplication(url, handleUnknown)
}
+7
View File
@@ -0,0 +1,7 @@
package models
import "encoding/xml"
type ProjectInformation struct {
XMLName xml.Name `xml:"ProjectInformation"`
}
+49
View File
@@ -0,0 +1,49 @@
package models
import (
"encoding/xml"
"plcopenxmlbuilder/utils"
"schema"
)
type Resourse struct {
XMLName xml.Name `xml:"resource"`
Name string `xml:"name,attr"`
Task []*Task `xml:"task"`
GlobalVars *GlobalVars `xml:"globalVars,omitempty"`
}
func (r *Resourse) AddTasks(dev *schema.Device) error {
for _, task := range dev.Tasks {
if err := utils.CheckKindOfTask(task.KindOfTask); err != nil {
return err
}
if err := utils.CheckIntervalUnit(task.IntervalUnit); err != nil {
return err
}
t, err := r.AddTask(task.Name, task.KindOfTask, task.IntervalUnit, task.Interval, task.Priority)
if err != nil {
return err
}
t.AddPouInstances(task)
}
return nil
}
func (r *Resourse) AddTask(name, kindOfTask, intervalUnit string, interval, priority int) (*Task, error) {
t := &Task{
Name: name,
Priority: priority,
}
t.SetInterval(interval)
err := t.AddAddData(kindOfTask, intervalUnit, interval)
if err != nil {
return nil, err
}
r.Task = append(r.Task, t)
return t, nil
}
+16
View File
@@ -0,0 +1,16 @@
package models
import (
"encoding/xml"
)
type Scaling struct {
XMLName xml.Name `xml:"scaling"`
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
func (s *Scaling) Default() {
s.X = 1
s.Y = 1
}
+14
View File
@@ -0,0 +1,14 @@
package models
import (
"encoding/xml"
)
type Sfc struct {
XMLName xml.Name `xml:"sfc"`
Scaling Scaling `xml:"scaling"`
}
func (sfc *Sfc) Default() {
sfc.Scaling.Default()
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type SimpleValue struct {
XMLName xml.Name `xml:"simpleValue"`
Value string `xml:"value,attr"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type StructValue struct {
XMLName xml.Name `xml:"structValue"`
Value []*Value `xml:"value,omitempty"`
}
+60
View File
@@ -0,0 +1,60 @@
package models
import (
"encoding/xml"
"fmt"
"schema"
)
type Task struct {
XMLName xml.Name `xml:"task"`
Name string `xml:"name,attr"`
Interval string `xml:"interval,attr"`
Priority int `xml:"priority,attr"`
PouInstance []*PouInstance `xml:"pouInstance"`
AddData *AddData `xml:"addData"`
}
func (t *Task) AddAddData(kindOfTask, intervalUnit string, interval int) error {
if t.AddData != nil {
return nil
}
t.AddData = NewAddData()
_, err := t.AddData.AddTasksettings(URL_PLCOPENXML, HANDLE_IMPLEMENTATION, kindOfTask, intervalUnit, interval)
if err != nil {
return err
}
return nil
}
// convert interval to plcopenxml format in value in milliseconds
func (t *Task) SetInterval(interval int) {
//convert to secconts
i := interval / 1000
if i < 1 {
i = 0
}
t.Interval = fmt.Sprintf("PT%dS", i)
}
func (t *Task) AddPouInstances(task *schema.Task) {
for _, prg := range task.Programs {
t.AddPouInstance(prg.Name, prg.TypeName)
}
}
func (t *Task) AddPouInstance(name, typeName string) *PouInstance {
pI := &PouInstance{
Name: name,
TypeName: typeName,
Documentation: &Documentation{
Xhtml: Xhtml{
Xmlns: XMLNS,
},
},
}
t.PouInstance = append(t.PouInstance, pI)
return pI
}
+13
View File
@@ -0,0 +1,13 @@
package models
import (
"encoding/xml"
)
type TaskSettings struct {
XMLName xml.Name `xml:"TaskSettings"`
KindOfTask string `xml:"KindOfTask,attr"`
Interval int `xml:"Interval,attr"`
IntervalUnit string `xml:"IntervalUnit,attr"`
Watchdog *Watchdog `xml:"Watchdog"`
}
+8
View File
@@ -0,0 +1,8 @@
package models
import "encoding/xml"
type Type struct {
XMLName xml.Name `xml:"type"`
Array *Array `xml:"array,omitempty"`
}
+11
View File
@@ -0,0 +1,11 @@
package models
import (
"encoding/xml"
)
type Types struct {
XMLName xml.Name `xml:"types"`
DataTypes DataTypes `xml:"dataTypes"`
Pous Pous `xml:"pous"`
}
+9
View File
@@ -0,0 +1,9 @@
package models
import "encoding/xml"
type Value struct {
XMLName xml.Name `xml:"value"`
StructValue *StructValue `xml:"structValue,omitempty"`
SimpleValue *SimpleValue `xml:"simpleValue,omitempty"`
}
+10
View File
@@ -0,0 +1,10 @@
package models
import "encoding/xml"
type Variable struct {
XMLName xml.Name `xml:"variable"`
Type Type `xml:"type"`
InitialValue *InitialValue `xml:"initialValue,omitempty"`
Documentation *Documentation `xml:"documentation,omitempty"`
}
+11
View File
@@ -0,0 +1,11 @@
package models
import (
"encoding/xml"
)
type Watchdog struct {
XMLName xml.Name `xml:"Watchdog"`
Enabled bool `xml:"Enabled,attr"`
TimeUnit string `xml:"TimeUnit,attr"`
}
+15
View File
@@ -0,0 +1,15 @@
package models
import (
"encoding/xml"
)
const (
XMLNS = "http://www.w3.org/1999/xhtml"
)
type Xhtml struct {
XMLName xml.Name `xml:"xhtml"`
Xmlns string `xml:"xmlns,attr"`
Text string `xml:",chardata"`
}
+47
View File
@@ -0,0 +1,47 @@
package test
import (
"plcopenxmlbuilder/facades"
"schema"
"testing"
)
func TestCreatePLCOpenXML(t *testing.T) {
builder, err := facades.NewBuilder("twincat3", facades.PLC)
if err != nil {
t.Log(err)
}
proj := schema.Project{
ProjectName: "CH_BE_STEFFISBURG_SCHOENAU_SPORTANLAGE_TA03",
Information: schema.Information{
CompanyName: "Buehler & Scherler",
ProductName: "PLCopenXML Builder",
Version: "0.0.1",
},
Devices: []*schema.Device{{
Name: "SPS001",
Tasks: []*schema.Task{
{
Name: "Main",
KindOfTask: "Cyclic",
IntervalUnit: "ms",
Interval: 100,
Priority: 10,
Programs: []*schema.Program{
{
Name: "Main",
},
},
},
},
}},
}
xmlBytes, err := builder.Marshal(&proj)
if err != nil {
t.Log(err)
}
t.Log(string(xmlBytes))
}
+32
View File
@@ -0,0 +1,32 @@
package utils
import "fmt"
const (
CYCLIC = "Cyclic"
FREEEWHEEL = "Freewheel"
)
func CheckKindOfTask(kindOfTask string) error {
switch kindOfTask {
case FREEEWHEEL, CYCLIC:
default:
return fmt.Errorf("%s kindOfTask not supported", kindOfTask)
}
return nil
}
const (
SECOND = "s"
MILLISECOND = "ms"
MICROSECOND = "us"
)
func CheckIntervalUnit(intervalUnit string) error {
switch intervalUnit {
case SECOND, MILLISECOND, MICROSECOND:
default:
return fmt.Errorf("%s interval unit not supported", intervalUnit)
}
return nil
}