From c0140f9fc16fcd4b1dd3d89d43723985e0717d8e Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 28 Apr 2022 13:25:04 +0200 Subject: [PATCH] Neue Version von Hubi vom 28.04.2022 - siehe: https://www.mikrocontroller.net/topic/525778?page=4#7048605 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- tools/HoyDtuSim/CircularBuffer.h | 158 ++++++++ tools/HoyDtuSim/Debug.h | 23 ++ tools/HoyDtuSim/HM1200.h | 38 ++ tools/HoyDtuSim/HM600.h | 37 ++ tools/HoyDtuSim/HoyDtuSim.ino | 605 +++++++++++++++++++++++++++++++ tools/HoyDtuSim/Inverters.h | 283 +++++++++++++++ tools/HoyDtuSim/ModWebserver.h | 151 ++++++++ tools/HoyDtuSim/Settings.h | 69 ++++ tools/HoyDtuSim/Sonne.h | 55 +++ tools/HoyDtuSim/hm_crc.h | 102 ++++++ tools/HoyDtuSim/hm_packets.h | 93 +++++ tools/HoyDtuSim/wifi.h | 345 ++++++++++++++++++ 12 files changed, 1959 insertions(+) create mode 100644 tools/HoyDtuSim/CircularBuffer.h create mode 100644 tools/HoyDtuSim/Debug.h create mode 100644 tools/HoyDtuSim/HM1200.h create mode 100644 tools/HoyDtuSim/HM600.h create mode 100644 tools/HoyDtuSim/HoyDtuSim.ino create mode 100644 tools/HoyDtuSim/Inverters.h create mode 100644 tools/HoyDtuSim/ModWebserver.h create mode 100644 tools/HoyDtuSim/Settings.h create mode 100644 tools/HoyDtuSim/Sonne.h create mode 100644 tools/HoyDtuSim/hm_crc.h create mode 100644 tools/HoyDtuSim/hm_packets.h create mode 100644 tools/HoyDtuSim/wifi.h diff --git a/tools/HoyDtuSim/CircularBuffer.h b/tools/HoyDtuSim/CircularBuffer.h new file mode 100644 index 00000000..a7fafdb7 --- /dev/null +++ b/tools/HoyDtuSim/CircularBuffer.h @@ -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 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 diff --git a/tools/HoyDtuSim/Debug.h b/tools/HoyDtuSim/Debug.h new file mode 100644 index 00000000..3b2807d6 --- /dev/null +++ b/tools/HoyDtuSim/Debug.h @@ -0,0 +1,23 @@ +#ifndef __DEBUG_H + +#define __DEBUG_H + +#ifdef DEBUG + #define DEBUG_OUT Serial +#else +//--- +// disable Serial DEBUG output + #define DEBUG_OUT DummySerial + static class { + public: + void begin(...) {} + void print(...) {} + void println(...) {} + void flush() {} + bool available() { return false;} + int readBytes(...) { return 0;} + int printf (...) {return 0;} + } DummySerial; +#endif + +#endif diff --git a/tools/HoyDtuSim/HM1200.h b/tools/HoyDtuSim/HM1200.h new file mode 100644 index 00000000..c8c27d87 --- /dev/null +++ b/tools/HoyDtuSim/HM1200.h @@ -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 diff --git a/tools/HoyDtuSim/HM600.h b/tools/HoyDtuSim/HM600.h new file mode 100644 index 00000000..9ba5c1a8 --- /dev/null +++ b/tools/HoyDtuSim/HM600.h @@ -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 diff --git a/tools/HoyDtuSim/HoyDtuSim.ino b/tools/HoyDtuSim/HoyDtuSim.ino new file mode 100644 index 00000000..87423e49 --- /dev/null +++ b/tools/HoyDtuSim/HoyDtuSim.ino @@ -0,0 +1,605 @@ +#include +#include +#include "CircularBuffer.h" +#include +#include "printf.h" +#include +#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 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 +} diff --git a/tools/HoyDtuSim/Inverters.h b/tools/HoyDtuSim/Inverters.h new file mode 100644 index 00000000..af929af2 --- /dev/null +++ b/tools/HoyDtuSim/Inverters.h @@ -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 diff --git a/tools/HoyDtuSim/ModWebserver.h b/tools/HoyDtuSim/ModWebserver.h new file mode 100644 index 00000000..13c88df2 --- /dev/null +++ b/tools/HoyDtuSim/ModWebserver.h @@ -0,0 +1,151 @@ +// ################# WebServer ################# + +#ifndef __MODWEBSERVER_H +#define __MODWEBSERVER_H +#define MODWEBSERVER + +#include +#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 = ""; + out += "

Hilfe

"; + out += "

"; + out += ""; + out += ""; + out += ""; + out += ""; + out += "
/zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden
/datazum Abruf der Messwerte in der Form Name=wert
:{port+1}/updateOTA
/rebootstartet neu
"; + server.send (200, "text/html", out); +} + + +void handleReboot () { + //------------------- + returnOK (); + ESP.reset(); +} + + +void handleRoot() { + //---------------- + String out = ""; + out += ""; + out += "

Hoymiles Micro-Inverters

"; + char floatString[20]; + char line[100]; + for (uint8_t wr = 0; wr < anzInv; wr++) { + out += "

" + String(inverters[wr].name) + "

"; + out += "

S/N " + String (getSerialNoTxt(wr)) + "

"; + out += "

"; + out += ""; + for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { + dtostrf (getMeasureValue(wr, i),1, getDigits(wr,i), floatString); + sprintf(line, "", getMeasureName(wr, i), floatString, getUnit(wr, i)); + //DEBUG_OUT.println(line); + out += String(line); +/* out += ""; + out += ""; + out += ""; */ + } + out += "
KanalWertEinheit
%s%s%s
" + getMeasureName(i) + "" + String(getMeasureValue(i)) + "
" + String(getUnit(i)) + "
"; + } + int pos = out.indexOf("°"); + do { + if (pos>1) { + out = out.substring (0, pos) + "°" + out.substring(pos+2); + } + pos = out.indexOf("°"); + } while (pos>1); + + out += ""; + 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 + +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 diff --git a/tools/HoyDtuSim/Settings.h b/tools/HoyDtuSim/Settings.h new file mode 100644 index 00000000..3e263fd5 --- /dev/null +++ b/tools/HoyDtuSim/Settings.h @@ -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 diff --git a/tools/HoyDtuSim/Sonne.h b/tools/HoyDtuSim/Sonne.h new file mode 100644 index 00000000..e80a8356 --- /dev/null +++ b/tools/HoyDtuSim/Sonne.h @@ -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 diff --git a/tools/HoyDtuSim/hm_crc.h b/tools/HoyDtuSim/hm_crc.h new file mode 100644 index 00000000..8a029619 --- /dev/null +++ b/tools/HoyDtuSim/hm_crc.h @@ -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 diff --git a/tools/HoyDtuSim/hm_packets.h b/tools/HoyDtuSim/hm_packets.h new file mode 100644 index 00000000..c6959cd9 --- /dev/null +++ b/tools/HoyDtuSim/hm_packets.h @@ -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 diff --git a/tools/HoyDtuSim/wifi.h b/tools/HoyDtuSim/wifi.h new file mode 100644 index 00000000..d98cc40f --- /dev/null +++ b/tools/HoyDtuSim/wifi.h @@ -0,0 +1,345 @@ +#ifndef __WIFI_H +#define __WIFI_H + +#include "Settings.h" +#include "Debug.h" +#include +#include // 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 +#include + +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