2023-01-07 16:41:15 +11:00
|
|
|
/*
|
|
|
|
Copyright 2023 Josh Deprez
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-01-01 16:51:16 +11:00
|
|
|
package main
|
|
|
|
|
2021-01-12 10:45:52 +11:00
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2021-01-01 16:51:16 +11:00
|
|
|
type register struct {
|
|
|
|
name string
|
2021-01-11 10:21:53 +11:00
|
|
|
conv func([]byte) (float64, error)
|
2021-01-01 16:51:16 +11:00
|
|
|
mult float64
|
|
|
|
unit string
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:21:53 +11:00
|
|
|
func (r *register) read(data []byte) (float64, error) {
|
|
|
|
n, err := r.conv(data)
|
|
|
|
return n * r.mult, err
|
2021-01-01 16:51:16 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
var sungrowInputRegs = map[uint16]*register{
|
|
|
|
// 4990 - 4999: serial number in ASCII
|
2021-01-26 10:24:25 +11:00
|
|
|
5001: {"nominal_power", u16req, 0.1, "kW"},
|
|
|
|
5003: {"daily_power_yield", u16req, 0.1, "kWh"},
|
|
|
|
5004: {"total_power_yield", u32req, 1, "kWh"},
|
|
|
|
5006: {"total_running_time", u32req, 1, "h"},
|
|
|
|
5008: {"internal_temperature", u16req, 0.1, "°C"},
|
|
|
|
5011: {"pv1_potential", u16req, 0.1, "V"},
|
|
|
|
5012: {"pv1_current", u16req, 0.1, "A"},
|
|
|
|
5013: {"pv2_potential", u16req, 0.1, "V"},
|
|
|
|
5014: {"pv2_current", u16req, 0.1, "A"},
|
|
|
|
5015: {"pv3_potential", u16opt, 0.1, "V"},
|
|
|
|
5016: {"pv3_current", u16opt, 0.1, "A"},
|
|
|
|
5017: {"total_dc_power", u32req, 1, "W"},
|
|
|
|
5019: {"phase_a_potential", u16req, 0.1, "V"},
|
|
|
|
5020: {"phase_b_potential", u16opt, 0.1, "V"},
|
|
|
|
5021: {"phase_c_potential", u16opt, 0.1, "V"},
|
|
|
|
5022: {"phase_a_current", u16req, 0.1, "A"},
|
|
|
|
5023: {"phase_b_current", u16opt, 0.1, "A"},
|
|
|
|
5024: {"phase_c_current", u16opt, 0.1, "A"},
|
|
|
|
5031: {"output_real_power", u32req, 1, "VA"},
|
|
|
|
5033: {"output_reactive_power", s32opt, 1, "VAr"},
|
|
|
|
5035: {"power_factor", s16opt, 0.001, ""},
|
|
|
|
5036: {"frequency", u16req, 0.1, "Hz"},
|
|
|
|
5049: {"nominal_reactive_power", s16opt, 0.1, "kVA"},
|
|
|
|
|
2021-02-03 21:25:08 +11:00
|
|
|
5083: {"meter_power", s32req, 1, "W"},
|
2021-02-03 21:27:41 +11:00
|
|
|
5085: {"meter_a_phase_power", s32opt, 1, "W"},
|
|
|
|
5087: {"meter_b_phase_power", s32opt, 1, "W"},
|
|
|
|
5089: {"meter_c_phase_power", s32opt, 1, "W"},
|
2021-02-03 21:25:08 +11:00
|
|
|
5091: {"usage_power", s32req, 1, "W"},
|
|
|
|
5093: {"daily_export_energy", u32req, 0.1, "kWh"},
|
|
|
|
5095: {"total_export_energy", u32req, 0.1, "kWh"},
|
|
|
|
5097: {"daily_import_energy", u32req, 0.1, "kWh"},
|
|
|
|
5099: {"total_import_energy", u32req, 0.1, "kWh"},
|
2021-01-26 10:24:25 +11:00
|
|
|
|
|
|
|
5113: {"daily_running_time", u16req, 1, "m"},
|
|
|
|
5144: {"total_power_yield_2", u32req, 0.1, "kWh"},
|
2021-01-12 10:45:52 +11:00
|
|
|
5148: {"frequency_2", frequency2, 0.01, "Hz"},
|
|
|
|
}
|
|
|
|
|
|
|
|
var errSkippableRead = errors.New("skip read")
|
|
|
|
|
|
|
|
func frequency2(data []byte) (float64, error) {
|
|
|
|
n := binary.BigEndian.Uint16(data)
|
|
|
|
if n == 0xFFFF {
|
|
|
|
// probably a misread
|
|
|
|
return 0, fmt.Errorf("likely misread: frequency_2 read %v", n)
|
|
|
|
}
|
|
|
|
if n == 100 {
|
|
|
|
// this translates as 1 Hz, which is unlikely
|
2021-01-12 10:46:46 +11:00
|
|
|
return 0, fmt.Errorf("%w: frequency_2 is 1.00Hz", errSkippableRead)
|
2021-01-12 10:45:52 +11:00
|
|
|
}
|
|
|
|
return float64(n), nil
|
2021-01-01 16:51:16 +11:00
|
|
|
}
|
2021-01-12 10:49:06 +11:00
|
|
|
|
2021-01-26 10:24:25 +11:00
|
|
|
func u16opt(data []byte) (float64, error) {
|
|
|
|
return float64(binary.BigEndian.Uint16(data)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func u16req(data []byte) (float64, error) {
|
2021-01-12 10:49:06 +11:00
|
|
|
n := binary.BigEndian.Uint16(data)
|
|
|
|
if n == 0xFFFF {
|
|
|
|
// probably a misread
|
2021-01-26 10:24:25 +11:00
|
|
|
return 0, fmt.Errorf("likely misread: u16req read %v", n)
|
2021-01-12 10:49:06 +11:00
|
|
|
}
|
|
|
|
return float64(n), nil
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:24:25 +11:00
|
|
|
func s16opt(data []byte) (float64, error) {
|
2021-01-12 10:49:06 +11:00
|
|
|
return float64(int16(binary.BigEndian.Uint16(data))), nil
|
|
|
|
}
|
|
|
|
|
2023-01-07 16:41:15 +11:00
|
|
|
// func s16req(data []byte) (float64, error) {
|
|
|
|
// n := binary.BigEndian.Uint16(data)
|
|
|
|
// if n == 0x7FFF {
|
|
|
|
// // probably a misread
|
|
|
|
// return 0, fmt.Errorf("likely misread: s16req read %v", n)
|
|
|
|
// }
|
|
|
|
// return float64(int16(n)), nil
|
|
|
|
// }
|
|
|
|
|
|
|
|
// func u32opt(data []byte) (float64, error) {
|
|
|
|
// // Little-endian big-endian :#
|
|
|
|
// return float64(uint32(binary.BigEndian.Uint16(data)) + uint32(binary.BigEndian.Uint16(data[2:]))<<16), nil
|
|
|
|
// }
|
2021-01-26 10:24:25 +11:00
|
|
|
|
|
|
|
func u32req(data []byte) (float64, error) {
|
2021-01-12 10:49:06 +11:00
|
|
|
// Little-endian big-endian :#
|
|
|
|
n := uint32(binary.BigEndian.Uint16(data)) + uint32(binary.BigEndian.Uint16(data[2:]))<<16
|
|
|
|
if n == 0xFFFFFFFF {
|
|
|
|
// probably a misread
|
2021-01-26 10:24:25 +11:00
|
|
|
return 0, fmt.Errorf("likely misread: u32req read %v", n)
|
2021-01-12 10:49:06 +11:00
|
|
|
}
|
|
|
|
return float64(n), nil
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:24:25 +11:00
|
|
|
func s32opt(data []byte) (float64, error) {
|
2021-01-12 10:49:06 +11:00
|
|
|
// Little-endian big-endian :#
|
|
|
|
return float64(int32(binary.BigEndian.Uint16(data)) + int32(binary.BigEndian.Uint16(data[2:]))<<16), nil
|
|
|
|
}
|
2021-01-26 10:24:25 +11:00
|
|
|
|
|
|
|
func s32req(data []byte) (float64, error) {
|
|
|
|
// Little-endian big-endian :#
|
|
|
|
n := uint32(binary.BigEndian.Uint16(data)) + uint32(binary.BigEndian.Uint16(data[2:]))<<16
|
|
|
|
if n == 0x7FFFFFFF {
|
|
|
|
// probably a misread
|
|
|
|
return 0, fmt.Errorf("likely misread: s32req read %v", n)
|
|
|
|
}
|
|
|
|
return float64(int32(n)), nil
|
|
|
|
}
|