GoSungrow/mmHa/struct.go
2022-04-21 16:24:40 +10:00

633 lines
15 KiB
Go

package mmHa
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/api"
"encoding/json"
"errors"
"fmt"
mqtt "github.com/eclipse/paho.mqtt.golang"
"net/url"
"time"
)
type Mqtt 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"`
EntityPrefix string `json:"entity_prefix"`
url *url.URL
client mqtt.Client
pubClient mqtt.Client
clientOptions *mqtt.ClientOptions
LastRefresh time.Time `json:"-"`
PsId int64 `json:"-"`
Device Device
servicePrefix string
sensorPrefix string
lightPrefix string
switchPrefix string
binarySensorPrefix string
token mqtt.Token
firstRun bool
err error
}
func New(req Mqtt) *Mqtt {
var ret Mqtt
for range Only.Once {
ret.err = ret.setUrl(req)
if ret.err != nil {
break
}
ret.firstRun = true
ret.EntityPrefix = req.EntityPrefix
ret.servicePrefix = "homeassistant/sensor/" + req.ClientId
ret.sensorPrefix = "homeassistant/sensor/" + req.ClientId
ret.lightPrefix = "homeassistant/light/" + req.ClientId
ret.switchPrefix = "homeassistant/switch/" + req.ClientId
ret.binarySensorPrefix = "homeassistant/binary_sensor/" + req.ClientId
}
return &ret
}
func (m *Mqtt) IsFirstRun() bool {
return m.firstRun
}
func (m *Mqtt) IsNotFirstRun() bool {
return !m.firstRun
}
func (m *Mqtt) UnsetFirstRun() {
m.firstRun = false
}
func (m *Mqtt) GetError() error {
return m.err
}
func (m *Mqtt) IsError() bool {
if m.err != nil {
return true
}
return false
}
func (m *Mqtt) IsNewDay() bool {
var yes bool
for range Only.Once {
last := m.LastRefresh.Format("20060102")
now := time.Now().Format("20060102")
if last != now {
yes = true
break
}
}
return yes
}
func (m *Mqtt) setUrl(req Mqtt) error {
for range Only.Once {
// if req.Username == "" {
// m.err = errors.New("username empty")
// break
// }
m.Username = req.Username
// if req.Password == "" {
// m.err = errors.New("password empty")
// break
// }
m.Password = req.Password
if req.Host == "" {
m.err = errors.New("HASSIO mqtt host not defined")
break
}
m.Host = req.Host
if req.Port == "" {
req.Port = "1883"
}
m.Port = req.Port
u := fmt.Sprintf("tcp://%s:%s@%s:%s",
m.Username,
m.Password,
m.Host,
m.Port,
)
m.url, m.err = url.Parse(u)
}
return m.err
}
func (m *Mqtt) 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 *Mqtt) Connect() error {
for range Only.Once {
m.err = m.createClientOptions()
if m.err != nil {
break
}
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: 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.servicePrefix, "config"), 0, true, device.Json())
if m.err != nil {
break
}
m.err = m.Publish(JoinStringsForTopic(m.servicePrefix, "state"), 0, true, "ON")
if m.err != nil {
break
}
}
return m.err
}
func (m *Mqtt) Disconnect() error {
for range Only.Once {
m.client.Disconnect(250)
time.Sleep(1 * time.Second)
}
return m.err
}
// const ServiceBaseName = "GoSunGrow"
// const ServiceBaseUniqueId = ServiceBaseName + "_Service"
// const ServiceBaseTopic = "homeassistant/sensor/" + ServiceBaseName
// const SensorBaseTopic = "homeassistant/sensor/" + ServiceBaseName
func (m *Mqtt) createClientOptions() error {
for range Only.Once {
m.clientOptions = mqtt.NewClientOptions()
m.clientOptions.AddBroker(fmt.Sprintf("tcp://%s", m.url.Host))
m.clientOptions.SetUsername(m.url.User.Username())
password, _ := m.url.User.Password()
m.clientOptions.SetPassword(password)
m.clientOptions.SetClientID(m.ClientId)
m.clientOptions.WillTopic = JoinStringsForTopic(m.servicePrefix, "state")
m.clientOptions.WillPayload = []byte("OFF")
m.clientOptions.WillQos = 0
m.clientOptions.WillRetained = true
m.clientOptions.WillEnabled = true
}
return m.err
}
func (m *Mqtt) Subscribe(topic string) error {
for range Only.Once {
t := m.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("* [%s] %s\n", msg.Topic(), string(msg.Payload()))
})
if !t.WaitTimeout(m.Timeout) {
m.err = t.Error()
// m.err = errors.New("mqtt subscribe timeout")
}
}
return m.err
}
func (m *Mqtt) Publish(topic string, qos byte, retained bool, payload interface{}) error {
for range Only.Once {
t := m.client.Publish(topic, qos, retained, payload)
if !t.WaitTimeout(m.Timeout) {
m.err = t.Error()
// m.err = errors.New("mqtt publish timeout")
}
}
return m.err
}
// func (m *Mqtt) PublishConfig(config EntityConfig) error {
// // func (m *Mqtt) PublishConfig(t string, id string, name string, subName string, units string, valueName string, class string) error {
// switch config.Type {
// // case "sensor":
// // m.err = m.PublishSensorConfig(id, name, subName, units, valueName, class)
// case "binary_sensor":
// m.err = m.PublishBinarySensorConfig(config) // (id, name, subName, units, valueName, class)
// // case "lights":
// // m.err = m.PublishLightConfig(id, name, subName, units, valueName, class)
// // case "switch":
// // m.err = m.PublishSwitchConfig(id, name, subName, units, valueName, class)
// // default:
// // m.err = m.PublishSensorConfig(config)
// }
//
// return m.err
// }
func (m *Mqtt) PublishState(Type string, subtopic string, payload interface{}) error {
for range Only.Once {
// topic = JoinStringsForId(m.EntityPrefix, m.Device.Name, topic)
// topic = JoinStringsForTopic(m.sensorPrefix, topic, "state")
// st := JoinStringsForTopic(m.sensorPrefix, JoinStringsForId(m.EntityPrefix, m.Device.FullName, strings.ReplaceAll(subName, "/", ".")), "state")
topic := ""
switch Type {
case "sensor":
topic = JoinStringsForTopic(m.sensorPrefix, subtopic, "state")
case "binary_sensor":
topic = JoinStringsForTopic(m.binarySensorPrefix, subtopic, "state")
case "lights":
topic = JoinStringsForTopic(m.lightPrefix, subtopic, "state")
case "switch":
topic = JoinStringsForTopic(m.switchPrefix, subtopic, "state")
default:
topic = JoinStringsForTopic(m.sensorPrefix, subtopic, "state")
}
t := m.client.Publish(topic, 0, true, payload)
if !t.WaitTimeout(m.Timeout) {
m.err = t.Error()
}
}
return m.err
}
func (m *Mqtt) PublishValue(Type string, subtopic string, value string) error {
for range Only.Once {
topic := ""
switch Type {
case "sensor":
topic = JoinStringsForTopic(m.sensorPrefix, subtopic, "state")
// state := MqttState {
// LastReset: "", // m.GetLastReset(point.PointId),
// Value: value,
// }
// value = state.Json()
case "binary_sensor":
topic = JoinStringsForTopic(m.binarySensorPrefix, subtopic, "state")
// state := MqttState {
// LastReset: "", // m.GetLastReset(point.PointId),
// Value: value,
// }
// value = state.Json()
case "lights":
topic = JoinStringsForTopic(m.lightPrefix, subtopic, "state")
// state := MqttState {
// LastReset: "", // m.GetLastReset(point.PointId),
// Value: value,
// }
// value = state.Json()
case "switch":
topic = JoinStringsForTopic(m.switchPrefix, subtopic, "state")
// state := MqttState {
// LastReset: "", // m.GetLastReset(point.PointId),
// Value: value,
// }
// value = state.Json()
default:
topic = JoinStringsForTopic(m.sensorPrefix, subtopic, "state")
}
// t = JoinStringsForId(m.EntityPrefix, m.Device.Name, t)
// st := JoinStringsForTopic(m.sensorPrefix, JoinStringsForId(m.EntityPrefix, m.Device.FullName, strings.ReplaceAll(subName, "/", ".")), "state")
// payload := MqttState {
// LastReset: "", // m.GetLastReset(point.PointId),
// Value: value,
// }
// m.client.Publish(JoinStringsForTopic(m.sensorPrefix, t, "state"), 0, true, payload.Json())
t := m.client.Publish(topic, 0, true, value)
if !t.WaitTimeout(m.Timeout) {
m.err = t.Error()
}
}
return m.err
}
// func (m *Mqtt) PublishValue(t string, topic string, value string) error {
// switch t {
// case "sensor":
// m.err = m.PublishSensorValue(topic, value)
// case "binary_sensor":
// m.err = m.PublishBinarySensorState(topic, value)
// case "lights":
// m.err = m.PublishLightState(topic, value)
// case "switch":
// m.err = m.PublishSwitchState(topic, value)
// default:
// m.err = m.PublishSensorState(topic, value)
// }
//
// return m.err
// }
func (m *Mqtt) SetDeviceConfig(swname string, id string, name string, model string, vendor string, area string) error {
for range Only.Once {
id = JoinStringsForId(m.EntityPrefix, id)
m.Device = Device {
Connections: [][]string{
{swname, id},
},
Identifiers: []string{id},
Manufacturer: vendor,
Model: model,
Name: name,
SwVersion: swname + " https://github.com/MickMake/" + swname,
ViaDevice: swname,
SuggestedArea: area,
}
}
return m.err
}
type MqttState struct {
LastReset string `json:"last_reset,omitempty"`
Value string `json:"value"`
}
func (mq *MqttState) Json() string {
var ret string
for range Only.Once {
j, err := json.Marshal(*mq)
if err != nil {
ret = fmt.Sprintf("{ \"error\": \"%s\"", err)
break
}
ret = string(j)
}
return ret
}
type Availability struct {
PayloadAvailable string `json:"payload_available,omitempty" required:"false"`
PayloadNotAvailable string `json:"payload_not_available,omitempty" required:"false"`
Topic string `json:"topic,omitempty" required:"true"`
ValueTemplate string `json:"value_template,omitempty" required:"false"`
}
type SensorState string
func (m *Mqtt) GetLastReset(pointType string) string {
var ret string
for range Only.Once {
pt := api.GetDevicePoint(pointType)
if !pt.Valid {
break
}
ret = pt.WhenReset()
}
return ret
}
type EntityConfig struct {
Type string
Name string
SubName string
ParentId string
ParentName string
UniqueId string
FullName string
Units string
ValueName string
DeviceClass string
Icon string
Value string
ValueTemplate string
LastReset string
LastResetValueTemplate string
haType string
}
func (config *EntityConfig) IsSensor() bool {
var ok bool
for range Only.Once {
if config.IsBinarySensor() {
break
}
if config.IsSwitch() {
break
}
if config.IsLight() {
break
}
if config.Units == "" {
break
}
ok = true
}
return ok
}
func (config *EntityConfig) IsBinarySensor() bool {
var ok bool
for range Only.Once {
if config.Units == LabelBinarySensor {
ok = true
break
}
}
return ok
}
func (config *EntityConfig) IsSwitch() bool {
var ok bool
for range Only.Once {
if config.Units == LabelSwitch {
ok = true
break
}
}
return ok
}
func (config *EntityConfig) IsLight() bool {
var ok bool
for range Only.Once {
if config.Units == LabelBinarySensor {
ok = true
break
}
}
return ok
}
func (config *EntityConfig) FixConfig() {
for range Only.Once {
// mdi:power-socket-au
// mdi:solar-power
// mdi:home-lightning-bolt-outline
// mdi:transmission-tower
// mdi:transmission-tower-export
// mdi:transmission-tower-import
// mdi:transmission-tower-off
// mdi:home-battery-outline
// mdi:lightning-bolt
// mdi:check-circle-outline | mdi:arrow-right-bold
switch config.Units {
case LabelBinarySensor:
config.DeviceClass = SetDefault(config.DeviceClass, "power")
config.Icon = SetDefault(config.Icon, "mdi:check-circle-outline")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value }}")
case "MW":
fallthrough
case "kW":
fallthrough
case "W":
config.DeviceClass = SetDefault(config.DeviceClass, "power")
config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
// config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
// - Used with merged values.
case "MWh":
fallthrough
case "kWh":
fallthrough
case "Wh":
config.DeviceClass = SetDefault(config.DeviceClass, "energy")
config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "kvar":
config.DeviceClass = SetDefault(config.DeviceClass, "reactive_power")
config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "Hz":
config.DeviceClass = SetDefault(config.DeviceClass, "frequency")
config.Icon = SetDefault(config.Icon, "mdi:sine-wave")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "V":
config.DeviceClass = SetDefault(config.DeviceClass, "voltage")
config.Icon = SetDefault(config.Icon, "mdi:current-dc")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "A":
config.DeviceClass = SetDefault(config.DeviceClass, "current")
config.Icon = SetDefault(config.Icon, "mdi:current-ac")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "°C":
fallthrough
case "C":
fallthrough
case "℃":
config.DeviceClass = SetDefault(config.DeviceClass, "temperature")
config.Units = "°C"
config.Icon = SetDefault(config.Icon, "mdi:thermometer")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
case "%":
config.DeviceClass = SetDefault(config.DeviceClass, "battery")
config.Icon = SetDefault(config.Icon, "mdi:home-battery-outline")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
default:
config.DeviceClass = SetDefault(config.DeviceClass, "")
config.Icon = SetDefault(config.Icon, "")
config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value }}")
}
if config.LastReset != "" {
break
}
pt := api.GetDevicePoint(config.UniqueId)
if !pt.Valid {
break
}
config.LastReset = pt.WhenReset()
config.LastResetValueTemplate = SetDefault(config.LastResetValueTemplate, "{{ value_json.last_reset | as_datetime() }}")
// config.LastResetValueTemplate = SetDefault(config.LastResetValueTemplate, "{{ value_json.last_reset | int | timestamp_local | as_datetime }}")
}
}
func SetDefault(value string, def string) string {
if value == "" {
value = def
}
return value
}