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

Client-api #3

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
67f2ced
add bacnet package
SammyOina Aug 28, 2023
354108b
set base types and constants
SammyOina Aug 28, 2023
c46d913
add error classes and codes
SammyOina Aug 28, 2023
2e6bbc4
encode integers
SammyOina Aug 29, 2023
6c17e28
add objects
SammyOina Aug 29, 2023
498161a
encoding date time and init property value
SammyOina Aug 29, 2023
fc6b475
add who is
SammyOina Aug 30, 2023
1155568
add npdu
SammyOina Aug 31, 2023
b610764
testing whoIs
SammyOina Sep 4, 2023
12b76cc
add apdu
SammyOina Sep 5, 2023
732dc10
reorganize module
SammyOina Sep 5, 2023
292c949
add read property request
SammyOina Sep 8, 2023
f231ed7
add read property
SammyOina Sep 10, 2023
76792fa
add iam and you are
SammyOina Sep 11, 2023
6b2822e
add bacnet Value
SammyOina Sep 12, 2023
0c7837d
Fix error handling in BACnetValue.Decode()
SammyOina Sep 13, 2023
42fcc50
Fix decoding error for object_identifier and property_identifier in R…
SammyOina Sep 13, 2023
b8a8db0
Fix decoding error in BACnetValue.Decode()
SammyOina Sep 14, 2023
079d92b
finish implementing bacnet value
SammyOina Sep 15, 2023
3eb993e
Fix issue with unnecessary print statements and add BACnet header dec…
SammyOina Sep 15, 2023
62e09a3
Fix incorrect comparison in IAM.go
SammyOina Sep 18, 2023
f799994
Add new file writeProperty.go for writing a BACnet property value
SammyOina Sep 18, 2023
524fc09
Add BACnet client implementation
SammyOina Sep 19, 2023
b786a58
implement UDP/BACnetIP transport
SammyOina Sep 19, 2023
8cd1d22
Fix missing newline at the end of the file
SammyOina Sep 20, 2023
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
174 changes: 174 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package bacnet

import (
"context"
"errors"
"fmt"

"github.com/absmach/bacnet/pkg/bacnet"
"github.com/absmach/bacnet/pkg/encoding"
"github.com/absmach/bacnet/pkg/transport"
"golang.org/x/sync/errgroup"
)

var _ Client = (*client)(nil)

var errNoResponse = errors.New("no response received")

type Client interface {
// ReadProperty provides interface to readproperty and return the value read.
ReadProperty(ctx context.Context, address string, request bacnet.ReadPropertyRequest) ([]bacnet.BACnetValue, error)
// WriteProperty provides an interface to write a property value and returns a nil error if successful.
WriteProperty(ctx context.Context, address string, request bacnet.WritePropertyRequest) error
}

type client struct {
transport transport.Transport
}

// NewClient creates a new bacnet client given the transport interface.
func NewClient(transport transport.Transport) Client {
return &client{
transport: transport,
}
}

// ReadProperty provides interface to readproperty and return the value read.
func (c *client) ReadProperty(ctx context.Context, address string, request bacnet.ReadPropertyRequest) ([]bacnet.BACnetValue, error) {
destination, err := bacnet.NewBACnetAddress(0, nil, address)
if err != nil {
fmt.Println("dest err")
return []bacnet.BACnetValue{}, err
}
npdu := bacnet.NewNPDU(destination, nil, nil, nil)
npdu.Control.SetDataExpectingReply(true)
npdu.Control.SetNetworkPriority(bacnet.NormalMessage)

npduBytes, err := npdu.Encode()
if err != nil {
fmt.Println("npdu err")
return []bacnet.BACnetValue{}, err
}

apdu := bacnet.APDU{
PduType: bacnet.PDUTypeConfirmedServiceRequest,
ServiceChoice: byte(bacnet.ReadProperty),
SegmentedResponseAccepted: false,
MaxSegmentsAccepted: bacnet.BacnetMaxSegments(encoding.NoSegmentation),
InvokeID: 0,
}

mes := append(npduBytes, apdu.Encode()...)
mes = append(mes, request.Encode()...)

res := make(chan []byte, 1)
var eg errgroup.Group

eg.Go(func() error {
defer close(res)
return c.transport.Send(ctx, address, mes, int(bacnet.BVLCOriginalBroadcastNPDU), res)
})

if err := eg.Wait(); err != nil {
fmt.Println("send err")
return []bacnet.BACnetValue{}, err
}

select {
case <-ctx.Done():
return []bacnet.BACnetValue{}, ctx.Err()
case response, ok := <-res:
if !ok {
fmt.Println("not ok err")
return []bacnet.BACnetValue{}, errNoResponse
} else {
blvc := bacnet.BVLC{BVLLTypeBACnetIP: 0x81}
headerLength, _, _, err := blvc.Decode(response, 0)
if err != nil {
fmt.Println("blvc err")
return []bacnet.BACnetValue{}, err
}
npduRes := bacnet.NPDU{Version: 1}
npduLen, err := npduRes.Decode(response, headerLength)
if err != nil {
fmt.Println("npdu res err")
return []bacnet.BACnetValue{}, err
}
apduRes := bacnet.APDU{}
apduLen := apduRes.Decode(response, headerLength+npduLen)
readPropACK := bacnet.ReadPropertyACK{}
if _, err = readPropACK.Decode(response, headerLength+npduLen+apduLen-2, len(response)); err != nil {
fmt.Println("readprop dec err")
return []bacnet.BACnetValue{}, err
}
return readPropACK.PropertyValue, nil
}
default:
fmt.Println("default err")
return []bacnet.BACnetValue{}, errNoResponse
}
}

// WriteProperty provides an interface to write a property value and returns a nil error if successful.
func (c *client) WriteProperty(ctx context.Context, address string, request bacnet.WritePropertyRequest) error {
destination, err := bacnet.NewBACnetAddress(0, nil, address)
if err != nil {
return err
}
npdu := bacnet.NewNPDU(destination, nil, nil, nil)
npdu.Control.SetDataExpectingReply(true)
npdu.Control.SetNetworkPriority(bacnet.NormalMessage)

npduBytes, err := npdu.Encode()
if err != nil {
return err
}

apdu := bacnet.APDU{
PduType: bacnet.PDUTypeConfirmedServiceRequest,
ServiceChoice: byte(bacnet.WriteProperty),
SegmentedResponseAccepted: false,
MaxSegmentsAccepted: bacnet.BacnetMaxSegments(encoding.NoSegmentation),
InvokeID: 0,
}

mes := append(npduBytes, apdu.Encode()...)
mes = append(mes, request.Encode()...)

res := make(chan []byte, 1)
var eg errgroup.Group

eg.Go(func() error {
defer close(res)
return c.transport.Send(ctx, address, mes, int(bacnet.BVLCOriginalBroadcastNPDU), res)
})

if err := eg.Wait(); err != nil {
return err
}

select {
case <-ctx.Done():
return ctx.Err()
case response, ok := <-res:
if !ok {
return errNoResponse
} else {
blvc := bacnet.BVLC{BVLLTypeBACnetIP: 0x81}
headerLength, _, _, err := blvc.Decode(response, 0)
if err != nil {
return err
}
npduRes := bacnet.NPDU{Version: 1}
npduLen, err := npduRes.Decode(response, headerLength)
if err != nil {
return err
}
apduRes := bacnet.APDU{}
_ = apduRes.Decode(response, headerLength+npduLen)
return nil
}
default:
return errNoResponse
}
}
30 changes: 30 additions & 0 deletions example/readProperty/readProperty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"context"
"fmt"
"log"

bacClient "github.com/absmach/bacnet"
"github.com/absmach/bacnet/pkg/bacnet"
"github.com/absmach/bacnet/pkg/encoding"
"github.com/absmach/bacnet/pkg/transport/udp"
)

func main() {
transportClient, err := udp.NewClient("127.0.0.5:47809")
if err != nil {
log.Fatal(err)
}
defer transportClient.Close()
client := bacClient.NewClient(transportClient)
req := bacnet.ReadPropertyRequest{
PropertyIdentifier: encoding.PresentValue,
ObjectIdentifier: &bacnet.ObjectIdentifier{Type: encoding.AnalogInput, Instance: 10},
}
val, err := client.ReadProperty(context.Background(), "127.0.0.6:47809", req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", val)
}
127 changes: 127 additions & 0 deletions example/whoIs/whois.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"fmt"
"log"
"net"
"time"

"github.com/absmach/bacnet/pkg/bacnet"
"github.com/absmach/bacnet/pkg/transport"
"github.com/absmach/bacnet/pkg/transport/udp"
)

func main() {

var highLimit, lowLimit uint32 = 4000000, 0
req := bacnet.WhoIs{
HighLimit: &highLimit,
LowLimit: &lowLimit,
}
whoisBytes := req.Encode()

broads, err := udp.GetBroadcastAddress("127.0.0.6", 47809)
if err != nil {
log.Fatalf("failed to encode npdu with error %v", err)
}
broads, err = bacnet.NewBACnetAddress(0xFFFF, nil, "127.0.0.255:47809")
if err != nil {
log.Fatal(err)
}

npdu := bacnet.NewNPDU(broads, nil, nil, nil)
npdu.Control.SetNetworkPriority(bacnet.NormalMessage)
npduBytes, err := npdu.Encode()
if err != nil {
log.Fatalf("failed to encode npdu with error %v", err)
}

apdu := bacnet.APDU{
PduType: bacnet.PDUTypeUnconfirmedServiceRequest,
ServiceChoice: byte(bacnet.ServiceChoiceWhoIs),
}

apduBytes := apdu.Encode()

mes := append(npduBytes, apduBytes...)
mes = append(mes, whoisBytes...)

blvc := bacnet.NewBVLC(transport.IP)
blvcBytes := blvc.Encode(bacnet.BVLCOriginalBroadcastNPDU, uint16(len(mes)+4))
message := append(blvcBytes, mes...)

// Define the BACnet broadcast address (255.255.255.255:47808)
remoteAddr, err := net.ResolveUDPAddr("udp", "127.0.0.6:47809")
if err != nil {
fmt.Println("Error resolving remote address:", err)
return
}

localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
fmt.Println("Error: ", err)
return
}

// Create a UDP connectionBACnetAddress
conn, err := net.DialUDP("udp", localAddr, remoteAddr)
if err != nil {
fmt.Println("Error creating UDP connection:", err)
return
}
defer conn.Close()

// Send the WhoIsRequest packet
_, err = conn.Write(message)
if err != nil {
log.Fatal("Error sending WhoIsRequest:", err)
}

// Wait for responses
buffer := make([]byte, 1500)
conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // Set a timeout for responses

for {
n, _, err := conn.ReadFromUDP(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
// Timeout reached, no more responses
log.Println("No more responses received.")
break
}
log.Println("Error reading response:", err)
break
}

// Process the response (you'll need to parse BACnet responses here)
response := buffer[:n]
log.Printf("Received response: %X\n", response)
blvc := bacnet.BVLC{BVLLTypeBACnetIP: 0x81}
headerLength, function, msgLength, err := blvc.Decode(response, 0)
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
fmt.Printf("headerLength %v BVLCfunction %v msgLen %v\n", headerLength, function, msgLength)
fmt.Println("blvc", blvc)
fmt.Println(response[headerLength:])
npdu := bacnet.NPDU{Version: 1}
npduLen, err := npdu.Decode(response, headerLength)
if err != nil {
log.Fatal(err)
}
fmt.Println("npdu", npdu)
fmt.Println(response[headerLength+npduLen:])
apdu := bacnet.APDU{}
apduLen := apdu.Decode(response, headerLength+npduLen)
fmt.Println("apdu", apdu)
fmt.Println(response[headerLength+npduLen+apduLen:])
iam := bacnet.IAmRequest{}
iamLen, err := iam.Decode(response, headerLength+npduLen+apduLen)
if err != nil {
log.Fatal(err)
}
fmt.Println("iam", iam)
fmt.Println(response[headerLength+npduLen+apduLen+iamLen:])
}
}
31 changes: 31 additions & 0 deletions example/writeProperty/writeProperty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"context"
"log"

bacClient "github.com/absmach/bacnet"
"github.com/absmach/bacnet/pkg/bacnet"
"github.com/absmach/bacnet/pkg/encoding"
"github.com/absmach/bacnet/pkg/transport/udp"
)

func main() {
transportClient, err := udp.NewClient("127.0.0.5:47809")
if err != nil {
log.Fatal(err)
}
defer transportClient.Close()
client := bacClient.NewClient(transportClient)
valTag := encoding.Real
req := bacnet.WritePropertyRequest{
PropertyIdentifier: encoding.PresentValue,
ObjectIdentifier: bacnet.ObjectIdentifier{Type: encoding.AnalogInput, Instance: 10},
PropertyValue: []bacnet.BACnetValue{{Tag: &valTag, Value: float32(22.55)}},
}
if err := client.WriteProperty(context.Background(), "127.0.0.6:47809", req); err == nil {
log.Println("successful write")
} else {
log.Fatal(err)
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/absmach/bacnet

go 1.21.0

require golang.org/x/sync v0.3.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
Loading