This commit is contained in:
MickMake 2022-03-04 20:29:30 +11:00
parent 33b0a4fcf1
commit b84375acfe
59 changed files with 166009 additions and 440 deletions

137
.idea/workspace.xml generated
View File

@ -5,10 +5,36 @@
</component>
<component name="ChangeListManager">
<list default="true" id="76adadc9-ae71-42a6-82a1-66dbc8ecb14c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/iSolarCloud/api/struct_points.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/iSolarCloud/points.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/mmMqtt/cron.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd_config.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd_config.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd_cron.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd_cron.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd_data.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd_data.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd_data_sub.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd_data_sub.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/cmd_mqtt.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/cmd_mqtt.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/const.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/const.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/struct.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/struct.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/viper.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/viper.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/examples.txt" beforeDir="false" afterPath="$PROJECT_DIR$/examples.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/AppService/getPsDetailWithPsType/data.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/AppService/getPsDetailWithPsType/data.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/AppService/login/auth.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/AppService/login/auth.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/AppService/login/struct.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/AppService/login/struct.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceList/data.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceList/data.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys/data.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys/data.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/WebAppService/struct.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/WebAppService/struct.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/api/apiReflect/reflect.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/api/apiReflect/reflect.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/api/datetime.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/api/datetime.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/api/struct_response.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/api/struct_response.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/api/types.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/api/types.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/api/utils.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/api/utils.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/funcs.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/funcs.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/highlevel.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/highlevel.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iSolarCloud/struct.go" beforeDir="false" afterPath="$PROJECT_DIR$/iSolarCloud/struct.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/mmMqtt/sensors.go" beforeDir="false" afterPath="$PROJECT_DIR$/mmMqtt/sensors.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/mmMqtt/struct.go" beforeDir="false" afterPath="$PROJECT_DIR$/mmMqtt/struct.go" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -74,7 +100,7 @@
<configuration name="GoSungrow" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="GoSungrow" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="mqtt" />
<parameters value="data get realtime 1129147" />
<kind value="PACKAGE" />
<package value="$PROJECT_DIR$" />
<directory value="$PROJECT_DIR$" />
@ -116,11 +142,6 @@
<line>217</line>
<option name="timeStamp" value="89" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_data.go</url>
<line>201</line>
<option name="timeStamp" value="95" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/api/output/struct_csv.go</url>
<line>165</line>
@ -158,17 +179,17 @@
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceList/data.go</url>
<line>199</line>
<line>200</line>
<option name="timeStamp" value="202" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/funcs.go</url>
<line>133</line>
<line>135</line>
<option name="timeStamp" value="210" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/funcs.go</url>
<line>121</line>
<line>123</line>
<option name="timeStamp" value="211" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
@ -198,14 +219,106 @@
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_mqtt.go</url>
<line>109</line>
<option name="timeStamp" value="393" />
<line>239</line>
<option name="timeStamp" value="437" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_mqtt.go</url>
<line>238</line>
<option name="timeStamp" value="446" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>375</line>
<option name="timeStamp" value="476" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>391</line>
<option name="timeStamp" value="477" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys/data.go</url>
<line>107</line>
<option name="timeStamp" value="478" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys/data.go</url>
<line>142</line>
<option name="timeStamp" value="479" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>412</line>
<option name="timeStamp" value="480" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>422</line>
<option name="timeStamp" value="481" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>405</line>
<option name="timeStamp" value="483" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/getPsDetailWithPsType/data.go</url>
<line>260</line>
<option name="timeStamp" value="511" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>461</line>
<option name="timeStamp" value="521" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/api/struct_points.go</url>
<line>288</line>
<option name="timeStamp" value="523" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/api/struct_points.go</url>
<line>309</line>
<option name="timeStamp" value="529" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_mqtt.go</url>
<line>170</line>
<option name="timeStamp" value="562" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_mqtt.go</url>
<line>266</line>
<option name="timeStamp" value="564" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/cmd/cmd_mqtt.go</url>
<line>259</line>
<option name="timeStamp" value="565" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceList/data.go</url>
<line>290</line>
<option name="timeStamp" value="566" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/highlevel.go</url>
<line>417</line>
<option name="timeStamp" value="567" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys/struct.go</url>
<line>123</line>
<option name="timeStamp" value="568" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
<watches-manager>
<configuration name="GoApplicationRunConfiguration">
<watch expression="cmdDataSave" language="go" />
<watch expression="Cmd" language="go" />
<watch expression="payload.LastReset" language="go" />
</configuration>
</watches-manager>
</component>

View File

@ -49,6 +49,15 @@ func AttachRootCmd(cmd *cobra.Command) *cobra.Command {
rootViper.SetDefault(flagGoogleSheetUpdate, false)
_ = rootCmd.PersistentFlags().MarkHidden(flagGoogleSheetUpdate)
rootCmd.PersistentFlags().StringVarP(&Cmd.MqttUsername, flagMqttUsername, "", "", fmt.Sprintf("HASSIO: mqtt username."))
rootViper.SetDefault(flagMqttUsername, "")
rootCmd.PersistentFlags().StringVarP(&Cmd.MqttPassword, flagMqttPassword, "", "", fmt.Sprintf("HASSIO: mqtt password."))
rootViper.SetDefault(flagMqttPassword, "")
rootCmd.PersistentFlags().StringVarP(&Cmd.MqttHost, flagMqttHost, "", "", fmt.Sprintf("HASSIO: mqtt host."))
rootViper.SetDefault(flagMqttHost, "")
rootCmd.PersistentFlags().StringVarP(&Cmd.MqttPort, flagMqttPort, "", "", fmt.Sprintf("HASSIO: mqtt port."))
rootViper.SetDefault(flagMqttPort, "")
rootCmd.PersistentFlags().StringVarP(&Cmd.GitRepo, flagGitRepo, "", "", fmt.Sprintf("Git: Repo url for updates."))
rootViper.SetDefault(flagGitRepo, "")
rootCmd.PersistentFlags().StringVarP(&Cmd.GitRepoDir, flagGitRepoDir, "", "", fmt.Sprintf("Git: Local repo directory."))

View File

@ -67,10 +67,10 @@ func cmdConfigFunc(cmd *cobra.Command, args []string) {
func cmdConfigWriteFunc(_ *cobra.Command, args []string) {
for range Only.Once {
if len(args) == 1 {
Cmd.ConfigFile = args[0]
rootViper.SetConfigFile(Cmd.ConfigFile)
}
// if len(args) == 1 {
// Cmd.ConfigFile = args[0]
// rootViper.SetConfigFile(Cmd.ConfigFile)
// }
_, _ = fmt.Fprintf(os.Stderr, "Using config file '%s'\n", rootViper.ConfigFileUsed())
Cmd.Error = openConfig()

View File

@ -219,6 +219,16 @@ func LogPrint(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
func LogPrintDate(format string, args ...interface{}) {
fmt.Printf("%s ", TimeNow())
fmt.Printf(format, args...)
// fmt.Println()
}
func TimeNow() string {
return time.Now().Format("2006-01-02 15:04:05")
}
func ReExecute() error {
for range Only.Once {
LogPrint("Running scheduled command '%s'\n", strings.Join(os.Args, " "))

View File

@ -25,21 +25,6 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
cmdData.Example = PrintExamples(cmdData, "get <endpoint>", "put <endpoint>")
// // ********************************************************************************
// var cmdDataList = &cobra.Command{
// Use: "ls",
// Aliases: []string{"list"},
// Short: fmt.Sprintf("List iSolarCloud high-level data commands."),
// Long: fmt.Sprintf("List iSolarCloud high-level data commands."),
// DisableFlagParsing: false,
// DisableFlagsInUseLine: false,
// // PreRunE: Cmd.SunGrowArgs,
// Run: cmdDataListFunc,
// Args: cobra.RangeArgs(0, 1),
// }
// cmdData.AddCommand(cmdDataList)
// cmdDataList.Example = PrintExamples(cmdDataList, "", "areas", "endpoints", "<area name>")
// ********************************************************************************
var cmdDataLogin = &cobra.Command{
Use: "login",
@ -77,6 +62,9 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
AttachCmdDataTemplatePoints(cmdDataGet)
AttachCmdDataPoints(cmdDataGet)
AttachCmdDataPointNames(cmdDataGet)
AttachCmdDataMqtt(cmdDataGet)
AttachCmdDataRealTime(cmdDataGet)
AttachCmdDataPsDetails(cmdDataGet)
// ********************************************************************************
var cmdDataRaw = &cobra.Command{
@ -98,6 +86,9 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
AttachCmdDataTemplate(cmdDataRaw)
AttachCmdDataPoints(cmdDataRaw)
AttachCmdDataPointNames(cmdDataRaw)
AttachCmdDataMqtt(cmdDataRaw)
AttachCmdDataRealTime(cmdDataRaw)
AttachCmdDataPsDetails(cmdDataRaw)
// ********************************************************************************
var cmdDataSave = &cobra.Command{
@ -119,6 +110,9 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
AttachCmdDataTemplate(cmdDataSave)
AttachCmdDataPoints(cmdDataSave)
AttachCmdDataPointNames(cmdDataSave)
AttachCmdDataMqtt(cmdDataSave)
AttachCmdDataRealTime(cmdDataSave)
AttachCmdDataPsDetails(cmdDataSave)
// ********************************************************************************
var cmdDataGraph = &cobra.Command{
@ -139,6 +133,7 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
AttachCmdDataStats(cmdDataGraph)
AttachCmdDataTemplate(cmdDataGraph)
AttachCmdDataPoints(cmdDataGraph)
AttachCmdDataRealTime(cmdDataGraph)
// ********************************************************************************
var cmdDataPut = &cobra.Command{
@ -159,50 +154,6 @@ func AttachCmdData(cmd *cobra.Command) *cobra.Command {
}
// func cmdDataFunc(cmd *cobra.Command, _ []string) {
// Cmd.Error = cmd.Help()
// }
//
// func cmdDataListFunc(_ *cobra.Command, _ []string) {
// Cmd.SunGrow.ListHighLevel()
// }
//
// func cmdDataGetFunc(_ *cobra.Command, args []string) {
// for range Only.Once {
// Cmd.SunGrow.OutputType.SetHuman()
//
// args = fillArray(3, args)
// Cmd.Error = Cmd.SunGrow.GetHighLevel(args[0], args[1:]...)
// }
// }
//
// func cmdDataRawFunc(_ *cobra.Command, args []string) {
// for range Only.Once {
// Cmd.SunGrow.OutputType.SetRaw()
//
// args = fillArray(3, args)
// Cmd.Error = Cmd.SunGrow.GetHighLevel(args[0], args[1:]...)
// }
// }
//
// func cmdDataSaveFunc(_ *cobra.Command, args []string) {
// for range Only.Once {
// Cmd.SunGrow.OutputType.SetFile()
//
// args = fillArray(3, args)
// Cmd.Error = Cmd.SunGrow.GetHighLevel(args[0], args[1:]...)
// }
// }
//
// func cmdDataGraphFunc(_ *cobra.Command, args []string) {
// for range Only.Once {
// Cmd.SunGrow.OutputType.SetGraph()
//
// args = fillArray(4, args)
// Cmd.Error = Cmd.SunGrow.GetHighLevel(args[0], args[1:]...)
// }
// }
func cmdDataPutFunc(_ *cobra.Command, args []string) {
for range Only.Once {
fmt.Println("Not yet implemented.")

View File

@ -116,3 +116,72 @@ func AttachCmdDataPointNames(cmd *cobra.Command) *cobra.Command {
return cmd
}
func AttachCmdDataMqtt(cmd *cobra.Command) *cobra.Command {
// ********************************************************************************
var c = &cobra.Command{
Use: "mqtt-server",
Aliases: []string{"mqtt"},
Short: fmt.Sprintf("Get iSolarCloud MQTT service login details."),
Long: fmt.Sprintf("Get iSolarCloud MQTT service login details."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.SunGrowArgs,
RunE: func(cmd *cobra.Command, args []string) error {
_ = SwitchOutput(cmd)
args = fillArray(1, args)
return Cmd.SunGrow.GetIsolarcloudMqtt(args[0])
},
Args: cobra.RangeArgs(0, 1),
}
cmd.AddCommand(c)
c.Example = PrintExamples(c, "")
return cmd
}
func AttachCmdDataRealTime(cmd *cobra.Command) *cobra.Command {
// ********************************************************************************
var c = &cobra.Command{
Use: "real-time",
Aliases: []string{"realtime"},
Short: fmt.Sprintf("Get iSolarCloud real-time data."),
Long: fmt.Sprintf("Get iSolarCloud real-time data."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.SunGrowArgs,
RunE: func(cmd *cobra.Command, args []string) error {
_ = SwitchOutput(cmd)
args = fillArray(1, args)
return Cmd.SunGrow.GetRealTimeData(args[0])
},
Args: cobra.RangeArgs(0, 1),
}
cmd.AddCommand(c)
c.Example = PrintExamples(c, "")
return cmd
}
func AttachCmdDataPsDetails(cmd *cobra.Command) *cobra.Command {
// ********************************************************************************
var c = &cobra.Command{
Use: "psdetails",
Aliases: []string{"ps-details"},
Short: fmt.Sprintf("Get iSolarCloud ps details."),
Long: fmt.Sprintf("Get iSolarCloud ps details."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.SunGrowArgs,
RunE: func(cmd *cobra.Command, args []string) error {
_ = SwitchOutput(cmd)
args = fillArray(1, args)
return Cmd.SunGrow.GetPsDetails(args[0])
},
Args: cobra.RangeArgs(0, 1),
}
cmd.AddCommand(c)
c.Example = PrintExamples(c, "")
return cmd
}

View File

@ -3,9 +3,11 @@ package cmd
import (
"GoSungrow/Only"
"GoSungrow/mmMqtt"
"errors"
"fmt"
"github.com/go-co-op/gocron"
"github.com/spf13/cobra"
"math/rand"
"strings"
"time"
)
@ -15,28 +17,43 @@ func AttachCmdMqtt(cmd *cobra.Command) *cobra.Command {
var cmdMqtt = &cobra.Command{
Use: "mqtt",
Aliases: []string{""},
Short: fmt.Sprintf("All things MQTT related."),
Long: fmt.Sprintf("All things MQTT related."),
Short: fmt.Sprintf("Connect to a HASSIO broker."),
Long: fmt.Sprintf("Connect to a HASSIO broker."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.ProcessArgs,
PreRunE: Cmd.MqttArgs,
RunE: cmdMqttFunc,
Args: cobra.RangeArgs(0, 1),
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(cmdMqtt)
cmdMqtt.Example = PrintExamples(cmdMqtt, "sync", "sync all")
cmdMqtt.Example = PrintExamples(cmdMqtt, "run", "sync")
// ******************************************************************************** //
var cmdMqttSync = &cobra.Command{
Use: "update",
var cmdMqttRun = &cobra.Command{
Use: "run",
Aliases: []string{""},
Short: fmt.Sprintf("Sync to an MQTT broker."),
Long: fmt.Sprintf("Sync to an MQTT broker."),
Short: fmt.Sprintf("One-off sync to a HASSIO broker."),
Long: fmt.Sprintf("One-off sync to a HASSIO broker."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.ProcessArgs,
Run: cmdMqttSyncFunc,
PreRunE: Cmd.MqttArgs,
RunE: cmdMqttRunFunc,
Args: cobra.RangeArgs(0, 1),
}
cmdMqtt.AddCommand(cmdMqttRun)
cmdMqttRun.Example = PrintExamples(cmdMqttRun, "")
// ******************************************************************************** //
var cmdMqttSync = &cobra.Command{
Use: "sync",
Aliases: []string{""},
Short: fmt.Sprintf("Sync to a HASSIO MQTT broker."),
Long: fmt.Sprintf("Sync to a HASSIO MQTT broker."),
DisableFlagParsing: false,
DisableFlagsInUseLine: false,
PreRunE: Cmd.MqttArgs,
RunE: cmdMqttSyncFunc,
Args: cobra.RangeArgs(0, 1),
}
cmdMqtt.AddCommand(cmdMqttSync)
@ -46,30 +63,56 @@ func AttachCmdMqtt(cmd *cobra.Command) *cobra.Command {
}
func cmdMqttFunc(cmd *cobra.Command, args []string) error {
var err error
func (ca *CommandArgs) MqttArgs(cmd *cobra.Command, args []string) error {
for range Only.Once {
fmt.Println("# Starting MQTT HASSIO Service...")
ca.Error = ca.ProcessArgs(cmd, args)
if ca.Error != nil {
break
}
foo := mmMqtt.New(mmMqtt.Mqtt{
ClientId: "SunGrow",
Username: "mickmake",
Password: "rvsrzdd0",
Host: "10.0.5.21",
Port: "11883",
LogPrintDate("Connecting to MQTT HASSIO Service...\n")
Cmd.Mqtt = mmMqtt.New(mmMqtt.Mqtt {
ClientId: "GoSunGrow",
Username: Cmd.MqttUsername,
Password: Cmd.MqttPassword,
Host: Cmd.MqttHost,
Port: Cmd.MqttPort,
})
err = foo.GetError()
if err != nil {
Cmd.Error = Cmd.Mqtt.GetError()
if Cmd.Error != nil {
break
}
err = foo.Connect()
if err != nil {
Cmd.Error = Cmd.Mqtt.Connect()
if Cmd.Error != nil {
break
}
LogPrintDate("Connecting to SunGrow...\n")
Cmd.Error = Cmd.SunGrowArgs(cmd, args)
if Cmd.Error != nil {
break
}
if Cmd.Mqtt.PsId == 0 {
Cmd.Mqtt.PsId, Cmd.Error = Cmd.SunGrow.GetPsId()
if Cmd.Error != nil {
break
}
LogPrintDate("Found SunGrow device %d\n", Cmd.Mqtt.PsId)
}
}
return Cmd.Error
}
func cmdMqttFunc(cmd *cobra.Command, _ []string) error {
return cmd.Help()
}
func cmdMqttRunFunc(_ *cobra.Command, _ []string) error {
for range Only.Once {
// switch1 := mmMqtt.BinarySensor {
// Device: mmMqtt.Device {
// Connections: [][]string{{"sungrow_address", "0"}},
@ -93,131 +136,202 @@ func cmdMqttFunc(cmd *cobra.Command, args []string) error {
// break
// }
fmt.Println("# Checking in on SunGrow...")
err = Cmd.SunGrowArgs(cmd, args)
if err != nil {
// var psId int64
// psId, Cmd.Error = Cmd.SunGrow.GetPsId()
// if err != nil {
// break
// }
//
// fmt.Printf("# Found SunGrow device %d\n", psId)
// // Also getPowerStatistics, getHouseholdStoragePsReport, getPsList, getUpTimePoint,
// ep := Cmd.SunGrow.QueryDevice(psId)
// if ep.IsError() {
// Cmd.Error = ep.GetError()
// break
// }
//
// data := ep.GetData()
// fmt.Printf("# Adding %d entries to HASSIO.\n", len(data.Entries))
// for i, r := range data.Entries {
// fmt.Printf("%s ", r.PointId)
// Cmd.Error = foo.SensorPublishConfig(r.PointId, r.PointName, r.Unit, i)
// if err != nil {
// break
// }
// Cmd.Error = foo.SensorPublishState(r.PointId, r.Value)
// if err != nil {
// break
// }
// }
// fmt.Println()
// if err != nil {
// break
// }
Cmd.Error = MqttCron()
if Cmd.Error != nil {
break
}
var psId int64
psId, err = Cmd.SunGrow.GetPsId()
if err != nil {
break
}
fmt.Printf("# Found SunGrow device %d\n", psId)
// Also getPowerStatistics, getHouseholdStoragePsReport, getPsList, getUpTimePoint,
ep := Cmd.SunGrow.QueryDevice(psId)
if ep.IsError() {
err = ep.GetError()
break
}
data := ep.GetData()
fmt.Printf("# Adding %d entries to HASSIO.\n", len(data.Entries))
for i, r := range data.Entries {
fmt.Printf("%s ", r.PointId)
err = foo.SensorPublishConfig(r.PointId, r.PointName, r.Unit, i)
if err != nil {
break
}
err = foo.SensorPublishState(r.PointId, r.Value)
if err != nil {
break
}
}
fmt.Println()
if err != nil {
break
}
fmt.Println("# Starting ticker...")
LogPrintDate("Starting ticker...\n")
updateCounter := 0
timer := time.NewTicker(60 * time.Second)
for t := range timer.C {
if updateCounter < 5 {
updateCounter++
fmt.Printf("Wait: %d - %s\n", updateCounter, t.String())
LogPrintDate("Sleeping: %d\n", updateCounter)
continue
}
updateCounter = 0
fmt.Printf("Update: %s\n", t.String())
ep = Cmd.SunGrow.QueryDevice(psId)
if ep.IsError() {
err = ep.GetError()
LogPrintDate("Update: %s\n", t.String())
Cmd.Error = MqttCron()
if Cmd.Error != nil {
break
}
data = ep.GetData()
for _, r := range data.Entries {
// fmt.Printf("%s ", r.PointId)
err = foo.SensorPublishState(r.PointId, r.Value)
if err != nil {
break
}
}
// fmt.Println()
// ep = Cmd.SunGrow.QueryDevice(psId)
// if ep.IsError() {
// Cmd.Error = ep.GetError()
// break
// }
//
// data = ep.GetData()
// for _, r := range data.Entries {
// // fmt.Printf("%s ", r.PointId)
// Cmd.Error = foo.SensorPublishState(r.PointId, r.Value)
// if err != nil {
// break
// }
// }
// // fmt.Println()
}
if err != nil {
}
return Cmd.Error
}
func cmdMqttSyncFunc(_ *cobra.Command, args []string) error {
for range Only.Once {
// */1 * * * * /dir/command args args
cronString := "*/5 * * * *"
if len(args) > 0 {
cronString = strings.Join(args[0:5], " ")
cronString = strings.ReplaceAll(cronString, ".", "*")
}
Cron.Scheduler = gocron.NewScheduler(time.UTC)
Cron.Scheduler = Cron.Scheduler.Cron(cronString)
Cron.Scheduler = Cron.Scheduler.SingletonMode()
Cmd.Error = MqttCron()
if Cmd.Error != nil {
break
}
Cron.Job, Cmd.Error = Cron.Scheduler.Do(MqttCron)
if Cmd.Error != nil {
break
}
// switch {
// case len(args) == 0:
// Cmd.Error = cmd.Help()
//
// case args[0] == "all":
// // Cmd.Error = Cmd.GoogleUpdate()
//
// default:
// fmt.Println("Unknown sub-command.")
// _ = cmd.Help()
// }
}
return err
}
func toggle(v string) string {
switch v {
case "OFF":
v = "ON"
case "ON":
v = "OFF"
}
return v
}
func randoPercent() string {
t := time.Now()
min := 0
max := t.Second()
i := (rand.Intn(max - min) + min) * t.Minute() // / float64(max)
return fmt.Sprintf("%.2f", (float64(i) / 3600) * 100)
}
func randoKWh() string {
t := time.Now()
min := 0
max := t.Minute()
i := (rand.Intn(max - min) + min) * t.Second() // / float64(max)
return fmt.Sprintf("%.2f", (float64(i) / 3600) * 11000)
}
func cmdMqttSyncFunc(cmd *cobra.Command, args []string) {
for range Only.Once {
switch {
case len(args) == 0:
Cmd.Error = cmd.Help()
case args[0] == "all":
// Cmd.Error = Cmd.GoogleUpdate()
default:
fmt.Println("Unknown sub-command.")
_ = cmd.Help()
LogPrintDate("Created job schedule using '%s'\n", cronString)
Cron.Scheduler.StartBlocking()
if Cmd.Error != nil {
break
}
}
return Cmd.Error
}
func MqttCron() error {
for range Only.Once {
if Cmd.Mqtt == nil {
Cmd.Error = errors.New("mqtt not available")
break
}
if Cmd.SunGrow == nil {
Cmd.Error = errors.New("sungrow not available")
break
}
if Cmd.Mqtt.IsFirstRun() {
Cmd.Mqtt.UnsetFirstRun()
} else {
time.Sleep(time.Second * 40) // Takes up to 40 seconds for data to come in.
}
// Also getPowerStatistics, getHouseholdStoragePsReport, getPsList, getUpTimePoint,
ep := Cmd.SunGrow.QueryDevice(Cmd.Mqtt.PsId)
if ep.IsError() {
Cmd.Error = ep.GetError()
break
}
data := ep.GetData()
if Cmd.Mqtt.IsNewDay() {
LogPrintDate("New day: Configuring %d entries in HASSIO.\n", len(data.Entries))
for _, r := range data.Entries {
fmt.Printf(".")
// Cmd.Error = Cmd.Mqtt.SensorPublishConfig(r.PointId, r.PointName, r.Unit, i)
Cmd.Error = Cmd.Mqtt.SensorPublishConfig(r)
if Cmd.Error != nil {
break
}
}
fmt.Println()
}
LogPrintDate("Updating %d entries to HASSIO.\n", len(data.Entries))
for _, r := range data.Entries {
fmt.Printf(".")
// Cmd.Error = Cmd.Mqtt.SensorPublishState(r.PointId, r.Value)
Cmd.Error = Cmd.Mqtt.SensorPublishValue(r)
if Cmd.Error != nil {
break
}
}
fmt.Println()
Cmd.Mqtt.LastRefresh = time.Now()
if Cmd.Error != nil {
break
}
}
if Cmd.Error != nil {
LogPrintDate("Error: %s\n", Cmd.Error)
}
return Cmd.Error
}
// func toggle(v string) string {
// switch v {
// case "OFF":
// v = "ON"
// case "ON":
// v = "OFF"
// }
// return v
// }
//
// func randoPercent() string {
// t := time.Now()
// min := 0
// max := t.Second()
// i := (rand.Intn(max - min) + min) * t.Minute() // / float64(max)
// return fmt.Sprintf("%.2f", (float64(i) / 3600) * 100)
// }
//
// func randoKWh() string {
// t := time.Now()
// min := 0
// max := t.Minute()
// i := (rand.Intn(max - min) + min) * t.Second() // / float64(max)
// return fmt.Sprintf("%.2f", (float64(i) / 3600) * 11000)
// }

View File

@ -22,6 +22,11 @@ const (
flagApiLastLogin = "token-expiry"
flagApiOutputType = "out"
flagMqttUsername = "mqtt-user"
flagMqttPassword = "mqtt-password"
flagMqttHost = "mqtt-host"
flagMqttPort = "mqtt-port"
flagGoogleSheet = "google-sheet"
flagGoogleSheetUpdate = "update"

View File

@ -6,6 +6,7 @@ import (
"GoSungrow/iSolarCloud/AppService/login"
"GoSungrow/lsgo"
"GoSungrow/mmGit"
"GoSungrow/mmMqtt"
"errors"
"fmt"
"github.com/spf13/cobra"
@ -20,6 +21,7 @@ var DefaultAreas = []string{"all"}
type CommandArgs struct {
SunGrow *iSolarCloud.SunGrow
Git *mmGit.Git
Mqtt *mmMqtt.Mqtt
ConfigDir string
CacheDir string
@ -41,6 +43,12 @@ type CommandArgs struct {
ApiTokenFile string
ApiOutputType string
// HASSIO MQTT
MqttUsername string
MqttPassword string
MqttHost string
MqttPort string
// Google sheets
GoogleSheet string
GoogleSheetUpdate bool

View File

@ -69,6 +69,11 @@ func openConfig() error {
rootViper.SetDefault(flagApiPassword, defaultPassword)
rootViper.SetDefault(flagApiAppKey, Cmd.ApiAppKey)
rootViper.SetDefault(flagMqttUsername, Cmd.MqttUsername)
rootViper.SetDefault(flagMqttPassword, Cmd.MqttPassword)
rootViper.SetDefault(flagMqttHost, Cmd.MqttHost)
rootViper.SetDefault(flagMqttPort, Cmd.MqttPort)
rootViper.SetDefault(flagGoogleSheet, Cmd.GoogleSheet)
rootViper.SetDefault(flagGoogleSheetUpdate, Cmd.GoogleSheetUpdate)
@ -122,6 +127,11 @@ func writeConfig() error {
rootViper.Set(flagApiPassword, Cmd.ApiPassword)
rootViper.Set(flagApiAppKey, Cmd.ApiAppKey)
rootViper.Set(flagMqttUsername, Cmd.MqttUsername)
rootViper.Set(flagMqttPassword, Cmd.MqttPassword)
rootViper.Set(flagMqttHost, Cmd.MqttHost)
rootViper.Set(flagMqttPort, Cmd.MqttPort)
rootViper.Set(flagGoogleSheet, Cmd.GoogleSheet)
rootViper.Set(flagGoogleSheetUpdate, Cmd.GoogleSheetUpdate)
@ -159,6 +169,12 @@ func readConfig() error {
_, _ = fmt.Fprintf(os.Stderr, "Api UserPassword: %v\n", rootViper.Get(flagApiPassword))
_, _ = fmt.Fprintln(os.Stderr)
_, _ = fmt.Fprintf(os.Stderr, "HASSIO mqtt Username: %v\n", rootViper.Get(flagMqttUsername))
_, _ = fmt.Fprintf(os.Stderr, "HASSIO mqtt Password: %v\n", rootViper.Get(flagMqttPassword))
_, _ = fmt.Fprintf(os.Stderr, "HASSIO mqtt Host: %v\n", rootViper.Get(flagMqttHost))
_, _ = fmt.Fprintf(os.Stderr, "HASSIO mqtt Port: %v\n", rootViper.Get(flagMqttPort))
_, _ = fmt.Fprintln(os.Stderr)
_, _ = fmt.Fprintf(os.Stderr, "Git Repo URL: %v\n", rootViper.Get(flagGitRepo))
_, _ = fmt.Fprintf(os.Stderr, "Git Repo Dir: %v\n", rootViper.Get(flagGitRepoDir))
_, _ = fmt.Fprintf(os.Stderr, "Git Repo User: %v\n", rootViper.Get(flagGitUsername))

184
docs/AllPoints.txt Normal file
View File

@ -0,0 +1,184 @@
+----------+----------------------------+------+
| Point Id | Description | Unit |
+----------+----------------------------+------+
| p83106 | Load Power | kW |
| p13019 | Internal Air Temperature | ℃ |
| p13121 | Total Export Active Power | kW |
| p13149 | Purchased Power | kW |
| p13003 | Total DC Power | kW |
| p13142 | Battery Health (SOH) | % |
| p13143 | Battery Temperature | ℃ |
| p13150 | Battery Discharging Power | kW |
| p13126 | Battery Charging Power | kW |
| p13141 | Battery Level (SOC) | % |
+----------+----------------------------+------+
+----------+---------------------------------+--------+
| Point Id | Description | Unit |
+----------+---------------------------------+--------+
| p83012 | P-radiation-H | W/㎡ |
| p83013 | Daily Irradiation | Wh/m2 |
| p83239 | Total field reactive power | Mvar |
| p83001 | Inverter AC Power Normalization | kW/kWp |
| p13007 | Grid Frequency | Hz |
| p13013 | Total Power Factor | |
| p13018 | Total Apparent Power | VA |
| p13160 | Array Insulation Resistance | kΩ |
+----------+---------------------------------+--------+
+----------+---------------------------+------+
| Point Id | Description | Unit |
+----------+---------------------------+------+
| p13019 | Internal Air Temperature | ℃ |
| p13143 | Battery Temperature | ℃ |
| p83016 | Plant Ambient Temperature | ℃ |
| p83017 | Plant Module Temperature | ℃ |
+----------+---------------------------+------+
+----------+-----------------+------+
| Point Id | Description | Unit |
+----------+-----------------+------+
| p13161 | Bus Voltage | V |
| p13001 | MPPT1 Voltage | V |
| p13158 | Phase B Voltage | V |
| p13159 | Phase C Voltage | V |
| p13105 | MPPT2 Voltage | V |
| p13138 | Battery Voltage | V |
| p13157 | Phase A Voltage | V |
+----------+-----------------+------+
+----------+--------------------------------+------+
| Point Id | Description | Unit |
+----------+--------------------------------+------+
| p13002 | MPPT1 Current | A |
| p13009 | Phase B Current | A |
| p13162 | Max. Charging Current (BMS) | A |
| p18063 | Phase B Backup Current | A |
| p18062 | Phase A Backup Current | A |
| p18064 | Phase C Backup Current | A |
| p13008 | Phase A Current | A |
| p13010 | Phase C Current | A |
| p13139 | Battery Current | A |
| p13163 | Max. Discharging Current (BMS) | A |
| p13106 | MPPT2 Current | A |
+----------+--------------------------------+------+
+----------+----------------------------------------------------------+------+
| Point Id | Description | Unit |
+----------+----------------------------------------------------------+------+
| p83007 | Meter PR | % |
| p83010 | Inverter PR | % |
| p83019 | Power/Installed Power of Plant | % |
| p83129 | Battery SOC | % |
| p83420 | Current Power/Inverter Installed Capacity | % |
| p83023 | Plant PR | % |
| p83252 | Battery Level (SOC) | % |
| p83419 | Daily Highest Inverter Power/Inverter Installed Capacity | % |
| p13144 | Daily Self-consumption Rate | % |
| p13141 | Battery Level (SOC) | % |
| p13142 | Battery Health (SOH) | % |
+----------+----------------------------------------------------------+------+
+----------+--------------------------------------+------+
| Point Id | Description | Unit |
+----------+--------------------------------------+------+
| p83241 | Total field charge capacity | MWh |
| p83242 | Total field discharge capacity | MWh |
| p83235 | Total field chargeable energy | MWh |
| p83236 | Total field dischargeable energy | MWh |
| p83243 | Total field daily charge capacity | MWh |
| p83244 | Total field daily discharge capacity | MWh |
+----------+--------------------------------------+------+
+----------+---------------------------------------------------+------+
| Point Id | Description | Unit |
+----------+---------------------------------------------------+------+
| p83233 | Total field maximum rechargeable power | MW |
| p83234 | Total field maximum dischargeable power | MW |
| p83237 | Total field energy storage maximum reactive power | MW |
| p83238 | Total field energy storage active power | MW |
+----------+---------------------------------------------------+------+
+----------+---------------------------------------+------+
| Point Id | Description | Unit |
+----------+---------------------------------------+------+
| p83033 | Plant Power | kW |
| p83549 | Grid active power | kW |
| p83002 | Inverter AC Power | kW |
| p83032 | Meter AC Power | kW |
| p83128 | Total Active Power of Optical Storage | kW |
| p83106 | Load Power | kW |
| p13011 | Total Active Power | kW |
| p13121 | Total Export Active Power | kW |
| p13150 | Battery Discharging Power | kW |
| p13149 | Purchased Power | kW |
| p18068 | Total Backup Power | kW |
| p13126 | Battery Charging Power | kW |
| p18067 | Phase C Backup Power | kW |
| p13003 | Total DC Power | kW |
| p13119 | Total Load Active Power | kW |
| p18065 | Phase A Backup Power | kW |
| p18066 | Phase B Backup Power | kW |
+----------+---------------------------------------+------+
+----------+------------------------------------+------+
| Point Id | Description | Unit |
+----------+------------------------------------+------+
| p83005 | Daily Equivalent Hours of Meter | h |
| p83008 | Daily Equivalent Hours of Inverter | h |
| p83025 | Plant Equivalent Hours | h |
+----------+------------------------------------+------+
+----------+-----------------------------------------+------+
| Point Id | Description | Unit |
+----------+-----------------------------------------+------+
| p13029 | Daily Battery Discharging Energy | kWh |
| p13116 | Daily Load Energy Consumption from PV | kWh |
| p13137 | Total Load Energy Consumption from PV | kWh |
| p13174 | Daily Battery Charging Energy from PV | kWh |
| p13199 | Daily Load Energy Consumption | kWh |
| p13130 | Total Load Energy Consumption | kWh |
| p13134 | Total PV Yield | kWh |
| p13140 | Battery Capacity(kWh) | kWh |
| p13173 | Daily Feed-in Energy (PV) | kWh |
| p13175 | Total Feed-in Energy (PV) | kWh |
| p13176 | Total Battery Charging Energy from PV | kWh |
| p13125 | Total Feed-in Energy | kWh |
| p13028 | Daily Battery Charging Energy | kWh |
| p13035 | Total Battery Discharging Energy | kWh |
| p13148 | Total Purchased Energy | kWh |
| p13034 | Total Battery Charging Energy | kWh |
| p13112 | Daily PV Yield | kWh |
| p13122 | Daily Feed-in Energy | kWh |
| p13147 | Daily Purchased Energy | kWh |
| p83119 | Daily Feed-in Energy (PV) | kWh |
| p83124 | Total Load Energy Consumption | kWh |
| p83100 | Total Load Energy Consumption from PV | kWh |
| p83102 | Daily Purchased Energy | kWh |
| p83105 | Total Purchased Energy | kWh |
| p83021 | Accumulative Power Consumption by Meter | kWh |
| p83022 | Daily Yield of Plant | kWh |
| p83072 | Daily Feed-in Energy | kWh |
| p83004 | Inverter Total Yield | kWh |
| p83006 | Meter Daily Yield | kWh |
| p83009 | Daily Yield by Inverter | kWh |
| p83011 | Meter E-daily Consumption | kWh |
| p83018 | Daily Yield (Theoretical) | kWh |
| p83020 | Meter Total Yield | kWh |
| p83024 | Plant Total Yield | kWh |
| p83097 | Daily Load Energy Consumption from PV | kWh |
| p83075 | Total Feed-in Energy | kWh |
+----------+-----------------------------------------+------+
+----------+---------------------------------------+------+
| Point Id | Description | Unit |
+----------+---------------------------------------+------+
| p83002 | Inverter AC Power | kW |
| p83006 | Meter Daily Yield | kWh |
| p83549 | Grid active power | kW |
| p83011 | Meter E-daily Consumption | kWh |
| p83022 | Daily Yield of Plant | kWh |
| p83033 | Plant Power | kW |
| p83119 | Daily Feed-in Energy (PV) | kWh |
| p83032 | Meter AC Power | kW |
| p83097 | Daily Load Energy Consumption from PV | kWh |
| p83102 | Daily Purchased Energy | kWh |
| p83072 | Daily Feed-in Energy | kWh |
| p13028 | Daily Battery Charging Energy | kWh |
| p13122 | Daily Feed-in Energy | kWh |
| p13112 | Daily PV Yield | kWh |
| p13116 | Daily Load Energy Consumption from PV | kWh |
| p13147 | Daily Purchased Energy | kWh |
| p13173 | Daily Feed-in Energy (PV) | kWh |
| p13174 | Daily Battery Charging Energy from PV | kWh |
| p13199 | Daily Load Energy Consumption | kWh |
+----------+---------------------------------------+------+

BIN
docs/IMG-7925.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
docs/IMG-7926.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

BIN
docs/IMG-7927.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
docs/IMG-7928.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
docs/IMG-7929.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

BIN
docs/IMG-7930.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

BIN
docs/IMG-7931.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

BIN
docs/IMG-7932.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

BIN
docs/IMG-7933.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
docs/data/js/nc.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/data/js/zhuge.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
package getPsDetailWithPsType
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/api"
"GoSungrow/iSolarCloud/api/apiReflect"
"GoSungrow/iSolarCloud/api/output"
"fmt"
)
@ -165,3 +167,250 @@ func (e *ResultData) IsValid() error {
//
// return err
//}
func (e *EndPoint) GetDataTable() output.Table {
var table output.Table
for range Only.Once {
table = output.NewTable()
e.Error = table.SetTitle("")
if e.Error != nil {
break
}
_ = table.SetHeader(
"Date",
"Point Id",
"Description",
"Value",
"(Unit)",
"(Unit)",
)
now := api.TimeNowString()
keys := api.GetStructKeys(e.Response.ResultData)
for _, n := range keys.Sort() {
p := api.GetPoint(e.Response.ResultData.PsPsKey, n)
if p != nil {
_ = table.AddRow(
now,
api.NameDevicePoint(e.Response.ResultData.PsPsKey, n),
p.Description,
keys[n].Value,
p.Unit,
keys[n].Unit,
)
continue
}
_ = table.AddRow(
now,
api.NameDevicePoint(e.Response.ResultData.PsPsKey, n),
api.UpperCase(n),
keys[n].Value,
keys[n].Unit,
keys[n].Unit,
)
}
if len(e.Response.ResultData.StorageInverterData) == 0 {
break
}
for _, sid := range e.Response.ResultData.StorageInverterData {
keys = api.GetStructKeys(sid)
for _, n := range keys.Sort() {
p := api.GetPoint(sid.PsKey, n)
if p != nil {
_ = table.AddRow(
now,
api.NameDevicePoint(sid.PsKey, n),
p.Description,
keys[n].Value,
p.Unit,
keys[n].Unit,
)
continue
}
_ = table.AddRow(
now,
api.NameDevicePoint(sid.PsKey, n),
api.UpperCase(n),
keys[n].Value,
keys[n].Unit,
keys[n].Unit,
)
}
}
// table.InitGraph(output.GraphRequest {
// Title: "",
// TimeColumn: output.SetInteger(1),
// SearchColumn: output.SetInteger(2),
// NameColumn: output.SetInteger(4),
// ValueColumn: output.SetInteger(5),
// UnitsColumn: output.SetInteger(6),
// SearchString: output.SetString(""),
// MinLeftAxis: output.SetFloat(0),
// MaxLeftAxis: output.SetFloat(0),
// })
}
return table
}
func (e *EndPoint) GetData() api.Data {
var ret api.Data
// for range Only.Once {
// index := 0
// for _, d := range e.Response.ResultData.PageList {
// for _, p := range d.PointData {
// if p.Unit == "W" {
// fv, err := strconv.ParseFloat(p.Value, 64)
// fv = fv / 1000
// if err == nil {
// p.Value = fmt.Sprintf("%.3f", fv)
// p.Unit = "kW"
// }
// }
//
// ret.Entries = append(ret.Entries, api.DataEntry {
// Date: api.NewDateTime(p.TimeStamp),
// PointId: api.GetAreaPointName(d.PsKey, p.PointID),
// PointGroupName: p.PointGroupName,
// PointName: p.PointName,
// Value: p.Value,
// Unit: p.Unit,
// ValueType: api.GetPointType(d.PsKey, p.PointID),
// Index: index,
// })
//
// index++
// }
// }
// }
return ret
}
func (e *EndPoint) GetPsKeys() []string {
ret := []string{e.Response.ResultData.PsPsKey}
for _, l := range e.Response.ResultData.StorageInverterData {
ret = append(ret, l.PsKey)
}
return ret
}
// ChargingDischargingPowerMap
// Co2ReduceTotal
// CoalReduceTotal
// ConnectType
// CurrPower
// DesignCapacity
// EnergyScheme
// GcjLatitude
// GcjLongitude
// HasAmmeter
// HouseholdInverterData
// InstallerPsFaultStatus
// IsHaveEsInverter
// IsSingleInverter
// IsTransformSystem
// Latitude
// LoadPowerMap
// LoadPowerMapVirgin
// Longitude
// MapLatitude
// MapLongitude
// MeterReduceTotal
// MobleTel
// MonthEnergy
// MonthEnergyVirgin
// MonthIncome
// NegativeLoadMsg
// OwnerPsFaultStatus
// P83081Map
// P83081MapVirgin
// P83102Map
// P83102MapVirgin
// P83102Percent
// P83118Map
// P83118MapVirgin
// P83119Map
// P83119MapVirgin
// P83120Map
// P83120MapVirgin
// P83122
// P83124Map
// P83124MapVirgin
// P83202Map
// P83202MapVirgin
// P83532MapVirgin
// PowerChargeSetted
// PowerGridPowerMap
// PowerGridPowerMapVirgin
// PsCountryID
// PsDeviceType
// PsFaultStatus
// PsHealthStatus
// PsLocation
// PsName
// PsPsKey
// PsState
// PsType
// PvPowerMap
// PvPowerMapVirgin
// RobotNumSweepCapacity
// Num
// SweepCapacity
// }
// SelfConsumptionOffsetReminder
// So2ReduceTotal
// StorageInverterData
// CommunicationDevSn
// DevStatus
// DeviceCode
// DeviceModelCode
// DeviceName
// DeviceState
// DeviceType
// DrmStatus
// DrmStatusName
// EnergyFlow
// HasAmmeter
// InstallerDevFaultStatus
// InverterSn
// OwnerDevFaultStatus
// P13003Map
// P13003MapVirgin
// P13119Map
// P13119MapVirgin
// P13121Map
// P13121MapVirgin
// P13126Map
// P13126MapVirgin
// P13141
// P13149Map
// P13149MapVirgin
// P13150Map
// P13150MapVirgin
// PsKey
// UUID
// }
// TodayEnergy
// TodayEnergyVirgin
// TodayIncome
// TotalEnergy
// TotalEnergyVirgin
// TotalIncome
// TreeReduceTotal
// ValidFlag
// WgsLatitude
// WgsLongitude
// ZfzyMap
// ZfzyMapVirgin
// ZjzzMap
// ZjzzMapVirgin

View File

@ -21,13 +21,13 @@ type SunGrowAuth struct {
UserAccount string
UserPassword string
TokenFile string
Token string
// Token string
Force bool
lastLogin time.Time
newToken bool
retry int
err error
lastLogin time.Time
newToken bool
// retry int
err error
}
func (a *SunGrowAuth) Verify() error {
@ -61,11 +61,11 @@ func (a *SunGrowAuth) Verify() error {
func (e *EndPoint) Login(auth *SunGrowAuth) error {
for range Only.Once {
e.Auth = auth
e.Request.RequestData = RequestData{
e.Request.RequestData = RequestData {
UserAccount: auth.UserAccount,
UserPassword: auth.UserPassword,
}
e.Request.RequestCommon = api.RequestCommon{
e.Request.RequestCommon = api.RequestCommon {
Appkey: auth.AppKey,
SysCode: "900",
}
@ -81,29 +81,28 @@ func (e *EndPoint) Login(auth *SunGrowAuth) error {
}
if auth.Force {
e.Auth.Token = ""
e.Response.ResultData.Token = ""
// e.Error = os.Remove(filepath.Join(e.ApiRoot.GetCacheDir(), e.CacheFilename()))
// if e.Error != nil {
// break
// }
e.SetTokenInvalid()
}
if e.IsTokenValid() {
break
}
foo := Assert(e.Call())
e.Error = foo.GetError()
ep := Assert(e.Call())
e.Error = ep.GetError()
if e.Error != nil {
break
}
e.Request = foo.Request
e.Response = foo.Response
e.Request = ep.Request
e.Response = ep.Response
e.Auth.lastLogin = time.Now()
if e.IsTokenInvalid() {
break
}
// e.Auth.UserAccount = e.Response.ResultData.UserAccount
e.Auth.Token = e.Response.ResultData.Token
e.Auth.lastLogin, _ = time.Parse(LastLoginDateFormat, e.Response.ResultData.LoginLastDate)
e.Auth.newToken = true
e.Error = e.saveToken()
if e.Error != nil {
@ -114,38 +113,68 @@ func (e *EndPoint) Login(auth *SunGrowAuth) error {
return e.Error
}
func (e *EndPoint) IsTokenInvalid() bool {
func (e *EndPoint) SetTokenInvalid() {
for range Only.Once {
if e.Token() == "" {
e.Auth.newToken = true
break
}
if e.HoursFromLastLogin() > TokenValidHours {
e.Auth.newToken = true
break
}
// e.Auth.Token = ""
e.Response.ResultData.Token = ""
e.Auth.newToken = true
// e.Error = os.Remove(filepath.Join(e.ApiRoot.GetCacheDir(), e.CacheFilename()))
// if e.Error != nil {
// break
// }
}
return e.Auth.newToken
}
// func (e *EndPoint) SetTokenValid(t string) {
// // e.Auth.Token = t
// e.Response.ResultData.Token = t
// e.Auth.newToken = true
// }
func (e *EndPoint) IsTokenValid() bool {
var ok bool
for range Only.Once {
if e.Token() == "" {
if e.Response.ResponseCommon.IsTokenInvalid() {
e.SetTokenInvalid()
break
}
if e.HoursFromLastLogin() > TokenValidHours {
break
}
ok = true
}
return ok
if e.Response.ResultData.Token == "" {
e.SetTokenInvalid()
break
}
if e.HoursFromLastLogin() > TokenValidHours {
e.SetTokenInvalid()
break
}
e.Auth.newToken = false
}
return !e.Auth.newToken
}
func (e *EndPoint) HoursFromLastLogin() time.Duration {
return time.Now().Sub(e.Auth.lastLogin)
func (e *EndPoint) IsTokenInvalid() bool {
return !e.IsTokenValid()
// for range Only.Once {
// if e.Response.ResponseCommon.IsTokenInvalid() {
// e.Auth.newToken = true
// break
// }
// if e.Response.ResultData.Token == "" {
// e.Auth.newToken = true
// break
// }
// if e.HoursFromLastLogin() > TokenValidHours {
// e.Auth.newToken = true
// break
// }
// e.Auth.newToken = false
// }
// return e.Auth.newToken
}
func (e *EndPoint) HoursFromLastLogin() float64 {
return time.Now().Sub(e.Auth.lastLogin).Hours()
}
func (e *EndPoint) HasTokenChanged() bool {
@ -180,17 +209,12 @@ func (e *EndPoint) readTokenFile() error {
e.Auth.TokenFile = e.GetFilePath()
// e.Error = e.ApiReadDataFile(e.Auth.TokenFile, &e.Response.ResultData)
e.Error = output.FileRead(e.Auth.TokenFile, &e.Response.ResultData)
e.Error = output.FileRead(e.Auth.TokenFile, &e.Response)
if e.Error != nil {
break
}
e.Response.ResultData.Msg = ""
e.Response.ResultMsg = ""
e.Response.ResultCode = ""
e.Auth.Token = e.Token()
if e.Auth.Token == "" {
if e.Token() == "" {
e.Auth.newToken = true
break
}
@ -212,7 +236,7 @@ func (e *EndPoint) saveToken() error {
for range Only.Once {
e.Auth.TokenFile = e.GetFilePath()
e.Error = output.FileWrite(e.Auth.TokenFile, e.Response.ResultData, output.DefaultFileMode)
e.Error = output.FileWrite(e.Auth.TokenFile, e.Response, output.DefaultFileMode)
// e.Error = e.ApiWriteDataFile(e.Auth.TokenFile, e.Response.ResultData, 0644)
if e.Error != nil {
break

View File

@ -41,8 +41,8 @@ type Response struct {
// Init - Used to initialize a new endpoint instance. Usually called from an area.
func Init(apiRoot api.Web) EndPoint {
return EndPoint{
EndPointStruct: api.EndPointStruct{
return EndPoint {
EndPointStruct: api.EndPointStruct {
ApiRoot: apiRoot,
Area: api.GetArea(EndPoint{}),
Name: api.GetName(EndPoint{}),
@ -51,16 +51,16 @@ func Init(apiRoot api.Web) EndPoint {
Response: Response{},
Error: nil,
},
Auth: &SunGrowAuth{
Auth: &SunGrowAuth {
AppKey: "",
UserAccount: "",
UserPassword: "",
TokenFile: DefaultAuthTokenFile,
Token: "",
// Token: "",
Force: false,
lastLogin: time.Time{},
newToken: false,
retry: 0,
// retry: 0,
err: nil,
},
}

View File

@ -6,6 +6,7 @@ import (
"GoSungrow/iSolarCloud/api/apiReflect"
"GoSungrow/iSolarCloud/api/output"
"fmt"
"strconv"
)
const Url = "/v1/devService/queryDeviceList"
@ -221,16 +222,32 @@ func (e *EndPoint) GetDataTable() output.Table {
"Date",
"Point Id",
"Point Group Name",
"Point Name",
"Description",
"Value",
"Unit",
)
for _, d := range e.Response.ResultData.PageList {
for _, p := range d.PointData {
p.Value, p.Unit = api.DivideByThousandIfRequired(p.Value, p.Unit)
// gp := api.GetPointInt("", p.PointID)
// if gp != nil {
// _ = table.AddRow(
// api.NewDateTime(p.TimeStamp).PrintFull(),
// api.NameDevicePointInt(d.PsKey, p.PointID),
// p.PointGroupName,
// p.PointName,
// gp.Description,
// p.Value,
// p.Unit,
// gp.Unit,
// )
// continue
// }
_ = table.AddRow(
api.NewDateTime(p.TimeStamp).PrintFull(),
fmt.Sprintf("%s.%d", d.PsKey, p.PointID),
api.NameDevicePointInt(d.PsKey, p.PointID),
p.PointGroupName,
p.PointName,
p.Value,
@ -255,51 +272,37 @@ func (e *EndPoint) GetDataTable() output.Table {
return table
}
func (e *EndPoint) GetData() api.Data {
var ret api.Data
type Data struct {
// Headers DataHeaders
Entries []DataEntry
}
// type DataHeaders struct {
// Date string
// PointId string
// PointGroupName string
// PointName string
// Value string
// Unit string
// }
type DataEntry struct {
Date api.DateTime `json:"date"`
PointId string `json:"point_id"`
PointGroupName string `json:"point_group_name"`
PointName string `json:"point_name"`
Value string `json:"value"`
Unit string `json:"unit"`
}
func (e *EndPoint) GetData() Data {
var ret Data
for range Only.Once {
// ret.Headers = DataHeaders {
// Date: "Date",
// PointId: "Point Id",
// PointGroupName: "Point Group Name",
// PointName: "Point Name",
// Value: "Value",
// Unit: "Unit",
// }
index := 0
for _, d := range e.Response.ResultData.PageList {
for _, p := range d.PointData {
ret.Entries = append(ret.Entries, DataEntry {
if p.Unit == "W" {
fv, err := strconv.ParseFloat(p.Value, 64)
fv = fv / 1000
if err == nil {
p.Value = fmt.Sprintf("%.3f", fv)
p.Unit = "kW"
}
}
ret.Entries = append(ret.Entries, api.DataEntry {
Date: api.NewDateTime(p.TimeStamp),
PointId: fmt.Sprintf("%s.%d", d.PsKey, p.PointID),
PointId: api.NameDevicePointInt(d.PsKey, p.PointID),
PointGroupName: p.PointGroupName,
PointName: p.PointName,
Value: p.Value,
Unit: p.Unit,
ValueType: api.GetPointInt(d.PsKey, p.PointID),
Index: index,
})
index++
}
}
}
return ret
}

View File

@ -1,16 +1,18 @@
package queryDeviceRealTimeDataByPsKeys
import (
"GoSungrow/iSolarCloud/api"
"GoSungrow/iSolarCloud/api/apiReflect"
"GoSungrow/iSolarCloud/api/output"
"errors"
"fmt"
)
const Url = "/v1/devService/queryDeviceRealTimeDataByPsKeys"
const Disabled = true
const Disabled = false
type RequestData struct {
// DeviceType string `json:"device_type" required:"true"`
PsKeyList string `json:"ps_key_list" required:"true"`
}
func (rd RequestData) IsValid() error {
@ -56,3 +58,87 @@ func (e *ResultData) IsValid() error {
//
// return err
//}
func (e *EndPoint) GetDataTable() output.Table {
var table output.Table
// for range Only.Once {
// table = output.NewTable()
// e.Error = table.SetTitle("")
// if e.Error != nil {
// break
// }
//
// _ = table.SetHeader(
// "Date",
// "Point Id",
// "Point Group Name",
// "Point Name",
// "Value",
// "Unit",
// )
//
// for _, d := range e.Response.ResultData.PageList {
// for _, p := range d.PointData {
// _ = table.AddRow(
// api.NewDateTime(p.TimeStamp).PrintFull(),
// fmt.Sprintf("%s.%d", d.PsKey, p.PointID),
// p.PointGroupName,
// p.PointName,
// p.Value,
// p.Unit,
// )
// }
// }
//
// table.InitGraph(output.GraphRequest {
// Title: "",
// TimeColumn: output.SetInteger(1),
// SearchColumn: output.SetInteger(2),
// NameColumn: output.SetInteger(4),
// ValueColumn: output.SetInteger(5),
// UnitsColumn: output.SetInteger(6),
// SearchString: output.SetString(""),
// MinLeftAxis: output.SetFloat(0),
// MaxLeftAxis: output.SetFloat(0),
// })
//
// }
return table
}
func (e *EndPoint) GetData() api.Data {
var ret api.Data
// for range Only.Once {
// index := 0
// for _, d := range e.Response.ResultData.PageList {
// for _, p := range d.PointData {
// if p.Unit == "W" {
// fv, err := strconv.ParseFloat(p.Value, 64)
// fv = fv / 1000
// if err == nil {
// p.Value = fmt.Sprintf("%.3f", fv)
// p.Unit = "kW"
// }
// }
//
// ret.Entries = append(ret.Entries, api.DataEntry {
// Date: api.NewDateTime(p.TimeStamp),
// PointId: api.GetAreaPointName(d.PsKey, p.PointID),
// PointGroupName: p.PointGroupName,
// PointName: p.PointName,
// Value: p.Value,
// Unit: p.Unit,
// ValueType: api.GetPointType(d.PsKey, p.PointID),
// Index: index,
// })
//
// index++
// }
// }
// }
return ret
}

View File

@ -0,0 +1,107 @@
package getMqttConfigInfoByAppkey
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/api/apiReflect"
"GoSungrow/iSolarCloud/api/output"
"fmt"
)
const Url = "/v1/commonService/getMqttConfigInfoByAppkey"
const Disabled = false
const (
WebAppKey = "B0455FBE7AA0328DB57B59AA729F05D8"
LoginAppKey = "93D72E60331ABDCDC7B39ADC2D1F32B3"
)
type RequestData struct {
AppKey string `json:"app_key" required:"true"`
}
// IsValid Checks for validity of results data.
func (rd RequestData) IsValid() error {
return apiReflect.VerifyOptionsRequired(rd)
}
// Help provides more info to the user on request JSON fields.
func (rd RequestData) Help() string {
ret := fmt.Sprintf("")
return ret
}
// ResultData holds data returned from the API.
type ResultData struct {
Code string `json:"code"`
MqttPassword string `json:"mqtt_password"`
MqttRsaPublicKey string `json:"mqtt_rsa_public_key"`
MqttType string `json:"mqtt_type"`
MqttURLList []string `json:"mqtt_url_list"`
MqttURLListLan []string `json:"mqtt_url_list_lan"`
MqttUsername string `json:"mqtt_username"`
}
// IsValid Checks for validity of results data.
func (e *ResultData) IsValid() error {
var err error
// switch {
// case e.Dummy == "":
// break
// default:
// err = errors.New(fmt.Sprintf("unknown error '%s'", e.Dummy))
// }
return err
}
// type DecodeResultData ResultData
//
// func (e *ResultData) UnmarshalJSON(data []byte) error {
// var err error
//
// for range Only.Once {
// if len(data) == 0 {
// break
// }
// var pd DecodeResultData
//
// // Store ResultData
// _ = json.Unmarshal(data, &pd)
// e.Dummy = pd.Dummy
// }
//
// return err
// }
func (e *EndPoint) GetDataTable() output.Table {
var table output.Table
for range Only.Once {
table = output.NewTable()
e.Error = table.SetTitle("")
if e.Error != nil {
break
}
e.Error = table.SetHeader(
"AppKey",
"Name",
"Value",
)
if e.Error != nil {
break
}
// @TODO - Think about providing an apiReflect function that does this automatically.
_ = table.AddRow(e.Request.AppKey, "Code", e.Response.ResultData.Code)
_ = table.AddRow(e.Request.AppKey, "Mqtt Username", e.Response.ResultData.MqttUsername)
_ = table.AddRow(e.Request.AppKey, "Mqtt Password", e.Response.ResultData.MqttPassword)
_ = table.AddRow(e.Request.AppKey, "Mqtt Rsa Public Key", e.Response.ResultData.MqttRsaPublicKey)
_ = table.AddRow(e.Request.AppKey, "Mqtt Type", e.Response.ResultData.MqttType)
_ = table.AddRow(e.Request.AppKey, "Mqtt URL List", e.Response.ResultData.MqttURLList)
_ = table.AddRow(e.Request.AppKey, "Mqtt URL List Lan", e.Response.ResultData.MqttURLListLan)
}
return table
}

View File

@ -0,0 +1,360 @@
// Package getMqttConfigInfoByAppkey
// - This file is auto-generated from the update_all.sh script.
// Do not modify anything here. Any changes to this EndPoint should be made in the data.go file.
// The only exception is the AppService.login package.
package getMqttConfigInfoByAppkey
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/api"
"GoSungrow/iSolarCloud/api/apiReflect"
"GoSungrow/iSolarCloud/api/output"
"encoding/json"
"errors"
"fmt"
"time"
)
// api.EndPoint - Import API endpoint interface
var _ api.EndPoint = (*EndPoint)(nil)
// EndPoint - Holds the request, response and web method structures.
type EndPoint struct {
api.EndPointStruct
Request Request
Response Response
}
// Request - Holds the api.RequestCommon and user RequestData structures. See data.go for request fields.
type Request struct {
api.RequestCommon
RequestData
}
// Response - Holds the api.ResponseCommon and endpoint specific ResultData structures. See data.go for response fields.
type Response struct {
api.ResponseCommon
ResultData ResultData `json:"result_data"`
}
// Init - Used to initialize a new endpoint instance. Usually called from an area.
func Init(apiRoot api.Web) EndPoint {
return EndPoint{
EndPointStruct: api.EndPointStruct{
ApiRoot: apiRoot,
Area: api.GetArea(EndPoint{}),
Name: api.GetName(EndPoint{}),
Url: api.SetUrl(Url),
Request: Request{},
Response: Response{},
Error: nil,
},
}
}
// ******************************************************************************** //
// Methods not scoped by api.EndPoint interface type
// Init - If the endpoint needs to be re-initialized.
func (e EndPoint) Init(apiRoot api.Web) *EndPoint {
ret := Init(apiRoot)
return &ret
}
// GetRequest - Get the Request structure as scoped by this endpoint.
func (e EndPoint) GetRequest() Request {
return e.Request
}
// GetResponse - Get the Response structure as scoped by this endpoint.
func (e EndPoint) GetResponse() Response {
return e.Response
}
// Assert - Used to obtain locally scoped EndPoint methods, (not visible from api.EndPoint).
//goland:noinspection GoUnusedExportedFunction
func Assert(e api.EndPoint) EndPoint {
return e.(EndPoint)
}
// AssertResultData - Used to obtain locally scoped ResultData methods, (not visible from api.EndPoint).
//goland:noinspection GoUnusedExportedFunction
func AssertResultData(e api.EndPoint) ResultData {
return e.(EndPoint).Response.ResultData
}
// ******************************************************************************** //
// Methods defined by api.EndPoint interface type
// Help - Return help information on the JSON structure used to populate RequestData.
func (e EndPoint) Help() string {
ret := apiReflect.HelpOptions(e.Request.RequestData)
ret += fmt.Sprintf("JSON request:\t%s\n", e.GetRequestJson())
ret += e.Request.Help()
return ret
}
// IsDisabled - Is this endpoint disabled? See data.go Disabled constant.
func (e EndPoint) IsDisabled() bool {
return Disabled
}
// GetArea - Returns the API area that this EndPoint is located.
func (e EndPoint) GetArea() api.AreaName {
return e.Area
}
// GetName - Returns the API EndPoint name.
func (e EndPoint) GetName() api.EndPointName {
return e.Name
}
// GetUrl - Returns the API EndPoint url.
func (e EndPoint) GetUrl() api.EndPointUrl {
return e.Url
}
// Call - Once RequestData is populated, this will access the iSolarCloud API and populate ResultData.
func (e EndPoint) Call() api.EndPoint {
return e.ApiRoot.Get(e)
}
// GetJsonData - Get the JSON representation of ResultData, either as condensed or "pretty".
func (e EndPoint) GetJsonData(raw bool) output.Json {
if raw {
return output.Json(e.ApiRoot.Body)
} else {
return output.GetAsPrettyJson(e.Response.ResultData)
}
}
// SetError - Set the error code for this EndPoint.
func (e EndPoint) SetError(format string, a ...interface{}) api.EndPoint {
e.EndPointStruct.Error = errors.New(fmt.Sprintf(format, a...))
return e
}
// GetError - Get the error code for this EndPoint.
func (e EndPoint) GetError() error {
return e.EndPointStruct.Error
}
// IsError - Is there an error?
func (e EndPoint) IsError() bool {
if e.Error != nil {
return true
}
return false
}
// ReadDataFile - Read a JSON file and populate the ResultData structure.
// (File names will default to AREA-ENDPOINT.json )
func (e EndPoint) ReadDataFile() error {
// return e.FileRead("", &e.Response.ResultData)
return e.ApiReadDataFile(&e.Response.ResultData)
}
// WriteDataFile - Write to a file, the contents of ResultData as JSON.
// (File names will default to AREA-ENDPOINT.json )
func (e EndPoint) WriteDataFile() error {
// return e.FileWrite("", e.Response.ResultData, output.DefaultFileMode)
return e.ApiWriteDataFile(e.Response.ResultData)
}
// ********************************************************************************
// SetRequest - Save an interface reference as either api.RequestCommon or RequestData.
func (e EndPoint) SetRequest(ref interface{}) api.EndPoint {
for range Only.Once {
if apiReflect.GetPkgType(ref) == "api.RequestCommon" {
e.Request.RequestCommon = ref.(api.RequestCommon)
break
}
if apiReflect.GetType(ref) == "RequestData" {
e.Request.RequestData = ref.(RequestData)
e.Error = e.IsRequestValid()
break
}
e.Error = apiReflect.DoPkgTypesMatch(e.Request, ref)
if e.Error != nil {
break
}
e.Request = ref.(Request)
}
return e
}
// SetRequestByJson - Save RequestData from a JSON string.
func (e EndPoint) SetRequestByJson(j output.Json) api.EndPoint {
for range Only.Once {
e.Error = json.Unmarshal([]byte(j), &e.Request.RequestData)
if e.Error != nil {
break
}
e.Error = e.IsRequestValid()
if e.Error != nil {
break
}
}
return e
}
// RequestRef - Return the locally scoped Request structure.
func (e EndPoint) RequestRef() interface{} {
return e.Request
}
// GetRequestJson - Return the Request structure as a JSON string.
func (e EndPoint) GetRequestJson() output.Json {
return output.GetAsJson(e.Request.RequestData)
}
// // GetFingerprint - Used to formulate cache filenames.
// func (e EndPoint) GetFingerprint() string {
// return apiReflect.GetFingerprint(e.Request.RequestData)
// }
// IsRequestValid - Is api.RequestCommon and RequestData valid?
func (e EndPoint) IsRequestValid() error {
for range Only.Once {
// req := e.GetRequest()
// req := e.Request.RequestCommon
e.Error = e.Request.RequestCommon.IsValid()
if e.Error != nil {
break
}
e.Error = e.Request.RequestData.IsValid()
if e.Error != nil {
break
}
}
return e.Error
}
// ********************************************************************************
// SetResponse - Save a JSON string to the Response structure.
// (Used by the web call method.)
func (e EndPoint) SetResponse(ref []byte) api.EndPoint {
for range Only.Once {
e.Error = json.Unmarshal(ref, &e.Response)
if e.Error != nil {
break
}
}
return e
}
// GetResponseJson - Return the Response structure as a JSON string.
func (e EndPoint) GetResponseJson() output.Json {
return output.GetAsPrettyJson(e.Response)
}
// ResponseRef - Return the locally scoped Response structure.
func (e EndPoint) ResponseRef() interface{} {
return e.Response
}
// IsResponseValid - Is api.ResponseCommon and ResultData valid?
func (e EndPoint) IsResponseValid() error {
for range Only.Once {
e.Error = e.Response.ResponseCommon.IsValid()
if e.Error != nil {
break
}
e.Error = e.Response.ResultData.IsValid()
if e.Error != nil {
break
}
}
return e.Error
}
// String - Stringer method for this EndPoint.
func (e EndPoint) String() string {
return output.GetEndPointString(e)
}
// RequestString - Return the Request structure as a human-readable string.
func (e EndPoint) RequestString() string {
return output.GetRequestString(e.Request)
}
// ResponseString - Return the Response structure as a human-readable string.
func (e EndPoint) ResponseString() string {
return output.GetRequestString(e.Response)
}
// ********************************************************************************
// MarshalJSON - Marshall the EndPoint.
func (e EndPoint) MarshalJSON() ([]byte, error) {
return api.MarshalJSON(e)
// return json.Marshal(&struct {
// Area string `json:"area"`
// EndPoint string `json:"endpoint"`
// Host string `json:"api_host"`
// Url string `json:"endpoint_url"`
// Request interface{} `json:"request"`
// Response interface{} `json:"response"`
// }{
// Area: string(e.Area),
// EndPoint: string(e.Name),
// Host: e.ApiRoot.Url.String(),
// Url: e.Url.String(),
// Request: e.Request,
// Response: e.Response,
// })
}
// ********************************************************************************
// RequestFingerprint - Check if a cache file exists for this EndPoint.
func (e EndPoint) RequestFingerprint() string {
return e.ApiFingerprint(e.Request.RequestData)
}
// CacheFilename - Check if a cache file exists for this EndPoint.
func (e EndPoint) CacheFilename() string {
return e.ApiCacheFilename(e.Request.RequestData)
}
// // CheckCache - Check if a cache file exists for this EndPoint.
// func (e EndPoint) CheckCache() bool {
// return e.ApiCheckCache(e.Request.RequestData)
// }
//
// // ReadCache - Read a cache file and return it as an EndPoint structure.
// func (e EndPoint) ReadCache() api.EndPoint {
// e.Error = e.ApiReadCache(e.Request.RequestData, &e)
// return e
// }
//
// // WriteCache - Write this EndPoint structure out to a cache file.
// func (e EndPoint) WriteCache() error {
// return e.ApiWriteCache(e.Request.RequestData, e)
// }
// SetCacheTimeout - Set the cache timeout for this EndPoint. (Defaults to 1 hour.)
func (e EndPoint) SetCacheTimeout(duration time.Duration) api.EndPoint {
e.ApiRoot.SetCacheTimeout(duration)
return e
}
// GetCacheTimeout - Return the cache timeout for this EndPoint.
func (e EndPoint) GetCacheTimeout() time.Duration {
return e.ApiRoot.GetCacheTimeout()
}

View File

@ -78,6 +78,7 @@ import (
"GoSungrow/iSolarCloud/WebAppService/getInverterFactoryList"
"GoSungrow/iSolarCloud/WebAppService/getInverterInfo"
"GoSungrow/iSolarCloud/WebAppService/getLoadCurveList"
"GoSungrow/iSolarCloud/WebAppService/getMqttConfigInfoByAppkey"
"GoSungrow/iSolarCloud/WebAppService/getMultiPowers"
"GoSungrow/iSolarCloud/WebAppService/getOndutyQuery"
"GoSungrow/iSolarCloud/WebAppService/getOperateTicketUserList"
@ -214,6 +215,10 @@ func Init(apiRoot api.Web) Area {
api.GetName(queryUserCurveTemplateData.EndPoint{}): queryUserCurveTemplateData.Init(apiRoot),
api.GetName(getDeviceUuid.EndPoint{}): getDeviceUuid.Init(apiRoot), // /v1/devService/getDeviceUuid}
// Discovered from Chrome Dev Tools
api.GetName(getMqttConfigInfoByAppkey.EndPoint{}): getMqttConfigInfoByAppkey.Init(apiRoot), // /v1/devService/getDeviceUuid}
api.GetName(addMaterial.EndPoint{}): addMaterial.Init(apiRoot), // /v1/otherService/addMaterial}
api.GetName(addOptTicketInfo.EndPoint{}): addOptTicketInfo.Init(apiRoot), // /v1/faultService/addOptTicketInfo}
api.GetName(addSpareParts.EndPoint{}): addSpareParts.Init(apiRoot), // /v1/otherService/addSpareParts}
@ -409,6 +414,7 @@ func Init(apiRoot api.Web) Area {
// ****************************************
// Methods not scoped by api.EndPoint interface type
//goland:noinspection GoUnusedExportedFunction
func GetAreaName() string {
return string(api.GetArea(Area{}))
}
@ -427,6 +433,7 @@ func (a Area) GetEndPoint(name api.EndPointName) api.EndPoint {
// ****************************************
// Methods scoped by api.Area interface type
//goland:noinspection GoUnusedParameter
func (a Area) Init(apiRoot *api.Web) api.AreaStruct {
panic("implement me")
}
@ -442,30 +449,37 @@ func (a Area) GetEndPoints() api.TypeEndPoints {
return a.EndPoints
}
//goland:noinspection GoUnusedParameter
func (a Area) Call(name api.EndPointName) output.Json {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) SetRequest(name api.EndPointName, ref interface{}) error {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) GetRequest(name api.EndPointName) output.Json {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) GetResponse(name api.EndPointName) output.Json {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) GetData(name api.EndPointName) output.Json {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) IsValid(name api.EndPointName) error {
panic("implement me")
}
//goland:noinspection GoUnusedParameter
func (a Area) GetError(name api.EndPointName) error {
panic("implement me")
}

View File

@ -0,0 +1,143 @@
// Package apiReflect - Snaffooed from https://github.com/fatih/structs
package apiReflect
import (
"errors"
"fmt"
"reflect"
)
var (
errNotExported = errors.New("field is not exported")
errNotSettable = errors.New("field is not settable")
)
// Field represents a single struct field that encapsulates high level
// functions around the field.
type Field struct {
value reflect.Value
field reflect.StructField
defaultTag string
}
// Tag returns the value associated with key in the tag string. If there is no
// such key in the tag, Tag returns the empty string.
func (f *Field) Tag(key string) string {
return f.field.Tag.Get(key)
}
// Value returns the underlying value of the field. It panics if the field
// is not exported.
func (f *Field) Value() interface{} {
return f.value.Interface()
}
// IsEmbedded returns true if the given field is an anonymous field (embedded)
func (f *Field) IsEmbedded() bool {
return f.field.Anonymous
}
// IsExported returns true if the given field is exported.
func (f *Field) IsExported() bool {
return f.field.PkgPath == ""
}
// IsZero returns true if the given field is not initialized (has a zero value).
// It panics if the field is not exported.
func (f *Field) IsZero() bool {
zero := reflect.Zero(f.value.Type()).Interface()
current := f.Value()
return reflect.DeepEqual(current, zero)
}
// Name returns the name of the given field
func (f *Field) Name() string {
return f.field.Name
}
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
func (f *Field) Kind() reflect.Kind {
return f.value.Kind()
}
// Set sets the field to given value v. It returns an error if the field is not
// settable (not addressable or not exported) or if the given value's type
// doesn't match the fields type.
func (f *Field) Set(val interface{}) error {
// we can't set unexported fields, so be sure this field is exported
if !f.IsExported() {
return errNotExported
}
// do we get here? not sure...
if !f.value.CanSet() {
return errNotSettable
}
given := reflect.ValueOf(val)
if f.value.Kind() != given.Kind() {
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
}
f.value.Set(given)
return nil
}
// Zero sets the field to its zero value. It returns an error if the field is not
// settable (not addressable or not exported).
func (f *Field) Zero() error {
zero := reflect.Zero(f.value.Type()).Interface()
return f.Set(zero)
}
// Fields returns a slice of Fields. This is particular handy to get the fields
// of a nested struct . A struct tag with the content of "-" ignores the
// checking of that particular field. Example:
//
// // Field is ignored by this package.
// Field *http.Request `structs:"-"`
//
// It panics if field is not exported or if field's kind is not struct
func (f *Field) Fields() []*Field {
return getFields(f.value, f.defaultTag)
}
// Field returns the field from a nested struct. It panics if the nested struct
// is not exported or if the field was not found.
func (f *Field) Field(name string) *Field {
field, ok := f.FieldOk(name)
if !ok {
panic("field not found")
}
return field
}
// FieldOk returns the field from a nested struct. The boolean returns whether
// the field was found (true) or not (false).
func (f *Field) FieldOk(name string) (*Field, bool) {
value := &f.value
// value must be settable so we need to make sure it holds the address of the
// variable and not a copy, so we can pass the pointer to strctVal instead of a
// copy (which is not assigned to any variable, hence not settable).
// see "https://blog.golang.org/laws-of-reflection#TOC_8."
if f.value.Kind() != reflect.Ptr {
a := f.value.Addr()
value = &a
}
v := strctVal(value.Interface())
t := v.Type()
field, ok := t.FieldByName(name)
if !ok {
return nil, false
}
return &Field{
field: field,
value: v.FieldByName(name),
}, true
}

View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"hash/fnv"
"strconv"
// "github.com/google/uuid"
"path/filepath"
@ -626,6 +627,92 @@ func GetOptionsRequired(ref interface{}) Required {
return ret
}
type StructKey struct {
Name string
JsonName string
JsonValue string
Required string
Value string
Type reflect.Type
Interface interface{}
}
type StructKeys map[string]StructKey
//goland:noinspection GoUnusedFunction,GoUnusedExportedFunction
func GetStructKeys(ref interface{}, keys ...string) StructKeys {
ret := make(StructKeys)
s := New(ref)
if len(keys) == 0 {
keys = s.Names()
}
keyMap := make(map[string]bool)
for _, k := range keys {
keyMap[k] = true
}
// n := s.Names()
// fmt.Printf("%v\n", n)
//
// n2 := s.Name()
// fmt.Printf("%v\n", n2)
//
// n3 := s.TagName
// fmt.Printf("%v\n", n3)
//
// n4 := s.Fields()
// fmt.Printf("%v\n", n4)
//
// n5 := s.Map()
// fmt.Printf("%v\n", n5)
//
// n6 := s.Values()
// fmt.Printf("%v\n", n6)
//
// n7 := s.Field("")
// fmt.Printf("%v\n", n7)
for _, k := range New(ref).Fields() {
if _, ok := keyMap[k.Name()]; !ok {
continue
}
jValue := ""
value := ""
switch k.value.Type().Name() {
case "string":
value = k.Value().(string)
case "int":
v := k.Value().(int)
value = strconv.FormatInt(int64(v), 10)
case "int64":
value = strconv.FormatInt(k.Value().(int64), 10)
case "float64":
value = strconv.FormatFloat(k.Value().(float64), 'f', 6, 64)
default:
j, e := json.Marshal(k.Value())
if e == nil {
jValue = string(j)
} else {
jValue = fmt.Sprintf("%v", k.Value())
}
}
ret[k.Name()] = StructKey {
Name: k.Name(),
JsonName: k.Tag("json"),
JsonValue: jValue,
Value: value,
Required: k.Tag("required"),
Type: k.value.Type(),
Interface: k.Value(),
}
}
return ret
}
func (r *Required) IsRequired(field string) bool {
var ok bool
for _, f := range *r {

View File

@ -0,0 +1,585 @@
// Package apiReflect - Snaffooed from https://github.com/fatih/structs
// Package apiReflect contains various utilities functions to work with structs.
package apiReflect
import (
"fmt"
"reflect"
)
var (
// DefaultTagName is the default tag name for struct fields which provides
// a more granular to tweak certain structs. Lookup the necessary functions
// for more info.
DefaultTagName = "structs" // struct's field default tag name
)
// Struct encapsulates a struct type to provide several high level functions
// around the struct.
type Struct struct {
raw interface{}
value reflect.Value
TagName string
}
// New returns a new *Struct with the struct s. It panics if the s's kind is
// not struct.
func New(s interface{}) *Struct {
return &Struct{
raw: s,
value: strctVal(s),
TagName: DefaultTagName,
}
}
// Map converts the given struct to a map[string]interface{}, where the keys
// of the map are the field names and the values of the map the associated
// values of the fields. The default key string is the struct field name but
// can be changed in the struct field's tag value. The "structs" key in the
// struct's field tag value is the key name. Example:
//
// // Field appears in map as key "myName".
// Name string `structs:"myName"`
//
// A tag value with the content of "-" ignores that particular field. Example:
//
// // Field is ignored by this package.
// Field bool `structs:"-"`
//
// A tag value with the content of "string" uses the stringer to get the value. Example:
//
// // The value will be output of Animal's String() func.
// // Map will panic if Animal does not implement String().
// Field *Animal `structs:"field,string"`
//
// A tag value with the option of "flatten" used in a struct field is to flatten its fields
// in the output map. Example:
//
// // The FieldStruct's fields will be flattened into the output map.
// FieldStruct time.Time `structs:",flatten"`
//
// A tag value with the option of "omitnested" stops iterating further if the type
// is a struct. Example:
//
// // Field is not processed further by this package.
// Field time.Time `structs:"myName,omitnested"`
// Field *http.Request `structs:",omitnested"`
//
// A tag value with the option of "omitempty" ignores that particular field if
// the field value is empty. Example:
//
// // Field appears in map as key "myName", but the field is
// // skipped if empty.
// Field string `structs:"myName,omitempty"`
//
// // Field appears in map as key "Field" (the default), but
// // the field is skipped if empty.
// Field string `structs:",omitempty"`
//
// Note that only exported fields of a struct can be accessed, non exported
// fields will be neglected.
func (s *Struct) Map() map[string]interface{} {
out := make(map[string]interface{})
s.FillMap(out)
return out
}
// FillMap is the same as Map. Instead of returning the output, it fills the
// given map.
func (s *Struct) FillMap(out map[string]interface{}) {
if out == nil {
return
}
fields := s.structFields()
for _, field := range fields {
name := field.Name
val := s.value.FieldByName(name)
isSubStruct := false
var finalVal interface{}
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
if tagName != "" {
name = tagName
}
// if the value is a zero value and the field is marked as omitempty do
// not include
if tagOpts.Has("omitempty") {
zero := reflect.Zero(val.Type()).Interface()
current := val.Interface()
if reflect.DeepEqual(current, zero) {
continue
}
}
if !tagOpts.Has("omitnested") {
finalVal = s.nested(val)
v := reflect.ValueOf(val.Interface())
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Map, reflect.Struct:
isSubStruct = true
}
} else {
finalVal = val.Interface()
}
if tagOpts.Has("string") {
s, ok := val.Interface().(fmt.Stringer)
if ok {
out[name] = s.String()
}
continue
}
if isSubStruct && (tagOpts.Has("flatten")) {
for k := range finalVal.(map[string]interface{}) {
out[k] = finalVal.(map[string]interface{})[k]
}
} else {
out[name] = finalVal
}
}
}
// Values converts the given s struct's field values to a []interface{}. A
// struct tag with the content of "-" ignores the that particular field.
// Example:
//
// // Field is ignored by this package.
// Field int `structs:"-"`
//
// A value with the option of "omitnested" stops iterating further if the type
// is a struct. Example:
//
// // Fields is not processed further by this package.
// Field time.Time `structs:",omitnested"`
// Field *http.Request `structs:",omitnested"`
//
// A tag value with the option of "omitempty" ignores that particular field and
// is not added to the values if the field value is empty. Example:
//
// // Field is skipped if empty
// Field string `structs:",omitempty"`
//
// Note that only exported fields of a struct can be accessed, non exported
// fields will be neglected.
func (s *Struct) Values() []interface{} {
fields := s.structFields()
var t []interface{}
for _, field := range fields {
val := s.value.FieldByName(field.Name)
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
// if the value is a zero value and the field is marked as omitempty do
// not include
if tagOpts.Has("omitempty") {
zero := reflect.Zero(val.Type()).Interface()
current := val.Interface()
if reflect.DeepEqual(current, zero) {
continue
}
}
if tagOpts.Has("string") {
s, ok := val.Interface().(fmt.Stringer)
if ok {
t = append(t, s.String())
}
continue
}
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
// look out for embedded structs, and convert them to a
// []interface{} to be added to the final values slice
t = append(t, Values(val.Interface())...)
} else {
t = append(t, val.Interface())
}
}
return t
}
// Fields returns a slice of Fields. A struct tag with the content of "-"
// ignores the checking of that particular field. Example:
//
// // Field is ignored by this package.
// Field bool `structs:"-"`
//
// It panics if s's kind is not struct.
func (s *Struct) Fields() []*Field {
return getFields(s.value, s.TagName)
}
// Names returns a slice of field names. A struct tag with the content of "-"
// ignores the checking of that particular field. Example:
//
// // Field is ignored by this package.
// Field bool `structs:"-"`
//
// It panics if s's kind is not struct.
func (s *Struct) Names() []string {
fields := getFields(s.value, s.TagName)
names := make([]string, len(fields))
for i, field := range fields {
names[i] = field.Name()
}
return names
}
func getFields(v reflect.Value, tagName string) []*Field {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
var fields []*Field
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if tag := field.Tag.Get(tagName); tag == "-" {
continue
}
f := &Field{
field: field,
value: v.FieldByName(field.Name),
}
fields = append(fields, f)
}
return fields
}
// Field returns a new Field struct that provides several high level functions
// around a single struct field entity. It panics if the field is not found.
func (s *Struct) Field(name string) *Field {
f, ok := s.FieldOk(name)
if !ok {
panic("field not found")
}
return f
}
// FieldOk returns a new Field struct that provides several high level functions
// around a single struct field entity. The boolean returns true if the field
// was found.
func (s *Struct) FieldOk(name string) (*Field, bool) {
t := s.value.Type()
field, ok := t.FieldByName(name)
if !ok {
return nil, false
}
return &Field{
field: field,
value: s.value.FieldByName(name),
defaultTag: s.TagName,
}, true
}
// IsZero returns true if all fields in a struct is a zero value (not
// initialized) A struct tag with the content of "-" ignores the checking of
// that particular field. Example:
//
// // Field is ignored by this package.
// Field bool `structs:"-"`
//
// A value with the option of "omitnested" stops iterating further if the type
// is a struct. Example:
//
// // Field is not processed further by this package.
// Field time.Time `structs:"myName,omitnested"`
// Field *http.Request `structs:",omitnested"`
//
// Note that only exported fields of a struct can be accessed, non exported
// fields will be neglected. It panics if s's kind is not struct.
func (s *Struct) IsZero() bool {
fields := s.structFields()
for _, field := range fields {
val := s.value.FieldByName(field.Name)
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
ok := IsZero(val.Interface())
if !ok {
return false
}
continue
}
// zero value of the given field, such as "" for string, 0 for int
zero := reflect.Zero(val.Type()).Interface()
// current value of the given field
current := val.Interface()
if !reflect.DeepEqual(current, zero) {
return false
}
}
return true
}
// HasZero returns true if a field in a struct is not initialized (zero value).
// A struct tag with the content of "-" ignores the checking of that particular
// field. Example:
//
// // Field is ignored by this package.
// Field bool `structs:"-"`
//
// A value with the option of "omitnested" stops iterating further if the type
// is a struct. Example:
//
// // Field is not processed further by this package.
// Field time.Time `structs:"myName,omitnested"`
// Field *http.Request `structs:",omitnested"`
//
// Note that only exported fields of a struct can be accessed, non exported
// fields will be neglected. It panics if s's kind is not struct.
func (s *Struct) HasZero() bool {
fields := s.structFields()
for _, field := range fields {
val := s.value.FieldByName(field.Name)
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
ok := HasZero(val.Interface())
if ok {
return true
}
continue
}
// zero value of the given field, such as "" for string, 0 for int
zero := reflect.Zero(val.Type()).Interface()
// current value of the given field
current := val.Interface()
if reflect.DeepEqual(current, zero) {
return true
}
}
return false
}
// Name returns the structs's type name within its package. For more info refer
// to Name() function.
func (s *Struct) Name() string {
return s.value.Type().Name()
}
// structFields returns the exported struct fields for a given s struct. This
// is a convenient helper method to avoid duplicate code in some of the
// functions.
func (s *Struct) structFields() []reflect.StructField {
t := s.value.Type()
var f []reflect.StructField
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// we can't access the value of unexported fields
if field.PkgPath != "" {
continue
}
// don't check if it's omitted
if tag := field.Tag.Get(s.TagName); tag == "-" {
continue
}
f = append(f, field)
}
return f
}
func strctVal(s interface{}) reflect.Value {
v := reflect.ValueOf(s)
// if pointer get the underlying element≤
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
panic("not struct")
}
return v
}
// Map converts the given struct to a map[string]interface{}. For more info
// refer to Struct types Map() method. It panics if s's kind is not struct.
func Map(s interface{}) map[string]interface{} {
return New(s).Map()
}
// FillMap is the same as Map. Instead of returning the output, it fills the
// given map.
func FillMap(s interface{}, out map[string]interface{}) {
New(s).FillMap(out)
}
// Values converts the given struct to a []interface{}. For more info refer to
// Struct types Values() method. It panics if s's kind is not struct.
func Values(s interface{}) []interface{} {
return New(s).Values()
}
// Fields returns a slice of *Field. For more info refer to Struct types
// Fields() method. It panics if s's kind is not struct.
func Fields(s interface{}) []*Field {
return New(s).Fields()
}
// Names returns a slice of field names. For more info refer to Struct types
// Names() method. It panics if s's kind is not struct.
func Names(s interface{}) []string {
return New(s).Names()
}
// IsZero returns true if all fields is equal to a zero value. For more info
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
func IsZero(s interface{}) bool {
return New(s).IsZero()
}
// HasZero returns true if any field is equal to a zero value. For more info
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
func HasZero(s interface{}) bool {
return New(s).HasZero()
}
// IsStruct returns true if the given variable is a struct or a pointer to
// struct.
func IsStruct(s interface{}) bool {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// uninitialized zero value of a struct
if v.Kind() == reflect.Invalid {
return false
}
return v.Kind() == reflect.Struct
}
// Name returns the structs's type name within its package. It returns an
// empty string for unnamed types. It panics if s's kind is not struct.
func Name(s interface{}) string {
return New(s).Name()
}
// nested retrieves recursively all types for the given value and returns the
// nested value.
func (s *Struct) nested(val reflect.Value) interface{} {
var finalVal interface{}
v := reflect.ValueOf(val.Interface())
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
n := New(val.Interface())
n.TagName = s.TagName
m := n.Map()
// do not add the converted value if there are no exported fields, ie:
// time.Time
if len(m) == 0 {
finalVal = val.Interface()
} else {
finalVal = m
}
case reflect.Map:
// get the element type of the map
mapElem := val.Type()
switch val.Type().Kind() {
case reflect.Ptr, reflect.Array, reflect.Map,
reflect.Slice, reflect.Chan:
mapElem = val.Type().Elem()
if mapElem.Kind() == reflect.Ptr {
mapElem = mapElem.Elem()
}
}
// only iterate over struct types, ie: map[string]StructType,
// map[string][]StructType,
if mapElem.Kind() == reflect.Struct ||
(mapElem.Kind() == reflect.Slice &&
mapElem.Elem().Kind() == reflect.Struct) {
m := make(map[string]interface{}, val.Len())
for _, k := range val.MapKeys() {
m[k.String()] = s.nested(val.MapIndex(k))
}
finalVal = m
break
}
// TODO(arslan): should this be optional?
finalVal = val.Interface()
case reflect.Slice, reflect.Array:
if val.Type().Kind() == reflect.Interface {
finalVal = val.Interface()
break
}
// TODO(arslan): should this be optional?
// do not iterate of non struct types, just pass the value. Ie: []int,
// []string, co... We only iterate further if it's a struct.
// i.e []foo or []*foo
if val.Type().Elem().Kind() != reflect.Struct &&
!(val.Type().Elem().Kind() == reflect.Ptr &&
val.Type().Elem().Elem().Kind() == reflect.Struct) {
finalVal = val.Interface()
break
}
slices := make([]interface{}, val.Len())
for x := 0; x < val.Len(); x++ {
slices[x] = s.nested(val.Index(x))
}
finalVal = slices
default:
finalVal = val.Interface()
}
return finalVal
}

View File

@ -0,0 +1,33 @@
// Package apiReflect - Snaffooed from https://github.com/fatih/structs
package apiReflect
import "strings"
// tagOptions contains a slice of tag options
type tagOptions []string
// Has returns true if the given option is available in tagOptions
func (t tagOptions) Has(opt string) bool {
for _, tagOpt := range t {
if tagOpt == opt {
return true
}
}
return false
}
// parseTag splits a struct field's tag into its name and a list of options
// which comes after a name. A tag is in the form of: "name,option1,option2".
// The name can be neglectected.
func parseTag(tag string) (string, tagOptions) {
// tag is one of followings:
// ""
// "name"
// "name,opt"
// "name,opt,opt2"
// ",opt"
res := strings.Split(tag, ",")
return res[0], res[1:]
}

View File

@ -49,6 +49,10 @@ func NewDateTime(date string) DateTime {
return ret
}
func TimeNowString() string {
return time.Now().Format(DtLayout)
}
func (dt *DateTime) GetDayStartTimestamp() string {
var ret string
f1 := dt.Time.Round(time.Hour * 24)

View File

@ -0,0 +1,374 @@
package api
import (
"GoSungrow/Only"
"fmt"
"strconv"
"strings"
"time"
)
const (
PointTypeInstant = "instant"
PointTypeDaily = "daily"
PointTypeMonthly = "monthly"
PointTypeYearly = "yearly"
PointTypeTotal = "total"
)
type Point struct {
PsKey string
Id string
Description string
Unit string
Type string
}
type PointsMap map[string]Point
func (pm *PointsMap) Resolve(point string) *Point {
if ret, ok := (*pm)[point]; ok {
return &ret
}
return nil
}
func ResolvePoint(point string) *Point {
return Points.Resolve(point)
}
// p1
// p1001
// p1002
// p1005
// p1302
// p14
// p2
// p202
// p210
// p24
// p24001
// p24004
// p66
// p8018
// p8030
// p8031
// p8062
// p8063
// p81002
// p81022
// p81023
// p81202
// p81203
// p83011
// p83012
// p83039
// p83046
// p83048
// p83049
// p83050
// p83051
// p83054
// p83055
// p83067
// p83070
// p83072
// p83075
// p83076
// p83077
// p83080
// p83081
// p83086
// p83087
// p83088
// p83089
// p83095
// p83096
// p83101
// p83118
// p83119
// p83120
// p83121
// p83122
// p83127
// p83202
// p83532
// Points Discovered points from the API
var Points = PointsMap {
"p13001": { PsKey: "1129147_14_1_1", Id: "p13001", Description: "MPPT1 Voltage", Unit: "V", Type: PointTypeInstant },
"p13012": { PsKey: "1129147_14_1_1", Id: "p13012", Description: "Total Reactive Power", Unit: "kvar", Type: PointTypeDaily },
"p13105": { PsKey: "1129147_14_1_1", Id: "p13105", Description: "MPPT2 Voltage", Unit: "V", Type: PointTypeInstant },
"p13122": { PsKey: "1129147_14_1_1", Id: "p13122", Description: "Daily Feed-in Energy", Unit: "kWh", Type: PointTypeDaily },
"p13125": { PsKey: "1129147_14_1_1", Id: "p13125", Description: "Total Feed-in Energy", Unit: "kWh", Type: PointTypeTotal },
"p13138": { PsKey: "1129147_14_1_1", Id: "p13138", Description: "Battery Voltage", Unit: "V", Type: PointTypeInstant },
"p13144": { PsKey: "1129147_14_1_1", Id: "p13144", Description: "Daily Self-consumption Rate", Unit: "%", Type: PointTypeDaily },
"p13157": { PsKey: "1129147_14_1_1", Id: "p13157", Description: "Phase A Voltage", Unit: "V", Type: PointTypeInstant },
"p13158": { PsKey: "1129147_14_1_1", Id: "p13158", Description: "Phase B Voltage", Unit: "V", Type: PointTypeInstant },
"p13159": { PsKey: "1129147_14_1_1", Id: "p13159", Description: "Phase C Voltage", Unit: "V", Type: PointTypeInstant },
"p13161": { PsKey: "1129147_14_1_1", Id: "p13161", Description: "Bus Voltage", Unit: "V", Type: PointTypeInstant },
"p13173": { PsKey: "1129147_14_1_1", Id: "p13173", Description: "Daily Feed-in Energy (PV)", Unit: "kWh", Type: PointTypeDaily },
"p13175": { PsKey: "1129147_14_1_1", Id: "p13175", Description: "Total Feed-in Energy (PV)", Unit: "kWh", Type: PointTypeTotal },
"p13002": { PsKey: "1129147_14_1_1", Id: "p13002", Description: "MPPT1 Current", Unit: "A", Type: PointTypeInstant },
"p13003": { PsKey: "1129147_14_1_1", Id: "p13003", Description: "Total DC Power", Unit: "kW" },
"p13007": { PsKey: "1129147_14_1_1", Id: "p13007", Description: "Grid Frequency", Unit: "Hz", Type: PointTypeInstant },
"p13008": { PsKey: "1129147_14_1_1", Id: "p13008", Description: "Phase A Current", Unit: "A", Type: PointTypeInstant },
"p13009": { PsKey: "1129147_14_1_1", Id: "p13009", Description: "Phase B Current", Unit: "A", Type: PointTypeInstant },
"p13010": { PsKey: "1129147_14_1_1", Id: "p13010", Description: "Phase C Current", Unit: "A", Type: PointTypeInstant },
"p13011": { PsKey: "1129147_14_1_1", Id: "p13011", Description: "Total Active Power", Unit: "kW" },
"p13013": { PsKey: "1129147_14_1_1", Id: "p13013", Description: "Total Power Factor", Unit: "" },
"p13018": { PsKey: "1129147_14_1_1", Id: "p13018", Description: "Total Apparent Power", Unit: "VA" },
"p13019": { PsKey: "1129147_14_1_1", Id: "p13019", Description: "Internal Air Temperature", Unit: "℃", Type: PointTypeInstant },
"p13028": { PsKey: "1129147_14_1_1", Id: "p13028", Description: "Daily Battery Charging Energy", Unit: "kWh", Type: PointTypeDaily },
"p13029": { PsKey: "1129147_14_1_1", Id: "p13029", Description: "Daily Battery Discharging Energy", Unit: "kWh", Type: PointTypeDaily },
"p13034": { PsKey: "1129147_14_1_1", Id: "p13034", Description: "Total Battery Charging Energy", Unit: "kWh" , Type: PointTypeTotal },
"p13035": { PsKey: "1129147_14_1_1", Id: "p13035", Description: "Total Battery Discharging Energy", Unit: "kWh" , Type: PointTypeTotal },
"p13106": { PsKey: "1129147_14_1_1", Id: "p13106", Description: "MPPT2 Current", Unit: "A", Type: PointTypeInstant },
"p13112": { PsKey: "1129147_14_1_1", Id: "p13112", Description: "Daily PV Yield", Unit: "kWh", Type: PointTypeDaily },
"p13116": { PsKey: "1129147_14_1_1", Id: "p13116", Description: "Daily Load Energy Consumption from PV", Unit: "kWh", Type: PointTypeDaily },
"p13119": { PsKey: "1129147_14_1_1", Id: "p13119", Description: "Total Load Active Power", Unit: "kW" },
"p13121": { PsKey: "1129147_14_1_1", Id: "p13121", Description: "Total Export Active Power", Unit: "kW" },
"p13126": { PsKey: "1129147_14_1_1", Id: "p13126", Description: "Battery Charging Power", Unit: "kW" },
"p13130": { PsKey: "1129147_14_1_1", Id: "p13130", Description: "Total Load Energy Consumption", Unit: "kWh" , Type: PointTypeTotal },
"p13134": { PsKey: "1129147_14_1_1", Id: "p13134", Description: "Total PV Yield", Unit: "kWh" , Type: PointTypeTotal },
"p13137": { PsKey: "1129147_14_1_1", Id: "p13137", Description: "Total Load Energy Consumption from PV", Unit: "kWh" , Type: PointTypeTotal },
"p13139": { PsKey: "1129147_14_1_1", Id: "p13139", Description: "Battery Current", Unit: "A", Type: PointTypeInstant },
"p13140": { PsKey: "1129147_14_1_1", Id: "p13140", Description: "Battery Capacity(kWh)", Unit: "kWh" },
"p13141": { PsKey: "1129147_14_1_1", Id: "p13141", Description: "Battery Level (SOC)", Unit: "%", Type: PointTypeInstant },
"p13142": { PsKey: "1129147_14_1_1", Id: "p13142", Description: "Battery Health (SOH)", Unit: "%", Type: PointTypeInstant },
"p13143": { PsKey: "1129147_14_1_1", Id: "p13143", Description: "Battery Temperature", Unit: "℃", Type: PointTypeInstant },
"p13147": { PsKey: "1129147_14_1_1", Id: "p13147", Description: "Daily Purchased Energy", Unit: "kWh", Type: PointTypeDaily },
"p13148": { PsKey: "1129147_14_1_1", Id: "p13148", Description: "Total Purchased Energy", Unit: "kWh", Type: PointTypeTotal },
"p13149": { PsKey: "1129147_14_1_1", Id: "p13149", Description: "Purchased Power", Unit: "kW" },
"p13150": { PsKey: "1129147_14_1_1", Id: "p13150", Description: "Battery Discharging Power", Unit: "kW" },
"p13160": { PsKey: "1129147_14_1_1", Id: "p13160", Description: "Array Insulation Resistance", Unit: "kΩ", Type: PointTypeInstant },
"p13162": { PsKey: "1129147_14_1_1", Id: "p13162", Description: "Max. Charging Current (BMS)", Unit: "A", Type: PointTypeInstant },
"p13163": { PsKey: "1129147_14_1_1", Id: "p13163", Description: "Max. Discharging Current (BMS)", Unit: "A", Type: PointTypeInstant },
"p13174": { PsKey: "1129147_14_1_1", Id: "p13174", Description: "Daily Battery Charging Energy from PV", Unit: "kWh", Type: PointTypeDaily },
"p13176": { PsKey: "1129147_14_1_1", Id: "p13176", Description: "Total Battery Charging Energy from PV", Unit: "kWh", Type: PointTypeTotal },
"p13199": { PsKey: "1129147_14_1_1", Id: "p13199", Description: "Daily Load Energy Consumption", Unit: "kWh", Type: PointTypeDaily },
"p18062": { PsKey: "1129147_14_1_1", Id: "p18062", Description: "Phase A Backup Current", Unit: "A", Type: PointTypeInstant },
"p18063": { PsKey: "1129147_14_1_1", Id: "p18063", Description: "Phase B Backup Current", Unit: "A", Type: PointTypeInstant },
"p18064": { PsKey: "1129147_14_1_1", Id: "p18064", Description: "Phase C Backup Current", Unit: "A", Type: PointTypeInstant },
"p18065": { PsKey: "1129147_14_1_1", Id: "p18065", Description: "Phase A Backup Power", Unit: "kW" },
"p18066": { PsKey: "1129147_14_1_1", Id: "p18066", Description: "Phase B Backup Power", Unit: "kW" },
"p18067": { PsKey: "1129147_14_1_1", Id: "p18067", Description: "Phase C Backup Power", Unit: "kW" },
"p18068": { PsKey: "1129147_14_1_1", Id: "p18068", Description: "Total Backup Power", Unit: "kW" },
"p83001": { PsKey: "1129147_11_0_0", Id: "p83001", Description: "Inverter AC Power Normalization", Unit: "kW/kWp" },
"p83002": { PsKey: "1129147_11_0_0", Id: "p83002", Description: "Inverter AC Power", Unit: "kW" },
"p83004": { PsKey: "1129147_11_0_0", Id: "p83004", Description: "Inverter Total Yield", Unit: "kWh" },
"p83005": { PsKey: "1129147_11_0_0", Id: "p83005", Description: "Daily Equivalent Hours of Meter", Unit: "h", Type: PointTypeDaily },
"p83006": { PsKey: "1129147_11_0_0", Id: "p83006", Description: "Meter Daily Yield", Unit: "kWh" },
"p83007": { PsKey: "1129147_11_0_0", Id: "p83007", Description: "Meter PR", Unit: "%", Type: PointTypeInstant },
"p83008": { PsKey: "1129147_11_0_0", Id: "p83008", Description: "Daily Equivalent Hours of Inverter", Unit: "h", Type: PointTypeDaily },
"p83009": { PsKey: "1129147_11_0_0", Id: "p83009", Description: "Daily Yield by Inverter", Unit: "kWh", Type: PointTypeDaily },
"p83010": { PsKey: "1129147_11_0_0", Id: "p83010", Description: "Inverter PR", Unit: "%", Type: PointTypeInstant },
"p83013": { PsKey: "1129147_11_0_0", Id: "p83013", Description: "Daily Irradiation", Unit: "Wh/m2" },
"p83016": { PsKey: "1129147_11_0_0", Id: "p83016", Description: "Plant Ambient Temperature", Unit: "℃", Type: PointTypeInstant },
"p83017": { PsKey: "1129147_11_0_0", Id: "p83017", Description: "Plant Module Temperature", Unit: "℃", Type: PointTypeInstant },
"p83018": { PsKey: "1129147_11_0_0", Id: "p83018", Description: "Daily Yield (Theoretical)", Unit: "kWh", Type: PointTypeDaily },
"p83019": { PsKey: "1129147_11_0_0", Id: "p83019", Description: "Power/Installed Power of Plant", Unit: "%", Type: PointTypeInstant },
"p83020": { PsKey: "1129147_11_0_0", Id: "p83020", Description: "Meter Total Yield", Unit: "kWh" },
"p83021": { PsKey: "1129147_11_0_0", Id: "p83021", Description: "Accumulative Power Consumption by Meter", Unit: "kWh" },
"p83022": { PsKey: "1129147_11_0_0", Id: "p83022", Description: "Daily Yield of Plant", Unit: "kWh", Type: PointTypeDaily },
"p83023": { PsKey: "1129147_11_0_0", Id: "p83023", Description: "Plant PR", Unit: "%", Type: PointTypeInstant },
"p83024": { PsKey: "1129147_11_0_0", Id: "p83024", Description: "Plant Total Yield", Unit: "kWh" },
"p83025": { PsKey: "1129147_11_0_0", Id: "p83025", Description: "Plant Equivalent Hours", Unit: "h" },
"p83032": { PsKey: "1129147_11_0_0", Id: "p83032", Description: "Meter AC Power", Unit: "kW" },
"p83033": { PsKey: "1129147_11_0_0", Id: "p83033", Description: "Plant Power", Unit: "kW" },
"p83097": { PsKey: "1129147_11_0_0", Id: "p83097", Description: "Daily Load Energy Consumption from PV", Unit: "kWh", Type: PointTypeDaily },
"p83100": { PsKey: "1129147_11_0_0", Id: "p83100", Description: "Total Load Energy Consumption from PV", Unit: "kWh" },
"p83102": { PsKey: "1129147_11_0_0", Id: "p83102", Description: "Daily Purchased Energy", Unit: "kWh", Type: PointTypeDaily },
"p83105": { PsKey: "1129147_11_0_0", Id: "p83105", Description: "Total Purchased Energy", Unit: "kWh" },
"p83106": { PsKey: "1129147_11_0_0", Id: "p83106", Description: "Load Power", Unit: "kW" },
"p83124": { PsKey: "1129147_11_0_0", Id: "p83124", Description: "Total Load Energy Consumption", Unit: "MWh" },
"p83128": { PsKey: "1129147_11_0_0", Id: "p83128", Description: "Total Active Power of Optical Storage", Unit: "kW" },
"p83129": { PsKey: "1129147_11_0_0", Id: "p83129", Description: "Battery SOC", Unit: "%", Type: PointTypeInstant },
"p83233": { PsKey: "1129147_11_0_0", Id: "p83233", Description: "Total field maximum rechargeable power", Unit: "MW" },
"p83234": { PsKey: "1129147_11_0_0", Id: "p83234", Description: "Total field maximum dischargeable power", Unit: "MW" },
"p83235": { PsKey: "1129147_11_0_0", Id: "p83235", Description: "Total field chargeable energy", Unit: "MWh" },
"p83236": { PsKey: "1129147_11_0_0", Id: "p83236", Description: "Total field dischargeable energy", Unit: "MWh" },
"p83237": { PsKey: "1129147_11_0_0", Id: "p83237", Description: "Total field energy storage maximum reactive power", Unit: "MW" },
"p83238": { PsKey: "1129147_11_0_0", Id: "p83238", Description: "Total field energy storage active power", Unit: "MW" },
"p83239": { PsKey: "1129147_11_0_0", Id: "p83239", Description: "Total field reactive power", Unit: "Mvar" },
"p83241": { PsKey: "1129147_11_0_0", Id: "p83241", Description: "Total field charge capacity", Unit: "MWh" },
"p83242": { PsKey: "1129147_11_0_0", Id: "p83242", Description: "Total field discharge capacity", Unit: "MWh" },
"p83243": { PsKey: "1129147_11_0_0", Id: "p83243", Description: "Total field daily charge capacity", Unit: "MWh" },
"p83244": { PsKey: "1129147_11_0_0", Id: "p83244", Description: "Total field daily discharge capacity", Unit: "MWh" },
"p83252": { PsKey: "1129147_11_0_0", Id: "p83252", Description: "Battery Level (SOC)", Unit: "%", Type: PointTypeInstant },
"p83419": { PsKey: "1129147_11_0_0", Id: "p83419", Description: "Daily Highest Inverter Power/Inverter Installed Capacity", Unit: "%" },
"p83420": { PsKey: "1129147_11_0_0", Id: "p83420", Description: "Current Power/Inverter Installed Capacity", Unit: "%", Type: PointTypeInstant },
"p83549": { PsKey: "1129147_11_0_0", Id: "p83549", Description: "Grid active power", Unit: "kW" },
"p23014": { PsKey: "1129147_22_247_1", Id: "p23014", Description: "WLAN Signal Strength", Unit: "" },
}
func GetPoint(device string, point string) *Point {
return Points.Get(device, point)
}
func GetPointInt(device string, point int64) *Point {
return Points.Get(device, strconv.FormatInt(point, 10))
}
func GetDevicePoint(devicePoint string) *Point {
return Points.GetDevicePoint(devicePoint)
}
// func GetPointName(device string, point int64) string {
// return fmt.Sprintf("%s.%d", device, point)
// }
func NameDevicePointInt(device string, point int64) string {
return fmt.Sprintf("%s.%d", device, point)
}
func NameDevicePoint(device string, point string) string {
return fmt.Sprintf("%s.%s", device, point)
}
func (p *Point) WhenReset() string {
var ret string
for range Only.Once {
var err error
now := time.Now()
switch {
case p.IsInstant():
ret = ""
case p.IsDaily():
now, err = time.Parse("2006-01-02T15:04:05", now.Format("2006-01-02") + "T00:00:00")
// ret = fmt.Sprintf("%d", now.Unix())
ret = now.Format("2006-01-02T15:04:05") + ""
case p.IsMonthly():
now, err = time.Parse("2006-01-02T15:04:05", now.Format("2006-01") + "-01T00:00:00")
ret = fmt.Sprintf("%d", now.Unix())
ret = now.Format("2006-01-02T15:04:05") + ""
case p.IsYearly():
now, err = time.Parse("2006-01-02T15:04:05", now.Format("2006") + "-01-01T00:00:00")
ret = fmt.Sprintf("%d", now.Unix())
ret = now.Format("2006-01-02T15:04:05") + ""
case p.IsTotal():
// ret = "0"
ret = "1970-01-01T00:00:00"
default:
// ret = "0"
ret = "1970-01-01T00:00:00"
}
if err != nil {
now := time.Now()
ret = fmt.Sprintf("%d", now.Unix())
}
}
return ret
}
func (p Point) String() string {
return p.Type
}
func (pm PointsMap) Get(device string, point string) *Point {
dp := device + ".p" + strings.TrimPrefix(point, "p")
if p, ok := pm[dp]; ok {
return &p
}
dp = "p" + strings.TrimPrefix(point, "p")
if p, ok := pm[dp]; ok {
return &p
}
return nil
}
func (pm PointsMap) GetDevicePoint(devicePoint string) *Point {
ret := &Point{}
for range Only.Once {
s := strings.Split(devicePoint, ".")
if len(s) < 2 {
break
}
ret = pm.Get(s[0], s[1])
}
return ret
}
func (p Point) IsInstant() bool {
if p.Type == PointTypeInstant {
return true
}
return false
}
func (p Point) IsDaily() bool {
if p.Type == PointTypeDaily {
return true
}
return false
}
func (p Point) IsMonthly() bool {
if p.Type == PointTypeMonthly {
return true
}
return false
}
func (p Point) IsYearly() bool {
if p.Type == PointTypeYearly {
return true
}
return false
}
func (p Point) IsTotal() bool {
if p.Type == PointTypeTotal {
return true
}
return false
}
func UpperCase(s string) string {
s = strings.ReplaceAll(s, "_", " ")
s = strings.Title(s)
return s
}
type Data struct {
Entries []DataEntry
}
type DataEntry struct {
Date DateTime `json:"date"`
PointId string `json:"point_id"`
PointGroupName string `json:"point_group_name"`
PointName string `json:"point_name"`
Value string `json:"value"`
Unit string `json:"unit"`
ValueType *Point `json:"value_type"`
Index int `json:"index"`
}

View File

@ -48,17 +48,21 @@ func (req ResponseCommon) IsTokenValid() bool {
var ok bool
for range Only.Once {
switch {
case req.ResultMsg == "success":
ok = true
case req.ResultMsg == "er_token_login_invalid":
ok = false
default:
ok = false
case req.ResultMsg == "success":
ok = true
case req.ResultMsg == "er_token_login_invalid":
ok = false
default:
ok = false
}
}
return ok
}
func (req ResponseCommon) IsTokenInvalid() bool {
return !req.IsTokenValid()
}
func (req ResponseCommon) String() string {
var ret string
ret = fmt.Sprintf("ReqSerialNum:\t%s\n", req.ReqSerialNum)

View File

@ -1,6 +1,74 @@
package api
import (
"GoSungrow/Only"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
)
type UnitValue struct {
Unit string `json:"unit"`
Value string `json:"value"`
Point *Point `json:"point"`
}
type UnitValues []UnitValue
type UnitValueMap map[string]UnitValue
func (u *UnitValueMap) Sort() []string {
var ret []string
for n := range *u {
ret = append(ret, n)
}
sort.Strings(ret)
return ret
}
func JsonToUnitValue(j string) UnitValue {
var ret UnitValue
for range Only.Once {
err := json.Unmarshal([]byte(j), &ret)
if err != nil {
break
}
}
return ret
}
func Float32ToString(num float64) string {
s := fmt.Sprintf("%.6f", num)
return strings.TrimRight(strings.TrimRight(s, "0"), ".")
}
func Float64ToString(num float64) string {
s := fmt.Sprintf("%.6f", num)
return strings.TrimRight(strings.TrimRight(s, "0"), ".")
}
func DivideByThousand(num string) (string, error) {
fv, err := strconv.ParseFloat(num, 64)
if err == nil {
num = Float64ToString(fv / 1000)
}
return num, err
}
// func (u *UnitValue) GetStructName() string {
// var ret string
// apiReflect.GetName()
// return ret
// }
//
// func (u *UnitValue) GetJsonName() string {
// var ret string
// apiReflect.GetOptionsRequired()
// return ret
// }

View File

@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
)
@ -55,3 +56,40 @@ func GetUrl(u string) *url.URL {
}
return ret
}
func GetStructKeys(ref interface{}, keys ...string) UnitValueMap {
ret := make(UnitValueMap)
for _, k := range apiReflect.GetStructKeys(ref, keys...) {
p := UnitValue { Value: k.Value, Unit: "" }
if k.Type.Name() == "UnitValue" {
// v = JsonToUnitValue(k.JsonValue).Value
// u = JsonToUnitValue(k.JsonValue).Unit
p = JsonToUnitValue(k.JsonValue)
p.Value, p.Unit = DivideByThousandIfRequired(p.Value, p.Unit)
}
k.JsonName = strings.TrimSuffix(k.JsonName, "_map") // Bit of a hack, but hey... @TODO - Future self take note.
ret[k.JsonName] = p
}
return ret
}
// DivideByThousandIfRequired Sigh.... Another dodgy one.
func DivideByThousandIfRequired(value string, unit string) (string, string) {
switch unit {
case "Wh":
fallthrough
case "W":
fv, err := strconv.ParseFloat(value, 64)
if err == nil {
fv = fv / 1000
value, _ = DivideByThousand(value)
unit = "k" + unit
}
}
return value, unit
}

View File

@ -2,12 +2,14 @@ package iSolarCloud
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/AppService/getPsDetailWithPsType"
"GoSungrow/iSolarCloud/AppService/getPsList"
"GoSungrow/iSolarCloud/AppService/queryMutiPointDataList"
"GoSungrow/iSolarCloud/api"
"GoSungrow/iSolarCloud/api/output"
"errors"
"fmt"
"strconv"
"time"
)
@ -142,7 +144,7 @@ func (sg *SunGrow) GetPointData(date string, pointNames api.TemplatePoints) erro
break
}
sg.Error = sg.Output(ep2, table, "")
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}
@ -151,15 +153,21 @@ func (sg *SunGrow) GetPointData(date string, pointNames api.TemplatePoints) erro
return sg.Error
}
func (sg *SunGrow) Output(endpoint api.EndPoint, table output.Table, graphFilter string) error {
func (sg *SunGrow) Output(endpoint api.EndPoint, table *output.Table, graphFilter string) error {
for range Only.Once {
switch {
case sg.OutputType.IsNone():
case sg.OutputType.IsHuman():
if table == nil {
break
}
table.Print()
case sg.OutputType.IsFile():
if table == nil {
break
}
sg.Error = table.WriteCsvFile()
case sg.OutputType.IsRaw():
@ -169,6 +177,9 @@ func (sg *SunGrow) Output(endpoint api.EndPoint, table output.Table, graphFilter
fmt.Println(endpoint.GetJsonData(false))
case sg.OutputType.IsGraph():
if table == nil {
break
}
sg.Error = table.SetGraphFromJson(output.Json(graphFilter))
if sg.Error != nil {
break
@ -203,6 +214,32 @@ func (sg *SunGrow) GetPsId() (int64, error) {
return ret, sg.Error
}
func (sg *SunGrow) GetPsKeys() ([]string, error) {
var ret []string
for range Only.Once {
var psId int64
psId, sg.Error = sg.GetPsId()
if sg.Error != nil {
break
}
ep := sg.GetByStruct(
"AppService.getPsDetailWithPsType",
getPsDetailWithPsType.RequestData{PsId: strconv.FormatInt(psId, 10)},
DefaultCacheTimeout)
if ep.IsError() {
sg.Error = ep.GetError()
break
}
ep2 := getPsDetailWithPsType.Assert(ep)
ret = ep2.GetPsKeys()
}
return ret, sg.Error
}
func fillArray(count int, args []string) []string {
var ret []string
for range Only.Once {

View File

@ -3,16 +3,20 @@ package iSolarCloud
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/AppService/getPowerDevicePointNames"
"GoSungrow/iSolarCloud/AppService/getPsDetailWithPsType"
"GoSungrow/iSolarCloud/AppService/getPsList"
"GoSungrow/iSolarCloud/AppService/getTemplateList"
"GoSungrow/iSolarCloud/AppService/queryDeviceList"
"GoSungrow/iSolarCloud/AppService/queryDeviceRealTimeDataByPsKeys"
"GoSungrow/iSolarCloud/AppService/queryMutiPointDataList"
"GoSungrow/iSolarCloud/WebAppService/getMqttConfigInfoByAppkey"
"GoSungrow/iSolarCloud/WebAppService/queryUserCurveTemplateData"
"GoSungrow/iSolarCloud/api"
"GoSungrow/iSolarCloud/api/output"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
@ -103,7 +107,7 @@ func (sg *SunGrow) GetTemplateData(template string, date string, filter string)
break
}
sg.Error = sg.Output(ep, table, filter)
sg.Error = sg.Output(ep, &table, filter)
if sg.Error != nil {
break
}
@ -251,13 +255,11 @@ func (sg *SunGrow) PrintCurrentStats() error {
break
}
sg.Error = sg.Output(_getPsList, table, "")
sg.Error = sg.Output(_getPsList, &table, "")
if sg.Error != nil {
break
}
// ep = sg.GetByJson("AppService.queryDeviceList", fmt.Sprintf(`{"ps_id":"%d"}`, psId))
ep = sg.GetByStruct(
"AppService.queryDeviceList",
queryDeviceList.RequestData{PsId: strconv.FormatInt(psId, 10)},
@ -274,7 +276,7 @@ func (sg *SunGrow) PrintCurrentStats() error {
break
}
sg.Error = sg.Output(ep2, table, "")
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}
@ -328,7 +330,7 @@ func (sg *SunGrow) GetPointNames() error {
break
}
sg.Error = sg.Output(ep2, table, "")
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}
@ -356,7 +358,113 @@ func (sg *SunGrow) GetTemplates() error {
break
}
sg.Error = sg.Output(ep2, table, "")
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}
}
return sg.Error
}
func (sg *SunGrow) GetIsolarcloudMqtt(appKey string) error {
for range Only.Once {
if appKey == "" {
appKey = sg.GetAppKey()
}
ep := sg.GetByStruct(
"WebAppService.getMqttConfigInfoByAppkey",
getMqttConfigInfoByAppkey.RequestData{AppKey: appKey},
DefaultCacheTimeout,
)
if sg.Error != nil {
break
}
ep2 := getMqttConfigInfoByAppkey.Assert(ep)
table := ep2.GetDataTable()
if table.Error != nil {
sg.Error = table.Error
break
}
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}
}
return sg.Error
}
func (sg *SunGrow) GetRealTimeData(psKey string) error {
for range Only.Once {
if psKey == "" {
var psKeys []string
psKeys, sg.Error = sg.GetPsKeys()
if sg.Error != nil {
break
}
fmt.Printf("%v\n", psKeys)
psKey = strings.Join(psKeys, ",")
}
ep := sg.GetByStruct(
"AppService.queryDeviceRealTimeDataByPsKeys",
queryDeviceRealTimeDataByPsKeys.RequestData{PsKeyList: psKey},
DefaultCacheTimeout,
)
if sg.Error != nil {
break
}
ep2 := queryDeviceRealTimeDataByPsKeys.Assert(ep)
table := ep2.GetDataTable()
if table.Error != nil {
sg.Error = table.Error
break
}
sg.Error = sg.Output(ep2, nil, "")
if sg.Error != nil {
break
}
}
return sg.Error
}
func (sg *SunGrow) GetPsDetails(psid string) error {
for range Only.Once {
var psId int64
if psid == "" {
psId, sg.Error = sg.GetPsId()
} else {
psId, sg.Error = strconv.ParseInt(psid, 10, 64)
}
if sg.Error != nil {
break
}
ep := sg.GetByStruct(
"AppService.getPsDetailWithPsType",
getPsDetailWithPsType.RequestData{PsId: strconv.FormatInt(psId, 10)},
DefaultCacheTimeout)
if ep.IsError() {
sg.Error = ep.GetError()
break
}
ep2 := getPsDetailWithPsType.Assert(ep)
table := ep2.GetDataTable()
if table.Error != nil {
sg.Error = table.Error
break
}
sg.Error = sg.Output(ep2, &table, "")
if sg.Error != nil {
break
}

1
iSolarCloud/points.go Normal file
View File

@ -0,0 +1 @@
package iSolarCloud

View File

@ -20,6 +20,7 @@ type SunGrow struct {
Auth login.EndPoint
Areas api.Areas
Error error
NeedLogin bool
OutputType output.OutputType
// EndPoint api.EndPoint

1
mmMqtt/cron.go Normal file
View File

@ -0,0 +1 @@
package mmMqtt

View File

@ -96,6 +96,7 @@ type Sensor struct {
UniqueId string `json:"unique_id,omitempty" required:"false"`
UnitOfMeasurement string `json:"unit_of_measurement,omitempty" required:"false"`
ValueTemplate string `json:"value_template,omitempty" required:"false"`
LastReset string `json:"last_reset,omitempty" required:"false"`
// StateFunc func() string `json:"-"`
//

View File

@ -2,6 +2,8 @@ package mmMqtt
import (
"GoSungrow/Only"
"GoSungrow/iSolarCloud/api"
"encoding/json"
"errors"
"fmt"
mqtt "github.com/eclipse/paho.mqtt.golang"
@ -13,17 +15,21 @@ import (
type Mqtt struct {
ClientId string `json:"client_id"`
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
Port string `json:"port"`
ClientId string `json:"client_id"`
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
Port string `json:"port"`
url *url.URL
client mqtt.Client
pubClient mqtt.Client
url *url.URL
client mqtt.Client
pubClient mqtt.Client
clientOptions *mqtt.ClientOptions
err error
LastRefresh time.Time `json:"-"`
PsId int64 `json:"-"`
firstRun bool
err error
}
func New(req Mqtt) *Mqtt {
@ -34,11 +40,24 @@ func New(req Mqtt) *Mqtt {
if ret.err != nil {
break
}
ret.firstRun = true
}
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
}
@ -50,6 +69,20 @@ func (m *Mqtt) IsError() bool {
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 {
@ -66,7 +99,7 @@ func (m *Mqtt) setUrl(req Mqtt) error {
m.Password = req.Password
if req.Host == "" {
m.err = errors.New("host empty")
m.err = errors.New("HASSIO mqtt host not defined")
break
}
m.Host = req.Host
@ -197,13 +230,14 @@ func (m *Mqtt) SensorPublish(subtopic string, payload interface{}) error {
return m.err
}
func (m *Mqtt) SensorPublishConfig(id string, name string, units string, address int) error {
// func (m *Mqtt) SensorPublishConfig(id string, name string, units string, address int) error {
func (m *Mqtt) SensorPublishConfig(point api.DataEntry) error {
for range Only.Once {
a := strconv.Itoa(address)
id = strings.ReplaceAll("sungrow_" + id, ".", "-")
a := strconv.Itoa(point.Index)
id := strings.ReplaceAll("sungrow_" + point.PointId, ".", "-")
class := ""
switch units {
switch point.Unit {
case "MW":
fallthrough
case "kW":
@ -237,35 +271,74 @@ func (m *Mqtt) SensorPublishConfig(id string, name string, units string, address
class = "battery"
}
LastReset := m.GetLastReset(point.PointId)
LastResetValueTemplate := ""
if LastReset != "" {
LastResetValueTemplate = "{{ value_json.last_reset | as_datetime() }}"
// LastResetValueTemplate = "{{ value_json.last_reset | int | timestamp_local | as_datetime }}"
}
payload := Sensor {
Device: Device {
Connections: [][]string{{"sungrow_address", a}},
Identifiers: []string{id, "sungrow_address_" + a},
Manufacturer: "MickMake",
Model: "SunGrow inverter",
Name: name,
Name: point.PointName,
SwVersion: "GoSunGrow https://github.com/MickMake/GoSungrow",
ViaDevice: "gosungrow",
},
Name: "SunGrow " + name,
StateClass: "measurement",
StateTopic: SensorBaseTopic + "/" + id + "/state",
UniqueId: id,
UnitOfMeasurement: units,
DeviceClass: class,
Qos: 0,
ForceUpdate: true,
ExpireAfter: 0,
Encoding: "utf-8",
EnabledByDefault: true,
Name: "SunGrow " + point.PointName,
StateClass: "measurement",
StateTopic: SensorBaseTopic + "/" + id + "/state",
UniqueId: id,
UnitOfMeasurement: point.Unit,
DeviceClass: class,
Qos: 0,
ForceUpdate: true,
ExpireAfter: 0,
Encoding: "utf-8",
EnabledByDefault: true,
LastResetValueTemplate: LastResetValueTemplate,
LastReset: LastReset,
ValueTemplate: "{{ value_json.value | float }}",
// LastReset: time.Now().Format("2006-01-02T00:00:00+00:00"),
// LastResetValueTemplate: "{{entity_id}}",
// LastResetValueTemplate: "{{ (as_datetime((value_json.last_reset | int | timestamp_utc)|string+'Z')).isoformat() }}",
}
topic := SensorBaseTopic + "/" + id + "/config"
m.client.Publish(topic, 0, true, payload.Json())
m.client.Publish(SensorBaseTopic + "/" + id + "/config", 0, true, payload.Json())
}
return m.err
}
// func (m *Mqtt) SensorPublishValue(id string, value string) error {
func (m *Mqtt) SensorPublishValue(point api.DataEntry) error {
for range Only.Once {
id := strings.ReplaceAll("sungrow_" + point.PointId, ".", "-")
payload := MqttState {
LastReset: m.GetLastReset(point.PointId),
Value: point.Value,
}
m.client.Publish(SensorBaseTopic + "/" + id + "/state", 0, true, payload.Json())
}
return m.err
}
func (m *Mqtt) GetLastReset(pointType string) string {
var ret string
for range Only.Once {
pt := api.GetDevicePoint(pointType)
if pt == nil {
break
}
ret = pt.WhenReset()
}
return ret
}
func (m *Mqtt) SensorPublishState(id string, payload interface{}) error {
for range Only.Once {
id = strings.ReplaceAll("sungrow_" + id, ".", "-")
@ -274,105 +347,20 @@ func (m *Mqtt) SensorPublishState(id string, payload interface{}) error {
return m.err
}
type MqttState struct {
LastReset string `json:"last_reset,omitempty"`
Value string `json:"value"`
}
// func (m *Mqtt) SetKeyFile(path string) error {
//
// for range Only.Once {
// if path == "" {
// break
// }
//
// m.err = checkKeyFile(path)
// if m.err != nil {
// break
// }
//
// m.KeyFile = path
// }
//
// return m.err
// }
//
// func (m *Mqtt) SetToken(t string) error {
//
// for range Only.Once {
// if t == "" {
// break
// }
//
// m.Token = t
// }
//
// return m.err
// }
//
// func (m *Mqtt) SetRepo(repo string) error {
//
// for range Only.Once {
// if repo == "" {
// m.err = errors.New("repo empty")
// break
// }
// m.RepoUrl = repo
// }
//
// return m.err
// }
//
// func (m *Mqtt) SetDir(dir string) error {
//
// for range Only.Once {
// if dir == "" {
// m.err = errors.New("dir empty")
// break
// }
// m.RepoDir = dir
// }
//
// return m.err
// }
//
// func (m *Mqtt) SetDiffCmd(cmd string) error {
//
// for range Only.Once {
// if cmd == "" {
// cmd = "tkdiff"
// }
// m.DiffCmd = cmd
// }
//
// return m.err
// }
//
// func (m *Mqtt) IsOk() bool {
// var ok bool
//
// for range Only.Once {
// //if m.ApiUsername == "" {
// // m.Error = errors.New("username empty")
// // break
// //}
// //
// //if m.ApiPassword == "" {
// // m.Error = errors.New("password empty")
// // break
// //}
//
// if m.RepoUrl == "" {
// m.err = errors.New("repo empty")
// break
// }
//
// if m.RepoDir == "" {
// m.err = errors.New("repo dir empty")
// break
// }
//
// ok = true
// }
//
// return ok
// }
// func (m *Mqtt) IsNotOk() bool {
// return !m.IsOk()
// }
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
}