mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
go: add example for using libvillas in a Go application
This commit is contained in:
parent
56c966f61d
commit
3d1fe23fba
5 changed files with 356 additions and 13 deletions
103
go/cmd/example.go
Normal file
103
go/cmd/example.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package main
|
||||
|
||||
/** Example of using libvillas node-types in Go code
|
||||
*
|
||||
* This example demonstrate how you can use VILLASnode's
|
||||
* node-types from a Go application.
|
||||
*/
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg"
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/config"
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/node"
|
||||
|
||||
"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()
|
||||
|
||||
log.Printf("%s\n", n.NameFull())
|
||||
|
||||
now := time.Now()
|
||||
|
||||
smps_send := []node.Sample{
|
||||
{
|
||||
Sequence: 1234,
|
||||
Timestamps: pkg.Timestamps{
|
||||
Origin: pkg.Timestamp{now.Unix(), now.UnixNano()},
|
||||
},
|
||||
Data: []float64{1.1, 2.2, 3.3},
|
||||
},
|
||||
{
|
||||
Sequence: 1235,
|
||||
Timestamps: pkg.Timestamps{
|
||||
Origin: pkg.Timestamp{now.Unix(), now.UnixNano()},
|
||||
}, Data: []float64{4.4, 5.5, 6.6},
|
||||
},
|
||||
}
|
||||
|
||||
log.Printf("Sent: %+#v\n", smps_send)
|
||||
|
||||
cnt_written := n.Write(smps_send)
|
||||
smps_received := n.Read(cnt_written)
|
||||
|
||||
log.Printf("Received: %+#v\n", smps_received)
|
||||
|
||||
if len(smps_send) != len(smps_received) {
|
||||
log.Printf("Different length")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
if smps_send[0].Data[0] != smps_received[0].Data[0] {
|
||||
log.Printf("Different data")
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
202
go/pkg/node/node.go
Normal file
202
go/pkg/node/node.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
package node
|
||||
|
||||
// #cgo LDFLAGS: -lvillas
|
||||
// #include <villas/node.h>
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg"
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors"
|
||||
)
|
||||
|
||||
const MAX_SIGNALS = 64
|
||||
const NUM_HUGEPAGES = 100
|
||||
|
||||
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 errors.IntToError(int(C.node_prepare(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Check() error {
|
||||
return errors.IntToError(int(C.node_check(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Start() error {
|
||||
return errors.IntToError(int(C.node_start(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Stop() error {
|
||||
return errors.IntToError(int(C.node_stop(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Pause() error {
|
||||
return errors.IntToError(int(C.node_pause(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Resume() error {
|
||||
return errors.IntToError(int(C.node_resume(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Restart() error {
|
||||
return errors.IntToError(int(C.node_restart(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Close() error {
|
||||
return errors.IntToError(int(C.node_destroy(n.inst)))
|
||||
}
|
||||
|
||||
func (n *Node) Reverse() error {
|
||||
return errors.IntToError(int(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)
|
||||
}
|
||||
|
||||
type Sample pkg.Sample
|
||||
|
||||
func (s *Sample) ToC() *C.vsample {
|
||||
return C.sample_pack(
|
||||
C.uint(s.Sequence),
|
||||
&C.struct_timespec{
|
||||
tv_sec: C.long(s.Timestamps.Origin[0]),
|
||||
tv_nsec: C.long(s.Timestamps.Origin[1]),
|
||||
},
|
||||
&C.struct_timespec{
|
||||
tv_sec: C.long(s.Timestamps.Received[0]),
|
||||
tv_nsec: C.long(s.Timestamps.Received[1]),
|
||||
},
|
||||
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.Timestamps.Origin = pkg.Timestamp{int64(tsOrigin.tv_sec), int64(tsOrigin.tv_nsec)}
|
||||
s.Timestamps.Received = pkg.Timestamp{int64(tsReceived.tv_sec), int64(tsReceived.tv_nsec)}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
package main
|
||||
package node_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
node "git.rwth-aachen.de/acs/public/villas/node/go/pkg"
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/config"
|
||||
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/node"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
func TestNode(t *testing.T) {
|
||||
cfg := &config.LoopbackNode{
|
||||
Node: config.Node{
|
||||
Name: "lo1",
|
||||
|
@ -40,24 +39,24 @@ func main() {
|
|||
|
||||
n, err := node.NewNode(cfg, uuid.New())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create node: %s", err)
|
||||
t.Fatalf("Failed to create node: %s", err)
|
||||
}
|
||||
defer n.Close()
|
||||
|
||||
if err := n.Check(); err != nil {
|
||||
log.Fatalf("Failed to check node: %s", err)
|
||||
t.Fatalf("Failed to check node: %s", err)
|
||||
}
|
||||
|
||||
if err := n.Prepare(); err != nil {
|
||||
log.Fatalf("Failed to prepare node: %s", err)
|
||||
t.Fatalf("Failed to prepare node: %s", err)
|
||||
}
|
||||
|
||||
if err := n.Start(); err != nil {
|
||||
log.Fatalf("Failed to start node: %s", err)
|
||||
t.Fatalf("Failed to start node: %s", err)
|
||||
}
|
||||
defer n.Stop()
|
||||
|
||||
fmt.Printf("%s\n", n.NameFull())
|
||||
t.Logf("%s", n.NameFull())
|
||||
|
||||
smps_send := []node.Sample{
|
||||
{
|
||||
|
@ -72,10 +71,17 @@ func main() {
|
|||
},
|
||||
}
|
||||
|
||||
fmt.Printf("Sent: %+#v\n", smps_send)
|
||||
t.Logf("Sent: %+#v", smps_send)
|
||||
|
||||
cnt_written := n.Write(smps_send)
|
||||
smps_received := n.Read(cnt_written)
|
||||
if cnt_written != len(smps_send) {
|
||||
t.Fatalf("Failed to send all samples")
|
||||
}
|
||||
|
||||
fmt.Printf("Received: %+#v\n", smps_received)
|
||||
smps_received := n.Read(cnt_written)
|
||||
if len(smps_received) != cnt_written {
|
||||
t.Fatalf("Failed to receive samples back")
|
||||
}
|
||||
|
||||
t.Logf("Received: %+#v", smps_received)
|
||||
}
|
|
@ -18,6 +18,7 @@ type Timestamps struct {
|
|||
}
|
||||
|
||||
type Sample struct {
|
||||
Flags int `json:"-"`
|
||||
Timestamps Timestamps `json:"ts"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
Data []float64 `json:"data"`
|
||||
|
@ -43,3 +44,29 @@ func (s Sample) Bytes() []byte {
|
|||
b, _ := json.Marshal(s)
|
||||
return b
|
||||
}
|
||||
|
||||
func (s Sample) Equal(t Sample) bool {
|
||||
if s.Flags != t.Flags {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.Sequence != t.Sequence {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.Timestamps != t.Timestamps {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(s.Data) != len(t.Data) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, va := range s.Data {
|
||||
if vb := t.Data[i]; va != vb {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -153,6 +153,11 @@ add_library(villas SHARED ${LIB_SRC})
|
|||
target_include_directories(villas PUBLIC ${INCLUDE_DIRS})
|
||||
target_link_libraries(villas PUBLIC ${LIBRARIES})
|
||||
|
||||
# We need to link with -Bsymbolic in order to use link libvillas
|
||||
# with Go code (Cgo)
|
||||
# See also: https://stackoverflow.com/a/67299849
|
||||
target_link_options(villas PUBLIC "-Wl,-Bsymbolic")
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(villas PRIVATE -Wl,-all_load ${WHOLE_ARCHIVES} -Wl,-noall_load)
|
||||
else()
|
||||
|
|
Loading…
Add table
Reference in a new issue