Browse Source

Merge branch 'main' into dev

# Conflicts:
#	tools/esp8266/app.cpp
#	tools/esp8266/hmInverters.h
#	tools/esp8266/hmSystem.h

* added missing files
pull/21/head
lumapu 2 years ago
parent
commit
5844795447
  1. 5
      README.md
  2. BIN
      doc/AhoyMiles.fzz
  3. BIN
      doc/AhoyMiles_bb.png
  4. BIN
      doc/AhoyMiles_schem.png
  5. BIN
      doc/HM-400 data.xlsx
  6. 45
      doc/getting-started-ESP8266.md
  7. 43
      doc/hoymiles-format-description.txt
  8. 2
      tools/esp8266/app.cpp
  9. 4
      tools/esp8266/defines.h
  10. 122
      tools/esp8266/hmDefines.h
  11. 143
      tools/esp8266/hmInverter.h

5
README.md

@ -5,7 +5,8 @@ Various tools, examples, and documentation for communicating with Hoymiles micro
In particular:
* `doc\hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
* The `tools` folder contains various software tools for RaspberryPi and Arduino
* `doc/hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
* `doc/getting-started-ESP8266.md` shows the hardware setup for an ESP8266-based system
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32
Contributors are always welcome!

BIN
doc/AhoyMiles.fzz

Binary file not shown.

BIN
doc/AhoyMiles_bb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
doc/AhoyMiles_schem.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
doc/HM-400 data.xlsx

Binary file not shown.

45
doc/getting-started-ESP8266.md

@ -0,0 +1,45 @@
# Getting Started with an ESP8266
Wire Connections
```ditaa
+-----------+ +-----------+
| ESP8266 |--colour--| nRF24L01+ |
| | | |
| GND |---black--|[GND] |
| +3.3V |----red---| VCC |
| D4 |---grey---| CE |
| D8 |--purple--| CSN |
| D5 |---blue---| SCK |
| D7 |---green--| MOSI |
| D6 |---brown--| MISO |
| D3 |--yellow--| IRQ |
+-----------+ +-----------+
```
![plot](./AhoyMiles_bb.png)
Fritzing diagrams & schematics
* [AhoyMiles_bb.png](./AhoyMiles_bb.png)
* [AhoyMiles_schem.png](./AhoyMiles_schem.png)
* [AhoyMiles.fzz](./AhoyMiles.fzz)
Libraries to be installed in Arduino IDE:
* RF24
* TimeLib
Verify & Compile
* Connect to WiFi Network `ESP AHOY`
* Use password `esp_8266`
* Connect to Network settings
Setup
* WiFi
* Enter SSID `mynetwork`
* Enter Password `mypassword`
* Device Host Name
* Enter Device Name `esp-ahoy`
* General
* Hoymiles Address (e.g. 114173123456) `11:41:73:12:34:56`
* [x] Reboot device after successful save
Save

43
doc/hoymiles-format-description.txt

@ -418,14 +418,33 @@ Example 72220200 72220200 ? 2022-02-13
```
CMD 0x01: WR --> DTU: "Current DC data" (?) (shown for an HM-700)
CMD 0x01: WR --> DTU: "Current DC data" (?) (shown for an HM-700 and HM-400)
----------------------------------------------------------------------------------------------------------------------------------------------
HM-700 (2-channel):
95 72 22 02 00 72 22 02 00 01 00 01 01 4c 03 bd 0c 46 00 b5 00 03 00 05 00 00 BD 7F
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^
NameMID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p PV2.u PV2.i PV2.p ? CRC8 EOF
Units BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W] ?
Example 72220200 72220200 ? 33.2V 9.57A 317.2W 18.1V 0.03A 0.5W ?
HM-400 (1-channel):
byte 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
95 73 10 xx yy 73 10 xx yy 01 00 01 01 9A 00 46 01 21 00 00 FA E6 00 84 09 0C F5 DD BD 7F
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^
NameMID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p DC? P total? DC? P day V AC CRC8 EOF
Units BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.001kWh] [1Wh] [0.1V] ?
Example 7310xxyy 7310xxyy ? 41.0V 0.70A 28.9W 64.23kWh 132Wh 231.6V ?
legend
PVx.u: DC voltage of panel x
PVx.i: DC current of panel x
PVx.p: DC power of panel x
WR ser#: inverter serial, e.g. 11217310xxyy (HM-400) => 7310xxyy
P tot: DC (or AC)? power total (monthly/yearly?)
P day: DC (or AC)? power daily
V AC: AC voltage
```
- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed:
- HM-400 (single channel):
@ -456,7 +475,25 @@ Einheit BCD (letzte 8) BCD (letzte 8) ?
Beispiel 72220200 72220200 ? 9284 60 231.9V 50.00Hz 302.9W
```
- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed:
- ...
- until now, message never observed using a HM-400
```
CMD 0x82: WR --> DTU: "???" (?) (shown for an HM-400)
----------------------------------------------------------------------------------------------------------------------------------------------
HM-400 (1-channel):
byte 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
95 73 10 xx yy 73 10 xx yy 82 13 8A 01 1C 00 00 00 0C 03 E8 00 65 00 06 3C 1D 36 9E 8D 1
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^
NameMID WR ser# WR ser# CMD Freq P AC ? I AC ? Temp ? ? ? CRC8? EOF?
Units BCD (letzte 8) BCD (letzte 8) [0.01Hz] [0.1W] ? [0.01A] ? [0.1°C] ? ? ?
Example 7310xxyy 7310xxyy 50,02Hz 28,40W ? 0,120A ? 10,10°C ? ? ?
legend
Freq: frequency of inverter
P AC: AC power of inverter
I AC: AC current of inverter
Temp: temperature of inverter
WR ser#: inverter serial, e.g. 11217310xxyy (HM-400) => 7310xxyy
```
Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?)

2
tools/esp8266/app.cpp

@ -482,7 +482,7 @@ void app::saveValues(bool webSend = true) {
// type
mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20);
uint8_t type = atoi(buf);
mEep->write(ADDR_INV_TYPE + (i * MAX_NAME_LENGTH), type);
mEep->write(ADDR_INV_TYPE + i, type);
}
interval = mWeb->arg("invInterval").toInt();

4
tools/esp8266/defines.h

@ -25,7 +25,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 10
#define VERSION_PATCH 11
//-------------------------------------
@ -39,7 +39,7 @@ typedef struct {
// EEPROM
//-------------------------------------
#define SSID_LEN 32
#define PWD_LEN 32
#define PWD_LEN 63
#define DEVNAME_LEN 16
#define CRC_LEN 2 // uint16_t

122
tools/esp8266/hmDefines.h

@ -0,0 +1,122 @@
#ifndef __HM_DEFINES_H__
#define __HM_DEFINES_H__
#include "debug.h"
#include <cstdint>
// units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT};
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%"};
// field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT};
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct"};
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// CH0 is default channel (freq, ac, temp)
enum {CH0 = 0, CH1, CH2, CH3, CH4};
// received command ids, special command CMDFF for calculations
enum {CMD01 = 0x01, CMD02, CMD03, CMD82 = 0x82, CMD83, CMD84, CMDFF=0xff};
enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400};
const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400"};
#define NUM_INVERTER_TYPES 3
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 3
uint8_t cmdId; // received command id
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor
} byteAssign_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM400 HM350?, HM300?
//-------------------------------------
const byteAssign_t hm400assignment[] = {
{ FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 },
{ FLD_YT, UNIT_KWH, CH1, CMD01, 9, 4, 1000 },
{ FLD_YD, UNIT_WH, CH1, CMD01, 13, 2, 1000 },
{ FLD_UAC, UNIT_V, CH0, CMD01, 15, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, CMD82, 1, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, CMD82, 3, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, CMD82, 7, 2, 100 },
{ FLD_T, UNIT_C, CH0, CMD82, 11, 2, 10 }
};
#define HM400_LIST_LEN (sizeof(hm400assignment) / sizeof(byteAssign_t))
//-------------------------------------
// HM600, HM700
//-------------------------------------
const byteAssign_t hm600assignment[] = {
{ FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 },
{ FLD_UDC, UNIT_V, CH2, CMD01, 9, 2, 10 },
{ FLD_IDC, UNIT_A, CH2, CMD01, 11, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, CMD01, 13, 2, 10 },
{ FLD_YW, UNIT_WH, CH0, CMD02, 1, 2, 1 },
{ FLD_YT, UNIT_KWH, CH0, CMD02, 3, 4, 1000 },
{ FLD_YD, UNIT_WH, CH1, CMD02, 7, 2, 1 },
{ FLD_YD, UNIT_WH, CH2, CMD02, 9, 2, 1 },
{ FLD_UAC, UNIT_V, CH0, CMD02, 11, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, CMD02, 13, 2, 100 },
{ FLD_IAC, UNIT_A, CH0, CMD02, 15, 2, 10 },
{ FLD_T, UNIT_C, CH0, CMD83, 7, 2, 10 }
};
#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t))
//-------------------------------------
// HM1200, HM1500
//-------------------------------------
const byteAssign_t hm1200assignment[] = {
{ FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, CMD01, 9, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, CMD02, 5, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, CMD01, 13, 4, 1000 },
{ FLD_UDC, UNIT_V, CH2, CMD02, 9, 2, 10 },
{ FLD_IDC, UNIT_A, CH2, CMD01, 7, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, CMD01, 11, 2, 10 },
{ FLD_YD, UNIT_WH, CH2, CMD02, 7, 2, 1 },
{ FLD_YT, UNIT_KWH, CH2, CMD02, 1, 4, 1000 },
{ FLD_IDC, UNIT_A, CH3, CMD02, 11, 2, 100 },
{ FLD_PDC, UNIT_W, CH3, CMD02, 15, 2, 10 },
{ FLD_YD, UNIT_WH, CH3, CMD03, 11, 2, 1 },
{ FLD_YT, UNIT_KWH, CH3, CMD03, 3, 4, 1000 },
{ FLD_IDC, UNIT_A, CH4, CMD02, 13, 2, 100 },
{ FLD_PDC, UNIT_W, CH4, CMD03, 1, 2, 10 },
{ FLD_YD, UNIT_WH, CH4, CMD03, 13, 2, 1 },
{ FLD_YT, UNIT_KWH, CH4, CMD03, 7, 4, 1000 },
{ FLD_UAC, UNIT_V, CH0, CMD03, 15, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, CMD84, 7, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, CMD84, 3, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, CMD84, 1, 2, 100 },
{ FLD_PCT, UNIT_PCT, CH0, CMD84, 9, 2, 10 },
{ FLD_T, UNIT_C, CH0, CMD84, 11, 2, 10 }
};
#define HM1200_LIST_LEN (sizeof(hm1200assignment) / sizeof(byteAssign_t))
#endif /*__HM_DEFINES_H__*/

143
tools/esp8266/hmInverter.h

@ -0,0 +1,143 @@
#ifndef __HM_INVERTER_H__
#define __HM_INVERTER_H__
#include "hmDefines.h"
template <class RECORDTYPE=float>
class Inverter {
public:
uint8_t id; // unique id
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
uint8_t type; // integer which refers to inverter type
byteAssign_t* assign; // type of inverter
uint8_t listLen; // length of assignments
serial_u serial; // serial number as on barcode
serial_u radioId; // id converted to modbus
uint8_t channels; // number of PV channels (1-4)
RECORDTYPE *record; // pointer for values
Inverter() {
getAssignment();
toRadioId();
record = new RECORDTYPE[listLen];
memset(record, 0, sizeof(RECORDTYPE) * listLen);
}
~Inverter() {
// TODO: cleanup
}
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) {
uint8_t pos = 0;
for(; pos < listLen; pos++) {
if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId))
break;
}
return (pos >= listLen) ? 0xff : pos;
}
const char *getFieldName(uint8_t pos) {
return fields[assign[pos].fieldId];
}
const char *getUnit(uint8_t pos) {
return units[assign[pos].unitId];
}
uint8_t getChannel(uint8_t pos) {
return assign[pos].ch;
}
uint8_t getCmdId(uint8_t pos) {
return assign[pos].cmdId;
}
void addValue(uint8_t pos, uint8_t buf[]) {
uint8_t ptr = assign[pos].start;
uint8_t end = ptr + assign[pos].num;
uint16_t div = assign[pos].div;
uint32_t val = 0;
do {
val <<= 8;
val |= buf[ptr];
} while(++ptr != end);
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
}
RECORDTYPE getValue(uint8_t pos) {
return record[pos];
}
private:
void toRadioId(void) {
radioId.u64 = 0ULL;
radioId.b[4] = serial.b[0];
radioId.b[3] = serial.b[1];
radioId.b[2] = serial.b[2];
radioId.b[1] = serial.b[3];
radioId.b[0] = 0x01;
}
void getAssignment(void) {
if(INV_TYPE_HM600 == type) {
listLen = (uint8_t)(HM600_LIST_LEN);
assign = (byteAssign_t*)hm600assignment;
channels = 2;
}
else if(INV_TYPE_HM1200 == type) {
listLen = (uint8_t)(HM1200_LIST_LEN);
assign = (byteAssign_t*)hm1200assignment;
channels = 4;
}
else if(INV_TYPE_HM400 == type) {
listLen = (uint8_t)(HM400_LIST_LEN);
assign = (byteAssign_t*)hm400assignment;
channels = 1;
}
else {
listLen = 0;
channels = 0;
assign = NULL;
}
}
};
/**
* To calculate values which are not transmitted by the unit there is a generic
* list of functions which can be linked to the assignment.
* The special command 0xff (CMDFF) must be used.
*/
typedef float (*func_t)(Inverter<> *);
typedef struct {
uint8_t funcId; // unique id
func_t func; // function pointer
} calcFunc_t;
static float calcYieldTotalCh0(Inverter<> *iv) {
if(NULL != iv) {
float yield[iv->channels];
for(uint8_t i = 1; i <= iv->channels; i++) {
uint8_t pos = iv->getPosByChFld(i, FLD_YT);
//yield[i-1] = iv->getValue(iv)
}
}
return 1.0;
}
static float calcYieldDayCh0(Inverter<> *iv) {
return 1.0;
}
enum {CALC_YT_CH0 = 0, CALC_YD_CH0};
const calcFunc_t calcFunctions[] = {
{ CALC_YT_CH0, &calcYieldTotalCh0 },
{ CALC_YD_CH0, &calcYieldDayCh0 }
};
#endif /*__HM_INVERTER_H__*/
Loading…
Cancel
Save