mirror of
https://github.com/MickMake/GoSungrow.git
synced 2025-04-10 04:39:14 +02:00
v3.0.6 - Modbus support.
This commit is contained in:
parent
fc4bb2b4a2
commit
2bac94d2e3
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
|
||||
Mqtt *CmdMqtt
|
||||
Ha *CmdHa
|
||||
Modbus *CmdModbus
|
||||
|
||||
ConfigDir string
|
||||
CacheDir string
|
||||
@ -76,6 +77,9 @@ func init() {
|
||||
cmds.Mqtt.AttachCommand(cmdRoot)
|
||||
cmds.Mqtt.AttachFlags(cmdRoot, cmds.Unify.GetViper())
|
||||
|
||||
cmds.Modbus = NewCmdModbus()
|
||||
cmds.Modbus.AttachCommand(cmdRoot)
|
||||
|
||||
cmds.Ha = NewCmdHa()
|
||||
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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user