modbusdev

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

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

Go to latest
Published: Feb 15, 2021 License: MIT Imports: 10 Imported by: 1

README

modbusdev GoDoc

This package grew from a desire to have a simplified way of accessing the various registers on modebus devices I have presently. The aim was to build on top of the modbus package rather than try to replace it.

The package only contains data for devices I need, but others are easily added. Additionally at present the access is read-only but this will change as I start to interact with the devices in different ways.

When returning values, where possible the raw values are returned, but there is an option to also get factored values based on known factors for the device registers. Units for the values stored in registers can also be accessed.

Numbering

In order to allow for an index of registers I chose to go retro and use the Modicon convention address numbering. This allows for the appropiate register access method to be used and gives a unique index for each configured register. Register 0 has number 1 in this system.

Usage

To use the package,

  1. Create the modbus client in the usual way.
  2. Create an instance of a Reader using the NewReader function
  3. Read the registers you are interested in.

Example

package main

import (
    "log"

    "github.com/goburrow/modbus"
    "github.com/zathras777/modbusdev"
)

func main() {
    client := modbus.TCPClient("192.168.1.100:502")
    solax, err := modbusdev.NewReader(client, "solaxx1hybrid")
    if err != nil {
        log.Fatal(err)
    }
    solax.Dump(true)
}

This sample output was done after dark :-)

  30001: Grid Voltage                                     0.00 V
  30002: Grid Current                                     0.00 A
  30003: Inverter Power                                   0.00 W
  30004: PV1 Voltage                                      0.00 V
  30005: PV2 Voltage                                      0.00 V
  30006: PV1 Current                                      0.00 A
  30007: PV2 Current                                      0.00 A
  30008: Grid Frequency                                   0.00 Hz
  30009: Inner Temp                                       0.00 C
  30010: Run Mode                                         0.00 
  30011: PV1 Power                                        0.00 W
  30012: PV2 Power                                        0.00 W
  30021: Battery Voltage                                  0.00 V
  ...

Device List

I don't have many devices :-)

Simple Database Access

I've been using a PostgreSQL database, so have added a simple interface to allow for easier recording of data from Map() results across my projects that are using modbusdev.

Given this example JSON configuration,

{
    "database": {
        "Host": "localhost",
        "Port": 5432,
        "User": "dbusername",
        "Password": "dbuserpassword",
        "Name": "modbusdatabase"
        "Query": "INSERT INTO table (time%s) VALUES (NOW()%s)"
        "fields": [
            {"name": "pv1", "code": 30011},
            {"name": "pv2", "code": 30012},
            {"name": "inverter", "code": 30003},
            ...
        ]
    },

The supplied query should have 2 string placeholders (%s) which will be replaced with the field names and suitable query markers for the database query.

The first step is to import the configuration and decode the JSON.

...
struct jsonConfig struct {
    Database modbusdev.DatabaseConfig
}

func main() {
    client := modbus.TCPClient("192.168.1.100:502")
    solax, err := modbusdev.NewReader(client, "solaxx1hybrid")
    if err != nil {
        log.Fatal(err)
    }

	jsonCfg := jsonConfig{}
	jsonFile, err := os.Open("config.json")
	if err != nil {
		log.Fatal(err)
	}
	decoder := json.NewDecoder(jsonFile)
	err = decoder.Decode(&jsonCfg)
	jsonFile.Close()
	if err != nil {
		log.Fatal(err)
	}

With the database configuration parsed, we set up a loop to constantly read the data and insert into the database as follows,

    err = jsonCfg.Database.OpenDatabase()
    if err != nil {
        log.Fatal(err)
    }
    defer jsonCfg.Database.Close()

    for {
        mapData := solax.Map(true)
        if err := cfg.Database.Execute(mapData); err != nil {
			log.Print(err)
			break
		}
        time.Sleep(5 * time.Second)
    }
}

This is primarily written to simplify my home workflow so will likely not be useful for many folks!

Bugs & Improvements

Always happy to have bugs found. Even happier to have pull requests submitted :-)

If it's useful to you and you want additional devices added, submit the pull request and I'll merge them in.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegistersByName

func RegistersByName(device string) (registers map[int]Register, err error)

RegistersByName Given a device string, return the approrpriate map of registers.

Types

type DatabaseConnection

type DatabaseConnection struct {
	Host     string
	Port     uint
	User     string
	Password string
	Name     string

	SSL bool

	Query  string
	Fields []DatabaseField
	// contains filtered or unexported fields
}

DatabaseConnection Structure that holds details of database connection and query to be executed

func (DatabaseConnection) Close

func (dbC DatabaseConnection) Close()

Close Close the database connection, closing any open statements as well.

func (DatabaseConnection) Execute

func (dbC DatabaseConnection) Execute(data map[int]Value) error

Execute Execute the stored query using supplied map of values

func (*DatabaseConnection) OpenDatabase

func (dbC *DatabaseConnection) OpenDatabase() error

OpenDatabase Open a database connection and call Ping to verify the connection.

type DatabaseField

type DatabaseField struct {
	Name string
	Code int
}

DatabaseField Struct to allow for managing a relationship between a named field in the database and the corresponding element in the device Map() data

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader A reader structure allows us to tie a client to a device register map

func NewReader

func NewReader(client modbus.Client, device string) (rdr Reader, err error)

NewReader Return a configured Reader with the correct register mappings. Device names are converted to lower case for matching, so case provided is irrelevant.

func (*Reader) Dump

func (rdr *Reader) Dump(factored bool)

Dump Query all defined registers and print the results to stdout.

func (*Reader) Get

func (rdr *Reader) Get(code int, factored bool) (rValue Value, err error)

Get Return the data stored following a Read() call.

func (*Reader) Map

func (rdr *Reader) Map(factored bool) map[int]Value

Map Return a map object of the registers. If getting a register returns a value it is simply omitted from the map.

func (*Reader) Read

func (rdr *Reader) Read() error

Read Read the registers that are required to provide data for the configured device. This attempts a single call to the device.

func (*Reader) ReadRegister

func (rdr *Reader) ReadRegister(code int, factored bool) (val Value, err error)

ReadRegister Read the register specified by the code. This always causes the device to be queried.

func (*Reader) ScanHolding

func (rdr *Reader) ScanHolding(start, stop uint16)

ScanHolding Given a start and stop register, scan the holding registers. Added as a convenience.

func (*Reader) Units

func (rdr *Reader) Units(code int) string

Units For the given register code, return the units specified

type Register

type Register struct {
	Description string
	Units       string
	Register    uint16
	Format      string
	Factor      float64
}

Register Structure that contains details of the register value available.

type Value

type Value struct {
	Unsigned16 uint16
	Signed16   int16
	Unsigned32 uint32
	Signed32   int32
	Coil       bool
	Ieee32     float64
}

Value As there are a number of possible return values, we simply return this structure with the appropriate member set.

func (*Value) FormatBytes

func (val *Value) FormatBytes(format string, value []byte)

FormatBytes Given a format string and some bytes, attempt to correctly format them

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Reader A reader structure allows us to tie a client to a device register map

func NewWriter

func NewWriter(client modbus.Client, device string) (wrt Writer, err error)

NewWriter Return a configured Writer with the correct register mappings. Device names are converted to lower case for matching, so case provided is irrelevant.

func (*Writer) WriteDirect

func (wrt *Writer) WriteDirect(address, value uint16) error

WriteDirect Write the given values to the specified register

func (*Writer) WriteRegister

func (wrt *Writer) WriteRegister(code int, val Value) error

WriteRegister Write a given value to a register

func (*Writer) WriteSimple

func (wrt *Writer) WriteSimple(code, value int) error

WriteSimple Write a given int value to a register after converting the type (if possible)

Jump to

Keyboard shortcuts

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