Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

myPV Elwa2: refactor #15466

Merged
merged 6 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions charger/mypv-elwa2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package charger

// LICENSE

// Copyright (c) 2024 andig

// This module is NOT covered by the MIT license. All rights reserved.

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import (
"encoding/binary"
"sync/atomic"
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus"
"github.com/evcc-io/evcc/util/sponsor"
)

// MyPvElwa2 charger implementation
type MyPvElwa2 struct {
log *util.Logger
conn *modbus.Connection
power uint32
}

const (
elwaRegSetPower = 1000
elwaRegTemp = 1001
elwaRegTempLimit = 1002
elwaRegStatus = 1003
elwaRegPower = 1074
)

func init() {
registry.Add("ac-elwa-2", NewMyPvElwa2FromConfig)
}

// https://github.com/evcc-io/evcc/discussions/12761

// NewMyPvElwa2FromConfig creates a MyPvElwa2 charger from generic config
func NewMyPvElwa2FromConfig(other map[string]interface{}) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: 1,
}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

return NewMyPvElwa2(cc.URI, cc.ID)
}

// NewMyPvElwa2 creates myPV AC Elwa 2 charger
func NewMyPvElwa2(uri string, slaveID uint8) (api.Charger, error) {
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
if err != nil {
return nil, err
}

if !sponsor.IsAuthorized() {
return nil, api.ErrSponsorRequired
}

log := util.NewLogger("ac-elwa-2")
conn.Logger(log.TRACE)

wb := &MyPvElwa2{
log: log,
conn: conn,
}

go wb.heartbeat(30 * time.Second)

return wb, nil
}

var _ api.IconDescriber = (*MyPvElwa2)(nil)

// Icon implements the api.IconDescriber interface
func (v *MyPvElwa2) Icon() string {
return "waterheater"
}

var _ api.FeatureDescriber = (*MyPvElwa2)(nil)

// Features implements the api.FeatureDescriber interface
func (wb *MyPvElwa2) Features() []api.Feature {
return []api.Feature{api.IntegratedDevice, api.Heating}
}

func (wb *MyPvElwa2) heartbeat(timeout time.Duration) {
for range time.Tick(timeout) {
if power := uint16(atomic.LoadUint32(&wb.power)); power > 0 {
enabled, err := wb.Enabled()
if err == nil && enabled {
err = wb.setPower(power)
}
if err != nil {
wb.log.ERROR.Println("heartbeat:", err)
}
}
}
}

// Status implements the api.Charger interface
func (wb *MyPvElwa2) Status() (api.ChargeStatus, error) {
res := api.StatusA
b, err := wb.conn.ReadInputRegisters(elwaRegStatus, 1)
if err != nil {
return res, err
}

res = api.StatusB
if binary.BigEndian.Uint16(b) == 2 {
res = api.StatusC
}

return res, nil
}

// Enabled implements the api.Charger interface
func (wb *MyPvElwa2) Enabled() (bool, error) {
b, err := wb.conn.ReadHoldingRegisters(elwaRegSetPower, 1)
if err != nil {
return false, err
}

return binary.BigEndian.Uint16(b) > 0, nil
}

func (wb *MyPvElwa2) setPower(power uint16) error {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, power)

_, err := wb.conn.WriteMultipleRegisters(elwaRegSetPower, 1, b)
return err
}

// Enable implements the api.Charger interface
func (wb *MyPvElwa2) Enable(enable bool) error {
var power uint16
if enable {
power = uint16(atomic.LoadUint32(&wb.power))
}

return wb.setPower(power)
}

// MaxCurrent implements the api.Charger interface
func (wb *MyPvElwa2) MaxCurrent(current int64) error {
return wb.MaxCurrentMillis(float64(current))
}

var _ api.ChargerEx = (*MyPvElwa2)(nil)

// MaxCurrentMillis implements the api.ChargerEx interface
func (wb *MyPvElwa2) MaxCurrentMillis(current float64) error {
power := uint16(230 * current)

err := wb.setPower(power)
if err == nil {
atomic.StoreUint32(&wb.power, uint32(power))
}

return err
}

var _ api.Meter = (*MyPvElwa2)(nil)

// CurrentPower implements the api.Meter interface
func (wb *MyPvElwa2) CurrentPower() (float64, error) {
b, err := wb.conn.ReadInputRegisters(elwaRegPower, 1)
if err != nil {
return 0, err
}

return float64(binary.BigEndian.Uint16(b)), nil
}

var _ api.Battery = (*MyPvElwa2)(nil)

// CurrentPower implements the api.Meter interface
func (wb *MyPvElwa2) Soc() (float64, error) {
b, err := wb.conn.ReadInputRegisters(elwaRegTemp, 1)
if err != nil {
return 0, err
}

return float64(binary.BigEndian.Uint16(b)) / 10, nil
}

var _ api.SocLimiter = (*MyPvElwa2)(nil)

// GetLimitSoc implements the api.SocLimiter interface
func (wb *MyPvElwa2) GetLimitSoc() (int64, error) {
b, err := wb.conn.ReadInputRegisters(elwaRegTempLimit, 1)
if err != nil {
return 0, err
}

return int64(binary.BigEndian.Uint16(b)) / 10, nil
}
52 changes: 1 addition & 51 deletions templates/definition/charger/ac-elwa-2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,4 @@ params:
- name: modbus
choice: ["tcpip"]
render: |
type: custom
enable:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 1000
type: writeholding
decode: uint16
enabled:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 1000
type: holding
decode: uint16
maxcurrent:
source: go
script: |
230 * maxcurrent
out:
- name: power
type: int
config:
source: modbus
{{- include "modbus" . | indent 8 }}
register:
address: 1000
type: writeholding
decode: uint16
status:
source: combined
plugged:
source: const
value: 1
charging:
source: modbus
{{- include "modbus" . | indent 4 }}
register:
address: 1074 # alternativ 1003 mit bit pattern
type: holding
decode: uint16
soc:
source: modbus
{{- include "modbus" . | indent 2 }}
register:
address: 1001
type: holding
decode: uint16
scale: 0.1
features: ["integrateddevice", "heating"]
icon: waterheater
{{- include "modbus" . }}