diff --git a/go/cmd/test.go b/go/cmd/test.go new file mode 100644 index 000000000..03036909c --- /dev/null +++ b/go/cmd/test.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "log" + "time" + + node "git.rwth-aachen.de/acs/public/villas/node/go/pkg" + "git.rwth-aachen.de/acs/public/villas/node/go/pkg/config" + + "github.com/google/uuid" +) + +func main() { + cfg := &config.LoopbackNode{ + Node: config.Node{ + Name: "lo1", + Type: "loopback", + }, + In: config.NodeLoopbackIn{ + Hooks: []interface{}{ + &config.PrintHook{ + Hook: config.Hook{ + Type: "print", + }, + }, + }, + Signals: []config.Signal{ + { + Name: "sig1", + }, + { + Name: "sig2", + }, { + Name: "sig3", + }, + }, + }, + } + + n, err := node.NewNode(cfg, uuid.New()) + if err != nil { + log.Fatalf("Failed to create node: %s", err) + } + defer n.Close() + + if err := n.Check(); err != nil { + log.Fatalf("Failed to check node: %s", err) + } + + if err := n.Prepare(); err != nil { + log.Fatalf("Failed to prepare node: %s", err) + } + + if err := n.Start(); err != nil { + log.Fatalf("Failed to start node: %s", err) + } + defer n.Stop() + + fmt.Printf("%s\n", n.NameFull()) + + smps_send := []node.Sample{ + { + Sequence: 1234, + TimestampOrigin: time.Now(), + Data: []float64{1.1, 2.2, 3.3}, + }, + { + Sequence: 1235, + TimestampOrigin: time.Now(), + Data: []float64{4.4, 5.5, 6.6}, + }, + } + + fmt.Printf("Sent: %+#v\n", smps_send) + + cnt_written := n.Write(smps_send) + smps_received := n.Read(cnt_written) + + fmt.Printf("Received: %+#v\n", smps_received) +} diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 000000000..100158d90 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,5 @@ +module git.rwth-aachen.de/acs/public/villas/node/go + +go 1.17 + +require github.com/google/uuid v1.3.0 diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 000000000..3dfe1c9f2 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/go/pkg/config/hook.go b/go/pkg/config/hook.go new file mode 100644 index 000000000..477a25002 --- /dev/null +++ b/go/pkg/config/hook.go @@ -0,0 +1,10 @@ +package config + +type Hook struct { + Type string `json:"type"` + Priority int `json:"priority,omitempty"` +} + +type PrintHook struct { + Hook +} diff --git a/go/pkg/config/node.go b/go/pkg/config/node.go new file mode 100644 index 000000000..f61b1c9ef --- /dev/null +++ b/go/pkg/config/node.go @@ -0,0 +1,22 @@ +package config + +type Node struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type NodeDir struct { +} + +type NodeLoopbackIn struct { + NodeDir + + Signals []Signal `json:"signals"` + Hooks []interface{} `json:"hooks"` +} + +type LoopbackNode struct { + Node + + In NodeLoopbackIn `json:"in"` +} diff --git a/go/pkg/config/signal.go b/go/pkg/config/signal.go new file mode 100644 index 000000000..2282e500d --- /dev/null +++ b/go/pkg/config/signal.go @@ -0,0 +1,15 @@ +package config + +const ( + SignalTypeFloat = "float" + SignalTypeInteger = "integer" + SignalTypeBoolean = "boolean" + SignalTypeComplex = "complex" +) + +type Signal struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Unit string `json:"unit,omitempty"` + Init float64 `json:"init,omitempty"` +} diff --git a/go/pkg/node.go b/go/pkg/node.go new file mode 100644 index 000000000..a220c60fc --- /dev/null +++ b/go/pkg/node.go @@ -0,0 +1,215 @@ +package pkg + +// #cgo LDFLAGS: -lvillas +// #cgo CFLAGS: -DCGO +// #include +import "C" +import ( + "encoding/json" + "fmt" + "log" + "time" + "unsafe" + + "github.com/google/uuid" +) + +const MAX_SIGNALS = 64 +const NUM_HUGEPAGES = 100 + +func ret2err(ret C.int) error { + if ret == 0 { + return nil + } else { + return fmt.Errorf("ret=%d", ret) + } +} + +type Sample struct { + TimestampOrigin time.Time + TimestampReceived time.Time + Sequence uint + Flags int + Data []float64 +} + +func (s *Sample) ToC() *C.vsample { + return C.sample_pack( + C.uint(s.Sequence), + &C.struct_timespec{ + tv_sec: C.long(s.TimestampOrigin.Unix()), + tv_nsec: C.long(s.TimestampOrigin.Nanosecond()), + }, + &C.struct_timespec{ + tv_sec: C.long(s.TimestampReceived.Unix()), + tv_nsec: C.long(s.TimestampReceived.Nanosecond()), + }, + C.uint(len(s.Data)), + (*C.double)(unsafe.Pointer(&s.Data[0])), + ) +} + +func (s *Sample) FromC(c *C.vsample) { + var tsOrigin C.struct_timespec + var tsReceived C.struct_timespec + + len := C.sample_length(c) + + s.Data = make([]float64, uint(len)) + + C.sample_unpack(c, + (*C.uint)(unsafe.Pointer(&s.Sequence)), + &tsOrigin, + &tsReceived, + (*C.int)(unsafe.Pointer(&s.Flags)), + &len, + (*C.double)(unsafe.Pointer(&s.Data[0])), + ) + + s.TimestampOrigin = time.Unix(int64(tsOrigin.tv_sec), int64(tsOrigin.tv_nsec)) + s.TimestampReceived = time.Unix(int64(tsReceived.tv_sec), int64(tsReceived.tv_nsec)) +} + +type Node struct { + inst *C.vnode +} + +func IsValidNodeNname(name string) bool { + return bool(C.node_is_valid_name(C.CString(name))) +} + +func NewNode(cfg interface{}, sn_uuid uuid.UUID) (*Node, error) { + if js, err := json.Marshal(cfg); err != nil { + return nil, fmt.Errorf("failed to serialize config: %w", err) + } else { + log.Printf("Config = %s\n", js) + n := C.node_new(C.CString(string(js)), C.CString(sn_uuid.String())) + return &Node{n}, nil + } +} + +func (n *Node) Prepare() error { + return ret2err(C.node_prepare(n.inst)) +} + +func (n *Node) Check() error { + return ret2err(C.node_check(n.inst)) +} + +func (n *Node) Start() error { + return ret2err(C.node_start(n.inst)) +} + +func (n *Node) Stop() error { + return ret2err(C.node_stop(n.inst)) +} + +func (n *Node) Pause() error { + return ret2err(C.node_pause(n.inst)) +} + +func (n *Node) Resume() error { + return ret2err(C.node_resume(n.inst)) +} + +func (n *Node) Restart() error { + return ret2err(C.node_restart(n.inst)) +} + +func (n *Node) Close() error { + return ret2err(C.node_destroy(n.inst)) +} + +func (n *Node) Reverse() error { + return ret2err(C.node_reverse(n.inst)) +} + +func (n *Node) Read(cnt int) []Sample { + var csmps = make([]*C.vsample, cnt) + + for i := 0; i < cnt; i++ { + csmps[i] = C.sample_alloc(MAX_SIGNALS) + } + + read := int(C.node_read(n.inst, (**C.vsample)(unsafe.Pointer(&csmps[0])), C.uint(cnt))) + + var smps = make([]Sample, read) + for i := 0; i < read; i++ { + smps[i].FromC(csmps[i]) + C.sample_decref(csmps[i]) + } + + return smps +} + +func (n *Node) Write(smps []Sample) int { + cnt := len(smps) + var csmps = make([]*C.vsample, cnt) + + for i := 0; i < cnt; i++ { + csmps[i] = smps[i].ToC() + } + + return int(C.node_write(n.inst, (**C.vsample)(unsafe.Pointer(&csmps[0])), C.uint(cnt))) +} + +func (n *Node) PollFDs() []int { + var cfds [16]C.int + cnt := int(C.node_poll_fds(n.inst, (*C.int)(unsafe.Pointer(&cfds)))) + + var fds = make([]int, cnt) + for i := 0; i < cnt; i++ { + fds[i] = int(cfds[i]) + } + + return fds +} + +func (n *Node) NetemFDs() []int { + var cfds [16]C.int + cnt := int(C.node_netem_fds(n.inst, (*C.int)(unsafe.Pointer(&cfds)))) + + var fds = make([]int, cnt) + for i := 0; i < cnt; i++ { + fds[i] = int(cfds[i]) + } + + return fds +} + +func (n *Node) IsEnabled() bool { + return bool(C.node_is_enabled(n.inst)) +} + +func (n *Node) Name() string { + return C.GoString(C.node_name(n.inst)) +} + +func (n *Node) NameShort() string { + return C.GoString(C.node_name_short(n.inst)) +} + +func (n *Node) NameFull() string { + return C.GoString(C.node_name_full(n.inst)) +} + +func (n *Node) Details() string { + return C.GoString(C.node_details(n.inst)) +} + +func (n *Node) InputSignalsMaxCount() uint { + return uint(C.node_input_signals_max_cnt(n.inst)) +} + +func (n *Node) OutputSignalsMaxCount() uint { + return uint(C.node_output_signals_max_cnt(n.inst)) +} + +func (n *Node) ToJSON() string { + json_str := C.node_to_json_str(n.inst) + return C.GoString(json_str) +} + +func init() { + C.memory_init(NUM_HUGEPAGES) +}