mirror of https://github.com/lumapu/ahoy.git
				
				
			
				 67 changed files with 7717 additions and 564 deletions
			
			
		
								
									Binary file not shown.
								
							
						
					| After Width: | Height: | Size: 62 KiB | 
| After Width: | Height: | Size: 32 KiB | 
								
									Binary file not shown.
								
							
						
					
								
									Binary file not shown.
								
							
						
					| @ -0,0 +1,47 @@ | |||||
|  | # Getting Started with an ESP8266 | ||||
|  | 
 | ||||
|  | Wire Connections | ||||
|  | 
 | ||||
|  | ```ditaa | ||||
|  |     +-----------+          +-----------+ | ||||
|  |     |  ESP8266  |--colour--| nRF24L01+ | | ||||
|  |     |           |          |           | | ||||
|  |     |       GND |---black--|[GND]      | | ||||
|  |     |     +3.3V |----red---| VCC       | | ||||
|  |     |        D4 |---grey---| CE        | | ||||
|  |     |        D8 |--purple--| CSN       | | ||||
|  |     |        D5 |---blue---| SCK       | | ||||
|  |     |        D7 |---green--| MOSI      | | ||||
|  |     |        D6 |---brown--| MISO      | | ||||
|  |     |        D3 |--yellow--| IRQ       | | ||||
|  |     +-----------+          +-----------+ | ||||
|  | ``` | ||||
|  | 
 | ||||
|  |  | ||||
|  | 
 | ||||
|  | Fritzing diagrams & schematics | ||||
|  | * [AhoyMiles_bb.png](./AhoyMiles_bb.png) | ||||
|  | * [AhoyMiles_schem.png](./AhoyMiles_schem.png) | ||||
|  | * [AhoyMiles.fzz](./AhoyMiles.fzz) | ||||
|  | 
 | ||||
|  | Libraries to be installed in Arduino IDE: | ||||
|  | * RF24 | ||||
|  | * TimeLib | ||||
|  | 
 | ||||
|  | Verify & Compile | ||||
|  | * Connect to WiFi Network `ESP AHOY` | ||||
|  | * Use password `esp_8266` | ||||
|  | * Connect to Network settings  | ||||
|  | 
 | ||||
|  | Setup | ||||
|  | * WiFi  | ||||
|  |   * Enter SSID `mynetwork` | ||||
|  |   * Enter Password `mypassword` | ||||
|  | * Device Host Name | ||||
|  |   * Enter Device Name `esp-ahoy`  | ||||
|  | * General | ||||
|  |   * Hoymiles Address (e.g. 114173123456) | ||||
|  |   * Choose inverter type | ||||
|  |   * Set individual inverter name | ||||
|  |   * [x] Reboot device after successful save | ||||
|  | Save | ||||
| @ -0,0 +1,158 @@ | |||||
|  | /*
 | ||||
|  |   CircularBuffer - An Arduino circular buffering library for arbitrary types. | ||||
|  | 
 | ||||
|  |   Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl | ||||
|  |    | ||||
|  |   This library is free software; you can redistribute it and/or | ||||
|  |   modify it under the terms of the GNU Lesser General Public | ||||
|  |   License as published by the Free Software Foundation; either | ||||
|  |   version 2.1 of the License, or (at your option) any later version. | ||||
|  | 
 | ||||
|  |   This library is distributed in the hope that it will be useful, | ||||
|  |   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  |   Lesser General Public License for more details. | ||||
|  | 
 | ||||
|  |   You should have received a copy of the GNU Lesser General Public | ||||
|  |   License along with this library; if not, write to the Free Software | ||||
|  |   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  | */ | ||||
|  | 
 | ||||
|  | #ifndef CircularBuffer_h | ||||
|  | #define CircularBuffer_h | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define DISABLE_IRQ noInterrupts() | ||||
|  | #define RESTORE_IRQ interrupts() | ||||
|  | #else | ||||
|  | #define DISABLE_IRQ       \ | ||||
|  |   uint8_t sreg = SREG;    \ | ||||
|  |   cli(); | ||||
|  | 
 | ||||
|  | #define RESTORE_IRQ        \ | ||||
|  |   SREG = sreg; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | template <class T> class CircularBuffer | ||||
|  | { | ||||
|  |   public: | ||||
|  |     /** Constructor
 | ||||
|  |      * @param buffer   Preallocated buffer of at least size records. | ||||
|  |      * @param size     Number of records available in the buffer. | ||||
|  |      */ | ||||
|  |     CircularBuffer(T* buffer, const uint8_t size ) | ||||
|  |       : m_size(size), m_buff(buffer) | ||||
|  |     { | ||||
|  |       clear(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Clear all entries in the circular buffer. */ | ||||
|  |     void clear(void) | ||||
|  |     { | ||||
|  |       m_front = 0; | ||||
|  |       m_fill  = 0; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Test if the circular buffer is empty */ | ||||
|  |     inline bool empty(void) const | ||||
|  |     { | ||||
|  |       return !m_fill; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Return the number of records stored in the buffer */ | ||||
|  |     inline uint8_t available(void) const | ||||
|  |     { | ||||
|  |       return m_fill; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Test if the circular buffer is full */ | ||||
|  |     inline bool full(void) const | ||||
|  |     { | ||||
|  |       return m_fill == m_size; | ||||
|  |     } | ||||
|  |      | ||||
|  |     /** Aquire record on front of the buffer, for writing.
 | ||||
|  |      * After filling the record, it has to be pushed to actually | ||||
|  |      * add it to the buffer. | ||||
|  |      * @return Pointer to record, or NULL when buffer is full. | ||||
|  |      */ | ||||
|  |     T* getFront(void) const | ||||
|  |     { | ||||
|  |       DISABLE_IRQ; | ||||
|  |       T* f = NULL; | ||||
|  |       if (!full()) | ||||
|  |         f = get(m_front); | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return f; | ||||
|  |     } | ||||
|  |      | ||||
|  |     /** Push record to front of the buffer
 | ||||
|  |      * @param record   Record to push. If record was aquired previously (using getFront) its | ||||
|  |      *                 data will not be copied as it is already present in the buffer. | ||||
|  |      * @return True, when record was pushed successfully. | ||||
|  |      */ | ||||
|  |     bool pushFront(T* record) | ||||
|  |     { | ||||
|  |       bool ok = false; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!full()) | ||||
|  |       { | ||||
|  |         T* f = get(m_front); | ||||
|  |         if (f != record) | ||||
|  |           *f = *record; | ||||
|  |         m_front = (m_front+1) % m_size; | ||||
|  |         m_fill++; | ||||
|  |         ok = true; | ||||
|  |       } | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return ok; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Aquire record on back of the buffer, for reading.
 | ||||
|  |      * After reading the record, it has to be pop'ed to actually | ||||
|  |      * remove it from the buffer. | ||||
|  |      * @return Pointer to record, or NULL when buffer is empty. | ||||
|  |      */ | ||||
|  |     T* getBack(void) const | ||||
|  |     { | ||||
|  |       T* b = NULL; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!empty()) | ||||
|  |         b = get(back()); | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return b; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Remove record from back of the buffer.
 | ||||
|  |      * @return True, when record was pop'ed successfully. | ||||
|  |      */ | ||||
|  |     bool popBack(void) | ||||
|  |     { | ||||
|  |       bool ok = false; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!empty()) | ||||
|  |       { | ||||
|  |         m_fill--; | ||||
|  |         ok = true; | ||||
|  |       } | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return ok; | ||||
|  |     } | ||||
|  |      | ||||
|  |   protected: | ||||
|  |     inline T * get(const uint8_t idx) const | ||||
|  |     { | ||||
|  |       return &(m_buff[idx]); | ||||
|  |     } | ||||
|  |     inline uint8_t back(void) const | ||||
|  |     { | ||||
|  |       return (m_front - m_fill + m_size) % m_size; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     const uint8_t      m_size;     // Total number of records that can be stored in the buffer.
 | ||||
|  |     T* const           m_buff;     // Ptr to buffer holding all records.
 | ||||
|  |     volatile uint8_t   m_front;    // Index of front element (not pushed yet).
 | ||||
|  |     volatile uint8_t   m_fill;     // Amount of records currently pushed.
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif // CircularBuffer_h
 | ||||
| @ -0,0 +1,23 @@ | |||||
|  | #ifndef __DEBUG_H | ||||
|  | 
 | ||||
|  | #define __DEBUG_H | ||||
|  | 
 | ||||
|  | #ifdef DEBUG | ||||
|  |   #define DEBUG_OUT  Serial | ||||
|  | #else  | ||||
|  | //---
 | ||||
|  | // disable Serial DEBUG output
 | ||||
|  |   #define DEBUG_OUT DummySerial | ||||
|  |   static class { | ||||
|  |   public: | ||||
|  |       void begin(...) {} | ||||
|  |       void print(...) {} | ||||
|  |       void println(...) {} | ||||
|  |       void flush() {} | ||||
|  |       bool available() { return false;} | ||||
|  |       int  readBytes(...) { return 0;} | ||||
|  |       int  printf (...) {return 0;} | ||||
|  |   } DummySerial; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif  | ||||
| @ -0,0 +1,38 @@ | |||||
|  | #ifndef __HM1200_H | ||||
|  | #define __HM1200_H | ||||
|  | 
 | ||||
|  | #define HM1200 | ||||
|  | 
 | ||||
|  | const measureDef_t hm1200_measureDef[] = { | ||||
|  |     { IDX_UDC,      UNIT_V,   CH1, CMD01, 14, BYTES2, DIV10   }, | ||||
|  |     { IDX_IDC,      UNIT_A,   CH1, CMD01, 16, BYTES2, DIV100  }, | ||||
|  |     { IDX_PDC,      UNIT_W,   CH1, CMD01, 20, BYTES2, DIV10   }, | ||||
|  |     { IDX_E_TAG,    UNIT_WH,  CH1, CMD02, 16, BYTES2, DIV1    }, | ||||
|  |     { IDX_E_TOTAL,  UNIT_KWH, CH1, CMD01, 24, BYTES4, DIV1000 }, | ||||
|  |     { IDX_UDC,      UNIT_V,   CH2, CMD02, 20, BYTES2, DIV10   }, | ||||
|  |     { IDX_IDC,      UNIT_A,   CH2, CMD01, 18, BYTES2, DIV100  }, | ||||
|  |     { IDX_PDC,      UNIT_W,   CH2, CMD01, 22, BYTES2, DIV10   }, | ||||
|  |     { IDX_E_TAG,    UNIT_WH,  CH2, CMD02, 18, BYTES2, DIV1    }, | ||||
|  |     { IDX_E_TOTAL,  UNIT_KWH, CH2, CMD02, 12, BYTES4, DIV1000 }, | ||||
|  |     { IDX_IDC,      UNIT_A,   CH3, CMD02, 22, BYTES2, DIV100  }, | ||||
|  |     { IDX_PDC,      UNIT_W,   CH3, CMD02, 26, BYTES2, DIV10   }, | ||||
|  |     { IDX_E_TAG,    UNIT_WH,  CH3, CMD03, 22, BYTES2, DIV1    }, | ||||
|  |     { IDX_E_TOTAL,  UNIT_KWH, CH3, CMD03, 14, BYTES4, DIV1000 }, | ||||
|  |     { IDX_IDC,      UNIT_A,   CH4, CMD02, 24, BYTES2, DIV100  }, | ||||
|  |     { IDX_PDC,      UNIT_W,   CH4, CMD03, 12, BYTES2, DIV10   }, | ||||
|  |     { IDX_E_TAG,    UNIT_WH,  CH4, CMD03, 24, BYTES2, DIV1    }, | ||||
|  |     { IDX_E_TOTAL,  UNIT_KWH, CH4, CMD03, 18, BYTES4, DIV1000 }, | ||||
|  |     { IDX_UAC,      UNIT_V,   CH0, CMD03, 26, BYTES2, DIV10   }, | ||||
|  |     { IDX_IPV,      UNIT_A,   CH0, CMD84, 18, BYTES2, DIV100  }, | ||||
|  |     { IDX_PAC,      UNIT_W,   CH0, CMD84, 14, BYTES2, DIV10   }, | ||||
|  |     { IDX_FREQ,     UNIT_HZ,  CH0, CMD84, 12, BYTES2, DIV100  }, | ||||
|  |     { IDX_PERCNT,   UNIT_PCT, CH0, CMD84, 20, BYTES2, DIV10   }, | ||||
|  |     { IDX_WR_TEMP,  UNIT_C,   CH0, CMD84, 22, BYTES2, DIV10   } | ||||
|  | }; | ||||
|  | 
 | ||||
|  | measureCalc_t hm1200_measureCalc[] = {}; | ||||
|  | 
 | ||||
|  | #define HM1200_MEASURE_LIST_LEN  sizeof(hm1200_measureDef)/sizeof(measureDef_t) | ||||
|  | #define HM1200_CALCED_LIST_LEN    0 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,37 @@ | |||||
|  | #ifndef __HM600_H | ||||
|  | #define __HM600_H | ||||
|  | 
 | ||||
|  | #define HM600 | ||||
|  | #define HM700 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float calcEheute (float *measure) { return measure[8] + measure[9]; } | ||||
|  | float calcIpv    (float *measure) { return (measure[10] != 0 ? measure[12]/measure[10] : 0); } | ||||
|  | 
 | ||||
|  | const measureDef_t hm600_measureDef[] = { | ||||
|  |   { IDX_UDC,    CH1, UNIT_V,  CMD01, 14, BYTES2, DIV10}, | ||||
|  |   { IDX_IDC,    CH1, UNIT_A,  CMD01, 16, BYTES2, DIV100}, | ||||
|  |   { IDX_PDC,    CH1, UNIT_W,  CMD01, 18, BYTES2, DIV10}, | ||||
|  |   { IDX_UDC,    CH2, UNIT_V,  CMD01, 20, BYTES2, DIV10}, | ||||
|  |   { IDX_IDC,    CH2, UNIT_A,  CMD01, 22, BYTES2, DIV100}, | ||||
|  |   { IDX_PDC,    CH2, UNIT_W,  CMD01, 24, BYTES2, DIV10}, | ||||
|  |   { IDX_E_WOCHE,CH0, UNIT_WH, CMD02, 12, BYTES2, DIV1}, | ||||
|  |   { IDX_E_TOTAL,CH0, UNIT_WH, CMD02, 14, BYTES4, DIV1}, | ||||
|  |   { IDX_E_TAG,  CH1, UNIT_WH, CMD02, 18, BYTES2, DIV1}, | ||||
|  |   { IDX_E_TAG,  CH2, UNIT_WH, CMD02, 20, BYTES2, DIV1}, | ||||
|  |   { IDX_UAC,    CH0, UNIT_V,  CMD02, 22, BYTES2, DIV10}, | ||||
|  |   { IDX_FREQ,   CH0, UNIT_HZ, CMD02, 24, BYTES2, DIV100}, | ||||
|  |   { IDX_PAC,    CH0, UNIT_W,  CMD02, 26, BYTES2, DIV10}, | ||||
|  |   { IDX_WR_TEMP,CH0, UNIT_C,  CMD83, 18, BYTES2, DIV10}      | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | measureCalc_t hm600_measureCalc[] = { | ||||
|  |   { IDX_E_HEUTE,  UNIT_WH, DIV1,   &calcEheute}, | ||||
|  |   { IDX_IPV,      UNIT_A,  DIV100, &calcIpv} | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #define HM600_MEASURE_LIST_LEN  sizeof(hm600_measureDef)/sizeof(measureDef_t) | ||||
|  | #define HM600_CALCED_LIST_LEN   sizeof(hm600_measureCalc)/sizeof(measureCalc_t) | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,605 @@ | |||||
|  | #include <Arduino.h> | ||||
|  | #include <SPI.h> | ||||
|  | #include "CircularBuffer.h" | ||||
|  | #include <RF24.h> | ||||
|  | #include "printf.h" | ||||
|  | #include <RF24_config.h> | ||||
|  | #include "hm_crc.h" | ||||
|  | #include "hm_packets.h" | ||||
|  | 
 | ||||
|  | #include "Settings.h"     // Header für Einstellungen | ||||
|  | #include "Debug.h" | ||||
|  | #include "Inverters.h" | ||||
|  | 
 | ||||
|  | const char VERSION[] PROGMEM = "0.1.6"; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   #define DISABLE_EINT noInterrupts() | ||||
|  |   #define ENABLE_EINT  interrupts() | ||||
|  | #else     // für AVR z.B. ProMini oder Nano
 | ||||
|  |   #define DISABLE_EINT EIMSK = 0x00 | ||||
|  |   #define ENABLE_EINT EIMSK = 0x01 | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define PACKET_BUFFER_SIZE      (30)  | ||||
|  | #else | ||||
|  | #define PACKET_BUFFER_SIZE      (20)  | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | // Startup defaults until user reconfigures it
 | ||||
|  | //#define DEFAULT_RECV_CHANNEL    (3)             // 3 = Default channel for Hoymiles
 | ||||
|  | //#define DEFAULT_SEND_CHANNEL  (75)            // 40 = Default channel for Hoymiles, 61
 | ||||
|  | 
 | ||||
|  | static HM_Packets     hmPackets; | ||||
|  | static uint32_t       tickMillis; | ||||
|  | 
 | ||||
|  | // Set up nRF24L01 radio on SPI bus plus CE/CS pins
 | ||||
|  | // If more than one RF24 unit is used the another CS pin than 10 must be used
 | ||||
|  | // This pin is used hard coded in SPI library
 | ||||
|  | static RF24 Radio (RF1_CE_PIN, RF1_CS_PIN); | ||||
|  | 
 | ||||
|  | static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE]; | ||||
|  | 
 | ||||
|  | static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0])); | ||||
|  | 
 | ||||
|  | static Serial_header_t SerialHdr; | ||||
|  | 
 | ||||
|  | #define CHECKCRC  1 | ||||
|  | static uint16_t lastCRC; | ||||
|  | static uint16_t crc; | ||||
|  | 
 | ||||
|  | uint8_t         channels[]            = {3, 23, 40, 61, 75};   //{1, 3, 6, 9, 11, 23, 40, 61, 75}
 | ||||
|  | uint8_t         channelIdx            = 2;                         // fange mit 40 an
 | ||||
|  | uint8_t         DEFAULT_SEND_CHANNEL  = channels[channelIdx];      // = 40
 | ||||
|  | 
 | ||||
|  | #if USE_POOR_MAN_CHANNEL_HOPPING_RCV | ||||
|  | uint8_t         rcvChannelIdx         = 0;  | ||||
|  | uint8_t         rcvChannels[]         = {3, 23, 40, 61, 75};   //{1, 3, 6, 9, 11, 23, 40, 61, 75}
 | ||||
|  | uint8_t         DEFAULT_RECV_CHANNEL  = rcvChannels[rcvChannelIdx];      //3;
 | ||||
|  | uint8_t         intvl = 4;          // Zeit für poor man hopping
 | ||||
|  | int             hophop; | ||||
|  | #else | ||||
|  | uint8_t         DEFAULT_RECV_CHANNEL  = 3; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | boolean         valueChanged          = false; | ||||
|  | 
 | ||||
|  | static unsigned long timeLastPacket = millis(); | ||||
|  | static unsigned long timeLastIstTagCheck =  millis(); | ||||
|  | static unsigned long timeLastRcvChannelSwitch = millis(); | ||||
|  | 
 | ||||
|  | // Function forward declaration
 | ||||
|  | static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static const char BLANK = ' '; | ||||
|  | 
 | ||||
|  | static boolean istTag = true; | ||||
|  | 
 | ||||
|  | char CHANNELNAME_BUFFER[15]; | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   #include "wifi.h" | ||||
|  |   #include "ModWebserver.h" | ||||
|  |   #include "Sonne.h" | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | inline static void dumpData(uint8_t *p, int len) { | ||||
|  | //-----------------------------------------------
 | ||||
|  |   while (len > 0){ | ||||
|  |     if (*p < 16) | ||||
|  |       DEBUG_OUT.print(F("0")); | ||||
|  |     DEBUG_OUT.print(*p++, HEX); | ||||
|  |     len--; | ||||
|  |   } | ||||
|  |   DEBUG_OUT.print(BLANK); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float extractValue2 (uint8_t *p, int divisor) { | ||||
|  | //-------------------------------------------
 | ||||
|  |   uint16_t b1 = *p++; | ||||
|  |   return ((float) (b1 << 8) + *p) / (float) divisor; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float extractValue4 (uint8_t *p, int divisor) { | ||||
|  | //-------------------------------------------
 | ||||
|  |   uint32_t ret  = *p++; | ||||
|  |   for (uint8_t i = 1; i <= 3; i++) | ||||
|  |     ret = (ret << 8) + *p++; | ||||
|  |   return (ret / divisor); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void outChannel (uint8_t wr, uint8_t i) { | ||||
|  | //------------------------------------
 | ||||
|  |   DEBUG_OUT.print(getMeasureName(wr, i));  | ||||
|  |   DEBUG_OUT.print(F("\t:"));  | ||||
|  |   DEBUG_OUT.print(getMeasureValue(wr,i));  | ||||
|  |   DEBUG_OUT.println(BLANK);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void analyseWords (uint8_t *p) {    // p zeigt auf 01 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   DEBUG_OUT.print (F("analyse words:")); | ||||
|  |   p++; | ||||
|  |   for (int i = 0; i <12;i++) { | ||||
|  |     DEBUG_OUT.print(extractValue2(p,1)); | ||||
|  |     DEBUG_OUT.print(BLANK); | ||||
|  |     p++; | ||||
|  |   } | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void analyseLongs (uint8_t *p) {    // p zeigt auf 01 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   DEBUG_OUT.print (F("analyse longs:")); | ||||
|  |   p++; | ||||
|  |   for (int i = 0; i <12;i++) { | ||||
|  |     DEBUG_OUT.print(extractValue4(p,1)); | ||||
|  |     DEBUG_OUT.print(BLANK); | ||||
|  |     p++; | ||||
|  |   } | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void analyse (NRF24_packet_t *p) { | ||||
|  | //------------------------------
 | ||||
|  |   uint8_t wrIdx = findInverter (&p->packet[3]); | ||||
|  |   //DEBUG_OUT.print ("wrIdx="); DEBUG_OUT.println (wrIdx);
 | ||||
|  |   if (wrIdx == 0xFF) return; | ||||
|  |   uint8_t cmd = p->packet[11]; | ||||
|  |   float val = 0; | ||||
|  |   if (cmd == 0x01 || cmd == 0x02 || cmd == 0x83) { | ||||
|  |     const measureDef_t *defs = inverters[wrIdx].measureDef; | ||||
|  | 
 | ||||
|  |     for (uint8_t i = 0; i < inverters[wrIdx].anzMeasures; i++) { | ||||
|  |       if (defs[i].teleId == cmd) { | ||||
|  |         uint8_t pos = defs[i].pos; | ||||
|  |         if (defs[i].bytes == 2)   | ||||
|  |           val = extractValue2 (&p->packet[pos], getDivisor(wrIdx, i) ); | ||||
|  |         else if (defs[i].bytes == 4) | ||||
|  |           val = extractValue4 (&p->packet[pos], getDivisor(wrIdx, i) ); | ||||
|  |         valueChanged = valueChanged ||(val != inverters[wrIdx].values[i]); | ||||
|  |         inverters[wrIdx].values[i] = val; | ||||
|  |       }       | ||||
|  |     } | ||||
|  |     // calculated funstions
 | ||||
|  |     for (uint8_t i = 0; i < inverters[wrIdx].anzMeasureCalculated; i++) { | ||||
|  |       val = inverters[wrIdx].measureCalculated[i].f (inverters[wrIdx].values); | ||||
|  |       int idx = inverters[wrIdx].anzMeasures + i; | ||||
|  |       valueChanged = valueChanged ||(val != inverters[wrIdx].values[idx]); | ||||
|  |       inverters[wrIdx].values[idx] = val; | ||||
|  |     } | ||||
|  |   } | ||||
|  |   else if (cmd == 0x81) { | ||||
|  |     ; | ||||
|  |   } | ||||
|  |   else { | ||||
|  |     DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX); | ||||
|  |     analyseWords (&p->packet[11]); | ||||
|  |     analyseLongs (&p->packet[11]); | ||||
|  |     DEBUG_OUT.println(); | ||||
|  |   } | ||||
|  |   if (p->packetsLost > 0) { | ||||
|  |     DEBUG_OUT.print(F(" Lost: ")); | ||||
|  |     DEBUG_OUT.println(p->packetsLost); | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | IRAM_ATTR | ||||
|  | #endif | ||||
|  | void handleNrf1Irq() { | ||||
|  | //-------------------------
 | ||||
|  |   static uint8_t lostPacketCount = 0; | ||||
|  |   uint8_t pipe; | ||||
|  | 
 | ||||
|  |   DISABLE_EINT; | ||||
|  |    | ||||
|  |   // Loop until RX buffer(s) contain no more packets.
 | ||||
|  |   while (Radio.available(&pipe)) { | ||||
|  |     if (!packetBuffer.full()) { | ||||
|  |       NRF24_packet_t *p = packetBuffer.getFront(); | ||||
|  |       p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
 | ||||
|  |       p->packetsLost = lostPacketCount; | ||||
|  |       p->rcvChannel  = DEFAULT_RECV_CHANNEL; | ||||
|  |       uint8_t packetLen = Radio.getPayloadSize(); | ||||
|  |       if (packetLen > MAX_RF_PAYLOAD_SIZE) | ||||
|  |         packetLen = MAX_RF_PAYLOAD_SIZE; | ||||
|  | 
 | ||||
|  |       Radio.read(p->packet, packetLen); | ||||
|  |       packetBuffer.pushFront(p); | ||||
|  |       lostPacketCount = 0; | ||||
|  |     } | ||||
|  |     else { | ||||
|  |       // Buffer full. Increase lost packet counter.
 | ||||
|  |       bool tx_ok, tx_fail, rx_ready; | ||||
|  |       if (lostPacketCount < 255) | ||||
|  |         lostPacketCount++; | ||||
|  |       // Call 'whatHappened' to reset interrupt status.
 | ||||
|  |       Radio.whatHappened(tx_ok, tx_fail, rx_ready); | ||||
|  |       // Flush buffer to drop the packet.
 | ||||
|  |       Radio.flush_rx(); | ||||
|  |     } | ||||
|  |   } | ||||
|  |   ENABLE_EINT; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static void activateConf(void) { | ||||
|  | //-----------------------------
 | ||||
|  |   Radio.begin(); | ||||
|  |   // Disable shockburst for receiving and decode payload manually
 | ||||
|  |   Radio.setAutoAck(false); | ||||
|  |   Radio.setRetries(0, 0); | ||||
|  |   Radio.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |   Radio.setDataRate(DEFAULT_RF_DATARATE); | ||||
|  |   Radio.disableCRC(); | ||||
|  |   Radio.setAutoAck(0x00); | ||||
|  |   Radio.setPayloadSize(MAX_RF_PAYLOAD_SIZE); | ||||
|  |   Radio.setAddressWidth(5); | ||||
|  |   Radio.openReadingPipe(1, DTU_RADIO_ID); | ||||
|  | 
 | ||||
|  |   // We want only RX irqs
 | ||||
|  |   Radio.maskIRQ(true, true, false); | ||||
|  | 
 | ||||
|  |   // Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
 | ||||
|  |   Radio.setPALevel(RF24_PA_MAX); | ||||
|  |   Radio.startListening(); | ||||
|  | 
 | ||||
|  |   // Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
 | ||||
|  |   attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
 | ||||
|  | 
 | ||||
|  |   // Initialize SerialHdr header's address member to promiscuous address.
 | ||||
|  |   uint64_t addr = DTU_RADIO_ID; | ||||
|  |   for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) { | ||||
|  |     SerialHdr.address[i] = addr; | ||||
|  |     addr >>= 8; | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   //Radio.printDetails();
 | ||||
|  |   //DEBUG_OUT.println();
 | ||||
|  |   tickMillis = millis() + 200; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #define resetRF24() activateConf() | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void setup(void) { | ||||
|  | //--------------
 | ||||
|  |   #ifndef DEBUG | ||||
|  |   #ifndef ESP8266 | ||||
|  |   Serial.begin(SER_BAUDRATE); | ||||
|  |   #endif | ||||
|  |   #endif | ||||
|  |   printf_begin(); | ||||
|  |   DEBUG_OUT.begin(SER_BAUDRATE); | ||||
|  |   DEBUG_OUT.flush(); | ||||
|  | 
 | ||||
|  |   DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --")); | ||||
|  | 
 | ||||
|  |   // Configure nRF IRQ input
 | ||||
|  |   pinMode(RF1_IRQ_PIN, INPUT); | ||||
|  | 
 | ||||
|  |   activateConf(); | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   setupWifi(); | ||||
|  |   setupClock(); | ||||
|  |   setupWebServer(); | ||||
|  |   setupUpdateByOTA(); | ||||
|  |   calcSunUpDown (getNow()); | ||||
|  |   istTag = isDayTime(); | ||||
|  |   DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); | ||||
|  |   hmPackets.SetUnixTimeStamp (getNow()); | ||||
|  | #else | ||||
|  |   hmPackets.SetUnixTimeStamp(0x62456430); | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |   setupInverts(); | ||||
|  | } | ||||
|  | 
 | ||||
|  |  uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | 
 | ||||
|  | void isTime2Send () { | ||||
|  | //-----------------
 | ||||
|  |   // Second timer
 | ||||
|  |   static const uint8_t warteZeit = 1; | ||||
|  |   static uint8_t tickSec = 0; | ||||
|  |   if (millis() >= tickMillis) { | ||||
|  |     static uint8_t tel = 0; | ||||
|  |     tickMillis += warteZeit*1000;    //200;
 | ||||
|  |     tickSec++;  | ||||
|  |     | ||||
|  |     if (++tickSec >= 1) {   // 5
 | ||||
|  |       for (uint8_t c=0; c < warteZeit; c++) hmPackets.UnixTimeStampTick(); | ||||
|  |       tickSec = 0; | ||||
|  |     }  | ||||
|  | 
 | ||||
|  |     int32_t size = 0; | ||||
|  |     uint64_t dest =  0; | ||||
|  |     for (uint8_t wr = 0; wr < anzInv; wr++) { | ||||
|  |       dest = inverters[wr].RadioId; | ||||
|  | 
 | ||||
|  |       if (tel > 1) | ||||
|  |         tel = 0; | ||||
|  |        | ||||
|  |       if (tel == 0) { | ||||
|  |         #ifdef ESP8266 | ||||
|  |         hmPackets.SetUnixTimeStamp (getNow()); | ||||
|  |         #endif | ||||
|  |         size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8); | ||||
|  |         //DEBUG_OUT.print ("Timepacket mit cid="); DEBUG_OUT.println(sendBuf[10], HEX);
 | ||||
|  |       } | ||||
|  |       else if (tel <= 1)  | ||||
|  |         size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15,  0x80 + tel - 1); | ||||
|  | 
 | ||||
|  |       SendPacket (dest, (uint8_t *)&sendBuf, size); | ||||
|  |     }  // for wr
 | ||||
|  | 
 | ||||
|  |     tel++; | ||||
|  |      | ||||
|  | /*    for (uint8_t warte = 0; warte < 2; warte++) {
 | ||||
|  |       delay(1000); | ||||
|  |       hmPackets.UnixTimeStampTick(); | ||||
|  |     }*/  | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) { | ||||
|  | //-----------------------------------------------------
 | ||||
|  | 
 | ||||
|  |     // Write timestamp, packets lost, address and payload length
 | ||||
|  |     //printf(" %09lu ", SerialHdr.timestamp);
 | ||||
|  |     char _buf[20]; | ||||
|  |     sprintf_P(_buf, PSTR("rcv CH:%d "), p->rcvChannel); | ||||
|  |     DEBUG_OUT.print (_buf); | ||||
|  |     dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost)); | ||||
|  |     dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address)); | ||||
|  | 
 | ||||
|  |     // Trailing bit?!?
 | ||||
|  |     dumpData(&p->packet[0], 2); | ||||
|  | 
 | ||||
|  |     // Payload length from PCF
 | ||||
|  |     dumpData(&payloadLen, sizeof(payloadLen)); | ||||
|  | 
 | ||||
|  |     // Packet control field - PID Packet identification
 | ||||
|  |     uint8_t val = (p->packet[1] >> 1) & 0x03; | ||||
|  |     DEBUG_OUT.print(val); | ||||
|  |     DEBUG_OUT.print(F("  ")); | ||||
|  | 
 | ||||
|  |     if (payloadLen > 9) { | ||||
|  |       dumpData(&p->packet[2], 1); | ||||
|  |       dumpData(&p->packet[3], 4); | ||||
|  |       dumpData(&p->packet[7], 4); | ||||
|  |        | ||||
|  |       uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4; | ||||
|  | 
 | ||||
|  |       if (remain < 32) { | ||||
|  |         dumpData(&p->packet[11], remain); | ||||
|  |         printf_P(PSTR("%04X "), crc); | ||||
|  | 
 | ||||
|  |         if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) | ||||
|  |           DEBUG_OUT.print(0); | ||||
|  |         else | ||||
|  |           DEBUG_OUT.print(1); | ||||
|  |       } | ||||
|  |       else { | ||||
|  |         DEBUG_OUT.print(F("Ill remain ")); | ||||
|  |         DEBUG_OUT.print(remain); | ||||
|  |       } | ||||
|  |     } | ||||
|  |     else { | ||||
|  |       dumpData(&p->packet[2], payloadLen + 2); | ||||
|  |       printf_P(PSTR("%04X "), crc); | ||||
|  |     } | ||||
|  |     DEBUG_OUT.println();  | ||||
|  |     DEBUG_OUT.flush(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void writeArduinoInterface() { | ||||
|  | //--------------------------
 | ||||
|  |   if (valueChanged) { | ||||
|  |     for (uint8_t wr = 0; wr < anzInv; wr++) { | ||||
|  |       if (anzInv > 1) { | ||||
|  |         Serial.print(wr); Serial.print('.'); | ||||
|  |       } | ||||
|  |       for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { | ||||
|  |         Serial.print(getMeasureName(wr,i));    // Schnittstelle bei Arduino
 | ||||
|  |         Serial.print('=');  | ||||
|  |         Serial.print(getMeasureValue(wr,i), getDigits(wr,i));   // Schnittstelle bei Arduino
 | ||||
|  |         Serial.print (BLANK); | ||||
|  |         Serial.println (getUnit(wr, i)); | ||||
|  |       }  // for i
 | ||||
|  |        | ||||
|  |     }  // for wr
 | ||||
|  |     Serial.println(F("-----------------------")); | ||||
|  |     valueChanged = false; | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | boolean doCheckCrc (NRF24_packet_t *p, uint8_t payloadLen) { | ||||
|  | //--------------------------------------------------------
 | ||||
|  |   crc = 0xFFFF; | ||||
|  |   crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address))); | ||||
|  |   // Payload length
 | ||||
|  |   // Add one byte and one bit for 9-bit packet control field
 | ||||
|  |   crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1); | ||||
|  |    | ||||
|  |   if (CHECKCRC) { | ||||
|  |     // If CRC is invalid only show lost packets
 | ||||
|  |     if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) { | ||||
|  |       if (p->packetsLost > 0) { | ||||
|  |         DEBUG_OUT.print(F(" Lost: ")); | ||||
|  |         DEBUG_OUT.println(p->packetsLost); | ||||
|  |       } | ||||
|  |       packetBuffer.popBack(); | ||||
|  |       return false; | ||||
|  |     } | ||||
|  |    | ||||
|  |     // Dump a decoded packet only once
 | ||||
|  |     if (lastCRC == crc) { | ||||
|  |       packetBuffer.popBack(); | ||||
|  |       return false; | ||||
|  |     } | ||||
|  |     lastCRC = crc; | ||||
|  |   } | ||||
|  |    | ||||
|  |   // Don't dump mysterious ack packages
 | ||||
|  |   if (payloadLen == 0) { | ||||
|  |       packetBuffer.popBack(); | ||||
|  |       return false; | ||||
|  |   } | ||||
|  |   return true; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void poorManChannelHopping() { | ||||
|  | //--------------------------
 | ||||
|  |   if (hophop <= 0) return; | ||||
|  |   if (millis() >= timeLastRcvChannelSwitch + intvl) { | ||||
|  |     rcvChannelIdx++; | ||||
|  |     if (rcvChannelIdx >= sizeof(rcvChannels)) | ||||
|  |       rcvChannelIdx = 0; | ||||
|  |     DEFAULT_RECV_CHANNEL  = rcvChannels[rcvChannelIdx];  | ||||
|  |     DISABLE_EINT; | ||||
|  |     Radio.stopListening(); | ||||
|  |     Radio.setChannel (DEFAULT_RECV_CHANNEL); | ||||
|  |     Radio.startListening(); | ||||
|  |     ENABLE_EINT;       | ||||
|  |     timeLastRcvChannelSwitch = millis(); | ||||
|  |     hophop--; | ||||
|  |   } | ||||
|  |    | ||||
|  | } | ||||
|  | void loop(void) { | ||||
|  | //=============
 | ||||
|  |   // poor man channel hopping on receive
 | ||||
|  | #if USE_POOR_MAN_CHANNEL_HOPPING_RCV | ||||
|  |   poorManChannelHopping(); | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |   if (millis()  > timeLastPacket + 50000UL) { | ||||
|  |     DEBUG_OUT.println (F("Reset RF24")); | ||||
|  |     resetRF24(); | ||||
|  |     timeLastPacket = millis();  | ||||
|  |   } | ||||
|  |    | ||||
|  |   while (!packetBuffer.empty()) { | ||||
|  |     timeLastPacket = millis(); | ||||
|  |     // One or more records present
 | ||||
|  |     NRF24_packet_t *p = packetBuffer.getBack(); | ||||
|  | 
 | ||||
|  |     // Shift payload data due to 9-bit packet control field
 | ||||
|  |     for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) { | ||||
|  |       if (j > 0) | ||||
|  |         p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1); | ||||
|  |       else | ||||
|  |         p->packet[j] = (byte)(p->packet[j] >> 7); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     SerialHdr.timestamp   = p->timestamp; | ||||
|  |     SerialHdr.packetsLost = p->packetsLost; | ||||
|  | 
 | ||||
|  |     uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3); | ||||
|  |     // Check CRC
 | ||||
|  |     if (! doCheckCrc(p, payloadLen) ) | ||||
|  |       continue; | ||||
|  | 
 | ||||
|  |     #ifdef DEBUG | ||||
|  |     uint8_t cmd = p->packet[11]; | ||||
|  |     //if (cmd != 0x01 && cmd != 0x02 && cmd != 0x83 && cmd != 0x81)
 | ||||
|  |       outputPacket (p, payloadLen); | ||||
|  |     #endif | ||||
|  | 
 | ||||
|  |     analyse (p); | ||||
|  | 
 | ||||
|  |     #ifndef ESP8266 | ||||
|  |     writeArduinoInterface(); | ||||
|  |     #endif | ||||
|  |      | ||||
|  |     // Remove record as we're done with it.
 | ||||
|  |     packetBuffer.popBack(); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   if (istTag)  | ||||
|  |     isTime2Send(); | ||||
|  | 
 | ||||
|  |   #ifdef ESP8266 | ||||
|  |   checkWifi(); | ||||
|  |   webserverHandle();        | ||||
|  |   checkUpdateByOTA(); | ||||
|  |   if (hour() == 0 && minute() == 0) { | ||||
|  |     calcSunUpDown(getNow());   | ||||
|  |     delay (60*1000); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   if (millis() > timeLastIstTagCheck + 15UL * 60UL * 1000UL) {   // alle 15 Minuten neu berechnen ob noch hell
 | ||||
|  |     istTag = isDayTime(); | ||||
|  |     DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); | ||||
|  |     timeLastIstTagCheck = millis(); | ||||
|  |   } | ||||
|  |   #endif | ||||
|  | /*
 | ||||
|  |   if (millis() > timeLastPacket + 60UL*SECOND) {  // 60 Sekunden
 | ||||
|  |     channelIdx++; | ||||
|  |     if (channelIdx >= sizeof(channels)) channelIdx = 0; | ||||
|  |     DEFAULT_SEND_CHANNEL = channels[channelIdx]; | ||||
|  |     DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL); | ||||
|  |     timeLastPacket = millis(); | ||||
|  |   } | ||||
|  | */ | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) { | ||||
|  | //--------------------------------------------------------------
 | ||||
|  |   //DEBUG_OUT.print (F("Sende: ")); DEBUG_OUT.println (buf[9],  HEX);
 | ||||
|  |   //dumpData (buf, len); DEBUG_OUT.println();
 | ||||
|  |   DISABLE_EINT; | ||||
|  |   Radio.stopListening(); | ||||
|  | 
 | ||||
|  | #ifdef CHANNEL_HOP | ||||
|  |   static uint8_t hop = 0; | ||||
|  |   #if DEBUG_SEND     | ||||
|  |   DEBUG_OUT.print(F("Send... CH")); | ||||
|  |   DEBUG_OUT.println(channels[hop]); | ||||
|  |   #endif   | ||||
|  |   Radio.setChannel(channels[hop++]); | ||||
|  |   if (hop >= sizeof(channels) / sizeof(channels[0])) | ||||
|  |     hop = 0; | ||||
|  | #else | ||||
|  |   Radio.setChannel(DEFAULT_SEND_CHANNEL); | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |   Radio.openWritingPipe(dest); | ||||
|  |   Radio.setCRCLength(RF24_CRC_16); | ||||
|  |   Radio.enableDynamicPayloads(); | ||||
|  |   Radio.setAutoAck(true); | ||||
|  |   Radio.setRetries(3, 15); | ||||
|  | 
 | ||||
|  |   bool res = Radio.write(buf, len); | ||||
|  |   // Try to avoid zero payload acks (has no effect)
 | ||||
|  |   Radio.openWritingPipe(DUMMY_RADIO_ID); | ||||
|  | 
 | ||||
|  |   Radio.setAutoAck(false); | ||||
|  |   Radio.setRetries(0, 0); | ||||
|  |   Radio.disableDynamicPayloads(); | ||||
|  |   Radio.setCRCLength(RF24_CRC_DISABLED); | ||||
|  | 
 | ||||
|  |   Radio.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |   Radio.startListening(); | ||||
|  |   ENABLE_EINT; | ||||
|  | #if USE_POOR_MAN_CHANNEL_HOPPING_RCV | ||||
|  |   hophop = 5 * sizeof(rcvChannels); | ||||
|  | #endif | ||||
|  | } | ||||
| @ -0,0 +1,283 @@ | |||||
|  | #ifndef __INVERTERS_H | ||||
|  | #define __INVERTERS_H | ||||
|  | 
 | ||||
|  | // Ausgabe von Debug Infos auf der seriellen Console
 | ||||
|  | 
 | ||||
|  | #include "Settings.h" | ||||
|  | #include "Debug.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | typedef struct _NRF24_packet_t { | ||||
|  |   uint32_t timestamp; | ||||
|  |   uint8_t  packetsLost; | ||||
|  |   uint8_t  rcvChannel; | ||||
|  |   uint8_t  packet[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | } NRF24_packet_t; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | typedef struct _Serial_header_t { | ||||
|  |   unsigned long timestamp; | ||||
|  |   uint8_t  packetsLost; | ||||
|  |   uint8_t  address[RF_MAX_ADDR_WIDTH];    // MSB first, always RF_MAX_ADDR_WIDTH bytes.
 | ||||
|  | } Serial_header_t; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // structs für Inverter und Kanalwerte
 | ||||
|  | 
 | ||||
|  | // Liste der Einheiten
 | ||||
|  | enum UNITS {UNIT_V = 0, UNIT_HZ, UNIT_A, UNIT_W,  UNIT_WH, UNIT_C, UNIT_KWH, UNIT_MA, UNIT_PCT}; | ||||
|  | const char* const units[] = {"V", "Hz", "A", "W", "Wh", "°C", "KWh", "mA", "%"}; | ||||
|  | 
 | ||||
|  | // CH0 is default channel (freq, ac, temp)
 | ||||
|  | enum CHANNELS {CH0 = 0, CH1, CH2, CH3, CH4}; | ||||
|  | enum CMDS     {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84}; | ||||
|  | enum DIVS     {DIV1 = 0, DIV10, DIV100, DIV1000}; | ||||
|  | 
 | ||||
|  | #define BYTES2        2 | ||||
|  | #define BYTES4        4 | ||||
|  | 
 | ||||
|  | const char UDC[]      PROGMEM = "Udc"; | ||||
|  | const char IDC[]      PROGMEM = "Idc"; | ||||
|  | const char PDC[]      PROGMEM = "Pdc"; | ||||
|  | const char E_WOCHE[]  PROGMEM = "E-Woche"; | ||||
|  | const char E_TOTAL[]  PROGMEM = "E-Total"; | ||||
|  | const char E_TAG[]    PROGMEM = "E-Tag"; | ||||
|  | const char UAC[]      PROGMEM = "Uac"; | ||||
|  | const char FREQ[]     PROGMEM = "Freq.ac"; | ||||
|  | const char PAC[]      PROGMEM = "Pac"; | ||||
|  | const char E_HEUTE[]  PROGMEM  = "E-heute"; | ||||
|  | const char IPV[]      PROGMEM  = "Ipv"; | ||||
|  | const char WR_TEMP[]  PROGMEM = "WR-Temp"; | ||||
|  | const char PERCNT[]   PROGMEM = "Pct"; | ||||
|  | 
 | ||||
|  | #define IDX_UDC       0 | ||||
|  | #define IDX_IDC       1 | ||||
|  | #define IDX_PDC       2 | ||||
|  | #define IDX_E_WOCHE   3 | ||||
|  | #define IDX_E_TOTAL   4 | ||||
|  | #define IDX_E_TAG     5 | ||||
|  | #define IDX_UAC       6 | ||||
|  | #define IDX_FREQ      7 | ||||
|  | #define IDX_PAC       8 | ||||
|  | #define IDX_E_HEUTE   9 | ||||
|  | #define IDX_IPV      10 | ||||
|  | #define IDX_WR_TEMP  11 | ||||
|  | #define IDX_PERCNT   12     | ||||
|  | 
 | ||||
|  | const char* const NAMES[]  | ||||
|  |   = {UDC, IDC, PDC, E_WOCHE, E_TOTAL, E_TAG, UAC, FREQ, PAC, E_HEUTE, IPV, WR_TEMP, PERCNT}; | ||||
|  | 
 | ||||
|  | typedef float (*calcValueFunc)(float *); | ||||
|  | 
 | ||||
|  | struct measureDef_t { | ||||
|  |   uint8_t     nameIdx;        //const char* name;           // Zeiger auf den Messwertnamen
 | ||||
|  |   uint8_t     channel;        // 0..4, 
 | ||||
|  |   uint8_t     unitIdx;        // Index in die Liste der Einheiten 'units'
 | ||||
|  |   uint8_t     teleId;         // Telegramm ID, das was hinter der 2. WR Nummer im Telegramm, 02, 03, 83
 | ||||
|  |   uint8_t     pos;            // ab dieser POsition beginnt der Wert (Big Endian)
 | ||||
|  |   uint8_t     bytes;          // Anzahl der Bytes
 | ||||
|  |   uint8_t     digits; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | struct measureCalc_t { | ||||
|  |   uint8_t     nameIdx;        //const char* name;           // Zeiger auf den Messwertnamen
 | ||||
|  |   uint8_t     unitIdx;        // Index in die Liste der Einheiten 'units'
 | ||||
|  |   uint8_t     digits; | ||||
|  |   calcValueFunc f;            // die Funktion zur Berechnung von Werten, zb Summe von Werten
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | struct inverter_t { | ||||
|  |   uint8_t       ID;                       // Inverter-ID = Index
 | ||||
|  |   char          name[20];                 // Name des Inverters zb HM-600.1
 | ||||
|  |   uint64_t      serialNo;                 // dier Seriennummer wie im Barcode auf dem WR, also 1141.....
 | ||||
|  |   uint64_t      RadioId;                  // die gespiegelte (letzte 4 "Bytes") der Seriennummer
 | ||||
|  |   const measureDef_t  *measureDef;              // aus Include HMxxx.h : Liste mit Definitionen der Messwerte, wie Telgramm, offset, länge, ...
 | ||||
|  |   uint8_t       anzMeasures;              // Länge der Liste
 | ||||
|  |   measureCalc_t *measureCalculated;       // Liste mit Defintion für berechnete Werte
 | ||||
|  |   uint8_t       anzMeasureCalculated;     // Länge der Liste
 | ||||
|  |   uint8_t       anzTotalMeasures;         // Gesamtanzahl Messwerte
 | ||||
|  |   float         values[MAX_MEASURE_PER_INV];  // DIE Messewerte
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | char _buffer[20]; | ||||
|  | 
 | ||||
|  | uint8_t anzInv = 0; | ||||
|  | inverter_t inverters[MAX_ANZ_INV]; | ||||
|  |    | ||||
|  | union longlongasbytes { | ||||
|  |   uint64_t ull; | ||||
|  |   uint32_t ul[2]; | ||||
|  |   uint8_t bytes[8];   | ||||
|  | }; | ||||
|  | 
 | ||||
|  | char *uint64toa (uint64_t s) { | ||||
|  | //--------------------------------  
 | ||||
|  | //0x1141 72607952ULL   
 | ||||
|  |   sprintf(_buffer, "%lX%08lX", (unsigned long)(s>>32), (unsigned long)(s&0xFFFFFFFFULL)); | ||||
|  |   return _buffer; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | uint64_t Serial2RadioID (uint64_t sn) {    | ||||
|  | //----------------------------------
 | ||||
|  |   longlongasbytes llsn; | ||||
|  |   longlongasbytes res; | ||||
|  |   llsn.ull = sn; | ||||
|  |   res.ull = 0; | ||||
|  |   res.bytes[4] = llsn.bytes[0]; | ||||
|  |   res.bytes[3] = llsn.bytes[1]; | ||||
|  |   res.bytes[2] = llsn.bytes[2]; | ||||
|  |   res.bytes[1] = llsn.bytes[3]; | ||||
|  |   res.bytes[0] = 0x01; | ||||
|  |   return res.ull; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void addInverter (uint8_t _ID, const char * _name, uint64_t _serial,  | ||||
|  |                   const measureDef_t * liste, int anzMeasure, | ||||
|  |                   measureCalc_t * calcs, int anzMeasureCalculated) { | ||||
|  | //-------------------------------------------------------------------------------------
 | ||||
|  |   if (anzInv >= MAX_ANZ_INV) { | ||||
|  |     DEBUG_OUT.println(F("ANZ_INV zu klein!")); | ||||
|  |     return; | ||||
|  |   } | ||||
|  |   inverter_t *p = &(inverters[anzInv]); | ||||
|  |   p->ID                   = _ID; | ||||
|  |   strcpy (p->name, _name); | ||||
|  |   p->serialNo             = _serial; | ||||
|  |   p->RadioId              = Serial2RadioID(_serial); | ||||
|  |   p->measureDef           = liste; | ||||
|  |   p->anzMeasures          = anzMeasure; | ||||
|  |   p->anzMeasureCalculated = anzMeasureCalculated; | ||||
|  |   p->measureCalculated    = calcs; | ||||
|  |   p->anzTotalMeasures     = anzMeasure + anzMeasureCalculated; | ||||
|  |   memset (p->values, 0, sizeof(p->values)); | ||||
|  | 
 | ||||
|  |   DEBUG_OUT.print (F("WR       : "));      DEBUG_OUT.println(anzInv); | ||||
|  |   DEBUG_OUT.print (F("Type     : "));      DEBUG_OUT.println(_name); | ||||
|  |   DEBUG_OUT.print (F("Serial   : "));      DEBUG_OUT.println(uint64toa(_serial)); | ||||
|  |   DEBUG_OUT.print (F("Radio-ID : "));      DEBUG_OUT.println(uint64toa(p->RadioId)); | ||||
|  | 
 | ||||
|  |   anzInv++; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static uint8_t toggle = 0;           // nur für Test, ob's auch für mehere WR funzt
 | ||||
|  | uint8_t findInverter (uint8_t *fourbytes) { | ||||
|  | //---------------------------------------
 | ||||
|  |   for (uint8_t i = 0; i < anzInv; i++) { | ||||
|  |     longlongasbytes llb; | ||||
|  |     llb.ull = inverters[i].serialNo;   | ||||
|  |     if (llb.bytes[3] == fourbytes[0] &&  | ||||
|  |         llb.bytes[2] == fourbytes[1] &&  | ||||
|  |         llb.bytes[1] == fourbytes[2] && | ||||
|  |         llb.bytes[0] == fourbytes[3] ) | ||||
|  |       { | ||||
|  |         return i; | ||||
|  |         //if (toggle) toggle = 0; else toggle = 1; return toggle;     // Test ob mehr WR  auch geht
 | ||||
|  |       } | ||||
|  |   } | ||||
|  |   return 0xFF;      // nicht gefunden
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | char * error = {"error"}; | ||||
|  | 
 | ||||
|  | char *getMeasureName (uint8_t wr, uint8_t i){ | ||||
|  | //------------------------------------------
 | ||||
|  |   inverter_t *p = &(inverters[wr]); | ||||
|  |   if (i >= p->anzTotalMeasures) return error; | ||||
|  |   uint8_t idx, channel = 0; | ||||
|  |   if (i < p->anzMeasures) { | ||||
|  |     idx = p->measureDef[i].nameIdx;   | ||||
|  |     channel = p->measureDef[i].channel; | ||||
|  |   } | ||||
|  |   else { | ||||
|  |     idx = p->measureCalculated[i - p->anzMeasures].nameIdx;   | ||||
|  |   } | ||||
|  |   char tmp[20]; | ||||
|  |   strcpy_P (_buffer, NAMES[idx]); | ||||
|  |   if (channel) { | ||||
|  |     sprintf_P (tmp, PSTR(".CH%d"), channel); | ||||
|  |     strcat(_buffer,tmp); | ||||
|  |   } | ||||
|  |   return _buffer; | ||||
|  | } | ||||
|  | 
 | ||||
|  | const char *getUnit (uint8_t wr, uint8_t i) { | ||||
|  | //------------------------------------------
 | ||||
|  |   inverter_t *p = &(inverters[wr]); | ||||
|  |   if (i >= p->anzTotalMeasures) return error; | ||||
|  |   uint8_t idx; | ||||
|  |   if (i < p->anzMeasures) | ||||
|  |     idx = p->measureDef[i].unitIdx; | ||||
|  |   else | ||||
|  |     idx = p->measureCalculated[i-p->anzMeasures].unitIdx; | ||||
|  |    | ||||
|  |   //strcpy (_buffer, units[i]);
 | ||||
|  |   //return _buffer;
 | ||||
|  |   return units[idx]; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float getMeasureValue (uint8_t wr, uint8_t i) { | ||||
|  | //------------------------------------------
 | ||||
|  |   if (i >= inverters[wr].anzTotalMeasures) return 0.0; | ||||
|  |   return inverters[wr].values[i]; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | int getDivisor (uint8_t wr, uint8_t i) { | ||||
|  | //------------------------------------
 | ||||
|  |   inverter_t *p = &(inverters[wr]); | ||||
|  |   if (i >= p->anzTotalMeasures) return 1; | ||||
|  |   if (i < p->anzMeasures) { | ||||
|  |     uint8_t digits = p->measureDef[i].digits; | ||||
|  |     if (digits == DIV1) return 1; | ||||
|  |     if (digits == DIV10) return 10; | ||||
|  |     if (digits == DIV100) return 100; | ||||
|  |     if (digits == DIV1000) return 1000; | ||||
|  |     return 1; | ||||
|  |   } | ||||
|  |   else | ||||
|  |     return p->measureCalculated[i].digits;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | uint8_t getDigits (uint8_t wr, uint8_t i) { | ||||
|  | //---------------------------------------  
 | ||||
|  |   inverter_t *p = &(inverters[wr]); | ||||
|  |   if (i >= p->anzTotalMeasures) return 0; | ||||
|  |   if (i < p->anzMeasures)  | ||||
|  |     return p->measureDef[i].digits; | ||||
|  |   else | ||||
|  |     return p->measureCalculated[i-p->anzMeasures].digits;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | // +++++++++++++++++++++++++++++++++++    Inverter    ++++++++++++++++++++++++++++++++++++++++++++++
 | ||||
|  | 
 | ||||
|  | #include "HM600.h"            // für HM-600 und HM-700 | ||||
|  | 
 | ||||
|  | #include "HM1200.h" | ||||
|  | 
 | ||||
|  | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void setupInverts() { | ||||
|  | //-----------------  
 | ||||
|  | 
 | ||||
|  |   addInverter (0,"HM-600", 0x114172607952ULL,  | ||||
|  |                hm600_measureDef, HM600_MEASURE_LIST_LEN,     // Tabelle der Messwerte
 | ||||
|  |                hm600_measureCalc, HM600_CALCED_LIST_LEN); // Tabelle berechnete Werte
 | ||||
|  | 
 | ||||
|  | /*
 | ||||
|  |   addInverter (1,"HM-1200", 0x114172607952ULL,  | ||||
|  |                hm1200_measureDef, HM1200_MEASURE_LIST_LEN,     // Tabelle der Messwerte
 | ||||
|  |                hm1200_measureCalc, HM1200_CALCED_LIST_LEN);    // Tabelle berechnete Werte
 | ||||
|  | */ | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,151 @@ | |||||
|  | // #################  WebServer #################
 | ||||
|  | 
 | ||||
|  | #ifndef __MODWEBSERVER_H | ||||
|  | #define __MODWEBSERVER_H | ||||
|  | #define MODWEBSERVER | ||||
|  | 
 | ||||
|  | #include <ESP8266WebServer.h> | ||||
|  | #include "Debug.h" | ||||
|  | #include "Settings.h" | ||||
|  | 
 | ||||
|  | ESP8266WebServer server (WEBSERVER_PORT); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void returnOK () { | ||||
|  |   //--------------
 | ||||
|  |   server.send(200, F("text/plain"), ""); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void returnFail(String msg) { | ||||
|  |   //-------------------------
 | ||||
|  |   server.send(500, F("text/plain"), msg + "\r\n"); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void handleHelp () { | ||||
|  | //-----------------  
 | ||||
|  |   String out = "<html>"; | ||||
|  |   out += "<body><h2>Hilfe</h2>"; | ||||
|  |   out += "<br><br><table>"; | ||||
|  |   out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>"; | ||||
|  |   out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>"; | ||||
|  |   out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>"; | ||||
|  |   out += "<tr><td>/reboot</td><td>startet neu</td></tr>"; | ||||
|  |   out += "</table></body></html>"; | ||||
|  |   server.send (200, "text/html", out); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleReboot () { | ||||
|  |   //-------------------
 | ||||
|  |   returnOK (); | ||||
|  |   ESP.reset(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleRoot() { | ||||
|  |   //----------------
 | ||||
|  |   String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>"; | ||||
|  |   out += "<body>"; | ||||
|  |   out += "<h2>Hoymiles Micro-Inverters</h2>"; | ||||
|  |   char floatString[20]; | ||||
|  |   char line[100]; | ||||
|  |   for (uint8_t wr = 0; wr < anzInv; wr++) { | ||||
|  |     out += "<h3>" + String(inverters[wr].name) + "</h3>"; | ||||
|  |     out += "<h3>S/N " + String (getSerialNoTxt(wr)) + "</h3>"; | ||||
|  |     out += "<br><br><table border='1'>"; | ||||
|  |     out += "<tr><th>Kanal</th><th>Wert</th><th>Einheit</th></tr>"; | ||||
|  |     for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { | ||||
|  |       dtostrf (getMeasureValue(wr, i),1, getDigits(wr,i), floatString); | ||||
|  |       sprintf(line, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>", getMeasureName(wr, i), floatString, getUnit(wr, i)); | ||||
|  |       //DEBUG_OUT.println(line);
 | ||||
|  |       out += String(line);  | ||||
|  | /*        out += "<tr><td>" + getMeasureName(i) + "</td>";
 | ||||
|  |       out += "<td>" +  String(getMeasureValue(i)) + "</td></tr>"; | ||||
|  |       out += "<td>" +  String(getUnit(i)) + "</td></tr>"; */ | ||||
|  |     } | ||||
|  |     out += "</table>"; | ||||
|  |   } | ||||
|  |   int pos = out.indexOf("°"); | ||||
|  |   do { | ||||
|  |     if (pos>1) { | ||||
|  |       out = out.substring (0, pos) + "°" + out.substring(pos+2); | ||||
|  |     } | ||||
|  |     pos = out.indexOf("°"); | ||||
|  |   } while (pos>1); | ||||
|  |   | ||||
|  |   out += "</body></html>"; | ||||
|  |   server.send (200, "text/html", out); | ||||
|  |   //DEBUG_OUT.println (out);
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleData () { | ||||
|  | //-----------------
 | ||||
|  |   String out = ""; | ||||
|  |   for (uint8_t wr = 0; wr < anzInv; wr++) { | ||||
|  |     for (int i = 0; i < inverters[wr].anzTotalMeasures; i++) { | ||||
|  |       out += (anzInv <= 1 ? "" : String (wr) + ".") + String(getMeasureName(wr,i)) + '='  | ||||
|  |              + String (getMeasureValue(wr,i)) /*+ ' ' + String(getUnit(wr,i))*/ + '\n'; | ||||
|  |     } | ||||
|  |   } | ||||
|  |   server.send(200, "text/plain", out);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleNotFound() { | ||||
|  | //--------------------  
 | ||||
|  |   String message  = "URI: "; | ||||
|  |   message += server.uri(); | ||||
|  |   message += "\nMethod: "; | ||||
|  |   message += (server.method() == HTTP_GET) ? "GET" : "POST"; | ||||
|  |   message += "\nArguments: "; | ||||
|  |   message += server.args(); | ||||
|  |   message += "\n"; | ||||
|  |   for (uint8_t i = 0; i < server.args(); i++) { | ||||
|  |     message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; | ||||
|  |   } | ||||
|  |   server.send(404, "text/plain", message); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void setupWebServer (void) { | ||||
|  |   //-------------------------
 | ||||
|  |   server.begin(); | ||||
|  |   server.on("/",        handleRoot); | ||||
|  |   server.on("/reboot",  handleReboot); | ||||
|  |   server.on("/data",    handleData); | ||||
|  |   server.on("/help",    handleHelp); | ||||
|  |   //server.onNotFound(handleNotFound);    wegen Spiffs-Dateimanager
 | ||||
|  | 
 | ||||
|  |   DEBUG_OUT.println ("[HTTP] installed"); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void webserverHandle() { | ||||
|  | //====================
 | ||||
|  |   server.handleClient();   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // #################  OTA #################
 | ||||
|  | 
 | ||||
|  | #ifdef WITH_OTA | ||||
|  | #include <ESP8266HTTPUpdateServer.h> | ||||
|  | 
 | ||||
|  | ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT); | ||||
|  | ESP8266HTTPUpdateServer httpUpdater; | ||||
|  | 
 | ||||
|  | void setupUpdateByOTA () { | ||||
|  |   //------------------------
 | ||||
|  |   httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW); | ||||
|  |   httpUpdateServer.begin(); | ||||
|  |   DEBUG_OUT.println ("[OTA] installed"); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void checkUpdateByOTA() { | ||||
|  | //---------------------
 | ||||
|  |   httpUpdateServer.handleClient();   | ||||
|  | } | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,69 @@ | |||||
|  | #ifndef __SETTINGS_H | ||||
|  | #define __SETTINGS_H | ||||
|  | 
 | ||||
|  | // Ausgabe von Debug Infos auf der seriellen Console
 | ||||
|  | #define DEBUG | ||||
|  | #define SER_BAUDRATE            (115200) | ||||
|  | 
 | ||||
|  | #include "Debug.h" | ||||
|  | 
 | ||||
|  | // Ausgabe was gesendet wird; 0 oder 1 
 | ||||
|  | #define DEBUG_SEND  0    | ||||
|  | 
 | ||||
|  | // soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
 | ||||
|  | #define CHANNEL_HOP | ||||
|  | 
 | ||||
|  | // mit OTA Support, also update der Firmware über WLan mittels IP/update
 | ||||
|  | #define WITH_OTA | ||||
|  | 
 | ||||
|  | // Hardware configuration
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define RF1_CE_PIN (D4) | ||||
|  | #define RF1_CS_PIN (D8) | ||||
|  | #define RF1_IRQ_PIN (D3) | ||||
|  | #else | ||||
|  | #define RF1_CE_PIN (9) | ||||
|  | #define RF1_CS_PIN (10) | ||||
|  | #define RF1_IRQ_PIN (2) | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | // WR und DTU
 | ||||
|  | #define RF_MAX_ADDR_WIDTH       (5)  | ||||
|  | #define MAX_RF_PAYLOAD_SIZE     (32) | ||||
|  | #define DEFAULT_RF_DATARATE     (RF24_250KBPS)  // Datarate
 | ||||
|  | 
 | ||||
|  | #define USE_POOR_MAN_CHANNEL_HOPPING_RCV  1     // 0 = not use
 | ||||
|  | 
 | ||||
|  | #define DUMMY_RADIO_ID          ((uint64_t)0xDEADBEEF01ULL)  | ||||
|  | #define DTU_RADIO_ID            ((uint64_t)0x1234567801ULL) | ||||
|  | #define MAX_ANZ_INV 2             							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define MAX_MEASURE_PER_INV 25    // hier statisch, könnte auch dynamisch erzeugt werden, aber Overhead für dyn. Speicher?
 | ||||
|  | 
 | ||||
|  | // Webserver
 | ||||
|  | #define WEBSERVER_PORT          80 | ||||
|  | 
 | ||||
|  | // Time Server
 | ||||
|  | //#define TIMESERVER_NAME "pool.ntp.org"
 | ||||
|  | #define TIMESERVER_NAME "fritz.box"							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | 
 | ||||
|  | #ifdef WITH_OTA | ||||
|  | // OTA Einstellungen
 | ||||
|  | #define UPDATESERVER_PORT   WEBSERVER_PORT+1 | ||||
|  | #define UPDATESERVER_DIR    "/update" | ||||
|  | #define UPDATESERVER_USER   "?????"							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define UPDATESERVER_PW     "?????"							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | // internes WLan
 | ||||
|  | // PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) von fremden zu unterscheiden
 | ||||
|  | // gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
 | ||||
|  | #define SSID_PREFIX1         "pre1"							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define SSID_PREFIX2         "pre2"							// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define SSID_PASSWORD        "?????????????????"			// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | 
 | ||||
|  | // zur Berechnung von Sonnenauf- und -untergang
 | ||||
|  | #define  geoBreite  49.2866									// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define  geoLaenge  7.3416									// <<<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,55 @@ | |||||
|  | #ifndef __SONNE_H | ||||
|  | #define __SONNE_H | ||||
|  | 
 | ||||
|  | #include "Settings.h" | ||||
|  | #include "Debug.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | long SunDown, SunUp; | ||||
|  | 
 | ||||
|  | void calcSunUpDown (time_t date) { | ||||
|  |     //SunUpDown res = new SunUpDown();
 | ||||
|  |     boolean isSummerTime = false;   // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
 | ||||
|  |      | ||||
|  |     //- Bogenmass
 | ||||
|  |     double brad = geoBreite / 180.0 * PI; | ||||
|  |     // - Höhe Sonne -50 Bogenmin.
 | ||||
|  |     double h0 = -50.0 / 60.0 / 180.0 * PI; | ||||
|  |     //- Deklination dek, Tag des Jahres d0
 | ||||
|  |     int tage = 30 * month(date) - 30 + day(date);  | ||||
|  |     double dek = 0.40954 * sin (0.0172 * (tage - 79.35)); | ||||
|  |     double zh1 = sin (h0) - sin (brad)  *  sin(dek); | ||||
|  |     double zh2 = cos(brad) * cos(dek); | ||||
|  |     double zd = 12*acos (zh1/zh2) / PI; | ||||
|  |     double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939); | ||||
|  |     //-Sonnenuntergang
 | ||||
|  |     double tsu = 12 + zd - zgl; | ||||
|  |     double su = (tsu + (15.0 - geoLaenge) / 15.0);  | ||||
|  |     int std = (int)su; | ||||
|  |     int minute = (int) ((su - std)*60); | ||||
|  |     if (isSummerTime) std++; | ||||
|  |     SunDown = (100*std + minute) * 100; | ||||
|  |      | ||||
|  |     //- Sonnenaufgang
 | ||||
|  |     double tsa = 12 - zd - zgl; | ||||
|  |     double sa = (tsa + (15.0 - geoLaenge) /15.0);  | ||||
|  |     std = (int) sa; | ||||
|  |     minute = (int) ((sa - std)*60); | ||||
|  |     if (isSummerTime) std++; | ||||
|  |     SunUp = (100*std + minute) * 100; | ||||
|  |     DEBUG_OUT.print(F("Sonnenaufgang  :")); DEBUG_OUT.println(SunUp); | ||||
|  |     DEBUG_OUT.print(F("Sonnenuntergang:")); DEBUG_OUT.println(SunDown); | ||||
|  | } | ||||
|  | 
 | ||||
|  | boolean isDayTime() { | ||||
|  | //-----------------
 | ||||
|  | // 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
 | ||||
|  |   const int offset=60*15; | ||||
|  |   time_t no = getNow(); | ||||
|  |   long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100; | ||||
|  |   long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100; | ||||
|  | 
 | ||||
|  |   return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown));   | ||||
|  | } | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,102 @@ | |||||
|  | #ifndef __HM_CRC_H | ||||
|  | #define __HM_CRC_H | ||||
|  | 
 | ||||
|  | #define BITS_TO_BYTES(x)  (((x)+7)>>3) | ||||
|  | #define BYTES_TO_BITS(x)  ((x)<<3) | ||||
|  | 
 | ||||
|  | extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen); | ||||
|  | extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen); | ||||
|  | extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits); | ||||
|  | 
 | ||||
|  | //#define OUTPUT_DEBUG_INFO
 | ||||
|  | 
 | ||||
|  | #define CRC8_INIT               0x00 | ||||
|  | #define CRC8_POLY               0x01 | ||||
|  | 
 | ||||
|  | #define CRC16_MODBUS_POLYNOM    0xA001 | ||||
|  | 
 | ||||
|  | uint8_t crc8(uint8_t buf[], uint16_t len) { | ||||
|  |     uint8_t crc = CRC8_INIT; | ||||
|  |     for(uint8_t i = 0; i < len; i++) { | ||||
|  |         crc ^= buf[i]; | ||||
|  |         for(uint8_t b = 0; b < 8; b ++) { | ||||
|  |             crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); | ||||
|  |         } | ||||
|  |     } | ||||
|  |     return crc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | uint16_t crc16_modbus(uint8_t buf[], uint16_t len) { | ||||
|  |     uint16_t crc = 0xffff; | ||||
|  |     uint8_t lsb; | ||||
|  | 
 | ||||
|  |     for(uint8_t i = 0; i < len; i++) { | ||||
|  |         crc = crc ^ buf[i]; | ||||
|  |         for(int8_t b = 7; b >= 0; b--) { | ||||
|  |             lsb = (crc & 0x0001); | ||||
|  |             if(lsb == 0x01) | ||||
|  |                 crc--; | ||||
|  |             crc = crc >> 1; | ||||
|  |             if(lsb == 0x01) | ||||
|  |                 crc = crc ^ CRC16_MODBUS_POLYNOM; | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return crc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
 | ||||
|  | uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits) | ||||
|  | { | ||||
|  | 	uint16_t crc = startCRC; | ||||
|  | 	if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen))) | ||||
|  | 	{ | ||||
|  | 		// The length of the data might not be a multiple of full bytes.
 | ||||
|  | 		// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
 | ||||
|  | 		// calculate the CRC.
 | ||||
|  | 		uint16_t data; | ||||
|  | 		uint8_t byte, shift; | ||||
|  | 		uint16_t bitoffs = startBit; | ||||
|  | 
 | ||||
|  | 		// Get a new byte for the next 8 bits.
 | ||||
|  | 		byte = buf[bitoffs >> 3]; | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 		printf_P(PSTR("\nStart CRC %04X, %u bits:"), startCRC, len_bits); | ||||
|  | 		printf_P(PSTR("\nbyte %02X:"), byte); | ||||
|  | #endif | ||||
|  | 		while (bitoffs < len_bits + startBit) | ||||
|  | 		{ | ||||
|  | 			shift = bitoffs & 7; | ||||
|  | 			// Shift the active bit to the position of bit 15
 | ||||
|  | 			data = ((uint16_t)byte) << (8 + shift); | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 			printf_P(PSTR(" bit %u %u,"), shift, data & 0x8000 ? 1 : 0); | ||||
|  | #endif | ||||
|  | 			// Assure all other bits are 0
 | ||||
|  | 			data &= 0x8000; | ||||
|  | 			crc ^= data; | ||||
|  | 			if (crc & 0x8000) | ||||
|  | 			{ | ||||
|  | 				crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
 | ||||
|  | 			} | ||||
|  | 			else | ||||
|  | 			{ | ||||
|  | 				crc = (crc << 1); | ||||
|  | 			} | ||||
|  | 			++bitoffs; | ||||
|  | 			if (0 == (bitoffs & 7)) | ||||
|  | 			{ | ||||
|  | 				// Get a new byte for the next 8 bits.
 | ||||
|  | 				byte = buf[bitoffs >> 3]; | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 				printf_P(PSTR("crc %04X:"), crc); | ||||
|  | 				if (bitoffs < len_bits + startBit) | ||||
|  | 					printf_P(PSTR("\nbyte %02X:"), byte); | ||||
|  | #endif | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 	return crc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,93 @@ | |||||
|  | #ifndef __HM_PACKETS_H | ||||
|  | #define __HM_PACKETS_H | ||||
|  | 
 | ||||
|  | #include "hm_crc.h" | ||||
|  | 
 | ||||
|  | class HM_Packets | ||||
|  | { | ||||
|  | private: | ||||
|  | 	uint32_t unixTimeStamp; | ||||
|  | 
 | ||||
|  | 	void prepareBuffer(uint8_t *buf); | ||||
|  | 	void copyToBuffer(uint8_t *buf, uint32_t val); | ||||
|  | 	void copyToBufferBE(uint8_t *buf, uint32_t val); | ||||
|  | 
 | ||||
|  | public: | ||||
|  | 	void SetUnixTimeStamp(uint32_t ts); | ||||
|  | 	void UnixTimeStampTick(); | ||||
|  | 
 | ||||
|  | 	int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr); | ||||
|  | 	int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd); | ||||
|  | }; | ||||
|  | 
 | ||||
|  | void HM_Packets::SetUnixTimeStamp(uint32_t ts) | ||||
|  | { | ||||
|  | 	unixTimeStamp = ts; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::UnixTimeStampTick() | ||||
|  | { | ||||
|  | 	unixTimeStamp++; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::prepareBuffer(uint8_t *buf) | ||||
|  | { | ||||
|  | 	// minimal buffer size of 32 bytes is assumed
 | ||||
|  | 	memset(buf, 0x00, 32); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val) | ||||
|  | { | ||||
|  | 	buf[0]= (uint8_t)(val >> 24); | ||||
|  | 	buf[1]= (uint8_t)(val >> 16); | ||||
|  | 	buf[2]= (uint8_t)(val >> 8); | ||||
|  | 	buf[3]= (uint8_t)(val & 0xFF); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val) | ||||
|  | { | ||||
|  | 	memcpy(buf, &val, sizeof(uint32_t)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | static uint8_t cid = 0; | ||||
|  | 
 | ||||
|  | int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr) | ||||
|  | { | ||||
|  | 	prepareBuffer(buf); | ||||
|  | 
 | ||||
|  | 	buf[0] = 0x15; | ||||
|  | 	copyToBufferBE(&buf[1], wrAdr); | ||||
|  | 	copyToBufferBE(&buf[5], dtuAdr); | ||||
|  | 	buf[9] = 0x80; | ||||
|  |   buf[10] = 0x0B;        //0x0B;  0x03 0x11
 | ||||
|  | 	buf[11] = 0x00; | ||||
|  | 
 | ||||
|  | 	copyToBuffer(&buf[12], unixTimeStamp); | ||||
|  | 
 | ||||
|  | 	buf[19] = 0x05; | ||||
|  | 
 | ||||
|  | 	// CRC16
 | ||||
|  | 	uint16_t crc16 = crc16_modbus(&buf[10], 14); | ||||
|  | 	buf[24] = crc16 >> 8; | ||||
|  | 	buf[25] = crc16 & 0xFF; | ||||
|  | 
 | ||||
|  | 	// crc8
 | ||||
|  | 	buf[26] = crc8(&buf[0], 26); | ||||
|  | 
 | ||||
|  | 	return 27; | ||||
|  | } | ||||
|  | 
 | ||||
|  | int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd) | ||||
|  | { | ||||
|  | 	buf[0] = mid; | ||||
|  | 	copyToBufferBE(&buf[1], wrAdr); | ||||
|  | 	copyToBufferBE(&buf[5], dtuAdr); | ||||
|  | 	buf[9] = cmd; | ||||
|  | 
 | ||||
|  | 	// crc8
 | ||||
|  | 	buf[10] = crc8(&buf[0], 10); | ||||
|  | 
 | ||||
|  | 	return 11; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,345 @@ | |||||
|  | #ifndef __WIFI_H | ||||
|  | #define __WIFI_H | ||||
|  | 
 | ||||
|  | #include "Settings.h" | ||||
|  | #include "Debug.h" | ||||
|  | #include <ESP8266WiFi.h> | ||||
|  | #include <Pinger.h>       // von url=https://www.technologytourist.com    | ||||
|  | 
 | ||||
|  | String SSID = "";         // bestes WLan
 | ||||
|  | 
 | ||||
|  | // Prototypes
 | ||||
|  | time_t getNow (); | ||||
|  | boolean setupWifi (); | ||||
|  | boolean checkWifi(); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | String findWifi () { | ||||
|  | //----------------
 | ||||
|  |   String ssid; | ||||
|  |   int32_t rssi; | ||||
|  |   uint8_t encryptionType; | ||||
|  |   uint8_t* bssid; | ||||
|  |   int32_t channel; | ||||
|  |   bool hidden; | ||||
|  |   int scanResult; | ||||
|  |    | ||||
|  |   String best_ssid = ""; | ||||
|  |   int32_t best_rssi = -100; | ||||
|  |    | ||||
|  |   DEBUG_OUT.println(F("Starting WiFi scan...")); | ||||
|  | 
 | ||||
|  |   scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true); | ||||
|  | 
 | ||||
|  |   if (scanResult == 0) { | ||||
|  |     DEBUG_OUT.println(F("keine WLans")); | ||||
|  |   } else if (scanResult > 0) { | ||||
|  |     DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult); | ||||
|  | 
 | ||||
|  |     // Print unsorted scan results
 | ||||
|  |     for (int8_t i = 0; i < scanResult; i++) { | ||||
|  |       WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden); | ||||
|  | 
 | ||||
|  |       DEBUG_OUT.printf(PSTR("  %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"), | ||||
|  |                     i, | ||||
|  |                     channel, | ||||
|  |                     bssid[0], bssid[1], bssid[2], | ||||
|  |                     bssid[3], bssid[4], bssid[5], | ||||
|  |                     rssi, | ||||
|  |                     (encryptionType == ENC_TYPE_NONE) ? ' ' : '*', | ||||
|  |                     hidden ? 'H' : 'V', | ||||
|  |                     ssid.c_str()); | ||||
|  |       delay(1); | ||||
|  |       boolean check; | ||||
|  |       #ifdef SSID_PREFIX1 | ||||
|  |       check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1); | ||||
|  |       #else | ||||
|  |       check = true; | ||||
|  |       #endif     | ||||
|  |       #ifdef SSID_PREFIX2 | ||||
|  |       check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2); | ||||
|  |       #endif | ||||
|  |       if (check) { | ||||
|  |         if (rssi > best_rssi) { | ||||
|  |           best_rssi = rssi; | ||||
|  |           best_ssid = ssid; | ||||
|  |         } | ||||
|  |       } | ||||
|  |     } | ||||
|  |   } else { | ||||
|  |     DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   if (! best_ssid.equals("")) { | ||||
|  |     SSID = best_ssid; | ||||
|  |     DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str()); | ||||
|  |     return SSID; | ||||
|  |   } | ||||
|  |   else | ||||
|  |     return ""; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void IP2string (IPAddress IP, char * buf) { | ||||
|  |   sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void connectWifi() { | ||||
|  | //------------------
 | ||||
|  | //  if (SSID.equals(""))
 | ||||
|  |    String s = findWifi(); | ||||
|  | 
 | ||||
|  |   if (!SSID.equals("")) { | ||||
|  |     DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID); | ||||
|  |     //while (WiFi.status() != WL_CONNECTED) {
 | ||||
|  |     WiFi.begin (SSID, SSID_PASSWORD); | ||||
|  |     int versuche = 20; | ||||
|  |     while (WiFi.status() != WL_CONNECTED && versuche > 0) { | ||||
|  |       delay(1000); | ||||
|  |       versuche--; | ||||
|  |       DEBUG_OUT.print(versuche); DEBUG_OUT.print(' '); | ||||
|  |     } | ||||
|  |     //}
 | ||||
|  |     if (WiFi.status() == WL_CONNECTED) { | ||||
|  |       char buffer[30]; | ||||
|  |       IP2string (WiFi.localIP(), buffer); | ||||
|  |       String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer); | ||||
|  |       DEBUG_OUT.println (out); | ||||
|  |     } | ||||
|  |     else | ||||
|  |       DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID); | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | boolean setupWifi () { | ||||
|  | //------------------  
 | ||||
|  |   int count=5; | ||||
|  |   while (count-- && WiFi.status() != WL_CONNECTED) | ||||
|  |     connectWifi();    | ||||
|  |   return (WiFi.status() == WL_CONNECTED); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | Pinger pinger; | ||||
|  | IPAddress ROUTER = IPAddress(192,168,1,1); | ||||
|  | 
 | ||||
|  | boolean checkWifi() { | ||||
|  | //---------------
 | ||||
|  |   boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER); | ||||
|  |   if (NotConnected) { | ||||
|  |     setupWifi(); | ||||
|  |     if (WiFi.status() == WL_CONNECTED)  | ||||
|  |       getNow(); | ||||
|  |   } | ||||
|  |   return (WiFi.status() == WL_CONNECTED); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // ################  Clock  #################
 | ||||
|  | 
 | ||||
|  | #include <WiFiUdp.h> | ||||
|  | #include <TimeLib.h> | ||||
|  | 
 | ||||
|  | IPAddress     timeServer; | ||||
|  | unsigned int  localPort = 8888; | ||||
|  | const int     NTP_PACKET_SIZE= 48;            // NTP time stamp is in the first 48 bytes of the message
 | ||||
|  | byte          packetBuf[NTP_PACKET_SIZE];  // Buffer to hold incoming and outgoing packets
 | ||||
|  | const int     timeZone = 1;                   // Central European Time = +1
 | ||||
|  | long          SYNCINTERVALL = 0; | ||||
|  | WiFiUDP Udp;                                  // A UDP instance to let us send and receive packets over UDP
 | ||||
|  | 
 | ||||
|  | // prototypes
 | ||||
|  | time_t  getNtpTime (); | ||||
|  | void    sendNTPpacket (IPAddress &address); | ||||
|  | time_t  getNow (); | ||||
|  | char*   getDateTimeStr (time_t no = getNow()); | ||||
|  | time_t  offsetDayLightSaving (uint32_t local_t); | ||||
|  | bool    isDayofDaylightChange (time_t local_t); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void _setSyncInterval (long intervall) { | ||||
|  | //----------------------------------------  
 | ||||
|  |   SYNCINTERVALL = intervall; | ||||
|  |   setSyncInterval (intervall); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void setupClock() { | ||||
|  | //-----------------
 | ||||
|  |   WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
 | ||||
|  | 
 | ||||
|  |   Udp.begin(localPort); | ||||
|  | 
 | ||||
|  |   getNtpTime(); | ||||
|  | 
 | ||||
|  |   setSyncProvider (getNtpTime); | ||||
|  |   while(timeStatus()== timeNotSet) | ||||
|  |      delay(1); //
 | ||||
|  | 
 | ||||
|  |   _setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
 | ||||
|  | 
 | ||||
|  |   //lastClock = now();
 | ||||
|  |   //Serial.print("[NTP] get time from NTP server ");
 | ||||
|  |   getNow(); | ||||
|  |   //char buf[20];
 | ||||
|  |   DEBUG_OUT.print ("[NTP] get time from NTP server "); | ||||
|  |   DEBUG_OUT.print (timeServer); | ||||
|  |   //sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
 | ||||
|  |   DEBUG_OUT.print (": got "); | ||||
|  |   DEBUG_OUT.println (getDateTimeStr()); | ||||
|  | } | ||||
|  | 
 | ||||
|  | //*-------- NTP code ----------*/
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | time_t getNtpTime() { | ||||
|  | //-------------------
 | ||||
|  |   sendNTPpacket(timeServer); // send an NTP packet to a time server
 | ||||
|  |   //uint32_t beginWait = millis();
 | ||||
|  |   //while (millis() - beginWait < 1500) {
 | ||||
|  |   int versuch = 0; | ||||
|  |   while (versuch < 5) { | ||||
|  |     int wait = 150;      // results in max 1500 ms waitTime
 | ||||
|  |     while (wait--) { | ||||
|  |       int size = Udp.parsePacket(); | ||||
|  |       if (size >= NTP_PACKET_SIZE) { | ||||
|  |         //Serial.println("Receive NTP Response");
 | ||||
|  |         Udp.read(packetBuf, NTP_PACKET_SIZE);  // read packet into the buffer
 | ||||
|  |         unsigned long secsSince1900; | ||||
|  |         // convert four bytes starting at location 40 to a long integer
 | ||||
|  |         secsSince1900 =  (unsigned long)packetBuf[40] << 24; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[41] << 16; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[42] << 8; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[43]; | ||||
|  |         // time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
 | ||||
|  |    | ||||
|  |         time_t utc = secsSince1900 - 2208988800UL; | ||||
|  |         time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR; | ||||
|  |          | ||||
|  |         if (isDayofDaylightChange (utc) && hour(utc) <= 4) | ||||
|  |           _setSyncInterval (SECS_PER_HOUR); | ||||
|  |         else | ||||
|  |           _setSyncInterval (SECS_PER_DAY / 2); | ||||
|  |         | ||||
|  |         return now; | ||||
|  |       } | ||||
|  |       else | ||||
|  |         delay(10); | ||||
|  |     } | ||||
|  |     versuch++; | ||||
|  |   } | ||||
|  |   return 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // send an NTP request to the time server at the given address
 | ||||
|  | void sendNTPpacket(IPAddress& address) { | ||||
|  | //------------------------------------
 | ||||
|  |   memset(packetBuf, 0, NTP_PACKET_SIZE);  // set all bytes in the buffer to 0
 | ||||
|  |   // Initialize values needed to form NTP request
 | ||||
|  |   packetBuf[0] = B11100011;   // LI, Version, Mode
 | ||||
|  |   packetBuf[1] = 0;     // Stratum
 | ||||
|  |   packetBuf[2] = 6;     // Max Interval between messages in seconds
 | ||||
|  |   packetBuf[3] = 0xEC;  // Clock Precision
 | ||||
|  |   // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
 | ||||
|  |   packetBuf[12]  = 49;  // four-byte reference ID identifying
 | ||||
|  |   packetBuf[13]  = 0x4E; | ||||
|  |   packetBuf[14]  = 49; | ||||
|  |   packetBuf[15]  = 52; | ||||
|  |   // send the packet requesting a timestamp:
 | ||||
|  |   Udp.beginPacket(address, 123); //NTP requests are to port 123
 | ||||
|  |   Udp.write(packetBuf,NTP_PACKET_SIZE); | ||||
|  |   Udp.endPacket(); | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | int getTimeTrials = 0; | ||||
|  | 
 | ||||
|  | bool isValidDateTime (time_t no) { | ||||
|  |   return (year(no) > 2020 && year(no) < 2038);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | bool isDayofDaylightChange (time_t local_t) { | ||||
|  | //-----------------------------------------
 | ||||
|  |   int jahr  = year  (local_t); | ||||
|  |   int monat = month (local_t); | ||||
|  |   int tag   = day   (local_t); | ||||
|  |   bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) ||  | ||||
|  |                (monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7))); | ||||
|  |   DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret);  | ||||
|  |   return ret; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
 | ||||
|  | // übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
 | ||||
|  | time_t offsetDayLightSaving (uint32_t local_t) { | ||||
|  | //--------------------------------------------
 | ||||
|  |   int monat = month (local_t); | ||||
|  |   if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
 | ||||
|  |   if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
 | ||||
|  |   int jahr  = year (local_t); | ||||
|  |   int std   = hour (local_t); | ||||
|  |   //int tag   = day  (local_t);
 | ||||
|  |   int stundenBisHeute = (std + 24 * day(local_t)); | ||||
|  |   if ( (monat == 3  && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) ||  | ||||
|  |        (monat == 10 && stundenBisHeute <  (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) ) | ||||
|  |     return 1; | ||||
|  |   else | ||||
|  |     return 0; | ||||
|  |   /*
 | ||||
|  |   int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7)); | ||||
|  |   if (monat == 3  && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute <  stundenBisWechsel) | ||||
|  |     return 1; | ||||
|  |   else | ||||
|  |     return 0; | ||||
|  |     */ | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | time_t getNow () { | ||||
|  | //---------------
 | ||||
|  |   time_t jetzt = now(); | ||||
|  |   while (!isValidDateTime(jetzt) && getTimeTrials < 10)  { // ungültig, max 10x probieren
 | ||||
|  |     if (getTimeTrials) { | ||||
|  |       //Serial.print (getTimeTrials);
 | ||||
|  |       //Serial.println(". Versuch für getNtpTime");
 | ||||
|  |     } | ||||
|  |     jetzt = getNtpTime (); | ||||
|  |     if (isValidDateTime(jetzt)) { | ||||
|  |       setTime (jetzt); | ||||
|  |       getTimeTrials = 0; | ||||
|  |     } | ||||
|  |     else | ||||
|  |       getTimeTrials++; | ||||
|  |   } | ||||
|  |   //return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
 | ||||
|  |   return jetzt; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | char _timestr[24]; | ||||
|  | 
 | ||||
|  | char* getNowStr (time_t no = getNow()) { | ||||
|  | //------------------------------------
 | ||||
|  |   sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getTimeStr (time_t no = getNow()) { | ||||
|  | //------------------------------------
 | ||||
|  |   return getNowStr (no); | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getDateTimeStr (time_t no) { | ||||
|  | //------------------------------
 | ||||
|  |   sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getDateStr (time_t no) { | ||||
|  | //------------------------------
 | ||||
|  |   sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,158 @@ | |||||
|  | /*
 | ||||
|  |   CircularBuffer - An Arduino circular buffering library for arbitrary types. | ||||
|  | 
 | ||||
|  |   Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl | ||||
|  |    | ||||
|  |   This library is free software; you can redistribute it and/or | ||||
|  |   modify it under the terms of the GNU Lesser General Public | ||||
|  |   License as published by the Free Software Foundation; either | ||||
|  |   version 2.1 of the License, or (at your option) any later version. | ||||
|  | 
 | ||||
|  |   This library is distributed in the hope that it will be useful, | ||||
|  |   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  |   Lesser General Public License for more details. | ||||
|  | 
 | ||||
|  |   You should have received a copy of the GNU Lesser General Public | ||||
|  |   License along with this library; if not, write to the Free Software | ||||
|  |   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  | */ | ||||
|  | 
 | ||||
|  | #ifndef CircularBuffer_h | ||||
|  | #define CircularBuffer_h | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define DISABLE_IRQ noInterrupts() | ||||
|  | #define RESTORE_IRQ interrupts() | ||||
|  | #else | ||||
|  | #define DISABLE_IRQ       \ | ||||
|  |   uint8_t sreg = SREG;    \ | ||||
|  |   cli(); | ||||
|  | 
 | ||||
|  | #define RESTORE_IRQ        \ | ||||
|  |   SREG = sreg; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | template <class T> class CircularBuffer | ||||
|  | { | ||||
|  |   public: | ||||
|  |     /** Constructor
 | ||||
|  |      * @param buffer   Preallocated buffer of at least size records. | ||||
|  |      * @param size     Number of records available in the buffer. | ||||
|  |      */ | ||||
|  |     CircularBuffer(T* buffer, const uint8_t size ) | ||||
|  |       : m_size(size), m_buff(buffer) | ||||
|  |     { | ||||
|  |       clear(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Clear all entries in the circular buffer. */ | ||||
|  |     void clear(void) | ||||
|  |     { | ||||
|  |       m_front = 0; | ||||
|  |       m_fill  = 0; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Test if the circular buffer is empty */ | ||||
|  |     inline bool empty(void) const | ||||
|  |     { | ||||
|  |       return !m_fill; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Return the number of records stored in the buffer */ | ||||
|  |     inline uint8_t available(void) const | ||||
|  |     { | ||||
|  |       return m_fill; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Test if the circular buffer is full */ | ||||
|  |     inline bool full(void) const | ||||
|  |     { | ||||
|  |       return m_fill == m_size; | ||||
|  |     } | ||||
|  |      | ||||
|  |     /** Aquire record on front of the buffer, for writing.
 | ||||
|  |      * After filling the record, it has to be pushed to actually | ||||
|  |      * add it to the buffer. | ||||
|  |      * @return Pointer to record, or NULL when buffer is full. | ||||
|  |      */ | ||||
|  |     T* getFront(void) const | ||||
|  |     { | ||||
|  |       DISABLE_IRQ; | ||||
|  |       T* f = NULL; | ||||
|  |       if (!full()) | ||||
|  |         f = get(m_front); | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return f; | ||||
|  |     } | ||||
|  |      | ||||
|  |     /** Push record to front of the buffer
 | ||||
|  |      * @param record   Record to push. If record was aquired previously (using getFront) its | ||||
|  |      *                 data will not be copied as it is already present in the buffer. | ||||
|  |      * @return True, when record was pushed successfully. | ||||
|  |      */ | ||||
|  |     bool pushFront(T* record) | ||||
|  |     { | ||||
|  |       bool ok = false; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!full()) | ||||
|  |       { | ||||
|  |         T* f = get(m_front); | ||||
|  |         if (f != record) | ||||
|  |           *f = *record; | ||||
|  |         m_front = (m_front+1) % m_size; | ||||
|  |         m_fill++; | ||||
|  |         ok = true; | ||||
|  |       } | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return ok; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Aquire record on back of the buffer, for reading.
 | ||||
|  |      * After reading the record, it has to be pop'ed to actually | ||||
|  |      * remove it from the buffer. | ||||
|  |      * @return Pointer to record, or NULL when buffer is empty. | ||||
|  |      */ | ||||
|  |     T* getBack(void) const | ||||
|  |     { | ||||
|  |       T* b = NULL; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!empty()) | ||||
|  |         b = get(back()); | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return b; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     /** Remove record from back of the buffer.
 | ||||
|  |      * @return True, when record was pop'ed successfully. | ||||
|  |      */ | ||||
|  |     bool popBack(void) | ||||
|  |     { | ||||
|  |       bool ok = false; | ||||
|  |       DISABLE_IRQ; | ||||
|  |       if (!empty()) | ||||
|  |       { | ||||
|  |         m_fill--; | ||||
|  |         ok = true; | ||||
|  |       } | ||||
|  |       RESTORE_IRQ; | ||||
|  |       return ok; | ||||
|  |     } | ||||
|  |      | ||||
|  |   protected: | ||||
|  |     inline T * get(const uint8_t idx) const | ||||
|  |     { | ||||
|  |       return &(m_buff[idx]); | ||||
|  |     } | ||||
|  |     inline uint8_t back(void) const | ||||
|  |     { | ||||
|  |       return (m_front - m_fill + m_size) % m_size; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     const uint8_t      m_size;     // Total number of records that can be stored in the buffer.
 | ||||
|  |     T* const           m_buff;     // Ptr to buffer holding all records.
 | ||||
|  |     volatile uint8_t   m_front;    // Index of front element (not pushed yet).
 | ||||
|  |     volatile uint8_t   m_fill;     // Amount of records currently pushed.
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif // CircularBuffer_h
 | ||||
| @ -0,0 +1,23 @@ | |||||
|  | #ifndef __DEBUG_H | ||||
|  | 
 | ||||
|  | #define __DEBUG_H | ||||
|  | 
 | ||||
|  | #ifdef DEBUG | ||||
|  |   #define DEBUG_OUT  Serial | ||||
|  | #else  | ||||
|  | //---
 | ||||
|  | // disable Serial DEBUG output
 | ||||
|  |   #define DEBUG_OUT DummySerial | ||||
|  |   static class { | ||||
|  |   public: | ||||
|  |       void begin(...) {} | ||||
|  |       void print(...) {} | ||||
|  |       void println(...) {} | ||||
|  |       void flush() {} | ||||
|  |       bool available() { return false;} | ||||
|  |       int  readBytes(...) { return 0;} | ||||
|  |       int  printf (...) {return 0;} | ||||
|  |   } DummySerial; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif  | ||||
| @ -0,0 +1,129 @@ | |||||
|  | // #################  WebServer #################
 | ||||
|  | 
 | ||||
|  | #ifndef __MODWEBSERVER_H | ||||
|  | #define __MODWEBSERVER_H | ||||
|  | #define MODWEBSERVER | ||||
|  | 
 | ||||
|  | #include <ESP8266WebServer.h> | ||||
|  | #include "Debug.h" | ||||
|  | #include "Settings.h" | ||||
|  | 
 | ||||
|  | ESP8266WebServer server (WEBSERVER_PORT); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void returnOK () { | ||||
|  |   //--------------
 | ||||
|  |   server.send(200, F("text/plain"), ""); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void returnFail(String msg) { | ||||
|  |   //-------------------------
 | ||||
|  |   server.send(500, F("text/plain"), msg + "\r\n"); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void handleHelp () { | ||||
|  | //-----------------  
 | ||||
|  |   String out = "<html>"; | ||||
|  |   out += "<body><h2>Hilfe</h2>"; | ||||
|  |   out += "<br><br><table>"; | ||||
|  |   out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>"; | ||||
|  |   out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>"; | ||||
|  |   out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>"; | ||||
|  |   out += "<tr><td>/reboot</td><td>startet neu</td></tr>"; | ||||
|  |   out += "</table></body></html>"; | ||||
|  |   server.send (200, "text/html", out); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleReboot () { | ||||
|  |   //-------------------
 | ||||
|  |   returnOK (); | ||||
|  |   ESP.reset(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleRoot() { | ||||
|  |   //----------------
 | ||||
|  |   String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>"; | ||||
|  |   out += "<body>"; | ||||
|  |   out += "<h2>Hoymiles Micro-Inverter HM-600</h2>"; | ||||
|  |   out += "<br><br><table border='1'>"; | ||||
|  |   out += "<tr><th>Kanal</th><th>Wert</th></tr>"; | ||||
|  |   for (byte i = 0; i < ANZAHL_VALUES; i++) { | ||||
|  |     out += "<tr><td>" +  String(getChannelName(i)) + "</td>"; | ||||
|  |     out += "<td>" +  String(VALUES[i]) + "</td></tr>"; | ||||
|  |   } | ||||
|  |   out += "</table>"; | ||||
|  |   out += "</body></html>"; | ||||
|  |   server.send (200, "text/html", out); | ||||
|  |   //DEBUG_OUT.println (out);
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleData () { | ||||
|  | //-----------------
 | ||||
|  |   String out = ""; | ||||
|  |   for (int i = 0; i < ANZAHL_VALUES; i++) { | ||||
|  |     out += String(getChannelName(i)) + '=' + String (VALUES[i]) + '\n'; | ||||
|  |   } | ||||
|  |   server.send(200, "text/plain", out);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void handleNotFound() { | ||||
|  | //--------------------  
 | ||||
|  |   String message  = "URI: "; | ||||
|  |   message += server.uri(); | ||||
|  |   message += "\nMethod: "; | ||||
|  |   message += (server.method() == HTTP_GET) ? "GET" : "POST"; | ||||
|  |   message += "\nArguments: "; | ||||
|  |   message += server.args(); | ||||
|  |   message += "\n"; | ||||
|  |   for (uint8_t i = 0; i < server.args(); i++) { | ||||
|  |     message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; | ||||
|  |   } | ||||
|  |   server.send(404, "text/plain", message); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void setupWebServer (void) { | ||||
|  |   //-------------------------
 | ||||
|  |   server.on("/",        handleRoot); | ||||
|  |   server.on("/reboot",  handleReboot); | ||||
|  |   server.on("/data",    handleData); | ||||
|  |   server.on("/help",    handleHelp); | ||||
|  |   //server.onNotFound(handleNotFound);    wegen Spiffs-Dateimanager
 | ||||
|  | 
 | ||||
|  |   server.begin(); | ||||
|  |   DEBUG_OUT.println ("[HTTP] installed"); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void webserverHandle() { | ||||
|  | //====================
 | ||||
|  |   server.handleClient();   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // #################  OTA #################
 | ||||
|  | 
 | ||||
|  | #ifdef WITH_OTA | ||||
|  | #include <ESP8266HTTPUpdateServer.h> | ||||
|  | 
 | ||||
|  | ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT); | ||||
|  | ESP8266HTTPUpdateServer httpUpdater; | ||||
|  | 
 | ||||
|  | void setupUpdateByOTA () { | ||||
|  |   //------------------------
 | ||||
|  |   httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW); | ||||
|  |   httpUpdateServer.begin(); | ||||
|  |   DEBUG_OUT.println (F("[OTA] installed")); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void checkUpdateByOTA() { | ||||
|  | //---------------------
 | ||||
|  |   httpUpdateServer.handleClient();   | ||||
|  | } | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,597 @@ | |||||
|  | #include <Arduino.h> | ||||
|  | #include <SPI.h> | ||||
|  | #include "CircularBuffer.h" | ||||
|  | #include <RF24.h> | ||||
|  | #include <RF24_config.h> | ||||
|  | #include "hm_crc.h" | ||||
|  | #include "hm_packets.h" | ||||
|  | 
 | ||||
|  | #include "Settings.h"     // Header für Einstellungen | ||||
|  | 
 | ||||
|  | #include "Debug.h" | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   #define DISABLE_EINT noInterrupts() | ||||
|  |   #define ENABLE_EINT  interrupts() | ||||
|  | #else     // für AVR z.B. ProMini oder Nano
 | ||||
|  |   #define DISABLE_EINT EIMSK = 0x00 | ||||
|  |   #define ENABLE_EINT EIMSK = 0x01 | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #define RF_MAX_ADDR_WIDTH       (5)  | ||||
|  | #define MAX_RF_PAYLOAD_SIZE     (32) | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define PACKET_BUFFER_SIZE      (30)  | ||||
|  | #else | ||||
|  | #define PACKET_BUFFER_SIZE      (20)  | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | // Startup defaults until user reconfigures it
 | ||||
|  | #define DEFAULT_RECV_CHANNEL    (3)             // 3 = Default channel for Hoymiles
 | ||||
|  | //#define DEFAULT_SEND_CHANNEL  (75)            // 40 = Default channel for Hoymiles, 61
 | ||||
|  | #define DEFAULT_RF_DATARATE     (RF24_250KBPS)  // Datarate
 | ||||
|  | 
 | ||||
|  | #include "NRF24_sniff_types.h" | ||||
|  | 
 | ||||
|  | static HM_Packets     hmPackets; | ||||
|  | static uint32_t       tickMillis; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // Set up nRF24L01 radio on SPI bus plus CE/CS pins
 | ||||
|  | // If more than one RF24 unit is used the another CS pin than 10 must be used
 | ||||
|  | // This pin is used hard coded in SPI library
 | ||||
|  | static RF24 radio1 (RF1_CE_PIN, RF1_CS_PIN); | ||||
|  | 
 | ||||
|  | static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE]; | ||||
|  | 
 | ||||
|  | static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0])); | ||||
|  | 
 | ||||
|  | static Serial_header_t SerialHdr; | ||||
|  | 
 | ||||
|  | #define CHECKCRC  1 | ||||
|  | static uint16_t lastCRC; | ||||
|  | static uint16_t crc; | ||||
|  | 
 | ||||
|  | uint8_t         channels[]            = {/*3,*/ 23, 40, 61, 75};   //{1, 3, 6, 9, 11, 23, 40, 61, 75}
 | ||||
|  | uint8_t         channelIdx            = 1;                         // fange mit 40 an
 | ||||
|  | uint8_t         DEFAULT_SEND_CHANNEL  = channels[channelIdx];      // = 40
 | ||||
|  | 
 | ||||
|  | static unsigned long timeLastPacket = millis(); | ||||
|  | 
 | ||||
|  | // Function forward declaration
 | ||||
|  | static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len); | ||||
|  | char * getChannelName (uint8_t i); | ||||
|  | 
 | ||||
|  | static const int    ANZAHL_VALUES         = 16; | ||||
|  | static float        VALUES[ANZAHL_VALUES] = {}; | ||||
|  | static const char   *CHANNEL_NAMES[ANZAHL_VALUES]  | ||||
|  |    = {"P1.Udc", "P1.Idc", "P1.Pdc", "P2.Udc", "P2.Idc", "P2.Pdc",  | ||||
|  |       "E-Woche", "E-Total", "E1-Tag", "E2-Tag", "Uac", "Freq.ac", "Pac", "E-heute", "Ipv", "WR-Temp"}; | ||||
|  | static const uint8_t DIVISOR[ANZAHL_VALUES] = {10,100,10,10,100,10,1,1,1,1,10,100,10,0,0,10}; | ||||
|  | 
 | ||||
|  | static const char BLANK = ' '; | ||||
|  | 
 | ||||
|  | static boolean istTag = true; | ||||
|  | 
 | ||||
|  | char CHANNELNAME_BUFFER[15]; | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   #include "wifi.h" | ||||
|  |   #include "ModWebserver.h" | ||||
|  |   #include "Sonne.h" | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | char * getChannelName (uint8_t i) { | ||||
|  | //-------------------------------
 | ||||
|  |   memset (CHANNELNAME_BUFFER, 0, sizeof(CHANNELNAME_BUFFER)); | ||||
|  |   strcpy (CHANNELNAME_BUFFER, CHANNEL_NAMES[i]);  | ||||
|  |   //itoa (i, CHANNELNAME_BUFFER, 10);
 | ||||
|  |   return CHANNELNAME_BUFFER; | ||||
|  | } | ||||
|  | 
 | ||||
|  | inline static void dumpData(uint8_t *p, int len) { | ||||
|  | //-----------------------------------------------
 | ||||
|  |   while (len--){ | ||||
|  |     if (*p < 16) | ||||
|  |       DEBUG_OUT.print(F("0")); | ||||
|  |     DEBUG_OUT.print(*p++, HEX); | ||||
|  |   } | ||||
|  |   DEBUG_OUT.print(BLANK); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float extractValue2 (uint8_t *p, int divisor) { | ||||
|  | //-------------------------------------------
 | ||||
|  |   uint16_t b1 = *p++; | ||||
|  |   return ((float) (b1 << 8) + *p) / (float) divisor; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | float extractValue4 (uint8_t *p, int divisor) { | ||||
|  | //-------------------------------------------
 | ||||
|  |   uint32_t ret  = *p++; | ||||
|  |   for (uint8_t i = 1; i <= 3; i++) | ||||
|  |     ret = (ret << 8) + *p++; | ||||
|  |   return (ret / divisor); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void outChannel (uint8_t i) { | ||||
|  | //-------------------------
 | ||||
|  |   DEBUG_OUT.print(getChannelName(i)); DEBUG_OUT.print(F("\t:")); DEBUG_OUT.print(VALUES[i]); DEBUG_OUT.println(BLANK);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void analyse01 (uint8_t *p) {    // p zeigt auf 01 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   //DEBUG_OUT.print (F("analyse 01: "));
 | ||||
|  |   p += 3; | ||||
|  |   // PV1.U   PV1.I   PV1.P     PV2.U   PV2.I   PV2.P
 | ||||
|  |   // [0.1V]  [0.01A] [.1W]     [0.1V]  [0.01A] [.1W]
 | ||||
|  |   for (int i = 0; i < 6; i++) { | ||||
|  |     VALUES[i] = extractValue2 (p,DIVISOR[i]);   p += 2; | ||||
|  |     outChannel(i); | ||||
|  |   } | ||||
|  | /* 
 | ||||
|  |   DEBUG_OUT.print(F("PV1.U:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" PV1.I:")); DEBUG_OUT.print(extractValue2(p,100)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" PV1.Pac:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" PV2.U:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" PV2.I:")); DEBUG_OUT.print(extractValue2(p,100)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" PV2.Pac:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  | */ | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void analyse02 (uint8_t *p) {    // p zeigt auf 02 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   //DEBUG_OUT.print (F("analyse 02: "));
 | ||||
|  |   // +11 = Spannung, +13 = Frequenz, +15 = Leistung
 | ||||
|  |   //p += 11;
 | ||||
|  |   p++; | ||||
|  |   for (int i = 6; i < 13; i++) { | ||||
|  |     if (i == 7) { | ||||
|  |        VALUES[i] = extractValue4 (p,DIVISOR[i]);    | ||||
|  |        p += 4;     | ||||
|  |     }  | ||||
|  |     else { | ||||
|  |       VALUES[i] = extractValue2 (p,DIVISOR[i]);    | ||||
|  |       p += 2; | ||||
|  |     } | ||||
|  |     outChannel(i); | ||||
|  |   } | ||||
|  |   VALUES[13] = VALUES[8] + VALUES[9];     // E-heute = P1+P2
 | ||||
|  |   if (VALUES[10] > 0) | ||||
|  |     VALUES[14] = VALUES[12] / VALUES[10];   // Ipv = Pac / Spannung
 | ||||
|  | /*
 | ||||
|  |   DEBUG_OUT.print(F("P Woche:")); DEBUG_OUT.print(extractValue2(p,1)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" P Total:")); DEBUG_OUT.print(extractValue4(p,1)); | ||||
|  |   p += 4; | ||||
|  |   DEBUG_OUT.print(F(" P1 Tag:")); DEBUG_OUT.print(extractValue2(p,1)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" P2 Tag:")); DEBUG_OUT.print(extractValue2(p,1)); | ||||
|  |   p += 2; | ||||
|  |    | ||||
|  |   DEBUG_OUT.print(F(" Spannung:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" Freq.:")); DEBUG_OUT.print(extractValue2(p,100)); | ||||
|  |   p += 2; | ||||
|  |   DEBUG_OUT.print(F(" Leist.:")); DEBUG_OUT.print(extractValue2(p,10)); | ||||
|  | */ | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void analyse83 (uint8_t *p) {    // p zeigt auf 83 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   //DEBUG_OUT.print (F("++++++analyse 83:"));
 | ||||
|  |   p += 7; | ||||
|  |   VALUES[15] = extractValue2 (p,DIVISOR[15]);   | ||||
|  |   outChannel(15); | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void analyseWords (uint8_t *p) {    // p zeigt auf 01 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   DEBUG_OUT.print (F("analyse words:")); | ||||
|  |   p++; | ||||
|  |   for (int i = 0; i <12;i++) { | ||||
|  |     DEBUG_OUT.print(extractValue2(p,1)); | ||||
|  |     DEBUG_OUT.print(BLANK); | ||||
|  |     p++; | ||||
|  |   } | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void analyseLongs (uint8_t *p) {    // p zeigt auf 01 hinter 2. WR-Adr
 | ||||
|  | //----------------------------------
 | ||||
|  |   //uint16_t val;
 | ||||
|  |   DEBUG_OUT.print (F("analyse words:")); | ||||
|  |   p++; | ||||
|  |   for (int i = 0; i <12;i++) { | ||||
|  |     DEBUG_OUT.print(extractValue4(p,1)); | ||||
|  |     DEBUG_OUT.print(BLANK); | ||||
|  |     p++; | ||||
|  |   } | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | IRAM_ATTR | ||||
|  | #endif | ||||
|  | void handleNrf1Irq() { | ||||
|  | //-------------------------
 | ||||
|  |   static uint8_t lostPacketCount = 0; | ||||
|  |   uint8_t pipe; | ||||
|  |    | ||||
|  |   DISABLE_EINT; | ||||
|  |    | ||||
|  |   // Loop until RX buffer(s) contain no more packets.
 | ||||
|  |   while (radio1.available(&pipe)) { | ||||
|  |     if (!packetBuffer.full()) { | ||||
|  |       NRF24_packet_t *p = packetBuffer.getFront(); | ||||
|  |       p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
 | ||||
|  |       p->packetsLost = lostPacketCount; | ||||
|  |       uint8_t packetLen = radio1.getPayloadSize(); | ||||
|  |       if (packetLen > MAX_RF_PAYLOAD_SIZE) | ||||
|  |         packetLen = MAX_RF_PAYLOAD_SIZE; | ||||
|  | 
 | ||||
|  |       radio1.read(p->packet, packetLen); | ||||
|  |       packetBuffer.pushFront(p); | ||||
|  |       lostPacketCount = 0; | ||||
|  |     } | ||||
|  |     else { | ||||
|  |       // Buffer full. Increase lost packet counter.
 | ||||
|  |       bool tx_ok, tx_fail, rx_ready; | ||||
|  |       if (lostPacketCount < 255) | ||||
|  |         lostPacketCount++; | ||||
|  |       // Call 'whatHappened' to reset interrupt status.
 | ||||
|  |       radio1.whatHappened(tx_ok, tx_fail, rx_ready); | ||||
|  |       // Flush buffer to drop the packet.
 | ||||
|  |       radio1.flush_rx(); | ||||
|  |     } | ||||
|  |   } | ||||
|  |   ENABLE_EINT; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static void activateConf(void) { | ||||
|  | //-----------------------------
 | ||||
|  |   radio1.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |   radio1.setDataRate(DEFAULT_RF_DATARATE); | ||||
|  |   radio1.disableCRC(); | ||||
|  |   radio1.setAutoAck(0x00); | ||||
|  |   radio1.setPayloadSize(MAX_RF_PAYLOAD_SIZE); | ||||
|  |   radio1.setAddressWidth(5); | ||||
|  |   radio1.openReadingPipe(1, DTU_RADIO_ID); | ||||
|  | 
 | ||||
|  |   // We want only RX irqs
 | ||||
|  |   radio1.maskIRQ(true, true, false); | ||||
|  | 
 | ||||
|  |   // Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
 | ||||
|  |   radio1.setPALevel(RF24_PA_MAX); | ||||
|  |   radio1.startListening(); | ||||
|  | 
 | ||||
|  |   // Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
 | ||||
|  |   attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
 | ||||
|  | 
 | ||||
|  |   // Initialize SerialHdr header's address member to promiscuous address.
 | ||||
|  |   uint64_t addr = DTU_RADIO_ID; | ||||
|  |   for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) { | ||||
|  |     SerialHdr.address[i] = addr; | ||||
|  |     addr >>= 8; | ||||
|  |   } | ||||
|  | 
 | ||||
|  | #ifndef ESP8266 | ||||
|  |   DEBUG_OUT.println(F("\nRadio Config:")); | ||||
|  |   radio1.printPrettyDetails(); | ||||
|  |   DEBUG_OUT.println(); | ||||
|  | #endif | ||||
|  |   tickMillis = millis() + 200; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void setup(void) { | ||||
|  | //--------------
 | ||||
|  |   //Serial.begin(SER_BAUDRATE);
 | ||||
|  |   DEBUG_OUT.begin(SER_BAUDRATE); | ||||
|  |   DEBUG_OUT.flush(); | ||||
|  | 
 | ||||
|  |   DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --")); | ||||
|  | 
 | ||||
|  |   radio1.begin(); | ||||
|  | 
 | ||||
|  |   // Disable shockburst for receiving and decode payload manually
 | ||||
|  |   radio1.setAutoAck(false); | ||||
|  |   radio1.setRetries(0, 0); | ||||
|  | 
 | ||||
|  |   // Configure nRF IRQ input
 | ||||
|  |   pinMode(RF1_IRQ_PIN, INPUT); | ||||
|  | 
 | ||||
|  |   activateConf(); | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  |   setupWifi(); | ||||
|  |   setupClock(); | ||||
|  |   setupWebServer(); | ||||
|  |   setupUpdateByOTA(); | ||||
|  |   calcSunUpDown (getNow()); | ||||
|  |   istTag = isDayTime(); | ||||
|  |   DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht"); | ||||
|  |   hmPackets.SetUnixTimeStamp (getNow()); | ||||
|  | #else | ||||
|  |   hmPackets.SetUnixTimeStamp(0x62456430); | ||||
|  | #endif | ||||
|  | } | ||||
|  | 
 | ||||
|  |  uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | 
 | ||||
|  | void isTime2Send () { | ||||
|  | //-----------------
 | ||||
|  |   // Second timer
 | ||||
|  | 
 | ||||
|  |   if (millis() >= tickMillis) { | ||||
|  |     static uint8_t tel = 0; | ||||
|  |     tickMillis += 1000;    //200;
 | ||||
|  |     //tickSec++; 
 | ||||
|  |     hmPackets.UnixTimeStampTick(); | ||||
|  | /*    if (++tickSec >= 5) {   // 5
 | ||||
|  |       hmPackets.UnixTimeStampTick(); | ||||
|  |       tickSec = 0; | ||||
|  |     } */ | ||||
|  | 
 | ||||
|  |     int32_t size = 0; | ||||
|  |     uint64_t dest = WR1_RADIO_ID; | ||||
|  | 
 | ||||
|  |     if (tel > 5) | ||||
|  |       tel = 0; | ||||
|  |      | ||||
|  |     if (tel == 0) { | ||||
|  |       #ifdef ESP8266 | ||||
|  |       hmPackets.SetUnixTimeStamp (getNow()); | ||||
|  |       #endif | ||||
|  |       size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8); | ||||
|  |     } | ||||
|  |     else if (tel == 1) | ||||
|  |       size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x81); | ||||
|  |     else if (tel == 2) | ||||
|  |       size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80); | ||||
|  |     else if (tel == 3) { | ||||
|  |       size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x83); | ||||
|  |       //tel = 0;
 | ||||
|  |     } | ||||
|  |     else if (tel == 4) | ||||
|  |       size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x82); | ||||
|  |     else if (tel == 5) | ||||
|  |       size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x84); | ||||
|  | 
 | ||||
|  |     SendPacket(dest, (uint8_t *)&sendBuf, size); | ||||
|  | 
 | ||||
|  |     tel++; | ||||
|  |      | ||||
|  | /*    for (uint8_t warte = 0; warte < 2; warte++) {
 | ||||
|  |       delay(1000); | ||||
|  |       hmPackets.UnixTimeStampTick(); | ||||
|  |     }*/  | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) { | ||||
|  | //-----------------------------------------------------
 | ||||
|  | 
 | ||||
|  |     // Write timestamp, packets lost, address and payload length
 | ||||
|  |     //printf(" %09lu ", SerialHdr.timestamp);
 | ||||
|  |     dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost)); | ||||
|  |     dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address)); | ||||
|  | 
 | ||||
|  |     // Trailing bit?!?
 | ||||
|  |     dumpData(&p->packet[0], 2); | ||||
|  | 
 | ||||
|  |     // Payload length from PCF
 | ||||
|  |     dumpData(&payloadLen, sizeof(payloadLen)); | ||||
|  | 
 | ||||
|  |     // Packet control field - PID Packet identification
 | ||||
|  |     uint8_t val = (p->packet[1] >> 1) & 0x03; | ||||
|  |     DEBUG_OUT.print(val); | ||||
|  |     DEBUG_OUT.print(F("  ")); | ||||
|  | 
 | ||||
|  |     if (payloadLen > 9) { | ||||
|  |       dumpData(&p->packet[2], 1); | ||||
|  |       dumpData(&p->packet[3], 4); | ||||
|  |       dumpData(&p->packet[7], 4); | ||||
|  |        | ||||
|  |       uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4; | ||||
|  | 
 | ||||
|  |       if (remain < 32) { | ||||
|  |         dumpData(&p->packet[11], remain); | ||||
|  |         printf_P(PSTR("%04X "), crc); | ||||
|  | 
 | ||||
|  |         if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) | ||||
|  |           DEBUG_OUT.print(0); | ||||
|  |         else | ||||
|  |           DEBUG_OUT.print(1); | ||||
|  |       } | ||||
|  |       else { | ||||
|  |         DEBUG_OUT.print(F("Ill remain ")); | ||||
|  |         DEBUG_OUT.print(remain); | ||||
|  |       } | ||||
|  |     } | ||||
|  |     else { | ||||
|  |       dumpData(&p->packet[2], payloadLen + 2); | ||||
|  |       printf_P(PSTR("%04X "), crc); | ||||
|  |     } | ||||
|  |     DEBUG_OUT.println(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void loop(void) { | ||||
|  | //=============
 | ||||
|  |   while (!packetBuffer.empty()) { | ||||
|  |     timeLastPacket = millis(); | ||||
|  |     // One or more records present
 | ||||
|  |     NRF24_packet_t *p = packetBuffer.getBack(); | ||||
|  | 
 | ||||
|  |     // Shift payload data due to 9-bit packet control field
 | ||||
|  |     for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) { | ||||
|  |       if (j > 0) | ||||
|  |         p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1); | ||||
|  |       else | ||||
|  |         p->packet[j] = (byte)(p->packet[j] >> 7); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     SerialHdr.timestamp   = p->timestamp; | ||||
|  |     SerialHdr.packetsLost = p->packetsLost; | ||||
|  | 
 | ||||
|  |     // Check CRC
 | ||||
|  |     crc = 0xFFFF; | ||||
|  |     crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address))); | ||||
|  |     // Payload length
 | ||||
|  |     uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3); | ||||
|  |     // Add one byte and one bit for 9-bit packet control field
 | ||||
|  |     crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1); | ||||
|  | 
 | ||||
|  |     if (CHECKCRC) { | ||||
|  |       // If CRC is invalid only show lost packets
 | ||||
|  |       if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) { | ||||
|  |         if (p->packetsLost > 0) { | ||||
|  |           DEBUG_OUT.print(F(" Lost: ")); | ||||
|  |           DEBUG_OUT.println(p->packetsLost); | ||||
|  |         } | ||||
|  |         packetBuffer.popBack(); | ||||
|  |         continue; | ||||
|  |       } | ||||
|  | 
 | ||||
|  |       // Dump a decoded packet only once
 | ||||
|  |       if (lastCRC == crc) { | ||||
|  |         packetBuffer.popBack(); | ||||
|  |         continue; | ||||
|  |       } | ||||
|  |       lastCRC = crc; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     // Don't dump mysterious ack packages
 | ||||
|  |     if (payloadLen == 0) { | ||||
|  |         packetBuffer.popBack(); | ||||
|  |         continue; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     #ifdef DEBUG | ||||
|  |     outputPacket (p, payloadLen); | ||||
|  |     #endif | ||||
|  |      | ||||
|  |     uint8_t cmd = p->packet[11]; | ||||
|  |     if (cmd == 0x02) | ||||
|  |       analyse02 (&p->packet[11]); | ||||
|  |     else if (cmd == 0x01)      | ||||
|  |       analyse01 (&p->packet[11]);  | ||||
|  |     //if (p->packet[11] == 0x83 || p->packet[11] == 0x82) analyse83 (&p->packet[11], payloadLen);
 | ||||
|  |     else if (cmd == 0x03) { | ||||
|  |       analyseWords (&p->packet[11]); | ||||
|  |       analyseLongs (&p->packet[11]); | ||||
|  |     } | ||||
|  |     else if (cmd == 0x81)   // ???
 | ||||
|  |       ; | ||||
|  |     else if (cmd == 0x83)  | ||||
|  |       analyse83 (&p->packet[11]);  | ||||
|  |     else { | ||||
|  |       DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX); | ||||
|  |       analyseWords (&p->packet[11]); | ||||
|  |       analyseLongs (&p->packet[11]); | ||||
|  |     } | ||||
|  |     if (p->packetsLost > 0) { | ||||
|  |       DEBUG_OUT.print(F(" Lost: ")); | ||||
|  |       DEBUG_OUT.print(p->packetsLost); | ||||
|  |     } | ||||
|  |     DEBUG_OUT.println(); | ||||
|  | 
 | ||||
|  |     #ifndef ESP8266 | ||||
|  |     for (uint8_t i = 0; i < ANZAHL_VALUES; i++) { | ||||
|  |       //outChannel(i);
 | ||||
|  |       Serial.print(getChannelName(i)); Serial.print(':'); Serial.print(VALUES[i]); Serial.println(BLANK);  // Schnittstelle bei Arduino
 | ||||
|  |     } | ||||
|  |     DEBUG_OUT.println(); | ||||
|  |     #endif | ||||
|  |      | ||||
|  |     // Remove record as we're done with it.
 | ||||
|  |     packetBuffer.popBack(); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   if (istTag)  | ||||
|  |     isTime2Send(); | ||||
|  | 
 | ||||
|  |   #ifdef ESP8266 | ||||
|  |   checkWifi(); | ||||
|  |   webserverHandle(); | ||||
|  |   checkUpdateByOTA(); | ||||
|  |   if (hour() == 0 && minute() == 0) { | ||||
|  |     calcSunUpDown(getNow());   | ||||
|  |   } | ||||
|  |   if (minute() % 15 == 0 && second () == 0) {  // alle 15 Minuten neu berechnen ob noch hell
 | ||||
|  |     istTag = isDayTime(); | ||||
|  |     DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht"); | ||||
|  |   } | ||||
|  |   #endif | ||||
|  | /*
 | ||||
|  |   if (millis() > timeLastPacket + 60UL*SECOND) {  // 60 Sekunden
 | ||||
|  |     channelIdx++; | ||||
|  |     if (channelIdx >= sizeof(channels)) channelIdx = 0; | ||||
|  |     DEFAULT_SEND_CHANNEL = channels[channelIdx]; | ||||
|  |     DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL); | ||||
|  |     timeLastPacket = millis(); | ||||
|  |   } | ||||
|  | */ | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) { | ||||
|  | //--------------------------------------------------------------
 | ||||
|  |   DISABLE_EINT; | ||||
|  |   radio1.stopListening(); | ||||
|  | 
 | ||||
|  | #ifdef CHANNEL_HOP | ||||
|  |   static uint8_t hop = 0; | ||||
|  |   #if DEBUG_SEND     | ||||
|  |   DEBUG_OUT.print(F("Send... CH")); | ||||
|  |   DEBUG_OUT.println(channels[hop]); | ||||
|  |   #endif   | ||||
|  |   radio1.setChannel(channels[hop++]); | ||||
|  |   if (hop >= sizeof(channels) / sizeof(channels[0])) | ||||
|  |     hop = 0; | ||||
|  | #else | ||||
|  |   radio1.setChannel(DEFAULT_SEND_CHANNEL); | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |   radio1.openWritingPipe(dest); | ||||
|  |   radio1.setCRCLength(RF24_CRC_16); | ||||
|  |   radio1.enableDynamicPayloads(); | ||||
|  |   radio1.setAutoAck(true); | ||||
|  |   radio1.setRetries(3, 15); | ||||
|  | 
 | ||||
|  |   radio1.write(buf, len); | ||||
|  | 
 | ||||
|  |   // Try to avoid zero payload acks (has no effect)
 | ||||
|  |   radio1.openWritingPipe(DUMMY_RADIO_ID); | ||||
|  | 
 | ||||
|  |   radio1.setAutoAck(false); | ||||
|  |   radio1.setRetries(0, 0); | ||||
|  |   radio1.disableDynamicPayloads(); | ||||
|  |   radio1.setCRCLength(RF24_CRC_DISABLED); | ||||
|  | 
 | ||||
|  |   radio1.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |   radio1.startListening(); | ||||
|  |   ENABLE_EINT; | ||||
|  | } | ||||
| @ -0,0 +1,55 @@ | |||||
|  | /*
 | ||||
|  |   This file is part of NRF24_Sniff. | ||||
|  | 
 | ||||
|  |   Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl | ||||
|  |      | ||||
|  |   NRF24_Sniff is free software: you can redistribute it and/or modify | ||||
|  |   it under the terms of the GNU General Public License as published by | ||||
|  |   the Free Software Foundation, either version 3 of the License, or | ||||
|  |   (at your option) any later version. | ||||
|  | 
 | ||||
|  |   NRF24_Sniff is distributed in the hope that it will be useful, | ||||
|  |   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  |   GNU General Public License for more details. | ||||
|  | 
 | ||||
|  |   You should have received a copy of the GNU General Public License | ||||
|  |   along with NRF24_Sniff.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  | */ | ||||
|  | 
 | ||||
|  | #ifndef NRF24_sniff_types_h | ||||
|  | #define NRF24_sniff_types_h | ||||
|  | 
 | ||||
|  | typedef struct _NRF24_packet_t | ||||
|  | { | ||||
|  |   uint32_t timestamp; | ||||
|  |   uint8_t  packetsLost; | ||||
|  |   uint8_t  packet[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | } NRF24_packet_t; | ||||
|  | 
 | ||||
|  | typedef struct _Serial_header_t | ||||
|  | { | ||||
|  |   unsigned long timestamp; | ||||
|  |   uint8_t  packetsLost; | ||||
|  |   uint8_t  address[RF_MAX_ADDR_WIDTH];    // MSB first, always RF_MAX_ADDR_WIDTH bytes.
 | ||||
|  | } Serial_header_t; | ||||
|  | 
 | ||||
|  | typedef struct _Serial_config_t | ||||
|  | { | ||||
|  |   uint8_t channel; | ||||
|  |   uint8_t rate;                        // rf24_datarate_e: 0 = 1Mb/s, 1 = 2Mb/s, 2 = 250Kb/s
 | ||||
|  |   uint8_t addressLen;                  // Number of bytes used in address, range [2..5]
 | ||||
|  |   uint8_t addressPromiscLen;           // Number of bytes used in promiscuous address, range [2..5]. E.g. addressLen=5, addressPromiscLen=4 => 1 byte unique identifier.
 | ||||
|  |   uint64_t address;                    // Base address, LSB first.
 | ||||
|  |   uint8_t crcLength;                   // Length of active CRC, range [0..2]
 | ||||
|  |   uint8_t maxPayloadSize;              // Maximum size of payload for nRF (including nRF header), range[4?..32]
 | ||||
|  | } Serial_config_t; | ||||
|  | 
 | ||||
|  | #define MSG_TYPE_PACKET  (0) | ||||
|  | #define MSG_TYPE_CONFIG  (1) | ||||
|  | 
 | ||||
|  | #define SET_MSG_TYPE(var,type)   (((var) & 0x3F) | ((type) << 6)) | ||||
|  | #define GET_MSG_TYPE(var)        ((var) >> 6) | ||||
|  | #define GET_MSG_LEN(var)         ((var) & 0x3F) | ||||
|  | 
 | ||||
|  | #endif // NRF24_sniff_types_h
 | ||||
| @ -0,0 +1,82 @@ | |||||
|  | #ifndef __SETTINGS_H | ||||
|  | #define __SETTINGS_H | ||||
|  | 
 | ||||
|  | // Ausgabe von Debug Infos auf der seriellen Console
 | ||||
|  | #define DEBUG | ||||
|  | #define SER_BAUDRATE            (115200) | ||||
|  | 
 | ||||
|  | // Ausgabe was gesendet wird; 0 oder 1 
 | ||||
|  | #define DEBUG_SEND  0    | ||||
|  | 
 | ||||
|  | // soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
 | ||||
|  | #define CHANNEL_HOP | ||||
|  | 
 | ||||
|  | // mit OTA Support, also update der Firmware über WLan mittels IP/update
 | ||||
|  | #define WITH_OTA | ||||
|  | 
 | ||||
|  | // Hardware configuration
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define RF1_CE_PIN  (D4) | ||||
|  | #define RF1_CS_PIN  (D8) | ||||
|  | #define RF1_IRQ_PIN (D3) | ||||
|  | #else | ||||
|  | #define RF1_CE_PIN  (9) | ||||
|  | #define RF1_CS_PIN  (10) | ||||
|  | #define RF1_IRQ_PIN (2) | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | union longlongasbytes { | ||||
|  |   uint64_t ull; | ||||
|  |   uint8_t bytes[8];   | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | uint64_t Serial2RadioID (uint64_t sn) {    | ||||
|  | //----------------------------------
 | ||||
|  |   longlongasbytes llsn; | ||||
|  |   longlongasbytes res; | ||||
|  |   llsn.ull = sn; | ||||
|  |   res.ull = 0; | ||||
|  |   res.bytes[4] = llsn.bytes[0]; | ||||
|  |   res.bytes[3] = llsn.bytes[1]; | ||||
|  |   res.bytes[2] = llsn.bytes[2]; | ||||
|  |   res.bytes[1] = llsn.bytes[3]; | ||||
|  |   res.bytes[0] = 0x01; | ||||
|  |   return res.ull; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // WR und DTU
 | ||||
|  | #define DUMMY_RADIO_ID          ((uint64_t)0xDEADBEEF01ULL)  | ||||
|  | #define SerialWR                0x114172607952ULL				// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | uint64_t WR1_RADIO_ID           = Serial2RadioID (SerialWR);    // ((uint64_t)0x5279607201ULL);          
 | ||||
|  | #define DTU_RADIO_ID            ((uint64_t)0x1234567801ULL) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // Webserver
 | ||||
|  | #define WEBSERVER_PORT      80 | ||||
|  | 
 | ||||
|  | // Time Server
 | ||||
|  | //#define TIMESERVER_NAME "pool.ntp.org"
 | ||||
|  | #define TIMESERVER_NAME "fritz.box" | ||||
|  | 
 | ||||
|  | #ifdef WITH_OTA | ||||
|  | // OTA Einstellungen
 | ||||
|  | #define UPDATESERVER_PORT   WEBSERVER_PORT+1			 | ||||
|  | #define UPDATESERVER_DIR    "/update"							// mittels IP:81/update kommt man dann auf die OTA-Seite
 | ||||
|  | #define UPDATESERVER_USER   "username_für_OTA"					// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define UPDATESERVER_PW     "passwort_für_OTA"					// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | // internes WLan
 | ||||
|  | // PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) vonfremden zu unterscheiden
 | ||||
|  | // gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
 | ||||
|  | #define SSID_PREFIX1         "wlan1-Prefix"						// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define SSID_PREFIX2         "wlan2-Prefix"						// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | #define SSID_PASSWORD        "wlan-passwort"					// <<<<<<<<<<<<<<<<<<<<<<< anpassen
 | ||||
|  | 
 | ||||
|  | // zur Berechnung von Sonnenauf- und -untergang
 | ||||
|  | #define  geoBreite  49.2866 | ||||
|  | #define  geoLaenge  7.3416 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,55 @@ | |||||
|  | #ifndef __SONNE_H | ||||
|  | #define __SONNE_H | ||||
|  | 
 | ||||
|  | #include "Settings.h" | ||||
|  | #include "Debug.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | long SunDown, SunUp; | ||||
|  | 
 | ||||
|  | void calcSunUpDown (time_t date) { | ||||
|  |     //SunUpDown res = new SunUpDown();
 | ||||
|  |     boolean isSummerTime = false;   // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
 | ||||
|  |      | ||||
|  |     //- Bogenma�
 | ||||
|  |     double brad = geoBreite / 180.0 * PI; | ||||
|  |     // - H�he Sonne -50 Bogenmin.
 | ||||
|  |     double h0 = -50.0 / 60.0 / 180.0 * PI; | ||||
|  |     //- Deklination dek, Tag des Jahres d0
 | ||||
|  |     int tage = 30 * month(date) - 30 + day(date);  | ||||
|  |     double dek = 0.40954 * sin (0.0172 * (tage - 79.35)); | ||||
|  |     double zh1 = sin (h0) - sin (brad)  *  sin(dek); | ||||
|  |     double zh2 = cos(brad) * cos(dek); | ||||
|  |     double zd = 12*acos (zh1/zh2) / PI; | ||||
|  |     double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939); | ||||
|  |     //-Sonnenuntergang
 | ||||
|  |     double tsu = 12 + zd - zgl; | ||||
|  |     double su = (tsu + (15.0 - geoLaenge) / 15.0);  | ||||
|  |     int std = (int)su; | ||||
|  |     int minute = (int) ((su - std)*60); | ||||
|  |     if (isSummerTime) std++; | ||||
|  |     SunDown = (100*std + minute) * 100; | ||||
|  |      | ||||
|  |     //- Sonnenaufgang
 | ||||
|  |     double tsa = 12 - zd - zgl; | ||||
|  |     double sa = (tsa + (15.0 - geoLaenge) /15.0);  | ||||
|  |     std = (int) sa; | ||||
|  |     minute = (int) ((sa - std)*60); | ||||
|  |     if (isSummerTime) std++; | ||||
|  |     SunUp = (100*std + minute) * 100; | ||||
|  |     DEBUG_OUT.print("Sonnenaufgang  :"); DEBUG_OUT.println(SunUp); | ||||
|  |     DEBUG_OUT.print("Sonnenuntergang:"); DEBUG_OUT.println(SunDown); | ||||
|  | } | ||||
|  | 
 | ||||
|  | boolean isDayTime() { | ||||
|  | //-----------------
 | ||||
|  | // 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
 | ||||
|  |   const int offset=60*15; | ||||
|  |   time_t no = getNow(); | ||||
|  |   long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100; | ||||
|  |   long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100; | ||||
|  | 
 | ||||
|  |   return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown));   | ||||
|  | } | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,142 @@ | |||||
|  | 
 | ||||
|  | #include <stdio.h> | ||||
|  | #include <stdint.h> | ||||
|  | #include "hm_crc.h" | ||||
|  | //#define OUTPUT_DEBUG_INFO
 | ||||
|  | 
 | ||||
|  | /* Table of CRC values for high-order byte */ | ||||
|  | static const uint8_t auchCRCHi[] = { | ||||
|  | 	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, | ||||
|  | 	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, | ||||
|  | 	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, | ||||
|  | 	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, | ||||
|  | 	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, | ||||
|  | 	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, | ||||
|  | 	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, | ||||
|  | 	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, | ||||
|  | 	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, | ||||
|  | 	0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, | ||||
|  | 	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, | ||||
|  | 	0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, | ||||
|  | 	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, | ||||
|  | 	0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, | ||||
|  | 	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, | ||||
|  | 	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, | ||||
|  | 	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, | ||||
|  | 	0x40}; | ||||
|  | 
 | ||||
|  | /* Table of CRC values for low-order byte */ | ||||
|  | static const uint8_t auchCRCLo[] = { | ||||
|  | 	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, | ||||
|  | 	0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, | ||||
|  | 	0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, | ||||
|  | 	0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, | ||||
|  | 	0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, | ||||
|  | 	0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, | ||||
|  | 	0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, | ||||
|  | 	0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, | ||||
|  | 	0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, | ||||
|  | 	0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, | ||||
|  | 	0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, | ||||
|  | 	0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, | ||||
|  | 	0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, | ||||
|  | 	0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, | ||||
|  | 	0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, | ||||
|  | 	0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, | ||||
|  | 	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, | ||||
|  | 	0x40}; | ||||
|  | 
 | ||||
|  | uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen) | ||||
|  | { | ||||
|  | 	uint8_t uchCRCHi = 0xFF; /* high byte of CRC initialized */ | ||||
|  | 	uint8_t uchCRCLo = 0xFF; /* low byte of CRC initialized */ | ||||
|  | 	uint16_t uIndex;		 /* will index into CRC lookup table */ | ||||
|  | 	while (usDataLen--)		 /* pass through message buffer */ | ||||
|  | 	{ | ||||
|  | 		uIndex = uchCRCLo ^ *puchMsg++; /* calculate the CRC */ | ||||
|  | 		uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex]; | ||||
|  | 		uchCRCHi = auchCRCLo[uIndex]; | ||||
|  | 	} | ||||
|  | 	return (uchCRCHi << 8 | uchCRCLo); | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Hoymiles CRC8 calculation with poly 0x01, Initial value 0x00 and final XOR 0x00
 | ||||
|  | uint8_t crc8(uint8_t *buf, const uint16_t bufLen) | ||||
|  | { | ||||
|  | 	uint32_t crc; | ||||
|  | 	uint16_t i, bit; | ||||
|  | 
 | ||||
|  | 	crc = 0x00; | ||||
|  | 	for (i = 0; i < bufLen; i++) | ||||
|  | 	{ | ||||
|  | 		crc ^= buf[i]; | ||||
|  | 		for (bit = 0; bit < 8; bit++) | ||||
|  | 		{ | ||||
|  | 			if ((crc & 0x80) != 0) | ||||
|  | 			{ | ||||
|  | 				crc <<= 1; | ||||
|  | 				crc ^= 0x01; | ||||
|  | 			} | ||||
|  | 			else | ||||
|  | 			{ | ||||
|  | 				crc <<= 1; | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	return (crc & 0xFF); | ||||
|  | } | ||||
|  | 
 | ||||
|  | // NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
 | ||||
|  | uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits) | ||||
|  | { | ||||
|  | 	uint16_t crc = startCRC; | ||||
|  | 	if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen))) | ||||
|  | 	{ | ||||
|  | 		// The length of the data might not be a multiple of full bytes.
 | ||||
|  | 		// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
 | ||||
|  | 		// calculate the CRC.
 | ||||
|  | 		uint16_t data; | ||||
|  | 		uint8_t byte, shift; | ||||
|  | 		uint16_t bitoffs = startBit; | ||||
|  | 
 | ||||
|  | 		// Get a new byte for the next 8 bits.
 | ||||
|  | 		byte = buf[bitoffs >> 3]; | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 		printf("\nStart CRC %04X, %u bits:", startCRC, len_bits); | ||||
|  | 		printf("\nbyte %02X:", byte); | ||||
|  | #endif | ||||
|  | 		while (bitoffs < len_bits + startBit) | ||||
|  | 		{ | ||||
|  | 			shift = bitoffs & 7; | ||||
|  | 			// Shift the active bit to the position of bit 15
 | ||||
|  | 			data = ((uint16_t)byte) << (8 + shift); | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 			printf(" bit %u %u,", shift, data & 0x8000 ? 1 : 0); | ||||
|  | #endif | ||||
|  | 			// Assure all other bits are 0
 | ||||
|  | 			data &= 0x8000; | ||||
|  | 			crc ^= data; | ||||
|  | 			if (crc & 0x8000) | ||||
|  | 			{ | ||||
|  | 				crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
 | ||||
|  | 			} | ||||
|  | 			else | ||||
|  | 			{ | ||||
|  | 				crc = (crc << 1); | ||||
|  | 			} | ||||
|  | 			++bitoffs; | ||||
|  | 			if (0 == (bitoffs & 7)) | ||||
|  | 			{ | ||||
|  | 				// Get a new byte for the next 8 bits.
 | ||||
|  | 				byte = buf[bitoffs >> 3]; | ||||
|  | #ifdef OUTPUT_DEBUG_INFO | ||||
|  | 				printf("crc %04X:", crc); | ||||
|  | 				if (bitoffs < len_bits + startBit) | ||||
|  | 					printf("\nbyte %02X:", byte); | ||||
|  | #endif | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 	return crc; | ||||
|  | } | ||||
| @ -0,0 +1,8 @@ | |||||
|  | 
 | ||||
|  | 
 | ||||
|  | #define BITS_TO_BYTES(x)  (((x)+7)>>3) | ||||
|  | #define BYTES_TO_BITS(x)  ((x)<<3) | ||||
|  | 
 | ||||
|  | extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen); | ||||
|  | extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen); | ||||
|  | extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits); | ||||
| @ -0,0 +1,74 @@ | |||||
|  | #include "Arduino.h" | ||||
|  | 
 | ||||
|  | #include "hm_crc.h" | ||||
|  | #include "hm_packets.h" | ||||
|  | 
 | ||||
|  | void HM_Packets::SetUnixTimeStamp(uint32_t ts) | ||||
|  | { | ||||
|  | 	unixTimeStamp = ts; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::UnixTimeStampTick() | ||||
|  | { | ||||
|  | 	unixTimeStamp++; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::prepareBuffer(uint8_t *buf) | ||||
|  | { | ||||
|  | 	// minimal buffer size of 32 bytes is assumed
 | ||||
|  | 	memset(buf, 0x00, 32); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val) | ||||
|  | { | ||||
|  | 	buf[0]= (uint8_t)(val >> 24); | ||||
|  | 	buf[1]= (uint8_t)(val >> 16); | ||||
|  | 	buf[2]= (uint8_t)(val >> 8); | ||||
|  | 	buf[3]= (uint8_t)(val & 0xFF); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val) | ||||
|  | { | ||||
|  | 	memcpy(buf, &val, sizeof(uint32_t)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr) | ||||
|  | { | ||||
|  | 	prepareBuffer(buf); | ||||
|  | 
 | ||||
|  | 	buf[0] = 0x15; | ||||
|  | 	copyToBufferBE(&buf[1], wrAdr); | ||||
|  | 	copyToBufferBE(&buf[5], dtuAdr); | ||||
|  | 	buf[9] = 0x80; | ||||
|  | 
 | ||||
|  |   buf[10] = 0x0B;   // cid
 | ||||
|  | 	buf[11] = 0x00; | ||||
|  | 
 | ||||
|  | 	copyToBuffer(&buf[12], unixTimeStamp); | ||||
|  | 
 | ||||
|  | 	buf[19] = 0x05; | ||||
|  | 
 | ||||
|  | 	// CRC16
 | ||||
|  | 	uint16_t crc16 = crc16_modbus(&buf[10], 14); | ||||
|  | 	buf[24] = crc16 >> 8; | ||||
|  | 	buf[25] = crc16 & 0xFF; | ||||
|  | 
 | ||||
|  | 	// crc8
 | ||||
|  | 	buf[26] = crc8(&buf[0], 26); | ||||
|  | 
 | ||||
|  | 	return 27; | ||||
|  | } | ||||
|  | 
 | ||||
|  | int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd) | ||||
|  | { | ||||
|  | 	buf[0] = mid; | ||||
|  | 	copyToBufferBE(&buf[1], wrAdr); | ||||
|  | 	copyToBufferBE(&buf[5], dtuAdr); | ||||
|  | 	buf[9] = cmd; | ||||
|  | 
 | ||||
|  | 	// crc8
 | ||||
|  | 	buf[10] = crc8(&buf[0], 10); | ||||
|  | 
 | ||||
|  | 	return 11; | ||||
|  | } | ||||
| @ -0,0 +1,18 @@ | |||||
|  | 
 | ||||
|  | 
 | ||||
|  | class HM_Packets | ||||
|  | { | ||||
|  | private: | ||||
|  | 	uint32_t unixTimeStamp; | ||||
|  | 
 | ||||
|  | 	void prepareBuffer(uint8_t *buf); | ||||
|  | 	void copyToBuffer(uint8_t *buf, uint32_t val); | ||||
|  | 	void copyToBufferBE(uint8_t *buf, uint32_t val); | ||||
|  | 
 | ||||
|  | public: | ||||
|  | 	void SetUnixTimeStamp(uint32_t ts); | ||||
|  | 	void UnixTimeStampTick(); | ||||
|  | 
 | ||||
|  | 	int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr); | ||||
|  | 	int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd); | ||||
|  | }; | ||||
| @ -0,0 +1,345 @@ | |||||
|  | #ifndef __WIFI_H | ||||
|  | #define __WIFI_H | ||||
|  | 
 | ||||
|  | #include "Settings.h" | ||||
|  | #include "Debug.h" | ||||
|  | #include <ESP8266WiFi.h> | ||||
|  | #include <Pinger.h>       // von url=https://www.technologytourist.com    | ||||
|  | 
 | ||||
|  | String SSID = "";         // bestes WLan
 | ||||
|  | 
 | ||||
|  | // Prototypes
 | ||||
|  | time_t getNow (); | ||||
|  | boolean setupWifi (); | ||||
|  | boolean checkWifi(); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | String findWifi () { | ||||
|  | //----------------
 | ||||
|  |   String ssid; | ||||
|  |   int32_t rssi; | ||||
|  |   uint8_t encryptionType; | ||||
|  |   uint8_t* bssid; | ||||
|  |   int32_t channel; | ||||
|  |   bool hidden; | ||||
|  |   int scanResult; | ||||
|  |    | ||||
|  |   String best_ssid = ""; | ||||
|  |   int32_t best_rssi = -100; | ||||
|  |    | ||||
|  |   DEBUG_OUT.println(F("Starting WiFi scan...")); | ||||
|  | 
 | ||||
|  |   scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true); | ||||
|  | 
 | ||||
|  |   if (scanResult == 0) { | ||||
|  |     DEBUG_OUT.println(F("keine WLans")); | ||||
|  |   } else if (scanResult > 0) { | ||||
|  |     DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult); | ||||
|  | 
 | ||||
|  |     // Print unsorted scan results
 | ||||
|  |     for (int8_t i = 0; i < scanResult; i++) { | ||||
|  |       WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden); | ||||
|  | 
 | ||||
|  |       DEBUG_OUT.printf(PSTR("  %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"), | ||||
|  |                     i, | ||||
|  |                     channel, | ||||
|  |                     bssid[0], bssid[1], bssid[2], | ||||
|  |                     bssid[3], bssid[4], bssid[5], | ||||
|  |                     rssi, | ||||
|  |                     (encryptionType == ENC_TYPE_NONE) ? ' ' : '*', | ||||
|  |                     hidden ? 'H' : 'V', | ||||
|  |                     ssid.c_str()); | ||||
|  |       delay(1); | ||||
|  |       boolean check; | ||||
|  |       #ifdef SSID_PREFIX1 | ||||
|  |       check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1); | ||||
|  |       #else | ||||
|  |       check = true; | ||||
|  |       #endif     | ||||
|  |       #ifdef SSID_PREFIX2 | ||||
|  |       check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2); | ||||
|  |       #endif | ||||
|  |       if (check) { | ||||
|  |         if (rssi > best_rssi) { | ||||
|  |           best_rssi = rssi; | ||||
|  |           best_ssid = ssid; | ||||
|  |         } | ||||
|  |       } | ||||
|  |     } | ||||
|  |   } else { | ||||
|  |     DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   if (! best_ssid.equals("")) { | ||||
|  |     SSID = best_ssid; | ||||
|  |     DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str()); | ||||
|  |     return SSID; | ||||
|  |   } | ||||
|  |   else | ||||
|  |     return ""; | ||||
|  | } | ||||
|  | 
 | ||||
|  | void IP2string (IPAddress IP, char * buf) { | ||||
|  |   sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void connectWifi() { | ||||
|  | //------------------
 | ||||
|  | //  if (SSID.equals(""))
 | ||||
|  |    String s = findWifi(); | ||||
|  | 
 | ||||
|  |   if (!SSID.equals("")) { | ||||
|  |     DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID); | ||||
|  |     //while (WiFi.status() != WL_CONNECTED) {
 | ||||
|  |     WiFi.begin (SSID, SSID_PASSWORD); | ||||
|  |     int versuche = 20; | ||||
|  |     while (WiFi.status() != WL_CONNECTED && versuche > 0) { | ||||
|  |       delay(1000); | ||||
|  |       versuche--; | ||||
|  |       DEBUG_OUT.print(versuche); DEBUG_OUT.print(' '); | ||||
|  |     } | ||||
|  |     //}
 | ||||
|  |     if (WiFi.status() == WL_CONNECTED) { | ||||
|  |       char buffer[30]; | ||||
|  |       IP2string (WiFi.localIP(), buffer); | ||||
|  |       String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer); | ||||
|  |       DEBUG_OUT.println (out); | ||||
|  |     } | ||||
|  |     else | ||||
|  |       DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID); | ||||
|  |   } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | boolean setupWifi () { | ||||
|  | //------------------  
 | ||||
|  |   int count=5; | ||||
|  |   while (count-- && WiFi.status() != WL_CONNECTED) | ||||
|  |     connectWifi();    | ||||
|  |   return (WiFi.status() == WL_CONNECTED); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | Pinger pinger; | ||||
|  | IPAddress ROUTER = IPAddress(192,168,1,1); | ||||
|  | 
 | ||||
|  | boolean checkWifi() { | ||||
|  | //---------------
 | ||||
|  |   boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER); | ||||
|  |   if (NotConnected) { | ||||
|  |     setupWifi(); | ||||
|  |     if (WiFi.status() == WL_CONNECTED)  | ||||
|  |       getNow(); | ||||
|  |   } | ||||
|  |   return (WiFi.status() == WL_CONNECTED); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // ################  Clock  #################
 | ||||
|  | 
 | ||||
|  | #include <WiFiUdp.h> | ||||
|  | #include <TimeLib.h> | ||||
|  | 
 | ||||
|  | IPAddress     timeServer; | ||||
|  | unsigned int  localPort = 8888; | ||||
|  | const int     NTP_PACKET_SIZE= 48;            // NTP time stamp is in the first 48 bytes of the message
 | ||||
|  | byte          packetBuf[NTP_PACKET_SIZE];  // Buffer to hold incoming and outgoing packets
 | ||||
|  | const int     timeZone = 1;                   // Central European Time = +1
 | ||||
|  | long          SYNCINTERVALL = 0; | ||||
|  | WiFiUDP Udp;                                  // A UDP instance to let us send and receive packets over UDP
 | ||||
|  | 
 | ||||
|  | // prototypes
 | ||||
|  | time_t  getNtpTime (); | ||||
|  | void    sendNTPpacket (IPAddress &address); | ||||
|  | time_t  getNow (); | ||||
|  | char*   getDateTimeStr (time_t no = getNow()); | ||||
|  | time_t  offsetDayLightSaving (uint32_t local_t); | ||||
|  | bool    isDayofDaylightChange (time_t local_t); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | void _setSyncInterval (long intervall) { | ||||
|  | //----------------------------------------  
 | ||||
|  |   SYNCINTERVALL = intervall; | ||||
|  |   setSyncInterval (intervall); | ||||
|  | } | ||||
|  | 
 | ||||
|  | void setupClock() { | ||||
|  | //-----------------
 | ||||
|  |   WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
 | ||||
|  | 
 | ||||
|  |   Udp.begin(localPort); | ||||
|  | 
 | ||||
|  |   getNtpTime(); | ||||
|  | 
 | ||||
|  |   setSyncProvider (getNtpTime); | ||||
|  |   while(timeStatus()== timeNotSet) | ||||
|  |      delay(1); //
 | ||||
|  | 
 | ||||
|  |   _setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
 | ||||
|  | 
 | ||||
|  |   //lastClock = now();
 | ||||
|  |   //Serial.print("[NTP] get time from NTP server ");
 | ||||
|  |   getNow(); | ||||
|  |   //char buf[20];
 | ||||
|  |   DEBUG_OUT.print ("[NTP] get time from NTP server "); | ||||
|  |   DEBUG_OUT.print (timeServer); | ||||
|  |   //sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
 | ||||
|  |   DEBUG_OUT.print (": got "); | ||||
|  |   DEBUG_OUT.println (getDateTimeStr()); | ||||
|  | } | ||||
|  | 
 | ||||
|  | //*-------- NTP code ----------*/
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | time_t getNtpTime() { | ||||
|  | //-------------------
 | ||||
|  |   sendNTPpacket(timeServer); // send an NTP packet to a time server
 | ||||
|  |   //uint32_t beginWait = millis();
 | ||||
|  |   //while (millis() - beginWait < 1500) {
 | ||||
|  |   int versuch = 0; | ||||
|  |   while (versuch < 5) { | ||||
|  |     int wait = 150;      // results in max 1500 ms waitTime
 | ||||
|  |     while (wait--) { | ||||
|  |       int size = Udp.parsePacket(); | ||||
|  |       if (size >= NTP_PACKET_SIZE) { | ||||
|  |         //Serial.println("Receive NTP Response");
 | ||||
|  |         Udp.read(packetBuf, NTP_PACKET_SIZE);  // read packet into the buffer
 | ||||
|  |         unsigned long secsSince1900; | ||||
|  |         // convert four bytes starting at location 40 to a long integer
 | ||||
|  |         secsSince1900 =  (unsigned long)packetBuf[40] << 24; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[41] << 16; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[42] << 8; | ||||
|  |         secsSince1900 |= (unsigned long)packetBuf[43]; | ||||
|  |         // time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
 | ||||
|  |    | ||||
|  |         time_t utc = secsSince1900 - 2208988800UL; | ||||
|  |         time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR; | ||||
|  |          | ||||
|  |         if (isDayofDaylightChange (utc) && hour(utc) <= 4) | ||||
|  |           _setSyncInterval (SECS_PER_HOUR); | ||||
|  |         else | ||||
|  |           _setSyncInterval (SECS_PER_DAY / 2); | ||||
|  |         | ||||
|  |         return now; | ||||
|  |       } | ||||
|  |       else | ||||
|  |         delay(10); | ||||
|  |     } | ||||
|  |     versuch++; | ||||
|  |   } | ||||
|  |   return 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // send an NTP request to the time server at the given address
 | ||||
|  | void sendNTPpacket(IPAddress& address) { | ||||
|  | //------------------------------------
 | ||||
|  |   memset(packetBuf, 0, NTP_PACKET_SIZE);  // set all bytes in the buffer to 0
 | ||||
|  |   // Initialize values needed to form NTP request
 | ||||
|  |   packetBuf[0] = B11100011;   // LI, Version, Mode
 | ||||
|  |   packetBuf[1] = 0;     // Stratum
 | ||||
|  |   packetBuf[2] = 6;     // Max Interval between messages in seconds
 | ||||
|  |   packetBuf[3] = 0xEC;  // Clock Precision
 | ||||
|  |   // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
 | ||||
|  |   packetBuf[12]  = 49;  // four-byte reference ID identifying
 | ||||
|  |   packetBuf[13]  = 0x4E; | ||||
|  |   packetBuf[14]  = 49; | ||||
|  |   packetBuf[15]  = 52; | ||||
|  |   // send the packet requesting a timestamp:
 | ||||
|  |   Udp.beginPacket(address, 123); //NTP requests are to port 123
 | ||||
|  |   Udp.write(packetBuf,NTP_PACKET_SIZE); | ||||
|  |   Udp.endPacket(); | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | int getTimeTrials = 0; | ||||
|  | 
 | ||||
|  | bool isValidDateTime (time_t no) { | ||||
|  |   return (year(no) > 2020 && year(no) < 2038);   | ||||
|  | } | ||||
|  | 
 | ||||
|  | bool isDayofDaylightChange (time_t local_t) { | ||||
|  | //-----------------------------------------
 | ||||
|  |   int jahr  = year  (local_t); | ||||
|  |   int monat = month (local_t); | ||||
|  |   int tag   = day   (local_t); | ||||
|  |   bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) ||  | ||||
|  |                (monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7))); | ||||
|  |   DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret);  | ||||
|  |   return ret; | ||||
|  | } | ||||
|  | 
 | ||||
|  | // calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
 | ||||
|  | // übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
 | ||||
|  | time_t offsetDayLightSaving (uint32_t local_t) { | ||||
|  | //--------------------------------------------
 | ||||
|  |   int monat = month (local_t); | ||||
|  |   if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
 | ||||
|  |   if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
 | ||||
|  |   int jahr  = year (local_t); | ||||
|  |   int std   = hour (local_t); | ||||
|  |   //int tag   = day  (local_t);
 | ||||
|  |   int stundenBisHeute = (std + 24 * day(local_t)); | ||||
|  |   if ( (monat == 3  && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) ||  | ||||
|  |        (monat == 10 && stundenBisHeute <  (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) ) | ||||
|  |     return 1; | ||||
|  |   else | ||||
|  |     return 0; | ||||
|  |   /*
 | ||||
|  |   int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7)); | ||||
|  |   if (monat == 3  && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute <  stundenBisWechsel) | ||||
|  |     return 1; | ||||
|  |   else | ||||
|  |     return 0; | ||||
|  |     */ | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | time_t getNow () { | ||||
|  | //---------------
 | ||||
|  |   time_t jetzt = now(); | ||||
|  |   while (!isValidDateTime(jetzt) && getTimeTrials < 10)  { // ungültig, max 10x probieren
 | ||||
|  |     if (getTimeTrials) { | ||||
|  |       //Serial.print (getTimeTrials);
 | ||||
|  |       //Serial.println(". Versuch für getNtpTime");
 | ||||
|  |     } | ||||
|  |     jetzt = getNtpTime (); | ||||
|  |     if (isValidDateTime(jetzt)) { | ||||
|  |       setTime (jetzt); | ||||
|  |       getTimeTrials = 0; | ||||
|  |     } | ||||
|  |     else | ||||
|  |       getTimeTrials++; | ||||
|  |   } | ||||
|  |   //return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
 | ||||
|  |   return jetzt; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | char _timestr[24]; | ||||
|  | 
 | ||||
|  | char* getNowStr (time_t no = getNow()) { | ||||
|  | //------------------------------------
 | ||||
|  |   sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getTimeStr (time_t no = getNow()) { | ||||
|  | //------------------------------------
 | ||||
|  |   return getNowStr (no); | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getDateTimeStr (time_t no) { | ||||
|  | //------------------------------
 | ||||
|  |   sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | char* getDateStr (time_t no) { | ||||
|  | //------------------------------
 | ||||
|  |   sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no)); | ||||
|  |   return _timestr;   | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif | ||||
| @ -0,0 +1,157 @@ | |||||
|  | /*
 | ||||
|  |     CircularBuffer - An Arduino circular buffering library for arbitrary types. | ||||
|  | 
 | ||||
|  |     Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl | ||||
|  | 
 | ||||
|  |     This library is free software; you can redistribute it and/or | ||||
|  |     modify it under the terms of the GNU Lesser General Public | ||||
|  |     License as published by the Free Software Foundation; either | ||||
|  |     version 2.1 of the License, or (at your option) any later version. | ||||
|  | 
 | ||||
|  |     This library is distributed in the hope that it will be useful, | ||||
|  |     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  |     Lesser General Public License for more details. | ||||
|  | 
 | ||||
|  |     You should have received a copy of the GNU Lesser General Public | ||||
|  |     License along with this library; if not, write to the Free Software | ||||
|  |     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  | */ | ||||
|  | 
 | ||||
|  | #ifndef CircularBuffer_h | ||||
|  | #define CircularBuffer_h | ||||
|  | 
 | ||||
|  | #ifdef ESP8266 | ||||
|  | #define DISABLE_IRQ noInterrupts() | ||||
|  | #define RESTORE_IRQ interrupts() | ||||
|  | #else | ||||
|  | #define DISABLE_IRQ       \ | ||||
|  |     uint8_t sreg = SREG;    \ | ||||
|  |     cli(); | ||||
|  | 
 | ||||
|  | #define RESTORE_IRQ        \ | ||||
|  |     SREG = sreg; | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | template <class BUFFERTYPE, uint8_t BUFFERSIZE> | ||||
|  | class CircularBuffer { | ||||
|  | 
 | ||||
|  |     typedef BUFFERTYPE BufferType; | ||||
|  |     BufferType Buffer[BUFFERSIZE]; | ||||
|  | 
 | ||||
|  |     public: | ||||
|  |         CircularBuffer() : m_buff(Buffer) { | ||||
|  |             m_size = BUFFERSIZE; | ||||
|  |             clear(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Clear all entries in the circular buffer. */ | ||||
|  |         void clear(void) | ||||
|  |         { | ||||
|  |             m_front = 0; | ||||
|  |             m_fill  = 0; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Test if the circular buffer is empty */ | ||||
|  |         inline bool empty(void) const | ||||
|  |         { | ||||
|  |             return !m_fill; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Return the number of records stored in the buffer */ | ||||
|  |         inline uint8_t available(void) const | ||||
|  |         { | ||||
|  |             return m_fill; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Test if the circular buffer is full */ | ||||
|  |         inline bool full(void) const | ||||
|  |         { | ||||
|  |             return m_fill == m_size; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Aquire record on front of the buffer, for writing.
 | ||||
|  |          * After filling the record, it has to be pushed to actually | ||||
|  |          * add it to the buffer. | ||||
|  |          * @return Pointer to record, or NULL when buffer is full. | ||||
|  |          */ | ||||
|  |         BUFFERTYPE* getFront(void) const | ||||
|  |         { | ||||
|  |             DISABLE_IRQ; | ||||
|  |             BUFFERTYPE* f = NULL; | ||||
|  |             if (!full()) | ||||
|  |                 f = get(m_front); | ||||
|  |             RESTORE_IRQ; | ||||
|  |             return f; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Push record to front of the buffer
 | ||||
|  |          * @param record   Record to push. If record was aquired previously (using getFront) its | ||||
|  |          *                 data will not be copied as it is already present in the buffer. | ||||
|  |          * @return True, when record was pushed successfully. | ||||
|  |          */ | ||||
|  |         bool pushFront(BUFFERTYPE* record) | ||||
|  |         { | ||||
|  |             bool ok = false; | ||||
|  |             DISABLE_IRQ; | ||||
|  |             if (!full()) | ||||
|  |             { | ||||
|  |                 BUFFERTYPE* f = get(m_front); | ||||
|  |                 if (f != record) | ||||
|  |                     *f = *record; | ||||
|  |                 m_front = (m_front+1) % m_size; | ||||
|  |                 m_fill++; | ||||
|  |                 ok = true; | ||||
|  |             } | ||||
|  |             RESTORE_IRQ; | ||||
|  |             return ok; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Aquire record on back of the buffer, for reading.
 | ||||
|  |          * After reading the record, it has to be pop'ed to actually | ||||
|  |          * remove it from the buffer. | ||||
|  |          * @return Pointer to record, or NULL when buffer is empty. | ||||
|  |          */ | ||||
|  |         BUFFERTYPE* getBack(void) const | ||||
|  |         { | ||||
|  |             BUFFERTYPE* b = NULL; | ||||
|  |             DISABLE_IRQ; | ||||
|  |             if (!empty()) | ||||
|  |                 b = get(back()); | ||||
|  |             RESTORE_IRQ; | ||||
|  |             return b; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         /** Remove record from back of the buffer.
 | ||||
|  |          * @return True, when record was pop'ed successfully. | ||||
|  |          */ | ||||
|  |         bool popBack(void) | ||||
|  |         { | ||||
|  |             bool ok = false; | ||||
|  |             DISABLE_IRQ; | ||||
|  |             if (!empty()) | ||||
|  |             { | ||||
|  |                 m_fill--; | ||||
|  |                 ok = true; | ||||
|  |             } | ||||
|  |             RESTORE_IRQ; | ||||
|  |             return ok; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |     protected: | ||||
|  |         inline BUFFERTYPE * get(const uint8_t idx) const | ||||
|  |         { | ||||
|  |             return &(m_buff[idx]); | ||||
|  |         } | ||||
|  |         inline uint8_t back(void) const | ||||
|  |         { | ||||
|  |             return (m_front - m_fill + m_size) % m_size; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t          m_size;     // Total number of records that can be stored in the buffer.
 | ||||
|  |         BUFFERTYPE* const m_buff; | ||||
|  |         volatile uint8_t m_front;    // Index of front element (not pushed yet).
 | ||||
|  |         volatile uint8_t m_fill;     // Amount of records currently pushed.
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif // CircularBuffer_h
 | ||||
| @ -0,0 +1,60 @@ | |||||
|  | ## OVERVIEW | ||||
|  | 
 | ||||
|  | This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: <https://www.mikrocontroller.net/topic/525778?page=3#7033371> | ||||
|  | 
 | ||||
|  | The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: CS, CE and IRQ | ||||
|  | These pins can be changed from the /setup URL | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | ## Compile | ||||
|  | 
 | ||||
|  | This code can be compiled using Arduino. The settings were: | ||||
|  | 
 | ||||
|  | - Board: Generic ESP8266 Module | ||||
|  | - Flash-Size: 1MB (FS: none, OTA: 502kB) | ||||
|  | 
 | ||||
|  | ### Optional Configuration before compilation | ||||
|  | 
 | ||||
|  | - number of supported inverters (set to 3 by default) `defines.h` | ||||
|  | - enable channel hopping `hmRadio.h` | ||||
|  | - DTU radio id `hmRadio.h` | ||||
|  | - unformated list in webbrowser `/livedata` `defines.h`, `LIVEDATA_VISUALIZED` | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | ## Flash ESP with firmware | ||||
|  | 
 | ||||
|  | 1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities | ||||
|  | 2. repower the ESP | ||||
|  | 3. the ESP will start as access point (AP) if there is no network config stored in its eeprom | ||||
|  | 4. connect to the AP, you will be forwarded to the setup page | ||||
|  | 5. configure your WiFi settings, save, repower | ||||
|  | 6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | ## Usage | ||||
|  | 
 | ||||
|  | Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities: | ||||
|  | 
 | ||||
|  | - OTA Update (over the air update) | ||||
|  | - Configuration (Wifi, inverter(s), Pinout, MQTT) | ||||
|  | - visual display of the connected inverters / modules | ||||
|  | - some statistics about communication (debug) | ||||
|  | 
 | ||||
|  | The serial console will print the converted values which were read out of the inverter(s) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | ## Compatiblity | ||||
|  | 
 | ||||
|  | For now the following inverters should work out of the box: | ||||
|  | 
 | ||||
|  | - HM400 | ||||
|  | - HM600 | ||||
|  | - HM800 | ||||
|  | - HM1200 | ||||
|  | 
 | ||||
|  | ## USED LIBRARIES | ||||
|  | 
 | ||||
|  | - `Time` | ||||
|  | - `RF24` | ||||
|  | - `PubSubClient` | ||||
|  | 
 | ||||
| @ -0,0 +1,611 @@ | |||||
|  | #include "app.h" | ||||
|  | 
 | ||||
|  | #include "html/h/index_html.h" | ||||
|  | #include "html/h/setup_html.h" | ||||
|  | #include "html/h/hoymiles_html.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | app::app() : Main() { | ||||
|  |     mSendTicker     = 0xffff; | ||||
|  |     mSendInterval   = 0; | ||||
|  |     mMqttTicker     = 0xffff; | ||||
|  |     mMqttInterval   = 0; | ||||
|  |     mSerialTicker   = 0xffff; | ||||
|  |     mSerialInterval = 0; | ||||
|  |     mMqttActive     = false; | ||||
|  | 
 | ||||
|  |     mTicker = 0; | ||||
|  | 
 | ||||
|  |     mShowRebootRequest = false; | ||||
|  | 
 | ||||
|  |     mSerialValues = true; | ||||
|  |     mSerialDebug  = false; | ||||
|  | 
 | ||||
|  |     memset(mCmds, 0, sizeof(uint32_t)*DBG_CMD_LIST_LEN); | ||||
|  |     //memset(mChannelStat, 0, sizeof(uint32_t) * 4);
 | ||||
|  | 
 | ||||
|  |     mSys = new HmSystemType(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | app::~app(void) { | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::setup(uint32_t timeout) { | ||||
|  |     Main::setup(timeout); | ||||
|  | 
 | ||||
|  |     mWeb->on("/",          std::bind(&app::showIndex,      this)); | ||||
|  |     mWeb->on("/setup",     std::bind(&app::showSetup,      this)); | ||||
|  |     mWeb->on("/save",      std::bind(&app::showSave,       this)); | ||||
|  |     mWeb->on("/erase",     std::bind(&app::showErase,      this)); | ||||
|  |     mWeb->on("/cmdstat",   std::bind(&app::showStatistics, this)); | ||||
|  |     mWeb->on("/hoymiles",  std::bind(&app::showHoymiles,   this)); | ||||
|  |     mWeb->on("/livedata",  std::bind(&app::showLiveData,   this)); | ||||
|  | 
 | ||||
|  |     if(mSettingsValid) { | ||||
|  |         uint64_t invSerial; | ||||
|  |         char invName[MAX_NAME_LENGTH + 1] = {0}; | ||||
|  |         uint8_t invType; | ||||
|  | 
 | ||||
|  |         // inverter
 | ||||
|  |         for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { | ||||
|  |             mEep->read(ADDR_INV_ADDR + (i * 8),               &invSerial); | ||||
|  |             mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); | ||||
|  |             mEep->read(ADDR_INV_TYPE + i,                     &invType); | ||||
|  |             if(0ULL != invSerial) { | ||||
|  |                 mSys->addInverter(invName, invSerial, invType); | ||||
|  |                 DPRINTLN("add inverter: " + String(invName) + ", SN: " + String(invSerial, HEX) + ", type: " + String(invType)); | ||||
|  |             } | ||||
|  |         } | ||||
|  |         mEep->read(ADDR_INV_INTERVAL, &mSendInterval); | ||||
|  |         if(mSendInterval < 1) | ||||
|  |             mSendInterval = 1; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // pinout
 | ||||
|  |         mEep->read(ADDR_PINOUT,   &mSys->Radio.pinCs); | ||||
|  |         mEep->read(ADDR_PINOUT+1, &mSys->Radio.pinCe); | ||||
|  |         mEep->read(ADDR_PINOUT+2, &mSys->Radio.pinIrq); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // nrf24 amplifier power
 | ||||
|  |         mEep->read(ADDR_RF24_AMP_PWR, &mSys->Radio.AmplifierPower); | ||||
|  | 
 | ||||
|  |         // serial console
 | ||||
|  |         uint8_t tmp; | ||||
|  |         mEep->read(ADDR_SER_INTERVAL, &mSerialInterval); | ||||
|  |         mEep->read(ADDR_SER_ENABLE, &tmp); | ||||
|  |         mSerialValues = (tmp == 0x01); | ||||
|  |         mEep->read(ADDR_SER_DEBUG, &tmp); | ||||
|  |         mSerialDebug = (tmp == 0x01); | ||||
|  |         if(mSerialInterval < 1) | ||||
|  |             mSerialInterval = 1; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // mqtt
 | ||||
|  |         uint8_t mqttAddr[MQTT_ADDR_LEN]; | ||||
|  |         uint16_t mqttPort; | ||||
|  |         char mqttUser[MQTT_USER_LEN]; | ||||
|  |         char mqttPwd[MQTT_PWD_LEN]; | ||||
|  |         char mqttTopic[MQTT_TOPIC_LEN]; | ||||
|  |         mEep->read(ADDR_MQTT_ADDR,     mqttAddr,  MQTT_ADDR_LEN); | ||||
|  |         mEep->read(ADDR_MQTT_USER,     mqttUser,  MQTT_USER_LEN); | ||||
|  |         mEep->read(ADDR_MQTT_PWD,      mqttPwd,   MQTT_PWD_LEN); | ||||
|  |         mEep->read(ADDR_MQTT_TOPIC,    mqttTopic, MQTT_TOPIC_LEN); | ||||
|  |         mEep->read(ADDR_MQTT_INTERVAL, &mMqttInterval); | ||||
|  |         mEep->read(ADDR_MQTT_PORT,     &mqttPort); | ||||
|  | 
 | ||||
|  |         char addr[16] = {0}; | ||||
|  |         sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); | ||||
|  |         mMqttActive = (mqttAddr[0] > 0); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         if(mMqttInterval < 1) | ||||
|  |             mMqttInterval = 1; | ||||
|  |         mMqtt.setup(addr, mqttTopic, mqttUser, mqttPwd, mqttPort); | ||||
|  |         mMqttTicker = 0; | ||||
|  | 
 | ||||
|  |         mSerialTicker = 0; | ||||
|  | 
 | ||||
|  |         mMqtt.sendMsg("version", mVersion); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     mSys->setup(); | ||||
|  | 
 | ||||
|  |     if(!mWifiSettingsValid) | ||||
|  |         DPRINTLN("Warn: your settings are not valid! check [IP]/setup"); | ||||
|  |     else { | ||||
|  |         DPRINTLN("\n\n----------------------------------------"); | ||||
|  |         DPRINTLN("Welcome to AHOY!"); | ||||
|  |         DPRINT("\npoint your browser to http://"); | ||||
|  |         DPRINTLN(WiFi.localIP()); | ||||
|  |         DPRINTLN("to configure your device"); | ||||
|  |         DPRINTLN("----------------------------------------\n"); | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::loop(void) { | ||||
|  |     Main::loop(); | ||||
|  | 
 | ||||
|  |     if(!mSys->BufCtrl.empty()) { | ||||
|  |         uint8_t len, rptCnt; | ||||
|  |         packet_t *p = mSys->BufCtrl.getBack(); | ||||
|  |         if(mSerialDebug) | ||||
|  |             mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); | ||||
|  | 
 | ||||
|  |         if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) { | ||||
|  |             // process buffer only on first occurrence
 | ||||
|  |             if((0 != len) && (0 == rptCnt)) { | ||||
|  |                 uint8_t *cmd = &p->packet[11]; | ||||
|  |                 //DPRINTLN("CMD " + String(*cmd, HEX));
 | ||||
|  |                 //mSys->Radio.dumpBuf("Payload ", p->packet, len);
 | ||||
|  | 
 | ||||
|  |                 Inverter<> *iv = mSys->findInverter(&p->packet[3]); | ||||
|  |                 if(NULL != iv) { | ||||
|  |                     for(uint8_t i = 0; i < iv->listLen; i++) { | ||||
|  |                         if(iv->assign[i].cmdId == *cmd) | ||||
|  |                             iv->addValue(i, &p->packet[11]); | ||||
|  |                     } | ||||
|  |                     iv->doCalculations(); | ||||
|  |                 } | ||||
|  | 
 | ||||
|  |                 if(*cmd == 0x01)      mCmds[0]++; | ||||
|  |                 else if(*cmd == 0x02) mCmds[1]++; | ||||
|  |                 else if(*cmd == 0x03) mCmds[2]++; | ||||
|  |                 else if(*cmd == 0x81) mCmds[3]++; | ||||
|  |                 else if(*cmd == 0x82) mCmds[4]++; | ||||
|  |                 else if(*cmd == 0x83) mCmds[5]++; | ||||
|  |                 else if(*cmd == 0x84) mCmds[6]++; | ||||
|  |                 else                  mCmds[7]++; | ||||
|  | 
 | ||||
|  |                 /*if(p->sendCh == 23)      mChannelStat[0]++;
 | ||||
|  |                 else if(p->sendCh == 40) mChannelStat[1]++; | ||||
|  |                 else if(p->sendCh == 61) mChannelStat[2]++; | ||||
|  |                 else                     mChannelStat[3]++;*/ | ||||
|  |             } | ||||
|  |         } | ||||
|  |         mSys->BufCtrl.popBack(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     if(checkTicker(&mTicker, 1000)) { | ||||
|  |         if(++mSendTicker >= mSendInterval) { | ||||
|  |             mSendTicker = 0; | ||||
|  | 
 | ||||
|  |             Inverter<> *inv; | ||||
|  |             for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { | ||||
|  |                 inv = mSys->getInverterByPos(i); | ||||
|  |                 if(NULL != inv) { | ||||
|  |                     mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp); | ||||
|  |                     yield(); | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         if(mMqttActive) { | ||||
|  |             mMqtt.loop(); | ||||
|  |             if(++mMqttTicker > mMqttInterval) { | ||||
|  |                 mMqttInterval = 0; | ||||
|  |                 mMqtt.isConnected(true); | ||||
|  |                 char topic[30], val[10]; | ||||
|  |                 for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { | ||||
|  |                     Inverter<> *iv = mSys->getInverterByPos(id); | ||||
|  |                     if(NULL != iv) { | ||||
|  |                         for(uint8_t i = 0; i < iv->listLen; i++) { | ||||
|  |                             if(0.0f != iv->getValue(i)) { | ||||
|  |                                 snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); | ||||
|  |                                 snprintf(val, 10, "%.3f", iv->getValue(i)); | ||||
|  |                                 mMqtt.sendMsg(topic, val); | ||||
|  |                                 yield(); | ||||
|  |                             } | ||||
|  |                         } | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         if(mSerialValues) { | ||||
|  |             if(++mSerialTicker > mSerialInterval) { | ||||
|  |                 mSerialInterval = 0; | ||||
|  |                 char topic[30], val[10]; | ||||
|  |                 for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { | ||||
|  |                     Inverter<> *iv = mSys->getInverterByPos(id); | ||||
|  |                     if(NULL != iv) { | ||||
|  |                         for(uint8_t i = 0; i < iv->listLen; i++) { | ||||
|  |                             if(0.0f != iv->getValue(i)) { | ||||
|  |                                 snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); | ||||
|  |                                 snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i)); | ||||
|  |                                 DPRINTLN(String(topic) + ": " + String(val)); | ||||
|  |                             } | ||||
|  |                             yield(); | ||||
|  |                         } | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::handleIntr(void) { | ||||
|  |     mSys->Radio.handleIntr(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showIndex(void) { | ||||
|  |     String html = FPSTR(index_html); | ||||
|  |     html.replace("{DEVICE}", mDeviceName); | ||||
|  |     html.replace("{VERSION}", mVersion); | ||||
|  |     mWeb->send(200, "text/html", html); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showSetup(void) { | ||||
|  |     // overrides same method in main.cpp
 | ||||
|  | 
 | ||||
|  |     uint16_t interval; | ||||
|  | 
 | ||||
|  |     String html = FPSTR(setup_html); | ||||
|  |     html.replace("{SSID}", mStationSsid); | ||||
|  |     // PWD will be left at the default value (for protection)
 | ||||
|  |     // -> the PWD will only be changed if it does not match the placeholder "{PWD}"
 | ||||
|  | 
 | ||||
|  |     html.replace("{DEVICE}", String(mDeviceName)); | ||||
|  |     html.replace("{VERSION}", String(mVersion)); | ||||
|  |     if(mApActive) | ||||
|  |         html.replace("{IP}", String("http://192.168.1.1")); | ||||
|  |     else | ||||
|  |         html.replace("{IP}", ("http://" + String(WiFi.localIP().toString()))); | ||||
|  | 
 | ||||
|  |     String inv; | ||||
|  |     uint64_t invSerial; | ||||
|  |     char invName[MAX_NAME_LENGTH + 1] = {0}; | ||||
|  |     uint8_t invType; | ||||
|  |     for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { | ||||
|  |         mEep->read(ADDR_INV_ADDR + (i * 8),               &invSerial); | ||||
|  |         mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); | ||||
|  |         mEep->read(ADDR_INV_TYPE + i,                     &invType); | ||||
|  |         inv += "<p class=\"subdes\">Inverter "+ String(i) + "</p>"; | ||||
|  | 
 | ||||
|  |         inv += "<label for=\"inv" + String(i) + "Addr\">Address</label>"; | ||||
|  |         inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Addr\" value=\""; | ||||
|  |         if(0ULL != invSerial) | ||||
|  |             inv += String(invSerial, HEX); | ||||
|  |         inv += "\"/ maxlength=\"12\">"; | ||||
|  | 
 | ||||
|  |         inv += "<label for=\"inv" + String(i) + "Name\">Name</label>"; | ||||
|  |         inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Name\" value=\""; | ||||
|  |         inv += String(invName); | ||||
|  |         inv += "\"/ maxlength=\"" + String(MAX_NAME_LENGTH) + "\">"; | ||||
|  | 
 | ||||
|  |         inv += "<label for=\"inv" + String(i) + "Type\">Type</label>"; | ||||
|  |         inv += "<select name=\"inv" + String(i) + "Type\">"; | ||||
|  |         for(uint8_t t = 0; t < NUM_INVERTER_TYPES; t++) { | ||||
|  |             inv += "<option value=\"" + String(t) + "\""; | ||||
|  |             if(invType == t) | ||||
|  |                 inv += " selected"; | ||||
|  |             inv += ">" + String(invTypes[t]) + "</option>"; | ||||
|  |         } | ||||
|  |         inv += "</select>"; | ||||
|  |     } | ||||
|  |     html.replace("{INVERTERS}", String(inv)); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     // pinout
 | ||||
|  |     String pinout; | ||||
|  |     for(uint8_t i = 0; i < 3; i++) { | ||||
|  |         pinout += "<label for=\"" + String(pinArgNames[i]) + "\">" + String(pinNames[i]) + "</label>"; | ||||
|  |         pinout += "<select name=\"" + String(pinArgNames[i]) + "\">"; | ||||
|  |         for(uint8_t j = 0; j <= 16; j++) { | ||||
|  |             pinout += "<option value=\"" + String(j) + "\""; | ||||
|  |             switch(i) { | ||||
|  |                 default: if(j == mSys->Radio.pinCs)  pinout += " selected"; break; | ||||
|  |                 case 1:  if(j == mSys->Radio.pinCe)  pinout += " selected"; break; | ||||
|  |                 case 2:  if(j == mSys->Radio.pinIrq) pinout += " selected"; break; | ||||
|  |             } | ||||
|  |             pinout += ">" + String(wemosPins[j]) + "</option>"; | ||||
|  |         } | ||||
|  |         pinout += "</select>"; | ||||
|  |     } | ||||
|  |     html.replace("{PINOUT}", String(pinout)); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     // nrf24l01+
 | ||||
|  |     String rf24; | ||||
|  |     for(uint8_t i = 0; i <= 3; i++) { | ||||
|  |         rf24 += "<option value=\"" + String(i) + "\""; | ||||
|  |         if(i == mSys->Radio.AmplifierPower) | ||||
|  |             rf24 += " selected"; | ||||
|  |         rf24 += ">" + String(rf24AmpPower[i]) + "</option>"; | ||||
|  |     } | ||||
|  |     html.replace("{RF24}", String(rf24)); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     if(mSettingsValid) { | ||||
|  |         mEep->read(ADDR_INV_INTERVAL, &interval); | ||||
|  |         html.replace("{INV_INTVL}", String(interval)); | ||||
|  | 
 | ||||
|  |         uint8_t tmp; | ||||
|  |         mEep->read(ADDR_SER_INTERVAL, &interval); | ||||
|  |         mEep->read(ADDR_SER_ENABLE, &tmp); | ||||
|  |         html.replace("{SER_INTVL}", String(interval)); | ||||
|  |         html.replace("{SER_VAL_CB}", (tmp == 0x01) ? "checked" : ""); | ||||
|  |         mEep->read(ADDR_SER_DEBUG, &tmp); | ||||
|  |         html.replace("{SER_DBG_CB}", (tmp == 0x01) ? "checked" : ""); | ||||
|  | 
 | ||||
|  |         uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; | ||||
|  |         uint16_t mqttPort; | ||||
|  |         mEep->read(ADDR_MQTT_ADDR,     mqttAddr, MQTT_ADDR_LEN); | ||||
|  |         mEep->read(ADDR_MQTT_INTERVAL, &interval); | ||||
|  |         mEep->read(ADDR_MQTT_PORT,     &mqttPort); | ||||
|  | 
 | ||||
|  |         char addr[16] = {0}; | ||||
|  |         sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); | ||||
|  |         html.replace("{MQTT_ADDR}",  String(addr)); | ||||
|  |         html.replace("{MQTT_PORT}",  String(mqttPort)); | ||||
|  |         html.replace("{MQTT_USER}",  String(mMqtt.getUser())); | ||||
|  |         html.replace("{MQTT_PWD}",   String(mMqtt.getPwd())); | ||||
|  |         html.replace("{MQTT_TOPIC}", String(mMqtt.getTopic())); | ||||
|  |         html.replace("{MQTT_INTVL}", String(interval)); | ||||
|  |     } | ||||
|  |     else { | ||||
|  |         html.replace("{INV_INTVL}", "5"); | ||||
|  | 
 | ||||
|  |         html.replace("{SER_VAL_CB}", "checked"); | ||||
|  |         html.replace("{SER_DBG_CB}", ""); | ||||
|  |         html.replace("{SER_INTVL}", "10"); | ||||
|  | 
 | ||||
|  |         html.replace("{MQTT_ADDR}", ""); | ||||
|  |         html.replace("{MQTT_PORT}", "1883"); | ||||
|  |         html.replace("{MQTT_USER}", ""); | ||||
|  |         html.replace("{MQTT_PWD}", ""); | ||||
|  |         html.replace("{MQTT_TOPIC}", "/inverter"); | ||||
|  |         html.replace("{MQTT_INTVL}", "10"); | ||||
|  | 
 | ||||
|  |         html.replace("{SER_INTVL}", "10"); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     mWeb->send(200, "text/html", html); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showSave(void) { | ||||
|  |     saveValues(true); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showErase() { | ||||
|  |     eraseSettings(); | ||||
|  |     showReboot(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showStatistics(void) { | ||||
|  |     String content = "CMDs:\n"; | ||||
|  |     for(uint8_t i = 0; i < DBG_CMD_LIST_LEN; i ++) { | ||||
|  |         content += String("0x") + String(dbgCmds[i], HEX) + String(": ") + String(mCmds[i]) + String("\n"); | ||||
|  |     } | ||||
|  |     content += String("other: ") + String(mCmds[DBG_CMD_LIST_LEN]) + String("\n\n"); | ||||
|  | 
 | ||||
|  |     content += "Send Cnt: " + String(mSys->Radio.mSendCnt) + String("\n\n"); | ||||
|  | 
 | ||||
|  |     /*content += "\nCHANNELs:\n";
 | ||||
|  |     content += String("23: ") + String(mChannelStat[0]) + String("\n"); | ||||
|  |     content += String("40: ") + String(mChannelStat[1]) + String("\n"); | ||||
|  |     content += String("61: ") + String(mChannelStat[2]) + String("\n"); | ||||
|  |     content += String("75: ") + String(mChannelStat[3]) + String("\n");*/ | ||||
|  | 
 | ||||
|  |     if(!mSys->Radio.isChipConnected()) | ||||
|  |         content += "WARNING! your NRF24 module can't be reached, check the wiring and pinout (<a href=\"/setup\">setup</a>)\n"; | ||||
|  | 
 | ||||
|  |     if(mShowRebootRequest) | ||||
|  |         content += "INFO: reboot your ESP to apply all your configuration changes!\n"; | ||||
|  | 
 | ||||
|  |     if(!mSettingsValid) | ||||
|  |         content += "INFO: your settings are invalid, please switch to <a href=\"/setup\">setup</a> to correct this.\n"; | ||||
|  | 
 | ||||
|  |     content += "MQTT: "; | ||||
|  |     if(!mMqtt.isConnected()) | ||||
|  |         content += "not "; | ||||
|  |     content += "connected\n"; | ||||
|  | 
 | ||||
|  |     mWeb->send(200, "text/plain", content); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showHoymiles(void) { | ||||
|  |     String html = FPSTR(hoymiles_html); | ||||
|  |     html.replace("{DEVICE}", mDeviceName); | ||||
|  |     html.replace("{VERSION}", mVersion); | ||||
|  |     mWeb->send(200, "text/html", html); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::showLiveData(void) { | ||||
|  |     String modHtml; | ||||
|  |     for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { | ||||
|  |         Inverter<> *iv = mSys->getInverterByPos(id); | ||||
|  |         if(NULL != iv) { | ||||
|  | #ifdef LIVEDATA_VISUALIZED | ||||
|  |             uint8_t modNum, pos; | ||||
|  |             switch(iv->type) { | ||||
|  |                 default:              modNum = 1; break; | ||||
|  |                 case INV_TYPE_HM600: | ||||
|  |                 case INV_TYPE_HM800:  modNum = 2; break; | ||||
|  |                 case INV_TYPE_HM1200: modNum = 4; break; | ||||
|  |             } | ||||
|  | 
 | ||||
|  |             modHtml += "<div class=\"iv\">"; | ||||
|  |             modHtml += "<div class=\"ch-iv\"><span class=\"head\">" + String(iv->name) + "</span>"; | ||||
|  |             uint8_t list[8] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD}; | ||||
|  | 
 | ||||
|  |             for(uint8_t fld = 0; fld < 8; fld++) { | ||||
|  |                 pos = (iv->getPosByChFld(CH0, list[fld])); | ||||
|  |                 if(0xff != pos) { | ||||
|  |                     modHtml += "<div class=\"subgrp\">"; | ||||
|  |                     modHtml += "<span class=\"value\">" + String(iv->getValue(pos)); | ||||
|  |                     modHtml += "<span class=\"unit\">" + String(iv->getUnit(pos)) + "</span></span>"; | ||||
|  |                     modHtml += "<span class=\"info\">" + String(iv->getFieldName(pos)) + "</span>"; | ||||
|  |                     modHtml += "</div>"; | ||||
|  |                 } | ||||
|  |             } | ||||
|  |             modHtml += "</div>"; | ||||
|  | 
 | ||||
|  |             for(uint8_t ch = 1; ch <= modNum; ch ++) { | ||||
|  |                 modHtml += "<div class=\"ch\"><span class=\"head\">CHANNEL " + String(ch) + "</span>"; | ||||
|  |                 for(uint8_t j = 0; j < 5; j++) { | ||||
|  |                     switch(j) { | ||||
|  |                         default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break; | ||||
|  |                         case 1:  pos = (iv->getPosByChFld(ch, FLD_IDC)); break; | ||||
|  |                         case 2:  pos = (iv->getPosByChFld(ch, FLD_PDC)); break; | ||||
|  |                         case 3:  pos = (iv->getPosByChFld(ch, FLD_YD));  break; | ||||
|  |                         case 4:  pos = (iv->getPosByChFld(ch, FLD_YT));  break; | ||||
|  |                     } | ||||
|  |                     if(0xff != pos) { | ||||
|  |                         modHtml += "<span class=\"value\">" + String(iv->getValue(pos)); | ||||
|  |                         modHtml += "<span class=\"unit\">" + String(iv->getUnit(pos)) + "</span></span>"; | ||||
|  |                         modHtml += "<span class=\"info\">" + String(iv->getFieldName(pos)) + "</span>"; | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |                 modHtml += "</div>"; | ||||
|  |             } | ||||
|  | 
 | ||||
|  |             modHtml += "</div>"; | ||||
|  | #else | ||||
|  |             // dump all data to web frontend
 | ||||
|  |             modHtml = "<pre>"; | ||||
|  |             char topic[30], val[10]; | ||||
|  |             for(uint8_t i = 0; i < iv->listLen; i++) { | ||||
|  |                 snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); | ||||
|  |                 snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i)); | ||||
|  |                 modHtml += String(topic) + ": " + String(val) + "\n"; | ||||
|  |             } | ||||
|  |             modHtml += "</pre>"; | ||||
|  | #endif | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     mWeb->send(200, "text/html", modHtml); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::saveValues(bool webSend = true) { | ||||
|  |     Main::saveValues(false); // general configuration
 | ||||
|  | 
 | ||||
|  |     if(mWeb->args() > 0) { | ||||
|  |         char *p; | ||||
|  |         char buf[20] = {0}; | ||||
|  |         uint8_t i = 0; | ||||
|  |         uint16_t interval; | ||||
|  | 
 | ||||
|  |         // inverter
 | ||||
|  |         serial_u addr; | ||||
|  |         for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { | ||||
|  |             // address
 | ||||
|  |             mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); | ||||
|  |             if(strlen(buf) == 0) | ||||
|  |                 snprintf(buf, 20, "\0"); | ||||
|  |             addr.u64 = Serial2u64(buf); | ||||
|  |             mEep->write(ADDR_INV_ADDR + (i * 8), addr.u64); | ||||
|  | 
 | ||||
|  |             // name
 | ||||
|  |             mWeb->arg("inv" + String(i) + "Name").toCharArray(buf, 20); | ||||
|  |             mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), buf, MAX_NAME_LENGTH); | ||||
|  | 
 | ||||
|  |             // type
 | ||||
|  |             mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20); | ||||
|  |             uint8_t type = atoi(buf); | ||||
|  |             mEep->write(ADDR_INV_TYPE + i, type); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         interval = mWeb->arg("invInterval").toInt(); | ||||
|  |         mEep->write(ADDR_INV_INTERVAL, interval); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // pinout
 | ||||
|  |         for(uint8_t i = 0; i < 3; i ++) { | ||||
|  |             uint8_t pin = mWeb->arg(String(pinArgNames[i])).toInt(); | ||||
|  |             mEep->write(ADDR_PINOUT + i, pin); | ||||
|  |         } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // nrf24 amplifier power
 | ||||
|  |         mSys->Radio.AmplifierPower = mWeb->arg("rf24Power").toInt() & 0x03; | ||||
|  |         mEep->write(ADDR_RF24_AMP_PWR, mSys->Radio.AmplifierPower); | ||||
|  | 
 | ||||
|  |         // mqtt
 | ||||
|  |         uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; | ||||
|  |         uint16_t mqttPort; | ||||
|  |         char mqttUser[MQTT_USER_LEN]; | ||||
|  |         char mqttPwd[MQTT_PWD_LEN]; | ||||
|  |         char mqttTopic[MQTT_TOPIC_LEN]; | ||||
|  |         mWeb->arg("mqttAddr").toCharArray(buf, 20); | ||||
|  |         i = 0; | ||||
|  |         p = strtok(buf, "."); | ||||
|  |         while(NULL != p) { | ||||
|  |             mqttAddr[i++] = atoi(p); | ||||
|  |             p = strtok(NULL, "."); | ||||
|  |         } | ||||
|  |         mWeb->arg("mqttUser").toCharArray(mqttUser, MQTT_USER_LEN); | ||||
|  |         mWeb->arg("mqttPwd").toCharArray(mqttPwd, MQTT_PWD_LEN); | ||||
|  |         mWeb->arg("mqttTopic").toCharArray(mqttTopic, MQTT_TOPIC_LEN); | ||||
|  |         interval = mWeb->arg("mqttIntvl").toInt(); | ||||
|  |         mqttPort = mWeb->arg("mqttPort").toInt(); | ||||
|  |         mEep->write(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); | ||||
|  |         mEep->write(ADDR_MQTT_PORT, mqttPort); | ||||
|  |         mEep->write(ADDR_MQTT_USER, mqttUser, MQTT_USER_LEN); | ||||
|  |         mEep->write(ADDR_MQTT_PWD,  mqttPwd,  MQTT_PWD_LEN); | ||||
|  |         mEep->write(ADDR_MQTT_TOPIC, mqttTopic, MQTT_TOPIC_LEN); | ||||
|  |         mEep->write(ADDR_MQTT_INTERVAL, interval); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         // serial console
 | ||||
|  |         bool tmp; | ||||
|  |         interval = mWeb->arg("serIntvl").toInt(); | ||||
|  |         mEep->write(ADDR_SER_INTERVAL, interval); | ||||
|  |         tmp = (mWeb->arg("serEn") == "on"); | ||||
|  |         mEep->write(ADDR_SER_ENABLE, (uint8_t)((tmp) ? 0x01 : 0x00)); | ||||
|  |         tmp = (mWeb->arg("serDbg") == "on"); | ||||
|  |         mEep->write(ADDR_SER_DEBUG, (uint8_t)((tmp) ? 0x01 : 0x00)); | ||||
|  | 
 | ||||
|  |         updateCrc(); | ||||
|  |         if((mWeb->arg("reboot") == "on")) | ||||
|  |             showReboot(); | ||||
|  |         else { | ||||
|  |             mShowRebootRequest = true; | ||||
|  |             mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"3; URL=/setup\"></head><body>" | ||||
|  |                 "<p>saved</p></body></html>"); | ||||
|  |         } | ||||
|  |     } | ||||
|  |     else { | ||||
|  |         mWeb->send(200, "text/html", "<!doctype html><html><head><title>Error</title><meta http-equiv=\"refresh\" content=\"3; URL=/setup\"></head><body>" | ||||
|  |             "<p>Error while saving</p></body></html>"); | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void app::updateCrc(void) { | ||||
|  |     Main::updateCrc(); | ||||
|  | 
 | ||||
|  |     uint16_t crc; | ||||
|  |     crc = buildEEpCrc(ADDR_START_SETTINGS, (ADDR_NEXT - ADDR_START_SETTINGS)); | ||||
|  |     //DPRINTLN("new CRC: " + String(crc, HEX));
 | ||||
|  |     mEep->write(ADDR_SETTINGS_CRC, crc); | ||||
|  | } | ||||
| @ -0,0 +1,94 @@ | |||||
|  | #ifndef __APP_H__ | ||||
|  | #define __APP_H__ | ||||
|  | 
 | ||||
|  | #include <RF24.h> | ||||
|  | #include <RF24_config.h> | ||||
|  | 
 | ||||
|  | #include "defines.h" | ||||
|  | #include "main.h" | ||||
|  | 
 | ||||
|  | #include "CircularBuffer.h" | ||||
|  | #include "hmSystem.h" | ||||
|  | #include "mqtt.h" | ||||
|  | 
 | ||||
|  | typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType; | ||||
|  | typedef HmRadio<RF24_CE_PIN, RF24_CS_PIN, RF24_IRQ_PIN, BufferType> RadioType; | ||||
|  | typedef Inverter<float> InverterType; | ||||
|  | typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType; | ||||
|  | 
 | ||||
|  | const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)", | ||||
|  |                                 "D2 (GPIO4)", "D1 (GPIO5)", "GPIO6", "GPIO7", "GPIO8", | ||||
|  |                                 "GPIO9", "GPIO10", "GPIO11", "D6 (GPIO12)", "D7 (GPIO13)", | ||||
|  |                                 "D5 (GPIO14)", "D8 (GPIO15)", "D0 (GPIO16)"}; | ||||
|  | const char* const pinNames[] = {"CS", "CE", "IRQ"}; | ||||
|  | const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; | ||||
|  | 
 | ||||
|  | const uint8_t dbgCmds[] = {0x01, 0x02, 0x03, 0x81, 0x82, 0x83, 0x84}; | ||||
|  | #define DBG_CMD_LIST_LEN    7 | ||||
|  | 
 | ||||
|  | class app : public Main { | ||||
|  |     public: | ||||
|  |         app(); | ||||
|  |         ~app(); | ||||
|  | 
 | ||||
|  |         void setup(uint32_t timeout); | ||||
|  |         void loop(void); | ||||
|  |         void handleIntr(void); | ||||
|  | 
 | ||||
|  |         uint8_t getIrqPin(void) { | ||||
|  |             return mSys->Radio.pinIrq; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         void showIndex(void); | ||||
|  |         void showSetup(void); | ||||
|  |         void showSave(void); | ||||
|  |         void showErase(void); | ||||
|  |         void showStatistics(void); | ||||
|  |         void showHoymiles(void); | ||||
|  |         void showLiveData(void); | ||||
|  | 
 | ||||
|  |         void saveValues(bool webSend); | ||||
|  |         void updateCrc(void); | ||||
|  | 
 | ||||
|  |         uint64_t Serial2u64(const char *val) { | ||||
|  |             char tmp[3] = {0}; | ||||
|  |             uint64_t ret = 0ULL; | ||||
|  |             uint64_t u64; | ||||
|  |             for(uint8_t i = 0; i < 6; i++) { | ||||
|  |                 tmp[0] = val[i*2]; | ||||
|  |                 tmp[1] = val[i*2 + 1]; | ||||
|  |                 if((tmp[0] == '\0') || (tmp[1] == '\0')) | ||||
|  |                     break; | ||||
|  |                 u64 = strtol(tmp, NULL, 16); | ||||
|  |                 ret |= (u64 << ((5-i) << 3)); | ||||
|  |             } | ||||
|  |             return ret; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         bool mShowRebootRequest; | ||||
|  | 
 | ||||
|  |         HmSystemType *mSys; | ||||
|  | 
 | ||||
|  |         uint16_t mSendTicker; | ||||
|  |         uint16_t mSendInterval; | ||||
|  | 
 | ||||
|  |         uint32_t mCmds[DBG_CMD_LIST_LEN+1]; | ||||
|  |         //uint32_t mChannelStat[4];
 | ||||
|  |         uint32_t mRecCnt; | ||||
|  | 
 | ||||
|  |         // timer
 | ||||
|  |         uint32_t mTicker; | ||||
|  |         bool mSerialValues; | ||||
|  |         bool mSerialDebug; | ||||
|  | 
 | ||||
|  |         // mqtt
 | ||||
|  |         mqtt mMqtt; | ||||
|  |         uint16_t mMqttTicker; | ||||
|  |         uint16_t mMqttInterval; | ||||
|  |         bool mMqttActive; | ||||
|  |         uint16_t mSerialTicker; | ||||
|  |         uint16_t mSerialInterval; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__APP_H__*/ | ||||
| @ -0,0 +1,44 @@ | |||||
|  | #ifndef __CONFIG_H__ | ||||
|  | #define __CONFIG_H__ | ||||
|  | 
 | ||||
|  | // fallback WiFi info
 | ||||
|  | #define FB_WIFI_SSID    "YOUR_WIFI_SSID" | ||||
|  | #define FB_WIFI_PWD     "YOUR_WIFI_PWD" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // access point info
 | ||||
|  | #define WIFI_AP_SSID    "AHOY DTU" | ||||
|  | #define WIFI_AP_PWD     "esp_8266" | ||||
|  | // stay in access point mode all the time
 | ||||
|  | //#define AP_ONLY
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // CONFIGURATION - COMPILE TIME
 | ||||
|  | //-------------------------------------
 | ||||
|  | // time in seconds how long the station info (ssid + pwd) will be tried
 | ||||
|  | #define WIFI_TRY_CONNECT_TIME   15 | ||||
|  | 
 | ||||
|  | // time during the ESP will act as access point on connection failure (to
 | ||||
|  | // station) in seconds
 | ||||
|  | #define WIFI_AP_ACTIVE_TIME     3*60 | ||||
|  | 
 | ||||
|  | // default device name
 | ||||
|  | #define DEF_DEVICE_NAME         "ESP-DTU" | ||||
|  | 
 | ||||
|  | // number of packets hold in buffer
 | ||||
|  | #define PACKET_BUFFER_SIZE      30 | ||||
|  | 
 | ||||
|  | // number of configurable inverters
 | ||||
|  | #define MAX_NUM_INVERTERS       3 | ||||
|  | 
 | ||||
|  | // maximum human readable inverter name length
 | ||||
|  | #define MAX_NAME_LENGTH         16 | ||||
|  | 
 | ||||
|  | // maximum buffer length of packet received / sent to RF24 module
 | ||||
|  | #define MAX_RF_PAYLOAD_SIZE     64 | ||||
|  | 
 | ||||
|  | // changes the style of "/setup" page, visualized = nicer
 | ||||
|  | #define LIVEDATA_VISUALIZED | ||||
|  | 
 | ||||
|  | #endif /*__CONFIG_H__*/ | ||||
| @ -0,0 +1,43 @@ | |||||
|  | #include "crc.h" | ||||
|  | 
 | ||||
|  | uint8_t crc8(uint8_t buf[], uint8_t len) { | ||||
|  |     uint8_t crc = CRC8_INIT; | ||||
|  |     for(uint8_t i = 0; i < len; i++) { | ||||
|  |         crc ^= buf[i]; | ||||
|  |         for(uint8_t b = 0; b < 8; b ++) { | ||||
|  |             crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); | ||||
|  |         } | ||||
|  |     } | ||||
|  |     return crc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | uint16_t crc16(uint8_t buf[], uint8_t len) { | ||||
|  |     uint16_t crc = 0xffff; | ||||
|  |     uint8_t shift = 0; | ||||
|  | 
 | ||||
|  |     for(uint8_t i = 0; i < len; i ++) { | ||||
|  |         crc = crc ^ buf[i]; | ||||
|  |         for(uint8_t bit = 0; bit < 8; bit ++) { | ||||
|  |             shift = (crc & 0x0001); | ||||
|  |             crc = crc >> 1; | ||||
|  |             if(shift != 0) | ||||
|  |                 crc = crc ^ 0xA001; | ||||
|  |         } | ||||
|  |     } | ||||
|  |     return crc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit, uint16_t crcIn) { | ||||
|  |     uint16_t crc = crcIn; | ||||
|  |     uint8_t idx, val = buf[(startBit >> 3)]; | ||||
|  | 
 | ||||
|  |     for(uint16_t bit = startBit; bit < lenBits; bit ++) { | ||||
|  |         idx = bit & 0x07; | ||||
|  |         if(0 == idx) | ||||
|  |             val = buf[(bit >> 3)]; | ||||
|  |         crc ^= 0x8000 & (val << (8 + idx)); | ||||
|  |         crc = (crc & 0x8000) ? ((crc << 1) ^ CRC16_NRF24_POLYNOM) : (crc << 1); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return crc; | ||||
|  | } | ||||
| @ -0,0 +1,16 @@ | |||||
|  | #ifndef __CRC_H__ | ||||
|  | #define __CRC_H__ | ||||
|  | 
 | ||||
|  | #include <cstdint> | ||||
|  | 
 | ||||
|  | #define CRC8_INIT               0x00 | ||||
|  | #define CRC8_POLY               0x01 | ||||
|  | 
 | ||||
|  | #define CRC16_MODBUS_POLYNOM    0xA001 | ||||
|  | #define CRC16_NRF24_POLYNOM     0x1021 | ||||
|  | 
 | ||||
|  | uint8_t crc8(uint8_t buf[], uint8_t len); | ||||
|  | uint16_t crc16(uint8_t buf[], uint8_t len); | ||||
|  | uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff); | ||||
|  | 
 | ||||
|  | #endif /*__CRC_H__*/ | ||||
| @ -0,0 +1,39 @@ | |||||
|  | #ifndef __DEBUG_H__ | ||||
|  | #define __DEBUG_H__ | ||||
|  | 
 | ||||
|  | #ifdef NDEBUG | ||||
|  |   #define DPRINT(str) | ||||
|  |   #define DPRINTLN(str) | ||||
|  | #else | ||||
|  | 
 | ||||
|  | #ifndef DSERIAL | ||||
|  |   #define DSERIAL Serial | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |   template <class T> | ||||
|  |   inline void DPRINT(T str) { DSERIAL.print(str); } | ||||
|  |   template <class T> | ||||
|  |   inline void DPRINTLN(T str) { DPRINT(str); DPRINT(F("\r\n")); } | ||||
|  |   inline void DHEX(uint8_t b) { | ||||
|  |     if( b<0x10 ) DSERIAL.print('0'); | ||||
|  |     DSERIAL.print(b,HEX); | ||||
|  |   } | ||||
|  |   inline void DHEX(uint16_t b) { | ||||
|  |     if( b<0x10 ) DSERIAL.print(F("000")); | ||||
|  |     else if( b<0x100 ) DSERIAL.print(F("00")); | ||||
|  |     else if( b<0x1000 ) DSERIAL.print(F("0")); | ||||
|  |     DSERIAL.print(b,HEX); | ||||
|  |   } | ||||
|  |   inline void DHEX(uint32_t b) { | ||||
|  |     if( b<0x10 ) DSERIAL.print(F("0000000")); | ||||
|  |     else if( b<0x100 ) DSERIAL.print(F("000000")); | ||||
|  |     else if( b<0x1000 ) DSERIAL.print(F("00000")); | ||||
|  |     else if( b<0x10000 ) DSERIAL.print(F("0000")); | ||||
|  |     else if( b<0x100000 ) DSERIAL.print(F("000")); | ||||
|  |     else if( b<0x1000000 ) DSERIAL.print(F("00")); | ||||
|  |     else if( b<0x10000000 ) DSERIAL.print(F("0")); | ||||
|  |     DSERIAL.print(b,HEX); | ||||
|  |   } | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif /*__DEBUG_H__*/ | ||||
| @ -0,0 +1,92 @@ | |||||
|  | #ifndef __DEFINES_H__ | ||||
|  | #define __DEFINES_H__ | ||||
|  | 
 | ||||
|  | #include "config.h" | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // PINOUT (Default, can be changed in setup)
 | ||||
|  | //-------------------------------------
 | ||||
|  | #define RF24_CS_PIN         15 | ||||
|  | #define RF24_CE_PIN         2 | ||||
|  | #define RF24_IRQ_PIN        0 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // VERSION
 | ||||
|  | //-------------------------------------
 | ||||
|  | #define VERSION_MAJOR       0 | ||||
|  | #define VERSION_MINOR       3 | ||||
|  | #define VERSION_PATCH       6 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | typedef struct { | ||||
|  |     uint8_t sendCh; | ||||
|  |     uint8_t packet[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | } packet_t; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // EEPROM
 | ||||
|  | //-------------------------------------
 | ||||
|  | #define SSID_LEN            32 | ||||
|  | #define PWD_LEN             63 | ||||
|  | #define DEVNAME_LEN         16 | ||||
|  | #define CRC_LEN             2 // uint16_t
 | ||||
|  | 
 | ||||
|  | #define INV_ADDR_LEN        MAX_NUM_INVERTERS * 8                // uint64_t
 | ||||
|  | #define INV_NAME_LEN        MAX_NUM_INVERTERS * MAX_NAME_LENGTH  // char[]
 | ||||
|  | #define INV_TYPE_LEN        MAX_NUM_INVERTERS * 1                // uint8_t
 | ||||
|  | #define INV_INTERVAL_LEN    2                                    // uint16_t
 | ||||
|  | 
 | ||||
|  | #define PINOUT_LEN          3 // 3 pins: CS, CE, IRQ
 | ||||
|  | 
 | ||||
|  | #define RF24_AMP_PWR_LEN    1 | ||||
|  | 
 | ||||
|  | #define MQTT_ADDR_LEN       4 // IP
 | ||||
|  | #define MQTT_USER_LEN       16 | ||||
|  | #define MQTT_PWD_LEN        32 | ||||
|  | #define MQTT_TOPIC_LEN      32 | ||||
|  | #define MQTT_INTERVAL_LEN   2 // uint16_t
 | ||||
|  | #define MQTT_PORT_LEN       2 // uint16_t
 | ||||
|  | 
 | ||||
|  | #define SER_ENABLE_LEN      1 // uint8_t
 | ||||
|  | #define SER_DEBUG_LEN       1 // uint8_t
 | ||||
|  | #define SER_INTERVAL_LEN    2 // uint16_t
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #define ADDR_START          0 | ||||
|  | #define ADDR_SSID           ADDR_START | ||||
|  | #define ADDR_PWD            ADDR_SSID          + SSID_LEN | ||||
|  | #define ADDR_DEVNAME        ADDR_PWD           + PWD_LEN | ||||
|  | #define ADDR_WIFI_CRC       ADDR_DEVNAME       + DEVNAME_LEN | ||||
|  | #define ADDR_START_SETTINGS ADDR_WIFI_CRC      + CRC_LEN | ||||
|  | 
 | ||||
|  | #define ADDR_PINOUT         ADDR_START_SETTINGS | ||||
|  | 
 | ||||
|  | #define ADDR_RF24_AMP_PWR   ADDR_PINOUT        + PINOUT_LEN | ||||
|  | 
 | ||||
|  | #define ADDR_INV_ADDR       ADDR_RF24_AMP_PWR  + RF24_AMP_PWR_LEN | ||||
|  | #define ADDR_INV_NAME       ADDR_INV_ADDR      + INV_ADDR_LEN | ||||
|  | #define ADDR_INV_TYPE       ADDR_INV_NAME      + INV_NAME_LEN | ||||
|  | #define ADDR_INV_INTERVAL   ADDR_INV_TYPE      + INV_TYPE_LEN | ||||
|  | 
 | ||||
|  | #define ADDR_MQTT_ADDR      ADDR_INV_INTERVAL  + INV_INTERVAL_LEN | ||||
|  | #define ADDR_MQTT_USER      ADDR_MQTT_ADDR     + MQTT_ADDR_LEN | ||||
|  | #define ADDR_MQTT_PWD       ADDR_MQTT_USER     + MQTT_USER_LEN | ||||
|  | #define ADDR_MQTT_TOPIC     ADDR_MQTT_PWD      + MQTT_PWD_LEN | ||||
|  | #define ADDR_MQTT_INTERVAL  ADDR_MQTT_TOPIC    + MQTT_TOPIC_LEN | ||||
|  | #define ADDR_MQTT_PORT      ADDR_MQTT_INTERVAL + MQTT_INTERVAL_LEN | ||||
|  | 
 | ||||
|  | #define ADDR_SER_ENABLE     ADDR_MQTT_PORT     + MQTT_PORT_LEN | ||||
|  | #define ADDR_SER_DEBUG      ADDR_SER_ENABLE    + SER_ENABLE_LEN | ||||
|  | #define ADDR_SER_INTERVAL   ADDR_SER_DEBUG     + SER_DEBUG_LEN | ||||
|  | #define ADDR_NEXT           ADDR_SER_INTERVAL  + SER_INTERVAL_LEN | ||||
|  | 
 | ||||
|  | #define ADDR_SETTINGS_CRC   400 | ||||
|  | 
 | ||||
|  | #if(ADDR_SETTINGS_CRC <= ADDR_NEXT) | ||||
|  | #error address overlap! | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #endif /*__DEFINES_H__*/ | ||||
| @ -0,0 +1,133 @@ | |||||
|  | #ifndef __EEP_H__ | ||||
|  | #define __EEP_H__ | ||||
|  | 
 | ||||
|  | #include "Arduino.h" | ||||
|  | #include <EEPROM.h> | ||||
|  | 
 | ||||
|  | class eep { | ||||
|  |     public: | ||||
|  |         eep() { | ||||
|  |             EEPROM.begin(500); | ||||
|  |         } | ||||
|  |         ~eep() { | ||||
|  |             EEPROM.end(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, char *str, uint8_t length) { | ||||
|  |             for(uint8_t i = 0; i < length; i ++) { | ||||
|  |                 *(str++) = (char)EEPROM.read(addr++); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, float *value) { | ||||
|  |             uint8_t *p = (uint8_t*)value; | ||||
|  |             for(uint8_t i = 0; i < 4; i ++) { | ||||
|  |                 *(p++) = (uint8_t)EEPROM.read(addr++); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, bool *value) { | ||||
|  |             uint8_t intVal = 0x00; | ||||
|  |             intVal = EEPROM.read(addr++); | ||||
|  |             *value = (intVal == 0x01); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, uint8_t *value) { | ||||
|  |             *value = (EEPROM.read(addr++)); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, uint8_t data[], uint16_t length) { | ||||
|  |             for(uint16_t i = 0; i < length; i ++) { | ||||
|  |                 *(data++) = EEPROM.read(addr++); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, uint16_t *value) { | ||||
|  |             *value  = (EEPROM.read(addr++) << 8); | ||||
|  |             *value |= (EEPROM.read(addr++)); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, uint32_t *value) { | ||||
|  |             *value  = (EEPROM.read(addr++) << 24); | ||||
|  |             *value |= (EEPROM.read(addr++) << 16); | ||||
|  |             *value |= (EEPROM.read(addr++) <<  8); | ||||
|  |             *value |= (EEPROM.read(addr++)); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void read(uint32_t addr, uint64_t *value) { | ||||
|  |             read(addr, (uint32_t *)value); | ||||
|  |             *value <<= 32; | ||||
|  |             uint32_t tmp; | ||||
|  |             read(addr+4, &tmp); | ||||
|  |             *value |= tmp; | ||||
|  |             /**value  = (EEPROM.read(addr++) << 56);
 | ||||
|  |             *value |= (EEPROM.read(addr++) << 48); | ||||
|  |             *value |= (EEPROM.read(addr++) << 40); | ||||
|  |             *value |= (EEPROM.read(addr++) << 32); | ||||
|  |             *value |= (EEPROM.read(addr++) << 24); | ||||
|  |             *value |= (EEPROM.read(addr++) << 16); | ||||
|  |             *value |= (EEPROM.read(addr++) <<  8); | ||||
|  |             *value |= (EEPROM.read(addr++));*/ | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, const char *str, uint8_t length) { | ||||
|  |             for(uint8_t i = 0; i < length; i ++) { | ||||
|  |                 EEPROM.write(addr++, str[i]); | ||||
|  |             } | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, uint8_t data[], uint16_t length) { | ||||
|  |             for(uint16_t i = 0; i < length; i ++) { | ||||
|  |                 EEPROM.write(addr++, data[i]); | ||||
|  |             } | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, float value) { | ||||
|  |             uint8_t *p = (uint8_t*)&value; | ||||
|  |             for(uint8_t i = 0; i < 4; i ++) { | ||||
|  |                 EEPROM.write(addr++, p[i]); | ||||
|  |             } | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, bool value) { | ||||
|  |             uint8_t intVal = (value) ? 0x01 : 0x00; | ||||
|  |             EEPROM.write(addr++, intVal); | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, uint8_t value) { | ||||
|  |             EEPROM.write(addr++, value); | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, uint16_t value) { | ||||
|  |             EEPROM.write(addr++, (value >> 8) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value     ) & 0xff); | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint32_t addr, uint32_t value) { | ||||
|  |             EEPROM.write(addr++, (value >> 24) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 16) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >>  8) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value      ) & 0xff); | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void write(uint64_t addr, uint64_t value) { | ||||
|  |             EEPROM.write(addr++, (value >> 56) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 48) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 40) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 32) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 24) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >> 16) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value >>  8) & 0xff); | ||||
|  |             EEPROM.write(addr++, (value      ) & 0xff); | ||||
|  |             EEPROM.commit(); | ||||
|  |         } | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__EEP_H__*/ | ||||
| @ -0,0 +1,33 @@ | |||||
|  | 
 | ||||
|  | #include "Arduino.h" | ||||
|  | 
 | ||||
|  | #include <ESP8266WiFi.h> | ||||
|  | #include <DNSServer.h> | ||||
|  | #include <ESP8266WebServer.h> | ||||
|  | #include <Ticker.h> | ||||
|  | 
 | ||||
|  | #include <ESP8266HTTPUpdateServer.h> | ||||
|  | #include "app.h" | ||||
|  | #include "config.h" | ||||
|  | 
 | ||||
|  | app myApp; | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void setup() { | ||||
|  |     myApp.setup(WIFI_TRY_CONNECT_TIME); | ||||
|  | 
 | ||||
|  |     // TODO: move to HmRadio
 | ||||
|  |     attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void loop() { | ||||
|  |     myApp.loop(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | ICACHE_RAM_ATTR void handleIntr(void) { | ||||
|  |     myApp.handleIntr(); | ||||
|  | } | ||||
| @ -0,0 +1,159 @@ | |||||
|  | #ifndef __HM_DEFINES_H__ | ||||
|  | #define __HM_DEFINES_H__ | ||||
|  | 
 | ||||
|  | #include "debug.h" | ||||
|  | #include <cstdint> | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | union serial_u { | ||||
|  |     uint64_t u64; | ||||
|  |     uint8_t  b[8]; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // units
 | ||||
|  | enum {UNIT_V = 0, UNIT_A, UNIT_W,  UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT}; | ||||
|  | const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%"}; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // field types
 | ||||
|  | enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, | ||||
|  |         FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT}; | ||||
|  | const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", | ||||
|  |         "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct"}; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // indices to calculation functions, defined in hmInverter.h
 | ||||
|  | enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH}; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // CH0 is default channel (freq, ac, temp)
 | ||||
|  | enum {CH0 = 0, CH1, CH2, CH3, CH4}; | ||||
|  | // received command ids, special command CMDFF for calculations
 | ||||
|  | enum {CMD01 = 0x01, CMD02, CMD03, CMD82 = 0x82, CMD83, CMD84, CMDFF=0xff}; | ||||
|  | 
 | ||||
|  | enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400, INV_TYPE_HM800}; | ||||
|  | const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400", "HM800"}; | ||||
|  | #define NUM_INVERTER_TYPES   4 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | typedef struct { | ||||
|  |     uint8_t    fieldId; // field id
 | ||||
|  |     uint8_t    unitId;  // uint id
 | ||||
|  |     uint8_t    ch;      // channel 0 - 3
 | ||||
|  |     uint8_t    cmdId;   // received command id
 | ||||
|  |     uint8_t    start;   // pos of first byte in buffer
 | ||||
|  |     uint8_t    num;     // number of bytes in buffer
 | ||||
|  |     uint16_t   div;     // divisor
 | ||||
|  | } byteAssign_t; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | /**
 | ||||
|  |  *  indices are built for the buffer starting with cmd-id in first byte | ||||
|  |  *  (complete payload in buffer) | ||||
|  |  * */ | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // HM400 HM350?, HM300?
 | ||||
|  | //-------------------------------------
 | ||||
|  | const byteAssign_t hm400assignment[] = { | ||||
|  |     { FLD_UDC, UNIT_V,   CH1, CMD01,  3, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH1, CMD01,  5, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH1, CMD01,  7, 2, 10   }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH1, CMD01,  9, 4, 1000 }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH1, CMD01, 13, 2, 1    }, | ||||
|  |     { FLD_UAC, UNIT_V,   CH0, CMD01, 15, 2, 10   }, | ||||
|  |     { FLD_F,   UNIT_HZ,  CH0, CMD82,  1, 2, 100  }, | ||||
|  |     { FLD_PAC, UNIT_W,   CH0, CMD82,  3, 2, 10   }, | ||||
|  |     { FLD_IAC, UNIT_A,   CH0, CMD82,  7, 2, 100  }, | ||||
|  |     { FLD_T,   UNIT_C,   CH0, CMD82, 11, 2, 10   } | ||||
|  | }; | ||||
|  | #define HM400_LIST_LEN     (sizeof(hm400assignment) / sizeof(byteAssign_t)) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // HM600, HM700
 | ||||
|  | //-------------------------------------
 | ||||
|  | const byteAssign_t hm600assignment[] = { | ||||
|  |     { FLD_UDC, UNIT_V,   CH1, CMD01,  3, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH1, CMD01,  5, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH1, CMD01,  7, 2, 10   }, | ||||
|  |     { FLD_UDC, UNIT_V,   CH2, CMD01,  9, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH2, CMD01, 11, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH2, CMD01, 13, 2, 10   }, | ||||
|  |     { FLD_YW,  UNIT_WH,  CH0, CMD02,  1, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH0, CMD02,  3, 4, 1000 }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH1, CMD02,  7, 2, 1    }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH2, CMD02,  9, 2, 1    }, | ||||
|  |     { FLD_UAC, UNIT_V,   CH0, CMD02, 11, 2, 10   }, | ||||
|  |     { FLD_F,   UNIT_HZ,  CH0, CMD02, 13, 2, 100  }, | ||||
|  |     { FLD_PAC, UNIT_W,   CH0, CMD02, 15, 2, 10   }, | ||||
|  |     { FLD_IAC, UNIT_A,   CH0, CMD83,  3, 2, 100  }, | ||||
|  |     { FLD_T,   UNIT_C,   CH0, CMD83,  7, 2, 10   } | ||||
|  | }; | ||||
|  | #define HM600_LIST_LEN     (sizeof(hm600assignment) / sizeof(byteAssign_t)) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // HM800
 | ||||
|  | //-------------------------------------
 | ||||
|  | const byteAssign_t hm800assignment[] = { | ||||
|  | 
 | ||||
|  |     { FLD_UDC, UNIT_V,   CH1, CMD01,  3, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH1, CMD01,  5, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH1, CMD01,  7, 2, 10   }, | ||||
|  |     { FLD_UDC, UNIT_V,   CH2, CMD01,  9, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH2, CMD01, 11, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH2, CMD01, 13, 2, 10   }, | ||||
|  |     { FLD_YW,  UNIT_WH,  CH0, CMD02,  1, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH0, CMD02,  3, 4, 1000 }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH1, CMD02,  7, 2, 1    }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH2, CMD02,  9, 2, 1    }, | ||||
|  |     { FLD_UAC, UNIT_V,   CH0, CMD02, 11, 2, 10   }, | ||||
|  |     { FLD_F,   UNIT_HZ,  CH0, CMD02, 13, 2, 100  }, | ||||
|  |     { FLD_PAC, UNIT_W,   CH0, CMD02, 15, 2, 10   }, | ||||
|  |     { FLD_IAC, UNIT_A,   CH0, CMD83,  3, 2, 100  }, | ||||
|  |     { FLD_T,   UNIT_C,   CH0, CMD83,  7, 2, 10   } | ||||
|  | }; | ||||
|  | #define HM800_LIST_LEN     (sizeof(hm800assignment) / sizeof(byteAssign_t)) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-------------------------------------
 | ||||
|  | // HM1200, HM1500
 | ||||
|  | //-------------------------------------
 | ||||
|  | const byteAssign_t hm1200assignment[] = { | ||||
|  |     { FLD_UDC, UNIT_V,   CH1, CMD01,  3, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH1, CMD01,  5, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH1, CMD01,  9, 2, 10   }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH1, CMD02,  5, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH1, CMD01, 13, 4, 1000 }, | ||||
|  |     { FLD_UDC, UNIT_V,   CH3, CMD02,  9, 2, 10   }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH2, CMD01,  7, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH2, CMD01, 11, 2, 10   }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH2, CMD02,  7, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH2, CMD02,  1, 4, 1000 }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH3, CMD02, 11, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH3, CMD02, 15, 2, 10   }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH3, CMD03, 11, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH3, CMD03,  3, 4, 1000 }, | ||||
|  |     { FLD_IDC, UNIT_A,   CH4, CMD02, 13, 2, 100  }, | ||||
|  |     { FLD_PDC, UNIT_W,   CH4, CMD03,  1, 2, 10   }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH4, CMD03, 13, 2, 1    }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH4, CMD03,  7, 4, 1000 }, | ||||
|  |     { FLD_UAC, UNIT_V,   CH0, CMD03, 15, 2, 10   }, | ||||
|  |     { FLD_IAC, UNIT_A,   CH0, CMD84,  7, 2, 100  }, | ||||
|  |     { FLD_PAC, UNIT_W,   CH0, CMD84,  3, 2, 10   }, | ||||
|  |     { FLD_F,   UNIT_HZ,  CH0, CMD84,  1, 2, 100  }, | ||||
|  |     { FLD_PCT, UNIT_PCT, CH0, CMD84,  9, 2, 10   }, | ||||
|  |     { FLD_T,   UNIT_C,   CH0, CMD84, 11, 2, 10   }, | ||||
|  |     { FLD_YD,  UNIT_WH,  CH0, CMDFF, CALC_YD_CH0,   0, 0 }, | ||||
|  |     { FLD_YT,  UNIT_KWH, CH0, CMDFF, CALC_YT_CH0,   0, 0 }, | ||||
|  |     { FLD_UDC, UNIT_V,   CH2, CMDFF, CALC_UDC_CH, CH1, 0 }, | ||||
|  |     { FLD_UDC, UNIT_V,   CH4, CMDFF, CALC_UDC_CH, CH3, 0 } | ||||
|  | }; | ||||
|  | #define HM1200_LIST_LEN     (sizeof(hm1200assignment) / sizeof(byteAssign_t)) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif /*__HM_DEFINES_H__*/ | ||||
| @ -0,0 +1,213 @@ | |||||
|  | #ifndef __HM_INVERTER_H__ | ||||
|  | #define __HM_INVERTER_H__ | ||||
|  | 
 | ||||
|  | #include "hmDefines.h" | ||||
|  | 
 | ||||
|  | /**
 | ||||
|  |  * For values which are of interest and not transmitted by the inverter can be | ||||
|  |  * calculated automatically. | ||||
|  |  * A list of functions can be linked to the assignment and will be executed | ||||
|  |  * automatically. Their result does not differ from original read values. | ||||
|  |  * The special command 0xff (CMDFF) must be used. | ||||
|  |  */ | ||||
|  | 
 | ||||
|  | // forward declaration of class
 | ||||
|  | template <class RECORDTYPE=float> | ||||
|  | class Inverter; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // prototypes
 | ||||
|  | template<class T=float> | ||||
|  | static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0); | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0); | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | static T calcUdcCh(Inverter<> *iv, uint8_t arg0); | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | using func_t = T (Inverter<> *, uint8_t); | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | struct calcFunc_t { | ||||
|  |     uint8_t funcId; // unique id
 | ||||
|  |     func_t<T>*  func;   // function pointer
 | ||||
|  | } ; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | // list of all available functions, mapped in hmDefines.h
 | ||||
|  | template<class T=float> | ||||
|  | const calcFunc_t<T> calcFunctions[] = { | ||||
|  |     { CALC_YT_CH0, &calcYieldTotalCh0 }, | ||||
|  |     { CALC_YD_CH0, &calcYieldDayCh0   }, | ||||
|  |     { CALC_UDC_CH, &calcUdcCh         } | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | template <class RECORDTYPE> | ||||
|  | class Inverter { | ||||
|  |     public: | ||||
|  |         uint8_t       id;       // unique id
 | ||||
|  |         char          name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
 | ||||
|  |         uint8_t       type;     // integer which refers to inverter type
 | ||||
|  |         byteAssign_t* assign;   // type of inverter
 | ||||
|  |         uint8_t       listLen;  // length of assignments
 | ||||
|  |         serial_u      serial;   // serial number as on barcode
 | ||||
|  |         serial_u      radioId;  // id converted to modbus
 | ||||
|  |         uint8_t       channels; // number of PV channels (1-4)
 | ||||
|  |         RECORDTYPE    *record;  // pointer for values
 | ||||
|  | 
 | ||||
|  |         Inverter() { | ||||
|  | 
 | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         ~Inverter() { | ||||
|  |             // TODO: cleanup
 | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void init(void) { | ||||
|  |             getAssignment(); | ||||
|  |             toRadioId(); | ||||
|  |             record = new RECORDTYPE[listLen]; | ||||
|  |             memset(name, 0, MAX_NAME_LENGTH); | ||||
|  |             memset(record, 0, sizeof(RECORDTYPE) * listLen); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) { | ||||
|  |             uint8_t pos = 0; | ||||
|  |             for(; pos < listLen; pos++) { | ||||
|  |                 if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId)) | ||||
|  |                     break; | ||||
|  |             } | ||||
|  |             return (pos >= listLen) ? 0xff : pos; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         const char *getFieldName(uint8_t pos) { | ||||
|  |             return fields[assign[pos].fieldId]; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         const char *getUnit(uint8_t pos) { | ||||
|  |             return units[assign[pos].unitId]; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getChannel(uint8_t pos) { | ||||
|  |             return assign[pos].ch; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getCmdId(uint8_t pos) { | ||||
|  |             return assign[pos].cmdId; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void addValue(uint8_t pos, uint8_t buf[]) { | ||||
|  |             uint8_t ptr  = assign[pos].start; | ||||
|  |             uint8_t end  = ptr + assign[pos].num; | ||||
|  |             uint16_t div = assign[pos].div; | ||||
|  | 
 | ||||
|  |             uint32_t val = 0; | ||||
|  |             do { | ||||
|  |                 val <<= 8; | ||||
|  |                 val |= buf[ptr]; | ||||
|  |             } while(++ptr != end); | ||||
|  | 
 | ||||
|  |             record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         RECORDTYPE getValue(uint8_t pos) { | ||||
|  |             return record[pos]; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void doCalculations(void) { | ||||
|  |             for(uint8_t i = 0; i < listLen; i++) { | ||||
|  |                 if(CMDFF == assign[i].cmdId) { | ||||
|  |                     record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num); | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         void toRadioId(void) { | ||||
|  |             radioId.u64  = 0ULL; | ||||
|  |             radioId.b[4] = serial.b[0]; | ||||
|  |             radioId.b[3] = serial.b[1]; | ||||
|  |             radioId.b[2] = serial.b[2]; | ||||
|  |             radioId.b[1] = serial.b[3]; | ||||
|  |             radioId.b[0] = 0x01; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void getAssignment(void) { | ||||
|  |             if(INV_TYPE_HM400 == type) { | ||||
|  |                 listLen  = (uint8_t)(HM400_LIST_LEN); | ||||
|  |                 assign   = (byteAssign_t*)hm400assignment; | ||||
|  |                 channels = 1; | ||||
|  |             } | ||||
|  |             else if(INV_TYPE_HM600 == type) { | ||||
|  |                 listLen  = (uint8_t)(HM600_LIST_LEN); | ||||
|  |                 assign   = (byteAssign_t*)hm600assignment; | ||||
|  |                 channels = 2; | ||||
|  |             } | ||||
|  |             else if(INV_TYPE_HM800 == type) { | ||||
|  |                 listLen  = (uint8_t)(HM800_LIST_LEN); | ||||
|  |                 assign   = (byteAssign_t*)hm800assignment; | ||||
|  |                 channels = 2; | ||||
|  |             } | ||||
|  |             else if(INV_TYPE_HM1200 == type) { | ||||
|  |                 listLen  = (uint8_t)(HM1200_LIST_LEN); | ||||
|  |                 assign   = (byteAssign_t*)hm1200assignment; | ||||
|  |                 channels = 4; | ||||
|  |             } | ||||
|  |             else { | ||||
|  |                 listLen  = 0; | ||||
|  |                 channels = 0; | ||||
|  |                 assign   = NULL; | ||||
|  |             } | ||||
|  |         } | ||||
|  | }; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | /**
 | ||||
|  |  * To calculate values which are not transmitted by the unit there is a generic | ||||
|  |  * list of functions which can be linked to the assignment. | ||||
|  |  * The special command 0xff (CMDFF) must be used. | ||||
|  |  */ | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) { | ||||
|  |     if(NULL != iv) { | ||||
|  |         T yield = 0; | ||||
|  |         for(uint8_t i = 1; i <= iv->channels; i++) { | ||||
|  |             uint8_t pos = iv->getPosByChFld(i, FLD_YT); | ||||
|  |             yield += iv->getValue(pos); | ||||
|  |         } | ||||
|  |         return yield; | ||||
|  |     } | ||||
|  |     return 0.0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) { | ||||
|  |     if(NULL != iv) { | ||||
|  |         T yield = 0; | ||||
|  |         for(uint8_t i = 1; i <= iv->channels; i++) { | ||||
|  |             uint8_t pos = iv->getPosByChFld(i, FLD_YD); | ||||
|  |             yield += iv->getValue(pos); | ||||
|  |         } | ||||
|  |         return yield; | ||||
|  |     } | ||||
|  |     return 0.0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | template<class T=float> | ||||
|  | static T calcUdcCh(Inverter<> *iv, uint8_t arg0) { | ||||
|  |     // arg0 = channel of source
 | ||||
|  |     for(uint8_t i = 0; i < iv->listLen; i++) { | ||||
|  |         if((FLD_UDC == iv->assign[i].fieldId) && (arg0 == iv->assign[i].ch)) { | ||||
|  |             return iv->getValue(i); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return 0.0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | #endif /*__HM_INVERTER_H__*/ | ||||
| @ -0,0 +1,272 @@ | |||||
|  | #ifndef __RADIO_H__ | ||||
|  | #define __RADIO_H__ | ||||
|  | 
 | ||||
|  | #include <RF24.h> | ||||
|  | #include <RF24_config.h> | ||||
|  | #include "crc.h" | ||||
|  | 
 | ||||
|  | //#define CHANNEL_HOP // switch between channels or use static channel to send
 | ||||
|  | 
 | ||||
|  | #define DEFAULT_RECV_CHANNEL    3 | ||||
|  | #define SPI_SPEED               1000000 | ||||
|  | 
 | ||||
|  | #define DTU_RADIO_ID            ((uint64_t)0x1234567801ULL) | ||||
|  | #define DUMMY_RADIO_ID          ((uint64_t)0xDEADBEEF01ULL) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | const char* const rf24AmpPower[] = {"MIN", "LOW", "HIGH", "MAX"}; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | // MACROS
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | #define CP_U32_LittleEndian(buf, v) ({ \ | ||||
|  |     uint8_t *b = buf; \ | ||||
|  |     b[0] = ((v >> 24) & 0xff); \ | ||||
|  |     b[1] = ((v >> 16) & 0xff); \ | ||||
|  |     b[2] = ((v >>  8) & 0xff); \ | ||||
|  |     b[3] = ((v      ) & 0xff); \ | ||||
|  | }) | ||||
|  | 
 | ||||
|  | #define CP_U32_BigEndian(buf, v) ({ \ | ||||
|  |     uint8_t *b = buf; \ | ||||
|  |     b[3] = ((v >> 24) & 0xff); \ | ||||
|  |     b[2] = ((v >> 16) & 0xff); \ | ||||
|  |     b[1] = ((v >>  8) & 0xff); \ | ||||
|  |     b[0] = ((v      ) & 0xff); \ | ||||
|  | }) | ||||
|  | 
 | ||||
|  | #define BIT_CNT(x)  ((x)<<3) | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | // HM Radio class
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | template <uint8_t CE_PIN, uint8_t CS_PIN, uint8_t IRQ_PIN, class BUFFER, uint64_t DTU_ID=DTU_RADIO_ID> | ||||
|  | class HmRadio { | ||||
|  |     public: | ||||
|  |         HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { | ||||
|  |             mChanOut[0] = 23; | ||||
|  |             mChanOut[1] = 40; | ||||
|  |             mChanOut[2] = 61; | ||||
|  |             mChanOut[3] = 75; | ||||
|  |             mChanIdx = 1; | ||||
|  | 
 | ||||
|  |             calcDtuCrc(); | ||||
|  | 
 | ||||
|  |             pinCs  = CS_PIN; | ||||
|  |             pinCe  = CE_PIN; | ||||
|  |             pinIrq = IRQ_PIN; | ||||
|  | 
 | ||||
|  |             AmplifierPower = 1; | ||||
|  |             mSendCnt       = 0; | ||||
|  |         } | ||||
|  |         ~HmRadio() {} | ||||
|  | 
 | ||||
|  |         void setup(BUFFER *ctrl) { | ||||
|  |             //DPRINTLN("HmRadio::setup, pins: " + String(pinCs) + ", " + String(pinCe) + ", " + String(pinIrq));
 | ||||
|  |             pinMode(pinIrq, INPUT_PULLUP); | ||||
|  | 
 | ||||
|  |             mBufCtrl = ctrl; | ||||
|  | 
 | ||||
|  |             mNrf24.begin(pinCe, pinCs); | ||||
|  |             mNrf24.setRetries(0, 0); | ||||
|  | 
 | ||||
|  |             mNrf24.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |             mNrf24.setDataRate(RF24_250KBPS); | ||||
|  |             mNrf24.disableCRC(); | ||||
|  |             mNrf24.setAutoAck(false); | ||||
|  |             mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE); | ||||
|  |             mNrf24.setAddressWidth(5); | ||||
|  |             mNrf24.openReadingPipe(1, DTU_RADIO_ID); | ||||
|  | 
 | ||||
|  |             // enable only receiving interrupts
 | ||||
|  |             mNrf24.maskIRQ(true, true, false); | ||||
|  | 
 | ||||
|  |             DPRINTLN("RF24 Amp Pwr: RF24_PA_" + String(rf24AmpPower[AmplifierPower])); | ||||
|  |             mNrf24.setPALevel(AmplifierPower & 0x03); | ||||
|  |             mNrf24.startListening(); | ||||
|  | 
 | ||||
|  |             DPRINTLN("Radio Config:"); | ||||
|  |             mNrf24.printPrettyDetails(); | ||||
|  | 
 | ||||
|  |             mSendChannel = getDefaultChannel(); | ||||
|  | 
 | ||||
|  |             if(!mNrf24.isChipConnected()) { | ||||
|  |                 DPRINTLN("WARNING! your NRF24 module can't be reached, check the wiring"); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void handleIntr(void) { | ||||
|  |             uint8_t pipe, len; | ||||
|  |             packet_t *p; | ||||
|  | 
 | ||||
|  |             DISABLE_IRQ; | ||||
|  |             while(mNrf24.available(&pipe)) { | ||||
|  |                 if(!mBufCtrl->full()) { | ||||
|  |                     p = mBufCtrl->getFront(); | ||||
|  |                     memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); | ||||
|  |                     p->sendCh = mSendChannel; | ||||
|  |                     len = mNrf24.getPayloadSize(); | ||||
|  |                     if(len > MAX_RF_PAYLOAD_SIZE) | ||||
|  |                         len = MAX_RF_PAYLOAD_SIZE; | ||||
|  | 
 | ||||
|  |                     mNrf24.read(p->packet, len); | ||||
|  |                     mBufCtrl->pushFront(p); | ||||
|  |                 } | ||||
|  |                 else { | ||||
|  |                     bool tx_ok, tx_fail, rx_ready; | ||||
|  |                     mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
 | ||||
|  |                     mNrf24.flush_rx(); // drop the packet
 | ||||
|  |                 } | ||||
|  |             } | ||||
|  |             RESTORE_IRQ; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getDefaultChannel(void) { | ||||
|  |             return mChanOut[2]; | ||||
|  |         } | ||||
|  |         uint8_t getLastChannel(void) { | ||||
|  |             return mChanOut[mChanIdx]; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getNxtChannel(void) { | ||||
|  |             if(++mChanIdx >= 4) | ||||
|  |                 mChanIdx = 0; | ||||
|  |             return mChanOut[mChanIdx]; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void sendTimePacket(uint64_t invId, uint32_t ts) { | ||||
|  |             sendCmdPacket(invId, 0x15, 0x80, false); | ||||
|  |             mSendBuf[10] = 0x0b; // cid
 | ||||
|  |             mSendBuf[11] = 0x00; | ||||
|  |             CP_U32_LittleEndian(&mSendBuf[12], ts); | ||||
|  |             mSendBuf[19] = 0x05; | ||||
|  | 
 | ||||
|  |             uint16_t crc = crc16(&mSendBuf[10], 14); | ||||
|  |             mSendBuf[24] = (crc >> 8) & 0xff; | ||||
|  |             mSendBuf[25] = (crc     ) & 0xff; | ||||
|  |             mSendBuf[26] = crc8(mSendBuf, 26); | ||||
|  | 
 | ||||
|  |             sendPacket(invId, mSendBuf, 27); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t cmd, bool calcCrc = true) { | ||||
|  |             memset(mSendBuf, 0, MAX_RF_PAYLOAD_SIZE); | ||||
|  |             mSendBuf[0] = mid; // message id
 | ||||
|  |             CP_U32_BigEndian(&mSendBuf[1], (invId  >> 8)); | ||||
|  |             CP_U32_BigEndian(&mSendBuf[5], (DTU_ID >> 8)); | ||||
|  |             mSendBuf[9]  = cmd; | ||||
|  |             if(calcCrc) { | ||||
|  |                 mSendBuf[10] = crc8(mSendBuf, 10); | ||||
|  |                 sendPacket(invId, mSendBuf, 11); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { | ||||
|  |             *len = (buf[0] >> 2); | ||||
|  |             for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { | ||||
|  |                 buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); | ||||
|  |             } | ||||
|  |             uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); | ||||
|  | 
 | ||||
|  |             bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); | ||||
|  | 
 | ||||
|  |             if(valid) { | ||||
|  |                 if(mLastCrc == crc) | ||||
|  |                     *rptCnt = (++mRptCnt); | ||||
|  |                 else { | ||||
|  |                     mRptCnt = 0; | ||||
|  |                     *rptCnt = 0; | ||||
|  |                     mLastCrc = crc; | ||||
|  |                 } | ||||
|  |             } | ||||
|  | 
 | ||||
|  |             return valid; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { | ||||
|  |             DPRINT(String(info)); | ||||
|  |             for(uint8_t i = 0; i < len; i++) { | ||||
|  |                 if(buf[i] < 10) | ||||
|  |                     DPRINT("0"); | ||||
|  |                 DHEX(buf[i]); | ||||
|  |                 DPRINT(" "); | ||||
|  |             } | ||||
|  |             DPRINTLN(""); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         bool isChipConnected(void) { | ||||
|  |             return mNrf24.isChipConnected(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t pinCs; | ||||
|  |         uint8_t pinCe; | ||||
|  |         uint8_t pinIrq; | ||||
|  | 
 | ||||
|  |         uint8_t AmplifierPower; | ||||
|  |         uint32_t mSendCnt; | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len) { | ||||
|  |             //DPRINTLN("sent packet: #" + String(mSendCnt));
 | ||||
|  |             //dumpBuf("SEN ", buf, len);
 | ||||
|  | 
 | ||||
|  |             DISABLE_IRQ; | ||||
|  |             mNrf24.stopListening(); | ||||
|  | 
 | ||||
|  |         #ifdef CHANNEL_HOP | ||||
|  |             mSendChannel = getNxtChannel(); | ||||
|  |         #else | ||||
|  |             mSendChannel = getDefaultChannel(); | ||||
|  |         #endif | ||||
|  |             mNrf24.setChannel(mSendChannel); | ||||
|  |             //DPRINTLN("CH: " + String(mSendChannel));
 | ||||
|  | 
 | ||||
|  |             mNrf24.openWritingPipe(invId); // TODO: deprecated
 | ||||
|  |             mNrf24.setCRCLength(RF24_CRC_16); | ||||
|  |             mNrf24.enableDynamicPayloads(); | ||||
|  |             mNrf24.setAutoAck(true); | ||||
|  |             mNrf24.setRetries(3, 15); | ||||
|  | 
 | ||||
|  |             mNrf24.write(buf, len); | ||||
|  | 
 | ||||
|  |             // Try to avoid zero payload acks (has no effect)
 | ||||
|  |             mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
 | ||||
|  | 
 | ||||
|  |             mNrf24.setAutoAck(false); | ||||
|  |             mNrf24.setRetries(0, 0); | ||||
|  |             mNrf24.disableDynamicPayloads(); | ||||
|  |             mNrf24.setCRCLength(RF24_CRC_DISABLED); | ||||
|  | 
 | ||||
|  |             mNrf24.setChannel(DEFAULT_RECV_CHANNEL); | ||||
|  |             mNrf24.startListening(); | ||||
|  | 
 | ||||
|  |             RESTORE_IRQ; | ||||
|  |             mSendCnt++; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void calcDtuCrc(void) { | ||||
|  |             uint64_t addr = DTU_RADIO_ID; | ||||
|  |             uint8_t tmp[5]; | ||||
|  |             for(int8_t i = 4; i >= 0; i--) { | ||||
|  |                 tmp[i] = addr; | ||||
|  |                 addr >>= 8; | ||||
|  |             } | ||||
|  |             mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5)); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t mChanOut[4]; | ||||
|  |         uint8_t mChanIdx; | ||||
|  |         uint16_t mDtuIdCrc; | ||||
|  |         uint16_t mLastCrc; | ||||
|  |         uint8_t mRptCnt; | ||||
|  | 
 | ||||
|  |         RF24 mNrf24; | ||||
|  |         uint8_t mSendChannel; | ||||
|  |         BUFFER *mBufCtrl; | ||||
|  |         uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__RADIO_H__*/ | ||||
| @ -0,0 +1,82 @@ | |||||
|  | #ifndef __HM_SYSTEM_H__ | ||||
|  | #define __HM_SYSTEM_H__ | ||||
|  | 
 | ||||
|  | #include "hmInverter.h" | ||||
|  | #ifndef NO_RADIO | ||||
|  | #include "hmRadio.h" | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | template <class RADIO, class BUFFER, uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>> | ||||
|  | class HmSystem { | ||||
|  |     public: | ||||
|  |         typedef RADIO RadioType; | ||||
|  |         RadioType Radio; | ||||
|  |         typedef BUFFER BufferType; | ||||
|  |         BufferType BufCtrl; | ||||
|  | 
 | ||||
|  |         HmSystem() { | ||||
|  |             mNumInv = 0; | ||||
|  |         } | ||||
|  |         ~HmSystem() { | ||||
|  |             // TODO: cleanup
 | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void setup() { | ||||
|  |             Radio.setup(&BufCtrl); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         INVERTERTYPE *addInverter(const char *name, uint64_t serial, uint8_t type) { | ||||
|  |             if(MAX_INVERTER <= mNumInv) { | ||||
|  |                 DPRINT("max number of inverters reached!"); | ||||
|  |                 return NULL; | ||||
|  |             } | ||||
|  |             INVERTERTYPE *p = &mInverter[mNumInv]; | ||||
|  |             p->id         = mNumInv; | ||||
|  |             p->serial.u64 = serial; | ||||
|  |             p->type       = type; | ||||
|  |             p->init(); | ||||
|  |             uint8_t len   = (uint8_t)strlen(name); | ||||
|  |             strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); | ||||
|  | 
 | ||||
|  |             if(NULL == p->assign) { | ||||
|  |                 DPRINT("no assignment for type found!"); | ||||
|  |                 return NULL; | ||||
|  |             } | ||||
|  |             else { | ||||
|  |                 mNumInv ++; | ||||
|  |                 return p; | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         INVERTERTYPE *findInverter(uint8_t buf[]) { | ||||
|  |             INVERTERTYPE *p; | ||||
|  |             for(uint8_t i = 0; i < mNumInv; i++) { | ||||
|  |                 p = &mInverter[i]; | ||||
|  |                 if((p->serial.b[3] == buf[0]) | ||||
|  |                     && (p->serial.b[2] == buf[1]) | ||||
|  |                     && (p->serial.b[1] == buf[2]) | ||||
|  |                     && (p->serial.b[0] == buf[3])) | ||||
|  |                     return p; | ||||
|  |             } | ||||
|  |             return NULL; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         INVERTERTYPE *getInverterByPos(uint8_t pos) { | ||||
|  |             if(mInverter[pos].serial.u64 != 0ULL) | ||||
|  |                 return &mInverter[pos]; | ||||
|  |             else | ||||
|  |                 return NULL; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         uint8_t getNumInverters(void) { | ||||
|  |             return mNumInv; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         INVERTERTYPE mInverter[MAX_INVERTER]; | ||||
|  |         uint8_t mNumInv; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__HM_SYSTEM_H__*/ | ||||
| @ -0,0 +1,28 @@ | |||||
|  | import re | ||||
|  | 
 | ||||
|  | def convert2Header(inFile): | ||||
|  |     outName  = "h/" + inFile.replace(".", "_") + ".h" | ||||
|  |     fileType = inFile.split(".")[1] | ||||
|  | 
 | ||||
|  |     f = open(inFile, "r") | ||||
|  |     data = f.read().replace('\n', '') | ||||
|  |     f.close() | ||||
|  |     if fileType == "html": | ||||
|  |         data = re.sub(r"\>\s+\<", '><', data)           # whitespaces between xml tags | ||||
|  |         data = re.sub(r"(\;|\}|\>|\{)\s+", r'\1', data) # whitespaces inner javascript | ||||
|  |         data = re.sub(r"\"", '\\\"', data)              # escape quotation marks | ||||
|  |     else: | ||||
|  |         data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css | ||||
|  | 
 | ||||
|  |     define = inFile.split(".")[0].upper() | ||||
|  |     f = open(outName, "w") | ||||
|  |     f.write("#ifndef __{}_H__\n".format(define)) | ||||
|  |     f.write("#define __{}_H__\n".format(define)) | ||||
|  |     f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFile.replace(".", "_"), data)) | ||||
|  |     f.write("#endif /*__{}_H__*/\n".format(define)) | ||||
|  |     f.close() | ||||
|  | 
 | ||||
|  | convert2Header("index.html") | ||||
|  | convert2Header("setup.html") | ||||
|  | convert2Header("hoymiles.html") | ||||
|  | convert2Header("style.css") | ||||
| @ -0,0 +1,4 @@ | |||||
|  | #ifndef __HOYMILES_H__ | ||||
|  | #define __HOYMILES_H__ | ||||
|  | const char hoymiles_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">getAjax('/livedata', 'livedata');window.setInterval(\"getAjax('/livedata', 'livedata')\", 10000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><div id=\"livedata\"></div><p>Every 10 seconds the values are updated</p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>"; | ||||
|  | #endif /*__HOYMILES_H__*/ | ||||
| @ -0,0 +1,4 @@ | |||||
|  | #ifndef __INDEX_H__ | ||||
|  | #define __INDEX_H__ | ||||
|  | const char index_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">window.setInterval(\"getAjax('/uptime', 'uptime')\", 1000);window.setInterval(\"getAjax('/time', 'time')\", 1000);window.setInterval(\"getAjax('/cmdstat', 'cmds')\", 2000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/hoymiles\">Visualization</a><br/><br/><a href=\"/setup\">Setup</a><br/></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Time: </span><span id=\"time\"></span></p><p><span class=\"des\">Statistics: </span><pre id=\"cmds\"></pre></p><div id=\"note\">This project was started from <a href=\"https://www.mikrocontroller.net/topic/525778\" target=\"_blank\">this discussion. (Mikrocontroller.net)</a><br/>New updates can be found on Github: <a href=\"https://github.com/grindylow/ahoy\" target=\"_blank\">https://github.com/grindylow/ahoy</a><br/><br/>Please report issues using the feature provided by Github.            </div></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY :: {VERSION}</p><p class=\"right\"><a href=\"/reboot\">Reboot</a></p></div></body></html>"; | ||||
|  | #endif /*__INDEX_H__*/ | ||||
| @ -0,0 +1,4 @@ | |||||
|  | #ifndef __SETUP_H__ | ||||
|  | #define __SETUP_H__ | ||||
|  | const char setup_html[] PROGMEM = "<!doctype html><html><head><title>Setup - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body><h1>Setup</h1><div id=\"setup\" class=\"content\"><div id=\"content\"><p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.                </p><form method=\"post\" action=\"{IP}/save\"><p class=\"des\">WiFi</p><label for=\"ssid\">SSID</label><input type=\"text\" class=\"text\" name=\"ssid\" value=\"{SSID}\"/><label for=\"pwd\">Password</label><input type=\"password\" class=\"text\" name=\"pwd\" value=\"{PWD}\"/><p class=\"des\">Device Host Name</p><label for=\"device\">Device Name</label><input type=\"text\" class=\"text\" name=\"device\" value=\"{DEVICE}\"/><a class=\"erase\" href=\"/erase\">ERASE SETTINGS (not WiFi)</a><p class=\"des\">Inverter</p>{INVERTERS}<br/><p class=\"subdes\">General</p><label for=\"invInterval\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"invInterval\" value=\"{INV_INTVL}\"/><p class=\"des\">Pinout (Wemos)</p>{PINOUT}<p class=\"des\">Radio (NRF24L01+)</p><label for=\"rf24Power\">Amplifier Power Level</label><select name=\"rf24Power\">{RF24}</select><p class=\"des\">MQTT</p><label for=\"mqttAddr\">Broker / Server IP</label><input type=\"text\" class=\"text\" name=\"mqttAddr\" value=\"{MQTT_ADDR}\"/><label for=\"mqttPort\">Port</label><input type=\"text\" class=\"text\" name=\"mqttPort\" value=\"{MQTT_PORT}\"/><label for=\"mqttUser\">Username (optional)</label><input type=\"text\" class=\"text\" name=\"mqttUser\" value=\"{MQTT_USER}\"/><label for=\"mqttPwd\">Password (optional)</label><input type=\"text\" class=\"text\" name=\"mqttPwd\" value=\"{MQTT_PWD}\"/><label for=\"mqttTopic\">Topic</label><input type=\"text\" class=\"text\" name=\"mqttTopic\" value=\"{MQTT_TOPIC}\"/><label for=\"mqttIntvl\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"mqttIntvl\" value=\"{MQTT_INTVL}\"/><p class=\"des\">Serial Console</p><label for=\"serEn\">print inverter data</label><input type=\"checkbox\" class=\"cb\" name=\"serEn\" {SER_VAL_CB}/><br/><label for=\"serDbg\">print RF24 debug</label><input type=\"checkbox\" class=\"cb\" name=\"serDbg\" {SER_DBG_CB}/><br/><label for=\"serIntvl\">Interval (s)</label><input type=\"text\" class=\"text\" name=\"serIntvl\" value=\"{SER_INTVL}\"/><p class=\"des\"> </p><label for=\"reboot\">Reboot device after successful save</label><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><input type=\"submit\" value=\"save\" class=\"btn\" /></form></div></div><div id=\"footer\"><p class=\"left\"><a href=\"{IP}/\">Home</a></p><p class=\"left\"><a href=\"{IP}/update\">Update Firmware</a></p><p class=\"right\">AHOY - {VERSION}</p><p class=\"right\"><a href=\"{IP}/factory\">Factory Reset</a></p><p class=\"right\"><a href=\"{IP}/reboot\">Reboot</a></p></div></body></html>"; | ||||
|  | #endif /*__SETUP_H__*/ | ||||
| @ -0,0 +1,4 @@ | |||||
|  | #ifndef __STYLE_H__ | ||||
|  | #define __STYLE_H__ | ||||
|  | const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}.des {margin-top:35px;font-size:13pt;color:#006ec0;}.subdes {font-size:12pt;color:#006ec0;margin-left:7px;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}a.erase {background-color:#006ec0;color:#fff;padding:7px;display:inline-block;margin-top:30px;float:right;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;border-top:5px solid #fff;}#footer p, #footer a {color:#fff;padding:0 7px 0 7px;font-size:10pt !important;}div.content {background-color:#fff;padding-bottom:65px;overflow:auto;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;margin:10px 0 30px;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin-left:10px;}.left {float:left;}.right {float:right;}div.ch-iv {width:100%;background-color:#32b004;display:inline-block;margin-bottom:20px;padding-bottom:20px;overflow:auto;}div.ch {width:250px;min-height:420px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;overflow:auto;padding-bottom:20px;}div.ch .value, div.ch .info, div.ch .head, div.ch-iv  .value, div.ch-iv .info, div.ch-iv .head {color:#fff;display:block;width:100%;text-align:center;}.subgrp {float:left;width:250px;}div.ch .unit, div.ch-iv .unit {font-size:19px;margin-left:10px;}div.ch .value, div.ch-iv .value {margin-top:20px;font-size:30px;}div.ch .info, div.ch-iv .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}div.ch-iv .head {background-color:#1c6800;padding:10px 0 10px 0;}div.iv {max-width:1060px;}div.ch:last-child {margin-right:0px !important;}#note {margin:50px 10px 10px 10px;padding-top:10px;width:100%;border-top:1px solid #bbb;}"; | ||||
|  | #endif /*__STYLE_H__*/ | ||||
| @ -0,0 +1,42 @@ | |||||
|  | <!doctype html> | ||||
|  | <html> | ||||
|  |     <head> | ||||
|  |         <title>Index - {DEVICE}</title> | ||||
|  |         <link rel="stylesheet" type="text/css" href="style.css"/> | ||||
|  |         <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|  |         <script type="text/javascript"> | ||||
|  |             getAjax('/livedata', 'livedata'); | ||||
|  |             window.setInterval("getAjax('/livedata', 'livedata')", 10000); | ||||
|  | 
 | ||||
|  |             function getAjax(url, resid) { | ||||
|  |                 var http = null; | ||||
|  |                 http = new XMLHttpRequest(); | ||||
|  |                 if(http != null) { | ||||
|  |                    http.open("GET", url, true); | ||||
|  |                    http.onreadystatechange = print; | ||||
|  |                    http.send(null); | ||||
|  |                 } | ||||
|  | 
 | ||||
|  |                 function print() { | ||||
|  |                     if(http.readyState == 4) { | ||||
|  |                         document.getElementById(resid).innerHTML = http.responseText; | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         </script> | ||||
|  |         <style type="text/css"> | ||||
|  |         </style> | ||||
|  |     </head> | ||||
|  |     <body> | ||||
|  |         <h1>AHOY - {DEVICE}</h1> | ||||
|  |         <div id="content" class="content"> | ||||
|  |             <div id="livedata"></div> | ||||
|  |             <p>Every 10 seconds the values are updated</p> | ||||
|  |         </div> | ||||
|  |         <div id="footer"> | ||||
|  |             <p class="left">© 2022</p> | ||||
|  |             <p class="left"><a href="/">Home</a></p> | ||||
|  |             <p class="right">AHOY :: {VERSION}</p> | ||||
|  |         </div> | ||||
|  |     </body> | ||||
|  | </html> | ||||
| @ -0,0 +1,55 @@ | |||||
|  | <!doctype html> | ||||
|  | <html> | ||||
|  |     <head> | ||||
|  |         <title>Index - {DEVICE}</title> | ||||
|  |         <link rel="stylesheet" type="text/css" href="style.css"/> | ||||
|  |         <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|  |         <script type="text/javascript"> | ||||
|  |             window.setInterval("getAjax('/uptime', 'uptime')", 1000); | ||||
|  |             window.setInterval("getAjax('/time', 'time')", 1000); | ||||
|  |             window.setInterval("getAjax('/cmdstat', 'cmds')", 2000); | ||||
|  | 
 | ||||
|  |             function getAjax(url, resid) { | ||||
|  |                 var http = null; | ||||
|  |                 http = new XMLHttpRequest(); | ||||
|  |                 if(http != null) { | ||||
|  |                    http.open("GET", url, true); | ||||
|  |                    http.onreadystatechange = print; | ||||
|  |                    http.send(null); | ||||
|  |                 } | ||||
|  | 
 | ||||
|  |                 function print() { | ||||
|  |                     if(http.readyState == 4) { | ||||
|  |                         document.getElementById(resid).innerHTML = http.responseText; | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         </script> | ||||
|  |     </head> | ||||
|  |     <body> | ||||
|  |         <h1>AHOY - {DEVICE}</h1> | ||||
|  |         <div id="content" class="content"> | ||||
|  |             <p> | ||||
|  |                 <a href="/hoymiles">Visualization</a><br/> | ||||
|  |                 <br/> | ||||
|  |                 <a href="/setup">Setup</a><br/> | ||||
|  |             </p> | ||||
|  |             <p><span class="des">Uptime: </span><span id="uptime"></span></p> | ||||
|  |             <p><span class="des">Time: </span><span id="time"></span></p> | ||||
|  |             <p><span class="des">Statistics: </span><pre id="cmds"></pre></p> | ||||
|  | 
 | ||||
|  |             <div id="note"> | ||||
|  |                 This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a><br/> | ||||
|  |                 New updates can be found on Github: <a href="https://github.com/grindylow/ahoy" target="_blank">https://github.com/grindylow/ahoy</a><br/> | ||||
|  |                 <br/> | ||||
|  |                 Please report issues using the feature provided by Github. | ||||
|  |             </div> | ||||
|  |         </div> | ||||
|  |         <div id="footer"> | ||||
|  |             <p class="left">© 2022</p> | ||||
|  |             <p class="left"><a href="/update">Update Firmware</a></p> | ||||
|  |             <p class="right">AHOY :: {VERSION}</p> | ||||
|  |             <p class="right"><a href="/reboot">Reboot</a></p> | ||||
|  |         </div> | ||||
|  |     </body> | ||||
|  | </html> | ||||
| @ -0,0 +1,79 @@ | |||||
|  | <!doctype html> | ||||
|  | <html> | ||||
|  |     <head> | ||||
|  |         <title>Setup - {DEVICE}</title> | ||||
|  |         <link rel="stylesheet" type="text/css" href="style.css"/> | ||||
|  |         <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|  |     </head> | ||||
|  |     <body> | ||||
|  |         <h1>Setup</h1> | ||||
|  |         <div id="setup" class="content"> | ||||
|  |             <div id="content"> | ||||
|  |                 <p> | ||||
|  |                     Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. | ||||
|  |                 </p> | ||||
|  |                 <form method="post" action="{IP}/save"> | ||||
|  |                     <p class="des">WiFi</p> | ||||
|  |                     <label for="ssid">SSID</label> | ||||
|  |                     <input type="text" class="text" name="ssid" value="{SSID}"/> | ||||
|  |                     <label for="pwd">Password</label> | ||||
|  |                     <input type="password" class="text" name="pwd" value="{PWD}"/> | ||||
|  |                     <p class="des">Device Host Name</p> | ||||
|  |                     <label for="device">Device Name</label> | ||||
|  |                     <input type="text" class="text" name="device" value="{DEVICE}"/> | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |                     <a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a> | ||||
|  | 
 | ||||
|  |                     <p class="des">Inverter</p> | ||||
|  |                     {INVERTERS}<br/> | ||||
|  |                     <p class="subdes">General</p> | ||||
|  |                     <label for="invInterval">Interval (s)</label> | ||||
|  |                     <input type="text" class="text" name="invInterval" value="{INV_INTVL}"/> | ||||
|  | 
 | ||||
|  |                     <p class="des">Pinout (Wemos)</p> | ||||
|  |                     {PINOUT} | ||||
|  | 
 | ||||
|  |                     <p class="des">Radio (NRF24L01+)</p> | ||||
|  |                     <label for="rf24Power">Amplifier Power Level</label> | ||||
|  |                     <select name="rf24Power">{RF24}</select> | ||||
|  | 
 | ||||
|  |                     <p class="des">MQTT</p> | ||||
|  |                     <label for="mqttAddr">Broker / Server IP</label> | ||||
|  |                     <input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}"/> | ||||
|  |                     <label for="mqttPort">Port</label> | ||||
|  |                     <input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/> | ||||
|  |                     <label for="mqttUser">Username (optional)</label> | ||||
|  |                     <input type="text" class="text" name="mqttUser" value="{MQTT_USER}"/> | ||||
|  |                     <label for="mqttPwd">Password (optional)</label> | ||||
|  |                     <input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/> | ||||
|  |                     <label for="mqttTopic">Topic</label> | ||||
|  |                     <input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/> | ||||
|  |                     <label for="mqttIntvl">Interval (s)</label> | ||||
|  |                     <input type="text" class="text" name="mqttIntvl" value="{MQTT_INTVL}"/> | ||||
|  | 
 | ||||
|  |                     <p class="des">Serial Console</p> | ||||
|  |                     <label for="serEn">print inverter data</label> | ||||
|  |                     <input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/> | ||||
|  |                     <label for="serDbg">print RF24 debug</label> | ||||
|  |                     <input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/> | ||||
|  |                     <label for="serIntvl">Interval (s)</label> | ||||
|  |                     <input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/> | ||||
|  | 
 | ||||
|  |                     <p class="des"> </p> | ||||
|  |                     <label for="reboot">Reboot device after successful save</label> | ||||
|  |                     <input type="checkbox" class="cb" name="reboot"/> | ||||
|  |                     <input type="submit" value="save" class="btn" /> | ||||
|  |                 </form> | ||||
|  |             </div> | ||||
|  |         </div> | ||||
|  | 
 | ||||
|  |         <div id="footer"> | ||||
|  |             <p class="left"><a href="{IP}/">Home</a></p> | ||||
|  |             <p class="left"><a href="{IP}/update">Update Firmware</a></p> | ||||
|  |             <p class="right">AHOY - {VERSION}</p> | ||||
|  |             <p class="right"><a href="{IP}/factory">Factory Reset</a></p> | ||||
|  |             <p class="right"><a href="{IP}/reboot">Reboot</a></p> | ||||
|  |         </div> | ||||
|  |     </body> | ||||
|  | </html> | ||||
| @ -0,0 +1,190 @@ | |||||
|  | h1 { | ||||
|  |     margin: 0; | ||||
|  |     padding: 20pt; | ||||
|  |     font-size: 22pt; | ||||
|  |     color: #fff; | ||||
|  |     background-color: #006ec0; | ||||
|  |     display: block; | ||||
|  |     text-transform: uppercase; | ||||
|  | } | ||||
|  | 
 | ||||
|  | html, body { | ||||
|  |     font-family: Arial; | ||||
|  |     margin: 0; | ||||
|  |     padding: 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | p { | ||||
|  |     text-align: justify; | ||||
|  |     font-size: 13pt; | ||||
|  | } | ||||
|  | 
 | ||||
|  | .des { | ||||
|  |     margin-top: 35px; | ||||
|  |     font-size: 13pt; | ||||
|  |     color: #006ec0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | .subdes { | ||||
|  |     font-size: 12pt; | ||||
|  |     color: #006ec0; | ||||
|  |     margin-left: 7px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | a:link, a:visited { | ||||
|  |     text-decoration: none; | ||||
|  |     font-size: 13pt; | ||||
|  |     color: #006ec0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | a:hover, a:focus { | ||||
|  |     color: #f00; | ||||
|  | } | ||||
|  | 
 | ||||
|  | a.erase { | ||||
|  |     background-color: #006ec0; | ||||
|  |     color: #fff; | ||||
|  |     padding: 7px; | ||||
|  |     display: inline-block; | ||||
|  |     margin-top: 30px; | ||||
|  |     float: right; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #content { | ||||
|  |     padding: 15px 15px 60px 15px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #footer { | ||||
|  |     position: fixed; | ||||
|  |     bottom: 0px; | ||||
|  |     height: 45px; | ||||
|  |     background-color: #006ec0; | ||||
|  |     width: 100%; | ||||
|  |     border-top: 5px solid #fff; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #footer p, #footer a { | ||||
|  |     color: #fff; | ||||
|  |     padding: 0 7px 0 7px; | ||||
|  |     font-size: 10pt !important; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.content { | ||||
|  |     background-color: #fff; | ||||
|  |     padding-bottom: 65px; | ||||
|  |     overflow: auto; | ||||
|  | } | ||||
|  | 
 | ||||
|  | input, select { | ||||
|  |     padding: 7px; | ||||
|  |     font-size: 13pt; | ||||
|  | } | ||||
|  | 
 | ||||
|  | input.text, select { | ||||
|  |     width: 70%; | ||||
|  |     box-sizing: border-box; | ||||
|  |     margin-bottom: 10px; | ||||
|  |     border: 1px solid #ccc; | ||||
|  | } | ||||
|  | 
 | ||||
|  | input.btn { | ||||
|  |     background-color: #006ec0; | ||||
|  |     color: #fff; | ||||
|  |     border: 0px; | ||||
|  |     float: right; | ||||
|  |     margin: 10px 0 30px; | ||||
|  |     text-transform: uppercase; | ||||
|  | } | ||||
|  | 
 | ||||
|  | input.cb { | ||||
|  |     margin-bottom: 20px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | label { | ||||
|  |     width: 20%; | ||||
|  |     display: inline-block; | ||||
|  |     font-size: 12pt; | ||||
|  |     padding-right: 10px; | ||||
|  |     margin-left: 10px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | .left { | ||||
|  |     float: left; | ||||
|  | } | ||||
|  | 
 | ||||
|  | .right { | ||||
|  |     float: right; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch-iv { | ||||
|  |     width: 100%; | ||||
|  |     background-color: #32b004; | ||||
|  |     display: inline-block; | ||||
|  |     margin-bottom: 20px; | ||||
|  |     padding-bottom: 20px; | ||||
|  |     overflow: auto; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch { | ||||
|  |     width: 250px; | ||||
|  |     min-height: 420px; | ||||
|  |     background-color: #006ec0; | ||||
|  |     display: inline-block; | ||||
|  |     margin-right: 20px; | ||||
|  |     margin-bottom: 20px; | ||||
|  |     overflow: auto; | ||||
|  |     padding-bottom: 20px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch .value, div.ch .info, div.ch .head, div.ch-iv  .value, div.ch-iv .info, div.ch-iv .head { | ||||
|  |     color: #fff; | ||||
|  |     display: block; | ||||
|  |     width: 100%; | ||||
|  |     text-align: center; | ||||
|  | } | ||||
|  | 
 | ||||
|  | .subgrp { | ||||
|  |     float: left; | ||||
|  |     width: 250px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch .unit, div.ch-iv .unit { | ||||
|  |     font-size: 19px; | ||||
|  |     margin-left: 10px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch .value, div.ch-iv .value { | ||||
|  |     margin-top: 20px; | ||||
|  |     font-size: 30px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch .info, div.ch-iv .info { | ||||
|  |     margin-top: 3px; | ||||
|  |     font-size: 10px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch .head { | ||||
|  |     background-color: #003c80; | ||||
|  |     padding: 10px 0 10px 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch-iv .head { | ||||
|  |     background-color: #1c6800; | ||||
|  |     padding: 10px 0 10px 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.iv { | ||||
|  |     max-width: 1060px; | ||||
|  | } | ||||
|  | 
 | ||||
|  | div.ch:last-child { | ||||
|  |     margin-right: 0px !important; | ||||
|  | } | ||||
|  | 
 | ||||
|  | #note { | ||||
|  |     margin: 50px 10px 10px 10px; | ||||
|  |     padding-top: 10px; | ||||
|  |     width: 100%; | ||||
|  |     border-top: 1px solid #bbb; | ||||
|  | } | ||||
| @ -0,0 +1,432 @@ | |||||
|  | #include "main.h" | ||||
|  | #include "version.h" | ||||
|  | 
 | ||||
|  | #include "html/h/style_css.h" | ||||
|  | #include "html/h/setup_html.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | Main::Main(void) { | ||||
|  |     mDns     = new DNSServer(); | ||||
|  |     mWeb     = new ESP8266WebServer(80); | ||||
|  |     mUpdater = new ESP8266HTTPUpdateServer(); | ||||
|  |     mUdp     = new WiFiUDP(); | ||||
|  | 
 | ||||
|  |     mApActive          = true; | ||||
|  |     mWifiSettingsValid = false; | ||||
|  |     mSettingsValid     = false; | ||||
|  | 
 | ||||
|  |     mLimit       = 10; | ||||
|  |     mNextTryTs   = 0; | ||||
|  |     mApLastTick  = 0; | ||||
|  | 
 | ||||
|  |     snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); | ||||
|  | 
 | ||||
|  |     memset(&mDeviceName, 0, DEVNAME_LEN); | ||||
|  | 
 | ||||
|  |     mEep = new eep(); | ||||
|  |     Serial.begin(115200); | ||||
|  | 
 | ||||
|  |     mUptimeSecs     = 0; | ||||
|  |     mUptimeTicker   = 0xffffffff; | ||||
|  |     mUptimeInterval = 1000; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::setup(uint32_t timeout) { | ||||
|  |     bool startAp = mApActive; | ||||
|  |     mLimit = timeout; | ||||
|  | 
 | ||||
|  |     mWeb->on("/setup",     std::bind(&Main::showSetup,      this)); | ||||
|  |     mWeb->on("/save",      std::bind(&Main::showSave,       this)); | ||||
|  |     mWeb->on("/uptime",    std::bind(&Main::showUptime,     this)); | ||||
|  |     mWeb->on("/time",      std::bind(&Main::showTime,       this)); | ||||
|  |     mWeb->on("/style.css", std::bind(&Main::showCss,        this)); | ||||
|  |     mWeb->on("/reboot",    std::bind(&Main::showReboot,     this)); | ||||
|  |     mWeb->on("/factory",   std::bind(&Main::showFactoryRst, this)); | ||||
|  |     mWeb->onNotFound (     std::bind(&Main::showNotFound,   this)); | ||||
|  | 
 | ||||
|  |     startAp = getConfig(); | ||||
|  | 
 | ||||
|  | #ifndef AP_ONLY | ||||
|  |     if(false == startAp) | ||||
|  |         startAp = setupStation(timeout); | ||||
|  | #else | ||||
|  |     setupAp(WIFI_AP_SSID, WIFI_AP_PWD); | ||||
|  | #endif | ||||
|  | 
 | ||||
|  |     if(!startAp) { | ||||
|  |         mTimestamp  = getNtpTime(); | ||||
|  |         DPRINTLN("[NTP]: " + getDateTimeStr(getNtpTime())); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     mUpdater->setup(mWeb); | ||||
|  |     mApActive = startAp; | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::loop(void) { | ||||
|  |     if(mApActive) { | ||||
|  |         mDns->processNextRequest(); | ||||
|  | #ifndef AP_ONLY | ||||
|  |         if(checkTicker(&mNextTryTs, (WIFI_AP_ACTIVE_TIME * 1000))) { | ||||
|  |             mApLastTick = millis(); | ||||
|  |             mApActive = setupStation(mLimit); | ||||
|  |             if(mApActive) { | ||||
|  |                 if(strlen(WIFI_AP_PWD) < 8) | ||||
|  |                     DPRINTLN("ERROR: password must be at least 8 characters long"); | ||||
|  |                 setupAp(WIFI_AP_SSID, WIFI_AP_PWD); | ||||
|  |             } | ||||
|  |         } | ||||
|  |         else { | ||||
|  |             if(millis() - mApLastTick > 10000) { | ||||
|  |                 mApLastTick = millis(); | ||||
|  |                 DPRINTLN("AP will be closed in " + String((mNextTryTs - mApLastTick) / 1000) + " seconds"); | ||||
|  |             } | ||||
|  |         } | ||||
|  | #endif | ||||
|  |     } | ||||
|  |     mWeb->handleClient(); | ||||
|  | 
 | ||||
|  |     if(checkTicker(&mUptimeTicker, mUptimeInterval)) { | ||||
|  |         mUptimeSecs++; | ||||
|  |         mTimestamp++; | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | bool Main::getConfig(void) { | ||||
|  |     bool mApActive = false; | ||||
|  | 
 | ||||
|  |     mWifiSettingsValid = checkEEpCrc(ADDR_START, ADDR_WIFI_CRC, ADDR_WIFI_CRC); | ||||
|  |     mSettingsValid = checkEEpCrc(ADDR_START_SETTINGS, (ADDR_NEXT-ADDR_START_SETTINGS), ADDR_SETTINGS_CRC); | ||||
|  | 
 | ||||
|  |     if(mWifiSettingsValid) { | ||||
|  |         mEep->read(ADDR_SSID,    mStationSsid, SSID_LEN); | ||||
|  |         mEep->read(ADDR_PWD,     mStationPwd, PWD_LEN); | ||||
|  |         mEep->read(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN); | ||||
|  |     } | ||||
|  |     else { | ||||
|  |         /*mApActive = true;
 | ||||
|  |         memset(mStationSsid, 0, SSID_LEN); | ||||
|  |         memset(mStationPwd, 0, PWD_LEN); | ||||
|  |         memset(mDeviceName, 0, DEVNAME_LEN); | ||||
|  | 
 | ||||
|  |         // erase application settings except wifi settings
 | ||||
|  |         eraseSettings();*/ | ||||
|  |         snprintf(mStationSsid, SSID_LEN,    "%s", FB_WIFI_SSID); | ||||
|  |         snprintf(mStationPwd,  PWD_LEN,     "%s", FB_WIFI_PWD); | ||||
|  |         snprintf(mDeviceName,  DEVNAME_LEN, "%s", DEF_DEVICE_NAME); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return mApActive; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::setupAp(const char *ssid, const char *pwd) { | ||||
|  |     IPAddress apIp(192, 168, 1, 1); | ||||
|  | 
 | ||||
|  |     DPRINTLN("\n---------\nAP MODE\nSSDI: " | ||||
|  |         + String(ssid) + "\nPWD: " | ||||
|  |         + String(pwd) + "\nActive for: " | ||||
|  |         + String(WIFI_AP_ACTIVE_TIME) + " seconds" | ||||
|  |         + "\n---------\n"); | ||||
|  |     DPRINTLN("DBG: " + String(mNextTryTs)); | ||||
|  | 
 | ||||
|  |     WiFi.mode(WIFI_AP); | ||||
|  |     WiFi.softAPConfig(apIp, apIp, IPAddress(255, 255, 255, 0)); | ||||
|  |     WiFi.softAP(ssid, pwd); | ||||
|  | 
 | ||||
|  |     mDns->start(mDnsPort, "*", apIp); | ||||
|  | 
 | ||||
|  |     mWeb->onNotFound([&]() { | ||||
|  |         showSetup(); | ||||
|  |     }); | ||||
|  |     mWeb->on("/", std::bind(&Main::showSetup, this)); | ||||
|  | 
 | ||||
|  |     mWeb->begin(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | bool Main::setupStation(uint32_t timeout) { | ||||
|  |     int32_t cnt; | ||||
|  |     bool startAp = false; | ||||
|  | 
 | ||||
|  |     if(timeout >= 3) | ||||
|  |         cnt = (timeout - 3) / 2 * 10; | ||||
|  |     else { | ||||
|  |         timeout = 1; | ||||
|  |         cnt = 1; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     WiFi.mode(WIFI_STA); | ||||
|  |     WiFi.begin(mStationSsid, mStationPwd); | ||||
|  |     if(String(mDeviceName) != "") | ||||
|  |         WiFi.hostname(mDeviceName); | ||||
|  | 
 | ||||
|  |     delay(2000); | ||||
|  |     DPRINTLN("connect to network '" + String(mStationSsid) + "' ..."); | ||||
|  |     while (WiFi.status() != WL_CONNECTED) { | ||||
|  |         delay(100); | ||||
|  |         if(cnt % 100 == 0) | ||||
|  |             Serial.println("."); | ||||
|  |         else | ||||
|  |             Serial.print("."); | ||||
|  | 
 | ||||
|  |         if(timeout > 0) { // limit == 0 -> no limit
 | ||||
|  |             if(--cnt <= 0) { | ||||
|  |                 if(WiFi.status() != WL_CONNECTED) { | ||||
|  |                     startAp = true; | ||||
|  |                     WiFi.disconnect(); | ||||
|  |                 } | ||||
|  |                 delay(100); | ||||
|  |                 break; | ||||
|  |             } | ||||
|  |         } | ||||
|  |     } | ||||
|  |     Serial.println("."); | ||||
|  | 
 | ||||
|  |     if(false == startAp) { | ||||
|  |         mWeb->begin(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     delay(1000); | ||||
|  | 
 | ||||
|  |     return startAp; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showSetup(void) { | ||||
|  |     String html = FPSTR(setup_html); | ||||
|  |     html.replace("{SSID}", mStationSsid); | ||||
|  |     // PWD will be left at the default value (for protection)
 | ||||
|  |     // -> the PWD will only be changed if it does not match the default "{PWD}"
 | ||||
|  |     html.replace("{DEVICE}", String(mDeviceName)); | ||||
|  |     html.replace("{VERSION}", String(mVersion)); | ||||
|  |     if(mApActive) | ||||
|  |         html.replace("{IP}", String("http://192.168.1.1")); | ||||
|  |     else | ||||
|  |         html.replace("{IP}", ("http://" + String(WiFi.localIP().toString()))); | ||||
|  | 
 | ||||
|  |     mWeb->send(200, "text/html", html); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showCss(void) { | ||||
|  |     mWeb->send(200, "text/css", FPSTR(style_css)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showSave(void) { | ||||
|  |     saveValues(true); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::saveValues(bool webSend = true) { | ||||
|  |     if(mWeb->args() > 0) { | ||||
|  |         if(mWeb->arg("ssid") != "") { | ||||
|  |             memset(mStationSsid, 0, SSID_LEN); | ||||
|  |             mWeb->arg("ssid").toCharArray(mStationSsid, SSID_LEN); | ||||
|  |             mEep->write(ADDR_SSID, mStationSsid, SSID_LEN); | ||||
|  | 
 | ||||
|  |             if(mWeb->arg("pwd") != "{PWD}") { | ||||
|  |                 memset(mStationPwd, 0, PWD_LEN); | ||||
|  |                 mWeb->arg("pwd").toCharArray(mStationPwd, PWD_LEN); | ||||
|  |                 mEep->write(ADDR_PWD, mStationPwd, PWD_LEN); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         memset(mDeviceName, 0, DEVNAME_LEN); | ||||
|  |         mWeb->arg("device").toCharArray(mDeviceName, DEVNAME_LEN); | ||||
|  |         mEep->write(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         updateCrc(); | ||||
|  |         if(webSend) { | ||||
|  |             if(mWeb->arg("reboot") == "on") | ||||
|  |                 showReboot(); | ||||
|  |             else // TODO: add device name as redirect in AP-mode
 | ||||
|  |                 mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>" | ||||
|  |                              "<p>saved</p></body></html>"); | ||||
|  |         } | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::updateCrc(void) { | ||||
|  |     uint16_t crc; | ||||
|  |     crc = buildEEpCrc(ADDR_START, ADDR_WIFI_CRC); | ||||
|  |     //Serial.println("new CRC: " + String(crc, HEX));
 | ||||
|  |     mEep->write(ADDR_WIFI_CRC, crc); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showUptime(void) { | ||||
|  |     char time[20] = {0}; | ||||
|  | 
 | ||||
|  |     int upTimeSc = uint32_t((mUptimeSecs) % 60); | ||||
|  |     int upTimeMn = uint32_t((mUptimeSecs / (60)) % 60); | ||||
|  |     int upTimeHr = uint32_t((mUptimeSecs / (60 * 60)) % 24); | ||||
|  |     int upTimeDy = uint32_t((mUptimeSecs / (60 * 60 * 24)) % 365); | ||||
|  | 
 | ||||
|  |     snprintf(time, 20, "%d Tage, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc); | ||||
|  | 
 | ||||
|  |     mWeb->send(200, "text/plain", String(time)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showTime(void) { | ||||
|  |     mWeb->send(200, "text/plain", getDateTimeStr(mTimestamp)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showNotFound(void) { | ||||
|  |     String msg = "File Not Found\n\n"; | ||||
|  |     msg += "URI: "; | ||||
|  |     msg += mWeb->uri(); | ||||
|  |     msg += "\nMethod: "; | ||||
|  |     msg += ( mWeb->method() == HTTP_GET ) ? "GET" : "POST"; | ||||
|  |     msg += "\nArguments: "; | ||||
|  |     msg += mWeb->args(); | ||||
|  |     msg += "\n"; | ||||
|  | 
 | ||||
|  |     for(uint8_t i = 0; i < mWeb->args(); i++ ) { | ||||
|  |         msg += " " + mWeb->argName(i) + ": " + mWeb->arg(i) + "\n"; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     mWeb->send(404, "text/plain", msg); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showReboot(void) { | ||||
|  |     mWeb->send(200, "text/html", "<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"); | ||||
|  |     delay(1000); | ||||
|  |     ESP.restart(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::showFactoryRst(void) { | ||||
|  |     String content = ""; | ||||
|  |     int refresh = 3; | ||||
|  |     if(mWeb->args() > 0) { | ||||
|  |         if(mWeb->arg("reset").toInt() == 1) { | ||||
|  |             eraseSettings(true); | ||||
|  |             content = "factory reset: success\n\nrebooting ... "; | ||||
|  |             refresh = 10; | ||||
|  |         } | ||||
|  |         else { | ||||
|  |             content = "factory reset: aborted"; | ||||
|  |             refresh = 3; | ||||
|  |         } | ||||
|  |     } | ||||
|  |     else { | ||||
|  |         content = "<h1>Factory Reset</h1>"; | ||||
|  |         content += "<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>"; | ||||
|  |         refresh = 120; | ||||
|  |     } | ||||
|  |     mWeb->send(200, "text/html", "<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"" + String(refresh) + "; URL=/\"></head><body>" + content + "</body></html>"); | ||||
|  |     if(refresh == 10) { | ||||
|  |         delay(1000); | ||||
|  |         ESP.restart(); | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | time_t Main::getNtpTime(void) { | ||||
|  |     time_t date = 0; | ||||
|  |     IPAddress timeServer; | ||||
|  |     uint8_t buf[NTP_PACKET_SIZE]; | ||||
|  |     uint8_t retry = 0; | ||||
|  | 
 | ||||
|  |     WiFi.hostByName (TIMESERVER_NAME, timeServer); | ||||
|  |     mUdp->begin(TIME_LOCAL_PORT); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     sendNTPpacket(timeServer); | ||||
|  | 
 | ||||
|  |     while(retry++ < 5) { | ||||
|  |         int wait = 150; | ||||
|  |         while(--wait) { | ||||
|  |             if(NTP_PACKET_SIZE <= mUdp->parsePacket()) { | ||||
|  |                 uint64_t secsSince1900; | ||||
|  |                 mUdp->read(buf, NTP_PACKET_SIZE); | ||||
|  |                 secsSince1900  = (buf[40] << 24); | ||||
|  |                 secsSince1900 |= (buf[41] << 16); | ||||
|  |                 secsSince1900 |= (buf[42] <<  8); | ||||
|  |                 secsSince1900 |= (buf[43]      ); | ||||
|  | 
 | ||||
|  |                 date = secsSince1900 - 2208988800UL; // UTC time
 | ||||
|  |                 date += (TIMEZONE + offsetDayLightSaving(date)) * 3600; | ||||
|  |                 break; | ||||
|  |             } | ||||
|  |             else | ||||
|  |                 delay(10); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return date; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void Main::sendNTPpacket(IPAddress& address) { | ||||
|  |     uint8_t buf[NTP_PACKET_SIZE] = {0}; | ||||
|  | 
 | ||||
|  |     buf[0] = B11100011; // LI, Version, Mode
 | ||||
|  |     buf[1] = 0;         // Stratum
 | ||||
|  |     buf[2] = 6;         // Max Interval between messages in seconds
 | ||||
|  |     buf[3] = 0xEC;      // Clock Precision
 | ||||
|  |     // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
 | ||||
|  |     buf[12] = 49;       // four-byte reference ID identifying
 | ||||
|  |     buf[13] = 0x4E; | ||||
|  |     buf[14] = 49; | ||||
|  |     buf[15] = 52; | ||||
|  | 
 | ||||
|  |     mUdp->beginPacket(address, 123); // NTP request, port 123
 | ||||
|  |     mUdp->write(buf, NTP_PACKET_SIZE); | ||||
|  |     mUdp->endPacket(); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | String Main::getDateTimeStr(time_t t) { | ||||
|  |     char str[20] = {0}; | ||||
|  |     sprintf(str, "%04d-%02d-%02d+%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); | ||||
|  |     return String(str); | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | // calculates the daylight saving time for middle Europe. Input: Unixtime in UTC
 | ||||
|  | // from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
 | ||||
|  | time_t Main::offsetDayLightSaving (uint32_t local_t) { | ||||
|  |     int m = month (local_t); | ||||
|  |     if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
 | ||||
|  |     if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
 | ||||
|  |     int y = year (local_t); | ||||
|  |     int h = hour (local_t); | ||||
|  |     int hToday = (h + 24 * day(local_t)); | ||||
|  |     if((m == 3  && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7))) | ||||
|  |         || (m == 10 && hToday <  (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) ) | ||||
|  |         return 1; | ||||
|  |     else | ||||
|  |         return 0; | ||||
|  | } | ||||
| @ -0,0 +1,125 @@ | |||||
|  | #ifndef __MAIN_H__ | ||||
|  | #define __MAIN_H__ | ||||
|  | 
 | ||||
|  | #include "Arduino.h" | ||||
|  | 
 | ||||
|  | #include <ESP8266WiFi.h> | ||||
|  | #include <DNSServer.h> | ||||
|  | #include <ESP8266WebServer.h> | ||||
|  | 
 | ||||
|  | #include <ESP8266HTTPUpdateServer.h> | ||||
|  | 
 | ||||
|  | // NTP
 | ||||
|  | #include <WiFiUdp.h> | ||||
|  | #include <TimeLib.h> | ||||
|  | 
 | ||||
|  | #include "eep.h" | ||||
|  | #include "defines.h" | ||||
|  | #include "crc.h" | ||||
|  | #include "debug.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | const byte mDnsPort = 53; | ||||
|  | 
 | ||||
|  | /* TIMESERVER CONFIG */ | ||||
|  | #define TIMESERVER_NAME     "pool.ntp.org" | ||||
|  | #define TIME_LOCAL_PORT     8888 | ||||
|  | #define NTP_PACKET_SIZE     48 | ||||
|  | #define TIMEZONE            1 // Central European time +1
 | ||||
|  | 
 | ||||
|  | class Main { | ||||
|  |     public: | ||||
|  |         Main(void); | ||||
|  |         virtual void setup(uint32_t timeout); | ||||
|  |         virtual void loop(); | ||||
|  |         String getDateTimeStr (time_t t); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |     protected: | ||||
|  |         void showReboot(void); | ||||
|  |         virtual void saveValues(bool webSend); | ||||
|  |         virtual void updateCrc(void); | ||||
|  | 
 | ||||
|  |         inline uint16_t buildEEpCrc(uint32_t start, uint32_t length) { | ||||
|  |             uint8_t buf[length]; | ||||
|  |             mEep->read(start, buf, length); | ||||
|  |             return crc16(buf, length); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         bool checkEEpCrc(uint32_t start, uint32_t length, uint32_t crcPos) { | ||||
|  |             uint16_t crcRd, crcCheck; | ||||
|  |             crcCheck = buildEEpCrc(start, length); | ||||
|  |             mEep->read(crcPos, &crcRd); | ||||
|  |             return (crcCheck == crcRd); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void eraseSettings(bool all = false) { | ||||
|  |             uint8_t buf[64] = {0}; | ||||
|  |             uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS; | ||||
|  |             uint16_t end; | ||||
|  |             do { | ||||
|  |                 end = addr + 64; | ||||
|  |                 if(end > (ADDR_SETTINGS_CRC + 2)) | ||||
|  |                     end = (ADDR_SETTINGS_CRC + 2); | ||||
|  |                 DPRINTLN("erase: 0x" + String(addr, HEX) + " - 0x" + String(end, HEX)); | ||||
|  |                 mEep->write(addr, buf, (end-addr)); | ||||
|  |                 addr = end; | ||||
|  |             } while(addr < (ADDR_SETTINGS_CRC + 2)); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         inline bool checkTicker(uint32_t *ticker, uint32_t interval) { | ||||
|  |             uint32_t mil = millis(); | ||||
|  |             if(mil >= *ticker) { | ||||
|  |                 *ticker = mil + interval; | ||||
|  |                 return true; | ||||
|  |             } | ||||
|  |             else if(mil < (*ticker - interval)) { | ||||
|  |                 *ticker = mil + interval; | ||||
|  |                 return true; | ||||
|  |             } | ||||
|  | 
 | ||||
|  |             return false; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         char mStationSsid[SSID_LEN]; | ||||
|  |         char mStationPwd[PWD_LEN]; | ||||
|  |         bool mWifiSettingsValid; | ||||
|  |         bool mSettingsValid; | ||||
|  |         bool mApActive; | ||||
|  |         ESP8266WebServer *mWeb; | ||||
|  |         char mVersion[9]; | ||||
|  |         char mDeviceName[DEVNAME_LEN]; | ||||
|  |         eep *mEep; | ||||
|  |         uint32_t mTimestamp; | ||||
|  |         uint32_t mLimit; | ||||
|  |         uint32_t mNextTryTs; | ||||
|  |         uint32_t mApLastTick; | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         bool getConfig(void); | ||||
|  |         void setupAp(const char *ssid, const char *pwd); | ||||
|  |         bool setupStation(uint32_t timeout); | ||||
|  | 
 | ||||
|  |         void showNotFound(void); | ||||
|  |         virtual void showSetup(void); | ||||
|  |         virtual void showSave(void); | ||||
|  |         void showUptime(void); | ||||
|  |         void showTime(void); | ||||
|  |         void showCss(void); | ||||
|  |         void showFactoryRst(void); | ||||
|  | 
 | ||||
|  |         time_t getNtpTime(void); | ||||
|  |         void sendNTPpacket(IPAddress& address); | ||||
|  |         time_t offsetDayLightSaving (uint32_t local_t); | ||||
|  | 
 | ||||
|  |         uint32_t mUptimeTicker; | ||||
|  |         uint16_t mUptimeInterval; | ||||
|  |         uint32_t mUptimeSecs; | ||||
|  | 
 | ||||
|  |         DNSServer *mDns; | ||||
|  |         ESP8266HTTPUpdateServer *mUpdater; | ||||
|  | 
 | ||||
|  |         WiFiUDP *mUdp; // for time server
 | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__MAIN_H__*/ | ||||
| @ -0,0 +1,87 @@ | |||||
|  | #ifndef __MQTT_H__ | ||||
|  | #define __MQTT_H__ | ||||
|  | 
 | ||||
|  | #include <ESP8266WiFi.h> | ||||
|  | #include <PubSubClient.h> | ||||
|  | #include "defines.h" | ||||
|  | 
 | ||||
|  | class mqtt { | ||||
|  |     public: | ||||
|  |         mqtt() { | ||||
|  |             mClient     = new PubSubClient(mEspClient); | ||||
|  |             mAddressSet = false; | ||||
|  | 
 | ||||
|  |             memset(mUser,  0, MQTT_USER_LEN); | ||||
|  |             memset(mPwd,   0, MQTT_PWD_LEN); | ||||
|  |             memset(mTopic, 0, MQTT_TOPIC_LEN); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         ~mqtt() { | ||||
|  |             delete mClient; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void setup(const char *broker, const char *topic, const char *user, const char *pwd, uint16_t port) { | ||||
|  |             mAddressSet = true; | ||||
|  |             mClient->setServer(broker, port); | ||||
|  | 
 | ||||
|  |             snprintf(mUser, MQTT_USER_LEN, "%s", user); | ||||
|  |             snprintf(mPwd, MQTT_PWD_LEN, "%s", pwd); | ||||
|  |             snprintf(mTopic, MQTT_TOPIC_LEN, "%s", topic); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void sendMsg(const char *topic, const char *msg) { | ||||
|  |             if(mAddressSet) { | ||||
|  |                 char top[64]; | ||||
|  |                 snprintf(top, 64, "%s/%s", mTopic, topic); | ||||
|  | 
 | ||||
|  |                 if(!mClient->connected()) | ||||
|  |                     reconnect(); | ||||
|  |                 mClient->publish(top, msg); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         bool isConnected(bool doRecon = false) { | ||||
|  |             if(doRecon) | ||||
|  |                 reconnect(); | ||||
|  |             return mClient->connected(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         char *getUser(void) { | ||||
|  |             return mUser; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         char *getPwd(void) { | ||||
|  |             return mPwd; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         char *getTopic(void) { | ||||
|  |             return mTopic; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         void loop() { | ||||
|  |             //if(!mClient->connected())
 | ||||
|  |             //    reconnect();
 | ||||
|  |             mClient->loop(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |     private: | ||||
|  |         void reconnect(void) { | ||||
|  |             if(!mClient->connected()) { | ||||
|  |                 String mqttId = "ESP-" + String(random(0xffff), HEX); | ||||
|  |                 if((strlen(mUser) > 0) && (strlen(mPwd) > 0)) | ||||
|  |                     mClient->connect(mqttId.c_str(), mUser, mPwd); | ||||
|  |                 else | ||||
|  |                     mClient->connect(mqttId.c_str()); | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         WiFiClient mEspClient; | ||||
|  |         PubSubClient *mClient; | ||||
|  | 
 | ||||
|  |         bool mAddressSet; | ||||
|  |         char mUser[MQTT_USER_LEN]; | ||||
|  |         char mPwd[MQTT_PWD_LEN]; | ||||
|  |         char mTopic[MQTT_TOPIC_LEN]; | ||||
|  | }; | ||||
|  | 
 | ||||
|  | #endif /*__MQTT_H_*/ | ||||
| @ -0,0 +1,31 @@ | |||||
|  |  | ||||
|  | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
|  | # Visual Studio Version 16 | ||||
|  | VisualStudioVersion = 16.0.32002.261 | ||||
|  | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
|  | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hmClassTest", "hmClassTest\hmClassTest.vcxproj", "{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}" | ||||
|  | EndProject | ||||
|  | Global | ||||
|  | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
|  | 		Debug|x64 = Debug|x64 | ||||
|  | 		Debug|x86 = Debug|x86 | ||||
|  | 		Release|x64 = Release|x64 | ||||
|  | 		Release|x86 = Release|x86 | ||||
|  | 	EndGlobalSection | ||||
|  | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x64.ActiveCfg = Debug|x64 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x64.Build.0 = Debug|x64 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x86.ActiveCfg = Debug|Win32 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Debug|x86.Build.0 = Debug|Win32 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x64.ActiveCfg = Release|x64 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x64.Build.0 = Release|x64 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x86.ActiveCfg = Release|Win32 | ||||
|  | 		{4D899C12-DE0E-4CDB-B48C-FDFEC331F219}.Release|x86.Build.0 = Release|Win32 | ||||
|  | 	EndGlobalSection | ||||
|  | 	GlobalSection(SolutionProperties) = preSolution | ||||
|  | 		HideSolutionNode = FALSE | ||||
|  | 	EndGlobalSection | ||||
|  | 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
|  | 		SolutionGuid = {7C291F74-09F6-4C84-99E1-6E7294062385} | ||||
|  | 	EndGlobalSection | ||||
|  | EndGlobal | ||||
| @ -0,0 +1,147 @@ | |||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||
|  | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
|  |   <ItemGroup Label="ProjectConfigurations"> | ||||
|  |     <ProjectConfiguration Include="Debug|Win32"> | ||||
|  |       <Configuration>Debug</Configuration> | ||||
|  |       <Platform>Win32</Platform> | ||||
|  |     </ProjectConfiguration> | ||||
|  |     <ProjectConfiguration Include="Release|Win32"> | ||||
|  |       <Configuration>Release</Configuration> | ||||
|  |       <Platform>Win32</Platform> | ||||
|  |     </ProjectConfiguration> | ||||
|  |     <ProjectConfiguration Include="Debug|x64"> | ||||
|  |       <Configuration>Debug</Configuration> | ||||
|  |       <Platform>x64</Platform> | ||||
|  |     </ProjectConfiguration> | ||||
|  |     <ProjectConfiguration Include="Release|x64"> | ||||
|  |       <Configuration>Release</Configuration> | ||||
|  |       <Platform>x64</Platform> | ||||
|  |     </ProjectConfiguration> | ||||
|  |   </ItemGroup> | ||||
|  |   <ItemGroup> | ||||
|  |     <ClCompile Include="..\src\main.cpp" /> | ||||
|  |   </ItemGroup> | ||||
|  |   <PropertyGroup Label="Globals"> | ||||
|  |     <VCProjectVersion>16.0</VCProjectVersion> | ||||
|  |     <Keyword>Win32Proj</Keyword> | ||||
|  |     <ProjectGuid>{4d899c12-de0e-4cdb-b48c-fdfec331f219}</ProjectGuid> | ||||
|  |     <RootNamespace>hmClassTest</RootNamespace> | ||||
|  |     <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> | ||||
|  |   </PropertyGroup> | ||||
|  |   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> | ||||
|  |     <ConfigurationType>Application</ConfigurationType> | ||||
|  |     <UseDebugLibraries>true</UseDebugLibraries> | ||||
|  |     <PlatformToolset>v142</PlatformToolset> | ||||
|  |     <CharacterSet>Unicode</CharacterSet> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> | ||||
|  |     <ConfigurationType>Application</ConfigurationType> | ||||
|  |     <UseDebugLibraries>false</UseDebugLibraries> | ||||
|  |     <PlatformToolset>v142</PlatformToolset> | ||||
|  |     <WholeProgramOptimization>true</WholeProgramOptimization> | ||||
|  |     <CharacterSet>Unicode</CharacterSet> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> | ||||
|  |     <ConfigurationType>Application</ConfigurationType> | ||||
|  |     <UseDebugLibraries>true</UseDebugLibraries> | ||||
|  |     <PlatformToolset>v142</PlatformToolset> | ||||
|  |     <CharacterSet>Unicode</CharacterSet> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> | ||||
|  |     <ConfigurationType>Application</ConfigurationType> | ||||
|  |     <UseDebugLibraries>false</UseDebugLibraries> | ||||
|  |     <PlatformToolset>v142</PlatformToolset> | ||||
|  |     <WholeProgramOptimization>true</WholeProgramOptimization> | ||||
|  |     <CharacterSet>Unicode</CharacterSet> | ||||
|  |   </PropertyGroup> | ||||
|  |   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||||
|  |   <ImportGroup Label="ExtensionSettings"> | ||||
|  |   </ImportGroup> | ||||
|  |   <ImportGroup Label="Shared"> | ||||
|  |   </ImportGroup> | ||||
|  |   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|  |     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|  |   </ImportGroup> | ||||
|  |   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||||
|  |     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|  |   </ImportGroup> | ||||
|  |   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||||
|  |     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|  |   </ImportGroup> | ||||
|  |   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|  |     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|  |   </ImportGroup> | ||||
|  |   <PropertyGroup Label="UserMacros" /> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|  |     <LinkIncremental>true</LinkIncremental> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||||
|  |     <LinkIncremental>false</LinkIncremental> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||||
|  |     <LinkIncremental>true</LinkIncremental> | ||||
|  |   </PropertyGroup> | ||||
|  |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|  |     <LinkIncremental>false</LinkIncremental> | ||||
|  |   </PropertyGroup> | ||||
|  |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|  |     <ClCompile> | ||||
|  |       <WarningLevel>Level3</WarningLevel> | ||||
|  |       <SDLCheck>true</SDLCheck> | ||||
|  |       <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|  |       <ConformanceMode>true</ConformanceMode> | ||||
|  |     </ClCompile> | ||||
|  |     <Link> | ||||
|  |       <SubSystem>Console</SubSystem> | ||||
|  |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|  |     </Link> | ||||
|  |   </ItemDefinitionGroup> | ||||
|  |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||||
|  |     <ClCompile> | ||||
|  |       <WarningLevel>Level3</WarningLevel> | ||||
|  |       <FunctionLevelLinking>true</FunctionLevelLinking> | ||||
|  |       <IntrinsicFunctions>true</IntrinsicFunctions> | ||||
|  |       <SDLCheck>true</SDLCheck> | ||||
|  |       <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|  |       <ConformanceMode>true</ConformanceMode> | ||||
|  |     </ClCompile> | ||||
|  |     <Link> | ||||
|  |       <SubSystem>Console</SubSystem> | ||||
|  |       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||
|  |       <OptimizeReferences>true</OptimizeReferences> | ||||
|  |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|  |     </Link> | ||||
|  |   </ItemDefinitionGroup> | ||||
|  |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||||
|  |     <ClCompile> | ||||
|  |       <WarningLevel>Level3</WarningLevel> | ||||
|  |       <SDLCheck>true</SDLCheck> | ||||
|  |       <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|  |       <ConformanceMode>true</ConformanceMode> | ||||
|  |     </ClCompile> | ||||
|  |     <Link> | ||||
|  |       <SubSystem>Console</SubSystem> | ||||
|  |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|  |     </Link> | ||||
|  |   </ItemDefinitionGroup> | ||||
|  |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|  |     <ClCompile> | ||||
|  |       <WarningLevel>Level3</WarningLevel> | ||||
|  |       <FunctionLevelLinking>true</FunctionLevelLinking> | ||||
|  |       <IntrinsicFunctions>true</IntrinsicFunctions> | ||||
|  |       <SDLCheck>true</SDLCheck> | ||||
|  |       <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|  |       <ConformanceMode>true</ConformanceMode> | ||||
|  |     </ClCompile> | ||||
|  |     <Link> | ||||
|  |       <SubSystem>Console</SubSystem> | ||||
|  |       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||
|  |       <OptimizeReferences>true</OptimizeReferences> | ||||
|  |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|  |     </Link> | ||||
|  |   </ItemDefinitionGroup> | ||||
|  |   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||||
|  |   <ImportGroup Label="ExtensionTargets"> | ||||
|  |   </ImportGroup> | ||||
|  | </Project> | ||||
| @ -0,0 +1,22 @@ | |||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||
|  | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
|  |   <ItemGroup> | ||||
|  |     <Filter Include="Quelldateien"> | ||||
|  |       <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> | ||||
|  |       <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions> | ||||
|  |     </Filter> | ||||
|  |     <Filter Include="Headerdateien"> | ||||
|  |       <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> | ||||
|  |       <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions> | ||||
|  |     </Filter> | ||||
|  |     <Filter Include="Ressourcendateien"> | ||||
|  |       <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> | ||||
|  |       <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> | ||||
|  |     </Filter> | ||||
|  |   </ItemGroup> | ||||
|  |   <ItemGroup> | ||||
|  |     <ClCompile Include="..\src\main.cpp"> | ||||
|  |       <Filter>Quelldateien</Filter> | ||||
|  |     </ClCompile> | ||||
|  |   </ItemGroup> | ||||
|  | </Project> | ||||
| @ -0,0 +1,4 @@ | |||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||
|  | <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
|  |   <PropertyGroup /> | ||||
|  | </Project> | ||||
| @ -0,0 +1,105 @@ | |||||
|  | #ifdef _MSC_VER | ||||
|  | #define _CRT_SECURE_NO_WARNINGS | ||||
|  | #endif | ||||
|  | 
 | ||||
|  | #include <cstdint> | ||||
|  | #include <cstdio> | ||||
|  | #include <cstdlib> | ||||
|  | #include <cstring> | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | #define MAX_NUM_INVERTERS       3 | ||||
|  | #define MAX_NAME_LENGTH         16 | ||||
|  | #define NDEBUG | ||||
|  | #define NO_RADIO | ||||
|  | 
 | ||||
|  | #include "../../../hmDefines.h" | ||||
|  | #include "../../../hmInverter.h" | ||||
|  | #include "../../../hmSystem.h" | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | typedef int RadioType; | ||||
|  | typedef int BufferType; | ||||
|  | typedef Inverter<float> InverterType; | ||||
|  | typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void valToBuf(InverterType *iv, uint8_t fld, uint8_t ch, float val, uint8_t bufPos); | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | int main(int argc, char* argv[]) { | ||||
|  |     HmSystemType sys; | ||||
|  |     InverterType *iv0, *iv1; | ||||
|  |     uint8_t buf[30] = { 0xcc }; | ||||
|  | 
 | ||||
|  |     iv0 = sys.addInverter("HM1200", 0x1122334455ULL, INV_TYPE_HM1200); | ||||
|  |     iv1 = sys.addInverter("HM600",  0x1234567891ULL, INV_TYPE_HM600); | ||||
|  | 
 | ||||
|  |     valToBuf(iv0, FLD_UDC, CH1, 29.5,  3); | ||||
|  |     valToBuf(iv0, FLD_UDC, CH3, 30.6,  9); | ||||
|  |     valToBuf(iv0, FLD_YD,  CH1, 1234,  5); | ||||
|  |     valToBuf(iv0, FLD_YD,  CH2, 1199,  7); | ||||
|  |     valToBuf(iv0, FLD_YD,  CH3,  899, 11); | ||||
|  |     valToBuf(iv0, FLD_YD,  CH4,  932, 13); | ||||
|  |     valToBuf(iv0, FLD_YT,  CH1, 40.123, 13); | ||||
|  |     valToBuf(iv0, FLD_YT,  CH2, 57.231,  1); | ||||
|  |     valToBuf(iv0, FLD_YT,  CH3, 59.372,  3); | ||||
|  |     valToBuf(iv0, FLD_YT,  CH4, 43.966,  7); | ||||
|  | 
 | ||||
|  |     iv0->doCalculations(); | ||||
|  |     for(uint8_t i = 0; i < iv0->listLen; i ++) { | ||||
|  |         float val = iv0->getValue(i); | ||||
|  |         if(0.0 != val) { | ||||
|  |             printf("%10s [CH%d] = %.3f %s\n", iv0->getFieldName(i), iv0->getChannel(i), val, iv0->getUnit(i)); | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return 0; | ||||
|  | } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | //-----------------------------------------------------------------------------
 | ||||
|  | void valToBuf(InverterType *iv, uint8_t fld, uint8_t ch, float val, uint8_t bufPos) { | ||||
|  |     uint8_t buf[30] = { 0xcc }; | ||||
|  |     uint8_t len; | ||||
|  |     uint16_t factor; | ||||
|  | 
 | ||||
|  |     switch(fld) { | ||||
|  |         default:     len = 2; break; | ||||
|  |         case FLD_YT: len = 4; break; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     switch(fld) { | ||||
|  |         case FLD_YD:  factor = 1;    break; | ||||
|  |         case FLD_UDC: | ||||
|  |         case FLD_PDC: | ||||
|  |         case FLD_UAC: | ||||
|  |         case FLD_PAC: | ||||
|  |         case FLD_PCT: | ||||
|  |         case FLD_T:   factor = 10;   break; | ||||
|  |         case FLD_IDC: | ||||
|  |         case FLD_IAC: | ||||
|  |         case FLD_F:   factor = 100;  break; | ||||
|  |         default:      factor = 1000; break; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     uint8_t *p = &buf[bufPos]; | ||||
|  | 
 | ||||
|  |     uint32_t intval = (uint32_t)(val * factor); | ||||
|  |     if(2 == len) { | ||||
|  |         p[0] = (intval >> 8) & 0xff; | ||||
|  |         p[1] = (intval     ) & 0xff; | ||||
|  |     } | ||||
|  |     else { | ||||
|  |         p[0] = (intval >> 24) & 0xff; | ||||
|  |         p[1] = (intval >> 16) & 0xff; | ||||
|  |         p[2] = (intval >>  8) & 0xff; | ||||
|  |         p[3] = (intval      ) & 0xff; | ||||
|  |     } | ||||
|  |     iv->addValue(iv->getPosByChFld(ch, fld), buf); | ||||
|  | } | ||||
					Loading…
					
					
				
		Reference in new issue