vedirect

package module
v0.0.0-...-4e34b13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2024 License: MIT Imports: 20 Imported by: 1

README

Go tools for VE.Direct

go get github.com/brianolson/vedirect
import "github.com/brianolson/vedirect"

Read serial from Victron Energy devices. Parse and make available as Go objects or JSON. Some utility code for dealing with the records.

See example apps cmd/vedump and cmd/vesend

A series of records is often compressed to be only the fields that change. For example, in the raw serial protocol the Product ID and Serial Number will be in every record printed every second, but when I return a series of records those are in the first record and not the next 999. If the voltage changes from one record to the next but the amperage doesn't, the amperage won't be in the next record. The full record for any time can be reconstructed by starting with the first record and applying each next record as an update.

A timestamp record "_t" is added to each record at the unix milliseconds* when the record was fully received and parsed from the serial port. ( * time since 1970-01-01 00:00:00 UTC )

vedump

cmd/vedump is a trivial program that reads the statuses posted to the serial port and prints them as json-per-line records to stdandard out.

vesend

vesend reads from a serial port and optionally:

  • POST data to some URL
  • server web interface and API with recent data
  • poll MPPT internal temperature register via HEX protocol

vesend started as a tool to collect data from the serial port and upload it to a cloud server, but grew to do more.

vesend -dev /dev/serial/by-id/usb-VictronEnergy_BV_VE_Direct_cable_*-port0  -post 'https://127.0.0.1:1234/my_post_receiver' -send-period 1000 -z -serve :8080
curl http://127.0.0.1:8080/s/index.html
curl http://127.0.0.1:8080/ve.json

Documentation

Index

Constants

View Source
const DefaultBinSeconds = 60
View Source
const DefaultKeepCount = 20000

Variables

View Source
var DebugEnabled bool = false
View Source
var DebugWriter io.Writer = os.Stderr
View Source
var ErrHexDataShort = errors.New("VE HEX data too short for desired register")
View Source
var ErrHexTypeUnknown = errors.New("VE HEX data not a known register value type")
View Source
var ErrNoOutput = errors.New("no output configured")
View Source
var ErrNoTime = errors.New("record lacks _t time")
View Source
var ErrNotANumber = errors.New("could not convert non-number to int64")
View Source
var ErrNotData error = errors.New("VE HEX message is not 07 or 0A data")
View Source
var ErrTimeWrongType = errors.New("_t record wrong type not int64")
View Source
var IntFields map[string]string

IntFields map field name to unit description (if any).

Some fields will have empty string for unit description.

View Source
var OtherFields map[string]bool

OtherFields is a set of known VE.Direct fields that are not int.

isKnownOtherField := vedirect.OtherFields["BLAH"]
View Source
var ParseRecordDebug io.Writer
View Source
var RegTypeNameToRegType map[string]RegType = map[string]RegType{
	"u8":  RegType_u8,
	"u16": RegType_u16,
	"u32": RegType_u32,
	"s8":  RegType_s8,
	"s16": RegType_s16,
	"s32": RegType_s32,
	"unk": RegType_unk,
}

Functions

func FloatWholeUnits

func FloatWholeUnits(v int64, unit string) (float64, string)

FloatWholeUnits uses the unit string from IntFields to convert some values into float64 of their whole unit. e.g. (10_000, "mV)" -> (10.0, "V")

If there was no conversion, unit returned is same as passed in.

func ParseRecord

func ParseRecord(rec map[string]string) map[string]interface{}

ParseRecord will parse some field values to int64

func ParseRecordField

func ParseRecordField(k string, v any) any

func ParseRecordFieldString

func ParseRecordFieldString(k, v string) any

func ParsedRecordDeltas

func ParsedRecordDeltas(alldata []map[string]interface{}) []map[string]interface{}

ParsedRecordDeltas converts records from ParseRecord() into a list of record deltas. The first record has full data and each following record only has fields that changed.

func ParsedRecordRebuild

func ParsedRecordRebuild(deltas []map[string]interface{})

Convert *in-place* deltas into whole records

func StringRecordDeltas

func StringRecordDeltas(batch []map[string]string, deltas []map[string]interface{}, keyframePeriod int) []map[string]interface{}

StringRecordDeltas makes a list of deltas. The first record has all its fields, each record after only has fields that have changed. passed in deltas object is appeneded to, or may be nil. Output records have ParseRecord applied

Types

type Command

type Command byte
const (
	// Enter bootloader = 0x00
	Ping       Command = 1
	AppVersion Command = 3
	ProductId  Command = 4
	Restart    Command = 6
	Get        Command = 7
	Set        Command = 8
	Async      Command = 0xA
)

type Option

type Option int
const (
	AddTime Option = 1
)

type RecTimeSort

type RecTimeSort []map[string]interface{}

func (*RecTimeSort) Len

func (rts *RecTimeSort) Len() int

Len is part of sort.Interface

func (*RecTimeSort) Less

func (rts *RecTimeSort) Less(i, j int) bool

func (*RecTimeSort) Swap

func (rts *RecTimeSort) Swap(i, j int)

type RegType

type RegType int
const (
	RegType_u8  RegType = 1
	RegType_u16 RegType = 2
	RegType_u32 RegType = 3
	RegType_s8  RegType = 4
	RegType_s16 RegType = 5
	RegType_s32 RegType = 6
	RegType_unk RegType = 7
)

type StreamingSummary

type StreamingSummary struct {
	// BinSeconds is the number of seconds of raw samples to merge
	BinSeconds int

	// KeepCount is the number of merged samples to keep
	KeepCount int
	// contains filtered or unexported fields
}

Merge VE.Direct records based on time (e.g. 1 minute average of 1 second records), keep the most recent N.

Different fields are merged on different rules, some are averaged, some are last-value-wins

BinSeconds * KeepCount is the amonut of total time covered. {BinSeconds:60, KeepCount: 24*60} will merge data into 1 minute bins and keep the most recent 24 hours of data.

func (*StreamingSummary) Add

func (sum *StreamingSummary) Add(rec map[string]interface{}) error

func (*StreamingSummary) GetData

func (sum *StreamingSummary) GetData(raw_after int64) []map[string]interface{}

GetData returns a merged set of data with merged samples before some time and raw samples after.

func (*StreamingSummary) GetRawRecent

func (sum *StreamingSummary) GetRawRecent(after int64, limit int) []map[string]interface{}

get the newest records, up to limit.

after is time.Time.UnixMilli()

func (*StreamingSummary) GetSummedRecent

func (sum *StreamingSummary) GetSummedRecent(after int64, limit int) []map[string]interface{}

get the newest records, up to limit the _t time for a summary is the _last_ time of any sample within the summarized range, thus you can query GetRawRecent after= from a summed _t value

type VERegValue

type VERegValue struct {
	Register VERegister
	Value    any
}

func ParseHexRecord

func ParseHexRecord(x string) (value *VERegValue, err error)

Parse register value from a VE.HEX message. Not fully general, this filters on 0x7 and 0xA messages which are:

  • 0x7 response to register get
  • 0xA asynchronously volunteered register update

type VERegister

type VERegister struct {
	Address     uint16   `json:"a"`
	Name        string   `json:"n"`
	Scale       *float64 `json:"s,omitempty"`
	Size        RegType  `json:"f"`
	Unit        string   `json:"u,omitempty"`
	SummaryMode string   `json:"m,omitempty"`
}

func VE_MPPT_Registers

func VE_MPPT_Registers() []VERegister

func VE_PhoenixInverter_Registers

func VE_PhoenixInverter_Registers() []VERegister

type Vedirect

type Vedirect struct {
	// AddTime if true will add to each record {"_t": time.Now().UnixMilli()}
	AddTime bool
	// contains filtered or unexported fields
}

func Open

func Open(path string, out chan<- map[string]string, wg *sync.WaitGroup, ctx context.Context, debugOut io.Writer, options ...Option) (v *Vedirect, err error)

Open a VE.Direct serial device (starts a thread). path is the serial device. out chan receives data. wg may be nil. debugOut may be nil.

func (*Vedirect) SendHexCommand

func (v *Vedirect) SendHexCommand(cmd Command, msg []byte) error

SendHexCommand sends a HEX protocol command to a VE.Direct device.

If successful, response will come in normal message stream parsed into data["_x"] = "aabbccddeeff"

Parsed HEX messages have their command nybble filled out to a whole byte so that hex.DecodeString(data["_x"]) should work.

Actual message fields vary by length and content and are left to application code, but you might want "encoding/binary" LittleEndian.Uint16([]byte) and .PutUint16([]byte, uint16)

Directories

Path Synopsis
cmd
ve_arch_serv Module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL