mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-28 09:46:26 +02:00
Neue Version von Hubi vom 28.04.2022 - siehe:
https://www.mikrocontroller.net/topic/525778?page=4#7048605 ersetzt NRF24_SendRcv Kommentare dazu: - Projekt jetzt umgenannt in HoyDtuSim (Hoymiles DTU Simulation) -Läuft auf Arduino (bei mir auf Pro Mini) und ESP (Wemos D1 mini), je nachdem wie man kompiliert - Channel hopping für senden und Empfangen (poor man's ...) ist eingebaut und bringt konstante Antworten; obige Erkenntnisse über Kanäle abwärts sind noch nicht eingebaut - da manchmal ein Abbruch der RF-Verbindung vorkam (auch schon oben erwähnt) wird jetzt nach ca 50 Sekunden ohne Empfang das RF-Modul neu initialisiert und es geht problemlos weiter - Definitionen für HM-600 und HM-1200 sind implementiert, andere können anhand der beiden Beispiele sicher leicht impl. werden - Anpassungen sind in der Settings.h zu machen
This commit is contained in:
parent
20cd77a6c2
commit
c0140f9fc1
12 changed files with 1959 additions and 0 deletions
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
|
Loading…
Add table
Reference in a new issue