Add price zones to fixed tariff (#5583)

This commit is contained in:
andig 2022-12-29 10:14:24 +01:00 • committed by GitHub
parent 34c57bf7c8
commit 83e6773862
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 683 additions and 13 deletions

View file

@ -139,9 +139,15 @@ loadpoints:
tariffs:
currency: EUR # three letter ISO-4217 currency code (default EUR)
grid:
# either static grid price
# either static grid price (or price zones)
type: fixed
price: 0.294 # EUR/kWh
zones:
- days: Mo-Fr
hours: 2-5
price: 0.2 # EUR/kWh
- days: Sa,So
price: 0.15 # EUR/kWh
# # or variable via tibber
# type: tibber

7
go.mod
View file

@ -31,10 +31,12 @@ require (
github.com/fatih/structs v1.1.0
github.com/foogod/go-powerwall v0.2.0
github.com/glebarez/sqlite v1.6.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ping/ping v1.1.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/godbus/dbus/v5 v5.1.0
github.com/gokrazy/updater v0.0.0-20211121155532-30ae8cd650ea
github.com/golang-module/carbon v1.7.2
github.com/golang/mock v1.6.0
github.com/google/go-github/v32 v32.1.0
github.com/google/uuid v1.3.0
@ -116,13 +118,15 @@ require (
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/glebarez/go-sqlite v1.20.0 // indirect
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/gobuffalo/envy v1.7.0 // indirect
github.com/gobuffalo/packd v0.3.0 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@ -164,6 +168,7 @@ require (
github.com/rickb777/date v1.20.1 // indirect
github.com/rickb777/plural v1.4.1 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/rogpeppe/go-internal v1.6.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/afero v1.9.3 // indirect

28
go.sum
View file

@ -182,7 +182,9 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
github.com/containrrr/shoutrrr v0.6.1 h1:6ih7jA6mo3t6C97MZbd3SxL/kRizOE3bI9CpBQZ6wzg=
github.com/containrrr/shoutrrr v0.6.1/go.mod h1:ye9jGX5YzMnJ76waaNVWlJ4luhMEyt1EWU5unYTQSb0=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -190,6 +192,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -304,6 +307,14 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
@ -322,6 +333,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gokrazy/updater v0.0.0-20211121155532-30ae8cd650ea h1:NM/sLCKWWc9mnMtJZGbwOtaAwEpCEe6tlcXWKp3LEKk=
github.com/gokrazy/updater v0.0.0-20211121155532-30ae8cd650ea/go.mod h1:PYOvzGOL4nlBmuxu7IyKQTFLaxr61+WPRNRzVtuYOHw=
github.com/golang-module/carbon v1.7.2 h1:92v/5h0YORJPQqAi+dDuqnN/YWJ6rA8nsfgW5dx9N/M=
github.com/golang-module/carbon v1.7.2/go.mod h1:nUMnXq90Rv8a7h2+YOo2BGKS77Y0w/hMPm4/a8h19N8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -530,6 +543,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joeshaw/carwings v0.0.0-20220711204617-d90801cf2848 h1:mUoi+B77PKkv/pYCR7uOh5ouZdgzFAwOZ3HU8Jr1P+4=
github.com/joeshaw/carwings v0.0.0-20220711204617-d90801cf2848/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -552,6 +566,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@ -561,6 +576,7 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0=
github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8=
github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA=
@ -586,6 +602,7 @@ github.com/lorenzodonini/ocpp-go v0.16.1-0.20220927200117-d4afed542168/go.mod h1
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@ -777,8 +794,11 @@ github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGG
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -816,6 +836,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
@ -826,6 +847,7 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
@ -861,6 +883,7 @@ github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDH
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -914,9 +937,11 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -1079,6 +1104,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1086,6 +1112,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1208,6 +1235,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=

View file

@ -1,14 +1,19 @@
package tariff
import (
"time"
"fmt"
"sort"
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/tariff/fixed"
"github.com/evcc-io/evcc/util"
"github.com/golang-module/carbon"
)
type Fixed struct {
Price float64
clock clock.Clock
zones fixed.Zones
}
var _ api.Tariff = (*Fixed)(nil)
@ -18,23 +23,104 @@ func init() {
}
func NewFixedFromConfig(other map[string]interface{}) (api.Tariff, error) {
var cc Fixed
var cc struct {
Price float64
Zones []struct {
Price float64
Days, Hours string
}
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return &cc, nil
t := &Fixed{
clock: clock.New(),
zones: []fixed.Zone{
{Price: cc.Price}, // full week is implicit
},
}
for _, z := range cc.Zones {
days, err := fixed.ParseDays(z.Days)
if err != nil {
return nil, err
}
hours, err := fixed.ParseTimeRanges(z.Hours)
if err != nil && z.Hours != "" {
return nil, err
}
if len(hours) == 0 {
t.zones = append(t.zones, fixed.Zone{
Price: z.Price,
Days: days,
})
continue
}
for _, h := range hours {
t.zones = append(t.zones, fixed.Zone{
Price: z.Price,
Days: days,
Hours: h,
})
}
}
sort.Sort(t.zones)
return t, nil
}
// Rates implements the api.Tariff interface
func (t *Fixed) Rates() (api.Rates, error) {
start := time.Now().Truncate(time.Hour)
rr := api.Rates{{
Start: start,
End: start.Add(time.Duration(24*7) * time.Hour),
Price: t.Price,
}}
var res api.Rates
return rr, nil
start := carbon.Time2Carbon(t.clock.Now().Local()).StartOfDay()
for i := 0; i < 7; i++ {
dow := fixed.Day((start.DayOfWeek() + i) % 7)
zones := t.zones.ForDay(dow)
if len(zones) == 0 {
return nil, fmt.Errorf("no zones for weekday %d", dow)
}
dayStart := start.AddDays(i)
markers := zones.TimeTableMarkers()
for i, m := range markers {
ts := dayStart.AddMinutes(m.Minutes())
var zone *fixed.Zone
for j := len(zones) - 1; j >= 0; j-- {
if zones[j].Hours.Contains(m) {
zone = &zones[j]
break
}
}
if zone == nil {
panic("could not find zone")
}
// end rate at end of day or next marker
end := dayStart.AddDay()
if i+1 < len(markers) {
end = dayStart.AddMinutes(markers[i+1].Minutes())
}
rate := api.Rate{
Price: zone.Price,
Start: ts.Carbon2Time(),
End: end.Carbon2Time(),
}
res = append(res, rate)
}
}
return res, nil
}

116
tariff/fixed/day.go Normal file
View file

@ -0,0 +1,116 @@
package fixed
import (
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/exp/slices"
)
//go:generate enumer -type Day
type Day int
const (
Sunday Day = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
var Week = []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}
var shortDays = map[string]Day{
// english
"sun": Sunday,
"mon": Monday,
"tue": Tuesday,
"wed": Wednesday,
"thu": Thursday,
"fri": Friday,
"sat": Saturday,
// german
"so": Sunday,
"mo": Monday,
"di": Tuesday,
"mi": Wednesday,
"do": Thursday,
"fr": Friday,
"sa": Saturday,
}
// ParseDay parses a single day
func ParseDay(s string) (Day, error) {
s = strings.ToLower(strings.TrimSpace(s))
// full string
if d, err := DayString(s); err == nil {
return d, nil
}
// short string
if d, ok := shortDays[s]; ok {
return d, nil
}
d, err := strconv.Atoi(s)
if d < 0 || d > 7 || err != nil {
return 0, fmt.Errorf("invalid day: %s", s)
}
return Day(d % 7), nil
}
// ParseDays converts a days string into a slice of individual days
// Days format:
//
// day[-day][, ...]
func ParseDays(s string) ([]Day, error) {
var res []Day
for _, segment := range strings.Split(s, ",") {
fromto := strings.SplitN(segment, "-", 2)
if len(fromto) == 0 {
return nil, fmt.Errorf("invalid day range: %s", segment)
}
from, err := ParseDay(fromto[0])
if err != nil {
return nil, err
}
res = append(res, Day(from%7))
if len(fromto) == 2 {
to, err := ParseDay(fromto[1])
if err != nil {
return nil, err
}
if to < from {
to += 7
}
for d := from + 1; d <= to; d++ {
res = append(res, d%7)
}
}
}
if len(res) > 7 {
return nil, errors.New("too many days")
}
sorted := make([]Day, len(res))
copy(sorted, res)
slices.Sort(sorted)
if len(slices.Compact(sorted)) < len(res) {
return nil, errors.New("duplicate days")
}
return res, nil
}

View file

@ -0,0 +1,98 @@
// Code generated by "enumer -type Day"; DO NOT EDIT.
package fixed
import (
"fmt"
"strings"
)
const _DayName = "SundayMondayTuesdayWednesdayThursdayFridaySaturday"
var _DayIndex = [...]uint8{0, 6, 12, 19, 28, 36, 42, 50}
const _DayLowerName = "sundaymondaytuesdaywednesdaythursdayfridaysaturday"
func (i Day) String() string {
if i < 0 || i >= Day(len(_DayIndex)-1) {
return fmt.Sprintf("Day(%d)", i)
}
return _DayName[_DayIndex[i]:_DayIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _DayNoOp() {
var x [1]struct{}
_ = x[Sunday-(0)]
_ = x[Monday-(1)]
_ = x[Tuesday-(2)]
_ = x[Wednesday-(3)]
_ = x[Thursday-(4)]
_ = x[Friday-(5)]
_ = x[Saturday-(6)]
}
var _DayValues = []Day{Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}
var _DayNameToValueMap = map[string]Day{
_DayName[0:6]: Sunday,
_DayLowerName[0:6]: Sunday,
_DayName[6:12]: Monday,
_DayLowerName[6:12]: Monday,
_DayName[12:19]: Tuesday,
_DayLowerName[12:19]: Tuesday,
_DayName[19:28]: Wednesday,
_DayLowerName[19:28]: Wednesday,
_DayName[28:36]: Thursday,
_DayLowerName[28:36]: Thursday,
_DayName[36:42]: Friday,
_DayLowerName[36:42]: Friday,
_DayName[42:50]: Saturday,
_DayLowerName[42:50]: Saturday,
}
var _DayNames = []string{
_DayName[0:6],
_DayName[6:12],
_DayName[12:19],
_DayName[19:28],
_DayName[28:36],
_DayName[36:42],
_DayName[42:50],
}
// DayString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func DayString(s string) (Day, error) {
if val, ok := _DayNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _DayNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to Day values", s)
}
// DayValues returns all values of the enum
func DayValues() []Day {
return _DayValues
}
// DayStrings returns a slice of all String values of the enum
func DayStrings() []string {
strs := make([]string, len(_DayNames))
copy(strs, _DayNames)
return strs
}
// IsADay returns "true" if the value is listed in the enum definition. "false" otherwise
func (i Day) IsADay() bool {
for _, v := range _DayValues {
if i == v {
return true
}
}
return false
}

53
tariff/fixed/day_test.go Normal file
View file

@ -0,0 +1,53 @@
package fixed
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseDays(t *testing.T) {
d, err := ParseDays(" sunday ")
assert.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("sun")
assert.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("so")
assert.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("0 ")
assert.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays(" 7")
assert.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays(" 6-7 ")
assert.NoError(t, err)
assert.Equal(t, []Day{Saturday, Sunday}, d)
d, err = ParseDays("1- 7")
assert.NoError(t, err)
assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
d, err = ParseDays("1, 3-7")
assert.NoError(t, err)
assert.Equal(t, []Day{Monday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
_, err = ParseDays("-1")
assert.EqualError(t, err, "invalid day: ")
_, err = ParseDays(" 8 ")
assert.EqualError(t, err, "invalid day: 8")
_, err = ParseDays("1, 1")
assert.EqualError(t, err, "duplicate days")
_, err = ParseDays("0,1,2,3,4,5,6,7")
assert.EqualError(t, err, "too many days")
}

93
tariff/fixed/timerange.go Normal file
View file

@ -0,0 +1,93 @@
package fixed
import (
"fmt"
"strings"
"time"
)
type HourMin struct {
Hour, Min int
}
func (hm HourMin) Minutes() int {
return 60*hm.Hour + hm.Min
}
func (hm HourMin) IsNil() bool {
return hm.Hour+hm.Min == 0
}
func (hm HourMin) String() string {
return fmt.Sprintf("%02d:%02d", hm.Hour, hm.Min)
}
type TimeRange struct {
From, To HourMin
}
func (tr TimeRange) Contains(hm HourMin) bool {
ts := hm.Minutes()
return tr.From.Minutes() <= ts && (tr.To.IsNil() || tr.To.Minutes() > ts)
}
func (tr TimeRange) IsNil() bool {
return tr.From.IsNil() && tr.To.IsNil()
}
func (tr TimeRange) String() string {
to := tr.To.String()
if tr.To.IsNil() {
to += "(+1)"
}
return tr.From.String() + "-" + to
}
func parseTime(s string) (HourMin, error) {
s = strings.TrimSpace(s)
t, err := time.ParseInLocation("15:04", s, time.Local)
if err == nil {
return HourMin{t.Hour(), t.Minute()}, nil
}
t, err = time.ParseInLocation("15", s, time.Local)
if err == nil {
return HourMin{t.Hour(), 0}, nil
}
return HourMin{}, fmt.Errorf("invalid time: %s", s)
}
func ParseTimeRange(s string) (TimeRange, error) {
fromto := strings.SplitN(s, "-", 2)
if len(fromto) != 2 {
return TimeRange{}, fmt.Errorf("invalid time range: %s", s)
}
from, err := parseTime(fromto[0])
if err != nil {
return TimeRange{}, err
}
to, err := parseTime(fromto[1])
if err != nil {
return TimeRange{}, err
}
return TimeRange{from, to}, nil
}
func ParseTimeRanges(s string) ([]TimeRange, error) {
var res []TimeRange
for _, segment := range strings.Split(s, ",") {
tr, err := ParseTimeRange(segment)
if err != nil {
return nil, err
}
res = append(res, tr)
}
return res, nil
}

View file

@ -0,0 +1,49 @@
package fixed
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseTimeRange(t *testing.T) {
d, err := ParseTimeRanges(" 7:00 - 12:30 ")
assert.NoError(t, err)
assert.Equal(t, []TimeRange{{HourMin{7, 0}, HourMin{12, 30}}}, d, "hour:min range")
d, err = ParseTimeRanges("8-10")
assert.NoError(t, err)
assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{10, 0}}}, d, "hour range")
d, err = ParseTimeRanges("8-10,20-22:30")
assert.NoError(t, err)
assert.Equal(t, []TimeRange{
{HourMin{8, 0}, HourMin{10, 0}},
{HourMin{20, 0}, HourMin{22, 30}},
}, d)
}
func TestTimeRangeContains(t *testing.T) {
tr := TimeRange{
From: HourMin{1, 0},
To: HourMin{2, 0},
}
assert.False(t, tr.Contains(HourMin{0, 0}))
assert.True(t, tr.Contains(HourMin{1, 0}))
assert.True(t, tr.Contains(HourMin{1, 59}))
assert.False(t, tr.Contains(HourMin{2, 0}))
assert.False(t, tr.Contains(HourMin{3, 0}))
}
func TestTimeRangeOpenEndContains(t *testing.T) {
tr := TimeRange{
From: HourMin{1, 0},
}
assert.False(t, tr.Contains(HourMin{0, 0}))
assert.True(t, tr.Contains(HourMin{1, 0}))
assert.True(t, tr.Contains(HourMin{1, 59}))
assert.True(t, tr.Contains(HourMin{2, 0}))
assert.True(t, tr.Contains(HourMin{23, 0}))
}

60
tariff/fixed/zone.go Normal file
View file

@ -0,0 +1,60 @@
package fixed
import (
"sort"
"golang.org/x/exp/slices"
)
type Zone struct {
Price float64
Days []Day
Hours TimeRange
}
type Zones []Zone
// implement sort.Interface
func (r Zones) Len() int {
return len(r)
}
func (r Zones) Less(i, j int) bool {
if r[i].Hours.From.Minutes() == r[j].Hours.From.Minutes() {
return r[i].Hours.To.Minutes() > r[j].Hours.To.Minutes()
}
return r[i].Hours.From.Minutes() < r[j].Hours.From.Minutes()
}
func (r Zones) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
// ForDay returns the zones for given day in ascending order
func (r Zones) ForDay(day Day) Zones {
var zones Zones
for _, z := range r {
if slices.Contains(z.Days, day) || len(z.Days) == 0 {
zones = append(zones, z)
}
}
sort.Sort(zones)
return zones
}
// TimeTableMarkers returns list of zone start/end markers
func (r Zones) TimeTableMarkers() []HourMin {
res := []HourMin{{Hour: 0, Min: 0}}
for _, z := range r {
if !z.Hours.From.IsNil() {
res = append(res, z.Hours.From)
}
if !z.Hours.To.IsNil() {
res = append(res, z.Hours.To)
}
}
return res
}

41
tariff/fixed/zone_test.go Normal file
View file

@ -0,0 +1,41 @@
package fixed
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestZonesForDay(t *testing.T) {
zones := Zones{
{Days: nil},
{Days: []Day{Monday, Tuesday, Wednesday}},
{Days: []Day{Wednesday, Thursday, Friday}},
}
assert.Len(t, zones.ForDay(Sunday), 1)
assert.Len(t, zones.ForDay(Monday), 2)
assert.Len(t, zones.ForDay(Wednesday), 3)
assert.Len(t, zones.ForDay(Thursday), 2)
}
func TestZonesTimeTableMarkers(t *testing.T) {
zones := Zones{
{Hours: TimeRange{
From: HourMin{1, 0},
To: HourMin{2, 0},
}},
{Hours: TimeRange{
From: HourMin{3, 30},
To: HourMin{4, 30},
}},
}
assert.Equal(t, []HourMin{
{0, 0},
{1, 0},
{2, 0},
{3, 30},
{4, 30},
}, zones.TimeTableMarkers())
}

35
tariff/fixed_test.go Normal file
View file

@ -0,0 +1,35 @@
package tariff
import (
"testing"
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/tariff/fixed"
"github.com/golang-module/carbon"
"github.com/stretchr/testify/assert"
)
func TestFixed(t *testing.T) {
tf := &Fixed{
clock: clock.NewMock(),
zones: []fixed.Zone{
{Price: 0.3},
},
}
var expect api.Rates
for i := 0; i < 7; i++ {
dayStart := carbon.Time2Carbon(tf.clock.Now()).StartOfDay().AddDays(i)
expect = append(expect, api.Rate{
Price: 0.3,
Start: dayStart.Carbon2Time(),
End: dayStart.AddDay().Carbon2Time(),
})
}
rates, err := tf.Rates()
assert.NoError(t, err)
assert.Equal(t, expect, rates)
}