mirror of https://github.com/lumapu/ahoy.git
74 changed files with 7866 additions and 672 deletions
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,47 @@ |
|||||
|
# 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 | |
||||
|
+-----------+ +-----------+ |
||||
|
``` |
||||
|
|
||||
|
 |
||||
|
|
||||
|
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) |
||||
|
* Choose inverter type |
||||
|
* Set individual inverter name |
||||
|
* [x] Reboot device after successful save |
||||
|
Save |
File diff suppressed because it is too large
@ -0,0 +1,158 @@ |
|||||
|
/*
|
||||
|
CircularBuffer - An Arduino circular buffering library for arbitrary types. |
||||
|
|
||||
|
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl |
||||
|
|
||||
|
This library is free software; you can redistribute it and/or |
||||
|
modify it under the terms of the GNU Lesser General Public |
||||
|
License as published by the Free Software Foundation; either |
||||
|
version 2.1 of the License, or (at your option) any later version. |
||||
|
|
||||
|
This library is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
|
Lesser General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU Lesser General Public |
||||
|
License along with this library; if not, write to the Free Software |
||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
||||
|
*/ |
||||
|
|
||||
|
#ifndef CircularBuffer_h |
||||
|
#define CircularBuffer_h |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define DISABLE_IRQ noInterrupts() |
||||
|
#define RESTORE_IRQ interrupts() |
||||
|
#else |
||||
|
#define DISABLE_IRQ \ |
||||
|
uint8_t sreg = SREG; \ |
||||
|
cli(); |
||||
|
|
||||
|
#define RESTORE_IRQ \ |
||||
|
SREG = sreg; |
||||
|
#endif |
||||
|
|
||||
|
template <class T> class CircularBuffer |
||||
|
{ |
||||
|
public: |
||||
|
/** Constructor
|
||||
|
* @param buffer Preallocated buffer of at least size records. |
||||
|
* @param size Number of records available in the buffer. |
||||
|
*/ |
||||
|
CircularBuffer(T* buffer, const uint8_t size ) |
||||
|
: m_size(size), m_buff(buffer) |
||||
|
{ |
||||
|
clear(); |
||||
|
} |
||||
|
|
||||
|
/** Clear all entries in the circular buffer. */ |
||||
|
void clear(void) |
||||
|
{ |
||||
|
m_front = 0; |
||||
|
m_fill = 0; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is empty */ |
||||
|
inline bool empty(void) const |
||||
|
{ |
||||
|
return !m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Return the number of records stored in the buffer */ |
||||
|
inline uint8_t available(void) const |
||||
|
{ |
||||
|
return m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is full */ |
||||
|
inline bool full(void) const |
||||
|
{ |
||||
|
return m_fill == m_size; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on front of the buffer, for writing.
|
||||
|
* After filling the record, it has to be pushed to actually |
||||
|
* add it to the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is full. |
||||
|
*/ |
||||
|
T* getFront(void) const |
||||
|
{ |
||||
|
DISABLE_IRQ; |
||||
|
T* f = NULL; |
||||
|
if (!full()) |
||||
|
f = get(m_front); |
||||
|
RESTORE_IRQ; |
||||
|
return f; |
||||
|
} |
||||
|
|
||||
|
/** Push record to front of the buffer
|
||||
|
* @param record Record to push. If record was aquired previously (using getFront) its |
||||
|
* data will not be copied as it is already present in the buffer. |
||||
|
* @return True, when record was pushed successfully. |
||||
|
*/ |
||||
|
bool pushFront(T* record) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!full()) |
||||
|
{ |
||||
|
T* f = get(m_front); |
||||
|
if (f != record) |
||||
|
*f = *record; |
||||
|
m_front = (m_front+1) % m_size; |
||||
|
m_fill++; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on back of the buffer, for reading.
|
||||
|
* After reading the record, it has to be pop'ed to actually |
||||
|
* remove it from the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is empty. |
||||
|
*/ |
||||
|
T* getBack(void) const |
||||
|
{ |
||||
|
T* b = NULL; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
b = get(back()); |
||||
|
RESTORE_IRQ; |
||||
|
return b; |
||||
|
} |
||||
|
|
||||
|
/** Remove record from back of the buffer.
|
||||
|
* @return True, when record was pop'ed successfully. |
||||
|
*/ |
||||
|
bool popBack(void) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
{ |
||||
|
m_fill--; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
inline T * get(const uint8_t idx) const |
||||
|
{ |
||||
|
return &(m_buff[idx]); |
||||
|
} |
||||
|
inline uint8_t back(void) const |
||||
|
{ |
||||
|
return (m_front - m_fill + m_size) % m_size; |
||||
|
} |
||||
|
|
||||
|
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
|
T* const m_buff; // Ptr to buffer holding all records.
|
||||
|
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
|
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
|
}; |
||||
|
|
||||
|
#endif // CircularBuffer_h
|
@ -0,0 +1,23 @@ |
|||||
|
#ifndef __DEBUG_H |
||||
|
|
||||
|
#define __DEBUG_H |
||||
|
|
||||
|
#ifdef DEBUG |
||||
|
#define DEBUG_OUT Serial |
||||
|
#else |
||||
|
//---
|
||||
|
// disable Serial DEBUG output
|
||||
|
#define DEBUG_OUT DummySerial |
||||
|
static class { |
||||
|
public: |
||||
|
void begin(...) {} |
||||
|
void print(...) {} |
||||
|
void println(...) {} |
||||
|
void flush() {} |
||||
|
bool available() { return false;} |
||||
|
int readBytes(...) { return 0;} |
||||
|
int printf (...) {return 0;} |
||||
|
} DummySerial; |
||||
|
#endif |
||||
|
|
||||
|
#endif |
@ -0,0 +1,38 @@ |
|||||
|
#ifndef __HM1200_H |
||||
|
#define __HM1200_H |
||||
|
|
||||
|
#define HM1200 |
||||
|
|
||||
|
const measureDef_t hm1200_measureDef[] = { |
||||
|
{ IDX_UDC, UNIT_V, CH1, CMD01, 14, BYTES2, DIV10 }, |
||||
|
{ IDX_IDC, UNIT_A, CH1, CMD01, 16, BYTES2, DIV100 }, |
||||
|
{ IDX_PDC, UNIT_W, CH1, CMD01, 20, BYTES2, DIV10 }, |
||||
|
{ IDX_E_TAG, UNIT_WH, CH1, CMD02, 16, BYTES2, DIV1 }, |
||||
|
{ IDX_E_TOTAL, UNIT_KWH, CH1, CMD01, 24, BYTES4, DIV1000 }, |
||||
|
{ IDX_UDC, UNIT_V, CH2, CMD02, 20, BYTES2, DIV10 }, |
||||
|
{ IDX_IDC, UNIT_A, CH2, CMD01, 18, BYTES2, DIV100 }, |
||||
|
{ IDX_PDC, UNIT_W, CH2, CMD01, 22, BYTES2, DIV10 }, |
||||
|
{ IDX_E_TAG, UNIT_WH, CH2, CMD02, 18, BYTES2, DIV1 }, |
||||
|
{ IDX_E_TOTAL, UNIT_KWH, CH2, CMD02, 12, BYTES4, DIV1000 }, |
||||
|
{ IDX_IDC, UNIT_A, CH3, CMD02, 22, BYTES2, DIV100 }, |
||||
|
{ IDX_PDC, UNIT_W, CH3, CMD02, 26, BYTES2, DIV10 }, |
||||
|
{ IDX_E_TAG, UNIT_WH, CH3, CMD03, 22, BYTES2, DIV1 }, |
||||
|
{ IDX_E_TOTAL, UNIT_KWH, CH3, CMD03, 14, BYTES4, DIV1000 }, |
||||
|
{ IDX_IDC, UNIT_A, CH4, CMD02, 24, BYTES2, DIV100 }, |
||||
|
{ IDX_PDC, UNIT_W, CH4, CMD03, 12, BYTES2, DIV10 }, |
||||
|
{ IDX_E_TAG, UNIT_WH, CH4, CMD03, 24, BYTES2, DIV1 }, |
||||
|
{ IDX_E_TOTAL, UNIT_KWH, CH4, CMD03, 18, BYTES4, DIV1000 }, |
||||
|
{ IDX_UAC, UNIT_V, CH0, CMD03, 26, BYTES2, DIV10 }, |
||||
|
{ IDX_IPV, UNIT_A, CH0, CMD84, 18, BYTES2, DIV100 }, |
||||
|
{ IDX_PAC, UNIT_W, CH0, CMD84, 14, BYTES2, DIV10 }, |
||||
|
{ IDX_FREQ, UNIT_HZ, CH0, CMD84, 12, BYTES2, DIV100 }, |
||||
|
{ IDX_PERCNT, UNIT_PCT, CH0, CMD84, 20, BYTES2, DIV10 }, |
||||
|
{ IDX_WR_TEMP, UNIT_C, CH0, CMD84, 22, BYTES2, DIV10 } |
||||
|
}; |
||||
|
|
||||
|
measureCalc_t hm1200_measureCalc[] = {}; |
||||
|
|
||||
|
#define HM1200_MEASURE_LIST_LEN sizeof(hm1200_measureDef)/sizeof(measureDef_t) |
||||
|
#define HM1200_CALCED_LIST_LEN 0 |
||||
|
|
||||
|
#endif |
@ -0,0 +1,37 @@ |
|||||
|
#ifndef __HM600_H |
||||
|
#define __HM600_H |
||||
|
|
||||
|
#define HM600 |
||||
|
#define HM700 |
||||
|
|
||||
|
|
||||
|
float calcEheute (float *measure) { return measure[8] + measure[9]; } |
||||
|
float calcIpv (float *measure) { return (measure[10] != 0 ? measure[12]/measure[10] : 0); } |
||||
|
|
||||
|
const measureDef_t hm600_measureDef[] = { |
||||
|
{ IDX_UDC, CH1, UNIT_V, CMD01, 14, BYTES2, DIV10}, |
||||
|
{ IDX_IDC, CH1, UNIT_A, CMD01, 16, BYTES2, DIV100}, |
||||
|
{ IDX_PDC, CH1, UNIT_W, CMD01, 18, BYTES2, DIV10}, |
||||
|
{ IDX_UDC, CH2, UNIT_V, CMD01, 20, BYTES2, DIV10}, |
||||
|
{ IDX_IDC, CH2, UNIT_A, CMD01, 22, BYTES2, DIV100}, |
||||
|
{ IDX_PDC, CH2, UNIT_W, CMD01, 24, BYTES2, DIV10}, |
||||
|
{ IDX_E_WOCHE,CH0, UNIT_WH, CMD02, 12, BYTES2, DIV1}, |
||||
|
{ IDX_E_TOTAL,CH0, UNIT_WH, CMD02, 14, BYTES4, DIV1}, |
||||
|
{ IDX_E_TAG, CH1, UNIT_WH, CMD02, 18, BYTES2, DIV1}, |
||||
|
{ IDX_E_TAG, CH2, UNIT_WH, CMD02, 20, BYTES2, DIV1}, |
||||
|
{ IDX_UAC, CH0, UNIT_V, CMD02, 22, BYTES2, DIV10}, |
||||
|
{ IDX_FREQ, CH0, UNIT_HZ, CMD02, 24, BYTES2, DIV100}, |
||||
|
{ IDX_PAC, CH0, UNIT_W, CMD02, 26, BYTES2, DIV10}, |
||||
|
{ IDX_WR_TEMP,CH0, UNIT_C, CMD83, 18, BYTES2, DIV10} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
measureCalc_t hm600_measureCalc[] = { |
||||
|
{ IDX_E_HEUTE, UNIT_WH, DIV1, &calcEheute}, |
||||
|
{ IDX_IPV, UNIT_A, DIV100, &calcIpv} |
||||
|
}; |
||||
|
|
||||
|
#define HM600_MEASURE_LIST_LEN sizeof(hm600_measureDef)/sizeof(measureDef_t) |
||||
|
#define HM600_CALCED_LIST_LEN sizeof(hm600_measureCalc)/sizeof(measureCalc_t) |
||||
|
|
||||
|
#endif |
@ -0,0 +1,605 @@ |
|||||
|
#include <Arduino.h> |
||||
|
#include <SPI.h> |
||||
|
#include "CircularBuffer.h" |
||||
|
#include <RF24.h> |
||||
|
#include "printf.h" |
||||
|
#include <RF24_config.h> |
||||
|
#include "hm_crc.h" |
||||
|
#include "hm_packets.h" |
||||
|
|
||||
|
#include "Settings.h" // Header für Einstellungen |
||||
|
#include "Debug.h" |
||||
|
#include "Inverters.h" |
||||
|
|
||||
|
const char VERSION[] PROGMEM = "0.1.6"; |
||||
|
|
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define DISABLE_EINT noInterrupts() |
||||
|
#define ENABLE_EINT interrupts() |
||||
|
#else // für AVR z.B. ProMini oder Nano
|
||||
|
#define DISABLE_EINT EIMSK = 0x00 |
||||
|
#define ENABLE_EINT EIMSK = 0x01 |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define PACKET_BUFFER_SIZE (30) |
||||
|
#else |
||||
|
#define PACKET_BUFFER_SIZE (20) |
||||
|
#endif |
||||
|
|
||||
|
// Startup defaults until user reconfigures it
|
||||
|
//#define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles
|
||||
|
//#define DEFAULT_SEND_CHANNEL (75) // 40 = Default channel for Hoymiles, 61
|
||||
|
|
||||
|
static HM_Packets hmPackets; |
||||
|
static uint32_t tickMillis; |
||||
|
|
||||
|
// Set up nRF24L01 radio on SPI bus plus CE/CS pins
|
||||
|
// If more than one RF24 unit is used the another CS pin than 10 must be used
|
||||
|
// This pin is used hard coded in SPI library
|
||||
|
static RF24 Radio (RF1_CE_PIN, RF1_CS_PIN); |
||||
|
|
||||
|
static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE]; |
||||
|
|
||||
|
static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0])); |
||||
|
|
||||
|
static Serial_header_t SerialHdr; |
||||
|
|
||||
|
#define CHECKCRC 1 |
||||
|
static uint16_t lastCRC; |
||||
|
static uint16_t crc; |
||||
|
|
||||
|
uint8_t channels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
|
uint8_t channelIdx = 2; // fange mit 40 an
|
||||
|
uint8_t DEFAULT_SEND_CHANNEL = channels[channelIdx]; // = 40
|
||||
|
|
||||
|
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
||||
|
uint8_t rcvChannelIdx = 0; |
||||
|
uint8_t rcvChannels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
|
uint8_t DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx]; //3;
|
||||
|
uint8_t intvl = 4; // Zeit für poor man hopping
|
||||
|
int hophop; |
||||
|
#else |
||||
|
uint8_t DEFAULT_RECV_CHANNEL = 3; |
||||
|
#endif |
||||
|
|
||||
|
boolean valueChanged = false; |
||||
|
|
||||
|
static unsigned long timeLastPacket = millis(); |
||||
|
static unsigned long timeLastIstTagCheck = millis(); |
||||
|
static unsigned long timeLastRcvChannelSwitch = millis(); |
||||
|
|
||||
|
// Function forward declaration
|
||||
|
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len); |
||||
|
|
||||
|
|
||||
|
static const char BLANK = ' '; |
||||
|
|
||||
|
static boolean istTag = true; |
||||
|
|
||||
|
char CHANNELNAME_BUFFER[15]; |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#include "wifi.h" |
||||
|
#include "ModWebserver.h" |
||||
|
#include "Sonne.h" |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
inline static void dumpData(uint8_t *p, int len) { |
||||
|
//-----------------------------------------------
|
||||
|
while (len > 0){ |
||||
|
if (*p < 16) |
||||
|
DEBUG_OUT.print(F("0")); |
||||
|
DEBUG_OUT.print(*p++, HEX); |
||||
|
len--; |
||||
|
} |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
float extractValue2 (uint8_t *p, int divisor) { |
||||
|
//-------------------------------------------
|
||||
|
uint16_t b1 = *p++; |
||||
|
return ((float) (b1 << 8) + *p) / (float) divisor; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
float extractValue4 (uint8_t *p, int divisor) { |
||||
|
//-------------------------------------------
|
||||
|
uint32_t ret = *p++; |
||||
|
for (uint8_t i = 1; i <= 3; i++) |
||||
|
ret = (ret << 8) + *p++; |
||||
|
return (ret / divisor); |
||||
|
} |
||||
|
|
||||
|
void outChannel (uint8_t wr, uint8_t i) { |
||||
|
//------------------------------------
|
||||
|
DEBUG_OUT.print(getMeasureName(wr, i)); |
||||
|
DEBUG_OUT.print(F("\t:")); |
||||
|
DEBUG_OUT.print(getMeasureValue(wr,i)); |
||||
|
DEBUG_OUT.println(BLANK); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void analyseWords (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
DEBUG_OUT.print (F("analyse words:")); |
||||
|
p++; |
||||
|
for (int i = 0; i <12;i++) { |
||||
|
DEBUG_OUT.print(extractValue2(p,1)); |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
p++; |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
void analyseLongs (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
DEBUG_OUT.print (F("analyse longs:")); |
||||
|
p++; |
||||
|
for (int i = 0; i <12;i++) { |
||||
|
DEBUG_OUT.print(extractValue4(p,1)); |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
p++; |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void analyse (NRF24_packet_t *p) { |
||||
|
//------------------------------
|
||||
|
uint8_t wrIdx = findInverter (&p->packet[3]); |
||||
|
//DEBUG_OUT.print ("wrIdx="); DEBUG_OUT.println (wrIdx);
|
||||
|
if (wrIdx == 0xFF) return; |
||||
|
uint8_t cmd = p->packet[11]; |
||||
|
float val = 0; |
||||
|
if (cmd == 0x01 || cmd == 0x02 || cmd == 0x83) { |
||||
|
const measureDef_t *defs = inverters[wrIdx].measureDef; |
||||
|
|
||||
|
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasures; i++) { |
||||
|
if (defs[i].teleId == cmd) { |
||||
|
uint8_t pos = defs[i].pos; |
||||
|
if (defs[i].bytes == 2) |
||||
|
val = extractValue2 (&p->packet[pos], getDivisor(wrIdx, i) ); |
||||
|
else if (defs[i].bytes == 4) |
||||
|
val = extractValue4 (&p->packet[pos], getDivisor(wrIdx, i) ); |
||||
|
valueChanged = valueChanged ||(val != inverters[wrIdx].values[i]); |
||||
|
inverters[wrIdx].values[i] = val; |
||||
|
} |
||||
|
} |
||||
|
// calculated funstions
|
||||
|
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasureCalculated; i++) { |
||||
|
val = inverters[wrIdx].measureCalculated[i].f (inverters[wrIdx].values); |
||||
|
int idx = inverters[wrIdx].anzMeasures + i; |
||||
|
valueChanged = valueChanged ||(val != inverters[wrIdx].values[idx]); |
||||
|
inverters[wrIdx].values[idx] = val; |
||||
|
} |
||||
|
} |
||||
|
else if (cmd == 0x81) { |
||||
|
; |
||||
|
} |
||||
|
else { |
||||
|
DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX); |
||||
|
analyseWords (&p->packet[11]); |
||||
|
analyseLongs (&p->packet[11]); |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
if (p->packetsLost > 0) { |
||||
|
DEBUG_OUT.print(F(" Lost: ")); |
||||
|
DEBUG_OUT.println(p->packetsLost); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
IRAM_ATTR |
||||
|
#endif |
||||
|
void handleNrf1Irq() { |
||||
|
//-------------------------
|
||||
|
static uint8_t lostPacketCount = 0; |
||||
|
uint8_t pipe; |
||||
|
|
||||
|
DISABLE_EINT; |
||||
|
|
||||
|
// Loop until RX buffer(s) contain no more packets.
|
||||
|
while (Radio.available(&pipe)) { |
||||
|
if (!packetBuffer.full()) { |
||||
|
NRF24_packet_t *p = packetBuffer.getFront(); |
||||
|
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
||||
|
p->packetsLost = lostPacketCount; |
||||
|
p->rcvChannel = DEFAULT_RECV_CHANNEL; |
||||
|
uint8_t packetLen = Radio.getPayloadSize(); |
||||
|
if (packetLen > MAX_RF_PAYLOAD_SIZE) |
||||
|
packetLen = MAX_RF_PAYLOAD_SIZE; |
||||
|
|
||||
|
Radio.read(p->packet, packetLen); |
||||
|
packetBuffer.pushFront(p); |
||||
|
lostPacketCount = 0; |
||||
|
} |
||||
|
else { |
||||
|
// Buffer full. Increase lost packet counter.
|
||||
|
bool tx_ok, tx_fail, rx_ready; |
||||
|
if (lostPacketCount < 255) |
||||
|
lostPacketCount++; |
||||
|
// Call 'whatHappened' to reset interrupt status.
|
||||
|
Radio.whatHappened(tx_ok, tx_fail, rx_ready); |
||||
|
// Flush buffer to drop the packet.
|
||||
|
Radio.flush_rx(); |
||||
|
} |
||||
|
} |
||||
|
ENABLE_EINT; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static void activateConf(void) { |
||||
|
//-----------------------------
|
||||
|
Radio.begin(); |
||||
|
// Disable shockburst for receiving and decode payload manually
|
||||
|
Radio.setAutoAck(false); |
||||
|
Radio.setRetries(0, 0); |
||||
|
Radio.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
Radio.setDataRate(DEFAULT_RF_DATARATE); |
||||
|
Radio.disableCRC(); |
||||
|
Radio.setAutoAck(0x00); |
||||
|
Radio.setPayloadSize(MAX_RF_PAYLOAD_SIZE); |
||||
|
Radio.setAddressWidth(5); |
||||
|
Radio.openReadingPipe(1, DTU_RADIO_ID); |
||||
|
|
||||
|
// We want only RX irqs
|
||||
|
Radio.maskIRQ(true, true, false); |
||||
|
|
||||
|
// Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
|
||||
|
Radio.setPALevel(RF24_PA_MAX); |
||||
|
Radio.startListening(); |
||||
|
|
||||
|
// Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
|
||||
|
attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
|
||||
|
|
||||
|
// Initialize SerialHdr header's address member to promiscuous address.
|
||||
|
uint64_t addr = DTU_RADIO_ID; |
||||
|
for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) { |
||||
|
SerialHdr.address[i] = addr; |
||||
|
addr >>= 8; |
||||
|
} |
||||
|
|
||||
|
//Radio.printDetails();
|
||||
|
//DEBUG_OUT.println();
|
||||
|
tickMillis = millis() + 200; |
||||
|
} |
||||
|
|
||||
|
#define resetRF24() activateConf() |
||||
|
|
||||
|
|
||||
|
void setup(void) { |
||||
|
//--------------
|
||||
|
#ifndef DEBUG |
||||
|
#ifndef ESP8266 |
||||
|
Serial.begin(SER_BAUDRATE); |
||||
|
#endif |
||||
|
#endif |
||||
|
printf_begin(); |
||||
|
DEBUG_OUT.begin(SER_BAUDRATE); |
||||
|
DEBUG_OUT.flush(); |
||||
|
|
||||
|
DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --")); |
||||
|
|
||||
|
// Configure nRF IRQ input
|
||||
|
pinMode(RF1_IRQ_PIN, INPUT); |
||||
|
|
||||
|
activateConf(); |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
setupWifi(); |
||||
|
setupClock(); |
||||
|
setupWebServer(); |
||||
|
setupUpdateByOTA(); |
||||
|
calcSunUpDown (getNow()); |
||||
|
istTag = isDayTime(); |
||||
|
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); |
||||
|
hmPackets.SetUnixTimeStamp (getNow()); |
||||
|
#else |
||||
|
hmPackets.SetUnixTimeStamp(0x62456430); |
||||
|
#endif |
||||
|
|
||||
|
setupInverts(); |
||||
|
} |
||||
|
|
||||
|
uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE]; |
||||
|
|
||||
|
void isTime2Send () { |
||||
|
//-----------------
|
||||
|
// Second timer
|
||||
|
static const uint8_t warteZeit = 1; |
||||
|
static uint8_t tickSec = 0; |
||||
|
if (millis() >= tickMillis) { |
||||
|
static uint8_t tel = 0; |
||||
|
tickMillis += warteZeit*1000; //200;
|
||||
|
tickSec++; |
||||
|
|
||||
|
if (++tickSec >= 1) { // 5
|
||||
|
for (uint8_t c=0; c < warteZeit; c++) hmPackets.UnixTimeStampTick(); |
||||
|
tickSec = 0; |
||||
|
} |
||||
|
|
||||
|
int32_t size = 0; |
||||
|
uint64_t dest = 0; |
||||
|
for (uint8_t wr = 0; wr < anzInv; wr++) { |
||||
|
dest = inverters[wr].RadioId; |
||||
|
|
||||
|
if (tel > 1) |
||||
|
tel = 0; |
||||
|
|
||||
|
if (tel == 0) { |
||||
|
#ifdef ESP8266 |
||||
|
hmPackets.SetUnixTimeStamp (getNow()); |
||||
|
#endif |
||||
|
size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8); |
||||
|
//DEBUG_OUT.print ("Timepacket mit cid="); DEBUG_OUT.println(sendBuf[10], HEX);
|
||||
|
} |
||||
|
else if (tel <= 1) |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80 + tel - 1); |
||||
|
|
||||
|
SendPacket (dest, (uint8_t *)&sendBuf, size); |
||||
|
} // for wr
|
||||
|
|
||||
|
tel++; |
||||
|
|
||||
|
/* for (uint8_t warte = 0; warte < 2; warte++) {
|
||||
|
delay(1000); |
||||
|
hmPackets.UnixTimeStampTick(); |
||||
|
}*/ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) { |
||||
|
//-----------------------------------------------------
|
||||
|
|
||||
|
// Write timestamp, packets lost, address and payload length
|
||||
|
//printf(" %09lu ", SerialHdr.timestamp);
|
||||
|
char _buf[20]; |
||||
|
sprintf_P(_buf, PSTR("rcv CH:%d "), p->rcvChannel); |
||||
|
DEBUG_OUT.print (_buf); |
||||
|
dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost)); |
||||
|
dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address)); |
||||
|
|
||||
|
// Trailing bit?!?
|
||||
|
dumpData(&p->packet[0], 2); |
||||
|
|
||||
|
// Payload length from PCF
|
||||
|
dumpData(&payloadLen, sizeof(payloadLen)); |
||||
|
|
||||
|
// Packet control field - PID Packet identification
|
||||
|
uint8_t val = (p->packet[1] >> 1) & 0x03; |
||||
|
DEBUG_OUT.print(val); |
||||
|
DEBUG_OUT.print(F(" ")); |
||||
|
|
||||
|
if (payloadLen > 9) { |
||||
|
dumpData(&p->packet[2], 1); |
||||
|
dumpData(&p->packet[3], 4); |
||||
|
dumpData(&p->packet[7], 4); |
||||
|
|
||||
|
uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4; |
||||
|
|
||||
|
if (remain < 32) { |
||||
|
dumpData(&p->packet[11], remain); |
||||
|
printf_P(PSTR("%04X "), crc); |
||||
|
|
||||
|
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) |
||||
|
DEBUG_OUT.print(0); |
||||
|
else |
||||
|
DEBUG_OUT.print(1); |
||||
|
} |
||||
|
else { |
||||
|
DEBUG_OUT.print(F("Ill remain ")); |
||||
|
DEBUG_OUT.print(remain); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
dumpData(&p->packet[2], payloadLen + 2); |
||||
|
printf_P(PSTR("%04X "), crc); |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
DEBUG_OUT.flush(); |
||||
|
} |
||||
|
|
||||
|
void writeArduinoInterface() { |
||||
|
//--------------------------
|
||||
|
if (valueChanged) { |
||||
|
for (uint8_t wr = 0; wr < anzInv; wr++) { |
||||
|
if (anzInv > 1) { |
||||
|
Serial.print(wr); Serial.print('.'); |
||||
|
} |
||||
|
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
||||
|
Serial.print(getMeasureName(wr,i)); // Schnittstelle bei Arduino
|
||||
|
Serial.print('='); |
||||
|
Serial.print(getMeasureValue(wr,i), getDigits(wr,i)); // Schnittstelle bei Arduino
|
||||
|
Serial.print (BLANK); |
||||
|
Serial.println (getUnit(wr, i)); |
||||
|
} // for i
|
||||
|
|
||||
|
} // for wr
|
||||
|
Serial.println(F("-----------------------")); |
||||
|
valueChanged = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
boolean doCheckCrc (NRF24_packet_t *p, uint8_t payloadLen) { |
||||
|
//--------------------------------------------------------
|
||||
|
crc = 0xFFFF; |
||||
|
crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address))); |
||||
|
// Payload length
|
||||
|
// Add one byte and one bit for 9-bit packet control field
|
||||
|
crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1); |
||||
|
|
||||
|
if (CHECKCRC) { |
||||
|
// If CRC is invalid only show lost packets
|
||||
|
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) { |
||||
|
if (p->packetsLost > 0) { |
||||
|
DEBUG_OUT.print(F(" Lost: ")); |
||||
|
DEBUG_OUT.println(p->packetsLost); |
||||
|
} |
||||
|
packetBuffer.popBack(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Dump a decoded packet only once
|
||||
|
if (lastCRC == crc) { |
||||
|
packetBuffer.popBack(); |
||||
|
return false; |
||||
|
} |
||||
|
lastCRC = crc; |
||||
|
} |
||||
|
|
||||
|
// Don't dump mysterious ack packages
|
||||
|
if (payloadLen == 0) { |
||||
|
packetBuffer.popBack(); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void poorManChannelHopping() { |
||||
|
//--------------------------
|
||||
|
if (hophop <= 0) return; |
||||
|
if (millis() >= timeLastRcvChannelSwitch + intvl) { |
||||
|
rcvChannelIdx++; |
||||
|
if (rcvChannelIdx >= sizeof(rcvChannels)) |
||||
|
rcvChannelIdx = 0; |
||||
|
DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx]; |
||||
|
DISABLE_EINT; |
||||
|
Radio.stopListening(); |
||||
|
Radio.setChannel (DEFAULT_RECV_CHANNEL); |
||||
|
Radio.startListening(); |
||||
|
ENABLE_EINT; |
||||
|
timeLastRcvChannelSwitch = millis(); |
||||
|
hophop--; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
void loop(void) { |
||||
|
//=============
|
||||
|
// poor man channel hopping on receive
|
||||
|
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
||||
|
poorManChannelHopping(); |
||||
|
#endif |
||||
|
|
||||
|
if (millis() > timeLastPacket + 50000UL) { |
||||
|
DEBUG_OUT.println (F("Reset RF24")); |
||||
|
resetRF24(); |
||||
|
timeLastPacket = millis(); |
||||
|
} |
||||
|
|
||||
|
while (!packetBuffer.empty()) { |
||||
|
timeLastPacket = millis(); |
||||
|
// One or more records present
|
||||
|
NRF24_packet_t *p = packetBuffer.getBack(); |
||||
|
|
||||
|
// Shift payload data due to 9-bit packet control field
|
||||
|
for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) { |
||||
|
if (j > 0) |
||||
|
p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1); |
||||
|
else |
||||
|
p->packet[j] = (byte)(p->packet[j] >> 7); |
||||
|
} |
||||
|
|
||||
|
SerialHdr.timestamp = p->timestamp; |
||||
|
SerialHdr.packetsLost = p->packetsLost; |
||||
|
|
||||
|
uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3); |
||||
|
// Check CRC
|
||||
|
if (! doCheckCrc(p, payloadLen) ) |
||||
|
continue; |
||||
|
|
||||
|
#ifdef DEBUG |
||||
|
uint8_t cmd = p->packet[11]; |
||||
|
//if (cmd != 0x01 && cmd != 0x02 && cmd != 0x83 && cmd != 0x81)
|
||||
|
outputPacket (p, payloadLen); |
||||
|
#endif |
||||
|
|
||||
|
analyse (p); |
||||
|
|
||||
|
#ifndef ESP8266 |
||||
|
writeArduinoInterface(); |
||||
|
#endif |
||||
|
|
||||
|
// Remove record as we're done with it.
|
||||
|
packetBuffer.popBack(); |
||||
|
} |
||||
|
|
||||
|
if (istTag) |
||||
|
isTime2Send(); |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
checkWifi(); |
||||
|
webserverHandle(); |
||||
|
checkUpdateByOTA(); |
||||
|
if (hour() == 0 && minute() == 0) { |
||||
|
calcSunUpDown(getNow()); |
||||
|
delay (60*1000); |
||||
|
} |
||||
|
|
||||
|
if (millis() > timeLastIstTagCheck + 15UL * 60UL * 1000UL) { // alle 15 Minuten neu berechnen ob noch hell
|
||||
|
istTag = isDayTime(); |
||||
|
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); |
||||
|
timeLastIstTagCheck = millis(); |
||||
|
} |
||||
|
#endif |
||||
|
/*
|
||||
|
if (millis() > timeLastPacket + 60UL*SECOND) { // 60 Sekunden
|
||||
|
channelIdx++; |
||||
|
if (channelIdx >= sizeof(channels)) channelIdx = 0; |
||||
|
DEFAULT_SEND_CHANNEL = channels[channelIdx]; |
||||
|
DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL); |
||||
|
timeLastPacket = millis(); |
||||
|
} |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) { |
||||
|
//--------------------------------------------------------------
|
||||
|
//DEBUG_OUT.print (F("Sende: ")); DEBUG_OUT.println (buf[9], HEX);
|
||||
|
//dumpData (buf, len); DEBUG_OUT.println();
|
||||
|
DISABLE_EINT; |
||||
|
Radio.stopListening(); |
||||
|
|
||||
|
#ifdef CHANNEL_HOP |
||||
|
static uint8_t hop = 0; |
||||
|
#if DEBUG_SEND |
||||
|
DEBUG_OUT.print(F("Send... CH")); |
||||
|
DEBUG_OUT.println(channels[hop]); |
||||
|
#endif |
||||
|
Radio.setChannel(channels[hop++]); |
||||
|
if (hop >= sizeof(channels) / sizeof(channels[0])) |
||||
|
hop = 0; |
||||
|
#else |
||||
|
Radio.setChannel(DEFAULT_SEND_CHANNEL); |
||||
|
#endif |
||||
|
|
||||
|
Radio.openWritingPipe(dest); |
||||
|
Radio.setCRCLength(RF24_CRC_16); |
||||
|
Radio.enableDynamicPayloads(); |
||||
|
Radio.setAutoAck(true); |
||||
|
Radio.setRetries(3, 15); |
||||
|
|
||||
|
bool res = Radio.write(buf, len); |
||||
|
// Try to avoid zero payload acks (has no effect)
|
||||
|
Radio.openWritingPipe(DUMMY_RADIO_ID); |
||||
|
|
||||
|
Radio.setAutoAck(false); |
||||
|
Radio.setRetries(0, 0); |
||||
|
Radio.disableDynamicPayloads(); |
||||
|
Radio.setCRCLength(RF24_CRC_DISABLED); |
||||
|
|
||||
|
Radio.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
Radio.startListening(); |
||||
|
ENABLE_EINT; |
||||
|
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
||||
|
hophop = 5 * sizeof(rcvChannels); |
||||
|
#endif |
||||
|
} |
@ -0,0 +1,283 @@ |
|||||
|
#ifndef __INVERTERS_H |
||||
|
#define __INVERTERS_H |
||||
|
|
||||
|
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include "Debug.h" |
||||
|
|
||||
|
|
||||
|
typedef struct _NRF24_packet_t { |
||||
|
uint32_t timestamp; |
||||
|
uint8_t packetsLost; |
||||
|
uint8_t rcvChannel; |
||||
|
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; |
||||
|
} NRF24_packet_t; |
||||
|
|
||||
|
|
||||
|
typedef struct _Serial_header_t { |
||||
|
unsigned long timestamp; |
||||
|
uint8_t packetsLost; |
||||
|
uint8_t address[RF_MAX_ADDR_WIDTH]; // MSB first, always RF_MAX_ADDR_WIDTH bytes.
|
||||
|
} Serial_header_t; |
||||
|
|
||||
|
|
||||
|
// structs für Inverter und Kanalwerte
|
||||
|
|
||||
|
// Liste der Einheiten
|
||||
|
enum UNITS {UNIT_V = 0, UNIT_HZ, UNIT_A, UNIT_W, UNIT_WH, UNIT_C, UNIT_KWH, UNIT_MA, UNIT_PCT}; |
||||
|
const char* const units[] = {"V", "Hz", "A", "W", "Wh", "°C", "KWh", "mA", "%"}; |
||||
|
|
||||
|
// CH0 is default channel (freq, ac, temp)
|
||||
|
enum CHANNELS {CH0 = 0, CH1, CH2, CH3, CH4}; |
||||
|
enum CMDS {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84}; |
||||
|
enum DIVS {DIV1 = 0, DIV10, DIV100, DIV1000}; |
||||
|
|
||||
|
#define BYTES2 2 |
||||
|
#define BYTES4 4 |
||||
|
|
||||
|
const char UDC[] PROGMEM = "Udc"; |
||||
|
const char IDC[] PROGMEM = "Idc"; |
||||
|
const char PDC[] PROGMEM = "Pdc"; |
||||
|
const char E_WOCHE[] PROGMEM = "E-Woche"; |
||||
|
const char E_TOTAL[] PROGMEM = "E-Total"; |
||||
|
const char E_TAG[] PROGMEM = "E-Tag"; |
||||
|
const char UAC[] PROGMEM = "Uac"; |
||||
|
const char FREQ[] PROGMEM = "Freq.ac"; |
||||
|
const char PAC[] PROGMEM = "Pac"; |
||||
|
const char E_HEUTE[] PROGMEM = "E-heute"; |
||||
|
const char IPV[] PROGMEM = "Ipv"; |
||||
|
const char WR_TEMP[] PROGMEM = "WR-Temp"; |
||||
|
const char PERCNT[] PROGMEM = "Pct"; |
||||
|
|
||||
|
#define IDX_UDC 0 |
||||
|
#define IDX_IDC 1 |
||||
|
#define IDX_PDC 2 |
||||
|
#define IDX_E_WOCHE 3 |
||||
|
#define IDX_E_TOTAL 4 |
||||
|
#define IDX_E_TAG 5 |
||||
|
#define IDX_UAC 6 |
||||
|
#define IDX_FREQ 7 |
||||
|
#define IDX_PAC 8 |
||||
|
#define IDX_E_HEUTE 9 |
||||
|
#define IDX_IPV 10 |
||||
|
#define IDX_WR_TEMP 11 |
||||
|
#define IDX_PERCNT 12 |
||||
|
|
||||
|
const char* const NAMES[] |
||||
|
= {UDC, IDC, PDC, E_WOCHE, E_TOTAL, E_TAG, UAC, FREQ, PAC, E_HEUTE, IPV, WR_TEMP, PERCNT}; |
||||
|
|
||||
|
typedef float (*calcValueFunc)(float *); |
||||
|
|
||||
|
struct measureDef_t { |
||||
|
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
||||
|
uint8_t channel; // 0..4,
|
||||
|
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
||||
|
uint8_t teleId; // Telegramm ID, das was hinter der 2. WR Nummer im Telegramm, 02, 03, 83
|
||||
|
uint8_t pos; // ab dieser POsition beginnt der Wert (Big Endian)
|
||||
|
uint8_t bytes; // Anzahl der Bytes
|
||||
|
uint8_t digits; |
||||
|
}; |
||||
|
|
||||
|
struct measureCalc_t { |
||||
|
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
||||
|
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
||||
|
uint8_t digits; |
||||
|
calcValueFunc f; // die Funktion zur Berechnung von Werten, zb Summe von Werten
|
||||
|
}; |
||||
|
|
||||
|
|
||||
|
struct inverter_t { |
||||
|
uint8_t ID; // Inverter-ID = Index
|
||||
|
char name[20]; // Name des Inverters zb HM-600.1
|
||||
|
uint64_t serialNo; // dier Seriennummer wie im Barcode auf dem WR, also 1141.....
|
||||
|
uint64_t RadioId; // die gespiegelte (letzte 4 "Bytes") der Seriennummer
|
||||
|
const measureDef_t *measureDef; // aus Include HMxxx.h : Liste mit Definitionen der Messwerte, wie Telgramm, offset, länge, ...
|
||||
|
uint8_t anzMeasures; // Länge der Liste
|
||||
|
measureCalc_t *measureCalculated; // Liste mit Defintion für berechnete Werte
|
||||
|
uint8_t anzMeasureCalculated; // Länge der Liste
|
||||
|
uint8_t anzTotalMeasures; // Gesamtanzahl Messwerte
|
||||
|
float values[MAX_MEASURE_PER_INV]; // DIE Messewerte
|
||||
|
}; |
||||
|
|
||||
|
|
||||
|
char _buffer[20]; |
||||
|
|
||||
|
uint8_t anzInv = 0; |
||||
|
inverter_t inverters[MAX_ANZ_INV]; |
||||
|
|
||||
|
union longlongasbytes { |
||||
|
uint64_t ull; |
||||
|
uint32_t ul[2]; |
||||
|
uint8_t bytes[8]; |
||||
|
}; |
||||
|
|
||||
|
char *uint64toa (uint64_t s) { |
||||
|
//--------------------------------
|
||||
|
//0x1141 72607952ULL
|
||||
|
sprintf(_buffer, "%lX%08lX", (unsigned long)(s>>32), (unsigned long)(s&0xFFFFFFFFULL)); |
||||
|
return _buffer; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
uint64_t Serial2RadioID (uint64_t sn) { |
||||
|
//----------------------------------
|
||||
|
longlongasbytes llsn; |
||||
|
longlongasbytes res; |
||||
|
llsn.ull = sn; |
||||
|
res.ull = 0; |
||||
|
res.bytes[4] = llsn.bytes[0]; |
||||
|
res.bytes[3] = llsn.bytes[1]; |
||||
|
res.bytes[2] = llsn.bytes[2]; |
||||
|
res.bytes[1] = llsn.bytes[3]; |
||||
|
res.bytes[0] = 0x01; |
||||
|
return res.ull; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void addInverter (uint8_t _ID, const char * _name, uint64_t _serial, |
||||
|
const measureDef_t * liste, int anzMeasure, |
||||
|
measureCalc_t * calcs, int anzMeasureCalculated) { |
||||
|
//-------------------------------------------------------------------------------------
|
||||
|
if (anzInv >= MAX_ANZ_INV) { |
||||
|
DEBUG_OUT.println(F("ANZ_INV zu klein!")); |
||||
|
return; |
||||
|
} |
||||
|
inverter_t *p = &(inverters[anzInv]); |
||||
|
p->ID = _ID; |
||||
|
strcpy (p->name, _name); |
||||
|
p->serialNo = _serial; |
||||
|
p->RadioId = Serial2RadioID(_serial); |
||||
|
p->measureDef = liste; |
||||
|
p->anzMeasures = anzMeasure; |
||||
|
p->anzMeasureCalculated = anzMeasureCalculated; |
||||
|
p->measureCalculated = calcs; |
||||
|
p->anzTotalMeasures = anzMeasure + anzMeasureCalculated; |
||||
|
memset (p->values, 0, sizeof(p->values)); |
||||
|
|
||||
|
DEBUG_OUT.print (F("WR : ")); DEBUG_OUT.println(anzInv); |
||||
|
DEBUG_OUT.print (F("Type : ")); DEBUG_OUT.println(_name); |
||||
|
DEBUG_OUT.print (F("Serial : ")); DEBUG_OUT.println(uint64toa(_serial)); |
||||
|
DEBUG_OUT.print (F("Radio-ID : ")); DEBUG_OUT.println(uint64toa(p->RadioId)); |
||||
|
|
||||
|
anzInv++; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static uint8_t toggle = 0; // nur für Test, ob's auch für mehere WR funzt
|
||||
|
uint8_t findInverter (uint8_t *fourbytes) { |
||||
|
//---------------------------------------
|
||||
|
for (uint8_t i = 0; i < anzInv; i++) { |
||||
|
longlongasbytes llb; |
||||
|
llb.ull = inverters[i].serialNo; |
||||
|
if (llb.bytes[3] == fourbytes[0] && |
||||
|
llb.bytes[2] == fourbytes[1] && |
||||
|
llb.bytes[1] == fourbytes[2] && |
||||
|
llb.bytes[0] == fourbytes[3] ) |
||||
|
{ |
||||
|
return i; |
||||
|
//if (toggle) toggle = 0; else toggle = 1; return toggle; // Test ob mehr WR auch geht
|
||||
|
} |
||||
|
} |
||||
|
return 0xFF; // nicht gefunden
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
char * error = {"error"}; |
||||
|
|
||||
|
char *getMeasureName (uint8_t wr, uint8_t i){ |
||||
|
//------------------------------------------
|
||||
|
inverter_t *p = &(inverters[wr]); |
||||
|
if (i >= p->anzTotalMeasures) return error; |
||||
|
uint8_t idx, channel = 0; |
||||
|
if (i < p->anzMeasures) { |
||||
|
idx = p->measureDef[i].nameIdx; |
||||
|
channel = p->measureDef[i].channel; |
||||
|
} |
||||
|
else { |
||||
|
idx = p->measureCalculated[i - p->anzMeasures].nameIdx; |
||||
|
} |
||||
|
char tmp[20]; |
||||
|
strcpy_P (_buffer, NAMES[idx]); |
||||
|
if (channel) { |
||||
|
sprintf_P (tmp, PSTR(".CH%d"), channel); |
||||
|
strcat(_buffer,tmp); |
||||
|
} |
||||
|
return _buffer; |
||||
|
} |
||||
|
|
||||
|
const char *getUnit (uint8_t wr, uint8_t i) { |
||||
|
//------------------------------------------
|
||||
|
inverter_t *p = &(inverters[wr]); |
||||
|
if (i >= p->anzTotalMeasures) return error; |
||||
|
uint8_t idx; |
||||
|
if (i < p->anzMeasures) |
||||
|
idx = p->measureDef[i].unitIdx; |
||||
|
else |
||||
|
idx = p->measureCalculated[i-p->anzMeasures].unitIdx; |
||||
|
|
||||
|
//strcpy (_buffer, units[i]);
|
||||
|
//return _buffer;
|
||||
|
return units[idx]; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
float getMeasureValue (uint8_t wr, uint8_t i) { |
||||
|
//------------------------------------------
|
||||
|
if (i >= inverters[wr].anzTotalMeasures) return 0.0; |
||||
|
return inverters[wr].values[i]; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
int getDivisor (uint8_t wr, uint8_t i) { |
||||
|
//------------------------------------
|
||||
|
inverter_t *p = &(inverters[wr]); |
||||
|
if (i >= p->anzTotalMeasures) return 1; |
||||
|
if (i < p->anzMeasures) { |
||||
|
uint8_t digits = p->measureDef[i].digits; |
||||
|
if (digits == DIV1) return 1; |
||||
|
if (digits == DIV10) return 10; |
||||
|
if (digits == DIV100) return 100; |
||||
|
if (digits == DIV1000) return 1000; |
||||
|
return 1; |
||||
|
} |
||||
|
else |
||||
|
return p->measureCalculated[i].digits; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
uint8_t getDigits (uint8_t wr, uint8_t i) { |
||||
|
//---------------------------------------
|
||||
|
inverter_t *p = &(inverters[wr]); |
||||
|
if (i >= p->anzTotalMeasures) return 0; |
||||
|
if (i < p->anzMeasures) |
||||
|
return p->measureDef[i].digits; |
||||
|
else |
||||
|
return p->measureCalculated[i-p->anzMeasures].digits; |
||||
|
} |
||||
|
|
||||
|
// +++++++++++++++++++++++++++++++++++ Inverter ++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
|
||||
|
#include "HM600.h" // für HM-600 und HM-700 |
||||
|
|
||||
|
#include "HM1200.h" |
||||
|
|
||||
|
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
|
||||
|
|
||||
|
void setupInverts() { |
||||
|
//-----------------
|
||||
|
|
||||
|
addInverter (0,"HM-600", 0x114172607952ULL, |
||||
|
hm600_measureDef, HM600_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
||||
|
hm600_measureCalc, HM600_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
||||
|
|
||||
|
/*
|
||||
|
addInverter (1,"HM-1200", 0x114172607952ULL, |
||||
|
hm1200_measureDef, HM1200_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
||||
|
hm1200_measureCalc, HM1200_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#endif |
@ -0,0 +1,151 @@ |
|||||
|
// ################# WebServer #################
|
||||
|
|
||||
|
#ifndef __MODWEBSERVER_H |
||||
|
#define __MODWEBSERVER_H |
||||
|
#define MODWEBSERVER |
||||
|
|
||||
|
#include <ESP8266WebServer.h> |
||||
|
#include "Debug.h" |
||||
|
#include "Settings.h" |
||||
|
|
||||
|
ESP8266WebServer server (WEBSERVER_PORT); |
||||
|
|
||||
|
|
||||
|
void returnOK () { |
||||
|
//--------------
|
||||
|
server.send(200, F("text/plain"), ""); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void returnFail(String msg) { |
||||
|
//-------------------------
|
||||
|
server.send(500, F("text/plain"), msg + "\r\n"); |
||||
|
} |
||||
|
|
||||
|
void handleHelp () { |
||||
|
//-----------------
|
||||
|
String out = "<html>"; |
||||
|
out += "<body><h2>Hilfe</h2>"; |
||||
|
out += "<br><br><table>"; |
||||
|
out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>"; |
||||
|
out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>"; |
||||
|
out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>"; |
||||
|
out += "<tr><td>/reboot</td><td>startet neu</td></tr>"; |
||||
|
out += "</table></body></html>"; |
||||
|
server.send (200, "text/html", out); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleReboot () { |
||||
|
//-------------------
|
||||
|
returnOK (); |
||||
|
ESP.reset(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleRoot() { |
||||
|
//----------------
|
||||
|
String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>"; |
||||
|
out += "<body>"; |
||||
|
out += "<h2>Hoymiles Micro-Inverters</h2>"; |
||||
|
char floatString[20]; |
||||
|
char line[100]; |
||||
|
for (uint8_t wr = 0; wr < anzInv; wr++) { |
||||
|
out += "<h3>" + String(inverters[wr].name) + "</h3>"; |
||||
|
out += "<h3>S/N " + String (getSerialNoTxt(wr)) + "</h3>"; |
||||
|
out += "<br><br><table border='1'>"; |
||||
|
out += "<tr><th>Kanal</th><th>Wert</th><th>Einheit</th></tr>"; |
||||
|
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
||||
|
dtostrf (getMeasureValue(wr, i),1, getDigits(wr,i), floatString); |
||||
|
sprintf(line, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>", getMeasureName(wr, i), floatString, getUnit(wr, i)); |
||||
|
//DEBUG_OUT.println(line);
|
||||
|
out += String(line); |
||||
|
/* out += "<tr><td>" + getMeasureName(i) + "</td>";
|
||||
|
out += "<td>" + String(getMeasureValue(i)) + "</td></tr>"; |
||||
|
out += "<td>" + String(getUnit(i)) + "</td></tr>"; */ |
||||
|
} |
||||
|
out += "</table>"; |
||||
|
} |
||||
|
int pos = out.indexOf("°"); |
||||
|
do { |
||||
|
if (pos>1) { |
||||
|
out = out.substring (0, pos) + "°" + out.substring(pos+2); |
||||
|
} |
||||
|
pos = out.indexOf("°"); |
||||
|
} while (pos>1); |
||||
|
|
||||
|
out += "</body></html>"; |
||||
|
server.send (200, "text/html", out); |
||||
|
//DEBUG_OUT.println (out);
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleData () { |
||||
|
//-----------------
|
||||
|
String out = ""; |
||||
|
for (uint8_t wr = 0; wr < anzInv; wr++) { |
||||
|
for (int i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
||||
|
out += (anzInv <= 1 ? "" : String (wr) + ".") + String(getMeasureName(wr,i)) + '=' |
||||
|
+ String (getMeasureValue(wr,i)) /*+ ' ' + String(getUnit(wr,i))*/ + '\n'; |
||||
|
} |
||||
|
} |
||||
|
server.send(200, "text/plain", out); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleNotFound() { |
||||
|
//--------------------
|
||||
|
String message = "URI: "; |
||||
|
message += server.uri(); |
||||
|
message += "\nMethod: "; |
||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST"; |
||||
|
message += "\nArguments: "; |
||||
|
message += server.args(); |
||||
|
message += "\n"; |
||||
|
for (uint8_t i = 0; i < server.args(); i++) { |
||||
|
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; |
||||
|
} |
||||
|
server.send(404, "text/plain", message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void setupWebServer (void) { |
||||
|
//-------------------------
|
||||
|
server.begin(); |
||||
|
server.on("/", handleRoot); |
||||
|
server.on("/reboot", handleReboot); |
||||
|
server.on("/data", handleData); |
||||
|
server.on("/help", handleHelp); |
||||
|
//server.onNotFound(handleNotFound); wegen Spiffs-Dateimanager
|
||||
|
|
||||
|
DEBUG_OUT.println ("[HTTP] installed"); |
||||
|
} |
||||
|
|
||||
|
void webserverHandle() { |
||||
|
//====================
|
||||
|
server.handleClient(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// ################# OTA #################
|
||||
|
|
||||
|
#ifdef WITH_OTA |
||||
|
#include <ESP8266HTTPUpdateServer.h> |
||||
|
|
||||
|
ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT); |
||||
|
ESP8266HTTPUpdateServer httpUpdater; |
||||
|
|
||||
|
void setupUpdateByOTA () { |
||||
|
//------------------------
|
||||
|
httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW); |
||||
|
httpUpdateServer.begin(); |
||||
|
DEBUG_OUT.println ("[OTA] installed"); |
||||
|
} |
||||
|
|
||||
|
void checkUpdateByOTA() { |
||||
|
//---------------------
|
||||
|
httpUpdateServer.handleClient(); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#endif |
@ -0,0 +1,69 @@ |
|||||
|
#ifndef __SETTINGS_H |
||||
|
#define __SETTINGS_H |
||||
|
|
||||
|
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
|
#define DEBUG |
||||
|
#define SER_BAUDRATE (115200) |
||||
|
|
||||
|
#include "Debug.h" |
||||
|
|
||||
|
// Ausgabe was gesendet wird; 0 oder 1
|
||||
|
#define DEBUG_SEND 0 |
||||
|
|
||||
|
// soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
|
||||
|
#define CHANNEL_HOP |
||||
|
|
||||
|
// mit OTA Support, also update der Firmware über WLan mittels IP/update
|
||||
|
#define WITH_OTA |
||||
|
|
||||
|
// Hardware configuration
|
||||
|
#ifdef ESP8266 |
||||
|
#define RF1_CE_PIN (D4) |
||||
|
#define RF1_CS_PIN (D8) |
||||
|
#define RF1_IRQ_PIN (D3) |
||||
|
#else |
||||
|
#define RF1_CE_PIN (9) |
||||
|
#define RF1_CS_PIN (10) |
||||
|
#define RF1_IRQ_PIN (2) |
||||
|
#endif |
||||
|
|
||||
|
// WR und DTU
|
||||
|
#define RF_MAX_ADDR_WIDTH (5) |
||||
|
#define MAX_RF_PAYLOAD_SIZE (32) |
||||
|
#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
|
||||
|
|
||||
|
#define USE_POOR_MAN_CHANNEL_HOPPING_RCV 1 // 0 = not use
|
||||
|
|
||||
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
||||
|
#define MAX_ANZ_INV 2 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define MAX_MEASURE_PER_INV 25 // hier statisch, könnte auch dynamisch erzeugt werden, aber Overhead für dyn. Speicher?
|
||||
|
|
||||
|
// Webserver
|
||||
|
#define WEBSERVER_PORT 80 |
||||
|
|
||||
|
// Time Server
|
||||
|
//#define TIMESERVER_NAME "pool.ntp.org"
|
||||
|
#define TIMESERVER_NAME "fritz.box" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
|
||||
|
#ifdef WITH_OTA |
||||
|
// OTA Einstellungen
|
||||
|
#define UPDATESERVER_PORT WEBSERVER_PORT+1 |
||||
|
#define UPDATESERVER_DIR "/update" |
||||
|
#define UPDATESERVER_USER "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define UPDATESERVER_PW "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#endif |
||||
|
|
||||
|
// internes WLan
|
||||
|
// PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) von fremden zu unterscheiden
|
||||
|
// gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
|
||||
|
#define SSID_PREFIX1 "pre1" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define SSID_PREFIX2 "pre2" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define SSID_PASSWORD "?????????????????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
|
||||
|
// zur Berechnung von Sonnenauf- und -untergang
|
||||
|
#define geoBreite 49.2866 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define geoLaenge 7.3416 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
|
||||
|
|
||||
|
#endif |
@ -0,0 +1,55 @@ |
|||||
|
#ifndef __SONNE_H |
||||
|
#define __SONNE_H |
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include "Debug.h" |
||||
|
|
||||
|
|
||||
|
long SunDown, SunUp; |
||||
|
|
||||
|
void calcSunUpDown (time_t date) { |
||||
|
//SunUpDown res = new SunUpDown();
|
||||
|
boolean isSummerTime = false; // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
|
||||
|
|
||||
|
//- Bogenmass
|
||||
|
double brad = geoBreite / 180.0 * PI; |
||||
|
// - Höhe Sonne -50 Bogenmin.
|
||||
|
double h0 = -50.0 / 60.0 / 180.0 * PI; |
||||
|
//- Deklination dek, Tag des Jahres d0
|
||||
|
int tage = 30 * month(date) - 30 + day(date); |
||||
|
double dek = 0.40954 * sin (0.0172 * (tage - 79.35)); |
||||
|
double zh1 = sin (h0) - sin (brad) * sin(dek); |
||||
|
double zh2 = cos(brad) * cos(dek); |
||||
|
double zd = 12*acos (zh1/zh2) / PI; |
||||
|
double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939); |
||||
|
//-Sonnenuntergang
|
||||
|
double tsu = 12 + zd - zgl; |
||||
|
double su = (tsu + (15.0 - geoLaenge) / 15.0); |
||||
|
int std = (int)su; |
||||
|
int minute = (int) ((su - std)*60); |
||||
|
if (isSummerTime) std++; |
||||
|
SunDown = (100*std + minute) * 100; |
||||
|
|
||||
|
//- Sonnenaufgang
|
||||
|
double tsa = 12 - zd - zgl; |
||||
|
double sa = (tsa + (15.0 - geoLaenge) /15.0); |
||||
|
std = (int) sa; |
||||
|
minute = (int) ((sa - std)*60); |
||||
|
if (isSummerTime) std++; |
||||
|
SunUp = (100*std + minute) * 100; |
||||
|
DEBUG_OUT.print(F("Sonnenaufgang :")); DEBUG_OUT.println(SunUp); |
||||
|
DEBUG_OUT.print(F("Sonnenuntergang:")); DEBUG_OUT.println(SunDown); |
||||
|
} |
||||
|
|
||||
|
boolean isDayTime() { |
||||
|
//-----------------
|
||||
|
// 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
|
||||
|
const int offset=60*15; |
||||
|
time_t no = getNow(); |
||||
|
long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100; |
||||
|
long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100; |
||||
|
|
||||
|
return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown)); |
||||
|
} |
||||
|
|
||||
|
#endif |
@ -0,0 +1,102 @@ |
|||||
|
#ifndef __HM_CRC_H |
||||
|
#define __HM_CRC_H |
||||
|
|
||||
|
#define BITS_TO_BYTES(x) (((x)+7)>>3) |
||||
|
#define BYTES_TO_BITS(x) ((x)<<3) |
||||
|
|
||||
|
extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen); |
||||
|
extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen); |
||||
|
extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits); |
||||
|
|
||||
|
//#define OUTPUT_DEBUG_INFO
|
||||
|
|
||||
|
#define CRC8_INIT 0x00 |
||||
|
#define CRC8_POLY 0x01 |
||||
|
|
||||
|
#define CRC16_MODBUS_POLYNOM 0xA001 |
||||
|
|
||||
|
uint8_t crc8(uint8_t buf[], uint16_t len) { |
||||
|
uint8_t crc = CRC8_INIT; |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
crc ^= buf[i]; |
||||
|
for(uint8_t b = 0; b < 8; b ++) { |
||||
|
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); |
||||
|
} |
||||
|
} |
||||
|
return crc; |
||||
|
} |
||||
|
|
||||
|
uint16_t crc16_modbus(uint8_t buf[], uint16_t len) { |
||||
|
uint16_t crc = 0xffff; |
||||
|
uint8_t lsb; |
||||
|
|
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
crc = crc ^ buf[i]; |
||||
|
for(int8_t b = 7; b >= 0; b--) { |
||||
|
lsb = (crc & 0x0001); |
||||
|
if(lsb == 0x01) |
||||
|
crc--; |
||||
|
crc = crc >> 1; |
||||
|
if(lsb == 0x01) |
||||
|
crc = crc ^ CRC16_MODBUS_POLYNOM; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return crc; |
||||
|
} |
||||
|
|
||||
|
// NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
|
uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits) |
||||
|
{ |
||||
|
uint16_t crc = startCRC; |
||||
|
if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen))) |
||||
|
{ |
||||
|
// The length of the data might not be a multiple of full bytes.
|
||||
|
// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
|
||||
|
// calculate the CRC.
|
||||
|
uint16_t data; |
||||
|
uint8_t byte, shift; |
||||
|
uint16_t bitoffs = startBit; |
||||
|
|
||||
|
// Get a new byte for the next 8 bits.
|
||||
|
byte = buf[bitoffs >> 3]; |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf_P(PSTR("\nStart CRC %04X, %u bits:"), startCRC, len_bits); |
||||
|
printf_P(PSTR("\nbyte %02X:"), byte); |
||||
|
#endif |
||||
|
while (bitoffs < len_bits + startBit) |
||||
|
{ |
||||
|
shift = bitoffs & 7; |
||||
|
// Shift the active bit to the position of bit 15
|
||||
|
data = ((uint16_t)byte) << (8 + shift); |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf_P(PSTR(" bit %u %u,"), shift, data & 0x8000 ? 1 : 0); |
||||
|
#endif |
||||
|
// Assure all other bits are 0
|
||||
|
data &= 0x8000; |
||||
|
crc ^= data; |
||||
|
if (crc & 0x8000) |
||||
|
{ |
||||
|
crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
crc = (crc << 1); |
||||
|
} |
||||
|
++bitoffs; |
||||
|
if (0 == (bitoffs & 7)) |
||||
|
{ |
||||
|
// Get a new byte for the next 8 bits.
|
||||
|
byte = buf[bitoffs >> 3]; |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf_P(PSTR("crc %04X:"), crc); |
||||
|
if (bitoffs < len_bits + startBit) |
||||
|
printf_P(PSTR("\nbyte %02X:"), byte); |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return crc; |
||||
|
} |
||||
|
|
||||
|
#endif |
@ -0,0 +1,93 @@ |
|||||
|
#ifndef __HM_PACKETS_H |
||||
|
#define __HM_PACKETS_H |
||||
|
|
||||
|
#include "hm_crc.h" |
||||
|
|
||||
|
class HM_Packets |
||||
|
{ |
||||
|
private: |
||||
|
uint32_t unixTimeStamp; |
||||
|
|
||||
|
void prepareBuffer(uint8_t *buf); |
||||
|
void copyToBuffer(uint8_t *buf, uint32_t val); |
||||
|
void copyToBufferBE(uint8_t *buf, uint32_t val); |
||||
|
|
||||
|
public: |
||||
|
void SetUnixTimeStamp(uint32_t ts); |
||||
|
void UnixTimeStampTick(); |
||||
|
|
||||
|
int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr); |
||||
|
int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd); |
||||
|
}; |
||||
|
|
||||
|
void HM_Packets::SetUnixTimeStamp(uint32_t ts) |
||||
|
{ |
||||
|
unixTimeStamp = ts; |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::UnixTimeStampTick() |
||||
|
{ |
||||
|
unixTimeStamp++; |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::prepareBuffer(uint8_t *buf) |
||||
|
{ |
||||
|
// minimal buffer size of 32 bytes is assumed
|
||||
|
memset(buf, 0x00, 32); |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val) |
||||
|
{ |
||||
|
buf[0]= (uint8_t)(val >> 24); |
||||
|
buf[1]= (uint8_t)(val >> 16); |
||||
|
buf[2]= (uint8_t)(val >> 8); |
||||
|
buf[3]= (uint8_t)(val & 0xFF); |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val) |
||||
|
{ |
||||
|
memcpy(buf, &val, sizeof(uint32_t)); |
||||
|
} |
||||
|
|
||||
|
static uint8_t cid = 0; |
||||
|
|
||||
|
int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr) |
||||
|
{ |
||||
|
prepareBuffer(buf); |
||||
|
|
||||
|
buf[0] = 0x15; |
||||
|
copyToBufferBE(&buf[1], wrAdr); |
||||
|
copyToBufferBE(&buf[5], dtuAdr); |
||||
|
buf[9] = 0x80; |
||||
|
buf[10] = 0x0B; //0x0B; 0x03 0x11
|
||||
|
buf[11] = 0x00; |
||||
|
|
||||
|
copyToBuffer(&buf[12], unixTimeStamp); |
||||
|
|
||||
|
buf[19] = 0x05; |
||||
|
|
||||
|
// CRC16
|
||||
|
uint16_t crc16 = crc16_modbus(&buf[10], 14); |
||||
|
buf[24] = crc16 >> 8; |
||||
|
buf[25] = crc16 & 0xFF; |
||||
|
|
||||
|
// crc8
|
||||
|
buf[26] = crc8(&buf[0], 26); |
||||
|
|
||||
|
return 27; |
||||
|
} |
||||
|
|
||||
|
int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd) |
||||
|
{ |
||||
|
buf[0] = mid; |
||||
|
copyToBufferBE(&buf[1], wrAdr); |
||||
|
copyToBufferBE(&buf[5], dtuAdr); |
||||
|
buf[9] = cmd; |
||||
|
|
||||
|
// crc8
|
||||
|
buf[10] = crc8(&buf[0], 10); |
||||
|
|
||||
|
return 11; |
||||
|
} |
||||
|
|
||||
|
#endif |
@ -0,0 +1,345 @@ |
|||||
|
#ifndef __WIFI_H |
||||
|
#define __WIFI_H |
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include "Debug.h" |
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <Pinger.h> // von url=https://www.technologytourist.com |
||||
|
|
||||
|
String SSID = ""; // bestes WLan
|
||||
|
|
||||
|
// Prototypes
|
||||
|
time_t getNow (); |
||||
|
boolean setupWifi (); |
||||
|
boolean checkWifi(); |
||||
|
|
||||
|
|
||||
|
String findWifi () { |
||||
|
//----------------
|
||||
|
String ssid; |
||||
|
int32_t rssi; |
||||
|
uint8_t encryptionType; |
||||
|
uint8_t* bssid; |
||||
|
int32_t channel; |
||||
|
bool hidden; |
||||
|
int scanResult; |
||||
|
|
||||
|
String best_ssid = ""; |
||||
|
int32_t best_rssi = -100; |
||||
|
|
||||
|
DEBUG_OUT.println(F("Starting WiFi scan...")); |
||||
|
|
||||
|
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true); |
||||
|
|
||||
|
if (scanResult == 0) { |
||||
|
DEBUG_OUT.println(F("keine WLans")); |
||||
|
} else if (scanResult > 0) { |
||||
|
DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult); |
||||
|
|
||||
|
// Print unsorted scan results
|
||||
|
for (int8_t i = 0; i < scanResult; i++) { |
||||
|
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden); |
||||
|
|
||||
|
DEBUG_OUT.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"), |
||||
|
i, |
||||
|
channel, |
||||
|
bssid[0], bssid[1], bssid[2], |
||||
|
bssid[3], bssid[4], bssid[5], |
||||
|
rssi, |
||||
|
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*', |
||||
|
hidden ? 'H' : 'V', |
||||
|
ssid.c_str()); |
||||
|
delay(1); |
||||
|
boolean check; |
||||
|
#ifdef SSID_PREFIX1 |
||||
|
check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1); |
||||
|
#else |
||||
|
check = true; |
||||
|
#endif |
||||
|
#ifdef SSID_PREFIX2 |
||||
|
check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2); |
||||
|
#endif |
||||
|
if (check) { |
||||
|
if (rssi > best_rssi) { |
||||
|
best_rssi = rssi; |
||||
|
best_ssid = ssid; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult); |
||||
|
} |
||||
|
|
||||
|
if (! best_ssid.equals("")) { |
||||
|
SSID = best_ssid; |
||||
|
DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str()); |
||||
|
return SSID; |
||||
|
} |
||||
|
else |
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
void IP2string (IPAddress IP, char * buf) { |
||||
|
sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]); |
||||
|
} |
||||
|
|
||||
|
void connectWifi() { |
||||
|
//------------------
|
||||
|
// if (SSID.equals(""))
|
||||
|
String s = findWifi(); |
||||
|
|
||||
|
if (!SSID.equals("")) { |
||||
|
DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID); |
||||
|
//while (WiFi.status() != WL_CONNECTED) {
|
||||
|
WiFi.begin (SSID, SSID_PASSWORD); |
||||
|
int versuche = 20; |
||||
|
while (WiFi.status() != WL_CONNECTED && versuche > 0) { |
||||
|
delay(1000); |
||||
|
versuche--; |
||||
|
DEBUG_OUT.print(versuche); DEBUG_OUT.print(' '); |
||||
|
} |
||||
|
//}
|
||||
|
if (WiFi.status() == WL_CONNECTED) { |
||||
|
char buffer[30]; |
||||
|
IP2string (WiFi.localIP(), buffer); |
||||
|
String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer); |
||||
|
DEBUG_OUT.println (out); |
||||
|
} |
||||
|
else |
||||
|
DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
boolean setupWifi () { |
||||
|
//------------------
|
||||
|
int count=5; |
||||
|
while (count-- && WiFi.status() != WL_CONNECTED) |
||||
|
connectWifi(); |
||||
|
return (WiFi.status() == WL_CONNECTED); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Pinger pinger; |
||||
|
IPAddress ROUTER = IPAddress(192,168,1,1); |
||||
|
|
||||
|
boolean checkWifi() { |
||||
|
//---------------
|
||||
|
boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER); |
||||
|
if (NotConnected) { |
||||
|
setupWifi(); |
||||
|
if (WiFi.status() == WL_CONNECTED) |
||||
|
getNow(); |
||||
|
} |
||||
|
return (WiFi.status() == WL_CONNECTED); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
// ################ Clock #################
|
||||
|
|
||||
|
#include <WiFiUdp.h> |
||||
|
#include <TimeLib.h> |
||||
|
|
||||
|
IPAddress timeServer; |
||||
|
unsigned int localPort = 8888; |
||||
|
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
|
||||
|
byte packetBuf[NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets
|
||||
|
const int timeZone = 1; // Central European Time = +1
|
||||
|
long SYNCINTERVALL = 0; |
||||
|
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
|
||||
|
|
||||
|
// prototypes
|
||||
|
time_t getNtpTime (); |
||||
|
void sendNTPpacket (IPAddress &address); |
||||
|
time_t getNow (); |
||||
|
char* getDateTimeStr (time_t no = getNow()); |
||||
|
time_t offsetDayLightSaving (uint32_t local_t); |
||||
|
bool isDayofDaylightChange (time_t local_t); |
||||
|
|
||||
|
|
||||
|
void _setSyncInterval (long intervall) { |
||||
|
//----------------------------------------
|
||||
|
SYNCINTERVALL = intervall; |
||||
|
setSyncInterval (intervall); |
||||
|
} |
||||
|
|
||||
|
void setupClock() { |
||||
|
//-----------------
|
||||
|
WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
|
||||
|
|
||||
|
Udp.begin(localPort); |
||||
|
|
||||
|
getNtpTime(); |
||||
|
|
||||
|
setSyncProvider (getNtpTime); |
||||
|
while(timeStatus()== timeNotSet) |
||||
|
delay(1); //
|
||||
|
|
||||
|
_setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
|
||||
|
|
||||
|
//lastClock = now();
|
||||
|
//Serial.print("[NTP] get time from NTP server ");
|
||||
|
getNow(); |
||||
|
//char buf[20];
|
||||
|
DEBUG_OUT.print ("[NTP] get time from NTP server "); |
||||
|
DEBUG_OUT.print (timeServer); |
||||
|
//sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
|
DEBUG_OUT.print (": got "); |
||||
|
DEBUG_OUT.println (getDateTimeStr()); |
||||
|
} |
||||
|
|
||||
|
//*-------- NTP code ----------*/
|
||||
|
|
||||
|
|
||||
|
time_t getNtpTime() { |
||||
|
//-------------------
|
||||
|
sendNTPpacket(timeServer); // send an NTP packet to a time server
|
||||
|
//uint32_t beginWait = millis();
|
||||
|
//while (millis() - beginWait < 1500) {
|
||||
|
int versuch = 0; |
||||
|
while (versuch < 5) { |
||||
|
int wait = 150; // results in max 1500 ms waitTime
|
||||
|
while (wait--) { |
||||
|
int size = Udp.parsePacket(); |
||||
|
if (size >= NTP_PACKET_SIZE) { |
||||
|
//Serial.println("Receive NTP Response");
|
||||
|
Udp.read(packetBuf, NTP_PACKET_SIZE); // read packet into the buffer
|
||||
|
unsigned long secsSince1900; |
||||
|
// convert four bytes starting at location 40 to a long integer
|
||||
|
secsSince1900 = (unsigned long)packetBuf[40] << 24; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[41] << 16; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[42] << 8; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[43]; |
||||
|
// time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
|
||||
|
|
||||
|
time_t utc = secsSince1900 - 2208988800UL; |
||||
|
time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR; |
||||
|
|
||||
|
if (isDayofDaylightChange (utc) && hour(utc) <= 4) |
||||
|
_setSyncInterval (SECS_PER_HOUR); |
||||
|
else |
||||
|
_setSyncInterval (SECS_PER_DAY / 2); |
||||
|
|
||||
|
return now; |
||||
|
} |
||||
|
else |
||||
|
delay(10); |
||||
|
} |
||||
|
versuch++; |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// send an NTP request to the time server at the given address
|
||||
|
void sendNTPpacket(IPAddress& address) { |
||||
|
//------------------------------------
|
||||
|
memset(packetBuf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
|
||||
|
// Initialize values needed to form NTP request
|
||||
|
packetBuf[0] = B11100011; // LI, Version, Mode
|
||||
|
packetBuf[1] = 0; // Stratum
|
||||
|
packetBuf[2] = 6; // Max Interval between messages in seconds
|
||||
|
packetBuf[3] = 0xEC; // Clock Precision
|
||||
|
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
|
packetBuf[12] = 49; // four-byte reference ID identifying
|
||||
|
packetBuf[13] = 0x4E; |
||||
|
packetBuf[14] = 49; |
||||
|
packetBuf[15] = 52; |
||||
|
// send the packet requesting a timestamp:
|
||||
|
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
||||
|
Udp.write(packetBuf,NTP_PACKET_SIZE); |
||||
|
Udp.endPacket(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
int getTimeTrials = 0; |
||||
|
|
||||
|
bool isValidDateTime (time_t no) { |
||||
|
return (year(no) > 2020 && year(no) < 2038); |
||||
|
} |
||||
|
|
||||
|
bool isDayofDaylightChange (time_t local_t) { |
||||
|
//-----------------------------------------
|
||||
|
int jahr = year (local_t); |
||||
|
int monat = month (local_t); |
||||
|
int tag = day (local_t); |
||||
|
bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) || |
||||
|
(monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7))); |
||||
|
DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
|
||||
|
// übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
|
time_t offsetDayLightSaving (uint32_t local_t) { |
||||
|
//--------------------------------------------
|
||||
|
int monat = month (local_t); |
||||
|
if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
|
if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
|
int jahr = year (local_t); |
||||
|
int std = hour (local_t); |
||||
|
//int tag = day (local_t);
|
||||
|
int stundenBisHeute = (std + 24 * day(local_t)); |
||||
|
if ( (monat == 3 && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) || |
||||
|
(monat == 10 && stundenBisHeute < (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) ) |
||||
|
return 1; |
||||
|
else |
||||
|
return 0; |
||||
|
/*
|
||||
|
int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7)); |
||||
|
if (monat == 3 && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute < stundenBisWechsel) |
||||
|
return 1; |
||||
|
else |
||||
|
return 0; |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
time_t getNow () { |
||||
|
//---------------
|
||||
|
time_t jetzt = now(); |
||||
|
while (!isValidDateTime(jetzt) && getTimeTrials < 10) { // ungültig, max 10x probieren
|
||||
|
if (getTimeTrials) { |
||||
|
//Serial.print (getTimeTrials);
|
||||
|
//Serial.println(". Versuch für getNtpTime");
|
||||
|
} |
||||
|
jetzt = getNtpTime (); |
||||
|
if (isValidDateTime(jetzt)) { |
||||
|
setTime (jetzt); |
||||
|
getTimeTrials = 0; |
||||
|
} |
||||
|
else |
||||
|
getTimeTrials++; |
||||
|
} |
||||
|
//return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
|
||||
|
return jetzt; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
char _timestr[24]; |
||||
|
|
||||
|
char* getNowStr (time_t no = getNow()) { |
||||
|
//------------------------------------
|
||||
|
sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
char* getTimeStr (time_t no = getNow()) { |
||||
|
//------------------------------------
|
||||
|
return getNowStr (no); |
||||
|
} |
||||
|
|
||||
|
char* getDateTimeStr (time_t no) { |
||||
|
//------------------------------
|
||||
|
sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
char* getDateStr (time_t no) { |
||||
|
//------------------------------
|
||||
|
sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#endif |
@ -0,0 +1,158 @@ |
|||||
|
/*
|
||||
|
CircularBuffer - An Arduino circular buffering library for arbitrary types. |
||||
|
|
||||
|
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl |
||||
|
|
||||
|
This library is free software; you can redistribute it and/or |
||||
|
modify it under the terms of the GNU Lesser General Public |
||||
|
License as published by the Free Software Foundation; either |
||||
|
version 2.1 of the License, or (at your option) any later version. |
||||
|
|
||||
|
This library is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
|
Lesser General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU Lesser General Public |
||||
|
License along with this library; if not, write to the Free Software |
||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
||||
|
*/ |
||||
|
|
||||
|
#ifndef CircularBuffer_h |
||||
|
#define CircularBuffer_h |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define DISABLE_IRQ noInterrupts() |
||||
|
#define RESTORE_IRQ interrupts() |
||||
|
#else |
||||
|
#define DISABLE_IRQ \ |
||||
|
uint8_t sreg = SREG; \ |
||||
|
cli(); |
||||
|
|
||||
|
#define RESTORE_IRQ \ |
||||
|
SREG = sreg; |
||||
|
#endif |
||||
|
|
||||
|
template <class T> class CircularBuffer |
||||
|
{ |
||||
|
public: |
||||
|
/** Constructor
|
||||
|
* @param buffer Preallocated buffer of at least size records. |
||||
|
* @param size Number of records available in the buffer. |
||||
|
*/ |
||||
|
CircularBuffer(T* buffer, const uint8_t size ) |
||||
|
: m_size(size), m_buff(buffer) |
||||
|
{ |
||||
|
clear(); |
||||
|
} |
||||
|
|
||||
|
/** Clear all entries in the circular buffer. */ |
||||
|
void clear(void) |
||||
|
{ |
||||
|
m_front = 0; |
||||
|
m_fill = 0; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is empty */ |
||||
|
inline bool empty(void) const |
||||
|
{ |
||||
|
return !m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Return the number of records stored in the buffer */ |
||||
|
inline uint8_t available(void) const |
||||
|
{ |
||||
|
return m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is full */ |
||||
|
inline bool full(void) const |
||||
|
{ |
||||
|
return m_fill == m_size; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on front of the buffer, for writing.
|
||||
|
* After filling the record, it has to be pushed to actually |
||||
|
* add it to the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is full. |
||||
|
*/ |
||||
|
T* getFront(void) const |
||||
|
{ |
||||
|
DISABLE_IRQ; |
||||
|
T* f = NULL; |
||||
|
if (!full()) |
||||
|
f = get(m_front); |
||||
|
RESTORE_IRQ; |
||||
|
return f; |
||||
|
} |
||||
|
|
||||
|
/** Push record to front of the buffer
|
||||
|
* @param record Record to push. If record was aquired previously (using getFront) its |
||||
|
* data will not be copied as it is already present in the buffer. |
||||
|
* @return True, when record was pushed successfully. |
||||
|
*/ |
||||
|
bool pushFront(T* record) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!full()) |
||||
|
{ |
||||
|
T* f = get(m_front); |
||||
|
if (f != record) |
||||
|
*f = *record; |
||||
|
m_front = (m_front+1) % m_size; |
||||
|
m_fill++; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on back of the buffer, for reading.
|
||||
|
* After reading the record, it has to be pop'ed to actually |
||||
|
* remove it from the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is empty. |
||||
|
*/ |
||||
|
T* getBack(void) const |
||||
|
{ |
||||
|
T* b = NULL; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
b = get(back()); |
||||
|
RESTORE_IRQ; |
||||
|
return b; |
||||
|
} |
||||
|
|
||||
|
/** Remove record from back of the buffer.
|
||||
|
* @return True, when record was pop'ed successfully. |
||||
|
*/ |
||||
|
bool popBack(void) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
{ |
||||
|
m_fill--; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
inline T * get(const uint8_t idx) const |
||||
|
{ |
||||
|
return &(m_buff[idx]); |
||||
|
} |
||||
|
inline uint8_t back(void) const |
||||
|
{ |
||||
|
return (m_front - m_fill + m_size) % m_size; |
||||
|
} |
||||
|
|
||||
|
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
|
T* const m_buff; // Ptr to buffer holding all records.
|
||||
|
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
|
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
|
}; |
||||
|
|
||||
|
#endif // CircularBuffer_h
|
@ -0,0 +1,23 @@ |
|||||
|
#ifndef __DEBUG_H |
||||
|
|
||||
|
#define __DEBUG_H |
||||
|
|
||||
|
#ifdef DEBUG |
||||
|
#define DEBUG_OUT Serial |
||||
|
#else |
||||
|
//---
|
||||
|
// disable Serial DEBUG output
|
||||
|
#define DEBUG_OUT DummySerial |
||||
|
static class { |
||||
|
public: |
||||
|
void begin(...) {} |
||||
|
void print(...) {} |
||||
|
void println(...) {} |
||||
|
void flush() {} |
||||
|
bool available() { return false;} |
||||
|
int readBytes(...) { return 0;} |
||||
|
int printf (...) {return 0;} |
||||
|
} DummySerial; |
||||
|
#endif |
||||
|
|
||||
|
#endif |
@ -0,0 +1,129 @@ |
|||||
|
// ################# WebServer #################
|
||||
|
|
||||
|
#ifndef __MODWEBSERVER_H |
||||
|
#define __MODWEBSERVER_H |
||||
|
#define MODWEBSERVER |
||||
|
|
||||
|
#include <ESP8266WebServer.h> |
||||
|
#include "Debug.h" |
||||
|
#include "Settings.h" |
||||
|
|
||||
|
ESP8266WebServer server (WEBSERVER_PORT); |
||||
|
|
||||
|
|
||||
|
void returnOK () { |
||||
|
//--------------
|
||||
|
server.send(200, F("text/plain"), ""); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void returnFail(String msg) { |
||||
|
//-------------------------
|
||||
|
server.send(500, F("text/plain"), msg + "\r\n"); |
||||
|
} |
||||
|
|
||||
|
void handleHelp () { |
||||
|
//-----------------
|
||||
|
String out = "<html>"; |
||||
|
out += "<body><h2>Hilfe</h2>"; |
||||
|
out += "<br><br><table>"; |
||||
|
out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>"; |
||||
|
out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>"; |
||||
|
out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>"; |
||||
|
out += "<tr><td>/reboot</td><td>startet neu</td></tr>"; |
||||
|
out += "</table></body></html>"; |
||||
|
server.send (200, "text/html", out); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleReboot () { |
||||
|
//-------------------
|
||||
|
returnOK (); |
||||
|
ESP.reset(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleRoot() { |
||||
|
//----------------
|
||||
|
String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>"; |
||||
|
out += "<body>"; |
||||
|
out += "<h2>Hoymiles Micro-Inverter HM-600</h2>"; |
||||
|
out += "<br><br><table border='1'>"; |
||||
|
out += "<tr><th>Kanal</th><th>Wert</th></tr>"; |
||||
|
for (byte i = 0; i < ANZAHL_VALUES; i++) { |
||||
|
out += "<tr><td>" + String(getChannelName(i)) + "</td>"; |
||||
|
out += "<td>" + String(VALUES[i]) + "</td></tr>"; |
||||
|
} |
||||
|
out += "</table>"; |
||||
|
out += "</body></html>"; |
||||
|
server.send (200, "text/html", out); |
||||
|
//DEBUG_OUT.println (out);
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleData () { |
||||
|
//-----------------
|
||||
|
String out = ""; |
||||
|
for (int i = 0; i < ANZAHL_VALUES; i++) { |
||||
|
out += String(getChannelName(i)) + '=' + String (VALUES[i]) + '\n'; |
||||
|
} |
||||
|
server.send(200, "text/plain", out); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void handleNotFound() { |
||||
|
//--------------------
|
||||
|
String message = "URI: "; |
||||
|
message += server.uri(); |
||||
|
message += "\nMethod: "; |
||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST"; |
||||
|
message += "\nArguments: "; |
||||
|
message += server.args(); |
||||
|
message += "\n"; |
||||
|
for (uint8_t i = 0; i < server.args(); i++) { |
||||
|
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; |
||||
|
} |
||||
|
server.send(404, "text/plain", message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void setupWebServer (void) { |
||||
|
//-------------------------
|
||||
|
server.on("/", handleRoot); |
||||
|
server.on("/reboot", handleReboot); |
||||
|
server.on("/data", handleData); |
||||
|
server.on("/help", handleHelp); |
||||
|
//server.onNotFound(handleNotFound); wegen Spiffs-Dateimanager
|
||||
|
|
||||
|
server.begin(); |
||||
|
DEBUG_OUT.println ("[HTTP] installed"); |
||||
|
} |
||||
|
|
||||
|
void webserverHandle() { |
||||
|
//====================
|
||||
|
server.handleClient(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// ################# OTA #################
|
||||
|
|
||||
|
#ifdef WITH_OTA |
||||
|
#include <ESP8266HTTPUpdateServer.h> |
||||
|
|
||||
|
ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT); |
||||
|
ESP8266HTTPUpdateServer httpUpdater; |
||||
|
|
||||
|
void setupUpdateByOTA () { |
||||
|
//------------------------
|
||||
|
httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW); |
||||
|
httpUpdateServer.begin(); |
||||
|
DEBUG_OUT.println (F("[OTA] installed")); |
||||
|
} |
||||
|
|
||||
|
void checkUpdateByOTA() { |
||||
|
//---------------------
|
||||
|
httpUpdateServer.handleClient(); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#endif |
@ -0,0 +1,597 @@ |
|||||
|
#include <Arduino.h> |
||||
|
#include <SPI.h> |
||||
|
#include "CircularBuffer.h" |
||||
|
#include <RF24.h> |
||||
|
#include <RF24_config.h> |
||||
|
#include "hm_crc.h" |
||||
|
#include "hm_packets.h" |
||||
|
|
||||
|
#include "Settings.h" // Header für Einstellungen |
||||
|
|
||||
|
#include "Debug.h" |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define DISABLE_EINT noInterrupts() |
||||
|
#define ENABLE_EINT interrupts() |
||||
|
#else // für AVR z.B. ProMini oder Nano
|
||||
|
#define DISABLE_EINT EIMSK = 0x00 |
||||
|
#define ENABLE_EINT EIMSK = 0x01 |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#define RF_MAX_ADDR_WIDTH (5) |
||||
|
#define MAX_RF_PAYLOAD_SIZE (32) |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define PACKET_BUFFER_SIZE (30) |
||||
|
#else |
||||
|
#define PACKET_BUFFER_SIZE (20) |
||||
|
#endif |
||||
|
|
||||
|
// Startup defaults until user reconfigures it
|
||||
|
#define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles
|
||||
|
//#define DEFAULT_SEND_CHANNEL (75) // 40 = Default channel for Hoymiles, 61
|
||||
|
#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
|
||||
|
|
||||
|
#include "NRF24_sniff_types.h" |
||||
|
|
||||
|
static HM_Packets hmPackets; |
||||
|
static uint32_t tickMillis; |
||||
|
|
||||
|
|
||||
|
// Set up nRF24L01 radio on SPI bus plus CE/CS pins
|
||||
|
// If more than one RF24 unit is used the another CS pin than 10 must be used
|
||||
|
// This pin is used hard coded in SPI library
|
||||
|
static RF24 radio1 (RF1_CE_PIN, RF1_CS_PIN); |
||||
|
|
||||
|
static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE]; |
||||
|
|
||||
|
static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0])); |
||||
|
|
||||
|
static Serial_header_t SerialHdr; |
||||
|
|
||||
|
#define CHECKCRC 1 |
||||
|
static uint16_t lastCRC; |
||||
|
static uint16_t crc; |
||||
|
|
||||
|
uint8_t channels[] = {/*3,*/ 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
|
uint8_t channelIdx = 1; // fange mit 40 an
|
||||
|
uint8_t DEFAULT_SEND_CHANNEL = channels[channelIdx]; // = 40
|
||||
|
|
||||
|
static unsigned long timeLastPacket = millis(); |
||||
|
|
||||
|
// Function forward declaration
|
||||
|
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len); |
||||
|
char * getChannelName (uint8_t i); |
||||
|
|
||||
|
static const int ANZAHL_VALUES = 16; |
||||
|
static float VALUES[ANZAHL_VALUES] = {}; |
||||
|
static const char *CHANNEL_NAMES[ANZAHL_VALUES] |
||||
|
= {"P1.Udc", "P1.Idc", "P1.Pdc", "P2.Udc", "P2.Idc", "P2.Pdc", |
||||
|
"E-Woche", "E-Total", "E1-Tag", "E2-Tag", "Uac", "Freq.ac", "Pac", "E-heute", "Ipv", "WR-Temp"}; |
||||
|
static const uint8_t DIVISOR[ANZAHL_VALUES] = {10,100,10,10,100,10,1,1,1,1,10,100,10,0,0,10}; |
||||
|
|
||||
|
static const char BLANK = ' '; |
||||
|
|
||||
|
static boolean istTag = true; |
||||
|
|
||||
|
char CHANNELNAME_BUFFER[15]; |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#include "wifi.h" |
||||
|
#include "ModWebserver.h" |
||||
|
#include "Sonne.h" |
||||
|
#endif |
||||
|
|
||||
|
char * getChannelName (uint8_t i) { |
||||
|
//-------------------------------
|
||||
|
memset (CHANNELNAME_BUFFER, 0, sizeof(CHANNELNAME_BUFFER)); |
||||
|
strcpy (CHANNELNAME_BUFFER, CHANNEL_NAMES[i]); |
||||
|
//itoa (i, CHANNELNAME_BUFFER, 10);
|
||||
|
return CHANNELNAME_BUFFER; |
||||
|
} |
||||
|
|
||||
|
inline static void dumpData(uint8_t *p, int len) { |
||||
|
//-----------------------------------------------
|
||||
|
while (len--){ |
||||
|
if (*p < 16) |
||||
|
DEBUG_OUT.print(F("0")); |
||||
|
DEBUG_OUT.print(*p++, HEX); |
||||
|
} |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
float extractValue2 (uint8_t *p, int divisor) { |
||||
|
//-------------------------------------------
|
||||
|
uint16_t b1 = *p++; |
||||
|
return ((float) (b1 << 8) + *p) / (float) divisor; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
float extractValue4 (uint8_t *p, int divisor) { |
||||
|
//-------------------------------------------
|
||||
|
uint32_t ret = *p++; |
||||
|
for (uint8_t i = 1; i <= 3; i++) |
||||
|
ret = (ret << 8) + *p++; |
||||
|
return (ret / divisor); |
||||
|
} |
||||
|
|
||||
|
void outChannel (uint8_t i) { |
||||
|
//-------------------------
|
||||
|
DEBUG_OUT.print(getChannelName(i)); DEBUG_OUT.print(F("\t:")); DEBUG_OUT.print(VALUES[i]); DEBUG_OUT.println(BLANK); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void analyse01 (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
//DEBUG_OUT.print (F("analyse 01: "));
|
||||
|
p += 3; |
||||
|
// PV1.U PV1.I PV1.P PV2.U PV2.I PV2.P
|
||||
|
// [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W]
|
||||
|
for (int i = 0; i < 6; i++) { |
||||
|
VALUES[i] = extractValue2 (p,DIVISOR[i]); p += 2; |
||||
|
outChannel(i); |
||||
|
} |
||||
|
/*
|
||||
|
DEBUG_OUT.print(F("PV1.U:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" PV1.I:")); DEBUG_OUT.print(extractValue2(p,100)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" PV1.Pac:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" PV2.U:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" PV2.I:")); DEBUG_OUT.print(extractValue2(p,100)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" PV2.Pac:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
*/ |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void analyse02 (uint8_t *p) { // p zeigt auf 02 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
//DEBUG_OUT.print (F("analyse 02: "));
|
||||
|
// +11 = Spannung, +13 = Frequenz, +15 = Leistung
|
||||
|
//p += 11;
|
||||
|
p++; |
||||
|
for (int i = 6; i < 13; i++) { |
||||
|
if (i == 7) { |
||||
|
VALUES[i] = extractValue4 (p,DIVISOR[i]); |
||||
|
p += 4; |
||||
|
} |
||||
|
else { |
||||
|
VALUES[i] = extractValue2 (p,DIVISOR[i]); |
||||
|
p += 2; |
||||
|
} |
||||
|
outChannel(i); |
||||
|
} |
||||
|
VALUES[13] = VALUES[8] + VALUES[9]; // E-heute = P1+P2
|
||||
|
if (VALUES[10] > 0) |
||||
|
VALUES[14] = VALUES[12] / VALUES[10]; // Ipv = Pac / Spannung
|
||||
|
/*
|
||||
|
DEBUG_OUT.print(F("P Woche:")); DEBUG_OUT.print(extractValue2(p,1)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" P Total:")); DEBUG_OUT.print(extractValue4(p,1)); |
||||
|
p += 4; |
||||
|
DEBUG_OUT.print(F(" P1 Tag:")); DEBUG_OUT.print(extractValue2(p,1)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" P2 Tag:")); DEBUG_OUT.print(extractValue2(p,1)); |
||||
|
p += 2; |
||||
|
|
||||
|
DEBUG_OUT.print(F(" Spannung:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" Freq.:")); DEBUG_OUT.print(extractValue2(p,100)); |
||||
|
p += 2; |
||||
|
DEBUG_OUT.print(F(" Leist.:")); DEBUG_OUT.print(extractValue2(p,10)); |
||||
|
*/ |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void analyse83 (uint8_t *p) { // p zeigt auf 83 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
//DEBUG_OUT.print (F("++++++analyse 83:"));
|
||||
|
p += 7; |
||||
|
VALUES[15] = extractValue2 (p,DIVISOR[15]); |
||||
|
outChannel(15); |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
void analyseWords (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
DEBUG_OUT.print (F("analyse words:")); |
||||
|
p++; |
||||
|
for (int i = 0; i <12;i++) { |
||||
|
DEBUG_OUT.print(extractValue2(p,1)); |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
p++; |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
void analyseLongs (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
|
//----------------------------------
|
||||
|
//uint16_t val;
|
||||
|
DEBUG_OUT.print (F("analyse words:")); |
||||
|
p++; |
||||
|
for (int i = 0; i <12;i++) { |
||||
|
DEBUG_OUT.print(extractValue4(p,1)); |
||||
|
DEBUG_OUT.print(BLANK); |
||||
|
p++; |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
IRAM_ATTR |
||||
|
#endif |
||||
|
void handleNrf1Irq() { |
||||
|
//-------------------------
|
||||
|
static uint8_t lostPacketCount = 0; |
||||
|
uint8_t pipe; |
||||
|
|
||||
|
DISABLE_EINT; |
||||
|
|
||||
|
// Loop until RX buffer(s) contain no more packets.
|
||||
|
while (radio1.available(&pipe)) { |
||||
|
if (!packetBuffer.full()) { |
||||
|
NRF24_packet_t *p = packetBuffer.getFront(); |
||||
|
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
||||
|
p->packetsLost = lostPacketCount; |
||||
|
uint8_t packetLen = radio1.getPayloadSize(); |
||||
|
if (packetLen > MAX_RF_PAYLOAD_SIZE) |
||||
|
packetLen = MAX_RF_PAYLOAD_SIZE; |
||||
|
|
||||
|
radio1.read(p->packet, packetLen); |
||||
|
packetBuffer.pushFront(p); |
||||
|
lostPacketCount = 0; |
||||
|
} |
||||
|
else { |
||||
|
// Buffer full. Increase lost packet counter.
|
||||
|
bool tx_ok, tx_fail, rx_ready; |
||||
|
if (lostPacketCount < 255) |
||||
|
lostPacketCount++; |
||||
|
// Call 'whatHappened' to reset interrupt status.
|
||||
|
radio1.whatHappened(tx_ok, tx_fail, rx_ready); |
||||
|
// Flush buffer to drop the packet.
|
||||
|
radio1.flush_rx(); |
||||
|
} |
||||
|
} |
||||
|
ENABLE_EINT; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static void activateConf(void) { |
||||
|
//-----------------------------
|
||||
|
radio1.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
radio1.setDataRate(DEFAULT_RF_DATARATE); |
||||
|
radio1.disableCRC(); |
||||
|
radio1.setAutoAck(0x00); |
||||
|
radio1.setPayloadSize(MAX_RF_PAYLOAD_SIZE); |
||||
|
radio1.setAddressWidth(5); |
||||
|
radio1.openReadingPipe(1, DTU_RADIO_ID); |
||||
|
|
||||
|
// We want only RX irqs
|
||||
|
radio1.maskIRQ(true, true, false); |
||||
|
|
||||
|
// Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
|
||||
|
radio1.setPALevel(RF24_PA_MAX); |
||||
|
radio1.startListening(); |
||||
|
|
||||
|
// Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
|
||||
|
attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
|
||||
|
|
||||
|
// Initialize SerialHdr header's address member to promiscuous address.
|
||||
|
uint64_t addr = DTU_RADIO_ID; |
||||
|
for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) { |
||||
|
SerialHdr.address[i] = addr; |
||||
|
addr >>= 8; |
||||
|
} |
||||
|
|
||||
|
#ifndef ESP8266 |
||||
|
DEBUG_OUT.println(F("\nRadio Config:")); |
||||
|
radio1.printPrettyDetails(); |
||||
|
DEBUG_OUT.println(); |
||||
|
#endif |
||||
|
tickMillis = millis() + 200; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void setup(void) { |
||||
|
//--------------
|
||||
|
//Serial.begin(SER_BAUDRATE);
|
||||
|
DEBUG_OUT.begin(SER_BAUDRATE); |
||||
|
DEBUG_OUT.flush(); |
||||
|
|
||||
|
DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --")); |
||||
|
|
||||
|
radio1.begin(); |
||||
|
|
||||
|
// Disable shockburst for receiving and decode payload manually
|
||||
|
radio1.setAutoAck(false); |
||||
|
radio1.setRetries(0, 0); |
||||
|
|
||||
|
// Configure nRF IRQ input
|
||||
|
pinMode(RF1_IRQ_PIN, INPUT); |
||||
|
|
||||
|
activateConf(); |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
setupWifi(); |
||||
|
setupClock(); |
||||
|
setupWebServer(); |
||||
|
setupUpdateByOTA(); |
||||
|
calcSunUpDown (getNow()); |
||||
|
istTag = isDayTime(); |
||||
|
DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht"); |
||||
|
hmPackets.SetUnixTimeStamp (getNow()); |
||||
|
#else |
||||
|
hmPackets.SetUnixTimeStamp(0x62456430); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE]; |
||||
|
|
||||
|
void isTime2Send () { |
||||
|
//-----------------
|
||||
|
// Second timer
|
||||
|
|
||||
|
if (millis() >= tickMillis) { |
||||
|
static uint8_t tel = 0; |
||||
|
tickMillis += 1000; //200;
|
||||
|
//tickSec++;
|
||||
|
hmPackets.UnixTimeStampTick(); |
||||
|
/* if (++tickSec >= 5) { // 5
|
||||
|
hmPackets.UnixTimeStampTick(); |
||||
|
tickSec = 0; |
||||
|
} */ |
||||
|
|
||||
|
int32_t size = 0; |
||||
|
uint64_t dest = WR1_RADIO_ID; |
||||
|
|
||||
|
if (tel > 5) |
||||
|
tel = 0; |
||||
|
|
||||
|
if (tel == 0) { |
||||
|
#ifdef ESP8266 |
||||
|
hmPackets.SetUnixTimeStamp (getNow()); |
||||
|
#endif |
||||
|
size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8); |
||||
|
} |
||||
|
else if (tel == 1) |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x81); |
||||
|
else if (tel == 2) |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80); |
||||
|
else if (tel == 3) { |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x83); |
||||
|
//tel = 0;
|
||||
|
} |
||||
|
else if (tel == 4) |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x82); |
||||
|
else if (tel == 5) |
||||
|
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x84); |
||||
|
|
||||
|
SendPacket(dest, (uint8_t *)&sendBuf, size); |
||||
|
|
||||
|
tel++; |
||||
|
|
||||
|
/* for (uint8_t warte = 0; warte < 2; warte++) {
|
||||
|
delay(1000); |
||||
|
hmPackets.UnixTimeStampTick(); |
||||
|
}*/ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) { |
||||
|
//-----------------------------------------------------
|
||||
|
|
||||
|
// Write timestamp, packets lost, address and payload length
|
||||
|
//printf(" %09lu ", SerialHdr.timestamp);
|
||||
|
dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost)); |
||||
|
dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address)); |
||||
|
|
||||
|
// Trailing bit?!?
|
||||
|
dumpData(&p->packet[0], 2); |
||||
|
|
||||
|
// Payload length from PCF
|
||||
|
dumpData(&payloadLen, sizeof(payloadLen)); |
||||
|
|
||||
|
// Packet control field - PID Packet identification
|
||||
|
uint8_t val = (p->packet[1] >> 1) & 0x03; |
||||
|
DEBUG_OUT.print(val); |
||||
|
DEBUG_OUT.print(F(" ")); |
||||
|
|
||||
|
if (payloadLen > 9) { |
||||
|
dumpData(&p->packet[2], 1); |
||||
|
dumpData(&p->packet[3], 4); |
||||
|
dumpData(&p->packet[7], 4); |
||||
|
|
||||
|
uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4; |
||||
|
|
||||
|
if (remain < 32) { |
||||
|
dumpData(&p->packet[11], remain); |
||||
|
printf_P(PSTR("%04X "), crc); |
||||
|
|
||||
|
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) |
||||
|
DEBUG_OUT.print(0); |
||||
|
else |
||||
|
DEBUG_OUT.print(1); |
||||
|
} |
||||
|
else { |
||||
|
DEBUG_OUT.print(F("Ill remain ")); |
||||
|
DEBUG_OUT.print(remain); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
dumpData(&p->packet[2], payloadLen + 2); |
||||
|
printf_P(PSTR("%04X "), crc); |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void loop(void) { |
||||
|
//=============
|
||||
|
while (!packetBuffer.empty()) { |
||||
|
timeLastPacket = millis(); |
||||
|
// One or more records present
|
||||
|
NRF24_packet_t *p = packetBuffer.getBack(); |
||||
|
|
||||
|
// Shift payload data due to 9-bit packet control field
|
||||
|
for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) { |
||||
|
if (j > 0) |
||||
|
p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1); |
||||
|
else |
||||
|
p->packet[j] = (byte)(p->packet[j] >> 7); |
||||
|
} |
||||
|
|
||||
|
SerialHdr.timestamp = p->timestamp; |
||||
|
SerialHdr.packetsLost = p->packetsLost; |
||||
|
|
||||
|
// Check CRC
|
||||
|
crc = 0xFFFF; |
||||
|
crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address))); |
||||
|
// Payload length
|
||||
|
uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3); |
||||
|
// Add one byte and one bit for 9-bit packet control field
|
||||
|
crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1); |
||||
|
|
||||
|
if (CHECKCRC) { |
||||
|
// If CRC is invalid only show lost packets
|
||||
|
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) { |
||||
|
if (p->packetsLost > 0) { |
||||
|
DEBUG_OUT.print(F(" Lost: ")); |
||||
|
DEBUG_OUT.println(p->packetsLost); |
||||
|
} |
||||
|
packetBuffer.popBack(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Dump a decoded packet only once
|
||||
|
if (lastCRC == crc) { |
||||
|
packetBuffer.popBack(); |
||||
|
continue; |
||||
|
} |
||||
|
lastCRC = crc; |
||||
|
} |
||||
|
|
||||
|
// Don't dump mysterious ack packages
|
||||
|
if (payloadLen == 0) { |
||||
|
packetBuffer.popBack(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
#ifdef DEBUG |
||||
|
outputPacket (p, payloadLen); |
||||
|
#endif |
||||
|
|
||||
|
uint8_t cmd = p->packet[11]; |
||||
|
if (cmd == 0x02) |
||||
|
analyse02 (&p->packet[11]); |
||||
|
else if (cmd == 0x01) |
||||
|
analyse01 (&p->packet[11]); |
||||
|
//if (p->packet[11] == 0x83 || p->packet[11] == 0x82) analyse83 (&p->packet[11], payloadLen);
|
||||
|
else if (cmd == 0x03) { |
||||
|
analyseWords (&p->packet[11]); |
||||
|
analyseLongs (&p->packet[11]); |
||||
|
} |
||||
|
else if (cmd == 0x81) // ???
|
||||
|
; |
||||
|
else if (cmd == 0x83) |
||||
|
analyse83 (&p->packet[11]); |
||||
|
else { |
||||
|
DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX); |
||||
|
analyseWords (&p->packet[11]); |
||||
|
analyseLongs (&p->packet[11]); |
||||
|
} |
||||
|
if (p->packetsLost > 0) { |
||||
|
DEBUG_OUT.print(F(" Lost: ")); |
||||
|
DEBUG_OUT.print(p->packetsLost); |
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
|
||||
|
#ifndef ESP8266 |
||||
|
for (uint8_t i = 0; i < ANZAHL_VALUES; i++) { |
||||
|
//outChannel(i);
|
||||
|
Serial.print(getChannelName(i)); Serial.print(':'); Serial.print(VALUES[i]); Serial.println(BLANK); // Schnittstelle bei Arduino
|
||||
|
} |
||||
|
DEBUG_OUT.println(); |
||||
|
#endif |
||||
|
|
||||
|
// Remove record as we're done with it.
|
||||
|
packetBuffer.popBack(); |
||||
|
} |
||||
|
|
||||
|
if (istTag) |
||||
|
isTime2Send(); |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
checkWifi(); |
||||
|
webserverHandle(); |
||||
|
checkUpdateByOTA(); |
||||
|
if (hour() == 0 && minute() == 0) { |
||||
|
calcSunUpDown(getNow()); |
||||
|
} |
||||
|
if (minute() % 15 == 0 && second () == 0) { // alle 15 Minuten neu berechnen ob noch hell
|
||||
|
istTag = isDayTime(); |
||||
|
DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht"); |
||||
|
} |
||||
|
#endif |
||||
|
/*
|
||||
|
if (millis() > timeLastPacket + 60UL*SECOND) { // 60 Sekunden
|
||||
|
channelIdx++; |
||||
|
if (channelIdx >= sizeof(channels)) channelIdx = 0; |
||||
|
DEFAULT_SEND_CHANNEL = channels[channelIdx]; |
||||
|
DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL); |
||||
|
timeLastPacket = millis(); |
||||
|
} |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) { |
||||
|
//--------------------------------------------------------------
|
||||
|
DISABLE_EINT; |
||||
|
radio1.stopListening(); |
||||
|
|
||||
|
#ifdef CHANNEL_HOP |
||||
|
static uint8_t hop = 0; |
||||
|
#if DEBUG_SEND |
||||
|
DEBUG_OUT.print(F("Send... CH")); |
||||
|
DEBUG_OUT.println(channels[hop]); |
||||
|
#endif |
||||
|
radio1.setChannel(channels[hop++]); |
||||
|
if (hop >= sizeof(channels) / sizeof(channels[0])) |
||||
|
hop = 0; |
||||
|
#else |
||||
|
radio1.setChannel(DEFAULT_SEND_CHANNEL); |
||||
|
#endif |
||||
|
|
||||
|
radio1.openWritingPipe(dest); |
||||
|
radio1.setCRCLength(RF24_CRC_16); |
||||
|
radio1.enableDynamicPayloads(); |
||||
|
radio1.setAutoAck(true); |
||||
|
radio1.setRetries(3, 15); |
||||
|
|
||||
|
radio1.write(buf, len); |
||||
|
|
||||
|
// Try to avoid zero payload acks (has no effect)
|
||||
|
radio1.openWritingPipe(DUMMY_RADIO_ID); |
||||
|
|
||||
|
radio1.setAutoAck(false); |
||||
|
radio1.setRetries(0, 0); |
||||
|
radio1.disableDynamicPayloads(); |
||||
|
radio1.setCRCLength(RF24_CRC_DISABLED); |
||||
|
|
||||
|
radio1.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
radio1.startListening(); |
||||
|
ENABLE_EINT; |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
/*
|
||||
|
This file is part of NRF24_Sniff. |
||||
|
|
||||
|
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl |
||||
|
|
||||
|
NRF24_Sniff is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
NRF24_Sniff is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with NRF24_Sniff. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
|
||||
|
#ifndef NRF24_sniff_types_h |
||||
|
#define NRF24_sniff_types_h |
||||
|
|
||||
|
typedef struct _NRF24_packet_t |
||||
|
{ |
||||
|
uint32_t timestamp; |
||||
|
uint8_t packetsLost; |
||||
|
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; |
||||
|
} NRF24_packet_t; |
||||
|
|
||||
|
typedef struct _Serial_header_t |
||||
|
{ |
||||
|
unsigned long timestamp; |
||||
|
uint8_t packetsLost; |
||||
|
uint8_t address[RF_MAX_ADDR_WIDTH]; // MSB first, always RF_MAX_ADDR_WIDTH bytes.
|
||||
|
} Serial_header_t; |
||||
|
|
||||
|
typedef struct _Serial_config_t |
||||
|
{ |
||||
|
uint8_t channel; |
||||
|
uint8_t rate; // rf24_datarate_e: 0 = 1Mb/s, 1 = 2Mb/s, 2 = 250Kb/s
|
||||
|
uint8_t addressLen; // Number of bytes used in address, range [2..5]
|
||||
|
uint8_t addressPromiscLen; // Number of bytes used in promiscuous address, range [2..5]. E.g. addressLen=5, addressPromiscLen=4 => 1 byte unique identifier.
|
||||
|
uint64_t address; // Base address, LSB first.
|
||||
|
uint8_t crcLength; // Length of active CRC, range [0..2]
|
||||
|
uint8_t maxPayloadSize; // Maximum size of payload for nRF (including nRF header), range[4?..32]
|
||||
|
} Serial_config_t; |
||||
|
|
||||
|
#define MSG_TYPE_PACKET (0) |
||||
|
#define MSG_TYPE_CONFIG (1) |
||||
|
|
||||
|
#define SET_MSG_TYPE(var,type) (((var) & 0x3F) | ((type) << 6)) |
||||
|
#define GET_MSG_TYPE(var) ((var) >> 6) |
||||
|
#define GET_MSG_LEN(var) ((var) & 0x3F) |
||||
|
|
||||
|
#endif // NRF24_sniff_types_h
|
@ -0,0 +1,82 @@ |
|||||
|
#ifndef __SETTINGS_H |
||||
|
#define __SETTINGS_H |
||||
|
|
||||
|
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
|
#define DEBUG |
||||
|
#define SER_BAUDRATE (115200) |
||||
|
|
||||
|
// Ausgabe was gesendet wird; 0 oder 1
|
||||
|
#define DEBUG_SEND 0 |
||||
|
|
||||
|
// soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
|
||||
|
#define CHANNEL_HOP |
||||
|
|
||||
|
// mit OTA Support, also update der Firmware über WLan mittels IP/update
|
||||
|
#define WITH_OTA |
||||
|
|
||||
|
// Hardware configuration
|
||||
|
#ifdef ESP8266 |
||||
|
#define RF1_CE_PIN (D4) |
||||
|
#define RF1_CS_PIN (D8) |
||||
|
#define RF1_IRQ_PIN (D3) |
||||
|
#else |
||||
|
#define RF1_CE_PIN (9) |
||||
|
#define RF1_CS_PIN (10) |
||||
|
#define RF1_IRQ_PIN (2) |
||||
|
#endif |
||||
|
|
||||
|
union longlongasbytes { |
||||
|
uint64_t ull; |
||||
|
uint8_t bytes[8]; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
uint64_t Serial2RadioID (uint64_t sn) { |
||||
|
//----------------------------------
|
||||
|
longlongasbytes llsn; |
||||
|
longlongasbytes res; |
||||
|
llsn.ull = sn; |
||||
|
res.ull = 0; |
||||
|
res.bytes[4] = llsn.bytes[0]; |
||||
|
res.bytes[3] = llsn.bytes[1]; |
||||
|
res.bytes[2] = llsn.bytes[2]; |
||||
|
res.bytes[1] = llsn.bytes[3]; |
||||
|
res.bytes[0] = 0x01; |
||||
|
return res.ull; |
||||
|
} |
||||
|
|
||||
|
// WR und DTU
|
||||
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
||||
|
#define SerialWR 0x114172607952ULL // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
uint64_t WR1_RADIO_ID = Serial2RadioID (SerialWR); // ((uint64_t)0x5279607201ULL);
|
||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
||||
|
|
||||
|
|
||||
|
// Webserver
|
||||
|
#define WEBSERVER_PORT 80 |
||||
|
|
||||
|
// Time Server
|
||||
|
//#define TIMESERVER_NAME "pool.ntp.org"
|
||||
|
#define TIMESERVER_NAME "fritz.box" |
||||
|
|
||||
|
#ifdef WITH_OTA |
||||
|
// OTA Einstellungen
|
||||
|
#define UPDATESERVER_PORT WEBSERVER_PORT+1 |
||||
|
#define UPDATESERVER_DIR "/update" // mittels IP:81/update kommt man dann auf die OTA-Seite
|
||||
|
#define UPDATESERVER_USER "username_für_OTA" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define UPDATESERVER_PW "passwort_für_OTA" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#endif |
||||
|
|
||||
|
// internes WLan
|
||||
|
// PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) vonfremden zu unterscheiden
|
||||
|
// gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
|
||||
|
#define SSID_PREFIX1 "wlan1-Prefix" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define SSID_PREFIX2 "wlan2-Prefix" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
#define SSID_PASSWORD "wlan-passwort" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
|
||||
|
// zur Berechnung von Sonnenauf- und -untergang
|
||||
|
#define geoBreite 49.2866 |
||||
|
#define geoLaenge 7.3416 |
||||
|
|
||||
|
|
||||
|
#endif |
@ -0,0 +1,55 @@ |
|||||
|
#ifndef __SONNE_H |
||||
|
#define __SONNE_H |
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include "Debug.h" |
||||
|
|
||||
|
|
||||
|
long SunDown, SunUp; |
||||
|
|
||||
|
void calcSunUpDown (time_t date) { |
||||
|
//SunUpDown res = new SunUpDown();
|
||||
|
boolean isSummerTime = false; // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
|
||||
|
|
||||
|
//- Bogenma�
|
||||
|
double brad = geoBreite / 180.0 * PI; |
||||
|
// - H�he Sonne -50 Bogenmin.
|
||||
|
double h0 = -50.0 / 60.0 / 180.0 * PI; |
||||
|
//- Deklination dek, Tag des Jahres d0
|
||||
|
int tage = 30 * month(date) - 30 + day(date); |
||||
|
double dek = 0.40954 * sin (0.0172 * (tage - 79.35)); |
||||
|
double zh1 = sin (h0) - sin (brad) * sin(dek); |
||||
|
double zh2 = cos(brad) * cos(dek); |
||||
|
double zd = 12*acos (zh1/zh2) / PI; |
||||
|
double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939); |
||||
|
//-Sonnenuntergang
|
||||
|
double tsu = 12 + zd - zgl; |
||||
|
double su = (tsu + (15.0 - geoLaenge) / 15.0); |
||||
|
int std = (int)su; |
||||
|
int minute = (int) ((su - std)*60); |
||||
|
if (isSummerTime) std++; |
||||
|
SunDown = (100*std + minute) * 100; |
||||
|
|
||||
|
//- Sonnenaufgang
|
||||
|
double tsa = 12 - zd - zgl; |
||||
|
double sa = (tsa + (15.0 - geoLaenge) /15.0); |
||||
|
std = (int) sa; |
||||
|
minute = (int) ((sa - std)*60); |
||||
|
if (isSummerTime) std++; |
||||
|
SunUp = (100*std + minute) * 100; |
||||
|
DEBUG_OUT.print("Sonnenaufgang :"); DEBUG_OUT.println(SunUp); |
||||
|
DEBUG_OUT.print("Sonnenuntergang:"); DEBUG_OUT.println(SunDown); |
||||
|
} |
||||
|
|
||||
|
boolean isDayTime() { |
||||
|
//-----------------
|
||||
|
// 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
|
||||
|
const int offset=60*15; |
||||
|
time_t no = getNow(); |
||||
|
long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100; |
||||
|
long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100; |
||||
|
|
||||
|
return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown)); |
||||
|
} |
||||
|
|
||||
|
#endif |
@ -0,0 +1,142 @@ |
|||||
|
|
||||
|
#include <stdio.h> |
||||
|
#include <stdint.h> |
||||
|
#include "hm_crc.h" |
||||
|
//#define OUTPUT_DEBUG_INFO
|
||||
|
|
||||
|
/* Table of CRC values for high-order byte */ |
||||
|
static const uint8_t auchCRCHi[] = { |
||||
|
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, |
||||
|
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, |
||||
|
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, |
||||
|
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, |
||||
|
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, |
||||
|
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, |
||||
|
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, |
||||
|
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, |
||||
|
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, |
||||
|
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, |
||||
|
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, |
||||
|
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, |
||||
|
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, |
||||
|
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, |
||||
|
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, |
||||
|
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, |
||||
|
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, |
||||
|
0x40}; |
||||
|
|
||||
|
/* Table of CRC values for low-order byte */ |
||||
|
static const uint8_t auchCRCLo[] = { |
||||
|
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, |
||||
|
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, |
||||
|
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, |
||||
|
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, |
||||
|
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, |
||||
|
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, |
||||
|
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, |
||||
|
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, |
||||
|
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, |
||||
|
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, |
||||
|
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, |
||||
|
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, |
||||
|
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, |
||||
|
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, |
||||
|
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, |
||||
|
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, |
||||
|
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, |
||||
|
0x40}; |
||||
|
|
||||
|
uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen) |
||||
|
{ |
||||
|
uint8_t uchCRCHi = 0xFF; /* high byte of CRC initialized */ |
||||
|
uint8_t uchCRCLo = 0xFF; /* low byte of CRC initialized */ |
||||
|
uint16_t uIndex; /* will index into CRC lookup table */ |
||||
|
while (usDataLen--) /* pass through message buffer */ |
||||
|
{ |
||||
|
uIndex = uchCRCLo ^ *puchMsg++; /* calculate the CRC */ |
||||
|
uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex]; |
||||
|
uchCRCHi = auchCRCLo[uIndex]; |
||||
|
} |
||||
|
return (uchCRCHi << 8 | uchCRCLo); |
||||
|
} |
||||
|
|
||||
|
// Hoymiles CRC8 calculation with poly 0x01, Initial value 0x00 and final XOR 0x00
|
||||
|
uint8_t crc8(uint8_t *buf, const uint16_t bufLen) |
||||
|
{ |
||||
|
uint32_t crc; |
||||
|
uint16_t i, bit; |
||||
|
|
||||
|
crc = 0x00; |
||||
|
for (i = 0; i < bufLen; i++) |
||||
|
{ |
||||
|
crc ^= buf[i]; |
||||
|
for (bit = 0; bit < 8; bit++) |
||||
|
{ |
||||
|
if ((crc & 0x80) != 0) |
||||
|
{ |
||||
|
crc <<= 1; |
||||
|
crc ^= 0x01; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
crc <<= 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return (crc & 0xFF); |
||||
|
} |
||||
|
|
||||
|
// NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
|
uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits) |
||||
|
{ |
||||
|
uint16_t crc = startCRC; |
||||
|
if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen))) |
||||
|
{ |
||||
|
// The length of the data might not be a multiple of full bytes.
|
||||
|
// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
|
||||
|
// calculate the CRC.
|
||||
|
uint16_t data; |
||||
|
uint8_t byte, shift; |
||||
|
uint16_t bitoffs = startBit; |
||||
|
|
||||
|
// Get a new byte for the next 8 bits.
|
||||
|
byte = buf[bitoffs >> 3]; |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf("\nStart CRC %04X, %u bits:", startCRC, len_bits); |
||||
|
printf("\nbyte %02X:", byte); |
||||
|
#endif |
||||
|
while (bitoffs < len_bits + startBit) |
||||
|
{ |
||||
|
shift = bitoffs & 7; |
||||
|
// Shift the active bit to the position of bit 15
|
||||
|
data = ((uint16_t)byte) << (8 + shift); |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf(" bit %u %u,", shift, data & 0x8000 ? 1 : 0); |
||||
|
#endif |
||||
|
// Assure all other bits are 0
|
||||
|
data &= 0x8000; |
||||
|
crc ^= data; |
||||
|
if (crc & 0x8000) |
||||
|
{ |
||||
|
crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
crc = (crc << 1); |
||||
|
} |
||||
|
++bitoffs; |
||||
|
if (0 == (bitoffs & 7)) |
||||
|
{ |
||||
|
// Get a new byte for the next 8 bits.
|
||||
|
byte = buf[bitoffs >> 3]; |
||||
|
#ifdef OUTPUT_DEBUG_INFO |
||||
|
printf("crc %04X:", crc); |
||||
|
if (bitoffs < len_bits + startBit) |
||||
|
printf("\nbyte %02X:", byte); |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return crc; |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
|
||||
|
|
||||
|
#define BITS_TO_BYTES(x) (((x)+7)>>3) |
||||
|
#define BYTES_TO_BITS(x) ((x)<<3) |
||||
|
|
||||
|
extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen); |
||||
|
extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen); |
||||
|
extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits); |
@ -0,0 +1,74 @@ |
|||||
|
#include "Arduino.h" |
||||
|
|
||||
|
#include "hm_crc.h" |
||||
|
#include "hm_packets.h" |
||||
|
|
||||
|
void HM_Packets::SetUnixTimeStamp(uint32_t ts) |
||||
|
{ |
||||
|
unixTimeStamp = ts; |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::UnixTimeStampTick() |
||||
|
{ |
||||
|
unixTimeStamp++; |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::prepareBuffer(uint8_t *buf) |
||||
|
{ |
||||
|
// minimal buffer size of 32 bytes is assumed
|
||||
|
memset(buf, 0x00, 32); |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val) |
||||
|
{ |
||||
|
buf[0]= (uint8_t)(val >> 24); |
||||
|
buf[1]= (uint8_t)(val >> 16); |
||||
|
buf[2]= (uint8_t)(val >> 8); |
||||
|
buf[3]= (uint8_t)(val & 0xFF); |
||||
|
} |
||||
|
|
||||
|
void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val) |
||||
|
{ |
||||
|
memcpy(buf, &val, sizeof(uint32_t)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr) |
||||
|
{ |
||||
|
prepareBuffer(buf); |
||||
|
|
||||
|
buf[0] = 0x15; |
||||
|
copyToBufferBE(&buf[1], wrAdr); |
||||
|
copyToBufferBE(&buf[5], dtuAdr); |
||||
|
buf[9] = 0x80; |
||||
|
|
||||
|
buf[10] = 0x0B; // cid
|
||||
|
buf[11] = 0x00; |
||||
|
|
||||
|
copyToBuffer(&buf[12], unixTimeStamp); |
||||
|
|
||||
|
buf[19] = 0x05; |
||||
|
|
||||
|
// CRC16
|
||||
|
uint16_t crc16 = crc16_modbus(&buf[10], 14); |
||||
|
buf[24] = crc16 >> 8; |
||||
|
buf[25] = crc16 & 0xFF; |
||||
|
|
||||
|
// crc8
|
||||
|
buf[26] = crc8(&buf[0], 26); |
||||
|
|
||||
|
return 27; |
||||
|
} |
||||
|
|
||||
|
int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd) |
||||
|
{ |
||||
|
buf[0] = mid; |
||||
|
copyToBufferBE(&buf[1], wrAdr); |
||||
|
copyToBufferBE(&buf[5], dtuAdr); |
||||
|
buf[9] = cmd; |
||||
|
|
||||
|
// crc8
|
||||
|
buf[10] = crc8(&buf[0], 10); |
||||
|
|
||||
|
return 11; |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
|
||||
|
|
||||
|
class HM_Packets |
||||
|
{ |
||||
|
private: |
||||
|
uint32_t unixTimeStamp; |
||||
|
|
||||
|
void prepareBuffer(uint8_t *buf); |
||||
|
void copyToBuffer(uint8_t *buf, uint32_t val); |
||||
|
void copyToBufferBE(uint8_t *buf, uint32_t val); |
||||
|
|
||||
|
public: |
||||
|
void SetUnixTimeStamp(uint32_t ts); |
||||
|
void UnixTimeStampTick(); |
||||
|
|
||||
|
int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr); |
||||
|
int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd); |
||||
|
}; |
@ -0,0 +1,345 @@ |
|||||
|
#ifndef __WIFI_H |
||||
|
#define __WIFI_H |
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include "Debug.h" |
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <Pinger.h> // von url=https://www.technologytourist.com |
||||
|
|
||||
|
String SSID = ""; // bestes WLan
|
||||
|
|
||||
|
// Prototypes
|
||||
|
time_t getNow (); |
||||
|
boolean setupWifi (); |
||||
|
boolean checkWifi(); |
||||
|
|
||||
|
|
||||
|
String findWifi () { |
||||
|
//----------------
|
||||
|
String ssid; |
||||
|
int32_t rssi; |
||||
|
uint8_t encryptionType; |
||||
|
uint8_t* bssid; |
||||
|
int32_t channel; |
||||
|
bool hidden; |
||||
|
int scanResult; |
||||
|
|
||||
|
String best_ssid = ""; |
||||
|
int32_t best_rssi = -100; |
||||
|
|
||||
|
DEBUG_OUT.println(F("Starting WiFi scan...")); |
||||
|
|
||||
|
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true); |
||||
|
|
||||
|
if (scanResult == 0) { |
||||
|
DEBUG_OUT.println(F("keine WLans")); |
||||
|
} else if (scanResult > 0) { |
||||
|
DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult); |
||||
|
|
||||
|
// Print unsorted scan results
|
||||
|
for (int8_t i = 0; i < scanResult; i++) { |
||||
|
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden); |
||||
|
|
||||
|
DEBUG_OUT.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"), |
||||
|
i, |
||||
|
channel, |
||||
|
bssid[0], bssid[1], bssid[2], |
||||
|
bssid[3], bssid[4], bssid[5], |
||||
|
rssi, |
||||
|
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*', |
||||
|
hidden ? 'H' : 'V', |
||||
|
ssid.c_str()); |
||||
|
delay(1); |
||||
|
boolean check; |
||||
|
#ifdef SSID_PREFIX1 |
||||
|
check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1); |
||||
|
#else |
||||
|
check = true; |
||||
|
#endif |
||||
|
#ifdef SSID_PREFIX2 |
||||
|
check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2); |
||||
|
#endif |
||||
|
if (check) { |
||||
|
if (rssi > best_rssi) { |
||||
|
best_rssi = rssi; |
||||
|
best_ssid = ssid; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult); |
||||
|
} |
||||
|
|
||||
|
if (! best_ssid.equals("")) { |
||||
|
SSID = best_ssid; |
||||
|
DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str()); |
||||
|
return SSID; |
||||
|
} |
||||
|
else |
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
void IP2string (IPAddress IP, char * buf) { |
||||
|
sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]); |
||||
|
} |
||||
|
|
||||
|
void connectWifi() { |
||||
|
//------------------
|
||||
|
// if (SSID.equals(""))
|
||||
|
String s = findWifi(); |
||||
|
|
||||
|
if (!SSID.equals("")) { |
||||
|
DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID); |
||||
|
//while (WiFi.status() != WL_CONNECTED) {
|
||||
|
WiFi.begin (SSID, SSID_PASSWORD); |
||||
|
int versuche = 20; |
||||
|
while (WiFi.status() != WL_CONNECTED && versuche > 0) { |
||||
|
delay(1000); |
||||
|
versuche--; |
||||
|
DEBUG_OUT.print(versuche); DEBUG_OUT.print(' '); |
||||
|
} |
||||
|
//}
|
||||
|
if (WiFi.status() == WL_CONNECTED) { |
||||
|
char buffer[30]; |
||||
|
IP2string (WiFi.localIP(), buffer); |
||||
|
String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer); |
||||
|
DEBUG_OUT.println (out); |
||||
|
} |
||||
|
else |
||||
|
DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
boolean setupWifi () { |
||||
|
//------------------
|
||||
|
int count=5; |
||||
|
while (count-- && WiFi.status() != WL_CONNECTED) |
||||
|
connectWifi(); |
||||
|
return (WiFi.status() == WL_CONNECTED); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Pinger pinger; |
||||
|
IPAddress ROUTER = IPAddress(192,168,1,1); |
||||
|
|
||||
|
boolean checkWifi() { |
||||
|
//---------------
|
||||
|
boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER); |
||||
|
if (NotConnected) { |
||||
|
setupWifi(); |
||||
|
if (WiFi.status() == WL_CONNECTED) |
||||
|
getNow(); |
||||
|
} |
||||
|
return (WiFi.status() == WL_CONNECTED); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
// ################ Clock #################
|
||||
|
|
||||
|
#include <WiFiUdp.h> |
||||
|
#include <TimeLib.h> |
||||
|
|
||||
|
IPAddress timeServer; |
||||
|
unsigned int localPort = 8888; |
||||
|
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
|
||||
|
byte packetBuf[NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets
|
||||
|
const int timeZone = 1; // Central European Time = +1
|
||||
|
long SYNCINTERVALL = 0; |
||||
|
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
|
||||
|
|
||||
|
// prototypes
|
||||
|
time_t getNtpTime (); |
||||
|
void sendNTPpacket (IPAddress &address); |
||||
|
time_t getNow (); |
||||
|
char* getDateTimeStr (time_t no = getNow()); |
||||
|
time_t offsetDayLightSaving (uint32_t local_t); |
||||
|
bool isDayofDaylightChange (time_t local_t); |
||||
|
|
||||
|
|
||||
|
void _setSyncInterval (long intervall) { |
||||
|
//----------------------------------------
|
||||
|
SYNCINTERVALL = intervall; |
||||
|
setSyncInterval (intervall); |
||||
|
} |
||||
|
|
||||
|
void setupClock() { |
||||
|
//-----------------
|
||||
|
WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
|
||||
|
|
||||
|
Udp.begin(localPort); |
||||
|
|
||||
|
getNtpTime(); |
||||
|
|
||||
|
setSyncProvider (getNtpTime); |
||||
|
while(timeStatus()== timeNotSet) |
||||
|
delay(1); //
|
||||
|
|
||||
|
_setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
|
||||
|
|
||||
|
//lastClock = now();
|
||||
|
//Serial.print("[NTP] get time from NTP server ");
|
||||
|
getNow(); |
||||
|
//char buf[20];
|
||||
|
DEBUG_OUT.print ("[NTP] get time from NTP server "); |
||||
|
DEBUG_OUT.print (timeServer); |
||||
|
//sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
|
DEBUG_OUT.print (": got "); |
||||
|
DEBUG_OUT.println (getDateTimeStr()); |
||||
|
} |
||||
|
|
||||
|
//*-------- NTP code ----------*/
|
||||
|
|
||||
|
|
||||
|
time_t getNtpTime() { |
||||
|
//-------------------
|
||||
|
sendNTPpacket(timeServer); // send an NTP packet to a time server
|
||||
|
//uint32_t beginWait = millis();
|
||||
|
//while (millis() - beginWait < 1500) {
|
||||
|
int versuch = 0; |
||||
|
while (versuch < 5) { |
||||
|
int wait = 150; // results in max 1500 ms waitTime
|
||||
|
while (wait--) { |
||||
|
int size = Udp.parsePacket(); |
||||
|
if (size >= NTP_PACKET_SIZE) { |
||||
|
//Serial.println("Receive NTP Response");
|
||||
|
Udp.read(packetBuf, NTP_PACKET_SIZE); // read packet into the buffer
|
||||
|
unsigned long secsSince1900; |
||||
|
// convert four bytes starting at location 40 to a long integer
|
||||
|
secsSince1900 = (unsigned long)packetBuf[40] << 24; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[41] << 16; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[42] << 8; |
||||
|
secsSince1900 |= (unsigned long)packetBuf[43]; |
||||
|
// time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
|
||||
|
|
||||
|
time_t utc = secsSince1900 - 2208988800UL; |
||||
|
time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR; |
||||
|
|
||||
|
if (isDayofDaylightChange (utc) && hour(utc) <= 4) |
||||
|
_setSyncInterval (SECS_PER_HOUR); |
||||
|
else |
||||
|
_setSyncInterval (SECS_PER_DAY / 2); |
||||
|
|
||||
|
return now; |
||||
|
} |
||||
|
else |
||||
|
delay(10); |
||||
|
} |
||||
|
versuch++; |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// send an NTP request to the time server at the given address
|
||||
|
void sendNTPpacket(IPAddress& address) { |
||||
|
//------------------------------------
|
||||
|
memset(packetBuf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
|
||||
|
// Initialize values needed to form NTP request
|
||||
|
packetBuf[0] = B11100011; // LI, Version, Mode
|
||||
|
packetBuf[1] = 0; // Stratum
|
||||
|
packetBuf[2] = 6; // Max Interval between messages in seconds
|
||||
|
packetBuf[3] = 0xEC; // Clock Precision
|
||||
|
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
|
packetBuf[12] = 49; // four-byte reference ID identifying
|
||||
|
packetBuf[13] = 0x4E; |
||||
|
packetBuf[14] = 49; |
||||
|
packetBuf[15] = 52; |
||||
|
// send the packet requesting a timestamp:
|
||||
|
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
||||
|
Udp.write(packetBuf,NTP_PACKET_SIZE); |
||||
|
Udp.endPacket(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
int getTimeTrials = 0; |
||||
|
|
||||
|
bool isValidDateTime (time_t no) { |
||||
|
return (year(no) > 2020 && year(no) < 2038); |
||||
|
} |
||||
|
|
||||
|
bool isDayofDaylightChange (time_t local_t) { |
||||
|
//-----------------------------------------
|
||||
|
int jahr = year (local_t); |
||||
|
int monat = month (local_t); |
||||
|
int tag = day (local_t); |
||||
|
bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) || |
||||
|
(monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7))); |
||||
|
DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
|
||||
|
// übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
|
time_t offsetDayLightSaving (uint32_t local_t) { |
||||
|
//--------------------------------------------
|
||||
|
int monat = month (local_t); |
||||
|
if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
|
if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
|
int jahr = year (local_t); |
||||
|
int std = hour (local_t); |
||||
|
//int tag = day (local_t);
|
||||
|
int stundenBisHeute = (std + 24 * day(local_t)); |
||||
|
if ( (monat == 3 && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) || |
||||
|
(monat == 10 && stundenBisHeute < (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) ) |
||||
|
return 1; |
||||
|
else |
||||
|
return 0; |
||||
|
/*
|
||||
|
int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7)); |
||||
|
if (monat == 3 && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute < stundenBisWechsel) |
||||
|
return 1; |
||||
|
else |
||||
|
return 0; |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
time_t getNow () { |
||||
|
//---------------
|
||||
|
time_t jetzt = now(); |
||||
|
while (!isValidDateTime(jetzt) && getTimeTrials < 10) { // ungültig, max 10x probieren
|
||||
|
if (getTimeTrials) { |
||||
|
//Serial.print (getTimeTrials);
|
||||
|
//Serial.println(". Versuch für getNtpTime");
|
||||
|
} |
||||
|
jetzt = getNtpTime (); |
||||
|
if (isValidDateTime(jetzt)) { |
||||
|
setTime (jetzt); |
||||
|
getTimeTrials = 0; |
||||
|
} |
||||
|
else |
||||
|
getTimeTrials++; |
||||
|
} |
||||
|
//return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
|
||||
|
return jetzt; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
char _timestr[24]; |
||||
|
|
||||
|
char* getNowStr (time_t no = getNow()) { |
||||
|
//------------------------------------
|
||||
|
sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
char* getTimeStr (time_t no = getNow()) { |
||||
|
//------------------------------------
|
||||
|
return getNowStr (no); |
||||
|
} |
||||
|
|
||||
|
char* getDateTimeStr (time_t no) { |
||||
|
//------------------------------
|
||||
|
sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
char* getDateStr (time_t no) { |
||||
|
//------------------------------
|
||||
|
sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no)); |
||||
|
return _timestr; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#endif |
@ -0,0 +1,157 @@ |
|||||
|
/*
|
||||
|
CircularBuffer - An Arduino circular buffering library for arbitrary types. |
||||
|
|
||||
|
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl |
||||
|
|
||||
|
This library is free software; you can redistribute it and/or |
||||
|
modify it under the terms of the GNU Lesser General Public |
||||
|
License as published by the Free Software Foundation; either |
||||
|
version 2.1 of the License, or (at your option) any later version. |
||||
|
|
||||
|
This library is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
|
Lesser General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU Lesser General Public |
||||
|
License along with this library; if not, write to the Free Software |
||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
||||
|
*/ |
||||
|
|
||||
|
#ifndef CircularBuffer_h |
||||
|
#define CircularBuffer_h |
||||
|
|
||||
|
#ifdef ESP8266 |
||||
|
#define DISABLE_IRQ noInterrupts() |
||||
|
#define RESTORE_IRQ interrupts() |
||||
|
#else |
||||
|
#define DISABLE_IRQ \ |
||||
|
uint8_t sreg = SREG; \ |
||||
|
cli(); |
||||
|
|
||||
|
#define RESTORE_IRQ \ |
||||
|
SREG = sreg; |
||||
|
#endif |
||||
|
|
||||
|
template <class BUFFERTYPE, uint8_t BUFFERSIZE> |
||||
|
class CircularBuffer { |
||||
|
|
||||
|
typedef BUFFERTYPE BufferType; |
||||
|
BufferType Buffer[BUFFERSIZE]; |
||||
|
|
||||
|
public: |
||||
|
CircularBuffer() : m_buff(Buffer) { |
||||
|
m_size = BUFFERSIZE; |
||||
|
clear(); |
||||
|
} |
||||
|
|
||||
|
/** Clear all entries in the circular buffer. */ |
||||
|
void clear(void) |
||||
|
{ |
||||
|
m_front = 0; |
||||
|
m_fill = 0; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is empty */ |
||||
|
inline bool empty(void) const |
||||
|
{ |
||||
|
return !m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Return the number of records stored in the buffer */ |
||||
|
inline uint8_t available(void) const |
||||
|
{ |
||||
|
return m_fill; |
||||
|
} |
||||
|
|
||||
|
/** Test if the circular buffer is full */ |
||||
|
inline bool full(void) const |
||||
|
{ |
||||
|
return m_fill == m_size; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on front of the buffer, for writing.
|
||||
|
* After filling the record, it has to be pushed to actually |
||||
|
* add it to the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is full. |
||||
|
*/ |
||||
|
BUFFERTYPE* getFront(void) const |
||||
|
{ |
||||
|
DISABLE_IRQ; |
||||
|
BUFFERTYPE* f = NULL; |
||||
|
if (!full()) |
||||
|
f = get(m_front); |
||||
|
RESTORE_IRQ; |
||||
|
return f; |
||||
|
} |
||||
|
|
||||
|
/** Push record to front of the buffer
|
||||
|
* @param record Record to push. If record was aquired previously (using getFront) its |
||||
|
* data will not be copied as it is already present in the buffer. |
||||
|
* @return True, when record was pushed successfully. |
||||
|
*/ |
||||
|
bool pushFront(BUFFERTYPE* record) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!full()) |
||||
|
{ |
||||
|
BUFFERTYPE* f = get(m_front); |
||||
|
if (f != record) |
||||
|
*f = *record; |
||||
|
m_front = (m_front+1) % m_size; |
||||
|
m_fill++; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
/** Aquire record on back of the buffer, for reading.
|
||||
|
* After reading the record, it has to be pop'ed to actually |
||||
|
* remove it from the buffer. |
||||
|
* @return Pointer to record, or NULL when buffer is empty. |
||||
|
*/ |
||||
|
BUFFERTYPE* getBack(void) const |
||||
|
{ |
||||
|
BUFFERTYPE* b = NULL; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
b = get(back()); |
||||
|
RESTORE_IRQ; |
||||
|
return b; |
||||
|
} |
||||
|
|
||||
|
/** Remove record from back of the buffer.
|
||||
|
* @return True, when record was pop'ed successfully. |
||||
|
*/ |
||||
|
bool popBack(void) |
||||
|
{ |
||||
|
bool ok = false; |
||||
|
DISABLE_IRQ; |
||||
|
if (!empty()) |
||||
|
{ |
||||
|
m_fill--; |
||||
|
ok = true; |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
inline BUFFERTYPE * get(const uint8_t idx) const |
||||
|
{ |
||||
|
return &(m_buff[idx]); |
||||
|
} |
||||
|
inline uint8_t back(void) const |
||||
|
{ |
||||
|
return (m_front - m_fill + m_size) % m_size; |
||||
|
} |
||||
|
|
||||
|
uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
|
BUFFERTYPE* const m_buff; |
||||
|
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
|
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
|
}; |
||||
|
|
||||
|
#endif // CircularBuffer_h
|
@ -0,0 +1,60 @@ |
|||||
|
## OVERVIEW |
||||
|
|
||||
|
This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: <https://www.mikrocontroller.net/topic/525778?page=3#7033371> |
||||
|
|
||||
|
The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: CS, CE and IRQ |
||||
|
These pins can be changed from the /setup URL |
||||
|
|
||||
|
|
||||
|
## Compile |
||||
|
|
||||
|
This code can be compiled using Arduino. The settings were: |
||||
|
|
||||
|
- Board: Generic ESP8266 Module |
||||
|
- Flash-Size: 1MB (FS: none, OTA: 502kB) |
||||
|
|
||||
|
### Optional Configuration before compilation |
||||
|
|
||||
|
- number of supported inverters (set to 3 by default) `defines.h` |
||||
|
- enable channel hopping `hmRadio.h` |
||||
|
- DTU radio id `hmRadio.h` |
||||
|
- unformated list in webbrowser `/livedata` `defines.h`, `LIVEDATA_VISUALIZED` |
||||
|
|
||||
|
|
||||
|
## Flash ESP with firmware |
||||
|
|
||||
|
1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities |
||||
|
2. repower the ESP |
||||
|
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom |
||||
|
4. connect to the AP, you will be forwarded to the setup page |
||||
|
5. configure your WiFi settings, save, repower |
||||
|
6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. |
||||
|
|
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities: |
||||
|
|
||||
|
- OTA Update (over the air update) |
||||
|
- Configuration (Wifi, inverter(s), Pinout, MQTT) |
||||
|
- visual display of the connected inverters / modules |
||||
|
- some statistics about communication (debug) |
||||
|
|
||||
|
The serial console will print the converted values which were read out of the inverter(s) |
||||
|
|
||||
|
|
||||
|
## Compatiblity |
||||
|
|
||||
|
For now the following inverters should work out of the box: |
||||
|
|
||||
|
- HM400 |
||||
|
- HM600 |
||||
|
- HM800 |
||||
|
- HM1200 |
||||
|
|
||||
|
## USED LIBRARIES |
||||
|
|
||||
|
- `Time` |
||||
|
- `RF24` |
||||
|
- `PubSubClient` |
||||
|
|
@ -0,0 +1,611 @@ |
|||||
|
#include "app.h" |
||||
|
|
||||
|
#include "html/h/index_html.h" |
||||
|
#include "html/h/setup_html.h" |
||||
|
#include "html/h/hoymiles_html.h" |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
app::app() : Main() { |
||||
|
mSendTicker = 0xffff; |
||||
|
mSendInterval = 0; |
||||
|
mMqttTicker = 0xffff; |
||||
|
mMqttInterval = 0; |
||||
|
mSerialTicker = 0xffff; |
||||
|
mSerialInterval = 0; |
||||
|
mMqttActive = false; |
||||
|
|
||||
|
mTicker = 0; |
||||
|
|
||||
|
mShowRebootRequest = false; |
||||
|
|
||||
|
mSerialValues = true; |
||||
|
mSerialDebug = false; |
||||
|
|
||||
|
memset(mCmds, 0, sizeof(uint32_t)*DBG_CMD_LIST_LEN); |
||||
|
//memset(mChannelStat, 0, sizeof(uint32_t) * 4);
|
||||
|
|
||||
|
mSys = new HmSystemType(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
app::~app(void) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::setup(uint32_t timeout) { |
||||
|
Main::setup(timeout); |
||||
|
|
||||
|
mWeb->on("/", std::bind(&app::showIndex, this)); |
||||
|
mWeb->on("/setup", std::bind(&app::showSetup, this)); |
||||
|
mWeb->on("/save", std::bind(&app::showSave, this)); |
||||
|
mWeb->on("/erase", std::bind(&app::showErase, this)); |
||||
|
mWeb->on("/cmdstat", std::bind(&app::showStatistics, this)); |
||||
|
mWeb->on("/hoymiles", std::bind(&app::showHoymiles, this)); |
||||
|
mWeb->on("/livedata", std::bind(&app::showLiveData, this)); |
||||
|
|
||||
|
if(mSettingsValid) { |
||||
|
uint64_t invSerial; |
||||
|
char invName[MAX_NAME_LENGTH + 1] = {0}; |
||||
|
uint8_t invType; |
||||
|
|
||||
|
// inverter
|
||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
||||
|
mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); |
||||
|
mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); |
||||
|
mEep->read(ADDR_INV_TYPE + i, &invType); |
||||
|
if(0ULL != invSerial) { |
||||
|
mSys->addInverter(invName, invSerial, invType); |
||||
|
DPRINTLN("add inverter: " + String(invName) + ", SN: " + String(invSerial, HEX) + ", type: " + String(invType)); |
||||
|
} |
||||
|
} |
||||
|
mEep->read(ADDR_INV_INTERVAL, &mSendInterval); |
||||
|
if(mSendInterval < 1) |
||||
|
mSendInterval = 1; |
||||
|
|
||||
|
|
||||
|
// pinout
|
||||
|
mEep->read(ADDR_PINOUT, &mSys->Radio.pinCs); |
||||
|
mEep->read(ADDR_PINOUT+1, &mSys->Radio.pinCe); |
||||
|
mEep->read(ADDR_PINOUT+2, &mSys->Radio.pinIrq); |
||||
|
|
||||
|
|
||||
|
// nrf24 amplifier power
|
||||
|
mEep->read(ADDR_RF24_AMP_PWR, &mSys->Radio.AmplifierPower); |
||||
|
|
||||
|
// serial console
|
||||
|
uint8_t tmp; |
||||
|
mEep->read(ADDR_SER_INTERVAL, &mSerialInterval); |
||||
|
mEep->read(ADDR_SER_ENABLE, &tmp); |
||||
|
mSerialValues = (tmp == 0x01); |
||||
|
mEep->read(ADDR_SER_DEBUG, &tmp); |
||||
|
mSerialDebug = (tmp == 0x01); |
||||
|
if(mSerialInterval < 1) |
||||
|
mSerialInterval = 1; |
||||
|
|
||||
|
|
||||
|
// mqtt
|
||||
|
uint8_t mqttAddr[MQTT_ADDR_LEN]; |
||||
|
uint16_t mqttPort; |
||||
|
char mqttUser[MQTT_USER_LEN]; |
||||
|
char mqttPwd[MQTT_PWD_LEN]; |
||||
|
char mqttTopic[MQTT_TOPIC_LEN]; |
||||
|
mEep->read(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); |
||||
|
mEep->read(ADDR_MQTT_USER, mqttUser, MQTT_USER_LEN); |
||||
|
mEep->read(ADDR_MQTT_PWD, mqttPwd, MQTT_PWD_LEN); |
||||
|
mEep->read(ADDR_MQTT_TOPIC, mqttTopic, MQTT_TOPIC_LEN); |
||||
|
mEep->read(ADDR_MQTT_INTERVAL, &mMqttInterval); |
||||
|
mEep->read(ADDR_MQTT_PORT, &mqttPort); |
||||
|
|
||||
|
char addr[16] = {0}; |
||||
|
sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); |
||||
|
mMqttActive = (mqttAddr[0] > 0); |
||||
|
|
||||
|
|
||||
|
if(mMqttInterval < 1) |
||||
|
mMqttInterval = 1; |
||||
|
mMqtt.setup(addr, mqttTopic, mqttUser, mqttPwd, mqttPort); |
||||
|
mMqttTicker = 0; |
||||
|
|
||||
|
mSerialTicker = 0; |
||||
|
|
||||
|
mMqtt.sendMsg("version", mVersion); |
||||
|
} |
||||
|
|
||||
|
mSys->setup(); |
||||
|
|
||||
|
if(!mWifiSettingsValid) |
||||
|
DPRINTLN("Warn: your settings are not valid! check [IP]/setup"); |
||||
|
else { |
||||
|
DPRINTLN("\n\n----------------------------------------"); |
||||
|
DPRINTLN("Welcome to AHOY!"); |
||||
|
DPRINT("\npoint your browser to http://"); |
||||
|
DPRINTLN(WiFi.localIP()); |
||||
|
DPRINTLN("to configure your device"); |
||||
|
DPRINTLN("----------------------------------------\n"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::loop(void) { |
||||
|
Main::loop(); |
||||
|
|
||||
|
if(!mSys->BufCtrl.empty()) { |
||||
|
uint8_t len, rptCnt; |
||||
|
packet_t *p = mSys->BufCtrl.getBack(); |
||||
|
if(mSerialDebug) |
||||
|
mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); |
||||
|
|
||||
|
if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) { |
||||
|
// process buffer only on first occurrence
|
||||
|
if((0 != len) && (0 == rptCnt)) { |
||||
|
uint8_t *cmd = &p->packet[11]; |
||||
|
//DPRINTLN("CMD " + String(*cmd, HEX));
|
||||
|
//mSys->Radio.dumpBuf("Payload ", p->packet, len);
|
||||
|
|
||||
|
Inverter<> *iv = mSys->findInverter(&p->packet[3]); |
||||
|
if(NULL != iv) { |
||||
|
for(uint8_t i = 0; i < iv->listLen; i++) { |
||||
|
if(iv->assign[i].cmdId == *cmd) |
||||
|
iv->addValue(i, &p->packet[11]); |
||||
|
} |
||||
|
iv->doCalculations(); |
||||
|
} |
||||
|
|
||||
|
if(*cmd == 0x01) mCmds[0]++; |
||||
|
else if(*cmd == 0x02) mCmds[1]++; |
||||
|
else if(*cmd == 0x03) mCmds[2]++; |
||||
|
else if(*cmd == 0x81) mCmds[3]++; |
||||
|
else if(*cmd == 0x82) mCmds[4]++; |
||||
|
else if(*cmd == 0x83) mCmds[5]++; |
||||
|
else if(*cmd == 0x84) mCmds[6]++; |
||||
|
else mCmds[7]++; |
||||
|
|
||||
|
/*if(p->sendCh == 23) mChannelStat[0]++;
|
||||
|
else if(p->sendCh == 40) mChannelStat[1]++; |
||||
|
else if(p->sendCh == 61) mChannelStat[2]++; |
||||
|
else mChannelStat[3]++;*/ |
||||
|
} |
||||
|
} |
||||
|
mSys->BufCtrl.popBack(); |
||||
|
} |
||||
|
|
||||
|
if(checkTicker(&mTicker, 1000)) { |
||||
|
if(++mSendTicker >= mSendInterval) { |
||||
|
mSendTicker = 0; |
||||
|
|
||||
|
Inverter<> *inv; |
||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
||||
|
inv = mSys->getInverterByPos(i); |
||||
|
if(NULL != inv) { |
||||
|
mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp); |
||||
|
yield(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(mMqttActive) { |
||||
|
mMqtt.loop(); |
||||
|
if(++mMqttTicker > mMqttInterval) { |
||||
|
mMqttInterval = 0; |
||||
|
mMqtt.isConnected(true); |
||||
|
char topic[30], val[10]; |
||||
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { |
||||
|
Inverter<> *iv = mSys->getInverterByPos(id); |
||||
|
if(NULL != iv) { |
||||
|
for(uint8_t i = 0; i < iv->listLen; i++) { |
||||
|
if(0.0f != iv->getValue(i)) { |
||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); |
||||
|
snprintf(val, 10, "%.3f", iv->getValue(i)); |
||||
|
mMqtt.sendMsg(topic, val); |
||||
|
yield(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(mSerialValues) { |
||||
|
if(++mSerialTicker > mSerialInterval) { |
||||
|
mSerialInterval = 0; |
||||
|
char topic[30], val[10]; |
||||
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { |
||||
|
Inverter<> *iv = mSys->getInverterByPos(id); |
||||
|
if(NULL != iv) { |
||||
|
for(uint8_t i = 0; i < iv->listLen; i++) { |
||||
|
if(0.0f != iv->getValue(i)) { |
||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); |
||||
|
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i)); |
||||
|
DPRINTLN(String(topic) + ": " + String(val)); |
||||
|
} |
||||
|
yield(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::handleIntr(void) { |
||||
|
mSys->Radio.handleIntr(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showIndex(void) { |
||||
|
String html = FPSTR(index_html); |
||||
|
html.replace("{DEVICE}", mDeviceName); |
||||
|
html.replace("{VERSION}", mVersion); |
||||
|
mWeb->send(200, "text/html", html); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showSetup(void) { |
||||
|
// overrides same method in main.cpp
|
||||
|
|
||||
|
uint16_t interval; |
||||
|
|
||||
|
String html = FPSTR(setup_html); |
||||
|
html.replace("{SSID}", mStationSsid); |
||||
|
// PWD will be left at the default value (for protection)
|
||||
|
// -> the PWD will only be changed if it does not match the placeholder "{PWD}"
|
||||
|
|
||||
|
html.replace("{DEVICE}", String(mDeviceName)); |
||||
|
html.replace("{VERSION}", String(mVersion)); |
||||
|
if(mApActive) |
||||
|
html.replace("{IP}", String("http://192.168.1.1")); |
||||
|
else |
||||
|
html.replace("{IP}", ("http://" + String(WiFi.localIP().toString()))); |
||||
|
|
||||
|
String inv; |
||||
|
uint64_t invSerial; |
||||
|
char invName[MAX_NAME_LENGTH + 1] = {0}; |
||||
|
uint8_t invType; |
||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
||||
|
mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); |
||||
|
mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); |
||||
|
mEep->read(ADDR_INV_TYPE + i, &invType); |
||||
|
inv += "<p class=\"subdes\">Inverter "+ String(i) + "</p>"; |
||||
|
|
||||
|
inv += "<label for=\"inv" + String(i) + "Addr\">Address</label>"; |
||||
|
inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Addr\" value=\""; |
||||
|
if(0ULL != invSerial) |
||||
|
inv += String(invSerial, HEX); |
||||
|
inv += "\"/ maxlength=\"12\">"; |
||||
|
|
||||
|
inv += "<label for=\"inv" + String(i) + "Name\">Name</label>"; |
||||
|
inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Name\" value=\""; |
||||
|
inv += String(invName); |
||||
|
inv += "\"/ maxlength=\"" + String(MAX_NAME_LENGTH) + "\">"; |
||||
|
|
||||
|
inv += "<label for=\"inv" + String(i) + "Type\">Type</label>"; |
||||
|
inv += "<select name=\"inv" + String(i) + "Type\">"; |
||||
|
for(uint8_t t = 0; t < NUM_INVERTER_TYPES; t++) { |
||||
|
inv += "<option value=\"" + String(t) + "\""; |
||||
|
if(invType == t) |
||||
|
inv += " selected"; |
||||
|
inv += ">" + String(invTypes[t]) + "</option>"; |
||||
|
} |
||||
|
inv += "</select>"; |
||||
|
} |
||||
|
html.replace("{INVERTERS}", String(inv)); |
||||
|
|
||||
|
|
||||
|
// pinout
|
||||
|
String pinout; |
||||
|
for(uint8_t i = 0; i < 3; i++) { |
||||
|
pinout += "<label for=\"" + String(pinArgNames[i]) + "\">" + String(pinNames[i]) + "</label>"; |
||||
|
pinout += "<select name=\"" + String(pinArgNames[i]) + "\">"; |
||||
|
for(uint8_t j = 0; j <= 16; j++) { |
||||
|
pinout += "<option value=\"" + String(j) + "\""; |
||||
|
switch(i) { |
||||
|
default: if(j == mSys->Radio.pinCs) pinout += " selected"; break; |
||||
|
case 1: if(j == mSys->Radio.pinCe) pinout += " selected"; break; |
||||
|
case 2: if(j == mSys->Radio.pinIrq) pinout += " selected"; break; |
||||
|
} |
||||
|
pinout += ">" + String(wemosPins[j]) + "</option>"; |
||||
|
} |
||||
|
pinout += "</select>"; |
||||
|
} |
||||
|
html.replace("{PINOUT}", String(pinout)); |
||||
|
|
||||
|
|
||||
|
// nrf24l01+
|
||||
|
String rf24; |
||||
|
for(uint8_t i = 0; i <= 3; i++) { |
||||
|
rf24 += "<option value=\"" + String(i) + "\""; |
||||
|
if(i == mSys->Radio.AmplifierPower) |
||||
|
rf24 += " selected"; |
||||
|
rf24 += ">" + String(rf24AmpPower[i]) + "</option>"; |
||||
|
} |
||||
|
html.replace("{RF24}", String(rf24)); |
||||
|
|
||||
|
|
||||
|
if(mSettingsValid) { |
||||
|
mEep->read(ADDR_INV_INTERVAL, &interval); |
||||
|
html.replace("{INV_INTVL}", String(interval)); |
||||
|
|
||||
|
uint8_t tmp; |
||||
|
mEep->read(ADDR_SER_INTERVAL, &interval); |
||||
|
mEep->read(ADDR_SER_ENABLE, &tmp); |
||||
|
html.replace("{SER_INTVL}", String(interval)); |
||||
|
html.replace("{SER_VAL_CB}", (tmp == 0x01) ? "checked" : ""); |
||||
|
mEep->read(ADDR_SER_DEBUG, &tmp); |
||||
|
html.replace("{SER_DBG_CB}", (tmp == 0x01) ? "checked" : ""); |
||||
|
|
||||
|
uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; |
||||
|
uint16_t mqttPort; |
||||
|
mEep->read(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); |
||||
|
mEep->read(ADDR_MQTT_INTERVAL, &interval); |
||||
|
mEep->read(ADDR_MQTT_PORT, &mqttPort); |
||||
|
|
||||
|
char addr[16] = {0}; |
||||
|
sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); |
||||
|
html.replace("{MQTT_ADDR}", String(addr)); |
||||
|
html.replace("{MQTT_PORT}", String(mqttPort)); |
||||
|
html.replace("{MQTT_USER}", String(mMqtt.getUser())); |
||||
|
html.replace("{MQTT_PWD}", String(mMqtt.getPwd())); |
||||
|
html.replace("{MQTT_TOPIC}", String(mMqtt.getTopic())); |
||||
|
html.replace("{MQTT_INTVL}", String(interval)); |
||||
|
} |
||||
|
else { |
||||
|
html.replace("{INV_INTVL}", "5"); |
||||
|
|
||||
|
html.replace("{SER_VAL_CB}", "checked"); |
||||
|
html.replace("{SER_DBG_CB}", ""); |
||||
|
html.replace("{SER_INTVL}", "10"); |
||||
|
|
||||
|
html.replace("{MQTT_ADDR}", ""); |
||||
|
html.replace("{MQTT_PORT}", "1883"); |
||||
|
html.replace("{MQTT_USER}", ""); |
||||
|
html.replace("{MQTT_PWD}", ""); |
||||
|
html.replace("{MQTT_TOPIC}", "/inverter"); |
||||
|
html.replace("{MQTT_INTVL}", "10"); |
||||
|
|
||||
|
html.replace("{SER_INTVL}", "10"); |
||||
|
} |
||||
|
|
||||
|
mWeb->send(200, "text/html", html); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showSave(void) { |
||||
|
saveValues(true); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showErase() { |
||||
|
eraseSettings(); |
||||
|
showReboot(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showStatistics(void) { |
||||
|
String content = "CMDs:\n"; |
||||
|
for(uint8_t i = 0; i < DBG_CMD_LIST_LEN; i ++) { |
||||
|
content += String("0x") + String(dbgCmds[i], HEX) + String(": ") + String(mCmds[i]) + String("\n"); |
||||
|
} |
||||
|
content += String("other: ") + String(mCmds[DBG_CMD_LIST_LEN]) + String("\n\n"); |
||||
|
|
||||
|
content += "Send Cnt: " + String(mSys->Radio.mSendCnt) + String("\n\n"); |
||||
|
|
||||
|
/*content += "\nCHANNELs:\n";
|
||||
|
content += String("23: ") + String(mChannelStat[0]) + String("\n"); |
||||
|
content += String("40: ") + String(mChannelStat[1]) + String("\n"); |
||||
|
content += String("61: ") + String(mChannelStat[2]) + String("\n"); |
||||
|
content += String("75: ") + String(mChannelStat[3]) + String("\n");*/ |
||||
|
|
||||
|
if(!mSys->Radio.isChipConnected()) |
||||
|
content += "WARNING! your NRF24 module can't be reached, check the wiring and pinout (<a href=\"/setup\">setup</a>)\n"; |
||||
|
|
||||
|
if(mShowRebootRequest) |
||||
|
content += "INFO: reboot your ESP to apply all your configuration changes!\n"; |
||||
|
|
||||
|
if(!mSettingsValid) |
||||
|
content += "INFO: your settings are invalid, please switch to <a href=\"/setup\">setup</a> to correct this.\n"; |
||||
|
|
||||
|
content += "MQTT: "; |
||||
|
if(!mMqtt.isConnected()) |
||||
|
content += "not "; |
||||
|
content += "connected\n"; |
||||
|
|
||||
|
mWeb->send(200, "text/plain", content); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showHoymiles(void) { |
||||
|
String html = FPSTR(hoymiles_html); |
||||
|
html.replace("{DEVICE}", mDeviceName); |
||||
|
html.replace("{VERSION}", mVersion); |
||||
|
mWeb->send(200, "text/html", html); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::showLiveData(void) { |
||||
|
String modHtml; |
||||
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { |
||||
|
Inverter<> *iv = mSys->getInverterByPos(id); |
||||
|
if(NULL != iv) { |
||||
|
#ifdef LIVEDATA_VISUALIZED |
||||
|
uint8_t modNum, pos; |
||||
|
switch(iv->type) { |
||||
|
default: modNum = 1; break; |
||||
|
case INV_TYPE_HM600: |
||||
|
case INV_TYPE_HM800: modNum = 2; break; |
||||
|
case INV_TYPE_HM1200: modNum = 4; break; |
||||
|
} |
||||
|
|
||||
|
modHtml += "<div class=\"iv\">"; |
||||
|
modHtml += "<div class=\"ch-iv\"><span class=\"head\">" + String(iv->name) + "</span>"; |
||||
|
uint8_t list[8] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD}; |
||||
|
|
||||
|
for(uint8_t fld = 0; fld < 8; fld++) { |
||||
|
pos = (iv->getPosByChFld(CH0, list[fld])); |
||||
|
if(0xff != pos) { |
||||
|
modHtml += "<div class=\"subgrp\">"; |
||||
|
modHtml += "<span class=\"value\">" + String(iv->getValue(pos)); |
||||
|
modHtml += "<span class=\"unit\">" + String(iv->getUnit(pos)) + "</span></span>"; |
||||
|
modHtml += "<span class=\"info\">" + String(iv->getFieldName(pos)) + "</span>"; |
||||
|
modHtml += "</div>"; |
||||
|
} |
||||
|
} |
||||
|
modHtml += "</div>"; |
||||
|
|
||||
|
for(uint8_t ch = 1; ch <= modNum; ch ++) { |
||||
|
modHtml += "<div class=\"ch\"><span class=\"head\">CHANNEL " + String(ch) + "</span>"; |
||||
|
for(uint8_t j = 0; j < 5; j++) { |
||||
|
switch(j) { |
||||
|
default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break; |
||||
|
case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break; |
||||
|
case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break; |
||||
|
case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break; |
||||
|
case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break; |
||||
|
} |
||||
|
if(0xff != pos) { |
||||
|
modHtml += "<span class=\"value\">" + String(iv->getValue(pos)); |
||||
|
modHtml += "<span class=\"unit\">" + String(iv->getUnit(pos)) + "</span></span>"; |
||||
|
modHtml += "<span class=\"info\">" + String(iv->getFieldName(pos)) + "</span>"; |
||||
|
} |
||||
|
} |
||||
|
modHtml += "</div>"; |
||||
|
} |
||||
|
|
||||
|
modHtml += "</div>"; |
||||
|
#else |
||||
|
// dump all data to web frontend
|
||||
|
modHtml = "<pre>"; |
||||
|
char topic[30], val[10]; |
||||
|
for(uint8_t i = 0; i < iv->listLen; i++) { |
||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); |
||||
|
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i)); |
||||
|
modHtml += String(topic) + ": " + String(val) + "\n"; |
||||
|
} |
||||
|
modHtml += "</pre>"; |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
mWeb->send(200, "text/html", modHtml); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::saveValues(bool webSend = true) { |
||||
|
Main::saveValues(false); // general configuration
|
||||
|
|
||||
|
if(mWeb->args() > 0) { |
||||
|
char *p; |
||||
|
char buf[20] = {0}; |
||||
|
uint8_t i = 0; |
||||
|
uint16_t interval; |
||||
|
|
||||
|
// inverter
|
||||
|
serial_u addr; |
||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
||||
|
// address
|
||||
|
mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); |
||||
|
if(strlen(buf) == 0) |
||||
|
snprintf(buf, 20, "\0"); |
||||
|
addr.u64 = Serial2u64(buf); |
||||
|
mEep->write(ADDR_INV_ADDR + (i * 8), addr.u64); |
||||
|
|
||||
|
// name
|
||||
|
mWeb->arg("inv" + String(i) + "Name").toCharArray(buf, 20); |
||||
|
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), buf, MAX_NAME_LENGTH); |
||||
|
|
||||
|
// type
|
||||
|
mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20); |
||||
|
uint8_t type = atoi(buf); |
||||
|
mEep->write(ADDR_INV_TYPE + i, type); |
||||
|
} |
||||
|
|
||||
|
interval = mWeb->arg("invInterval").toInt(); |
||||
|
mEep->write(ADDR_INV_INTERVAL, interval); |
||||
|
|
||||
|
|
||||
|
// pinout
|
||||
|
for(uint8_t i = 0; i < 3; i ++) { |
||||
|
uint8_t pin = mWeb->arg(String(pinArgNames[i])).toInt(); |
||||
|
mEep->write(ADDR_PINOUT + i, pin); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// nrf24 amplifier power
|
||||
|
mSys->Radio.AmplifierPower = mWeb->arg("rf24Power").toInt() & 0x03; |
||||
|
mEep->write(ADDR_RF24_AMP_PWR, mSys->Radio.AmplifierPower); |
||||
|
|
||||
|
// mqtt
|
||||
|
uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; |
||||
|
uint16_t mqttPort; |
||||
|
char mqttUser[MQTT_USER_LEN]; |
||||
|
char mqttPwd[MQTT_PWD_LEN]; |
||||
|
char mqttTopic[MQTT_TOPIC_LEN]; |
||||
|
mWeb->arg("mqttAddr").toCharArray(buf, 20); |
||||
|
i = 0; |
||||
|
p = strtok(buf, "."); |
||||
|
while(NULL != p) { |
||||
|
mqttAddr[i++] = atoi(p); |
||||
|
p = strtok(NULL, "."); |
||||
|
} |
||||
|
mWeb->arg("mqttUser").toCharArray(mqttUser, MQTT_USER_LEN); |
||||
|
mWeb->arg("mqttPwd").toCharArray(mqttPwd, MQTT_PWD_LEN); |
||||
|
mWeb->arg("mqttTopic").toCharArray(mqttTopic, MQTT_TOPIC_LEN); |
||||
|
interval = mWeb->arg("mqttIntvl").toInt(); |
||||
|
mqttPort = mWeb->arg("mqttPort").toInt(); |
||||
|
mEep->write(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); |
||||
|
mEep->write(ADDR_MQTT_PORT, mqttPort); |
||||
|
mEep->write(ADDR_MQTT_USER, mqttUser, MQTT_USER_LEN); |
||||
|
mEep->write(ADDR_MQTT_PWD, mqttPwd, MQTT_PWD_LEN); |
||||
|
mEep->write(ADDR_MQTT_TOPIC, mqttTopic, MQTT_TOPIC_LEN); |
||||
|
mEep->write(ADDR_MQTT_INTERVAL, interval); |
||||
|
|
||||
|
|
||||
|
// serial console
|
||||
|
bool tmp; |
||||
|
interval = mWeb->arg("serIntvl").toInt(); |
||||
|
mEep->write(ADDR_SER_INTERVAL, interval); |
||||
|
tmp = (mWeb->arg("serEn") == "on"); |
||||
|
mEep->write(ADDR_SER_ENABLE, (uint8_t)((tmp) ? 0x01 : 0x00)); |
||||
|
tmp = (mWeb->arg("serDbg") == "on"); |
||||
|
mEep->write(ADDR_SER_DEBUG, (uint8_t)((tmp) ? 0x01 : 0x00)); |
||||
|
|
||||
|
updateCrc(); |
||||
|
if((mWeb->arg("reboot") == "on")) |
||||
|
showReboot(); |
||||
|
else { |
||||
|
mShowRebootRequest = true; |
||||
|
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"3; URL=/setup\"></head><body>" |
||||
|
"<p>saved</p></body></html>"); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Error</title><meta http-equiv=\"refresh\" content=\"3; URL=/setup\"></head><body>" |
||||
|
"<p>Error while saving</p></body></html>"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void app::updateCrc(void) { |
||||
|
Main::updateCrc(); |
||||
|
|
||||
|
uint16_t crc; |
||||
|
crc = buildEEpCrc(ADDR_START_SETTINGS, (ADDR_NEXT - ADDR_START_SETTINGS)); |
||||
|
//DPRINTLN("new CRC: " + String(crc, HEX));
|
||||
|
mEep->write(ADDR_SETTINGS_CRC, crc); |
||||
|
} |
@ -0,0 +1,94 @@ |
|||||
|
#ifndef __APP_H__ |
||||
|
#define __APP_H__ |
||||
|
|
||||
|
#include <RF24.h> |
||||
|
#include <RF24_config.h> |
||||
|
|
||||
|
#include "defines.h" |
||||
|
#include "main.h" |
||||
|
|
||||
|
#include "CircularBuffer.h" |
||||
|
#include "hmSystem.h" |
||||
|
#include "mqtt.h" |
||||
|
|
||||
|
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType; |
||||
|
typedef HmRadio<RF24_CE_PIN, RF24_CS_PIN, RF24_IRQ_PIN, BufferType> RadioType; |
||||
|
typedef Inverter<float> InverterType; |
||||
|
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType; |
||||
|
|
||||
|
const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)", |
||||
|
"D2 (GPIO4)", "D1 (GPIO5)", "GPIO6", "GPIO7", "GPIO8", |
||||
|
"GPIO9", "GPIO10", "GPIO11", "D6 (GPIO12)", "D7 (GPIO13)", |
||||
|
"D5 (GPIO14)", "D8 (GPIO15)", "D0 (GPIO16)"}; |
||||
|
const char* const pinNames[] = {"CS", "CE", "IRQ"}; |
||||
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; |
||||
|
|
||||
|
const uint8_t dbgCmds[] = {0x01, 0x02, 0x03, 0x81, 0x82, 0x83, 0x84}; |
||||
|
#define DBG_CMD_LIST_LEN 7 |
||||
|
|
||||
|
class app : public Main { |
||||
|
public: |
||||
|
app(); |
||||
|
~app(); |
||||
|
|
||||
|
void setup(uint32_t timeout); |
||||
|
void loop(void); |
||||
|
void handleIntr(void); |
||||
|
|
||||
|
uint8_t getIrqPin(void) { |
||||
|
return mSys->Radio.pinIrq; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void showIndex(void); |
||||
|
void showSetup(void); |
||||
|
void showSave(void); |
||||
|
void showErase(void); |
||||
|
void showStatistics(void); |
||||
|
void showHoymiles(void); |
||||
|
void showLiveData(void); |
||||
|
|
||||
|
void saveValues(bool webSend); |
||||
|
void updateCrc(void); |
||||
|
|
||||
|
uint64_t Serial2u64(const char *val) { |
||||
|
char tmp[3] = {0}; |
||||
|
uint64_t ret = 0ULL; |
||||
|
uint64_t u64; |
||||
|
for(uint8_t i = 0; i < 6; i++) { |
||||
|
tmp[0] = val[i*2]; |
||||
|
tmp[1] = val[i*2 + 1]; |
||||
|
if((tmp[0] == '\0') || (tmp[1] == '\0')) |
||||
|
break; |
||||
|
u64 = strtol(tmp, NULL, 16); |
||||
|
ret |= (u64 << ((5-i) << 3)); |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
bool mShowRebootRequest; |
||||
|
|
||||
|
HmSystemType *mSys; |
||||
|
|
||||
|
uint16_t mSendTicker; |
||||
|
uint16_t mSendInterval; |
||||
|
|
||||
|
uint32_t mCmds[DBG_CMD_LIST_LEN+1]; |
||||
|
//uint32_t mChannelStat[4];
|
||||
|
uint32_t mRecCnt; |
||||
|
|
||||
|
// timer
|
||||
|
uint32_t mTicker; |
||||
|
bool mSerialValues; |
||||
|
bool mSerialDebug; |
||||
|
|
||||
|
// mqtt
|
||||
|
mqtt mMqtt; |
||||
|
uint16_t mMqttTicker; |
||||
|
uint16_t mMqttInterval; |
||||
|
bool mMqttActive; |
||||
|
uint16_t mSerialTicker; |
||||
|
uint16_t mSerialInterval; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__APP_H__*/ |
@ -0,0 +1,44 @@ |
|||||
|
#ifndef __CONFIG_H__ |
||||
|
#define __CONFIG_H__ |
||||
|
|
||||
|
// fallback WiFi info
|
||||
|
#define FB_WIFI_SSID "YOUR_WIFI_SSID" |
||||
|
#define FB_WIFI_PWD "YOUR_WIFI_PWD" |
||||
|
|
||||
|
|
||||
|
// access point info
|
||||
|
#define WIFI_AP_SSID "AHOY DTU" |
||||
|
#define WIFI_AP_PWD "esp_8266" |
||||
|
// stay in access point mode all the time
|
||||
|
//#define AP_ONLY
|
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// CONFIGURATION - COMPILE TIME
|
||||
|
//-------------------------------------
|
||||
|
// time in seconds how long the station info (ssid + pwd) will be tried
|
||||
|
#define WIFI_TRY_CONNECT_TIME 15 |
||||
|
|
||||
|
// time during the ESP will act as access point on connection failure (to
|
||||
|
// station) in seconds
|
||||
|
#define WIFI_AP_ACTIVE_TIME 3*60 |
||||
|
|
||||
|
// default device name
|
||||
|
#define DEF_DEVICE_NAME "ESP-DTU" |
||||
|
|
||||
|
// number of packets hold in buffer
|
||||
|
#define PACKET_BUFFER_SIZE 30 |
||||
|
|
||||
|
// number of configurable inverters
|
||||
|
#define MAX_NUM_INVERTERS 3 |
||||
|
|
||||
|
// maximum human readable inverter name length
|
||||
|
#define MAX_NAME_LENGTH 16 |
||||
|
|
||||
|
// maximum buffer length of packet received / sent to RF24 module
|
||||
|
#define MAX_RF_PAYLOAD_SIZE 64 |
||||
|
|
||||
|
// changes the style of "/setup" page, visualized = nicer
|
||||
|
#define LIVEDATA_VISUALIZED |
||||
|
|
||||
|
#endif /*__CONFIG_H__*/ |
@ -0,0 +1,43 @@ |
|||||
|
#include "crc.h" |
||||
|
|
||||
|
uint8_t crc8(uint8_t buf[], uint8_t len) { |
||||
|
uint8_t crc = CRC8_INIT; |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
crc ^= buf[i]; |
||||
|
for(uint8_t b = 0; b < 8; b ++) { |
||||
|
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); |
||||
|
} |
||||
|
} |
||||
|
return crc; |
||||
|
} |
||||
|
|
||||
|
uint16_t crc16(uint8_t buf[], uint8_t len) { |
||||
|
uint16_t crc = 0xffff; |
||||
|
uint8_t shift = 0; |
||||
|
|
||||
|
for(uint8_t i = 0; i < len; i ++) { |
||||
|
crc = crc ^ buf[i]; |
||||
|
for(uint8_t bit = 0; bit < 8; bit ++) { |
||||
|
shift = (crc & 0x0001); |
||||
|
crc = crc >> 1; |
||||
|
if(shift != 0) |
||||
|
crc = crc ^ 0xA001; |
||||
|
} |
||||
|
} |
||||
|
return crc; |
||||
|
} |
||||
|
|
||||
|
uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit, uint16_t crcIn) { |
||||
|
uint16_t crc = crcIn; |
||||
|
uint8_t idx, val = buf[(startBit >> 3)]; |
||||
|
|
||||
|
for(uint16_t bit = startBit; bit < lenBits; bit ++) { |
||||
|
idx = bit & 0x07; |
||||
|
if(0 == idx) |
||||
|
val = buf[(bit >> 3)]; |
||||
|
crc ^= 0x8000 & (val << (8 + idx)); |
||||
|
crc = (crc & 0x8000) ? ((crc << 1) ^ CRC16_NRF24_POLYNOM) : (crc << 1); |
||||
|
} |
||||
|
|
||||
|
return crc; |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
#ifndef __CRC_H__ |
||||
|
#define __CRC_H__ |
||||
|
|
||||
|
#include <cstdint> |
||||
|
|
||||
|
#define CRC8_INIT 0x00 |
||||
|
#define CRC8_POLY 0x01 |
||||
|
|
||||
|
#define CRC16_MODBUS_POLYNOM 0xA001 |
||||
|
#define CRC16_NRF24_POLYNOM 0x1021 |
||||
|
|
||||
|
uint8_t crc8(uint8_t buf[], uint8_t len); |
||||
|
uint16_t crc16(uint8_t buf[], uint8_t len); |
||||
|
uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff); |
||||
|
|
||||
|
#endif /*__CRC_H__*/ |
@ -0,0 +1,39 @@ |
|||||
|
#ifndef __DEBUG_H__ |
||||
|
#define __DEBUG_H__ |
||||
|
|
||||
|
#ifdef NDEBUG |
||||
|
#define DPRINT(str) |
||||
|
#define DPRINTLN(str) |
||||
|
#else |
||||
|
|
||||
|
#ifndef DSERIAL |
||||
|
#define DSERIAL Serial |
||||
|
#endif |
||||
|
|
||||
|
template <class T> |
||||
|
inline void DPRINT(T str) { DSERIAL.print(str); } |
||||
|
template <class T> |
||||
|
inline void DPRINTLN(T str) { DPRINT(str); DPRINT(F("\r\n")); } |
||||
|
inline void DHEX(uint8_t b) { |
||||
|
if( b<0x10 ) DSERIAL.print('0'); |
||||
|
DSERIAL.print(b,HEX); |
||||
|
} |
||||
|
inline void DHEX(uint16_t b) { |
||||
|
if( b<0x10 ) DSERIAL.print(F("000")); |
||||
|
else if( b<0x100 ) DSERIAL.print(F("00")); |
||||
|
else if( b<0x1000 ) DSERIAL.print(F("0")); |
||||
|
DSERIAL.print(b,HEX); |
||||
|
} |
||||
|
inline void DHEX(uint32_t b) { |
||||
|
if( b<0x10 ) DSERIAL.print(F("0000000")); |
||||
|
else if( b<0x100 ) DSERIAL.print(F("000000")); |
||||
|
else if( b<0x1000 ) DSERIAL.print(F("00000")); |
||||
|
else if( b<0x10000 ) DSERIAL.print(F("0000")); |
||||
|
else if( b<0x100000 ) DSERIAL.print(F("000")); |
||||
|
else if( b<0x1000000 ) DSERIAL.print(F("00")); |
||||
|
else if( b<0x10000000 ) DSERIAL.print(F("0")); |
||||
|
DSERIAL.print(b,HEX); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#endif /*__DEBUG_H__*/ |
@ -0,0 +1,92 @@ |
|||||
|
#ifndef __DEFINES_H__ |
||||
|
#define __DEFINES_H__ |
||||
|
|
||||
|
#include "config.h" |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// PINOUT (Default, can be changed in setup)
|
||||
|
//-------------------------------------
|
||||
|
#define RF24_CS_PIN 15 |
||||
|
#define RF24_CE_PIN 2 |
||||
|
#define RF24_IRQ_PIN 0 |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// VERSION
|
||||
|
//-------------------------------------
|
||||
|
#define VERSION_MAJOR 0 |
||||
|
#define VERSION_MINOR 3 |
||||
|
#define VERSION_PATCH 6 |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
typedef struct { |
||||
|
uint8_t sendCh; |
||||
|
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; |
||||
|
} packet_t; |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// EEPROM
|
||||
|
//-------------------------------------
|
||||
|
#define SSID_LEN 32 |
||||
|
#define PWD_LEN 63 |
||||
|
#define DEVNAME_LEN 16 |
||||
|
#define CRC_LEN 2 // uint16_t
|
||||
|
|
||||
|
#define INV_ADDR_LEN MAX_NUM_INVERTERS * 8 // uint64_t
|
||||
|
#define INV_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH // char[]
|
||||
|
#define INV_TYPE_LEN MAX_NUM_INVERTERS * 1 // uint8_t
|
||||
|
#define INV_INTERVAL_LEN 2 // uint16_t
|
||||
|
|
||||
|
#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ
|
||||
|
|
||||
|
#define RF24_AMP_PWR_LEN 1 |
||||
|
|
||||
|
#define MQTT_ADDR_LEN 4 // IP
|
||||
|
#define MQTT_USER_LEN 16 |
||||
|
#define MQTT_PWD_LEN 32 |
||||
|
#define MQTT_TOPIC_LEN 32 |
||||
|
#define MQTT_INTERVAL_LEN 2 // uint16_t
|
||||
|
#define MQTT_PORT_LEN 2 // uint16_t
|
||||
|
|
||||
|
#define SER_ENABLE_LEN 1 // uint8_t
|
||||
|
#define SER_DEBUG_LEN 1 // uint8_t
|
||||
|
#define SER_INTERVAL_LEN 2 // uint16_t
|
||||
|
|
||||
|
|
||||
|
#define ADDR_START 0 |
||||
|
#define ADDR_SSID ADDR_START |
||||
|
#define ADDR_PWD ADDR_SSID + SSID_LEN |
||||
|
#define ADDR_DEVNAME ADDR_PWD + PWD_LEN |
||||
|
#define ADDR_WIFI_CRC ADDR_DEVNAME + DEVNAME_LEN |
||||
|
#define ADDR_START_SETTINGS ADDR_WIFI_CRC + CRC_LEN |
||||
|
|
||||
|
#define ADDR_PINOUT ADDR_START_SETTINGS |
||||
|
|
||||
|
#define ADDR_RF24_AMP_PWR ADDR_PINOUT + PINOUT_LEN |
||||
|
|
||||
|
#define ADDR_INV_ADDR ADDR_RF24_AMP_PWR + RF24_AMP_PWR_LEN |
||||
|
#define ADDR_INV_NAME ADDR_INV_ADDR + INV_ADDR_LEN |
||||
|
#define ADDR_INV_TYPE ADDR_INV_NAME + INV_NAME_LEN |
||||
|
#define ADDR_INV_INTERVAL ADDR_INV_TYPE + INV_TYPE_LEN |
||||
|
|
||||
|
#define ADDR_MQTT_ADDR ADDR_INV_INTERVAL + INV_INTERVAL_LEN |
||||
|
#define ADDR_MQTT_USER ADDR_MQTT_ADDR + MQTT_ADDR_LEN |
||||
|
#define ADDR_MQTT_PWD ADDR_MQTT_USER + MQTT_USER_LEN |
||||
|
#define ADDR_MQTT_TOPIC ADDR_MQTT_PWD + MQTT_PWD_LEN |
||||
|
#define ADDR_MQTT_INTERVAL ADDR_MQTT_TOPIC + MQTT_TOPIC_LEN |
||||
|
#define ADDR_MQTT_PORT ADDR_MQTT_INTERVAL + MQTT_INTERVAL_LEN |
||||
|
|
||||
|
#define ADDR_SER_ENABLE ADDR_MQTT_PORT + MQTT_PORT_LEN |
||||
|
#define ADDR_SER_DEBUG ADDR_SER_ENABLE + SER_ENABLE_LEN |
||||
|
#define ADDR_SER_INTERVAL ADDR_SER_DEBUG + SER_DEBUG_LEN |
||||
|
#define ADDR_NEXT ADDR_SER_INTERVAL + SER_INTERVAL_LEN |
||||
|
|
||||
|
#define ADDR_SETTINGS_CRC 400 |
||||
|
|
||||
|
#if(ADDR_SETTINGS_CRC <= ADDR_NEXT) |
||||
|
#error address overlap! |
||||
|
#endif |
||||
|
|
||||
|
#endif /*__DEFINES_H__*/ |
@ -0,0 +1,133 @@ |
|||||
|
#ifndef __EEP_H__ |
||||
|
#define __EEP_H__ |
||||
|
|
||||
|
#include "Arduino.h" |
||||
|
#include <EEPROM.h> |
||||
|
|
||||
|
class eep { |
||||
|
public: |
||||
|
eep() { |
||||
|
EEPROM.begin(500); |
||||
|
} |
||||
|
~eep() { |
||||
|
EEPROM.end(); |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, char *str, uint8_t length) { |
||||
|
for(uint8_t i = 0; i < length; i ++) { |
||||
|
*(str++) = (char)EEPROM.read(addr++); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, float *value) { |
||||
|
uint8_t *p = (uint8_t*)value; |
||||
|
for(uint8_t i = 0; i < 4; i ++) { |
||||
|
*(p++) = (uint8_t)EEPROM.read(addr++); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, bool *value) { |
||||
|
uint8_t intVal = 0x00; |
||||
|
intVal = EEPROM.read(addr++); |
||||
|
*value = (intVal == 0x01); |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, uint8_t *value) { |
||||
|
*value = (EEPROM.read(addr++)); |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, uint8_t data[], uint16_t length) { |
||||
|
for(uint16_t i = 0; i < length; i ++) { |
||||
|
*(data++) = EEPROM.read(addr++); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, uint16_t *value) { |
||||
|
*value = (EEPROM.read(addr++) << 8); |
||||
|
*value |= (EEPROM.read(addr++)); |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, uint32_t *value) { |
||||
|
*value = (EEPROM.read(addr++) << 24); |
||||
|
*value |= (EEPROM.read(addr++) << 16); |
||||
|
*value |= (EEPROM.read(addr++) << 8); |
||||
|
*value |= (EEPROM.read(addr++)); |
||||
|
} |
||||
|
|
||||
|
void read(uint32_t addr, uint64_t *value) { |
||||
|
read(addr, (uint32_t *)value); |
||||
|
*value <<= 32; |
||||
|
uint32_t tmp; |
||||
|
read(addr+4, &tmp); |
||||
|
*value |= tmp; |
||||
|
/**value = (EEPROM.read(addr++) << 56);
|
||||
|
*value |= (EEPROM.read(addr++) << 48); |
||||
|
*value |= (EEPROM.read(addr++) << 40); |
||||
|
*value |= (EEPROM.read(addr++) << 32); |
||||
|
*value |= (EEPROM.read(addr++) << 24); |
||||
|
*value |= (EEPROM.read(addr++) << 16); |
||||
|
*value |= (EEPROM.read(addr++) << 8); |
||||
|
*value |= (EEPROM.read(addr++));*/ |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, const char *str, uint8_t length) { |
||||
|
for(uint8_t i = 0; i < length; i ++) { |
||||
|
EEPROM.write(addr++, str[i]); |
||||
|
} |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, uint8_t data[], uint16_t length) { |
||||
|
for(uint16_t i = 0; i < length; i ++) { |
||||
|
EEPROM.write(addr++, data[i]); |
||||
|
} |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, float value) { |
||||
|
uint8_t *p = (uint8_t*)&value; |
||||
|
for(uint8_t i = 0; i < 4; i ++) { |
||||
|
EEPROM.write(addr++, p[i]); |
||||
|
} |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, bool value) { |
||||
|
uint8_t intVal = (value) ? 0x01 : 0x00; |
||||
|
EEPROM.write(addr++, intVal); |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, uint8_t value) { |
||||
|
EEPROM.write(addr++, value); |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, uint16_t value) { |
||||
|
EEPROM.write(addr++, (value >> 8) & 0xff); |
||||
|
EEPROM.write(addr++, (value ) & 0xff); |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint32_t addr, uint32_t value) { |
||||
|
EEPROM.write(addr++, (value >> 24) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 16) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 8) & 0xff); |
||||
|
EEPROM.write(addr++, (value ) & 0xff); |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
|
||||
|
void write(uint64_t addr, uint64_t value) { |
||||
|
EEPROM.write(addr++, (value >> 56) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 48) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 40) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 32) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 24) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 16) & 0xff); |
||||
|
EEPROM.write(addr++, (value >> 8) & 0xff); |
||||
|
EEPROM.write(addr++, (value ) & 0xff); |
||||
|
EEPROM.commit(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
#endif /*__EEP_H__*/ |
@ -0,0 +1,33 @@ |
|||||
|
|
||||
|
#include "Arduino.h" |
||||
|
|
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <DNSServer.h> |
||||
|
#include <ESP8266WebServer.h> |
||||
|
#include <Ticker.h> |
||||
|
|
||||
|
#include <ESP8266HTTPUpdateServer.h> |
||||
|
#include "app.h" |
||||
|
#include "config.h" |
||||
|
|
||||
|
app myApp; |
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void setup() { |
||||
|
myApp.setup(WIFI_TRY_CONNECT_TIME); |
||||
|
|
||||
|
// TODO: move to HmRadio
|
||||
|
attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void loop() { |
||||
|
myApp.loop(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
ICACHE_RAM_ATTR void handleIntr(void) { |
||||
|
myApp.handleIntr(); |
||||
|
} |
@ -0,0 +1,159 @@ |
|||||
|
#ifndef __HM_DEFINES_H__ |
||||
|
#define __HM_DEFINES_H__ |
||||
|
|
||||
|
#include "debug.h" |
||||
|
#include <cstdint> |
||||
|
|
||||
|
|
||||
|
union serial_u { |
||||
|
uint64_t u64; |
||||
|
uint8_t b[8]; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
// 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"}; |
||||
|
|
||||
|
|
||||
|
// indices to calculation functions, defined in hmInverter.h
|
||||
|
enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH}; |
||||
|
|
||||
|
|
||||
|
// 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, INV_TYPE_HM800}; |
||||
|
const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400", "HM800"}; |
||||
|
#define NUM_INVERTER_TYPES 4 |
||||
|
|
||||
|
|
||||
|
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, 1 }, |
||||
|
{ 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_PAC, UNIT_W, CH0, CMD02, 15, 2, 10 }, |
||||
|
{ FLD_IAC, UNIT_A, CH0, CMD83, 3, 2, 100 }, |
||||
|
{ FLD_T, UNIT_C, CH0, CMD83, 7, 2, 10 } |
||||
|
}; |
||||
|
#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t)) |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HM800
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hm800assignment[] = { |
||||
|
|
||||
|
{ 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_PAC, UNIT_W, CH0, CMD02, 15, 2, 10 }, |
||||
|
{ FLD_IAC, UNIT_A, CH0, CMD83, 3, 2, 100 }, |
||||
|
{ FLD_T, UNIT_C, CH0, CMD83, 7, 2, 10 } |
||||
|
}; |
||||
|
#define HM800_LIST_LEN (sizeof(hm800assignment) / 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, CH3, 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 }, |
||||
|
{ FLD_YD, UNIT_WH, CH0, CMDFF, CALC_YD_CH0, 0, 0 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH0, CMDFF, CALC_YT_CH0, 0, 0 }, |
||||
|
{ FLD_UDC, UNIT_V, CH2, CMDFF, CALC_UDC_CH, CH1, 0 }, |
||||
|
{ FLD_UDC, UNIT_V, CH4, CMDFF, CALC_UDC_CH, CH3, 0 } |
||||
|
}; |
||||
|
#define HM1200_LIST_LEN (sizeof(hm1200assignment) / sizeof(byteAssign_t)) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
#endif /*__HM_DEFINES_H__*/ |
@ -0,0 +1,213 @@ |
|||||
|
#ifndef __HM_INVERTER_H__ |
||||
|
#define __HM_INVERTER_H__ |
||||
|
|
||||
|
#include "hmDefines.h" |
||||
|
|
||||
|
/**
|
||||
|
* For values which are of interest and not transmitted by the inverter can be |
||||
|
* calculated automatically. |
||||
|
* A list of functions can be linked to the assignment and will be executed |
||||
|
* automatically. Their result does not differ from original read values. |
||||
|
* The special command 0xff (CMDFF) must be used. |
||||
|
*/ |
||||
|
|
||||
|
// forward declaration of class
|
||||
|
template <class RECORDTYPE=float> |
||||
|
class Inverter; |
||||
|
|
||||
|
|
||||
|
// prototypes
|
||||
|
template<class T=float> |
||||
|
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0); |
||||
|
|
||||
|
template<class T=float> |
||||
|
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0); |
||||
|
|
||||
|
template<class T=float> |
||||
|
static T calcUdcCh(Inverter<> *iv, uint8_t arg0); |
||||
|
|
||||
|
template<class T=float> |
||||
|
using func_t = T (Inverter<> *, uint8_t); |
||||
|
|
||||
|
template<class T=float> |
||||
|
struct calcFunc_t { |
||||
|
uint8_t funcId; // unique id
|
||||
|
func_t<T>* func; // function pointer
|
||||
|
} ; |
||||
|
|
||||
|
|
||||
|
// list of all available functions, mapped in hmDefines.h
|
||||
|
template<class T=float> |
||||
|
const calcFunc_t<T> calcFunctions[] = { |
||||
|
{ CALC_YT_CH0, &calcYieldTotalCh0 }, |
||||
|
{ CALC_YD_CH0, &calcYieldDayCh0 }, |
||||
|
{ CALC_UDC_CH, &calcUdcCh } |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
template <class RECORDTYPE> |
||||
|
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() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
~Inverter() { |
||||
|
// TODO: cleanup
|
||||
|
} |
||||
|
|
||||
|
void init(void) { |
||||
|
getAssignment(); |
||||
|
toRadioId(); |
||||
|
record = new RECORDTYPE[listLen]; |
||||
|
memset(name, 0, MAX_NAME_LENGTH); |
||||
|
memset(record, 0, sizeof(RECORDTYPE) * listLen); |
||||
|
} |
||||
|
|
||||
|
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]; |
||||
|
} |
||||
|
|
||||
|
void doCalculations(void) { |
||||
|
for(uint8_t i = 0; i < listLen; i++) { |
||||
|
if(CMDFF == assign[i].cmdId) { |
||||
|
record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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_HM400 == type) { |
||||
|
listLen = (uint8_t)(HM400_LIST_LEN); |
||||
|
assign = (byteAssign_t*)hm400assignment; |
||||
|
channels = 1; |
||||
|
} |
||||
|
else if(INV_TYPE_HM600 == type) { |
||||
|
listLen = (uint8_t)(HM600_LIST_LEN); |
||||
|
assign = (byteAssign_t*)hm600assignment; |
||||
|
channels = 2; |
||||
|
} |
||||
|
else if(INV_TYPE_HM800 == type) { |
||||
|
listLen = (uint8_t)(HM800_LIST_LEN); |
||||
|
assign = (byteAssign_t*)hm800assignment; |
||||
|
channels = 2; |
||||
|
} |
||||
|
else if(INV_TYPE_HM1200 == type) { |
||||
|
listLen = (uint8_t)(HM1200_LIST_LEN); |
||||
|
assign = (byteAssign_t*)hm1200assignment; |
||||
|
channels = 4; |
||||
|
} |
||||
|
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. |
||||
|
*/ |
||||
|
|
||||
|
template<class T=float> |
||||
|
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) { |
||||
|
if(NULL != iv) { |
||||
|
T yield = 0; |
||||
|
for(uint8_t i = 1; i <= iv->channels; i++) { |
||||
|
uint8_t pos = iv->getPosByChFld(i, FLD_YT); |
||||
|
yield += iv->getValue(pos); |
||||
|
} |
||||
|
return yield; |
||||
|
} |
||||
|
return 0.0; |
||||
|
} |
||||
|
|
||||
|
template<class T=float> |
||||
|
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) { |
||||
|
if(NULL != iv) { |
||||
|
T yield = 0; |
||||
|
for(uint8_t i = 1; i <= iv->channels; i++) { |
||||
|
uint8_t pos = iv->getPosByChFld(i, FLD_YD); |
||||
|
yield += iv->getValue(pos); |
||||
|
} |
||||
|
return yield; |
||||
|
} |
||||
|
return 0.0; |
||||
|
} |
||||
|
|
||||
|
template<class T=float> |
||||
|
static T calcUdcCh(Inverter<> *iv, uint8_t arg0) { |
||||
|
// arg0 = channel of source
|
||||
|
for(uint8_t i = 0; i < iv->listLen; i++) { |
||||
|
if((FLD_UDC == iv->assign[i].fieldId) && (arg0 == iv->assign[i].ch)) { |
||||
|
return iv->getValue(i); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0.0; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#endif /*__HM_INVERTER_H__*/ |
@ -0,0 +1,272 @@ |
|||||
|
#ifndef __RADIO_H__ |
||||
|
#define __RADIO_H__ |
||||
|
|
||||
|
#include <RF24.h> |
||||
|
#include <RF24_config.h> |
||||
|
#include "crc.h" |
||||
|
|
||||
|
//#define CHANNEL_HOP // switch between channels or use static channel to send
|
||||
|
|
||||
|
#define DEFAULT_RECV_CHANNEL 3 |
||||
|
#define SPI_SPEED 1000000 |
||||
|
|
||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
||||
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
||||
|
|
||||
|
|
||||
|
const char* const rf24AmpPower[] = {"MIN", "LOW", "HIGH", "MAX"}; |
||||
|
|
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// MACROS
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
#define CP_U32_LittleEndian(buf, v) ({ \ |
||||
|
uint8_t *b = buf; \ |
||||
|
b[0] = ((v >> 24) & 0xff); \ |
||||
|
b[1] = ((v >> 16) & 0xff); \ |
||||
|
b[2] = ((v >> 8) & 0xff); \ |
||||
|
b[3] = ((v ) & 0xff); \ |
||||
|
}) |
||||
|
|
||||
|
#define CP_U32_BigEndian(buf, v) ({ \ |
||||
|
uint8_t *b = buf; \ |
||||
|
b[3] = ((v >> 24) & 0xff); \ |
||||
|
b[2] = ((v >> 16) & 0xff); \ |
||||
|
b[1] = ((v >> 8) & 0xff); \ |
||||
|
b[0] = ((v ) & 0xff); \ |
||||
|
}) |
||||
|
|
||||
|
#define BIT_CNT(x) ((x)<<3) |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// HM Radio class
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
template <uint8_t CE_PIN, uint8_t CS_PIN, uint8_t IRQ_PIN, class BUFFER, uint64_t DTU_ID=DTU_RADIO_ID> |
||||
|
class HmRadio { |
||||
|
public: |
||||
|
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { |
||||
|
mChanOut[0] = 23; |
||||
|
mChanOut[1] = 40; |
||||
|
mChanOut[2] = 61; |
||||
|
mChanOut[3] = 75; |
||||
|
mChanIdx = 1; |
||||
|
|
||||
|
calcDtuCrc(); |
||||
|
|
||||
|
pinCs = CS_PIN; |
||||
|
pinCe = CE_PIN; |
||||
|
pinIrq = IRQ_PIN; |
||||
|
|
||||
|
AmplifierPower = 1; |
||||
|
mSendCnt = 0; |
||||
|
} |
||||
|
~HmRadio() {} |
||||
|
|
||||
|
void setup(BUFFER *ctrl) { |
||||
|
//DPRINTLN("HmRadio::setup, pins: " + String(pinCs) + ", " + String(pinCe) + ", " + String(pinIrq));
|
||||
|
pinMode(pinIrq, INPUT_PULLUP); |
||||
|
|
||||
|
mBufCtrl = ctrl; |
||||
|
|
||||
|
mNrf24.begin(pinCe, pinCs); |
||||
|
mNrf24.setRetries(0, 0); |
||||
|
|
||||
|
mNrf24.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
mNrf24.setDataRate(RF24_250KBPS); |
||||
|
mNrf24.disableCRC(); |
||||
|
mNrf24.setAutoAck(false); |
||||
|
mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE); |
||||
|
mNrf24.setAddressWidth(5); |
||||
|
mNrf24.openReadingPipe(1, DTU_RADIO_ID); |
||||
|
|
||||
|
// enable only receiving interrupts
|
||||
|
mNrf24.maskIRQ(true, true, false); |
||||
|
|
||||
|
DPRINTLN("RF24 Amp Pwr: RF24_PA_" + String(rf24AmpPower[AmplifierPower])); |
||||
|
mNrf24.setPALevel(AmplifierPower & 0x03); |
||||
|
mNrf24.startListening(); |
||||
|
|
||||
|
DPRINTLN("Radio Config:"); |
||||
|
mNrf24.printPrettyDetails(); |
||||
|
|
||||
|
mSendChannel = getDefaultChannel(); |
||||
|
|
||||
|
if(!mNrf24.isChipConnected()) { |
||||
|
DPRINTLN("WARNING! your NRF24 module can't be reached, check the wiring"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void handleIntr(void) { |
||||
|
uint8_t pipe, len; |
||||
|
packet_t *p; |
||||
|
|
||||
|
DISABLE_IRQ; |
||||
|
while(mNrf24.available(&pipe)) { |
||||
|
if(!mBufCtrl->full()) { |
||||
|
p = mBufCtrl->getFront(); |
||||
|
memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); |
||||
|
p->sendCh = mSendChannel; |
||||
|
len = mNrf24.getPayloadSize(); |
||||
|
if(len > MAX_RF_PAYLOAD_SIZE) |
||||
|
len = MAX_RF_PAYLOAD_SIZE; |
||||
|
|
||||
|
mNrf24.read(p->packet, len); |
||||
|
mBufCtrl->pushFront(p); |
||||
|
} |
||||
|
else { |
||||
|
bool tx_ok, tx_fail, rx_ready; |
||||
|
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
|
||||
|
mNrf24.flush_rx(); // drop the packet
|
||||
|
} |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
} |
||||
|
|
||||
|
uint8_t getDefaultChannel(void) { |
||||
|
return mChanOut[2]; |
||||
|
} |
||||
|
uint8_t getLastChannel(void) { |
||||
|
return mChanOut[mChanIdx]; |
||||
|
} |
||||
|
|
||||
|
uint8_t getNxtChannel(void) { |
||||
|
if(++mChanIdx >= 4) |
||||
|
mChanIdx = 0; |
||||
|
return mChanOut[mChanIdx]; |
||||
|
} |
||||
|
|
||||
|
void sendTimePacket(uint64_t invId, uint32_t ts) { |
||||
|
sendCmdPacket(invId, 0x15, 0x80, false); |
||||
|
mSendBuf[10] = 0x0b; // cid
|
||||
|
mSendBuf[11] = 0x00; |
||||
|
CP_U32_LittleEndian(&mSendBuf[12], ts); |
||||
|
mSendBuf[19] = 0x05; |
||||
|
|
||||
|
uint16_t crc = crc16(&mSendBuf[10], 14); |
||||
|
mSendBuf[24] = (crc >> 8) & 0xff; |
||||
|
mSendBuf[25] = (crc ) & 0xff; |
||||
|
mSendBuf[26] = crc8(mSendBuf, 26); |
||||
|
|
||||
|
sendPacket(invId, mSendBuf, 27); |
||||
|
} |
||||
|
|
||||
|
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t cmd, bool calcCrc = true) { |
||||
|
memset(mSendBuf, 0, MAX_RF_PAYLOAD_SIZE); |
||||
|
mSendBuf[0] = mid; // message id
|
||||
|
CP_U32_BigEndian(&mSendBuf[1], (invId >> 8)); |
||||
|
CP_U32_BigEndian(&mSendBuf[5], (DTU_ID >> 8)); |
||||
|
mSendBuf[9] = cmd; |
||||
|
if(calcCrc) { |
||||
|
mSendBuf[10] = crc8(mSendBuf, 10); |
||||
|
sendPacket(invId, mSendBuf, 11); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { |
||||
|
*len = (buf[0] >> 2); |
||||
|
for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { |
||||
|
buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); |
||||
|
} |
||||
|
uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); |
||||
|
|
||||
|
bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); |
||||
|
|
||||
|
if(valid) { |
||||
|
if(mLastCrc == crc) |
||||
|
*rptCnt = (++mRptCnt); |
||||
|
else { |
||||
|
mRptCnt = 0; |
||||
|
*rptCnt = 0; |
||||
|
mLastCrc = crc; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return valid; |
||||
|
} |
||||
|
|
||||
|
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { |
||||
|
DPRINT(String(info)); |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
if(buf[i] < 10) |
||||
|
DPRINT("0"); |
||||
|
DHEX(buf[i]); |
||||
|
DPRINT(" "); |
||||
|
} |
||||
|
DPRINTLN(""); |
||||
|
} |
||||
|
|
||||
|
bool isChipConnected(void) { |
||||
|
return mNrf24.isChipConnected(); |
||||
|
} |
||||
|
|
||||
|
uint8_t pinCs; |
||||
|
uint8_t pinCe; |
||||
|
uint8_t pinIrq; |
||||
|
|
||||
|
uint8_t AmplifierPower; |
||||
|
uint32_t mSendCnt; |
||||
|
|
||||
|
private: |
||||
|
void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len) { |
||||
|
//DPRINTLN("sent packet: #" + String(mSendCnt));
|
||||
|
//dumpBuf("SEN ", buf, len);
|
||||
|
|
||||
|
DISABLE_IRQ; |
||||
|
mNrf24.stopListening(); |
||||
|
|
||||
|
#ifdef CHANNEL_HOP |
||||
|
mSendChannel = getNxtChannel(); |
||||
|
#else |
||||
|
mSendChannel = getDefaultChannel(); |
||||
|
#endif |
||||
|
mNrf24.setChannel(mSendChannel); |
||||
|
//DPRINTLN("CH: " + String(mSendChannel));
|
||||
|
|
||||
|
mNrf24.openWritingPipe(invId); // TODO: deprecated
|
||||
|
mNrf24.setCRCLength(RF24_CRC_16); |
||||
|
mNrf24.enableDynamicPayloads(); |
||||
|
mNrf24.setAutoAck(true); |
||||
|
mNrf24.setRetries(3, 15); |
||||
|
|
||||
|
mNrf24.write(buf, len); |
||||
|
|
||||
|
// Try to avoid zero payload acks (has no effect)
|
||||
|
mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
|
||||
|
|
||||
|
mNrf24.setAutoAck(false); |
||||
|
mNrf24.setRetries(0, 0); |
||||
|
mNrf24.disableDynamicPayloads(); |
||||
|
mNrf24.setCRCLength(RF24_CRC_DISABLED); |
||||
|
|
||||
|
mNrf24.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
mNrf24.startListening(); |
||||
|
|
||||
|
RESTORE_IRQ; |
||||
|
mSendCnt++; |
||||
|
} |
||||
|
|
||||
|
void calcDtuCrc(void) { |
||||
|
uint64_t addr = DTU_RADIO_ID; |
||||
|
uint8_t tmp[5]; |
||||
|
for(int8_t i = 4; i >= 0; i--) { |
||||
|
tmp[i] = addr; |
||||
|
addr >>= 8; |
||||
|
} |
||||
|
mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5)); |
||||
|
} |
||||
|
|
||||
|
uint8_t mChanOut[4]; |
||||
|
uint8_t mChanIdx; |
||||
|
uint16_t mDtuIdCrc; |
||||
|
uint16_t mLastCrc; |
||||
|
uint8_t mRptCnt; |
||||
|
|
||||
|
RF24 mNrf24; |
||||
|
uint8_t mSendChannel; |
||||
|
BUFFER *mBufCtrl; |
||||
|
uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__RADIO_H__*/ |
@ -0,0 +1,82 @@ |
|||||
|
#ifndef __HM_SYSTEM_H__ |
||||
|
#define __HM_SYSTEM_H__ |
||||
|
|
||||
|
#include "hmInverter.h" |
||||
|
#ifndef NO_RADIO |
||||
|
#include "hmRadio.h" |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
|
||||
|
template <class RADIO, class BUFFER, uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>> |
||||
|
class HmSystem { |
||||
|
public: |
||||
|
typedef RADIO RadioType; |
||||
|
RadioType Radio; |
||||
|
typedef BUFFER BufferType; |
||||
|
BufferType BufCtrl; |
||||
|
|
||||
|
HmSystem() { |
||||
|
mNumInv = 0; |
||||
|
} |
||||
|
~HmSystem() { |
||||
|
// TODO: cleanup
|
||||
|
} |
||||
|
|
||||
|
void setup() { |
||||
|
Radio.setup(&BufCtrl); |
||||
|
} |
||||
|
|
||||
|
INVERTERTYPE *addInverter(const char *name, uint64_t serial, uint8_t type) { |
||||
|
if(MAX_INVERTER <= mNumInv) { |
||||
|
DPRINT("max number of inverters reached!"); |
||||
|
return NULL; |
||||
|
} |
||||
|
INVERTERTYPE *p = &mInverter[mNumInv]; |
||||
|
p->id = mNumInv; |
||||
|
p->serial.u64 = serial; |
||||
|
p->type = type; |
||||
|
p->init(); |
||||
|
uint8_t len = (uint8_t)strlen(name); |
||||
|
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); |
||||
|
|
||||
|
if(NULL == p->assign) { |
||||
|
DPRINT("no assignment for type found!"); |
||||
|
return NULL; |
||||
|
} |
||||
|
else { |
||||
|
mNumInv ++; |
||||
|
return p; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
INVERTERTYPE *findInverter(uint8_t buf[]) { |
||||
|
INVERTERTYPE *p; |
||||
|
for(uint8_t i = 0; i < mNumInv; i++) { |
||||
|
p = &mInverter[i]; |
||||
|
if((p->serial.b[3] == buf[0]) |
||||
|
&& (p->serial.b[2] == buf[1]) |
||||
|
&& (p->serial.b[1] == buf[2]) |
||||
|
&& (p->serial.b[0] == buf[3])) |
||||
|
return p; |
||||
|
} |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
INVERTERTYPE *getInverterByPos(uint8_t pos) { |
||||
|
if(mInverter[pos].serial.u64 != 0ULL) |
||||
|
return &mInverter[pos]; |
||||
|
else |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
uint8_t getNumInverters(void) { |
||||
|
return mNumInv; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
INVERTERTYPE mInverter[MAX_INVERTER]; |
||||
|
uint8_t mNumInv; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__HM_SYSTEM_H__*/ |
@ -0,0 +1,28 @@ |
|||||
|
import re |
||||
|
|
||||
|
def convert2Header(inFile): |
||||
|
outName = "h/" + inFile.replace(".", "_") + ".h" |
||||
|
fileType = inFile.split(".")[1] |
||||
|
|
||||
|
f = open(inFile, "r") |
||||
|
data = f.read().replace('\n', '') |
||||
|
f.close() |
||||
|
if fileType == "html": |
||||
|
data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags |
||||
|
data = re.sub(r"(\;|\}|\>|\{)\s+", r'\1', data) # whitespaces inner javascript |
||||
|
data = re.sub(r"\"", '\\\"', data) # escape quotation marks |
||||
|
else: |
||||
|
data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css |
||||
|
|
||||
|
define = inFile.split(".")[0].upper() |
||||
|
f = open(outName, "w") |
||||
|
f.write("#ifndef __{}_H__\n".format(define)) |
||||
|
f.write("#define __{}_H__\n".format(define)) |
||||
|
f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFile.replace(".", "_"), data)) |
||||
|
f.write("#endif /*__{}_H__*/\n".format(define)) |
||||
|
f.close() |
||||
|
|
||||
|
convert2Header("index.html") |
||||
|
convert2Header("setup.html") |
||||
|
convert2Header("hoymiles.html") |
||||
|
convert2Header("style.css") |
@ -0,0 +1,4 @@ |
|||||
|
#ifndef __HOYMILES_H__ |
||||
|
#define __HOYMILES_H__ |
||||
|
const char hoymiles_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">getAjax('/livedata', 'livedata');window.setInterval(\"getAjax('/livedata', 'livedata')\", 10000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><div id=\"livedata\"></div><p>Every 10 seconds the values are updated</p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>"; |
||||
|
#endif /*__HOYMILES_H__*/ |
@ -0,0 +1,4 @@ |
|||||
|
#ifndef __INDEX_H__ |
||||
|
#define __INDEX_H__ |
||||
|
const char index_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">window.setInterval(\"getAjax('/uptime', 'uptime')\", 1000);window.setInterval(\"getAjax('/time', 'time')\", 1000);window.setInterval(\"getAjax('/cmdstat', 'cmds')\", 2000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/hoymiles\">Visualization</a><br/><br/><a href=\"/setup\">Setup</a><br/></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Time: </span><span id=\"time\"></span></p><p><span class=\"des\">Statistics: </span><pre id=\"cmds\"></pre></p><div id=\"note\">This project was started from <a href=\"https://www.mikrocontroller.net/topic/525778\" target=\"_blank\">this discussion. (Mikrocontroller.net)</a><br/>New updates can be found on Github: <a href=\"https://github.com/grindylow/ahoy\" target=\"_blank\">https://github.com/grindylow/ahoy</a><br/><br/>Please report issues using the feature provided by Github. </div></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY :: {VERSION}</p><p class=\"right\"><a href=\"/reboot\">Reboot</a></p></div></body></html>"; |
||||
|
#endif /*__INDEX_H__*/ |
@ -0,0 +1,4 @@ |
|||||
|
#ifndef __SETUP_H__ |
||||
|
#define __SETUP_H__ |
||||
|
const char setup_html[] PROGMEM = "<!doctype html><html><head><title>Setup - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body><h1>Setup</h1><div id=\"setup\" class=\"content\"><div id=\"content\"><p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. </p><form method=\"post\" action=\"{IP}/save\"><p class=\"des\">WiFi</p><label for=\"ssid\">SSID</label><input type=\"text\" class=\"text\" name=\"ssid\" value=\"{SSID}\"/><label for=\"pwd\">Password</label><input type=\"password\" class=\"text\" name=\"pwd\" value=\"{PWD}\"/><p class=\"des\">Device Host Name</p><label for=\"device\">Device Name</label><input type=\"text\" class=\"text\" name=\"device\" value=\"{DEVICE}\"/><a class=\"erase\" href=\"/erase\">ERASE SETTINGS (not WiFi)</a><p class=\"des\">Inverter</p>{INVERTERS}<br/><p class=\"subdes\">General</p><label for=\"invInterval\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"invInterval\" value=\"{INV_INTVL}\"/><p class=\"des\">Pinout (Wemos)</p>{PINOUT}<p class=\"des\">Radio (NRF24L01+)</p><label for=\"rf24Power\">Amplifier Power Level</label><select name=\"rf24Power\">{RF24}</select><p class=\"des\">MQTT</p><label for=\"mqttAddr\">Broker / Server IP</label><input type=\"text\" class=\"text\" name=\"mqttAddr\" value=\"{MQTT_ADDR}\"/><label for=\"mqttPort\">Port</label><input type=\"text\" class=\"text\" name=\"mqttPort\" value=\"{MQTT_PORT}\"/><label for=\"mqttUser\">Username (optional)</label><input type=\"text\" class=\"text\" name=\"mqttUser\" value=\"{MQTT_USER}\"/><label for=\"mqttPwd\">Password (optional)</label><input type=\"text\" class=\"text\" name=\"mqttPwd\" value=\"{MQTT_PWD}\"/><label for=\"mqttTopic\">Topic</label><input type=\"text\" class=\"text\" name=\"mqttTopic\" value=\"{MQTT_TOPIC}\"/><label for=\"mqttIntvl\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"mqttIntvl\" value=\"{MQTT_INTVL}\"/><p class=\"des\">Serial Console</p><label for=\"serEn\">print inverter data</label><input type=\"checkbox\" class=\"cb\" name=\"serEn\" {SER_VAL_CB}/><br/><label for=\"serDbg\">print RF24 debug</label><input type=\"checkbox\" class=\"cb\" name=\"serDbg\" {SER_DBG_CB}/><br/><label for=\"serIntvl\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"serIntvl\" value=\"{SER_INTVL}\"/><p class=\"des\"> </p><label for=\"reboot\">Reboot device after successful save</label><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><input type=\"submit\" value=\"save\" class=\"btn\" /></form></div></div><div id=\"footer\"><p class=\"left\"><a href=\"{IP}/\">Home</a></p><p class=\"left\"><a href=\"{IP}/update\">Update Firmware</a></p><p class=\"right\">AHOY - {VERSION}</p><p class=\"right\"><a href=\"{IP}/factory\">Factory Reset</a></p><p class=\"right\"><a href=\"{IP}/reboot\">Reboot</a></p></div></body></html>"; |
||||
|
#endif /*__SETUP_H__*/ |
@ -0,0 +1,4 @@ |
|||||
|
#ifndef __STYLE_H__ |
||||
|
#define __STYLE_H__ |
||||
|
const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}.des {margin-top:35px;font-size:13pt;color:#006ec0;}.subdes {font-size:12pt;color:#006ec0;margin-left:7px;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}a.erase {background-color:#006ec0;color:#fff;padding:7px;display:inline-block;margin-top:30px;float:right;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;border-top:5px solid #fff;}#footer p, #footer a {color:#fff;padding:0 7px 0 7px;font-size:10pt !important;}div.content {background-color:#fff;padding-bottom:65px;overflow:auto;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;margin:10px 0 30px;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin-left:10px;}.left {float:left;}.right {float:right;}div.ch-iv {width:100%;background-color:#32b004;display:inline-block;margin-bottom:20px;padding-bottom:20px;overflow:auto;}div.ch {width:250px;min-height:420px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;overflow:auto;padding-bottom:20px;}div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head {color:#fff;display:block;width:100%;text-align:center;}.subgrp {float:left;width:250px;}div.ch .unit, div.ch-iv .unit {font-size:19px;margin-left:10px;}div.ch .value, div.ch-iv .value {margin-top:20px;font-size:30px;}div.ch .info, div.ch-iv .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}div.ch-iv .head {background-color:#1c6800;padding:10px 0 10px 0;}div.iv {max-width:1060px;}div.ch:last-child {margin-right:0px !important;}#note {margin:50px 10px 10px 10px;padding-top:10px;width:100%;border-top:1px solid #bbb;}"; |
||||
|
#endif /*__STYLE_H__*/ |
@ -0,0 +1,42 @@ |
|||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>Index - {DEVICE}</title> |
||||
|
<link rel="stylesheet" type="text/css" href="style.css"/> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<script type="text/javascript"> |
||||
|
getAjax('/livedata', 'livedata'); |
||||
|
window.setInterval("getAjax('/livedata', 'livedata')", 10000); |
||||
|
|
||||
|
function getAjax(url, resid) { |
||||
|
var http = null; |
||||
|
http = new XMLHttpRequest(); |
||||
|
if(http != null) { |
||||
|
http.open("GET", url, true); |
||||
|
http.onreadystatechange = print; |
||||
|
http.send(null); |
||||
|
} |
||||
|
|
||||
|
function print() { |
||||
|
if(http.readyState == 4) { |
||||
|
document.getElementById(resid).innerHTML = http.responseText; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style type="text/css"> |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<h1>AHOY - {DEVICE}</h1> |
||||
|
<div id="content" class="content"> |
||||
|
<div id="livedata"></div> |
||||
|
<p>Every 10 seconds the values are updated</p> |
||||
|
</div> |
||||
|
<div id="footer"> |
||||
|
<p class="left">© 2022</p> |
||||
|
<p class="left"><a href="/">Home</a></p> |
||||
|
<p class="right">AHOY :: {VERSION}</p> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,55 @@ |
|||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>Index - {DEVICE}</title> |
||||
|
<link rel="stylesheet" type="text/css" href="style.css"/> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<script type="text/javascript"> |
||||
|
window.setInterval("getAjax('/uptime', 'uptime')", 1000); |
||||
|
window.setInterval("getAjax('/time', 'time')", 1000); |
||||
|
window.setInterval("getAjax('/cmdstat', 'cmds')", 2000); |
||||
|
|
||||
|
function getAjax(url, resid) { |
||||
|
var http = null; |
||||
|
http = new XMLHttpRequest(); |
||||
|
if(http != null) { |
||||
|
http.open("GET", url, true); |
||||
|
http.onreadystatechange = print; |
||||
|
http.send(null); |
||||
|
} |
||||
|
|
||||
|
function print() { |
||||
|
if(http.readyState == 4) { |
||||
|
document.getElementById(resid).innerHTML = http.responseText; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
</head> |
||||
|
<body> |
||||
|
<h1>AHOY - {DEVICE}</h1> |
||||
|
<div id="content" class="content"> |
||||
|
<p> |
||||
|
<a href="/hoymiles">Visualization</a><br/> |
||||
|
<br/> |
||||
|
<a href="/setup">Setup</a><br/> |
||||
|
</p> |
||||
|
<p><span class="des">Uptime: </span><span id="uptime"></span></p> |
||||
|
<p><span class="des">Time: </span><span id="time"></span></p> |
||||
|
<p><span class="des">Statistics: </span><pre id="cmds"></pre></p> |
||||
|
|
||||
|
<div id="note"> |
||||
|
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a><br/> |
||||
|
New updates can be found on Github: <a href="https://github.com/grindylow/ahoy" target="_blank">https://github.com/grindylow/ahoy</a><br/> |
||||
|
<br/> |
||||
|
Please report issues using the feature provided by Github. |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="footer"> |
||||
|
<p class="left">© 2022</p> |
||||
|
<p class="left"><a href="/update">Update Firmware</a></p> |
||||
|
<p class="right">AHOY :: {VERSION}</p> |
||||
|
<p class="right"><a href="/reboot">Reboot</a></p> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,79 @@ |
|||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>Setup - {DEVICE}</title> |
||||
|
<link rel="stylesheet" type="text/css" href="style.css"/> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<h1>Setup</h1> |
||||
|
<div id="setup" class="content"> |
||||
|
<div id="content"> |
||||
|
<p> |
||||
|
Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. |
||||
|
</p> |
||||
|
<form method="post" action="{IP}/save"> |
||||
|
<p class="des">WiFi</p> |
||||
|
<label for="ssid">SSID</label> |
||||
|
<input type="text" class="text" name="ssid" value="{SSID}"/> |
||||
|
<label for="pwd">Password</label> |
||||
|
<input type="password" class="text" name="pwd" value="{PWD}"/> |
||||
|
<p class="des">Device Host Name</p> |
||||
|
<label for="device">Device Name</label> |
||||
|
<input type="text" class="text" name="device" value="{DEVICE}"/> |
||||
|
|
||||
|
|
||||
|
<a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a> |
||||
|
|
||||
|
<p class="des">Inverter</p> |
||||
|
{INVERTERS}<br/> |
||||
|
<p class="subdes">General</p> |
||||
|
<label for="invInterval">Interval (s)</label> |
||||
|
<input type="text" class="text" name="invInterval" value="{INV_INTVL}"/> |
||||
|
|
||||
|
<p class="des">Pinout (Wemos)</p> |
||||
|
{PINOUT} |
||||
|
|
||||
|
<p class="des">Radio (NRF24L01+)</p> |
||||
|
<label for="rf24Power">Amplifier Power Level</label> |
||||
|
<select name="rf24Power">{RF24}</select> |
||||
|
|
||||
|
<p class="des">MQTT</p> |
||||
|
<label for="mqttAddr">Broker / Server IP</label> |
||||
|
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}"/> |
||||
|
<label for="mqttPort">Port</label> |
||||
|
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/> |
||||
|
<label for="mqttUser">Username (optional)</label> |
||||
|
<input type="text" class="text" name="mqttUser" value="{MQTT_USER}"/> |
||||
|
<label for="mqttPwd">Password (optional)</label> |
||||
|
<input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/> |
||||
|
<label for="mqttTopic">Topic</label> |
||||
|
<input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/> |
||||
|
<label for="mqttIntvl">Interval (s)</label> |
||||
|
<input type="text" class="text" name="mqttIntvl" value="{MQTT_INTVL}"/> |
||||
|
|
||||
|
<p class="des">Serial Console</p> |
||||
|
<label for="serEn">print inverter data</label> |
||||
|
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/> |
||||
|
<label for="serDbg">print RF24 debug</label> |
||||
|
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/> |
||||
|
<label for="serIntvl">Interval (s)</label> |
||||
|
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/> |
||||
|
|
||||
|
<p class="des"> </p> |
||||
|
<label for="reboot">Reboot device after successful save</label> |
||||
|
<input type="checkbox" class="cb" name="reboot"/> |
||||
|
<input type="submit" value="save" class="btn" /> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="footer"> |
||||
|
<p class="left"><a href="{IP}/">Home</a></p> |
||||
|
<p class="left"><a href="{IP}/update">Update Firmware</a></p> |
||||
|
<p class="right">AHOY - {VERSION}</p> |
||||
|
<p class="right"><a href="{IP}/factory">Factory Reset</a></p> |
||||
|
<p class="right"><a href="{IP}/reboot">Reboot</a></p> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,190 @@ |
|||||
|
h1 { |
||||
|
margin: 0; |
||||
|
padding: 20pt; |
||||
|
font-size: 22pt; |
||||
|
color: #fff; |
||||
|
background-color: #006ec0; |
||||
|
display: block; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
html, body { |
||||
|
font-family: Arial; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
text-align: justify; |
||||
|
font-size: 13pt; |
||||
|
} |
||||
|
|
||||
|
.des { |
||||
|
margin-top: 35px; |
||||
|
font-size: 13pt; |
||||
|
color: #006ec0; |
||||
|
} |
||||
|
|
||||
|
.subdes { |
||||
|
font-size: 12pt; |
||||
|
color: #006ec0; |
||||
|
margin-left: 7px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
a:link, a:visited { |
||||
|
text-decoration: none; |
||||
|
font-size: 13pt; |
||||
|
color: #006ec0; |
||||
|
} |
||||
|
|
||||
|
a:hover, a:focus { |
||||
|
color: #f00; |
||||
|
} |
||||
|
|
||||
|
a.erase { |
||||
|
background-color: #006ec0; |
||||
|
color: #fff; |
||||
|
padding: 7px; |
||||
|
display: inline-block; |
||||
|
margin-top: 30px; |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
#content { |
||||
|
padding: 15px 15px 60px 15px; |
||||
|
} |
||||
|
|
||||
|
#footer { |
||||
|
position: fixed; |
||||
|
bottom: 0px; |
||||
|
height: 45px; |
||||
|
background-color: #006ec0; |
||||
|
width: 100%; |
||||
|
border-top: 5px solid #fff; |
||||
|
} |
||||
|
|
||||
|
#footer p, #footer a { |
||||
|
color: #fff; |
||||
|
padding: 0 7px 0 7px; |
||||
|
font-size: 10pt !important; |
||||
|
} |
||||
|
|
||||
|
div.content { |
||||
|
background-color: #fff; |
||||
|
padding-bottom: 65px; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
input, select { |
||||
|
padding: 7px; |
||||
|
font-size: 13pt; |
||||
|
} |
||||
|
|
||||
|
input.text, select { |
||||
|
width: 70%; |
||||
|
box-sizing: border-box; |
||||
|
margin-bottom: 10px; |
||||
|
border: 1px solid #ccc; |
||||
|
} |
||||
|
|
||||
|
input.btn { |
||||
|
background-color: #006ec0; |
||||
|
color: #fff; |
||||
|
border: 0px; |
||||
|
float: right; |
||||
|
margin: 10px 0 30px; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
input.cb { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
label { |
||||
|
width: 20%; |
||||
|
display: inline-block; |
||||
|
font-size: 12pt; |
||||
|
padding-right: 10px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.left { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
div.ch-iv { |
||||
|
width: 100%; |
||||
|
background-color: #32b004; |
||||
|
display: inline-block; |
||||
|
margin-bottom: 20px; |
||||
|
padding-bottom: 20px; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
div.ch { |
||||
|
width: 250px; |
||||
|
min-height: 420px; |
||||
|
background-color: #006ec0; |
||||
|
display: inline-block; |
||||
|
margin-right: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
overflow: auto; |
||||
|
padding-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head { |
||||
|
color: #fff; |
||||
|
display: block; |
||||
|
width: 100%; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.subgrp { |
||||
|
float: left; |
||||
|
width: 250px; |
||||
|
} |
||||
|
|
||||
|
div.ch .unit, div.ch-iv .unit { |
||||
|
font-size: 19px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
div.ch .value, div.ch-iv .value { |
||||
|
margin-top: 20px; |
||||
|
font-size: 30px; |
||||
|
} |
||||
|
|
||||
|
div.ch .info, div.ch-iv .info { |
||||
|
margin-top: 3px; |
||||
|
font-size: 10px; |
||||
|
} |
||||
|
|
||||
|
div.ch .head { |
||||
|
background-color: #003c80; |
||||
|
padding: 10px 0 10px 0; |
||||
|
} |
||||
|
|
||||
|
div.ch-iv .head { |
||||
|
background-color: #1c6800; |
||||
|
padding: 10px 0 10px 0; |
||||
|
} |
||||
|
|
||||
|
div.iv { |
||||
|
max-width: 1060px; |
||||
|
} |
||||
|
|
||||
|
div.ch:last-child { |
||||
|
margin-right: 0px !important; |
||||
|
} |
||||
|
|
||||
|
#note { |
||||
|
margin: 50px 10px 10px 10px; |
||||
|
padding-top: 10px; |
||||
|
width: 100%; |
||||
|
border-top: 1px solid #bbb; |
||||
|
} |
@ -0,0 +1,432 @@ |
|||||
|
#include "main.h" |
||||
|
#include "version.h" |
||||
|
|
||||
|
#include "html/h/style_css.h" |
||||
|
#include "html/h/setup_html.h" |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
Main::Main(void) { |
||||
|
mDns = new DNSServer(); |
||||
|
mWeb = new ESP8266WebServer(80); |
||||
|
mUpdater = new ESP8266HTTPUpdateServer(); |
||||
|
mUdp = new WiFiUDP(); |
||||
|
|
||||
|
mApActive = true; |
||||
|
mWifiSettingsValid = false; |
||||
|
mSettingsValid = false; |
||||
|
|
||||
|
mLimit = 10; |
||||
|
mNextTryTs = 0; |
||||
|
mApLastTick = 0; |
||||
|
|
||||
|
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); |
||||
|
|
||||
|
memset(&mDeviceName, 0, DEVNAME_LEN); |
||||
|
|
||||
|
mEep = new eep(); |
||||
|
Serial.begin(115200); |
||||
|
|
||||
|
mUptimeSecs = 0; |
||||
|
mUptimeTicker = 0xffffffff; |
||||
|
mUptimeInterval = 1000; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::setup(uint32_t timeout) { |
||||
|
bool startAp = mApActive; |
||||
|
mLimit = timeout; |
||||
|
|
||||
|
mWeb->on("/setup", std::bind(&Main::showSetup, this)); |
||||
|
mWeb->on("/save", std::bind(&Main::showSave, this)); |
||||
|
mWeb->on("/uptime", std::bind(&Main::showUptime, this)); |
||||
|
mWeb->on("/time", std::bind(&Main::showTime, this)); |
||||
|
mWeb->on("/style.css", std::bind(&Main::showCss, this)); |
||||
|
mWeb->on("/reboot", std::bind(&Main::showReboot, this)); |
||||
|
mWeb->on("/factory", std::bind(&Main::showFactoryRst, this)); |
||||
|
mWeb->onNotFound ( std::bind(&Main::showNotFound, this)); |
||||
|
|
||||
|
startAp = getConfig(); |
||||
|
|
||||
|
#ifndef AP_ONLY |
||||
|
if(false == startAp) |
||||
|
startAp = setupStation(timeout); |
||||
|
#else |
||||
|
setupAp(WIFI_AP_SSID, WIFI_AP_PWD); |
||||
|
#endif |
||||
|
|
||||
|
if(!startAp) { |
||||
|
mTimestamp = getNtpTime(); |
||||
|
DPRINTLN("[NTP]: " + getDateTimeStr(getNtpTime())); |
||||
|
} |
||||
|
|
||||
|
mUpdater->setup(mWeb); |
||||
|
mApActive = startAp; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::loop(void) { |
||||
|
if(mApActive) { |
||||
|
mDns->processNextRequest(); |
||||
|
#ifndef AP_ONLY |
||||
|
if(checkTicker(&mNextTryTs, (WIFI_AP_ACTIVE_TIME * 1000))) { |
||||
|
mApLastTick = millis(); |
||||
|
mApActive = setupStation(mLimit); |
||||
|
if(mApActive) { |
||||
|
if(strlen(WIFI_AP_PWD) < 8) |
||||
|
DPRINTLN("ERROR: password must be at least 8 characters long"); |
||||
|
setupAp(WIFI_AP_SSID, WIFI_AP_PWD); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
if(millis() - mApLastTick > 10000) { |
||||
|
mApLastTick = millis(); |
||||
|
DPRINTLN("AP will be closed in " + String((mNextTryTs - mApLastTick) / 1000) + " seconds"); |
||||
|
} |
||||
|
} |
||||
|
#endif |
||||
|
} |
||||
|
mWeb->handleClient(); |
||||
|
|
||||
|
if(checkTicker(&mUptimeTicker, mUptimeInterval)) { |
||||
|
mUptimeSecs++; |
||||
|
mTimestamp++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
bool Main::getConfig(void) { |
||||
|
bool mApActive = false; |
||||
|
|
||||
|
mWifiSettingsValid = checkEEpCrc(ADDR_START, ADDR_WIFI_CRC, ADDR_WIFI_CRC); |
||||
|
mSettingsValid = checkEEpCrc(ADDR_START_SETTINGS, (ADDR_NEXT-ADDR_START_SETTINGS), ADDR_SETTINGS_CRC); |
||||
|
|
||||
|
if(mWifiSettingsValid) { |
||||
|
mEep->read(ADDR_SSID, mStationSsid, SSID_LEN); |
||||
|
mEep->read(ADDR_PWD, mStationPwd, PWD_LEN); |
||||
|
mEep->read(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN); |
||||
|
} |
||||
|
else { |
||||
|
/*mApActive = true;
|
||||
|
memset(mStationSsid, 0, SSID_LEN); |
||||
|
memset(mStationPwd, 0, PWD_LEN); |
||||
|
memset(mDeviceName, 0, DEVNAME_LEN); |
||||
|
|
||||
|
// erase application settings except wifi settings
|
||||
|
eraseSettings();*/ |
||||
|
snprintf(mStationSsid, SSID_LEN, "%s", FB_WIFI_SSID); |
||||
|
snprintf(mStationPwd, PWD_LEN, "%s", FB_WIFI_PWD); |
||||
|
snprintf(mDeviceName, DEVNAME_LEN, "%s", DEF_DEVICE_NAME); |
||||
|
} |
||||
|
|
||||
|
return mApActive; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::setupAp(const char *ssid, const char *pwd) { |
||||
|
IPAddress apIp(192, 168, 1, 1); |
||||
|
|
||||
|
DPRINTLN("\n---------\nAP MODE\nSSDI: " |
||||
|
+ String(ssid) + "\nPWD: " |
||||
|
+ String(pwd) + "\nActive for: " |
||||
|
+ String(WIFI_AP_ACTIVE_TIME) + " seconds" |
||||
|
+ "\n---------\n"); |
||||
|
DPRINTLN("DBG: " + String(mNextTryTs)); |
||||
|
|
||||
|
WiFi.mode(WIFI_AP); |
||||
|
WiFi.softAPConfig(apIp, apIp, IPAddress(255, 255, 255, 0)); |
||||
|
WiFi.softAP(ssid, pwd); |
||||
|
|
||||
|
mDns->start(mDnsPort, "*", apIp); |
||||
|
|
||||
|
mWeb->onNotFound([&]() { |
||||
|
showSetup(); |
||||
|
}); |
||||
|
mWeb->on("/", std::bind(&Main::showSetup, this)); |
||||
|
|
||||
|
mWeb->begin(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
bool Main::setupStation(uint32_t timeout) { |
||||
|
int32_t cnt; |
||||
|
bool startAp = false; |
||||
|
|
||||
|
if(timeout >= 3) |
||||
|
cnt = (timeout - 3) / 2 * 10; |
||||
|
else { |
||||
|
timeout = 1; |
||||
|
cnt = 1; |
||||
|
} |
||||
|
|
||||
|
WiFi.mode(WIFI_STA); |
||||
|
WiFi.begin(mStationSsid, mStationPwd); |
||||
|
if(String(mDeviceName) != "") |
||||
|
WiFi.hostname(mDeviceName); |
||||
|
|
||||
|
delay(2000); |
||||
|
DPRINTLN("connect to network '" + String(mStationSsid) + "' ..."); |
||||
|
while (WiFi.status() != WL_CONNECTED) { |
||||
|
delay(100); |
||||
|
if(cnt % 100 == 0) |
||||
|
Serial.println("."); |
||||
|
else |
||||
|
Serial.print("."); |
||||
|
|
||||
|
if(timeout > 0) { // limit == 0 -> no limit
|
||||
|
if(--cnt <= 0) { |
||||
|
if(WiFi.status() != WL_CONNECTED) { |
||||
|
startAp = true; |
||||
|
WiFi.disconnect(); |
||||
|
} |
||||
|
delay(100); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
Serial.println("."); |
||||
|
|
||||
|
if(false == startAp) { |
||||
|
mWeb->begin(); |
||||
|
} |
||||
|
|
||||
|
delay(1000); |
||||
|
|
||||
|
return startAp; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showSetup(void) { |
||||
|
String html = FPSTR(setup_html); |
||||
|
html.replace("{SSID}", mStationSsid); |
||||
|
// PWD will be left at the default value (for protection)
|
||||
|
// -> the PWD will only be changed if it does not match the default "{PWD}"
|
||||
|
html.replace("{DEVICE}", String(mDeviceName)); |
||||
|
html.replace("{VERSION}", String(mVersion)); |
||||
|
if(mApActive) |
||||
|
html.replace("{IP}", String("http://192.168.1.1")); |
||||
|
else |
||||
|
html.replace("{IP}", ("http://" + String(WiFi.localIP().toString()))); |
||||
|
|
||||
|
mWeb->send(200, "text/html", html); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showCss(void) { |
||||
|
mWeb->send(200, "text/css", FPSTR(style_css)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showSave(void) { |
||||
|
saveValues(true); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::saveValues(bool webSend = true) { |
||||
|
if(mWeb->args() > 0) { |
||||
|
if(mWeb->arg("ssid") != "") { |
||||
|
memset(mStationSsid, 0, SSID_LEN); |
||||
|
mWeb->arg("ssid").toCharArray(mStationSsid, SSID_LEN); |
||||
|
mEep->write(ADDR_SSID, mStationSsid, SSID_LEN); |
||||
|
|
||||
|
if(mWeb->arg("pwd") != "{PWD}") { |
||||
|
memset(mStationPwd, 0, PWD_LEN); |
||||
|
mWeb->arg("pwd").toCharArray(mStationPwd, PWD_LEN); |
||||
|
mEep->write(ADDR_PWD, mStationPwd, PWD_LEN); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
memset(mDeviceName, 0, DEVNAME_LEN); |
||||
|
mWeb->arg("device").toCharArray(mDeviceName, DEVNAME_LEN); |
||||
|
mEep->write(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN); |
||||
|
|
||||
|
|
||||
|
updateCrc(); |
||||
|
if(webSend) { |
||||
|
if(mWeb->arg("reboot") == "on") |
||||
|
showReboot(); |
||||
|
else // TODO: add device name as redirect in AP-mode
|
||||
|
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>" |
||||
|
"<p>saved</p></body></html>"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::updateCrc(void) { |
||||
|
uint16_t crc; |
||||
|
crc = buildEEpCrc(ADDR_START, ADDR_WIFI_CRC); |
||||
|
//Serial.println("new CRC: " + String(crc, HEX));
|
||||
|
mEep->write(ADDR_WIFI_CRC, crc); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showUptime(void) { |
||||
|
char time[20] = {0}; |
||||
|
|
||||
|
int upTimeSc = uint32_t((mUptimeSecs) % 60); |
||||
|
int upTimeMn = uint32_t((mUptimeSecs / (60)) % 60); |
||||
|
int upTimeHr = uint32_t((mUptimeSecs / (60 * 60)) % 24); |
||||
|
int upTimeDy = uint32_t((mUptimeSecs / (60 * 60 * 24)) % 365); |
||||
|
|
||||
|
snprintf(time, 20, "%d Tage, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc); |
||||
|
|
||||
|
mWeb->send(200, "text/plain", String(time)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showTime(void) { |
||||
|
mWeb->send(200, "text/plain", getDateTimeStr(mTimestamp)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showNotFound(void) { |
||||
|
String msg = "File Not Found\n\n"; |
||||
|
msg += "URI: "; |
||||
|
msg += mWeb->uri(); |
||||
|
msg += "\nMethod: "; |
||||
|
msg += ( mWeb->method() == HTTP_GET ) ? "GET" : "POST"; |
||||
|
msg += "\nArguments: "; |
||||
|
msg += mWeb->args(); |
||||
|
msg += "\n"; |
||||
|
|
||||
|
for(uint8_t i = 0; i < mWeb->args(); i++ ) { |
||||
|
msg += " " + mWeb->argName(i) + ": " + mWeb->arg(i) + "\n"; |
||||
|
} |
||||
|
|
||||
|
mWeb->send(404, "text/plain", msg); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showReboot(void) { |
||||
|
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"); |
||||
|
delay(1000); |
||||
|
ESP.restart(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::showFactoryRst(void) { |
||||
|
String content = ""; |
||||
|
int refresh = 3; |
||||
|
if(mWeb->args() > 0) { |
||||
|
if(mWeb->arg("reset").toInt() == 1) { |
||||
|
eraseSettings(true); |
||||
|
content = "factory reset: success\n\nrebooting ... "; |
||||
|
refresh = 10; |
||||
|
} |
||||
|
else { |
||||
|
content = "factory reset: aborted"; |
||||
|
refresh = 3; |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
content = "<h1>Factory Reset</h1>"; |
||||
|
content += "<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>"; |
||||
|
refresh = 120; |
||||
|
} |
||||
|
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"" + String(refresh) + "; URL=/\"></head><body>" + content + "</body></html>"); |
||||
|
if(refresh == 10) { |
||||
|
delay(1000); |
||||
|
ESP.restart(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
time_t Main::getNtpTime(void) { |
||||
|
time_t date = 0; |
||||
|
IPAddress timeServer; |
||||
|
uint8_t buf[NTP_PACKET_SIZE]; |
||||
|
uint8_t retry = 0; |
||||
|
|
||||
|
WiFi.hostByName (TIMESERVER_NAME, timeServer); |
||||
|
mUdp->begin(TIME_LOCAL_PORT); |
||||
|
|
||||
|
|
||||
|
sendNTPpacket(timeServer); |
||||
|
|
||||
|
while(retry++ < 5) { |
||||
|
int wait = 150; |
||||
|
while(--wait) { |
||||
|
if(NTP_PACKET_SIZE <= mUdp->parsePacket()) { |
||||
|
uint64_t secsSince1900; |
||||
|
mUdp->read(buf, NTP_PACKET_SIZE); |
||||
|
secsSince1900 = (buf[40] << 24); |
||||
|
secsSince1900 |= (buf[41] << 16); |
||||
|
secsSince1900 |= (buf[42] << 8); |
||||
|
secsSince1900 |= (buf[43] ); |
||||
|
|
||||
|
date = secsSince1900 - 2208988800UL; // UTC time
|
||||
|
date += (TIMEZONE + offsetDayLightSaving(date)) * 3600; |
||||
|
break; |
||||
|
} |
||||
|
else |
||||
|
delay(10); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return date; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void Main::sendNTPpacket(IPAddress& address) { |
||||
|
uint8_t buf[NTP_PACKET_SIZE] = {0}; |
||||
|
|
||||
|
buf[0] = B11100011; // LI, Version, Mode
|
||||
|
buf[1] = 0; // Stratum
|
||||
|
buf[2] = 6; // Max Interval between messages in seconds
|
||||
|
buf[3] = 0xEC; // Clock Precision
|
||||
|
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
|
buf[12] = 49; // four-byte reference ID identifying
|
||||
|
buf[13] = 0x4E; |
||||
|
buf[14] = 49; |
||||
|
buf[15] = 52; |
||||
|
|
||||
|
mUdp->beginPacket(address, 123); // NTP request, port 123
|
||||
|
mUdp->write(buf, NTP_PACKET_SIZE); |
||||
|
mUdp->endPacket(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
String Main::getDateTimeStr(time_t t) { |
||||
|
char str[20] = {0}; |
||||
|
sprintf(str, "%04d-%02d-%02d+%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); |
||||
|
return String(str); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC
|
||||
|
// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
|
time_t Main::offsetDayLightSaving (uint32_t local_t) { |
||||
|
int m = month (local_t); |
||||
|
if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
|
if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
|
int y = year (local_t); |
||||
|
int h = hour (local_t); |
||||
|
int hToday = (h + 24 * day(local_t)); |
||||
|
if((m == 3 && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7))) |
||||
|
|| (m == 10 && hToday < (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) ) |
||||
|
return 1; |
||||
|
else |
||||
|
return 0; |
||||
|
} |
@ -0,0 +1,125 @@ |
|||||
|
#ifndef __MAIN_H__ |
||||
|
#define __MAIN_H__ |
||||
|
|
||||
|
#include "Arduino.h" |
||||
|
|
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <DNSServer.h> |
||||
|
#include <ESP8266WebServer.h> |
||||
|
|
||||
|
#include <ESP8266HTTPUpdateServer.h> |
||||
|
|
||||
|
// NTP
|
||||
|
#include <WiFiUdp.h> |
||||
|
#include <TimeLib.h> |
||||
|
|
||||
|
#include "eep.h" |
||||
|
#include "defines.h" |
||||
|
#include "crc.h" |
||||
|
#include "debug.h" |
||||
|
|
||||
|
|
||||
|
const byte mDnsPort = 53; |
||||
|
|
||||
|
/* TIMESERVER CONFIG */ |
||||
|
#define TIMESERVER_NAME "pool.ntp.org" |
||||
|
#define TIME_LOCAL_PORT 8888 |
||||
|
#define NTP_PACKET_SIZE 48 |
||||
|
#define TIMEZONE 1 // Central European time +1
|
||||
|
|
||||
|
class Main { |
||||
|
public: |
||||
|
Main(void); |
||||
|
virtual void setup(uint32_t timeout); |
||||
|
virtual void loop(); |
||||
|
String getDateTimeStr (time_t t); |
||||
|
|
||||
|
|
||||
|
protected: |
||||
|
void showReboot(void); |
||||
|
virtual void saveValues(bool webSend); |
||||
|
virtual void updateCrc(void); |
||||
|
|
||||
|
inline uint16_t buildEEpCrc(uint32_t start, uint32_t length) { |
||||
|
uint8_t buf[length]; |
||||
|
mEep->read(start, buf, length); |
||||
|
return crc16(buf, length); |
||||
|
} |
||||
|
|
||||
|
bool checkEEpCrc(uint32_t start, uint32_t length, uint32_t crcPos) { |
||||
|
uint16_t crcRd, crcCheck; |
||||
|
crcCheck = buildEEpCrc(start, length); |
||||
|
mEep->read(crcPos, &crcRd); |
||||
|
return (crcCheck == crcRd); |
||||
|
} |
||||
|
|
||||
|
void eraseSettings(bool all = false) { |
||||
|
uint8_t buf[64] = {0}; |
||||
|
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS; |
||||
|
uint16_t end; |
||||
|
do { |
||||
|
end = addr + 64; |
||||
|
if(end > (ADDR_SETTINGS_CRC + 2)) |
||||
|
end = (ADDR_SETTINGS_CRC + 2); |
||||
|
DPRINTLN("erase: 0x" + String(addr, HEX) + " - 0x" + String(end, HEX)); |
||||
|
mEep->write(addr, buf, (end-addr)); |
||||
|
addr = end; |
||||
|
} while(addr < (ADDR_SETTINGS_CRC + 2)); |
||||
|
} |
||||
|
|
||||
|
inline bool checkTicker(uint32_t *ticker, uint32_t interval) { |
||||
|
uint32_t mil = millis(); |
||||
|
if(mil >= *ticker) { |
||||
|
*ticker = mil + interval; |
||||
|
return true; |
||||
|
} |
||||
|
else if(mil < (*ticker - interval)) { |
||||
|
*ticker = mil + interval; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
char mStationSsid[SSID_LEN]; |
||||
|
char mStationPwd[PWD_LEN]; |
||||
|
bool mWifiSettingsValid; |
||||
|
bool mSettingsValid; |
||||
|
bool mApActive; |
||||
|
ESP8266WebServer *mWeb; |
||||
|
char mVersion[9]; |
||||
|
char mDeviceName[DEVNAME_LEN]; |
||||
|
eep *mEep; |
||||
|
uint32_t mTimestamp; |
||||
|
uint32_t mLimit; |
||||
|
uint32_t mNextTryTs; |
||||
|
uint32_t mApLastTick; |
||||
|
|
||||
|
private: |
||||
|
bool getConfig(void); |
||||
|
void setupAp(const char *ssid, const char *pwd); |
||||
|
bool setupStation(uint32_t timeout); |
||||
|
|
||||
|
void showNotFound(void); |
||||
|
virtual void showSetup(void); |
||||
|
virtual void showSave(void); |
||||
|
void showUptime(void); |
||||
|
void showTime(void); |
||||
|
void showCss(void); |
||||
|
void showFactoryRst(void); |
||||
|
|
||||
|
time_t getNtpTime(void); |
||||
|
void sendNTPpacket(IPAddress& address); |
||||
|
time_t offsetDayLightSaving (uint32_t local_t); |
||||
|
|
||||
|
uint32_t mUptimeTicker; |
||||
|
uint16_t mUptimeInterval; |
||||
|
uint32_t mUptimeSecs; |
||||
|
|
||||
|
DNSServer *mDns; |
||||
|
ESP8266HTTPUpdateServer *mUpdater; |
||||
|
|
||||
|
WiFiUDP *mUdp; // for time server
|
||||
|
}; |
||||
|
|
||||
|
#endif /*__MAIN_H__*/ |
@ -0,0 +1,87 @@ |
|||||
|
#ifndef __MQTT_H__ |
||||
|
#define __MQTT_H__ |
||||
|
|
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <PubSubClient.h> |
||||
|
#include "defines.h" |
||||
|
|
||||
|
class mqtt { |
||||
|
public: |
||||
|
mqtt() { |
||||
|
mClient = new PubSubClient(mEspClient); |
||||
|
mAddressSet = false; |
||||
|
|
||||
|
memset(mUser, 0, MQTT_USER_LEN); |
||||
|
memset(mPwd, 0, MQTT_PWD_LEN); |
||||
|
memset(mTopic, 0, MQTT_TOPIC_LEN); |
||||
|
} |
||||
|
|
||||
|
~mqtt() { |
||||
|
delete mClient; |
||||
|
} |
||||
|
|
||||
|
void setup(const char *broker, const char *topic, const char *user, const char *pwd, uint16_t port) { |
||||
|
mAddressSet = true; |
||||
|
mClient->setServer(broker, port); |
||||
|
|
||||
|
snprintf(mUser, MQTT_USER_LEN, "%s", user); |
||||
|
snprintf(mPwd, MQTT_PWD_LEN, "%s", pwd); |
||||
|
snprintf(mTopic, MQTT_TOPIC_LEN, "%s", topic); |
||||
|
} |
||||
|
|
||||
|
void sendMsg(const char *topic, const char *msg) { |
||||
|
if(mAddressSet) { |
||||
|
char top[64]; |
||||
|
snprintf(top, 64, "%s/%s", mTopic, topic); |
||||
|
|
||||
|
if(!mClient->connected()) |
||||
|
reconnect(); |
||||
|
mClient->publish(top, msg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool isConnected(bool doRecon = false) { |
||||
|
if(doRecon) |
||||
|
reconnect(); |
||||
|
return mClient->connected(); |
||||
|
} |
||||
|
|
||||
|
char *getUser(void) { |
||||
|
return mUser; |
||||
|
} |
||||
|
|
||||
|
char *getPwd(void) { |
||||
|
return mPwd; |
||||
|
} |
||||
|
|
||||
|
char *getTopic(void) { |
||||
|
return mTopic; |
||||
|
} |
||||
|
|
||||
|
void loop() { |
||||
|
//if(!mClient->connected())
|
||||
|
// reconnect();
|
||||
|
mClient->loop(); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void reconnect(void) { |
||||
|
if(!mClient->connected()) { |
||||
|
String mqttId = "ESP-" + String(random(0xffff), HEX); |
||||
|
if((strlen(mUser) > 0) && (strlen(mPwd) > 0)) |
||||
|
mClient->connect(mqttId.c_str(), mUser, mPwd); |
||||
|
else |
||||
|
mClient->connect(mqttId.c_str()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
WiFiClient mEspClient; |
||||
|
PubSubClient *mClient; |
||||
|
|
||||
|
bool mAddressSet; |
||||
|
char mUser[MQTT_USER_LEN]; |
||||
|
char mPwd[MQTT_PWD_LEN]; |
||||
|
char mTopic[MQTT_TOPIC_LEN]; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__MQTT_H_*/ |
@ -0,0 +1,31 @@ |
|||||
|
|
||||
|
Microsoft Visual Studio Solution File, Format Version 12.00 |
||||
|
# Visual Studio Version 16 |
||||
|
VisualStudioVersion = 16.0.32002.261 |
||||
|
MinimumVisualStudioVersion = 10.0.40219.1 |
||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hmClassTest", "hmClassTest\hmClassTest.vcxproj", "{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}" |
||||
|
EndProject |
||||
|
Global |
||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
||||
|
Debug|x64 = Debug|x64 |
||||
|
Debug|x86 = Debug|x86 |
||||
|
Release|x64 = Release|x64 |
||||
|
Release|x86 = Release|x86 |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x64.ActiveCfg = Debug|x64 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x64.Build.0 = Debug|x64 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x86.ActiveCfg = Debug|Win32 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x86.Build.0 = Debug|Win32 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x64.ActiveCfg = Release|x64 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x64.Build.0 = Release|x64 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x86.ActiveCfg = Release|Win32 |
||||
|
{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x86.Build.0 = Release|Win32 |
||||
|
EndGlobalSection |
||||
|
GlobalSection(SolutionProperties) = preSolution |
||||
|
HideSolutionNode = FALSE |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ExtensibilityGlobals) = postSolution |
||||
|
SolutionGuid = {7C291F74-09F6-4C84-99E1-6E7294062385} |
||||
|
EndGlobalSection |
||||
|
EndGlobal |
@ -0,0 +1,147 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup Label="ProjectConfigurations"> |
||||
|
<ProjectConfiguration Include="Debug|Win32"> |
||||
|
<Configuration>Debug</Configuration> |
||||
|
<Platform>Win32</Platform> |
||||
|
</ProjectConfiguration> |
||||
|
<ProjectConfiguration Include="Release|Win32"> |
||||
|
<Configuration>Release</Configuration> |
||||
|
<Platform>Win32</Platform> |
||||
|
</ProjectConfiguration> |
||||
|
<ProjectConfiguration Include="Debug|x64"> |
||||
|
<Configuration>Debug</Configuration> |
||||
|
<Platform>x64</Platform> |
||||
|
</ProjectConfiguration> |
||||
|
<ProjectConfiguration Include="Release|x64"> |
||||
|
<Configuration>Release</Configuration> |
||||
|
<Platform>x64</Platform> |
||||
|
</ProjectConfiguration> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ClCompile Include="..\src\main.cpp" /> |
||||
|
</ItemGroup> |
||||
|
<PropertyGroup Label="Globals"> |
||||
|
<VCProjectVersion>16.0</VCProjectVersion> |
||||
|
<Keyword>Win32Proj</Keyword> |
||||
|
<ProjectGuid>{4d899c12-de0e-4cdb-b48c-fdfec331f219}</ProjectGuid> |
||||
|
<RootNamespace>hmClassTest</RootNamespace> |
||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> |
||||
|
<ConfigurationType>Application</ConfigurationType> |
||||
|
<UseDebugLibraries>true</UseDebugLibraries> |
||||
|
<PlatformToolset>v142</PlatformToolset> |
||||
|
<CharacterSet>Unicode</CharacterSet> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> |
||||
|
<ConfigurationType>Application</ConfigurationType> |
||||
|
<UseDebugLibraries>false</UseDebugLibraries> |
||||
|
<PlatformToolset>v142</PlatformToolset> |
||||
|
<WholeProgramOptimization>true</WholeProgramOptimization> |
||||
|
<CharacterSet>Unicode</CharacterSet> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> |
||||
|
<ConfigurationType>Application</ConfigurationType> |
||||
|
<UseDebugLibraries>true</UseDebugLibraries> |
||||
|
<PlatformToolset>v142</PlatformToolset> |
||||
|
<CharacterSet>Unicode</CharacterSet> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> |
||||
|
<ConfigurationType>Application</ConfigurationType> |
||||
|
<UseDebugLibraries>false</UseDebugLibraries> |
||||
|
<PlatformToolset>v142</PlatformToolset> |
||||
|
<WholeProgramOptimization>true</WholeProgramOptimization> |
||||
|
<CharacterSet>Unicode</CharacterSet> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
||||
|
<ImportGroup Label="ExtensionSettings"> |
||||
|
</ImportGroup> |
||||
|
<ImportGroup Label="Shared"> |
||||
|
</ImportGroup> |
||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
|
</ImportGroup> |
||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
|
</ImportGroup> |
||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
|
</ImportGroup> |
||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
|
</ImportGroup> |
||||
|
<PropertyGroup Label="UserMacros" /> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
|
<LinkIncremental>true</LinkIncremental> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
|
<LinkIncremental>false</LinkIncremental> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
|
<LinkIncremental>true</LinkIncremental> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
|
<LinkIncremental>false</LinkIncremental> |
||||
|
</PropertyGroup> |
||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
|
<ClCompile> |
||||
|
<WarningLevel>Level3</WarningLevel> |
||||
|
<SDLCheck>true</SDLCheck> |
||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
|
<ConformanceMode>true</ConformanceMode> |
||||
|
</ClCompile> |
||||
|
<Link> |
||||
|
<SubSystem>Console</SubSystem> |
||||
|
<GenerateDebugInformation>true</GenerateDebugInformation> |
||||
|
</Link> |
||||
|
</ItemDefinitionGroup> |
||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
|
<ClCompile> |
||||
|
<WarningLevel>Level3</WarningLevel> |
||||
|
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
|
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
|
<SDLCheck>true</SDLCheck> |
||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
|
<ConformanceMode>true</ConformanceMode> |
||||
|
</ClCompile> |
||||
|
<Link> |
||||
|
<SubSystem>Console</SubSystem> |
||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding> |
||||
|
<OptimizeReferences>true</OptimizeReferences> |
||||
|
<GenerateDebugInformation>true</GenerateDebugInformation> |
||||
|
</Link> |
||||
|
</ItemDefinitionGroup> |
||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
|
<ClCompile> |
||||
|
<WarningLevel>Level3</WarningLevel> |
||||
|
<SDLCheck>true</SDLCheck> |
||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
|
<ConformanceMode>true</ConformanceMode> |
||||
|
</ClCompile> |
||||
|
<Link> |
||||
|
<SubSystem>Console</SubSystem> |
||||
|
<GenerateDebugInformation>true</GenerateDebugInformation> |
||||
|
</Link> |
||||
|
</ItemDefinitionGroup> |
||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
|
<ClCompile> |
||||
|
<WarningLevel>Level3</WarningLevel> |
||||
|
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
|
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
|
<SDLCheck>true</SDLCheck> |
||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
|
<ConformanceMode>true</ConformanceMode> |
||||
|
</ClCompile> |
||||
|
<Link> |
||||
|
<SubSystem>Console</SubSystem> |
||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding> |
||||
|
<OptimizeReferences>true</OptimizeReferences> |
||||
|
<GenerateDebugInformation>true</GenerateDebugInformation> |
||||
|
</Link> |
||||
|
</ItemDefinitionGroup> |
||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
||||
|
<ImportGroup Label="ExtensionTargets"> |
||||
|
</ImportGroup> |
||||
|
</Project> |
@ -0,0 +1,22 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup> |
||||
|
<Filter Include="Quelldateien"> |
||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> |
||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions> |
||||
|
</Filter> |
||||
|
<Filter Include="Headerdateien"> |
||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> |
||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions> |
||||
|
</Filter> |
||||
|
<Filter Include="Ressourcendateien"> |
||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> |
||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> |
||||
|
</Filter> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ClCompile Include="..\src\main.cpp"> |
||||
|
<Filter>Quelldateien</Filter> |
||||
|
</ClCompile> |
||||
|
</ItemGroup> |
||||
|
</Project> |
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<PropertyGroup /> |
||||
|
</Project> |
@ -0,0 +1,105 @@ |
|||||
|
#ifdef _MSC_VER |
||||
|
#define _CRT_SECURE_NO_WARNINGS |
||||
|
#endif |
||||
|
|
||||
|
#include <cstdint> |
||||
|
#include <cstdio> |
||||
|
#include <cstdlib> |
||||
|
#include <cstring> |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
#define MAX_NUM_INVERTERS 3 |
||||
|
#define MAX_NAME_LENGTH 16 |
||||
|
#define NDEBUG |
||||
|
#define NO_RADIO |
||||
|
|
||||
|
#include "../../../hmDefines.h" |
||||
|
#include "../../../hmInverter.h" |
||||
|
#include "../../../hmSystem.h" |
||||
|
|
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
typedef int RadioType; |
||||
|
typedef int BufferType; |
||||
|
typedef Inverter<float> InverterType; |
||||
|
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType; |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void valToBuf(InverterType *iv, uint8_t fld, uint8_t ch, float val, uint8_t bufPos); |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
int main(int argc, char* argv[]) { |
||||
|
HmSystemType sys; |
||||
|
InverterType *iv0, *iv1; |
||||
|
uint8_t buf[30] = { 0xcc }; |
||||
|
|
||||
|
iv0 = sys.addInverter("HM1200", 0x1122334455ULL, INV_TYPE_HM1200); |
||||
|
iv1 = sys.addInverter("HM600", 0x1234567891ULL, INV_TYPE_HM600); |
||||
|
|
||||
|
valToBuf(iv0, FLD_UDC, CH1, 29.5, 3); |
||||
|
valToBuf(iv0, FLD_UDC, CH3, 30.6, 9); |
||||
|
valToBuf(iv0, FLD_YD, CH1, 1234, 5); |
||||
|
valToBuf(iv0, FLD_YD, CH2, 1199, 7); |
||||
|
valToBuf(iv0, FLD_YD, CH3, 899, 11); |
||||
|
valToBuf(iv0, FLD_YD, CH4, 932, 13); |
||||
|
valToBuf(iv0, FLD_YT, CH1, 40.123, 13); |
||||
|
valToBuf(iv0, FLD_YT, CH2, 57.231, 1); |
||||
|
valToBuf(iv0, FLD_YT, CH3, 59.372, 3); |
||||
|
valToBuf(iv0, FLD_YT, CH4, 43.966, 7); |
||||
|
|
||||
|
iv0->doCalculations(); |
||||
|
for(uint8_t i = 0; i < iv0->listLen; i ++) { |
||||
|
float val = iv0->getValue(i); |
||||
|
if(0.0 != val) { |
||||
|
printf("%10s [CH%d] = %.3f %s\n", iv0->getFieldName(i), iv0->getChannel(i), val, iv0->getUnit(i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
void valToBuf(InverterType *iv, uint8_t fld, uint8_t ch, float val, uint8_t bufPos) { |
||||
|
uint8_t buf[30] = { 0xcc }; |
||||
|
uint8_t len; |
||||
|
uint16_t factor; |
||||
|
|
||||
|
switch(fld) { |
||||
|
default: len = 2; break; |
||||
|
case FLD_YT: len = 4; break; |
||||
|
} |
||||
|
|
||||
|
switch(fld) { |
||||
|
case FLD_YD: factor = 1; break; |
||||
|
case FLD_UDC: |
||||
|
case FLD_PDC: |
||||
|
case FLD_UAC: |
||||
|
case FLD_PAC: |
||||
|
case FLD_PCT: |
||||
|
case FLD_T: factor = 10; break; |
||||
|
case FLD_IDC: |
||||
|
case FLD_IAC: |
||||
|
case FLD_F: factor = 100; break; |
||||
|
default: factor = 1000; break; |
||||
|
} |
||||
|
|
||||
|
uint8_t *p = &buf[bufPos]; |
||||
|
|
||||
|
uint32_t intval = (uint32_t)(val * factor); |
||||
|
if(2 == len) { |
||||
|
p[0] = (intval >> 8) & 0xff; |
||||
|
p[1] = (intval ) & 0xff; |
||||
|
} |
||||
|
else { |
||||
|
p[0] = (intval >> 24) & 0xff; |
||||
|
p[1] = (intval >> 16) & 0xff; |
||||
|
p[2] = (intval >> 8) & 0xff; |
||||
|
p[3] = (intval ) & 0xff; |
||||
|
} |
||||
|
iv->addValue(iv->getPosByChFld(ch, fld), buf); |
||||
|
} |
@ -1,2 +1,3 @@ |
|||||
paho-mqtt |
paho-mqtt>=1.5 |
||||
crcmod |
crcmod>=1.7 |
||||
|
PyYAML>=5.0 |
||||
|
@ -1,84 +0,0 @@ |
|||||
#!/usr/bin/env python3 |
|
||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
import sys |
|
||||
import codecs |
|
||||
import re |
|
||||
import time |
|
||||
from datetime import datetime |
|
||||
import hoymiles |
|
||||
|
|
||||
logdata = """ |
|
||||
2022-05-01 12:29:02.139673 Transmit 368223: channel=40 len=27 ack=False | 15 72 22 01 43 78 56 34 12 80 0b 00 62 6e 60 ee 00 00 00 05 00 00 00 00 7e 58 25 |
|
||||
2022-05-01 12:29:02.184796 Received 27 bytes on channel 3 after tx 6912328ns: 95 72 22 01 43 72 22 01 43 01 00 01 01 4e 00 9d 02 0a 01 50 00 9d 02 10 00 00 91 |
|
||||
2022-05-01 12:29:02.184796 Decoder src=72220143, dst=72220143, cmd=1, u1=33.4V, i1=1.57A, p1=52.2W, u2=33.6V, i2=1.57A, p2=52.8W, uk1=1, uk2=0 |
|
||||
2022-05-01 12:29:02.226251 Received 27 bytes on channel 75 after tx 48355619ns: 95 72 22 01 43 72 22 01 43 02 88 1f 00 00 7f 08 00 94 00 97 08 e2 13 89 03 eb ec |
|
||||
2022-05-01 12:29:02.226251 Decoder src=72220143, dst=72220143, cmd=2, ac_u1=227.4V, ac_f=50.01Hz, ac_p1=100.3W, uk1=34847, uk2=0, uk3=32520, uk4=148, uk5=151 |
|
||||
2022-05-01 12:29:02.273766 Received 23 bytes on channel 75 after tx 95876606ns: 95 72 22 01 43 72 22 01 43 83 00 01 00 2c 03 e8 00 d8 00 06 0c 35 37 |
|
||||
2022-05-01 12:29:02.273766 Decoder src=72220143, dst=72220143, cmd=131, ac_i1=0.44A, t=21.60C, uk1=1, uk3=1000, uk5=6, uk6=3125 |
|
||||
""" |
|
||||
|
|
||||
def payload_from_log(line): |
|
||||
values = re.match(r'(?P<datetime>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+) Received.*: (?P<data>[0-9a-z ]+)$', line) |
|
||||
if values: |
|
||||
payload=values.group('data') |
|
||||
return hoymiles.InverterPacketFragment( |
|
||||
time_rx=datetime.strptime(values.group('datetime'), '%Y-%m-%d %H:%M:%S.%f'), |
|
||||
payload=bytes.fromhex(payload) |
|
||||
) |
|
||||
|
|
||||
with open('example-logs/example.log', 'r') as fh: |
|
||||
for line in fh: |
|
||||
kind = re.match(r'\d{4}-\d{2}-\d{2} \d\d:\d\d:\d\d.\d+ (?P<type>Transmit|Received)', line) |
|
||||
if kind: |
|
||||
if kind.group('type') == 'Transmit': |
|
||||
u, data = line.split('|') |
|
||||
rx_buffer = hoymiles.InverterTransaction( |
|
||||
request=bytes.fromhex(data)) |
|
||||
|
|
||||
elif kind.group('type') == 'Received': |
|
||||
try: |
|
||||
payload = payload_from_log(line) |
|
||||
print(payload) |
|
||||
except BufferError as err: |
|
||||
print(f'Debug: {err}') |
|
||||
payload = None |
|
||||
pass |
|
||||
if payload: |
|
||||
rx_buffer.frame_append(payload) |
|
||||
try: |
|
||||
#packet = rx_buffer.get_payload(72220143) |
|
||||
packet = rx_buffer.get_payload() |
|
||||
except BufferError as err: |
|
||||
print(f'Debug: {err}') |
|
||||
packet = None |
|
||||
pass |
|
||||
|
|
||||
if packet: |
|
||||
plen = len(packet) |
|
||||
dt = rx_buffer.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f") |
|
||||
iv = hoymiles.hm600_0b_response_decode(packet) |
|
||||
|
|
||||
print(f'{dt} Decoded: {plen}', end='') |
|
||||
print(f' string1=', end='') |
|
||||
print(f' {iv.dc_voltage_0}VDC', end='') |
|
||||
print(f' {iv.dc_current_0}A', end='') |
|
||||
print(f' {iv.dc_power_0}W', end='') |
|
||||
print(f' {iv.dc_energy_total_0}Wh', end='') |
|
||||
print(f' {iv.dc_energy_daily_0}Wh/day', end='') |
|
||||
print(f' string2=', end='') |
|
||||
print(f' {iv.dc_voltage_1}VDC', end='') |
|
||||
print(f' {iv.dc_current_1}A', end='') |
|
||||
print(f' {iv.dc_power_1}W', end='') |
|
||||
print(f' {iv.dc_energy_total_1}Wh', end='') |
|
||||
print(f' {iv.dc_energy_daily_1}Wh/day', end='') |
|
||||
print(f' phase1=', end='') |
|
||||
print(f' {iv.ac_voltage_0}VAC', end='') |
|
||||
print(f' {iv.ac_current_0}A', end='') |
|
||||
print(f' {iv.ac_power_0}W', end='') |
|
||||
print(f' inverter=', end='') |
|
||||
print(f' {iv.ac_frequency}Hz', end='') |
|
||||
print(f' {iv.temperature}°C', end='') |
|
||||
print() |
|
||||
|
|
||||
print('', end='', flush=True) |
|
Loading…
Reference in new issue