mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-21 04:46:10 +02:00
Merge branch 'main' into development03
This commit is contained in:
commit
0d3ef76b8a
7 changed files with 109 additions and 58 deletions
39
README.md
39
README.md
|
@ -1,28 +1,35 @@
|
||||||
 
|
 
|
||||||
|
|
||||||
|
# 🖐 Ahoy!
|
||||||

|

|
||||||
|
|
||||||
# ahoy
|
**Communicate with Hoymiles inverters via radio**. Get actual values like power, current, daily energy and set parameters like the power limit via web interface or MQTT. In this repository you will find different approaches means Hardware / Software to realize the described functionalities.
|
||||||
Ahoy is a project to bypass the original Hoymiles cloud solution.
|
|
||||||
In order to use this project, it is important what the area of application looks like.
|
|
||||||
With each version it is necessary to have an NRF24L01+.
|
|
||||||
|
|
||||||
Click on the link below you are interested in.
|
List of approaches
|
||||||
There you will find further explanations on how to proceed. (*Note: It is still under construction!*)
|
|
||||||
|
|
||||||
##### Most updated section
|
- [ESP8266/ESP32, C++](tools/esp8266/) 👈 the most effort is spent here
|
||||||
- [ESP8266](tools/esp8266/) that includes an web interface
|
- [Arduino Nano, C++](tools/nano/NRF24_SendRcv/)
|
||||||
|
- [Raspberry Pi, Python](tools/rpi/)
|
||||||
|
- [Others, C/C++](tools/nano/NRF24_SendRcv/)
|
||||||
|
|
||||||
##### will be updated as needed
|
## Quick Start with ESP8266
|
||||||
- [Arduino Nano](tools/nano/NRF24_SendRcv/)
|
- [Go here ✨](https://github.com/grindylow/ahoy/blob/ahoy_v0.5.16/tools/esp8266/README.md#things-needed)
|
||||||
- [Raspberry Pi](tools/rpi/)
|
|
||||||
- [others](tools/nano/NRF24_SendRcv/)
|
|
||||||
|
|
||||||
If errors occur or you have suggestions for ideas, please feel free to contact us [here](https://github.com/grindylow/ahoy/issues).
|
|
||||||
|
|
||||||
## Contact
|
## Success Stories
|
||||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
- [Getting the data into influxDB and visualize them in a Grafana Dashboard](https://grafana.com/grafana/dashboards/16850-pv-power-ahoy/) (thx @Carl)
|
||||||
|
|
||||||
https://discord.gg/WzhxEY62mB
|
## Support, Feedback, Information and Discussion
|
||||||
|
- [Discord Server (~ 300 Users)](https://discord.gg/WzhxEY62mB)
|
||||||
|
- [The root of development](https://www.mikrocontroller.net/topic/525778)
|
||||||
|
|
||||||
|
### Development
|
||||||
|
If you encounter issues use the issues here on github.
|
||||||
|
|
||||||
|
Please try to describe your issues as precise as possible and think about if this is a issue with the software here in the repository or other software components.
|
||||||
|
|
||||||
**Contributors are always welcome!**
|
**Contributors are always welcome!**
|
||||||
|
|
||||||
|
### Related Projects
|
||||||
|
- [OpenDTU](https://github.com/tbnobody/OpenDTU)
|
||||||
|
- [DTU Simulator](https://github.com/Ziyatoe/DTUsimMI1x00-Hoymiles)
|
||||||
|
|
BIN
doc/Wiring_ESP32_Schematic.png
Normal file
BIN
doc/Wiring_ESP32_Schematic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
BIN
doc/Wiring_ESP32_Symbol.png
Normal file
BIN
doc/Wiring_ESP32_Symbol.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
|
@ -76,11 +76,29 @@ Additional, there are 3 pins, which can be set individual:
|
||||||
|
|
||||||
#### ESP8266 wiring example
|
#### ESP8266 wiring example
|
||||||
This is an example wiring using a Wemos D1 mini.<br>
|
This is an example wiring using a Wemos D1 mini.<br>
|
||||||
<img src="https://github.com/grindylow/ahoy/blob/main/doc/AhoyWemos_Steckplatine.jpg" width="300">
|
##### Schematic
|
||||||
<img src="https://github.com/grindylow/ahoy/blob/main/doc/AhoyWemos_Schaltplan.jpg" width="300">
|

|
||||||
|
|
||||||
|
##### Symbolic view
|
||||||
|

|
||||||
|
|
||||||
|
#### ESP32 wiring example
|
||||||
|
Example wiring for a 38pin ESP32 module
|
||||||
|
|
||||||
|
##### Schematic
|
||||||
|

|
||||||
|
|
||||||
|
##### Symbolic view
|
||||||
|

|
||||||
|
|
||||||
|
##### ESP32 GPIO settings
|
||||||
|
For this wiring, set the 3 individual GPIOs under the /setup URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
CS D1 (GPIO5)
|
||||||
|
CE D2 (GPIO4)
|
||||||
|
IRQ D0 (GPIO16 - no IRQ!)
|
||||||
|
```
|
||||||
|
|
||||||
## Flash the Firmware on your Ahoy DTU Hardware
|
## Flash the Firmware on your Ahoy DTU Hardware
|
||||||
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
||||||
|
|
|
@ -30,30 +30,32 @@ ahoy:
|
||||||
|
|
||||||
volkszaehler:
|
volkszaehler:
|
||||||
disabled: true
|
disabled: true
|
||||||
url: 'http://localhost/middleware/'
|
inverters:
|
||||||
channels:
|
- serial: 114172220003
|
||||||
- type: 'temperature'
|
url: 'http://localhost/middleware/'
|
||||||
uid: 'ad578a40-1d97-11ed-8e8b-fda01a416575'
|
channels:
|
||||||
- type: 'frequency'
|
- type: 'temperature'
|
||||||
uid: ''
|
uid: 'ad578a40-1d97-11ed-8e8b-fda01a416575'
|
||||||
- type: 'ac_power0'
|
- type: 'frequency'
|
||||||
uid: '7ca5ac50-1e41-11ed-927f-610c4cb2c69e'
|
uid: ''
|
||||||
- type: 'ac_voltage0'
|
- type: 'ac_power0'
|
||||||
uid: '9a38e2e0-1d94-11ed-b539-25f8607ac030'
|
uid: '7ca5ac50-1e41-11ed-927f-610c4cb2c69e'
|
||||||
- type: 'ac_current0'
|
- type: 'ac_voltage0'
|
||||||
uid: 'a9a4daf0-1e41-11ed-b68c-eb73eef3d21d'
|
uid: '9a38e2e0-1d94-11ed-b539-25f8607ac030'
|
||||||
- type: 'dc_power0'
|
- type: 'ac_current0'
|
||||||
uid: '38eb3ca0-1e53-11ed-b830-792e70a592fa'
|
uid: 'a9a4daf0-1e41-11ed-b68c-eb73eef3d21d'
|
||||||
- type: 'dc_voltage0'
|
- type: 'dc_power0'
|
||||||
uid: ''
|
uid: '38eb3ca0-1e53-11ed-b830-792e70a592fa'
|
||||||
- type: 'dc_current0'
|
- type: 'dc_voltage0'
|
||||||
uid: ''
|
uid: ''
|
||||||
- type: 'dc_power1'
|
- type: 'dc_current0'
|
||||||
uid: '51c0e9d0-1e53-11ed-b574-8bc81547eb8f'
|
uid: ''
|
||||||
- type: 'dc_voltage1'
|
- type: 'dc_power1'
|
||||||
uid: ''
|
uid: '51c0e9d0-1e53-11ed-b574-8bc81547eb8f'
|
||||||
- type: 'dc_current1'
|
- type: 'dc_voltage1'
|
||||||
uid: ''
|
uid: ''
|
||||||
|
- type: 'dc_current1'
|
||||||
|
uid: ''
|
||||||
|
|
||||||
dtu:
|
dtu:
|
||||||
serial: 99978563001
|
serial: 99978563001
|
||||||
|
|
|
@ -647,7 +647,7 @@ class InverterTransaction:
|
||||||
self.time_rx = end_frame.time_rx
|
self.time_rx = end_frame.time_rx
|
||||||
tr_len = end_frame.seq - 0x80
|
tr_len = end_frame.seq - 0x80
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
seq_last = max(frames, key=lambda frame:frame.seq).seq
|
seq_last = max(frames, key=lambda frame:frame.seq).seq if len(frames) else 0
|
||||||
self.__retransmit_frame(seq_last + 1)
|
self.__retransmit_frame(seq_last + 1)
|
||||||
raise BufferError(f'Missing packet: Last packet {len(self.scratch)}')
|
raise BufferError(f'Missing packet: Last packet {len(self.scratch)}')
|
||||||
|
|
||||||
|
|
|
@ -208,14 +208,10 @@ try:
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
class VzInverterOutput:
|
||||||
def __init__(self, config, **params):
|
def __init__(self, config, session):
|
||||||
"""
|
self.session = session
|
||||||
Initialize VolkszaehlerOutputPlugin
|
self.serial = config.get('serial')
|
||||||
"""
|
|
||||||
super().__init__(**params)
|
|
||||||
|
|
||||||
self.session = requests.Session()
|
|
||||||
self.baseurl = config.get('url', 'http://localhost/middleware/')
|
self.baseurl = config.get('url', 'http://localhost/middleware/')
|
||||||
self.channels = dict()
|
self.channels = dict()
|
||||||
for channel in config.get('channels', []):
|
for channel in config.get('channels', []):
|
||||||
|
@ -224,7 +220,7 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
if uid and ctype:
|
if uid and ctype:
|
||||||
self.channels[ctype] = uid
|
self.channels[ctype] = uid
|
||||||
|
|
||||||
def store_status(self, response, **params):
|
def store_status(self, data, session):
|
||||||
"""
|
"""
|
||||||
Publish StatusResponse object
|
Publish StatusResponse object
|
||||||
|
|
||||||
|
@ -232,15 +228,9 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
|
|
||||||
:raises ValueError: when response is not instance of StatusResponse
|
:raises ValueError: when response is not instance of StatusResponse
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(response, StatusResponse):
|
|
||||||
raise ValueError('Data needs to be instance of StatusResponse')
|
|
||||||
|
|
||||||
if len(self.channels) == 0:
|
if len(self.channels) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = response.__dict__()
|
|
||||||
|
|
||||||
ts = int(round(data['time'].timestamp() * 1000))
|
ts = int(round(data['time'].timestamp() * 1000))
|
||||||
|
|
||||||
# AC Data
|
# AC Data
|
||||||
|
@ -277,3 +267,37 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
raise ValueError('Could not send request (%s)' % url)
|
raise ValueError('Could not send request (%s)' % url)
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
raise ValueError('Could not send request (%s)' % e)
|
raise ValueError('Could not send request (%s)' % e)
|
||||||
|
|
||||||
|
class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
|
def __init__(self, config, **params):
|
||||||
|
"""
|
||||||
|
Initialize VolkszaehlerOutputPlugin
|
||||||
|
"""
|
||||||
|
super().__init__(**params)
|
||||||
|
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.inverters = dict()
|
||||||
|
for inverterconfig in config.get('inverters', []):
|
||||||
|
serial = inverterconfig.get('serial')
|
||||||
|
output = VzInverterOutput(inverterconfig, self.session)
|
||||||
|
self.inverters[serial] = output
|
||||||
|
|
||||||
|
def store_status(self, response, **params):
|
||||||
|
"""
|
||||||
|
Publish StatusResponse object
|
||||||
|
|
||||||
|
:param hoymiles.decoders.StatusResponse response: StatusResponse object
|
||||||
|
|
||||||
|
:raises ValueError: when response is not instance of StatusResponse
|
||||||
|
"""
|
||||||
|
if not isinstance(response, StatusResponse):
|
||||||
|
raise ValueError('Data needs to be instance of StatusResponse')
|
||||||
|
|
||||||
|
if len(self.inverters) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = response.__dict__()
|
||||||
|
serial = data["inverter_ser"]
|
||||||
|
if serial in self.inverters:
|
||||||
|
output = self.inverters[serial]
|
||||||
|
output.store_status(data, self.session)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue