Skip to content

Commit

Permalink
Add handling of unimplemented points
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Jul 4, 2019
1 parent 4c2adf6 commit ef02c56
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 3 deletions.
63 changes: 62 additions & 1 deletion impl/impl_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package impl

import (
"github.com/crabmusket/gosunspec/spi"
"math"
"testing"

"github.com/crabmusket/gosunspec"
"github.com/crabmusket/gosunspec/spi"
)

func TestCompletePointInterface(t *testing.T) {
Expand All @@ -16,3 +19,61 @@ func TestCompleteDevice(t *testing.T) {
func TestCompleteArray(t *testing.T) {
_ = spi.ArraySPI((&array{}))
}

func TestNotImplemented(t *testing.T) {
cases := []struct {
e bool
v interface{}
}{
// float32
{false, float32(0)},
{true, math.Float32frombits(0x7fc00000)},
// int
{false, int16(0)},
{false, int32(0)},
{false, int64(0)},
{true, int16(-0x8000)},
{true, int32(-0x80000000)},
{true, int64(-0x8000000000000000)},
// uint
{false, uint16(0)},
{false, uint32(0)},
{false, uint64(0)},
{true, uint16(0xFFFF)},
{true, uint32(0xFFFFFFFF)},
{true, uint64(0xFFFFFFFFFFFFFFFF)},
// ipaddr
{false, sunspec.Ipaddr{0xFF, 0xFF, 0xFF, 0xFF}},
{false, sunspec.Ipv6addr{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
{true, sunspec.Ipaddr{0, 0, 0, 0}},
{true, sunspec.Ipv6addr{0, 0, 0, 0, 0, 0}},
// eui48
{false, sunspec.Eui48{0, 0, 0, 0, 0, 0}},
{true, sunspec.Eui48{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
// bitfield
{false, sunspec.Bitfield16(0)},
{false, sunspec.Bitfield32(0)},
{true, sunspec.Bitfield16(0xFFFF)},
{true, sunspec.Bitfield32(0xFFFFFFFF)},
// enum
{false, sunspec.Enum16(0)},
{false, sunspec.Enum32(0)},
{true, sunspec.Enum16(0xFFFF)},
{true, sunspec.Enum32(0xFFFFFFFF)},
}

for _, c := range cases {
p := point{
err: nil,
value: c.v,
}

if c.e != p.NotImplemented() {
t.Errorf("expected %v, got %v", c.e, c.v)
}

if v, ok := p.Value().(sunspec.NotImplemented); !ok {
t.Errorf("expected sunspec.NotImplemented, got %v", v)
}
}
}
94 changes: 92 additions & 2 deletions impl/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"encoding/binary"
"errors"
"fmt"
"math"
"strconv"

"github.com/crabmusket/gosunspec"
"github.com/crabmusket/gosunspec/smdx"
"github.com/crabmusket/gosunspec/spi"
"github.com/crabmusket/gosunspec/typelabel"
"github.com/crabmusket/gosunspec/typelen"
"math"
"strconv"
)

var (
Expand Down Expand Up @@ -89,6 +90,80 @@ func (p *point) Length() uint16 {
}
}

// NotImplemented validates if the actual value matches any of the
// unimplemented values according to the specification. In this case
// it will return true. NotImplemented can be used to check if the
// results returned by any of the getter functions is valid.
func (p *point) NotImplemented() bool {
p.checkerror()

notImplemented := false
switch v := p.value.(type) {
case float32:
if 0x7fc00000 == math.Float32bits(v) {
notImplemented = true
}
case int16:
if v == int16(math.MinInt16) {
notImplemented = true
}
case int32:
if v == int32(math.MinInt32) {
notImplemented = true
}
case int64:
if v == int64(math.MinInt64) {
notImplemented = true
}
case uint16:
if v == uint16(math.MaxUint16) {
notImplemented = true
}
case uint32:
if v == uint32(math.MaxUint32) {
notImplemented = true
}
case uint64:
if v == uint64(math.MaxUint64) {
notImplemented = true
}
case sunspec.Enum16:
if uint16(v) == uint16(math.MaxUint16) {
notImplemented = true
}
case sunspec.Enum32:
if uint32(v) == uint32(math.MaxUint32) {
notImplemented = true
}
case sunspec.Bitfield16:
if uint16(v) == uint16(math.MaxUint16) {
notImplemented = true
}
case sunspec.Bitfield32:
if uint32(v) == uint32(math.MaxUint32) {
notImplemented = true
}
case sunspec.Ipaddr:
// 4 bytes
if binary.BigEndian.Uint32(v[0:]) == 0 {
notImplemented = true
}
case sunspec.Ipv6addr:
// 2x8 bytes
if binary.BigEndian.Uint64(v[0:]) == 0 && binary.BigEndian.Uint64(v[8:]) == 0 {
notImplemented = true
}
case sunspec.Eui48:
// 4+2 bytes
if binary.BigEndian.Uint32(v[0:]) == uint32(math.MaxUint32) &&
binary.BigEndian.Uint16(v[4:]) == uint16(math.MaxUint16) {
notImplemented = true
}
}

return notImplemented
}

// Scales the point value with the associated scaling factor,
// if any, returning a float64 value.
func (p *point) ScaledValue() float64 {
Expand Down Expand Up @@ -118,6 +193,12 @@ func (p *point) ScaledValue() float64 {
default:
panic(fmt.Errorf("attempt to use non-numeric value as scaled value: point=%s", p.Id()))
}

// Unimplemented value
if p.NotImplemented() {
return math.NaN()
}

sf := sunspec.ScaleFactor(0)
if p.scaleFactor != nil {
sf = p.scaleFactor.ScaleFactor()
Expand All @@ -126,12 +207,21 @@ func (p *point) ScaledValue() float64 {
sf = sunspec.ScaleFactor(v)
}
}

// Unimplemented scale factor value
if int16(sf) == int16(math.MinInt16) {
return math.NaN()
}

return f * math.Pow(10, float64(sf))
}

// The value of the point, without regard to its actual type.
func (p *point) Value() interface{} {
p.checkerror()
if p.NotImplemented() {
return sunspec.NotImplemented(struct{}{})
}
return p.value
}

Expand Down
14 changes: 14 additions & 0 deletions sunspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type Ipv6addr [16]byte
// A 16-bit pad register. Always 0x8000.
type Pad uint16

// An unimplemented point's value
type NotImplemented interface{}

// A 16-bit scaling factor used to scale the value of some other integer register.
type ScaleFactor int16

Expand Down Expand Up @@ -309,12 +312,23 @@ type Point interface {
SetUint32(v uint32)
SetUint64(v uint64)

// NotImplemented validates if the value is unimplemented according to
// the specification. It can be used to check if the results
// returned by any of the getter functions is valid.
// This function does not- for sake of simplicity- validate Acc16/32/64
// and String types. These can be checked via their natural zero value.
NotImplemented() bool

// Answer the scaled value of the point as a float64. This method
// will panic if the Error() method of either the point or the
// related scaling factor is not nil.
// ScaledValue() checks for unimplemented values using NotImplemented()
// and returns NaN in this case.
ScaledValue() float64

// Answer the value of the point. This method will panic if Error() is not nil.
// Value checks for unimplemented values using NotImplemented()
// and returns the NotImplemented type in this case.
Value() interface{}

// Set the value associated with the point. This method will panic if the value
Expand Down

0 comments on commit ef02c56

Please sign in to comment.