mirror of
https://github.com/MickMake/GoSungrow.git
synced 2025-07-22 01:52:25 +02:00
v3.0.6 - Modbus support.
This commit is contained in:
127
cmd/cmd_modbus.go
Normal file
127
cmd/cmd_modbus.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoSungrow/cmdModbus"
|
||||||
|
"github.com/MickMake/GoUnify/Only"
|
||||||
|
"github.com/MickMake/GoUnify/cmdHelp"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/simonvetter/modbus
|
||||||
|
// https://github.com/goburrow/modbus
|
||||||
|
// -> fork -> https://github.com/activeshadow/modbus
|
||||||
|
// https://github.com/btfak/modbus
|
||||||
|
// -> fork -> https://github.com/dpapathanasiou/go-modbus
|
||||||
|
|
||||||
|
|
||||||
|
//goland:noinspection GoNameStartsWithPackageName
|
||||||
|
type CmdModbus struct {
|
||||||
|
CmdDefault
|
||||||
|
|
||||||
|
// Modbus client
|
||||||
|
ModbusUsername string
|
||||||
|
ModbusPassword string
|
||||||
|
ModbusHost string
|
||||||
|
ModbusPort string
|
||||||
|
|
||||||
|
Client *cmdModbus.ModBus
|
||||||
|
// points getDevicePointAttrs.PointsMap
|
||||||
|
// previous map[string]*api.DataEntries
|
||||||
|
|
||||||
|
optionLogLevel int
|
||||||
|
optionSleepDelay time.Duration
|
||||||
|
optionFetchSchedule time.Duration
|
||||||
|
// optionCacheTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCmdModbus() *CmdModbus {
|
||||||
|
var ret *CmdModbus
|
||||||
|
|
||||||
|
for range Only.Once {
|
||||||
|
ret = &CmdModbus {
|
||||||
|
CmdDefault: CmdDefault {
|
||||||
|
Error: nil,
|
||||||
|
cmd: nil,
|
||||||
|
SelfCmd: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
optionLogLevel: LogLevelInfo,
|
||||||
|
optionSleepDelay: time.Second * 40, // Takes up to 40 seconds for data to come in.
|
||||||
|
optionFetchSchedule: time.Minute * 5,
|
||||||
|
// previous: make(map[string]*api.DataEntries, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdModbus) AttachCommand(cmd *cobra.Command) *cobra.Command {
|
||||||
|
for range Only.Once {
|
||||||
|
if cmd == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.cmd = cmd
|
||||||
|
|
||||||
|
// ******************************************************************************** //
|
||||||
|
var cmdModbus = &cobra.Command{
|
||||||
|
Use: "modbus",
|
||||||
|
Aliases: []string{""},
|
||||||
|
Annotations: map[string]string{"group": "ModBus"},
|
||||||
|
Short: "Connect directly to a Sungrow inverter.",
|
||||||
|
Long: "Connect directly to a Sungrow inverter.",
|
||||||
|
DisableFlagParsing: false,
|
||||||
|
DisableFlagsInUseLine: false,
|
||||||
|
PreRunE: nil,
|
||||||
|
RunE: c.CmdModbus,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
}
|
||||||
|
cmd.AddCommand(cmdModbus)
|
||||||
|
cmdModbus.Example = cmdHelp.PrintExamples(cmdModbus, "run", "sync")
|
||||||
|
}
|
||||||
|
return c.SelfCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdModbus) AttachFlags(cmd *cobra.Command, viper *viper.Viper) {
|
||||||
|
for range Only.Once {
|
||||||
|
// cmd.PersistentFlags().StringVarP(&c.DbusUsername, flagDbusUsername, "", "", "HASSIO: mqtt username.")
|
||||||
|
// viper.SetDefault(flagDbusUsername, "")
|
||||||
|
// cmd.PersistentFlags().StringVarP(&c.DbusPassword, flagDbusPassword, "", "", "HASSIO: mqtt password.")
|
||||||
|
// viper.SetDefault(flagDbusPassword, "")
|
||||||
|
// cmd.PersistentFlags().StringVarP(&c.DbusHost, flagDbusHost, "", "", "HASSIO: mqtt host.")
|
||||||
|
// viper.SetDefault(flagDbusHost, "")
|
||||||
|
// cmd.PersistentFlags().StringVarP(&c.DbusPort, flagDbusPort, "", "", "HASSIO: mqtt port.")
|
||||||
|
// viper.SetDefault(flagDbusPort, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdModbus) ModbusArgs(_ *cobra.Command, _ []string) error {
|
||||||
|
for range Only.Once {
|
||||||
|
// c.LogInfo("Connecting to MQTT HASSIO Service...\n")
|
||||||
|
// c.Client = mmHa.New(mmHa.Dbus {
|
||||||
|
// ClientId: DefaultServiceName,
|
||||||
|
// EntityPrefix: DefaultServiceName,
|
||||||
|
// Username: c.DbusUsername,
|
||||||
|
// Password: c.DbusPassword,
|
||||||
|
// Host: c.DbusHost,
|
||||||
|
// Port: c.DbusPort,
|
||||||
|
// })
|
||||||
|
// c.Error = c.Client.GetError()
|
||||||
|
// if c.Error != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// c.LogInfo("Connecting to SunGrow...\n")
|
||||||
|
// c.Client.SungrowDevices, c.Error = cmds.Api.SunGrow.GetDeviceList()
|
||||||
|
// if c.Error != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdModbus) CmdModbus(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
@ -16,6 +16,7 @@ type Cmds struct {
|
|||||||
Show *CmdShow
|
Show *CmdShow
|
||||||
Mqtt *CmdMqtt
|
Mqtt *CmdMqtt
|
||||||
Ha *CmdHa
|
Ha *CmdHa
|
||||||
|
Modbus *CmdModbus
|
||||||
|
|
||||||
ConfigDir string
|
ConfigDir string
|
||||||
CacheDir string
|
CacheDir string
|
||||||
@ -76,6 +77,9 @@ func init() {
|
|||||||
cmds.Mqtt.AttachCommand(cmdRoot)
|
cmds.Mqtt.AttachCommand(cmdRoot)
|
||||||
cmds.Mqtt.AttachFlags(cmdRoot, cmds.Unify.GetViper())
|
cmds.Mqtt.AttachFlags(cmdRoot, cmds.Unify.GetViper())
|
||||||
|
|
||||||
|
cmds.Modbus = NewCmdModbus()
|
||||||
|
cmds.Modbus.AttachCommand(cmdRoot)
|
||||||
|
|
||||||
cmds.Ha = NewCmdHa()
|
cmds.Ha = NewCmdHa()
|
||||||
cmds.Ha.AttachCommand(cmdRoot)
|
cmds.Ha.AttachCommand(cmdRoot)
|
||||||
}
|
}
|
||||||
|
278
cmdModbus/struct.go
Normal file
278
cmdModbus/struct.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
package cmdModbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/MickMake/GoUnify/Only"
|
||||||
|
"github.com/MickMake/GoUnify/cmdLog"
|
||||||
|
"github.com/simonvetter/modbus"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type ModBus struct {
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
Timeout time.Duration `json:"timeout"`
|
||||||
|
ServerCert string `json:"server_cert"`
|
||||||
|
ClientCert string `json:"client_cert"`
|
||||||
|
ClientKey string `json:"client_key"`
|
||||||
|
|
||||||
|
url *url.URL
|
||||||
|
client *modbus.ModbusClient
|
||||||
|
config *modbus.ClientConfiguration
|
||||||
|
clientKeyPair tls.Certificate
|
||||||
|
serverCertPool *x509.CertPool
|
||||||
|
|
||||||
|
firstRun bool
|
||||||
|
err error
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func New(req ModBus) *ModBus {
|
||||||
|
var ret ModBus
|
||||||
|
|
||||||
|
for range Only.Once {
|
||||||
|
ret.config = &modbus.ClientConfiguration{}
|
||||||
|
|
||||||
|
ret.err = ret.setUrl(req)
|
||||||
|
if ret.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ret.firstRun = true
|
||||||
|
|
||||||
|
// ret.MqttDevices = make(map[string]Device)
|
||||||
|
// ret.SungrowPsIds = make(map[valueTypes.PsId]bool)
|
||||||
|
ret.Timeout = time.Second * 5
|
||||||
|
// ret.UserOptions.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) IsDebug() bool {
|
||||||
|
return m.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) SetDebug(debug bool) {
|
||||||
|
m.debug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) LogDebug(format string, args ...interface{}) {
|
||||||
|
if !m.debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmdLog.LogPrintDate(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) GetError() error {
|
||||||
|
return m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) IsError() bool {
|
||||||
|
if m.err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) setUrl(req ModBus) error {
|
||||||
|
|
||||||
|
for range Only.Once {
|
||||||
|
m.Host = req.Host
|
||||||
|
if m.Host == "" {
|
||||||
|
m.err = errors.New("HASSIO mqtt host not defined")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.config.URL = m.Host
|
||||||
|
|
||||||
|
m.Port = req.Port
|
||||||
|
if m.Port != "" {
|
||||||
|
m.config.URL = m.Host + ":" + m.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Username = req.Username
|
||||||
|
if m.Username != "" {
|
||||||
|
m.config.URL = m.Username + "@" + m.Host + ":" + m.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Password = req.Password
|
||||||
|
if m.Password != "" {
|
||||||
|
m.config.URL = m.Username + ":" + m.Password + "@" + m.Host + ":" + m.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.ClientCert != "") && (req.ClientKey != "") && (req.ServerCert != "") {
|
||||||
|
// load the client certificate and its associated private key, which
|
||||||
|
// are used to authenticate the client to the server
|
||||||
|
// "certs/client.cert.pem", "certs/client.key.pem")
|
||||||
|
m.clientKeyPair, m.err = tls.LoadX509KeyPair(req.ClientCert, req.ClientKey)
|
||||||
|
if m.err != nil {
|
||||||
|
fmt.Printf("failed to load client key pair: %v\n", m.err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// load either the server certificate or the certificate of the CA
|
||||||
|
// (Certificate Authority) which signed the server certificate
|
||||||
|
// "certs/server.cert.pem"
|
||||||
|
m.serverCertPool, m.err = modbus.LoadCertPool(req.ServerCert)
|
||||||
|
if m.err != nil {
|
||||||
|
fmt.Printf("failed to load server certificate/CA: %v\n", m.err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcp+tls is the moniker for MBAPS (modbus/tcp encapsulated in TLS),
|
||||||
|
// 802/tcp is the IANA-registered port for MBAPS.
|
||||||
|
// set the client-side cert and key
|
||||||
|
m.config.URL = "tcp+tls://" + m.config.URL
|
||||||
|
|
||||||
|
m.config.TLSClientCert = &m.clientKeyPair
|
||||||
|
// set the server/CA certificate
|
||||||
|
m.config.TLSRootCAs = m.serverCertPool
|
||||||
|
} else {
|
||||||
|
m.config.URL = "tcp://" + m.config.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
var u *url.URL
|
||||||
|
u, m.err = url.Parse(m.config.URL)
|
||||||
|
if m.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Modbus URL before: %s\n", m.config.URL)
|
||||||
|
req.config.URL = u.String()
|
||||||
|
fmt.Printf("Modbus URL after: %s\n", m.config.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) SetAuth(username string, password string) error {
|
||||||
|
|
||||||
|
for range Only.Once {
|
||||||
|
if username == "" {
|
||||||
|
m.err = errors.New("username empty")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.Username = username
|
||||||
|
|
||||||
|
if password == "" {
|
||||||
|
m.err = errors.New("password empty")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.Password = password
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModBus) Connect() error {
|
||||||
|
for range Only.Once {
|
||||||
|
m.client, m.err = modbus.NewClient(m.config)
|
||||||
|
if m.err != nil {
|
||||||
|
fmt.Printf("failed to create modbus client: %v\n", m.err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that the client is created and configured, attempt to connect
|
||||||
|
m.err = m.client.Open()
|
||||||
|
if m.err != nil {
|
||||||
|
fmt.Printf("failed to connect: %v\n", m.err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// m.client = mqtt.NewClient(m.clientOptions)
|
||||||
|
// token := m.client.Connect()
|
||||||
|
// for !token.WaitTimeout(3 * time.Second) {
|
||||||
|
// }
|
||||||
|
// if m.err = token.Error(); m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// if m.ClientId == "" {
|
||||||
|
// m.ClientId = "GoSungrow"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// device := Config {
|
||||||
|
// Entry: JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId), // m.servicePrefix
|
||||||
|
// Name: m.ClientId,
|
||||||
|
// UniqueId: m.ClientId, // + "_Service",
|
||||||
|
// StateTopic: "~/state",
|
||||||
|
// DeviceConfig: DeviceConfig {
|
||||||
|
// Identifiers: []string{"GoSungrow"},
|
||||||
|
// SwVersion: "GoSungrow https://github.com/MickMake/GoSungrow",
|
||||||
|
// Name: m.ClientId + " Service",
|
||||||
|
// Manufacturer: "MickMake",
|
||||||
|
// Model: "SunGrow",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// m.err = m.Publish(JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, "config"), 0, true, device.Json())
|
||||||
|
// if m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// m.err = m.Publish(JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, "state"), 0, true, "ON")
|
||||||
|
// if m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// _, m.err = m.SetDeviceConfig(
|
||||||
|
// m.DeviceName, m.DeviceName,
|
||||||
|
// "options", "Options", "", m.DeviceName,
|
||||||
|
// m.DeviceName,
|
||||||
|
// )
|
||||||
|
// if m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// m.err = m.SetOption("mqtt_debug", "ModBus Debug", m.funcModBusDebug)
|
||||||
|
// if m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// v := OptionDisabled
|
||||||
|
// if m.debug {
|
||||||
|
// v = OptionEnabled
|
||||||
|
// }
|
||||||
|
// m.err = m.SetOptionValue("mqtt_debug", v)
|
||||||
|
// if m.err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (m *ModBus) funcModBusDebug(_ mqtt.Client, msg mqtt.Message) {
|
||||||
|
// for range Only.Once {
|
||||||
|
// request := strings.ToLower(string(msg.Payload()))
|
||||||
|
// cmdLog.LogPrintDate("Option[%s] set to '%s'\n", msg.Topic(), request)
|
||||||
|
// if request == strings.ToLower(OptionEnabled) {
|
||||||
|
// m.err = m.SetOptionValue("mqtt_debug", OptionEnabled)
|
||||||
|
// m.debug = true
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// m.err = m.SetOptionValue("mqtt_debug", OptionDisabled)
|
||||||
|
// m.debug = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (m *ModBus) Disconnect() error {
|
||||||
|
for range Only.Once {
|
||||||
|
// close the connection
|
||||||
|
m.err = m.client.Close()
|
||||||
|
if m.err != nil {
|
||||||
|
fmt.Printf("failed to close connection: %v\n", m.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return m.err
|
||||||
|
}
|
Reference in New Issue
Block a user