mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-12 16:36:38 +02:00
Merge branch 'main' into dev
# Conflicts: # tools/esp8266/defines.h # tools/esp8266/hmSystem.h
This commit is contained in:
commit
c8a05efadc
33 changed files with 3714 additions and 17 deletions
|
@ -7,6 +7,11 @@ In particular:
|
||||||
|
|
||||||
* `doc/hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
|
* `doc/hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
|
||||||
* `doc/getting-started-ESP8266.md` shows the hardware setup for an ESP8266-based system
|
* `doc/getting-started-ESP8266.md` shows the hardware setup for an ESP8266-based system
|
||||||
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32
|
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32:
|
||||||
|
* A [version for ESP8266](tools/esp8266) that includes a web interface
|
||||||
|
* A [version for Arduino Nano](tools/nano/NRF24_SendRcv)
|
||||||
|
* An [alternative Version of the above](tools/NRF24_SendRcv)
|
||||||
|
* A [different implementation](tools/HoyDtuSim)
|
||||||
|
* An [implementation for Raspberry Pi](tools/rpi) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker.
|
||||||
|
|
||||||
Contributors are always welcome!
|
Contributors are always welcome!
|
||||||
|
|
Binary file not shown.
158
tools/HoyDtuSim/CircularBuffer.h
Normal file
158
tools/HoyDtuSim/CircularBuffer.h
Normal file
|
@ -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
|
23
tools/HoyDtuSim/Debug.h
Normal file
23
tools/HoyDtuSim/Debug.h
Normal file
|
@ -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
|
38
tools/HoyDtuSim/HM1200.h
Normal file
38
tools/HoyDtuSim/HM1200.h
Normal file
|
@ -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
|
37
tools/HoyDtuSim/HM600.h
Normal file
37
tools/HoyDtuSim/HM600.h
Normal file
|
@ -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
|
605
tools/HoyDtuSim/HoyDtuSim.ino
Normal file
605
tools/HoyDtuSim/HoyDtuSim.ino
Normal file
|
@ -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
|
||||||
|
}
|
283
tools/HoyDtuSim/Inverters.h
Normal file
283
tools/HoyDtuSim/Inverters.h
Normal file
|
@ -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
|
151
tools/HoyDtuSim/ModWebserver.h
Normal file
151
tools/HoyDtuSim/ModWebserver.h
Normal file
|
@ -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
|
69
tools/HoyDtuSim/Settings.h
Normal file
69
tools/HoyDtuSim/Settings.h
Normal file
|
@ -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
|
55
tools/HoyDtuSim/Sonne.h
Normal file
55
tools/HoyDtuSim/Sonne.h
Normal file
|
@ -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
|
102
tools/HoyDtuSim/hm_crc.h
Normal file
102
tools/HoyDtuSim/hm_crc.h
Normal file
|
@ -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
|
93
tools/HoyDtuSim/hm_packets.h
Normal file
93
tools/HoyDtuSim/hm_packets.h
Normal file
|
@ -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
|
345
tools/HoyDtuSim/wifi.h
Normal file
345
tools/HoyDtuSim/wifi.h
Normal file
|
@ -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
|
158
tools/NRF24_SendRcv/CircularBuffer.h
Normal file
158
tools/NRF24_SendRcv/CircularBuffer.h
Normal file
|
@ -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
|
23
tools/NRF24_SendRcv/Debug.h
Normal file
23
tools/NRF24_SendRcv/Debug.h
Normal file
|
@ -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
|
129
tools/NRF24_SendRcv/ModWebserver.h
Normal file
129
tools/NRF24_SendRcv/ModWebserver.h
Normal file
|
@ -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
|
597
tools/NRF24_SendRcv/NRF24_SendRcv.ino
Normal file
597
tools/NRF24_SendRcv/NRF24_SendRcv.ino
Normal file
|
@ -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;
|
||||||
|
}
|
55
tools/NRF24_SendRcv/NRF24_sniff_types.h
Normal file
55
tools/NRF24_SendRcv/NRF24_sniff_types.h
Normal file
|
@ -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
|
82
tools/NRF24_SendRcv/Settings.h
Normal file
82
tools/NRF24_SendRcv/Settings.h
Normal file
|
@ -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
|
55
tools/NRF24_SendRcv/Sonne.h
Normal file
55
tools/NRF24_SendRcv/Sonne.h
Normal file
|
@ -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<6D>
|
||||||
|
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
|
142
tools/NRF24_SendRcv/hm_crc.cpp
Normal file
142
tools/NRF24_SendRcv/hm_crc.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
8
tools/NRF24_SendRcv/hm_crc.h
Normal file
8
tools/NRF24_SendRcv/hm_crc.h
Normal file
|
@ -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);
|
74
tools/NRF24_SendRcv/hm_packets.cpp
Normal file
74
tools/NRF24_SendRcv/hm_packets.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
18
tools/NRF24_SendRcv/hm_packets.h
Normal file
18
tools/NRF24_SendRcv/hm_packets.h
Normal file
|
@ -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);
|
||||||
|
};
|
345
tools/NRF24_SendRcv/wifi.h
Normal file
345
tools/NRF24_SendRcv/wifi.h
Normal file
|
@ -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
|
|
@ -13,6 +13,13 @@ This code can be compiled using Arduino. The settings were:
|
||||||
- Board: Generic ESP8266 Module
|
- Board: Generic ESP8266 Module
|
||||||
- Flash-Size: 1MB (FS: none, OTA: 502kB)
|
- 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
|
## Flash ESP with firmware
|
||||||
|
|
||||||
|
@ -21,12 +28,12 @@ This code can be compiled using Arduino. The settings were:
|
||||||
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
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
|
4. connect to the AP, you will be forwarded to the setup page
|
||||||
5. configure your WiFi settings, save, repower
|
5. configure your WiFi settings, save, repower
|
||||||
6. check your router for the IP address of the module
|
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
|
## Usage
|
||||||
|
|
||||||
Connect the ESP to power and to your serial console. The webinterface has the following abilities:
|
Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities:
|
||||||
|
|
||||||
- OTA Update (over the air update)
|
- OTA Update (over the air update)
|
||||||
- Configuration (Wifi, inverter(s), Pinout, MQTT)
|
- Configuration (Wifi, inverter(s), Pinout, MQTT)
|
||||||
|
@ -40,11 +47,14 @@ The serial console will print the converted values which were read out of the in
|
||||||
|
|
||||||
For now the following inverters should work out of the box:
|
For now the following inverters should work out of the box:
|
||||||
|
|
||||||
|
- HM400
|
||||||
- HM600
|
- HM600
|
||||||
|
- HM800
|
||||||
- HM1200
|
- HM1200
|
||||||
|
|
||||||
## USED LIBRARIES
|
## USED LIBRARIES
|
||||||
|
|
||||||
- `Time`
|
- `Time`
|
||||||
- `RF24`
|
- `RF24`
|
||||||
|
- `PubSubClient`
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ void app::loop(void) {
|
||||||
if(NULL != inv) {
|
if(NULL != inv) {
|
||||||
mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp);
|
mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp);
|
||||||
yield();
|
yield();
|
||||||
delay(100);
|
//delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ void app::loop(void) {
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
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));
|
snprintf(val, 10, "%.3f", iv->getValue(i));
|
||||||
mMqtt.sendMsg(topic, val);
|
mMqtt.sendMsg(topic, val);
|
||||||
delay(20);
|
//delay(20);
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,6 +409,8 @@ void app::showLiveData(void) {
|
||||||
case INV_TYPE_HM1200: modNum = 4; break;
|
case INV_TYPE_HM1200: modNum = 4; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modHtml += "<div class=\"ch-group\"><h3>" + String(iv->name) + "</h3>";
|
||||||
|
|
||||||
for(uint8_t ch = 1; ch <= modNum; ch ++) {
|
for(uint8_t ch = 1; ch <= modNum; ch ++) {
|
||||||
modHtml += "<div class=\"ch\"><span class=\"head\">CHANNEL " + String(ch) + "</span>";
|
modHtml += "<div class=\"ch\"><span class=\"head\">CHANNEL " + String(ch) + "</span>";
|
||||||
for(uint8_t j = 0; j < 5; j++) {
|
for(uint8_t j = 0; j < 5; j++) {
|
||||||
|
@ -427,6 +429,8 @@ void app::showLiveData(void) {
|
||||||
}
|
}
|
||||||
modHtml += "</div>";
|
modHtml += "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modHtml += "</div>";
|
||||||
#else
|
#else
|
||||||
// dump all data to web frontend
|
// dump all data to web frontend
|
||||||
modHtml = "<pre>";
|
modHtml = "<pre>";
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 2
|
#define VERSION_MINOR 2
|
||||||
#define VERSION_PATCH 11
|
#define VERSION_PATCH 12
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
|
|
@ -26,9 +26,9 @@ enum {CH0 = 0, CH1, CH2, CH3, CH4};
|
||||||
// received command ids, special command CMDFF for calculations
|
// received command ids, special command CMDFF for calculations
|
||||||
enum {CMD01 = 0x01, CMD02, CMD03, CMD82 = 0x82, CMD83, CMD84, CMDFF=0xff};
|
enum {CMD01 = 0x01, CMD02, CMD03, CMD82 = 0x82, CMD83, CMD84, CMDFF=0xff};
|
||||||
|
|
||||||
enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400};
|
enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400, INV_TYPE_HM800};
|
||||||
const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400"};
|
const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400", "HM800"};
|
||||||
#define NUM_INVERTER_TYPES 3
|
#define NUM_INVERTER_TYPES 4
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t fieldId; // field id
|
uint8_t fieldId; // field id
|
||||||
|
@ -54,7 +54,7 @@ const byteAssign_t hm400assignment[] = {
|
||||||
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
|
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
|
||||||
{ FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 },
|
{ FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 },
|
||||||
{ FLD_YT, UNIT_KWH, CH1, CMD01, 9, 4, 1000 },
|
{ FLD_YT, UNIT_KWH, CH1, CMD01, 9, 4, 1000 },
|
||||||
{ FLD_YD, UNIT_WH, CH1, CMD01, 13, 2, 1000 },
|
{ FLD_YD, UNIT_WH, CH1, CMD01, 13, 2, 1 },
|
||||||
{ FLD_UAC, UNIT_V, CH0, CMD01, 15, 2, 10 },
|
{ FLD_UAC, UNIT_V, CH0, CMD01, 15, 2, 10 },
|
||||||
{ FLD_F, UNIT_HZ, CH0, CMD82, 1, 2, 100 },
|
{ FLD_F, UNIT_HZ, CH0, CMD82, 1, 2, 100 },
|
||||||
{ FLD_PAC, UNIT_W, CH0, CMD82, 3, 2, 10 },
|
{ FLD_PAC, UNIT_W, CH0, CMD82, 3, 2, 10 },
|
||||||
|
@ -86,6 +86,30 @@ const byteAssign_t hm600assignment[] = {
|
||||||
#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t))
|
#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
|
// HM1200, HM1500
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
|
|
@ -81,21 +81,26 @@ class Inverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAssignment(void) {
|
void getAssignment(void) {
|
||||||
if(INV_TYPE_HM600 == type) {
|
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);
|
listLen = (uint8_t)(HM600_LIST_LEN);
|
||||||
assign = (byteAssign_t*)hm600assignment;
|
assign = (byteAssign_t*)hm600assignment;
|
||||||
channels = 2;
|
channels = 2;
|
||||||
}
|
}
|
||||||
|
else if(INV_TYPE_HM800 == p->type) {
|
||||||
|
listLen = (uint8_t)(HM800_LIST_LEN);
|
||||||
|
assign = (byteAssign_t*)hm800assignment;
|
||||||
|
channels = 2;
|
||||||
|
}
|
||||||
else if(INV_TYPE_HM1200 == type) {
|
else if(INV_TYPE_HM1200 == type) {
|
||||||
listLen = (uint8_t)(HM1200_LIST_LEN);
|
listLen = (uint8_t)(HM1200_LIST_LEN);
|
||||||
assign = (byteAssign_t*)hm1200assignment;
|
assign = (byteAssign_t*)hm1200assignment;
|
||||||
channels = 4;
|
channels = 4;
|
||||||
}
|
}
|
||||||
else if(INV_TYPE_HM400 == type) {
|
|
||||||
listLen = (uint8_t)(HM400_LIST_LEN);
|
|
||||||
assign = (byteAssign_t*)hm400assignment;
|
|
||||||
channels = 1;
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
listLen = 0;
|
listLen = 0;
|
||||||
channels = 0;
|
channels = 0;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#ifndef __STYLE_H__
|
#ifndef __STYLE_H__
|
||||||
#define __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:14pt;color:#006ec0;}.subdes {font-size:13pt;color:#006ec0;margin-left:7px;}.fw {width:60px;display:block;float:left;}.color {width:50px;height:50px;border:1px solid #ccc;}.range {width:300px;}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%;}#footer p {color:#fff;padding-left:20px;padding-right:20px;font-size:10pt !important;}#footer a {color:#fff;}div.content {background-color:#fff;padding-bottom:65px;overflow:hidden;}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 {width:250px;height:410px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;}div.ch .value, div.ch .info, div.ch .head {color:#fff;display:block;width:100%;text-align:center;}div.ch .unit {font-size:19px;margin-left:10px;}div.ch .value {margin-top:20px;font-size:30px;}div.ch .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}";
|
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:14pt;color:#006ec0;}.subdes {font-size:13pt;color:#006ec0;margin-left:7px;}.fw {width:60px;display:block;float:left;}.color {width:50px;height:50px;border:1px solid #ccc;}.range {width:300px;}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%;}#footer p {color:#fff;padding-left:20px;padding-right:20px;font-size:10pt !important;}#footer a {color:#fff;}div.content {background-color:#fff;padding-bottom:65px;overflow:hidden;}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-group {display:inline-block;}div.ch {width:250px;height:410px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;}div.ch .value, div.ch .info, div.ch .head {color:#fff;display:block;width:100%;text-align:center;}div.ch .unit {font-size:19px;margin-left:10px;}div.ch .value {margin-top:20px;font-size:30px;}div.ch .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}";
|
||||||
#endif /*__STYLE_H__*/
|
#endif /*__STYLE_H__*/
|
||||||
|
|
|
@ -136,6 +136,10 @@ label {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.ch-group {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
div.ch {
|
div.ch {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 410px;
|
height: 410px;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue