mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-20 20:36:11 +02:00
Merge branch 'main' into pypackage
This commit is contained in:
commit
0946f1c765
67 changed files with 7717 additions and 564 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -10,3 +10,8 @@ compile_commands.json
|
|||
CTestTestfile.cmake
|
||||
_deps
|
||||
build
|
||||
/**/Debug
|
||||
/**/v16/*
|
||||
*.db
|
||||
*.suo
|
||||
*.ipch
|
||||
|
|
10
README.md
10
README.md
|
@ -5,7 +5,13 @@ Various tools, examples, and documentation for communicating with Hoymiles micro
|
|||
|
||||
In particular:
|
||||
|
||||
* `doc\hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
|
||||
* The `tools` folder contains various software tools for RaspberryPi and Arduino
|
||||
* `doc/hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project
|
||||
* `doc/getting-started-ESP8266.md` shows the hardware setup for an ESP8266-based system
|
||||
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32:
|
||||
* A [version for ESP8266](tools/esp8266) that includes a web interface
|
||||
* A [version for Arduino Nano](tools/nano/NRF24_SendRcv)
|
||||
* An [alternative Version of the above](tools/NRF24_SendRcv)
|
||||
* A [different implementation](tools/HoyDtuSim)
|
||||
* An [implementation for Raspberry Pi](tools/rpi) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker.
|
||||
|
||||
Contributors are always welcome!
|
||||
|
|
BIN
doc/AhoyMiles.fzz
Normal file
BIN
doc/AhoyMiles.fzz
Normal file
Binary file not shown.
BIN
doc/AhoyMiles_bb.png
Normal file
BIN
doc/AhoyMiles_bb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
doc/AhoyMiles_schem.png
Normal file
BIN
doc/AhoyMiles_schem.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
doc/HM-400 data.xlsx
Executable file
BIN
doc/HM-400 data.xlsx
Executable file
Binary file not shown.
BIN
doc/Hoymiles-SerialNumbers.xlsx
Normal file
BIN
doc/Hoymiles-SerialNumbers.xlsx
Normal file
Binary file not shown.
47
doc/getting-started-ESP8266.md
Normal file
47
doc/getting-started-ESP8266.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Getting Started with an ESP8266
|
||||
|
||||
Wire Connections
|
||||
|
||||
```ditaa
|
||||
+-----------+ +-----------+
|
||||
| ESP8266 |--colour--| nRF24L01+ |
|
||||
| | | |
|
||||
| GND |---black--|[GND] |
|
||||
| +3.3V |----red---| VCC |
|
||||
| D4 |---grey---| CE |
|
||||
| D8 |--purple--| CSN |
|
||||
| D5 |---blue---| SCK |
|
||||
| D7 |---green--| MOSI |
|
||||
| D6 |---brown--| MISO |
|
||||
| D3 |--yellow--| IRQ |
|
||||
+-----------+ +-----------+
|
||||
```
|
||||
|
||||

|
||||
|
||||
Fritzing diagrams & schematics
|
||||
* [AhoyMiles_bb.png](./AhoyMiles_bb.png)
|
||||
* [AhoyMiles_schem.png](./AhoyMiles_schem.png)
|
||||
* [AhoyMiles.fzz](./AhoyMiles.fzz)
|
||||
|
||||
Libraries to be installed in Arduino IDE:
|
||||
* RF24
|
||||
* TimeLib
|
||||
|
||||
Verify & Compile
|
||||
* Connect to WiFi Network `ESP AHOY`
|
||||
* Use password `esp_8266`
|
||||
* Connect to Network settings
|
||||
|
||||
Setup
|
||||
* WiFi
|
||||
* Enter SSID `mynetwork`
|
||||
* Enter Password `mypassword`
|
||||
* Device Host Name
|
||||
* Enter Device Name `esp-ahoy`
|
||||
* General
|
||||
* Hoymiles Address (e.g. 114173123456)
|
||||
* Choose inverter type
|
||||
* Set individual inverter name
|
||||
* [x] Reboot device after successful save
|
||||
Save
|
File diff suppressed because it is too large
Load diff
158
tools/HoyDtuSim/CircularBuffer.h
Normal file
158
tools/HoyDtuSim/CircularBuffer.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
CircularBuffer - An Arduino circular buffering library for arbitrary types.
|
||||
|
||||
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef CircularBuffer_h
|
||||
#define CircularBuffer_h
|
||||
|
||||
#ifdef ESP8266
|
||||
#define DISABLE_IRQ noInterrupts()
|
||||
#define RESTORE_IRQ interrupts()
|
||||
#else
|
||||
#define DISABLE_IRQ \
|
||||
uint8_t sreg = SREG; \
|
||||
cli();
|
||||
|
||||
#define RESTORE_IRQ \
|
||||
SREG = sreg;
|
||||
#endif
|
||||
|
||||
template <class T> class CircularBuffer
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* @param buffer Preallocated buffer of at least size records.
|
||||
* @param size Number of records available in the buffer.
|
||||
*/
|
||||
CircularBuffer(T* buffer, const uint8_t size )
|
||||
: m_size(size), m_buff(buffer)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
/** Clear all entries in the circular buffer. */
|
||||
void clear(void)
|
||||
{
|
||||
m_front = 0;
|
||||
m_fill = 0;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is empty */
|
||||
inline bool empty(void) const
|
||||
{
|
||||
return !m_fill;
|
||||
}
|
||||
|
||||
/** Return the number of records stored in the buffer */
|
||||
inline uint8_t available(void) const
|
||||
{
|
||||
return m_fill;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is full */
|
||||
inline bool full(void) const
|
||||
{
|
||||
return m_fill == m_size;
|
||||
}
|
||||
|
||||
/** Aquire record on front of the buffer, for writing.
|
||||
* After filling the record, it has to be pushed to actually
|
||||
* add it to the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is full.
|
||||
*/
|
||||
T* getFront(void) const
|
||||
{
|
||||
DISABLE_IRQ;
|
||||
T* f = NULL;
|
||||
if (!full())
|
||||
f = get(m_front);
|
||||
RESTORE_IRQ;
|
||||
return f;
|
||||
}
|
||||
|
||||
/** Push record to front of the buffer
|
||||
* @param record Record to push. If record was aquired previously (using getFront) its
|
||||
* data will not be copied as it is already present in the buffer.
|
||||
* @return True, when record was pushed successfully.
|
||||
*/
|
||||
bool pushFront(T* record)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!full())
|
||||
{
|
||||
T* f = get(m_front);
|
||||
if (f != record)
|
||||
*f = *record;
|
||||
m_front = (m_front+1) % m_size;
|
||||
m_fill++;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** Aquire record on back of the buffer, for reading.
|
||||
* After reading the record, it has to be pop'ed to actually
|
||||
* remove it from the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is empty.
|
||||
*/
|
||||
T* getBack(void) const
|
||||
{
|
||||
T* b = NULL;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
b = get(back());
|
||||
RESTORE_IRQ;
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Remove record from back of the buffer.
|
||||
* @return True, when record was pop'ed successfully.
|
||||
*/
|
||||
bool popBack(void)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
{
|
||||
m_fill--;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
protected:
|
||||
inline T * get(const uint8_t idx) const
|
||||
{
|
||||
return &(m_buff[idx]);
|
||||
}
|
||||
inline uint8_t back(void) const
|
||||
{
|
||||
return (m_front - m_fill + m_size) % m_size;
|
||||
}
|
||||
|
||||
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
T* const m_buff; // Ptr to buffer holding all records.
|
||||
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
};
|
||||
|
||||
#endif // CircularBuffer_h
|
23
tools/HoyDtuSim/Debug.h
Normal file
23
tools/HoyDtuSim/Debug.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef __DEBUG_H
|
||||
|
||||
#define __DEBUG_H
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_OUT Serial
|
||||
#else
|
||||
//---
|
||||
// disable Serial DEBUG output
|
||||
#define DEBUG_OUT DummySerial
|
||||
static class {
|
||||
public:
|
||||
void begin(...) {}
|
||||
void print(...) {}
|
||||
void println(...) {}
|
||||
void flush() {}
|
||||
bool available() { return false;}
|
||||
int readBytes(...) { return 0;}
|
||||
int printf (...) {return 0;}
|
||||
} DummySerial;
|
||||
#endif
|
||||
|
||||
#endif
|
38
tools/HoyDtuSim/HM1200.h
Normal file
38
tools/HoyDtuSim/HM1200.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef __HM1200_H
|
||||
#define __HM1200_H
|
||||
|
||||
#define HM1200
|
||||
|
||||
const measureDef_t hm1200_measureDef[] = {
|
||||
{ IDX_UDC, UNIT_V, CH1, CMD01, 14, BYTES2, DIV10 },
|
||||
{ IDX_IDC, UNIT_A, CH1, CMD01, 16, BYTES2, DIV100 },
|
||||
{ IDX_PDC, UNIT_W, CH1, CMD01, 20, BYTES2, DIV10 },
|
||||
{ IDX_E_TAG, UNIT_WH, CH1, CMD02, 16, BYTES2, DIV1 },
|
||||
{ IDX_E_TOTAL, UNIT_KWH, CH1, CMD01, 24, BYTES4, DIV1000 },
|
||||
{ IDX_UDC, UNIT_V, CH2, CMD02, 20, BYTES2, DIV10 },
|
||||
{ IDX_IDC, UNIT_A, CH2, CMD01, 18, BYTES2, DIV100 },
|
||||
{ IDX_PDC, UNIT_W, CH2, CMD01, 22, BYTES2, DIV10 },
|
||||
{ IDX_E_TAG, UNIT_WH, CH2, CMD02, 18, BYTES2, DIV1 },
|
||||
{ IDX_E_TOTAL, UNIT_KWH, CH2, CMD02, 12, BYTES4, DIV1000 },
|
||||
{ IDX_IDC, UNIT_A, CH3, CMD02, 22, BYTES2, DIV100 },
|
||||
{ IDX_PDC, UNIT_W, CH3, CMD02, 26, BYTES2, DIV10 },
|
||||
{ IDX_E_TAG, UNIT_WH, CH3, CMD03, 22, BYTES2, DIV1 },
|
||||
{ IDX_E_TOTAL, UNIT_KWH, CH3, CMD03, 14, BYTES4, DIV1000 },
|
||||
{ IDX_IDC, UNIT_A, CH4, CMD02, 24, BYTES2, DIV100 },
|
||||
{ IDX_PDC, UNIT_W, CH4, CMD03, 12, BYTES2, DIV10 },
|
||||
{ IDX_E_TAG, UNIT_WH, CH4, CMD03, 24, BYTES2, DIV1 },
|
||||
{ IDX_E_TOTAL, UNIT_KWH, CH4, CMD03, 18, BYTES4, DIV1000 },
|
||||
{ IDX_UAC, UNIT_V, CH0, CMD03, 26, BYTES2, DIV10 },
|
||||
{ IDX_IPV, UNIT_A, CH0, CMD84, 18, BYTES2, DIV100 },
|
||||
{ IDX_PAC, UNIT_W, CH0, CMD84, 14, BYTES2, DIV10 },
|
||||
{ IDX_FREQ, UNIT_HZ, CH0, CMD84, 12, BYTES2, DIV100 },
|
||||
{ IDX_PERCNT, UNIT_PCT, CH0, CMD84, 20, BYTES2, DIV10 },
|
||||
{ IDX_WR_TEMP, UNIT_C, CH0, CMD84, 22, BYTES2, DIV10 }
|
||||
};
|
||||
|
||||
measureCalc_t hm1200_measureCalc[] = {};
|
||||
|
||||
#define HM1200_MEASURE_LIST_LEN sizeof(hm1200_measureDef)/sizeof(measureDef_t)
|
||||
#define HM1200_CALCED_LIST_LEN 0
|
||||
|
||||
#endif
|
37
tools/HoyDtuSim/HM600.h
Normal file
37
tools/HoyDtuSim/HM600.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef __HM600_H
|
||||
#define __HM600_H
|
||||
|
||||
#define HM600
|
||||
#define HM700
|
||||
|
||||
|
||||
float calcEheute (float *measure) { return measure[8] + measure[9]; }
|
||||
float calcIpv (float *measure) { return (measure[10] != 0 ? measure[12]/measure[10] : 0); }
|
||||
|
||||
const measureDef_t hm600_measureDef[] = {
|
||||
{ IDX_UDC, CH1, UNIT_V, CMD01, 14, BYTES2, DIV10},
|
||||
{ IDX_IDC, CH1, UNIT_A, CMD01, 16, BYTES2, DIV100},
|
||||
{ IDX_PDC, CH1, UNIT_W, CMD01, 18, BYTES2, DIV10},
|
||||
{ IDX_UDC, CH2, UNIT_V, CMD01, 20, BYTES2, DIV10},
|
||||
{ IDX_IDC, CH2, UNIT_A, CMD01, 22, BYTES2, DIV100},
|
||||
{ IDX_PDC, CH2, UNIT_W, CMD01, 24, BYTES2, DIV10},
|
||||
{ IDX_E_WOCHE,CH0, UNIT_WH, CMD02, 12, BYTES2, DIV1},
|
||||
{ IDX_E_TOTAL,CH0, UNIT_WH, CMD02, 14, BYTES4, DIV1},
|
||||
{ IDX_E_TAG, CH1, UNIT_WH, CMD02, 18, BYTES2, DIV1},
|
||||
{ IDX_E_TAG, CH2, UNIT_WH, CMD02, 20, BYTES2, DIV1},
|
||||
{ IDX_UAC, CH0, UNIT_V, CMD02, 22, BYTES2, DIV10},
|
||||
{ IDX_FREQ, CH0, UNIT_HZ, CMD02, 24, BYTES2, DIV100},
|
||||
{ IDX_PAC, CH0, UNIT_W, CMD02, 26, BYTES2, DIV10},
|
||||
{ IDX_WR_TEMP,CH0, UNIT_C, CMD83, 18, BYTES2, DIV10}
|
||||
};
|
||||
|
||||
|
||||
measureCalc_t hm600_measureCalc[] = {
|
||||
{ IDX_E_HEUTE, UNIT_WH, DIV1, &calcEheute},
|
||||
{ IDX_IPV, UNIT_A, DIV100, &calcIpv}
|
||||
};
|
||||
|
||||
#define HM600_MEASURE_LIST_LEN sizeof(hm600_measureDef)/sizeof(measureDef_t)
|
||||
#define HM600_CALCED_LIST_LEN sizeof(hm600_measureCalc)/sizeof(measureCalc_t)
|
||||
|
||||
#endif
|
605
tools/HoyDtuSim/HoyDtuSim.ino
Normal file
605
tools/HoyDtuSim/HoyDtuSim.ino
Normal file
|
@ -0,0 +1,605 @@
|
|||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include "CircularBuffer.h"
|
||||
#include <RF24.h>
|
||||
#include "printf.h"
|
||||
#include <RF24_config.h>
|
||||
#include "hm_crc.h"
|
||||
#include "hm_packets.h"
|
||||
|
||||
#include "Settings.h" // Header für Einstellungen
|
||||
#include "Debug.h"
|
||||
#include "Inverters.h"
|
||||
|
||||
const char VERSION[] PROGMEM = "0.1.6";
|
||||
|
||||
|
||||
#ifdef ESP8266
|
||||
#define DISABLE_EINT noInterrupts()
|
||||
#define ENABLE_EINT interrupts()
|
||||
#else // für AVR z.B. ProMini oder Nano
|
||||
#define DISABLE_EINT EIMSK = 0x00
|
||||
#define ENABLE_EINT EIMSK = 0x01
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ESP8266
|
||||
#define PACKET_BUFFER_SIZE (30)
|
||||
#else
|
||||
#define PACKET_BUFFER_SIZE (20)
|
||||
#endif
|
||||
|
||||
// Startup defaults until user reconfigures it
|
||||
//#define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles
|
||||
//#define DEFAULT_SEND_CHANNEL (75) // 40 = Default channel for Hoymiles, 61
|
||||
|
||||
static HM_Packets hmPackets;
|
||||
static uint32_t tickMillis;
|
||||
|
||||
// Set up nRF24L01 radio on SPI bus plus CE/CS pins
|
||||
// If more than one RF24 unit is used the another CS pin than 10 must be used
|
||||
// This pin is used hard coded in SPI library
|
||||
static RF24 Radio (RF1_CE_PIN, RF1_CS_PIN);
|
||||
|
||||
static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE];
|
||||
|
||||
static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0]));
|
||||
|
||||
static Serial_header_t SerialHdr;
|
||||
|
||||
#define CHECKCRC 1
|
||||
static uint16_t lastCRC;
|
||||
static uint16_t crc;
|
||||
|
||||
uint8_t channels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
uint8_t channelIdx = 2; // fange mit 40 an
|
||||
uint8_t DEFAULT_SEND_CHANNEL = channels[channelIdx]; // = 40
|
||||
|
||||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV
|
||||
uint8_t rcvChannelIdx = 0;
|
||||
uint8_t rcvChannels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
uint8_t DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx]; //3;
|
||||
uint8_t intvl = 4; // Zeit für poor man hopping
|
||||
int hophop;
|
||||
#else
|
||||
uint8_t DEFAULT_RECV_CHANNEL = 3;
|
||||
#endif
|
||||
|
||||
boolean valueChanged = false;
|
||||
|
||||
static unsigned long timeLastPacket = millis();
|
||||
static unsigned long timeLastIstTagCheck = millis();
|
||||
static unsigned long timeLastRcvChannelSwitch = millis();
|
||||
|
||||
// Function forward declaration
|
||||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len);
|
||||
|
||||
|
||||
static const char BLANK = ' ';
|
||||
|
||||
static boolean istTag = true;
|
||||
|
||||
char CHANNELNAME_BUFFER[15];
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "wifi.h"
|
||||
#include "ModWebserver.h"
|
||||
#include "Sonne.h"
|
||||
#endif
|
||||
|
||||
|
||||
inline static void dumpData(uint8_t *p, int len) {
|
||||
//-----------------------------------------------
|
||||
while (len > 0){
|
||||
if (*p < 16)
|
||||
DEBUG_OUT.print(F("0"));
|
||||
DEBUG_OUT.print(*p++, HEX);
|
||||
len--;
|
||||
}
|
||||
DEBUG_OUT.print(BLANK);
|
||||
}
|
||||
|
||||
|
||||
float extractValue2 (uint8_t *p, int divisor) {
|
||||
//-------------------------------------------
|
||||
uint16_t b1 = *p++;
|
||||
return ((float) (b1 << 8) + *p) / (float) divisor;
|
||||
}
|
||||
|
||||
|
||||
float extractValue4 (uint8_t *p, int divisor) {
|
||||
//-------------------------------------------
|
||||
uint32_t ret = *p++;
|
||||
for (uint8_t i = 1; i <= 3; i++)
|
||||
ret = (ret << 8) + *p++;
|
||||
return (ret / divisor);
|
||||
}
|
||||
|
||||
void outChannel (uint8_t wr, uint8_t i) {
|
||||
//------------------------------------
|
||||
DEBUG_OUT.print(getMeasureName(wr, i));
|
||||
DEBUG_OUT.print(F("\t:"));
|
||||
DEBUG_OUT.print(getMeasureValue(wr,i));
|
||||
DEBUG_OUT.println(BLANK);
|
||||
}
|
||||
|
||||
|
||||
void analyseWords (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
DEBUG_OUT.print (F("analyse words:"));
|
||||
p++;
|
||||
for (int i = 0; i <12;i++) {
|
||||
DEBUG_OUT.print(extractValue2(p,1));
|
||||
DEBUG_OUT.print(BLANK);
|
||||
p++;
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
void analyseLongs (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
DEBUG_OUT.print (F("analyse longs:"));
|
||||
p++;
|
||||
for (int i = 0; i <12;i++) {
|
||||
DEBUG_OUT.print(extractValue4(p,1));
|
||||
DEBUG_OUT.print(BLANK);
|
||||
p++;
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
|
||||
void analyse (NRF24_packet_t *p) {
|
||||
//------------------------------
|
||||
uint8_t wrIdx = findInverter (&p->packet[3]);
|
||||
//DEBUG_OUT.print ("wrIdx="); DEBUG_OUT.println (wrIdx);
|
||||
if (wrIdx == 0xFF) return;
|
||||
uint8_t cmd = p->packet[11];
|
||||
float val = 0;
|
||||
if (cmd == 0x01 || cmd == 0x02 || cmd == 0x83) {
|
||||
const measureDef_t *defs = inverters[wrIdx].measureDef;
|
||||
|
||||
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasures; i++) {
|
||||
if (defs[i].teleId == cmd) {
|
||||
uint8_t pos = defs[i].pos;
|
||||
if (defs[i].bytes == 2)
|
||||
val = extractValue2 (&p->packet[pos], getDivisor(wrIdx, i) );
|
||||
else if (defs[i].bytes == 4)
|
||||
val = extractValue4 (&p->packet[pos], getDivisor(wrIdx, i) );
|
||||
valueChanged = valueChanged ||(val != inverters[wrIdx].values[i]);
|
||||
inverters[wrIdx].values[i] = val;
|
||||
}
|
||||
}
|
||||
// calculated funstions
|
||||
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasureCalculated; i++) {
|
||||
val = inverters[wrIdx].measureCalculated[i].f (inverters[wrIdx].values);
|
||||
int idx = inverters[wrIdx].anzMeasures + i;
|
||||
valueChanged = valueChanged ||(val != inverters[wrIdx].values[idx]);
|
||||
inverters[wrIdx].values[idx] = val;
|
||||
}
|
||||
}
|
||||
else if (cmd == 0x81) {
|
||||
;
|
||||
}
|
||||
else {
|
||||
DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX);
|
||||
analyseWords (&p->packet[11]);
|
||||
analyseLongs (&p->packet[11]);
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
if (p->packetsLost > 0) {
|
||||
DEBUG_OUT.print(F(" Lost: "));
|
||||
DEBUG_OUT.println(p->packetsLost);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
IRAM_ATTR
|
||||
#endif
|
||||
void handleNrf1Irq() {
|
||||
//-------------------------
|
||||
static uint8_t lostPacketCount = 0;
|
||||
uint8_t pipe;
|
||||
|
||||
DISABLE_EINT;
|
||||
|
||||
// Loop until RX buffer(s) contain no more packets.
|
||||
while (Radio.available(&pipe)) {
|
||||
if (!packetBuffer.full()) {
|
||||
NRF24_packet_t *p = packetBuffer.getFront();
|
||||
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
||||
p->packetsLost = lostPacketCount;
|
||||
p->rcvChannel = DEFAULT_RECV_CHANNEL;
|
||||
uint8_t packetLen = Radio.getPayloadSize();
|
||||
if (packetLen > MAX_RF_PAYLOAD_SIZE)
|
||||
packetLen = MAX_RF_PAYLOAD_SIZE;
|
||||
|
||||
Radio.read(p->packet, packetLen);
|
||||
packetBuffer.pushFront(p);
|
||||
lostPacketCount = 0;
|
||||
}
|
||||
else {
|
||||
// Buffer full. Increase lost packet counter.
|
||||
bool tx_ok, tx_fail, rx_ready;
|
||||
if (lostPacketCount < 255)
|
||||
lostPacketCount++;
|
||||
// Call 'whatHappened' to reset interrupt status.
|
||||
Radio.whatHappened(tx_ok, tx_fail, rx_ready);
|
||||
// Flush buffer to drop the packet.
|
||||
Radio.flush_rx();
|
||||
}
|
||||
}
|
||||
ENABLE_EINT;
|
||||
}
|
||||
|
||||
|
||||
static void activateConf(void) {
|
||||
//-----------------------------
|
||||
Radio.begin();
|
||||
// Disable shockburst for receiving and decode payload manually
|
||||
Radio.setAutoAck(false);
|
||||
Radio.setRetries(0, 0);
|
||||
Radio.setChannel(DEFAULT_RECV_CHANNEL);
|
||||
Radio.setDataRate(DEFAULT_RF_DATARATE);
|
||||
Radio.disableCRC();
|
||||
Radio.setAutoAck(0x00);
|
||||
Radio.setPayloadSize(MAX_RF_PAYLOAD_SIZE);
|
||||
Radio.setAddressWidth(5);
|
||||
Radio.openReadingPipe(1, DTU_RADIO_ID);
|
||||
|
||||
// We want only RX irqs
|
||||
Radio.maskIRQ(true, true, false);
|
||||
|
||||
// Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
|
||||
Radio.setPALevel(RF24_PA_MAX);
|
||||
Radio.startListening();
|
||||
|
||||
// Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
|
||||
attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
|
||||
|
||||
// Initialize SerialHdr header's address member to promiscuous address.
|
||||
uint64_t addr = DTU_RADIO_ID;
|
||||
for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) {
|
||||
SerialHdr.address[i] = addr;
|
||||
addr >>= 8;
|
||||
}
|
||||
|
||||
//Radio.printDetails();
|
||||
//DEBUG_OUT.println();
|
||||
tickMillis = millis() + 200;
|
||||
}
|
||||
|
||||
#define resetRF24() activateConf()
|
||||
|
||||
|
||||
void setup(void) {
|
||||
//--------------
|
||||
#ifndef DEBUG
|
||||
#ifndef ESP8266
|
||||
Serial.begin(SER_BAUDRATE);
|
||||
#endif
|
||||
#endif
|
||||
printf_begin();
|
||||
DEBUG_OUT.begin(SER_BAUDRATE);
|
||||
DEBUG_OUT.flush();
|
||||
|
||||
DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --"));
|
||||
|
||||
// Configure nRF IRQ input
|
||||
pinMode(RF1_IRQ_PIN, INPUT);
|
||||
|
||||
activateConf();
|
||||
|
||||
#ifdef ESP8266
|
||||
setupWifi();
|
||||
setupClock();
|
||||
setupWebServer();
|
||||
setupUpdateByOTA();
|
||||
calcSunUpDown (getNow());
|
||||
istTag = isDayTime();
|
||||
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht"));
|
||||
hmPackets.SetUnixTimeStamp (getNow());
|
||||
#else
|
||||
hmPackets.SetUnixTimeStamp(0x62456430);
|
||||
#endif
|
||||
|
||||
setupInverts();
|
||||
}
|
||||
|
||||
uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE];
|
||||
|
||||
void isTime2Send () {
|
||||
//-----------------
|
||||
// Second timer
|
||||
static const uint8_t warteZeit = 1;
|
||||
static uint8_t tickSec = 0;
|
||||
if (millis() >= tickMillis) {
|
||||
static uint8_t tel = 0;
|
||||
tickMillis += warteZeit*1000; //200;
|
||||
tickSec++;
|
||||
|
||||
if (++tickSec >= 1) { // 5
|
||||
for (uint8_t c=0; c < warteZeit; c++) hmPackets.UnixTimeStampTick();
|
||||
tickSec = 0;
|
||||
}
|
||||
|
||||
int32_t size = 0;
|
||||
uint64_t dest = 0;
|
||||
for (uint8_t wr = 0; wr < anzInv; wr++) {
|
||||
dest = inverters[wr].RadioId;
|
||||
|
||||
if (tel > 1)
|
||||
tel = 0;
|
||||
|
||||
if (tel == 0) {
|
||||
#ifdef ESP8266
|
||||
hmPackets.SetUnixTimeStamp (getNow());
|
||||
#endif
|
||||
size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8);
|
||||
//DEBUG_OUT.print ("Timepacket mit cid="); DEBUG_OUT.println(sendBuf[10], HEX);
|
||||
}
|
||||
else if (tel <= 1)
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80 + tel - 1);
|
||||
|
||||
SendPacket (dest, (uint8_t *)&sendBuf, size);
|
||||
} // for wr
|
||||
|
||||
tel++;
|
||||
|
||||
/* for (uint8_t warte = 0; warte < 2; warte++) {
|
||||
delay(1000);
|
||||
hmPackets.UnixTimeStampTick();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) {
|
||||
//-----------------------------------------------------
|
||||
|
||||
// Write timestamp, packets lost, address and payload length
|
||||
//printf(" %09lu ", SerialHdr.timestamp);
|
||||
char _buf[20];
|
||||
sprintf_P(_buf, PSTR("rcv CH:%d "), p->rcvChannel);
|
||||
DEBUG_OUT.print (_buf);
|
||||
dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost));
|
||||
dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address));
|
||||
|
||||
// Trailing bit?!?
|
||||
dumpData(&p->packet[0], 2);
|
||||
|
||||
// Payload length from PCF
|
||||
dumpData(&payloadLen, sizeof(payloadLen));
|
||||
|
||||
// Packet control field - PID Packet identification
|
||||
uint8_t val = (p->packet[1] >> 1) & 0x03;
|
||||
DEBUG_OUT.print(val);
|
||||
DEBUG_OUT.print(F(" "));
|
||||
|
||||
if (payloadLen > 9) {
|
||||
dumpData(&p->packet[2], 1);
|
||||
dumpData(&p->packet[3], 4);
|
||||
dumpData(&p->packet[7], 4);
|
||||
|
||||
uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4;
|
||||
|
||||
if (remain < 32) {
|
||||
dumpData(&p->packet[11], remain);
|
||||
printf_P(PSTR("%04X "), crc);
|
||||
|
||||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3]))
|
||||
DEBUG_OUT.print(0);
|
||||
else
|
||||
DEBUG_OUT.print(1);
|
||||
}
|
||||
else {
|
||||
DEBUG_OUT.print(F("Ill remain "));
|
||||
DEBUG_OUT.print(remain);
|
||||
}
|
||||
}
|
||||
else {
|
||||
dumpData(&p->packet[2], payloadLen + 2);
|
||||
printf_P(PSTR("%04X "), crc);
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
DEBUG_OUT.flush();
|
||||
}
|
||||
|
||||
void writeArduinoInterface() {
|
||||
//--------------------------
|
||||
if (valueChanged) {
|
||||
for (uint8_t wr = 0; wr < anzInv; wr++) {
|
||||
if (anzInv > 1) {
|
||||
Serial.print(wr); Serial.print('.');
|
||||
}
|
||||
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) {
|
||||
Serial.print(getMeasureName(wr,i)); // Schnittstelle bei Arduino
|
||||
Serial.print('=');
|
||||
Serial.print(getMeasureValue(wr,i), getDigits(wr,i)); // Schnittstelle bei Arduino
|
||||
Serial.print (BLANK);
|
||||
Serial.println (getUnit(wr, i));
|
||||
} // for i
|
||||
|
||||
} // for wr
|
||||
Serial.println(F("-----------------------"));
|
||||
valueChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean doCheckCrc (NRF24_packet_t *p, uint8_t payloadLen) {
|
||||
//--------------------------------------------------------
|
||||
crc = 0xFFFF;
|
||||
crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address)));
|
||||
// Payload length
|
||||
// Add one byte and one bit for 9-bit packet control field
|
||||
crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1);
|
||||
|
||||
if (CHECKCRC) {
|
||||
// If CRC is invalid only show lost packets
|
||||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) {
|
||||
if (p->packetsLost > 0) {
|
||||
DEBUG_OUT.print(F(" Lost: "));
|
||||
DEBUG_OUT.println(p->packetsLost);
|
||||
}
|
||||
packetBuffer.popBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dump a decoded packet only once
|
||||
if (lastCRC == crc) {
|
||||
packetBuffer.popBack();
|
||||
return false;
|
||||
}
|
||||
lastCRC = crc;
|
||||
}
|
||||
|
||||
// Don't dump mysterious ack packages
|
||||
if (payloadLen == 0) {
|
||||
packetBuffer.popBack();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void poorManChannelHopping() {
|
||||
//--------------------------
|
||||
if (hophop <= 0) return;
|
||||
if (millis() >= timeLastRcvChannelSwitch + intvl) {
|
||||
rcvChannelIdx++;
|
||||
if (rcvChannelIdx >= sizeof(rcvChannels))
|
||||
rcvChannelIdx = 0;
|
||||
DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx];
|
||||
DISABLE_EINT;
|
||||
Radio.stopListening();
|
||||
Radio.setChannel (DEFAULT_RECV_CHANNEL);
|
||||
Radio.startListening();
|
||||
ENABLE_EINT;
|
||||
timeLastRcvChannelSwitch = millis();
|
||||
hophop--;
|
||||
}
|
||||
|
||||
}
|
||||
void loop(void) {
|
||||
//=============
|
||||
// poor man channel hopping on receive
|
||||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV
|
||||
poorManChannelHopping();
|
||||
#endif
|
||||
|
||||
if (millis() > timeLastPacket + 50000UL) {
|
||||
DEBUG_OUT.println (F("Reset RF24"));
|
||||
resetRF24();
|
||||
timeLastPacket = millis();
|
||||
}
|
||||
|
||||
while (!packetBuffer.empty()) {
|
||||
timeLastPacket = millis();
|
||||
// One or more records present
|
||||
NRF24_packet_t *p = packetBuffer.getBack();
|
||||
|
||||
// Shift payload data due to 9-bit packet control field
|
||||
for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) {
|
||||
if (j > 0)
|
||||
p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1);
|
||||
else
|
||||
p->packet[j] = (byte)(p->packet[j] >> 7);
|
||||
}
|
||||
|
||||
SerialHdr.timestamp = p->timestamp;
|
||||
SerialHdr.packetsLost = p->packetsLost;
|
||||
|
||||
uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3);
|
||||
// Check CRC
|
||||
if (! doCheckCrc(p, payloadLen) )
|
||||
continue;
|
||||
|
||||
#ifdef DEBUG
|
||||
uint8_t cmd = p->packet[11];
|
||||
//if (cmd != 0x01 && cmd != 0x02 && cmd != 0x83 && cmd != 0x81)
|
||||
outputPacket (p, payloadLen);
|
||||
#endif
|
||||
|
||||
analyse (p);
|
||||
|
||||
#ifndef ESP8266
|
||||
writeArduinoInterface();
|
||||
#endif
|
||||
|
||||
// Remove record as we're done with it.
|
||||
packetBuffer.popBack();
|
||||
}
|
||||
|
||||
if (istTag)
|
||||
isTime2Send();
|
||||
|
||||
#ifdef ESP8266
|
||||
checkWifi();
|
||||
webserverHandle();
|
||||
checkUpdateByOTA();
|
||||
if (hour() == 0 && minute() == 0) {
|
||||
calcSunUpDown(getNow());
|
||||
delay (60*1000);
|
||||
}
|
||||
|
||||
if (millis() > timeLastIstTagCheck + 15UL * 60UL * 1000UL) { // alle 15 Minuten neu berechnen ob noch hell
|
||||
istTag = isDayTime();
|
||||
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht"));
|
||||
timeLastIstTagCheck = millis();
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
if (millis() > timeLastPacket + 60UL*SECOND) { // 60 Sekunden
|
||||
channelIdx++;
|
||||
if (channelIdx >= sizeof(channels)) channelIdx = 0;
|
||||
DEFAULT_SEND_CHANNEL = channels[channelIdx];
|
||||
DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL);
|
||||
timeLastPacket = millis();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) {
|
||||
//--------------------------------------------------------------
|
||||
//DEBUG_OUT.print (F("Sende: ")); DEBUG_OUT.println (buf[9], HEX);
|
||||
//dumpData (buf, len); DEBUG_OUT.println();
|
||||
DISABLE_EINT;
|
||||
Radio.stopListening();
|
||||
|
||||
#ifdef CHANNEL_HOP
|
||||
static uint8_t hop = 0;
|
||||
#if DEBUG_SEND
|
||||
DEBUG_OUT.print(F("Send... CH"));
|
||||
DEBUG_OUT.println(channels[hop]);
|
||||
#endif
|
||||
Radio.setChannel(channels[hop++]);
|
||||
if (hop >= sizeof(channels) / sizeof(channels[0]))
|
||||
hop = 0;
|
||||
#else
|
||||
Radio.setChannel(DEFAULT_SEND_CHANNEL);
|
||||
#endif
|
||||
|
||||
Radio.openWritingPipe(dest);
|
||||
Radio.setCRCLength(RF24_CRC_16);
|
||||
Radio.enableDynamicPayloads();
|
||||
Radio.setAutoAck(true);
|
||||
Radio.setRetries(3, 15);
|
||||
|
||||
bool res = Radio.write(buf, len);
|
||||
// Try to avoid zero payload acks (has no effect)
|
||||
Radio.openWritingPipe(DUMMY_RADIO_ID);
|
||||
|
||||
Radio.setAutoAck(false);
|
||||
Radio.setRetries(0, 0);
|
||||
Radio.disableDynamicPayloads();
|
||||
Radio.setCRCLength(RF24_CRC_DISABLED);
|
||||
|
||||
Radio.setChannel(DEFAULT_RECV_CHANNEL);
|
||||
Radio.startListening();
|
||||
ENABLE_EINT;
|
||||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV
|
||||
hophop = 5 * sizeof(rcvChannels);
|
||||
#endif
|
||||
}
|
283
tools/HoyDtuSim/Inverters.h
Normal file
283
tools/HoyDtuSim/Inverters.h
Normal file
|
@ -0,0 +1,283 @@
|
|||
#ifndef __INVERTERS_H
|
||||
#define __INVERTERS_H
|
||||
|
||||
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Debug.h"
|
||||
|
||||
|
||||
typedef struct _NRF24_packet_t {
|
||||
uint32_t timestamp;
|
||||
uint8_t packetsLost;
|
||||
uint8_t rcvChannel;
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
|
||||
} NRF24_packet_t;
|
||||
|
||||
|
||||
typedef struct _Serial_header_t {
|
||||
unsigned long timestamp;
|
||||
uint8_t packetsLost;
|
||||
uint8_t address[RF_MAX_ADDR_WIDTH]; // MSB first, always RF_MAX_ADDR_WIDTH bytes.
|
||||
} Serial_header_t;
|
||||
|
||||
|
||||
// structs für Inverter und Kanalwerte
|
||||
|
||||
// Liste der Einheiten
|
||||
enum UNITS {UNIT_V = 0, UNIT_HZ, UNIT_A, UNIT_W, UNIT_WH, UNIT_C, UNIT_KWH, UNIT_MA, UNIT_PCT};
|
||||
const char* const units[] = {"V", "Hz", "A", "W", "Wh", "°C", "KWh", "mA", "%"};
|
||||
|
||||
// CH0 is default channel (freq, ac, temp)
|
||||
enum CHANNELS {CH0 = 0, CH1, CH2, CH3, CH4};
|
||||
enum CMDS {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84};
|
||||
enum DIVS {DIV1 = 0, DIV10, DIV100, DIV1000};
|
||||
|
||||
#define BYTES2 2
|
||||
#define BYTES4 4
|
||||
|
||||
const char UDC[] PROGMEM = "Udc";
|
||||
const char IDC[] PROGMEM = "Idc";
|
||||
const char PDC[] PROGMEM = "Pdc";
|
||||
const char E_WOCHE[] PROGMEM = "E-Woche";
|
||||
const char E_TOTAL[] PROGMEM = "E-Total";
|
||||
const char E_TAG[] PROGMEM = "E-Tag";
|
||||
const char UAC[] PROGMEM = "Uac";
|
||||
const char FREQ[] PROGMEM = "Freq.ac";
|
||||
const char PAC[] PROGMEM = "Pac";
|
||||
const char E_HEUTE[] PROGMEM = "E-heute";
|
||||
const char IPV[] PROGMEM = "Ipv";
|
||||
const char WR_TEMP[] PROGMEM = "WR-Temp";
|
||||
const char PERCNT[] PROGMEM = "Pct";
|
||||
|
||||
#define IDX_UDC 0
|
||||
#define IDX_IDC 1
|
||||
#define IDX_PDC 2
|
||||
#define IDX_E_WOCHE 3
|
||||
#define IDX_E_TOTAL 4
|
||||
#define IDX_E_TAG 5
|
||||
#define IDX_UAC 6
|
||||
#define IDX_FREQ 7
|
||||
#define IDX_PAC 8
|
||||
#define IDX_E_HEUTE 9
|
||||
#define IDX_IPV 10
|
||||
#define IDX_WR_TEMP 11
|
||||
#define IDX_PERCNT 12
|
||||
|
||||
const char* const NAMES[]
|
||||
= {UDC, IDC, PDC, E_WOCHE, E_TOTAL, E_TAG, UAC, FREQ, PAC, E_HEUTE, IPV, WR_TEMP, PERCNT};
|
||||
|
||||
typedef float (*calcValueFunc)(float *);
|
||||
|
||||
struct measureDef_t {
|
||||
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
||||
uint8_t channel; // 0..4,
|
||||
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
||||
uint8_t teleId; // Telegramm ID, das was hinter der 2. WR Nummer im Telegramm, 02, 03, 83
|
||||
uint8_t pos; // ab dieser POsition beginnt der Wert (Big Endian)
|
||||
uint8_t bytes; // Anzahl der Bytes
|
||||
uint8_t digits;
|
||||
};
|
||||
|
||||
struct measureCalc_t {
|
||||
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
||||
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
||||
uint8_t digits;
|
||||
calcValueFunc f; // die Funktion zur Berechnung von Werten, zb Summe von Werten
|
||||
};
|
||||
|
||||
|
||||
struct inverter_t {
|
||||
uint8_t ID; // Inverter-ID = Index
|
||||
char name[20]; // Name des Inverters zb HM-600.1
|
||||
uint64_t serialNo; // dier Seriennummer wie im Barcode auf dem WR, also 1141.....
|
||||
uint64_t RadioId; // die gespiegelte (letzte 4 "Bytes") der Seriennummer
|
||||
const measureDef_t *measureDef; // aus Include HMxxx.h : Liste mit Definitionen der Messwerte, wie Telgramm, offset, länge, ...
|
||||
uint8_t anzMeasures; // Länge der Liste
|
||||
measureCalc_t *measureCalculated; // Liste mit Defintion für berechnete Werte
|
||||
uint8_t anzMeasureCalculated; // Länge der Liste
|
||||
uint8_t anzTotalMeasures; // Gesamtanzahl Messwerte
|
||||
float values[MAX_MEASURE_PER_INV]; // DIE Messewerte
|
||||
};
|
||||
|
||||
|
||||
char _buffer[20];
|
||||
|
||||
uint8_t anzInv = 0;
|
||||
inverter_t inverters[MAX_ANZ_INV];
|
||||
|
||||
union longlongasbytes {
|
||||
uint64_t ull;
|
||||
uint32_t ul[2];
|
||||
uint8_t bytes[8];
|
||||
};
|
||||
|
||||
char *uint64toa (uint64_t s) {
|
||||
//--------------------------------
|
||||
//0x1141 72607952ULL
|
||||
sprintf(_buffer, "%lX%08lX", (unsigned long)(s>>32), (unsigned long)(s&0xFFFFFFFFULL));
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
uint64_t Serial2RadioID (uint64_t sn) {
|
||||
//----------------------------------
|
||||
longlongasbytes llsn;
|
||||
longlongasbytes res;
|
||||
llsn.ull = sn;
|
||||
res.ull = 0;
|
||||
res.bytes[4] = llsn.bytes[0];
|
||||
res.bytes[3] = llsn.bytes[1];
|
||||
res.bytes[2] = llsn.bytes[2];
|
||||
res.bytes[1] = llsn.bytes[3];
|
||||
res.bytes[0] = 0x01;
|
||||
return res.ull;
|
||||
}
|
||||
|
||||
|
||||
void addInverter (uint8_t _ID, const char * _name, uint64_t _serial,
|
||||
const measureDef_t * liste, int anzMeasure,
|
||||
measureCalc_t * calcs, int anzMeasureCalculated) {
|
||||
//-------------------------------------------------------------------------------------
|
||||
if (anzInv >= MAX_ANZ_INV) {
|
||||
DEBUG_OUT.println(F("ANZ_INV zu klein!"));
|
||||
return;
|
||||
}
|
||||
inverter_t *p = &(inverters[anzInv]);
|
||||
p->ID = _ID;
|
||||
strcpy (p->name, _name);
|
||||
p->serialNo = _serial;
|
||||
p->RadioId = Serial2RadioID(_serial);
|
||||
p->measureDef = liste;
|
||||
p->anzMeasures = anzMeasure;
|
||||
p->anzMeasureCalculated = anzMeasureCalculated;
|
||||
p->measureCalculated = calcs;
|
||||
p->anzTotalMeasures = anzMeasure + anzMeasureCalculated;
|
||||
memset (p->values, 0, sizeof(p->values));
|
||||
|
||||
DEBUG_OUT.print (F("WR : ")); DEBUG_OUT.println(anzInv);
|
||||
DEBUG_OUT.print (F("Type : ")); DEBUG_OUT.println(_name);
|
||||
DEBUG_OUT.print (F("Serial : ")); DEBUG_OUT.println(uint64toa(_serial));
|
||||
DEBUG_OUT.print (F("Radio-ID : ")); DEBUG_OUT.println(uint64toa(p->RadioId));
|
||||
|
||||
anzInv++;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t toggle = 0; // nur für Test, ob's auch für mehere WR funzt
|
||||
uint8_t findInverter (uint8_t *fourbytes) {
|
||||
//---------------------------------------
|
||||
for (uint8_t i = 0; i < anzInv; i++) {
|
||||
longlongasbytes llb;
|
||||
llb.ull = inverters[i].serialNo;
|
||||
if (llb.bytes[3] == fourbytes[0] &&
|
||||
llb.bytes[2] == fourbytes[1] &&
|
||||
llb.bytes[1] == fourbytes[2] &&
|
||||
llb.bytes[0] == fourbytes[3] )
|
||||
{
|
||||
return i;
|
||||
//if (toggle) toggle = 0; else toggle = 1; return toggle; // Test ob mehr WR auch geht
|
||||
}
|
||||
}
|
||||
return 0xFF; // nicht gefunden
|
||||
}
|
||||
|
||||
|
||||
char * error = {"error"};
|
||||
|
||||
char *getMeasureName (uint8_t wr, uint8_t i){
|
||||
//------------------------------------------
|
||||
inverter_t *p = &(inverters[wr]);
|
||||
if (i >= p->anzTotalMeasures) return error;
|
||||
uint8_t idx, channel = 0;
|
||||
if (i < p->anzMeasures) {
|
||||
idx = p->measureDef[i].nameIdx;
|
||||
channel = p->measureDef[i].channel;
|
||||
}
|
||||
else {
|
||||
idx = p->measureCalculated[i - p->anzMeasures].nameIdx;
|
||||
}
|
||||
char tmp[20];
|
||||
strcpy_P (_buffer, NAMES[idx]);
|
||||
if (channel) {
|
||||
sprintf_P (tmp, PSTR(".CH%d"), channel);
|
||||
strcat(_buffer,tmp);
|
||||
}
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
const char *getUnit (uint8_t wr, uint8_t i) {
|
||||
//------------------------------------------
|
||||
inverter_t *p = &(inverters[wr]);
|
||||
if (i >= p->anzTotalMeasures) return error;
|
||||
uint8_t idx;
|
||||
if (i < p->anzMeasures)
|
||||
idx = p->measureDef[i].unitIdx;
|
||||
else
|
||||
idx = p->measureCalculated[i-p->anzMeasures].unitIdx;
|
||||
|
||||
//strcpy (_buffer, units[i]);
|
||||
//return _buffer;
|
||||
return units[idx];
|
||||
}
|
||||
|
||||
|
||||
float getMeasureValue (uint8_t wr, uint8_t i) {
|
||||
//------------------------------------------
|
||||
if (i >= inverters[wr].anzTotalMeasures) return 0.0;
|
||||
return inverters[wr].values[i];
|
||||
}
|
||||
|
||||
|
||||
int getDivisor (uint8_t wr, uint8_t i) {
|
||||
//------------------------------------
|
||||
inverter_t *p = &(inverters[wr]);
|
||||
if (i >= p->anzTotalMeasures) return 1;
|
||||
if (i < p->anzMeasures) {
|
||||
uint8_t digits = p->measureDef[i].digits;
|
||||
if (digits == DIV1) return 1;
|
||||
if (digits == DIV10) return 10;
|
||||
if (digits == DIV100) return 100;
|
||||
if (digits == DIV1000) return 1000;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return p->measureCalculated[i].digits;
|
||||
}
|
||||
|
||||
|
||||
uint8_t getDigits (uint8_t wr, uint8_t i) {
|
||||
//---------------------------------------
|
||||
inverter_t *p = &(inverters[wr]);
|
||||
if (i >= p->anzTotalMeasures) return 0;
|
||||
if (i < p->anzMeasures)
|
||||
return p->measureDef[i].digits;
|
||||
else
|
||||
return p->measureCalculated[i-p->anzMeasures].digits;
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++ Inverter ++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
#include "HM600.h" // für HM-600 und HM-700
|
||||
|
||||
#include "HM1200.h"
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
void setupInverts() {
|
||||
//-----------------
|
||||
|
||||
addInverter (0,"HM-600", 0x114172607952ULL,
|
||||
hm600_measureDef, HM600_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
||||
hm600_measureCalc, HM600_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
||||
|
||||
/*
|
||||
addInverter (1,"HM-1200", 0x114172607952ULL,
|
||||
hm1200_measureDef, HM1200_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
||||
hm1200_measureCalc, HM1200_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
#endif
|
151
tools/HoyDtuSim/ModWebserver.h
Normal file
151
tools/HoyDtuSim/ModWebserver.h
Normal file
|
@ -0,0 +1,151 @@
|
|||
// ################# WebServer #################
|
||||
|
||||
#ifndef __MODWEBSERVER_H
|
||||
#define __MODWEBSERVER_H
|
||||
#define MODWEBSERVER
|
||||
|
||||
#include <ESP8266WebServer.h>
|
||||
#include "Debug.h"
|
||||
#include "Settings.h"
|
||||
|
||||
ESP8266WebServer server (WEBSERVER_PORT);
|
||||
|
||||
|
||||
void returnOK () {
|
||||
//--------------
|
||||
server.send(200, F("text/plain"), "");
|
||||
}
|
||||
|
||||
|
||||
void returnFail(String msg) {
|
||||
//-------------------------
|
||||
server.send(500, F("text/plain"), msg + "\r\n");
|
||||
}
|
||||
|
||||
void handleHelp () {
|
||||
//-----------------
|
||||
String out = "<html>";
|
||||
out += "<body><h2>Hilfe</h2>";
|
||||
out += "<br><br><table>";
|
||||
out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>";
|
||||
out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>";
|
||||
out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>";
|
||||
out += "<tr><td>/reboot</td><td>startet neu</td></tr>";
|
||||
out += "</table></body></html>";
|
||||
server.send (200, "text/html", out);
|
||||
}
|
||||
|
||||
|
||||
void handleReboot () {
|
||||
//-------------------
|
||||
returnOK ();
|
||||
ESP.reset();
|
||||
}
|
||||
|
||||
|
||||
void handleRoot() {
|
||||
//----------------
|
||||
String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>";
|
||||
out += "<body>";
|
||||
out += "<h2>Hoymiles Micro-Inverters</h2>";
|
||||
char floatString[20];
|
||||
char line[100];
|
||||
for (uint8_t wr = 0; wr < anzInv; wr++) {
|
||||
out += "<h3>" + String(inverters[wr].name) + "</h3>";
|
||||
out += "<h3>S/N " + String (getSerialNoTxt(wr)) + "</h3>";
|
||||
out += "<br><br><table border='1'>";
|
||||
out += "<tr><th>Kanal</th><th>Wert</th><th>Einheit</th></tr>";
|
||||
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) {
|
||||
dtostrf (getMeasureValue(wr, i),1, getDigits(wr,i), floatString);
|
||||
sprintf(line, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>", getMeasureName(wr, i), floatString, getUnit(wr, i));
|
||||
//DEBUG_OUT.println(line);
|
||||
out += String(line);
|
||||
/* out += "<tr><td>" + getMeasureName(i) + "</td>";
|
||||
out += "<td>" + String(getMeasureValue(i)) + "</td></tr>";
|
||||
out += "<td>" + String(getUnit(i)) + "</td></tr>"; */
|
||||
}
|
||||
out += "</table>";
|
||||
}
|
||||
int pos = out.indexOf("°");
|
||||
do {
|
||||
if (pos>1) {
|
||||
out = out.substring (0, pos) + "°" + out.substring(pos+2);
|
||||
}
|
||||
pos = out.indexOf("°");
|
||||
} while (pos>1);
|
||||
|
||||
out += "</body></html>";
|
||||
server.send (200, "text/html", out);
|
||||
//DEBUG_OUT.println (out);
|
||||
}
|
||||
|
||||
|
||||
void handleData () {
|
||||
//-----------------
|
||||
String out = "";
|
||||
for (uint8_t wr = 0; wr < anzInv; wr++) {
|
||||
for (int i = 0; i < inverters[wr].anzTotalMeasures; i++) {
|
||||
out += (anzInv <= 1 ? "" : String (wr) + ".") + String(getMeasureName(wr,i)) + '='
|
||||
+ String (getMeasureValue(wr,i)) /*+ ' ' + String(getUnit(wr,i))*/ + '\n';
|
||||
}
|
||||
}
|
||||
server.send(200, "text/plain", out);
|
||||
}
|
||||
|
||||
|
||||
void handleNotFound() {
|
||||
//--------------------
|
||||
String message = "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
}
|
||||
|
||||
|
||||
void setupWebServer (void) {
|
||||
//-------------------------
|
||||
server.begin();
|
||||
server.on("/", handleRoot);
|
||||
server.on("/reboot", handleReboot);
|
||||
server.on("/data", handleData);
|
||||
server.on("/help", handleHelp);
|
||||
//server.onNotFound(handleNotFound); wegen Spiffs-Dateimanager
|
||||
|
||||
DEBUG_OUT.println ("[HTTP] installed");
|
||||
}
|
||||
|
||||
void webserverHandle() {
|
||||
//====================
|
||||
server.handleClient();
|
||||
}
|
||||
|
||||
|
||||
// ################# OTA #################
|
||||
|
||||
#ifdef WITH_OTA
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
|
||||
ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT);
|
||||
ESP8266HTTPUpdateServer httpUpdater;
|
||||
|
||||
void setupUpdateByOTA () {
|
||||
//------------------------
|
||||
httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW);
|
||||
httpUpdateServer.begin();
|
||||
DEBUG_OUT.println ("[OTA] installed");
|
||||
}
|
||||
|
||||
void checkUpdateByOTA() {
|
||||
//---------------------
|
||||
httpUpdateServer.handleClient();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
69
tools/HoyDtuSim/Settings.h
Normal file
69
tools/HoyDtuSim/Settings.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef __SETTINGS_H
|
||||
#define __SETTINGS_H
|
||||
|
||||
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
#define DEBUG
|
||||
#define SER_BAUDRATE (115200)
|
||||
|
||||
#include "Debug.h"
|
||||
|
||||
// Ausgabe was gesendet wird; 0 oder 1
|
||||
#define DEBUG_SEND 0
|
||||
|
||||
// soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
|
||||
#define CHANNEL_HOP
|
||||
|
||||
// mit OTA Support, also update der Firmware über WLan mittels IP/update
|
||||
#define WITH_OTA
|
||||
|
||||
// Hardware configuration
|
||||
#ifdef ESP8266
|
||||
#define RF1_CE_PIN (D4)
|
||||
#define RF1_CS_PIN (D8)
|
||||
#define RF1_IRQ_PIN (D3)
|
||||
#else
|
||||
#define RF1_CE_PIN (9)
|
||||
#define RF1_CS_PIN (10)
|
||||
#define RF1_IRQ_PIN (2)
|
||||
#endif
|
||||
|
||||
// WR und DTU
|
||||
#define RF_MAX_ADDR_WIDTH (5)
|
||||
#define MAX_RF_PAYLOAD_SIZE (32)
|
||||
#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
|
||||
|
||||
#define USE_POOR_MAN_CHANNEL_HOPPING_RCV 1 // 0 = not use
|
||||
|
||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||
#define MAX_ANZ_INV 2 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define MAX_MEASURE_PER_INV 25 // hier statisch, könnte auch dynamisch erzeugt werden, aber Overhead für dyn. Speicher?
|
||||
|
||||
// Webserver
|
||||
#define WEBSERVER_PORT 80
|
||||
|
||||
// Time Server
|
||||
//#define TIMESERVER_NAME "pool.ntp.org"
|
||||
#define TIMESERVER_NAME "fritz.box" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
||||
#ifdef WITH_OTA
|
||||
// OTA Einstellungen
|
||||
#define UPDATESERVER_PORT WEBSERVER_PORT+1
|
||||
#define UPDATESERVER_DIR "/update"
|
||||
#define UPDATESERVER_USER "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define UPDATESERVER_PW "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#endif
|
||||
|
||||
// internes WLan
|
||||
// PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) von fremden zu unterscheiden
|
||||
// gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
|
||||
#define SSID_PREFIX1 "pre1" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define SSID_PREFIX2 "pre2" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define SSID_PASSWORD "?????????????????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
||||
// zur Berechnung von Sonnenauf- und -untergang
|
||||
#define geoBreite 49.2866 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define geoLaenge 7.3416 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
||||
|
||||
#endif
|
55
tools/HoyDtuSim/Sonne.h
Normal file
55
tools/HoyDtuSim/Sonne.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef __SONNE_H
|
||||
#define __SONNE_H
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Debug.h"
|
||||
|
||||
|
||||
long SunDown, SunUp;
|
||||
|
||||
void calcSunUpDown (time_t date) {
|
||||
//SunUpDown res = new SunUpDown();
|
||||
boolean isSummerTime = false; // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
|
||||
|
||||
//- Bogenmass
|
||||
double brad = geoBreite / 180.0 * PI;
|
||||
// - Höhe Sonne -50 Bogenmin.
|
||||
double h0 = -50.0 / 60.0 / 180.0 * PI;
|
||||
//- Deklination dek, Tag des Jahres d0
|
||||
int tage = 30 * month(date) - 30 + day(date);
|
||||
double dek = 0.40954 * sin (0.0172 * (tage - 79.35));
|
||||
double zh1 = sin (h0) - sin (brad) * sin(dek);
|
||||
double zh2 = cos(brad) * cos(dek);
|
||||
double zd = 12*acos (zh1/zh2) / PI;
|
||||
double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939);
|
||||
//-Sonnenuntergang
|
||||
double tsu = 12 + zd - zgl;
|
||||
double su = (tsu + (15.0 - geoLaenge) / 15.0);
|
||||
int std = (int)su;
|
||||
int minute = (int) ((su - std)*60);
|
||||
if (isSummerTime) std++;
|
||||
SunDown = (100*std + minute) * 100;
|
||||
|
||||
//- Sonnenaufgang
|
||||
double tsa = 12 - zd - zgl;
|
||||
double sa = (tsa + (15.0 - geoLaenge) /15.0);
|
||||
std = (int) sa;
|
||||
minute = (int) ((sa - std)*60);
|
||||
if (isSummerTime) std++;
|
||||
SunUp = (100*std + minute) * 100;
|
||||
DEBUG_OUT.print(F("Sonnenaufgang :")); DEBUG_OUT.println(SunUp);
|
||||
DEBUG_OUT.print(F("Sonnenuntergang:")); DEBUG_OUT.println(SunDown);
|
||||
}
|
||||
|
||||
boolean isDayTime() {
|
||||
//-----------------
|
||||
// 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
|
||||
const int offset=60*15;
|
||||
time_t no = getNow();
|
||||
long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100;
|
||||
long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100;
|
||||
|
||||
return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown));
|
||||
}
|
||||
|
||||
#endif
|
102
tools/HoyDtuSim/hm_crc.h
Normal file
102
tools/HoyDtuSim/hm_crc.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
#ifndef __HM_CRC_H
|
||||
#define __HM_CRC_H
|
||||
|
||||
#define BITS_TO_BYTES(x) (((x)+7)>>3)
|
||||
#define BYTES_TO_BITS(x) ((x)<<3)
|
||||
|
||||
extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen);
|
||||
extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen);
|
||||
extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits);
|
||||
|
||||
//#define OUTPUT_DEBUG_INFO
|
||||
|
||||
#define CRC8_INIT 0x00
|
||||
#define CRC8_POLY 0x01
|
||||
|
||||
#define CRC16_MODBUS_POLYNOM 0xA001
|
||||
|
||||
uint8_t crc8(uint8_t buf[], uint16_t len) {
|
||||
uint8_t crc = CRC8_INIT;
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
crc ^= buf[i];
|
||||
for(uint8_t b = 0; b < 8; b ++) {
|
||||
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16_modbus(uint8_t buf[], uint16_t len) {
|
||||
uint16_t crc = 0xffff;
|
||||
uint8_t lsb;
|
||||
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
crc = crc ^ buf[i];
|
||||
for(int8_t b = 7; b >= 0; b--) {
|
||||
lsb = (crc & 0x0001);
|
||||
if(lsb == 0x01)
|
||||
crc--;
|
||||
crc = crc >> 1;
|
||||
if(lsb == 0x01)
|
||||
crc = crc ^ CRC16_MODBUS_POLYNOM;
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
// NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits)
|
||||
{
|
||||
uint16_t crc = startCRC;
|
||||
if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen)))
|
||||
{
|
||||
// The length of the data might not be a multiple of full bytes.
|
||||
// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
|
||||
// calculate the CRC.
|
||||
uint16_t data;
|
||||
uint8_t byte, shift;
|
||||
uint16_t bitoffs = startBit;
|
||||
|
||||
// Get a new byte for the next 8 bits.
|
||||
byte = buf[bitoffs >> 3];
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf_P(PSTR("\nStart CRC %04X, %u bits:"), startCRC, len_bits);
|
||||
printf_P(PSTR("\nbyte %02X:"), byte);
|
||||
#endif
|
||||
while (bitoffs < len_bits + startBit)
|
||||
{
|
||||
shift = bitoffs & 7;
|
||||
// Shift the active bit to the position of bit 15
|
||||
data = ((uint16_t)byte) << (8 + shift);
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf_P(PSTR(" bit %u %u,"), shift, data & 0x8000 ? 1 : 0);
|
||||
#endif
|
||||
// Assure all other bits are 0
|
||||
data &= 0x8000;
|
||||
crc ^= data;
|
||||
if (crc & 0x8000)
|
||||
{
|
||||
crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
}
|
||||
else
|
||||
{
|
||||
crc = (crc << 1);
|
||||
}
|
||||
++bitoffs;
|
||||
if (0 == (bitoffs & 7))
|
||||
{
|
||||
// Get a new byte for the next 8 bits.
|
||||
byte = buf[bitoffs >> 3];
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf_P(PSTR("crc %04X:"), crc);
|
||||
if (bitoffs < len_bits + startBit)
|
||||
printf_P(PSTR("\nbyte %02X:"), byte);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
#endif
|
93
tools/HoyDtuSim/hm_packets.h
Normal file
93
tools/HoyDtuSim/hm_packets.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
#ifndef __HM_PACKETS_H
|
||||
#define __HM_PACKETS_H
|
||||
|
||||
#include "hm_crc.h"
|
||||
|
||||
class HM_Packets
|
||||
{
|
||||
private:
|
||||
uint32_t unixTimeStamp;
|
||||
|
||||
void prepareBuffer(uint8_t *buf);
|
||||
void copyToBuffer(uint8_t *buf, uint32_t val);
|
||||
void copyToBufferBE(uint8_t *buf, uint32_t val);
|
||||
|
||||
public:
|
||||
void SetUnixTimeStamp(uint32_t ts);
|
||||
void UnixTimeStampTick();
|
||||
|
||||
int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr);
|
||||
int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd);
|
||||
};
|
||||
|
||||
void HM_Packets::SetUnixTimeStamp(uint32_t ts)
|
||||
{
|
||||
unixTimeStamp = ts;
|
||||
}
|
||||
|
||||
void HM_Packets::UnixTimeStampTick()
|
||||
{
|
||||
unixTimeStamp++;
|
||||
}
|
||||
|
||||
void HM_Packets::prepareBuffer(uint8_t *buf)
|
||||
{
|
||||
// minimal buffer size of 32 bytes is assumed
|
||||
memset(buf, 0x00, 32);
|
||||
}
|
||||
|
||||
void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val)
|
||||
{
|
||||
buf[0]= (uint8_t)(val >> 24);
|
||||
buf[1]= (uint8_t)(val >> 16);
|
||||
buf[2]= (uint8_t)(val >> 8);
|
||||
buf[3]= (uint8_t)(val & 0xFF);
|
||||
}
|
||||
|
||||
void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val)
|
||||
{
|
||||
memcpy(buf, &val, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static uint8_t cid = 0;
|
||||
|
||||
int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr)
|
||||
{
|
||||
prepareBuffer(buf);
|
||||
|
||||
buf[0] = 0x15;
|
||||
copyToBufferBE(&buf[1], wrAdr);
|
||||
copyToBufferBE(&buf[5], dtuAdr);
|
||||
buf[9] = 0x80;
|
||||
buf[10] = 0x0B; //0x0B; 0x03 0x11
|
||||
buf[11] = 0x00;
|
||||
|
||||
copyToBuffer(&buf[12], unixTimeStamp);
|
||||
|
||||
buf[19] = 0x05;
|
||||
|
||||
// CRC16
|
||||
uint16_t crc16 = crc16_modbus(&buf[10], 14);
|
||||
buf[24] = crc16 >> 8;
|
||||
buf[25] = crc16 & 0xFF;
|
||||
|
||||
// crc8
|
||||
buf[26] = crc8(&buf[0], 26);
|
||||
|
||||
return 27;
|
||||
}
|
||||
|
||||
int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd)
|
||||
{
|
||||
buf[0] = mid;
|
||||
copyToBufferBE(&buf[1], wrAdr);
|
||||
copyToBufferBE(&buf[5], dtuAdr);
|
||||
buf[9] = cmd;
|
||||
|
||||
// crc8
|
||||
buf[10] = crc8(&buf[0], 10);
|
||||
|
||||
return 11;
|
||||
}
|
||||
|
||||
#endif
|
345
tools/HoyDtuSim/wifi.h
Normal file
345
tools/HoyDtuSim/wifi.h
Normal file
|
@ -0,0 +1,345 @@
|
|||
#ifndef __WIFI_H
|
||||
#define __WIFI_H
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Debug.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <Pinger.h> // von url=https://www.technologytourist.com
|
||||
|
||||
String SSID = ""; // bestes WLan
|
||||
|
||||
// Prototypes
|
||||
time_t getNow ();
|
||||
boolean setupWifi ();
|
||||
boolean checkWifi();
|
||||
|
||||
|
||||
String findWifi () {
|
||||
//----------------
|
||||
String ssid;
|
||||
int32_t rssi;
|
||||
uint8_t encryptionType;
|
||||
uint8_t* bssid;
|
||||
int32_t channel;
|
||||
bool hidden;
|
||||
int scanResult;
|
||||
|
||||
String best_ssid = "";
|
||||
int32_t best_rssi = -100;
|
||||
|
||||
DEBUG_OUT.println(F("Starting WiFi scan..."));
|
||||
|
||||
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);
|
||||
|
||||
if (scanResult == 0) {
|
||||
DEBUG_OUT.println(F("keine WLans"));
|
||||
} else if (scanResult > 0) {
|
||||
DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult);
|
||||
|
||||
// Print unsorted scan results
|
||||
for (int8_t i = 0; i < scanResult; i++) {
|
||||
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
|
||||
|
||||
DEBUG_OUT.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"),
|
||||
i,
|
||||
channel,
|
||||
bssid[0], bssid[1], bssid[2],
|
||||
bssid[3], bssid[4], bssid[5],
|
||||
rssi,
|
||||
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
|
||||
hidden ? 'H' : 'V',
|
||||
ssid.c_str());
|
||||
delay(1);
|
||||
boolean check;
|
||||
#ifdef SSID_PREFIX1
|
||||
check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1);
|
||||
#else
|
||||
check = true;
|
||||
#endif
|
||||
#ifdef SSID_PREFIX2
|
||||
check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2);
|
||||
#endif
|
||||
if (check) {
|
||||
if (rssi > best_rssi) {
|
||||
best_rssi = rssi;
|
||||
best_ssid = ssid;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult);
|
||||
}
|
||||
|
||||
if (! best_ssid.equals("")) {
|
||||
SSID = best_ssid;
|
||||
DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str());
|
||||
return SSID;
|
||||
}
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
void IP2string (IPAddress IP, char * buf) {
|
||||
sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]);
|
||||
}
|
||||
|
||||
void connectWifi() {
|
||||
//------------------
|
||||
// if (SSID.equals(""))
|
||||
String s = findWifi();
|
||||
|
||||
if (!SSID.equals("")) {
|
||||
DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID);
|
||||
//while (WiFi.status() != WL_CONNECTED) {
|
||||
WiFi.begin (SSID, SSID_PASSWORD);
|
||||
int versuche = 20;
|
||||
while (WiFi.status() != WL_CONNECTED && versuche > 0) {
|
||||
delay(1000);
|
||||
versuche--;
|
||||
DEBUG_OUT.print(versuche); DEBUG_OUT.print(' ');
|
||||
}
|
||||
//}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
char buffer[30];
|
||||
IP2string (WiFi.localIP(), buffer);
|
||||
String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer);
|
||||
DEBUG_OUT.println (out);
|
||||
}
|
||||
else
|
||||
DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean setupWifi () {
|
||||
//------------------
|
||||
int count=5;
|
||||
while (count-- && WiFi.status() != WL_CONNECTED)
|
||||
connectWifi();
|
||||
return (WiFi.status() == WL_CONNECTED);
|
||||
}
|
||||
|
||||
|
||||
Pinger pinger;
|
||||
IPAddress ROUTER = IPAddress(192,168,1,1);
|
||||
|
||||
boolean checkWifi() {
|
||||
//---------------
|
||||
boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER);
|
||||
if (NotConnected) {
|
||||
setupWifi();
|
||||
if (WiFi.status() == WL_CONNECTED)
|
||||
getNow();
|
||||
}
|
||||
return (WiFi.status() == WL_CONNECTED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ################ Clock #################
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
#include <TimeLib.h>
|
||||
|
||||
IPAddress timeServer;
|
||||
unsigned int localPort = 8888;
|
||||
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
|
||||
byte packetBuf[NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets
|
||||
const int timeZone = 1; // Central European Time = +1
|
||||
long SYNCINTERVALL = 0;
|
||||
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
|
||||
|
||||
// prototypes
|
||||
time_t getNtpTime ();
|
||||
void sendNTPpacket (IPAddress &address);
|
||||
time_t getNow ();
|
||||
char* getDateTimeStr (time_t no = getNow());
|
||||
time_t offsetDayLightSaving (uint32_t local_t);
|
||||
bool isDayofDaylightChange (time_t local_t);
|
||||
|
||||
|
||||
void _setSyncInterval (long intervall) {
|
||||
//----------------------------------------
|
||||
SYNCINTERVALL = intervall;
|
||||
setSyncInterval (intervall);
|
||||
}
|
||||
|
||||
void setupClock() {
|
||||
//-----------------
|
||||
WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
|
||||
|
||||
Udp.begin(localPort);
|
||||
|
||||
getNtpTime();
|
||||
|
||||
setSyncProvider (getNtpTime);
|
||||
while(timeStatus()== timeNotSet)
|
||||
delay(1); //
|
||||
|
||||
_setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
|
||||
|
||||
//lastClock = now();
|
||||
//Serial.print("[NTP] get time from NTP server ");
|
||||
getNow();
|
||||
//char buf[20];
|
||||
DEBUG_OUT.print ("[NTP] get time from NTP server ");
|
||||
DEBUG_OUT.print (timeServer);
|
||||
//sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
DEBUG_OUT.print (": got ");
|
||||
DEBUG_OUT.println (getDateTimeStr());
|
||||
}
|
||||
|
||||
//*-------- NTP code ----------*/
|
||||
|
||||
|
||||
time_t getNtpTime() {
|
||||
//-------------------
|
||||
sendNTPpacket(timeServer); // send an NTP packet to a time server
|
||||
//uint32_t beginWait = millis();
|
||||
//while (millis() - beginWait < 1500) {
|
||||
int versuch = 0;
|
||||
while (versuch < 5) {
|
||||
int wait = 150; // results in max 1500 ms waitTime
|
||||
while (wait--) {
|
||||
int size = Udp.parsePacket();
|
||||
if (size >= NTP_PACKET_SIZE) {
|
||||
//Serial.println("Receive NTP Response");
|
||||
Udp.read(packetBuf, NTP_PACKET_SIZE); // read packet into the buffer
|
||||
unsigned long secsSince1900;
|
||||
// convert four bytes starting at location 40 to a long integer
|
||||
secsSince1900 = (unsigned long)packetBuf[40] << 24;
|
||||
secsSince1900 |= (unsigned long)packetBuf[41] << 16;
|
||||
secsSince1900 |= (unsigned long)packetBuf[42] << 8;
|
||||
secsSince1900 |= (unsigned long)packetBuf[43];
|
||||
// time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
|
||||
|
||||
time_t utc = secsSince1900 - 2208988800UL;
|
||||
time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR;
|
||||
|
||||
if (isDayofDaylightChange (utc) && hour(utc) <= 4)
|
||||
_setSyncInterval (SECS_PER_HOUR);
|
||||
else
|
||||
_setSyncInterval (SECS_PER_DAY / 2);
|
||||
|
||||
return now;
|
||||
}
|
||||
else
|
||||
delay(10);
|
||||
}
|
||||
versuch++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// send an NTP request to the time server at the given address
|
||||
void sendNTPpacket(IPAddress& address) {
|
||||
//------------------------------------
|
||||
memset(packetBuf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
|
||||
// Initialize values needed to form NTP request
|
||||
packetBuf[0] = B11100011; // LI, Version, Mode
|
||||
packetBuf[1] = 0; // Stratum
|
||||
packetBuf[2] = 6; // Max Interval between messages in seconds
|
||||
packetBuf[3] = 0xEC; // Clock Precision
|
||||
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
packetBuf[12] = 49; // four-byte reference ID identifying
|
||||
packetBuf[13] = 0x4E;
|
||||
packetBuf[14] = 49;
|
||||
packetBuf[15] = 52;
|
||||
// send the packet requesting a timestamp:
|
||||
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
||||
Udp.write(packetBuf,NTP_PACKET_SIZE);
|
||||
Udp.endPacket();
|
||||
|
||||
}
|
||||
|
||||
int getTimeTrials = 0;
|
||||
|
||||
bool isValidDateTime (time_t no) {
|
||||
return (year(no) > 2020 && year(no) < 2038);
|
||||
}
|
||||
|
||||
bool isDayofDaylightChange (time_t local_t) {
|
||||
//-----------------------------------------
|
||||
int jahr = year (local_t);
|
||||
int monat = month (local_t);
|
||||
int tag = day (local_t);
|
||||
bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) ||
|
||||
(monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7)));
|
||||
DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
|
||||
// übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
time_t offsetDayLightSaving (uint32_t local_t) {
|
||||
//--------------------------------------------
|
||||
int monat = month (local_t);
|
||||
if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
int jahr = year (local_t);
|
||||
int std = hour (local_t);
|
||||
//int tag = day (local_t);
|
||||
int stundenBisHeute = (std + 24 * day(local_t));
|
||||
if ( (monat == 3 && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) ||
|
||||
(monat == 10 && stundenBisHeute < (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) )
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
/*
|
||||
int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7));
|
||||
if (monat == 3 && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute < stundenBisWechsel)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
time_t getNow () {
|
||||
//---------------
|
||||
time_t jetzt = now();
|
||||
while (!isValidDateTime(jetzt) && getTimeTrials < 10) { // ungültig, max 10x probieren
|
||||
if (getTimeTrials) {
|
||||
//Serial.print (getTimeTrials);
|
||||
//Serial.println(". Versuch für getNtpTime");
|
||||
}
|
||||
jetzt = getNtpTime ();
|
||||
if (isValidDateTime(jetzt)) {
|
||||
setTime (jetzt);
|
||||
getTimeTrials = 0;
|
||||
}
|
||||
else
|
||||
getTimeTrials++;
|
||||
}
|
||||
//return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
|
||||
return jetzt;
|
||||
}
|
||||
|
||||
|
||||
char _timestr[24];
|
||||
|
||||
char* getNowStr (time_t no = getNow()) {
|
||||
//------------------------------------
|
||||
sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
char* getTimeStr (time_t no = getNow()) {
|
||||
//------------------------------------
|
||||
return getNowStr (no);
|
||||
}
|
||||
|
||||
char* getDateTimeStr (time_t no) {
|
||||
//------------------------------
|
||||
sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
char* getDateStr (time_t no) {
|
||||
//------------------------------
|
||||
sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
158
tools/NRF24_SendRcv/CircularBuffer.h
Normal file
158
tools/NRF24_SendRcv/CircularBuffer.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
CircularBuffer - An Arduino circular buffering library for arbitrary types.
|
||||
|
||||
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef CircularBuffer_h
|
||||
#define CircularBuffer_h
|
||||
|
||||
#ifdef ESP8266
|
||||
#define DISABLE_IRQ noInterrupts()
|
||||
#define RESTORE_IRQ interrupts()
|
||||
#else
|
||||
#define DISABLE_IRQ \
|
||||
uint8_t sreg = SREG; \
|
||||
cli();
|
||||
|
||||
#define RESTORE_IRQ \
|
||||
SREG = sreg;
|
||||
#endif
|
||||
|
||||
template <class T> class CircularBuffer
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* @param buffer Preallocated buffer of at least size records.
|
||||
* @param size Number of records available in the buffer.
|
||||
*/
|
||||
CircularBuffer(T* buffer, const uint8_t size )
|
||||
: m_size(size), m_buff(buffer)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
/** Clear all entries in the circular buffer. */
|
||||
void clear(void)
|
||||
{
|
||||
m_front = 0;
|
||||
m_fill = 0;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is empty */
|
||||
inline bool empty(void) const
|
||||
{
|
||||
return !m_fill;
|
||||
}
|
||||
|
||||
/** Return the number of records stored in the buffer */
|
||||
inline uint8_t available(void) const
|
||||
{
|
||||
return m_fill;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is full */
|
||||
inline bool full(void) const
|
||||
{
|
||||
return m_fill == m_size;
|
||||
}
|
||||
|
||||
/** Aquire record on front of the buffer, for writing.
|
||||
* After filling the record, it has to be pushed to actually
|
||||
* add it to the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is full.
|
||||
*/
|
||||
T* getFront(void) const
|
||||
{
|
||||
DISABLE_IRQ;
|
||||
T* f = NULL;
|
||||
if (!full())
|
||||
f = get(m_front);
|
||||
RESTORE_IRQ;
|
||||
return f;
|
||||
}
|
||||
|
||||
/** Push record to front of the buffer
|
||||
* @param record Record to push. If record was aquired previously (using getFront) its
|
||||
* data will not be copied as it is already present in the buffer.
|
||||
* @return True, when record was pushed successfully.
|
||||
*/
|
||||
bool pushFront(T* record)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!full())
|
||||
{
|
||||
T* f = get(m_front);
|
||||
if (f != record)
|
||||
*f = *record;
|
||||
m_front = (m_front+1) % m_size;
|
||||
m_fill++;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** Aquire record on back of the buffer, for reading.
|
||||
* After reading the record, it has to be pop'ed to actually
|
||||
* remove it from the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is empty.
|
||||
*/
|
||||
T* getBack(void) const
|
||||
{
|
||||
T* b = NULL;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
b = get(back());
|
||||
RESTORE_IRQ;
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Remove record from back of the buffer.
|
||||
* @return True, when record was pop'ed successfully.
|
||||
*/
|
||||
bool popBack(void)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
{
|
||||
m_fill--;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
protected:
|
||||
inline T * get(const uint8_t idx) const
|
||||
{
|
||||
return &(m_buff[idx]);
|
||||
}
|
||||
inline uint8_t back(void) const
|
||||
{
|
||||
return (m_front - m_fill + m_size) % m_size;
|
||||
}
|
||||
|
||||
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
T* const m_buff; // Ptr to buffer holding all records.
|
||||
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
};
|
||||
|
||||
#endif // CircularBuffer_h
|
23
tools/NRF24_SendRcv/Debug.h
Normal file
23
tools/NRF24_SendRcv/Debug.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef __DEBUG_H
|
||||
|
||||
#define __DEBUG_H
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_OUT Serial
|
||||
#else
|
||||
//---
|
||||
// disable Serial DEBUG output
|
||||
#define DEBUG_OUT DummySerial
|
||||
static class {
|
||||
public:
|
||||
void begin(...) {}
|
||||
void print(...) {}
|
||||
void println(...) {}
|
||||
void flush() {}
|
||||
bool available() { return false;}
|
||||
int readBytes(...) { return 0;}
|
||||
int printf (...) {return 0;}
|
||||
} DummySerial;
|
||||
#endif
|
||||
|
||||
#endif
|
129
tools/NRF24_SendRcv/ModWebserver.h
Normal file
129
tools/NRF24_SendRcv/ModWebserver.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
// ################# WebServer #################
|
||||
|
||||
#ifndef __MODWEBSERVER_H
|
||||
#define __MODWEBSERVER_H
|
||||
#define MODWEBSERVER
|
||||
|
||||
#include <ESP8266WebServer.h>
|
||||
#include "Debug.h"
|
||||
#include "Settings.h"
|
||||
|
||||
ESP8266WebServer server (WEBSERVER_PORT);
|
||||
|
||||
|
||||
void returnOK () {
|
||||
//--------------
|
||||
server.send(200, F("text/plain"), "");
|
||||
}
|
||||
|
||||
|
||||
void returnFail(String msg) {
|
||||
//-------------------------
|
||||
server.send(500, F("text/plain"), msg + "\r\n");
|
||||
}
|
||||
|
||||
void handleHelp () {
|
||||
//-----------------
|
||||
String out = "<html>";
|
||||
out += "<body><h2>Hilfe</h2>";
|
||||
out += "<br><br><table>";
|
||||
out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>";
|
||||
out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>";
|
||||
out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>";
|
||||
out += "<tr><td>/reboot</td><td>startet neu</td></tr>";
|
||||
out += "</table></body></html>";
|
||||
server.send (200, "text/html", out);
|
||||
}
|
||||
|
||||
|
||||
void handleReboot () {
|
||||
//-------------------
|
||||
returnOK ();
|
||||
ESP.reset();
|
||||
}
|
||||
|
||||
|
||||
void handleRoot() {
|
||||
//----------------
|
||||
String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>";
|
||||
out += "<body>";
|
||||
out += "<h2>Hoymiles Micro-Inverter HM-600</h2>";
|
||||
out += "<br><br><table border='1'>";
|
||||
out += "<tr><th>Kanal</th><th>Wert</th></tr>";
|
||||
for (byte i = 0; i < ANZAHL_VALUES; i++) {
|
||||
out += "<tr><td>" + String(getChannelName(i)) + "</td>";
|
||||
out += "<td>" + String(VALUES[i]) + "</td></tr>";
|
||||
}
|
||||
out += "</table>";
|
||||
out += "</body></html>";
|
||||
server.send (200, "text/html", out);
|
||||
//DEBUG_OUT.println (out);
|
||||
}
|
||||
|
||||
|
||||
void handleData () {
|
||||
//-----------------
|
||||
String out = "";
|
||||
for (int i = 0; i < ANZAHL_VALUES; i++) {
|
||||
out += String(getChannelName(i)) + '=' + String (VALUES[i]) + '\n';
|
||||
}
|
||||
server.send(200, "text/plain", out);
|
||||
}
|
||||
|
||||
|
||||
void handleNotFound() {
|
||||
//--------------------
|
||||
String message = "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
}
|
||||
|
||||
|
||||
void setupWebServer (void) {
|
||||
//-------------------------
|
||||
server.on("/", handleRoot);
|
||||
server.on("/reboot", handleReboot);
|
||||
server.on("/data", handleData);
|
||||
server.on("/help", handleHelp);
|
||||
//server.onNotFound(handleNotFound); wegen Spiffs-Dateimanager
|
||||
|
||||
server.begin();
|
||||
DEBUG_OUT.println ("[HTTP] installed");
|
||||
}
|
||||
|
||||
void webserverHandle() {
|
||||
//====================
|
||||
server.handleClient();
|
||||
}
|
||||
|
||||
|
||||
// ################# OTA #################
|
||||
|
||||
#ifdef WITH_OTA
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
|
||||
ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT);
|
||||
ESP8266HTTPUpdateServer httpUpdater;
|
||||
|
||||
void setupUpdateByOTA () {
|
||||
//------------------------
|
||||
httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW);
|
||||
httpUpdateServer.begin();
|
||||
DEBUG_OUT.println (F("[OTA] installed"));
|
||||
}
|
||||
|
||||
void checkUpdateByOTA() {
|
||||
//---------------------
|
||||
httpUpdateServer.handleClient();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
597
tools/NRF24_SendRcv/NRF24_SendRcv.ino
Normal file
597
tools/NRF24_SendRcv/NRF24_SendRcv.ino
Normal file
|
@ -0,0 +1,597 @@
|
|||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include "CircularBuffer.h"
|
||||
#include <RF24.h>
|
||||
#include <RF24_config.h>
|
||||
#include "hm_crc.h"
|
||||
#include "hm_packets.h"
|
||||
|
||||
#include "Settings.h" // Header für Einstellungen
|
||||
|
||||
#include "Debug.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#define DISABLE_EINT noInterrupts()
|
||||
#define ENABLE_EINT interrupts()
|
||||
#else // für AVR z.B. ProMini oder Nano
|
||||
#define DISABLE_EINT EIMSK = 0x00
|
||||
#define ENABLE_EINT EIMSK = 0x01
|
||||
#endif
|
||||
|
||||
|
||||
#define RF_MAX_ADDR_WIDTH (5)
|
||||
#define MAX_RF_PAYLOAD_SIZE (32)
|
||||
|
||||
#ifdef ESP8266
|
||||
#define PACKET_BUFFER_SIZE (30)
|
||||
#else
|
||||
#define PACKET_BUFFER_SIZE (20)
|
||||
#endif
|
||||
|
||||
// Startup defaults until user reconfigures it
|
||||
#define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles
|
||||
//#define DEFAULT_SEND_CHANNEL (75) // 40 = Default channel for Hoymiles, 61
|
||||
#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
|
||||
|
||||
#include "NRF24_sniff_types.h"
|
||||
|
||||
static HM_Packets hmPackets;
|
||||
static uint32_t tickMillis;
|
||||
|
||||
|
||||
// Set up nRF24L01 radio on SPI bus plus CE/CS pins
|
||||
// If more than one RF24 unit is used the another CS pin than 10 must be used
|
||||
// This pin is used hard coded in SPI library
|
||||
static RF24 radio1 (RF1_CE_PIN, RF1_CS_PIN);
|
||||
|
||||
static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE];
|
||||
|
||||
static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0]));
|
||||
|
||||
static Serial_header_t SerialHdr;
|
||||
|
||||
#define CHECKCRC 1
|
||||
static uint16_t lastCRC;
|
||||
static uint16_t crc;
|
||||
|
||||
uint8_t channels[] = {/*3,*/ 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
||||
uint8_t channelIdx = 1; // fange mit 40 an
|
||||
uint8_t DEFAULT_SEND_CHANNEL = channels[channelIdx]; // = 40
|
||||
|
||||
static unsigned long timeLastPacket = millis();
|
||||
|
||||
// Function forward declaration
|
||||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len);
|
||||
char * getChannelName (uint8_t i);
|
||||
|
||||
static const int ANZAHL_VALUES = 16;
|
||||
static float VALUES[ANZAHL_VALUES] = {};
|
||||
static const char *CHANNEL_NAMES[ANZAHL_VALUES]
|
||||
= {"P1.Udc", "P1.Idc", "P1.Pdc", "P2.Udc", "P2.Idc", "P2.Pdc",
|
||||
"E-Woche", "E-Total", "E1-Tag", "E2-Tag", "Uac", "Freq.ac", "Pac", "E-heute", "Ipv", "WR-Temp"};
|
||||
static const uint8_t DIVISOR[ANZAHL_VALUES] = {10,100,10,10,100,10,1,1,1,1,10,100,10,0,0,10};
|
||||
|
||||
static const char BLANK = ' ';
|
||||
|
||||
static boolean istTag = true;
|
||||
|
||||
char CHANNELNAME_BUFFER[15];
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "wifi.h"
|
||||
#include "ModWebserver.h"
|
||||
#include "Sonne.h"
|
||||
#endif
|
||||
|
||||
char * getChannelName (uint8_t i) {
|
||||
//-------------------------------
|
||||
memset (CHANNELNAME_BUFFER, 0, sizeof(CHANNELNAME_BUFFER));
|
||||
strcpy (CHANNELNAME_BUFFER, CHANNEL_NAMES[i]);
|
||||
//itoa (i, CHANNELNAME_BUFFER, 10);
|
||||
return CHANNELNAME_BUFFER;
|
||||
}
|
||||
|
||||
inline static void dumpData(uint8_t *p, int len) {
|
||||
//-----------------------------------------------
|
||||
while (len--){
|
||||
if (*p < 16)
|
||||
DEBUG_OUT.print(F("0"));
|
||||
DEBUG_OUT.print(*p++, HEX);
|
||||
}
|
||||
DEBUG_OUT.print(BLANK);
|
||||
}
|
||||
|
||||
|
||||
float extractValue2 (uint8_t *p, int divisor) {
|
||||
//-------------------------------------------
|
||||
uint16_t b1 = *p++;
|
||||
return ((float) (b1 << 8) + *p) / (float) divisor;
|
||||
}
|
||||
|
||||
|
||||
float extractValue4 (uint8_t *p, int divisor) {
|
||||
//-------------------------------------------
|
||||
uint32_t ret = *p++;
|
||||
for (uint8_t i = 1; i <= 3; i++)
|
||||
ret = (ret << 8) + *p++;
|
||||
return (ret / divisor);
|
||||
}
|
||||
|
||||
void outChannel (uint8_t i) {
|
||||
//-------------------------
|
||||
DEBUG_OUT.print(getChannelName(i)); DEBUG_OUT.print(F("\t:")); DEBUG_OUT.print(VALUES[i]); DEBUG_OUT.println(BLANK);
|
||||
}
|
||||
|
||||
|
||||
void analyse01 (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
//DEBUG_OUT.print (F("analyse 01: "));
|
||||
p += 3;
|
||||
// PV1.U PV1.I PV1.P PV2.U PV2.I PV2.P
|
||||
// [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W]
|
||||
for (int i = 0; i < 6; i++) {
|
||||
VALUES[i] = extractValue2 (p,DIVISOR[i]); p += 2;
|
||||
outChannel(i);
|
||||
}
|
||||
/*
|
||||
DEBUG_OUT.print(F("PV1.U:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" PV1.I:")); DEBUG_OUT.print(extractValue2(p,100));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" PV1.Pac:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" PV2.U:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" PV2.I:")); DEBUG_OUT.print(extractValue2(p,100));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" PV2.Pac:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
*/
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
|
||||
void analyse02 (uint8_t *p) { // p zeigt auf 02 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
//DEBUG_OUT.print (F("analyse 02: "));
|
||||
// +11 = Spannung, +13 = Frequenz, +15 = Leistung
|
||||
//p += 11;
|
||||
p++;
|
||||
for (int i = 6; i < 13; i++) {
|
||||
if (i == 7) {
|
||||
VALUES[i] = extractValue4 (p,DIVISOR[i]);
|
||||
p += 4;
|
||||
}
|
||||
else {
|
||||
VALUES[i] = extractValue2 (p,DIVISOR[i]);
|
||||
p += 2;
|
||||
}
|
||||
outChannel(i);
|
||||
}
|
||||
VALUES[13] = VALUES[8] + VALUES[9]; // E-heute = P1+P2
|
||||
if (VALUES[10] > 0)
|
||||
VALUES[14] = VALUES[12] / VALUES[10]; // Ipv = Pac / Spannung
|
||||
/*
|
||||
DEBUG_OUT.print(F("P Woche:")); DEBUG_OUT.print(extractValue2(p,1));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" P Total:")); DEBUG_OUT.print(extractValue4(p,1));
|
||||
p += 4;
|
||||
DEBUG_OUT.print(F(" P1 Tag:")); DEBUG_OUT.print(extractValue2(p,1));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" P2 Tag:")); DEBUG_OUT.print(extractValue2(p,1));
|
||||
p += 2;
|
||||
|
||||
DEBUG_OUT.print(F(" Spannung:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" Freq.:")); DEBUG_OUT.print(extractValue2(p,100));
|
||||
p += 2;
|
||||
DEBUG_OUT.print(F(" Leist.:")); DEBUG_OUT.print(extractValue2(p,10));
|
||||
*/
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
|
||||
void analyse83 (uint8_t *p) { // p zeigt auf 83 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
//DEBUG_OUT.print (F("++++++analyse 83:"));
|
||||
p += 7;
|
||||
VALUES[15] = extractValue2 (p,DIVISOR[15]);
|
||||
outChannel(15);
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
void analyseWords (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
DEBUG_OUT.print (F("analyse words:"));
|
||||
p++;
|
||||
for (int i = 0; i <12;i++) {
|
||||
DEBUG_OUT.print(extractValue2(p,1));
|
||||
DEBUG_OUT.print(BLANK);
|
||||
p++;
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
void analyseLongs (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
||||
//----------------------------------
|
||||
//uint16_t val;
|
||||
DEBUG_OUT.print (F("analyse words:"));
|
||||
p++;
|
||||
for (int i = 0; i <12;i++) {
|
||||
DEBUG_OUT.print(extractValue4(p,1));
|
||||
DEBUG_OUT.print(BLANK);
|
||||
p++;
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
|
||||
#ifdef ESP8266
|
||||
IRAM_ATTR
|
||||
#endif
|
||||
void handleNrf1Irq() {
|
||||
//-------------------------
|
||||
static uint8_t lostPacketCount = 0;
|
||||
uint8_t pipe;
|
||||
|
||||
DISABLE_EINT;
|
||||
|
||||
// Loop until RX buffer(s) contain no more packets.
|
||||
while (radio1.available(&pipe)) {
|
||||
if (!packetBuffer.full()) {
|
||||
NRF24_packet_t *p = packetBuffer.getFront();
|
||||
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
||||
p->packetsLost = lostPacketCount;
|
||||
uint8_t packetLen = radio1.getPayloadSize();
|
||||
if (packetLen > MAX_RF_PAYLOAD_SIZE)
|
||||
packetLen = MAX_RF_PAYLOAD_SIZE;
|
||||
|
||||
radio1.read(p->packet, packetLen);
|
||||
packetBuffer.pushFront(p);
|
||||
lostPacketCount = 0;
|
||||
}
|
||||
else {
|
||||
// Buffer full. Increase lost packet counter.
|
||||
bool tx_ok, tx_fail, rx_ready;
|
||||
if (lostPacketCount < 255)
|
||||
lostPacketCount++;
|
||||
// Call 'whatHappened' to reset interrupt status.
|
||||
radio1.whatHappened(tx_ok, tx_fail, rx_ready);
|
||||
// Flush buffer to drop the packet.
|
||||
radio1.flush_rx();
|
||||
}
|
||||
}
|
||||
ENABLE_EINT;
|
||||
}
|
||||
|
||||
|
||||
static void activateConf(void) {
|
||||
//-----------------------------
|
||||
radio1.setChannel(DEFAULT_RECV_CHANNEL);
|
||||
radio1.setDataRate(DEFAULT_RF_DATARATE);
|
||||
radio1.disableCRC();
|
||||
radio1.setAutoAck(0x00);
|
||||
radio1.setPayloadSize(MAX_RF_PAYLOAD_SIZE);
|
||||
radio1.setAddressWidth(5);
|
||||
radio1.openReadingPipe(1, DTU_RADIO_ID);
|
||||
|
||||
// We want only RX irqs
|
||||
radio1.maskIRQ(true, true, false);
|
||||
|
||||
// Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
|
||||
radio1.setPALevel(RF24_PA_MAX);
|
||||
radio1.startListening();
|
||||
|
||||
// Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
|
||||
attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
|
||||
|
||||
// Initialize SerialHdr header's address member to promiscuous address.
|
||||
uint64_t addr = DTU_RADIO_ID;
|
||||
for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) {
|
||||
SerialHdr.address[i] = addr;
|
||||
addr >>= 8;
|
||||
}
|
||||
|
||||
#ifndef ESP8266
|
||||
DEBUG_OUT.println(F("\nRadio Config:"));
|
||||
radio1.printPrettyDetails();
|
||||
DEBUG_OUT.println();
|
||||
#endif
|
||||
tickMillis = millis() + 200;
|
||||
}
|
||||
|
||||
|
||||
void setup(void) {
|
||||
//--------------
|
||||
//Serial.begin(SER_BAUDRATE);
|
||||
DEBUG_OUT.begin(SER_BAUDRATE);
|
||||
DEBUG_OUT.flush();
|
||||
|
||||
DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --"));
|
||||
|
||||
radio1.begin();
|
||||
|
||||
// Disable shockburst for receiving and decode payload manually
|
||||
radio1.setAutoAck(false);
|
||||
radio1.setRetries(0, 0);
|
||||
|
||||
// Configure nRF IRQ input
|
||||
pinMode(RF1_IRQ_PIN, INPUT);
|
||||
|
||||
activateConf();
|
||||
|
||||
#ifdef ESP8266
|
||||
setupWifi();
|
||||
setupClock();
|
||||
setupWebServer();
|
||||
setupUpdateByOTA();
|
||||
calcSunUpDown (getNow());
|
||||
istTag = isDayTime();
|
||||
DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht");
|
||||
hmPackets.SetUnixTimeStamp (getNow());
|
||||
#else
|
||||
hmPackets.SetUnixTimeStamp(0x62456430);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE];
|
||||
|
||||
void isTime2Send () {
|
||||
//-----------------
|
||||
// Second timer
|
||||
|
||||
if (millis() >= tickMillis) {
|
||||
static uint8_t tel = 0;
|
||||
tickMillis += 1000; //200;
|
||||
//tickSec++;
|
||||
hmPackets.UnixTimeStampTick();
|
||||
/* if (++tickSec >= 5) { // 5
|
||||
hmPackets.UnixTimeStampTick();
|
||||
tickSec = 0;
|
||||
} */
|
||||
|
||||
int32_t size = 0;
|
||||
uint64_t dest = WR1_RADIO_ID;
|
||||
|
||||
if (tel > 5)
|
||||
tel = 0;
|
||||
|
||||
if (tel == 0) {
|
||||
#ifdef ESP8266
|
||||
hmPackets.SetUnixTimeStamp (getNow());
|
||||
#endif
|
||||
size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8);
|
||||
}
|
||||
else if (tel == 1)
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x81);
|
||||
else if (tel == 2)
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80);
|
||||
else if (tel == 3) {
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x83);
|
||||
//tel = 0;
|
||||
}
|
||||
else if (tel == 4)
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x82);
|
||||
else if (tel == 5)
|
||||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x84);
|
||||
|
||||
SendPacket(dest, (uint8_t *)&sendBuf, size);
|
||||
|
||||
tel++;
|
||||
|
||||
/* for (uint8_t warte = 0; warte < 2; warte++) {
|
||||
delay(1000);
|
||||
hmPackets.UnixTimeStampTick();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) {
|
||||
//-----------------------------------------------------
|
||||
|
||||
// Write timestamp, packets lost, address and payload length
|
||||
//printf(" %09lu ", SerialHdr.timestamp);
|
||||
dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost));
|
||||
dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address));
|
||||
|
||||
// Trailing bit?!?
|
||||
dumpData(&p->packet[0], 2);
|
||||
|
||||
// Payload length from PCF
|
||||
dumpData(&payloadLen, sizeof(payloadLen));
|
||||
|
||||
// Packet control field - PID Packet identification
|
||||
uint8_t val = (p->packet[1] >> 1) & 0x03;
|
||||
DEBUG_OUT.print(val);
|
||||
DEBUG_OUT.print(F(" "));
|
||||
|
||||
if (payloadLen > 9) {
|
||||
dumpData(&p->packet[2], 1);
|
||||
dumpData(&p->packet[3], 4);
|
||||
dumpData(&p->packet[7], 4);
|
||||
|
||||
uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4;
|
||||
|
||||
if (remain < 32) {
|
||||
dumpData(&p->packet[11], remain);
|
||||
printf_P(PSTR("%04X "), crc);
|
||||
|
||||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3]))
|
||||
DEBUG_OUT.print(0);
|
||||
else
|
||||
DEBUG_OUT.print(1);
|
||||
}
|
||||
else {
|
||||
DEBUG_OUT.print(F("Ill remain "));
|
||||
DEBUG_OUT.print(remain);
|
||||
}
|
||||
}
|
||||
else {
|
||||
dumpData(&p->packet[2], payloadLen + 2);
|
||||
printf_P(PSTR("%04X "), crc);
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
}
|
||||
|
||||
|
||||
void loop(void) {
|
||||
//=============
|
||||
while (!packetBuffer.empty()) {
|
||||
timeLastPacket = millis();
|
||||
// One or more records present
|
||||
NRF24_packet_t *p = packetBuffer.getBack();
|
||||
|
||||
// Shift payload data due to 9-bit packet control field
|
||||
for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) {
|
||||
if (j > 0)
|
||||
p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1);
|
||||
else
|
||||
p->packet[j] = (byte)(p->packet[j] >> 7);
|
||||
}
|
||||
|
||||
SerialHdr.timestamp = p->timestamp;
|
||||
SerialHdr.packetsLost = p->packetsLost;
|
||||
|
||||
// Check CRC
|
||||
crc = 0xFFFF;
|
||||
crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address)));
|
||||
// Payload length
|
||||
uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3);
|
||||
// Add one byte and one bit for 9-bit packet control field
|
||||
crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1);
|
||||
|
||||
if (CHECKCRC) {
|
||||
// If CRC is invalid only show lost packets
|
||||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) {
|
||||
if (p->packetsLost > 0) {
|
||||
DEBUG_OUT.print(F(" Lost: "));
|
||||
DEBUG_OUT.println(p->packetsLost);
|
||||
}
|
||||
packetBuffer.popBack();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dump a decoded packet only once
|
||||
if (lastCRC == crc) {
|
||||
packetBuffer.popBack();
|
||||
continue;
|
||||
}
|
||||
lastCRC = crc;
|
||||
}
|
||||
|
||||
// Don't dump mysterious ack packages
|
||||
if (payloadLen == 0) {
|
||||
packetBuffer.popBack();
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
outputPacket (p, payloadLen);
|
||||
#endif
|
||||
|
||||
uint8_t cmd = p->packet[11];
|
||||
if (cmd == 0x02)
|
||||
analyse02 (&p->packet[11]);
|
||||
else if (cmd == 0x01)
|
||||
analyse01 (&p->packet[11]);
|
||||
//if (p->packet[11] == 0x83 || p->packet[11] == 0x82) analyse83 (&p->packet[11], payloadLen);
|
||||
else if (cmd == 0x03) {
|
||||
analyseWords (&p->packet[11]);
|
||||
analyseLongs (&p->packet[11]);
|
||||
}
|
||||
else if (cmd == 0x81) // ???
|
||||
;
|
||||
else if (cmd == 0x83)
|
||||
analyse83 (&p->packet[11]);
|
||||
else {
|
||||
DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX);
|
||||
analyseWords (&p->packet[11]);
|
||||
analyseLongs (&p->packet[11]);
|
||||
}
|
||||
if (p->packetsLost > 0) {
|
||||
DEBUG_OUT.print(F(" Lost: "));
|
||||
DEBUG_OUT.print(p->packetsLost);
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
|
||||
#ifndef ESP8266
|
||||
for (uint8_t i = 0; i < ANZAHL_VALUES; i++) {
|
||||
//outChannel(i);
|
||||
Serial.print(getChannelName(i)); Serial.print(':'); Serial.print(VALUES[i]); Serial.println(BLANK); // Schnittstelle bei Arduino
|
||||
}
|
||||
DEBUG_OUT.println();
|
||||
#endif
|
||||
|
||||
// Remove record as we're done with it.
|
||||
packetBuffer.popBack();
|
||||
}
|
||||
|
||||
if (istTag)
|
||||
isTime2Send();
|
||||
|
||||
#ifdef ESP8266
|
||||
checkWifi();
|
||||
webserverHandle();
|
||||
checkUpdateByOTA();
|
||||
if (hour() == 0 && minute() == 0) {
|
||||
calcSunUpDown(getNow());
|
||||
}
|
||||
if (minute() % 15 == 0 && second () == 0) { // alle 15 Minuten neu berechnen ob noch hell
|
||||
istTag = isDayTime();
|
||||
DEBUG_OUT.print ("Es ist "); DEBUG_OUT.println (istTag?"Tag":"Nacht");
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
if (millis() > timeLastPacket + 60UL*SECOND) { // 60 Sekunden
|
||||
channelIdx++;
|
||||
if (channelIdx >= sizeof(channels)) channelIdx = 0;
|
||||
DEFAULT_SEND_CHANNEL = channels[channelIdx];
|
||||
DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL);
|
||||
timeLastPacket = millis();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) {
|
||||
//--------------------------------------------------------------
|
||||
DISABLE_EINT;
|
||||
radio1.stopListening();
|
||||
|
||||
#ifdef CHANNEL_HOP
|
||||
static uint8_t hop = 0;
|
||||
#if DEBUG_SEND
|
||||
DEBUG_OUT.print(F("Send... CH"));
|
||||
DEBUG_OUT.println(channels[hop]);
|
||||
#endif
|
||||
radio1.setChannel(channels[hop++]);
|
||||
if (hop >= sizeof(channels) / sizeof(channels[0]))
|
||||
hop = 0;
|
||||
#else
|
||||
radio1.setChannel(DEFAULT_SEND_CHANNEL);
|
||||
#endif
|
||||
|
||||
radio1.openWritingPipe(dest);
|
||||
radio1.setCRCLength(RF24_CRC_16);
|
||||
radio1.enableDynamicPayloads();
|
||||
radio1.setAutoAck(true);
|
||||
radio1.setRetries(3, 15);
|
||||
|
||||
radio1.write(buf, len);
|
||||
|
||||
// Try to avoid zero payload acks (has no effect)
|
||||
radio1.openWritingPipe(DUMMY_RADIO_ID);
|
||||
|
||||
radio1.setAutoAck(false);
|
||||
radio1.setRetries(0, 0);
|
||||
radio1.disableDynamicPayloads();
|
||||
radio1.setCRCLength(RF24_CRC_DISABLED);
|
||||
|
||||
radio1.setChannel(DEFAULT_RECV_CHANNEL);
|
||||
radio1.startListening();
|
||||
ENABLE_EINT;
|
||||
}
|
55
tools/NRF24_SendRcv/NRF24_sniff_types.h
Normal file
55
tools/NRF24_SendRcv/NRF24_sniff_types.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
This file is part of NRF24_Sniff.
|
||||
|
||||
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
|
||||
|
||||
NRF24_Sniff is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
NRF24_Sniff is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with NRF24_Sniff. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NRF24_sniff_types_h
|
||||
#define NRF24_sniff_types_h
|
||||
|
||||
typedef struct _NRF24_packet_t
|
||||
{
|
||||
uint32_t timestamp;
|
||||
uint8_t packetsLost;
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
|
||||
} NRF24_packet_t;
|
||||
|
||||
typedef struct _Serial_header_t
|
||||
{
|
||||
unsigned long timestamp;
|
||||
uint8_t packetsLost;
|
||||
uint8_t address[RF_MAX_ADDR_WIDTH]; // MSB first, always RF_MAX_ADDR_WIDTH bytes.
|
||||
} Serial_header_t;
|
||||
|
||||
typedef struct _Serial_config_t
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t rate; // rf24_datarate_e: 0 = 1Mb/s, 1 = 2Mb/s, 2 = 250Kb/s
|
||||
uint8_t addressLen; // Number of bytes used in address, range [2..5]
|
||||
uint8_t addressPromiscLen; // Number of bytes used in promiscuous address, range [2..5]. E.g. addressLen=5, addressPromiscLen=4 => 1 byte unique identifier.
|
||||
uint64_t address; // Base address, LSB first.
|
||||
uint8_t crcLength; // Length of active CRC, range [0..2]
|
||||
uint8_t maxPayloadSize; // Maximum size of payload for nRF (including nRF header), range[4?..32]
|
||||
} Serial_config_t;
|
||||
|
||||
#define MSG_TYPE_PACKET (0)
|
||||
#define MSG_TYPE_CONFIG (1)
|
||||
|
||||
#define SET_MSG_TYPE(var,type) (((var) & 0x3F) | ((type) << 6))
|
||||
#define GET_MSG_TYPE(var) ((var) >> 6)
|
||||
#define GET_MSG_LEN(var) ((var) & 0x3F)
|
||||
|
||||
#endif // NRF24_sniff_types_h
|
82
tools/NRF24_SendRcv/Settings.h
Normal file
82
tools/NRF24_SendRcv/Settings.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#ifndef __SETTINGS_H
|
||||
#define __SETTINGS_H
|
||||
|
||||
// Ausgabe von Debug Infos auf der seriellen Console
|
||||
#define DEBUG
|
||||
#define SER_BAUDRATE (115200)
|
||||
|
||||
// Ausgabe was gesendet wird; 0 oder 1
|
||||
#define DEBUG_SEND 0
|
||||
|
||||
// soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
|
||||
#define CHANNEL_HOP
|
||||
|
||||
// mit OTA Support, also update der Firmware über WLan mittels IP/update
|
||||
#define WITH_OTA
|
||||
|
||||
// Hardware configuration
|
||||
#ifdef ESP8266
|
||||
#define RF1_CE_PIN (D4)
|
||||
#define RF1_CS_PIN (D8)
|
||||
#define RF1_IRQ_PIN (D3)
|
||||
#else
|
||||
#define RF1_CE_PIN (9)
|
||||
#define RF1_CS_PIN (10)
|
||||
#define RF1_IRQ_PIN (2)
|
||||
#endif
|
||||
|
||||
union longlongasbytes {
|
||||
uint64_t ull;
|
||||
uint8_t bytes[8];
|
||||
};
|
||||
|
||||
|
||||
uint64_t Serial2RadioID (uint64_t sn) {
|
||||
//----------------------------------
|
||||
longlongasbytes llsn;
|
||||
longlongasbytes res;
|
||||
llsn.ull = sn;
|
||||
res.ull = 0;
|
||||
res.bytes[4] = llsn.bytes[0];
|
||||
res.bytes[3] = llsn.bytes[1];
|
||||
res.bytes[2] = llsn.bytes[2];
|
||||
res.bytes[1] = llsn.bytes[3];
|
||||
res.bytes[0] = 0x01;
|
||||
return res.ull;
|
||||
}
|
||||
|
||||
// WR und DTU
|
||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||
#define SerialWR 0x114172607952ULL // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
uint64_t WR1_RADIO_ID = Serial2RadioID (SerialWR); // ((uint64_t)0x5279607201ULL);
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||
|
||||
|
||||
// Webserver
|
||||
#define WEBSERVER_PORT 80
|
||||
|
||||
// Time Server
|
||||
//#define TIMESERVER_NAME "pool.ntp.org"
|
||||
#define TIMESERVER_NAME "fritz.box"
|
||||
|
||||
#ifdef WITH_OTA
|
||||
// OTA Einstellungen
|
||||
#define UPDATESERVER_PORT WEBSERVER_PORT+1
|
||||
#define UPDATESERVER_DIR "/update" // mittels IP:81/update kommt man dann auf die OTA-Seite
|
||||
#define UPDATESERVER_USER "username_für_OTA" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define UPDATESERVER_PW "passwort_für_OTA" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#endif
|
||||
|
||||
// internes WLan
|
||||
// PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) vonfremden zu unterscheiden
|
||||
// gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
|
||||
#define SSID_PREFIX1 "wlan1-Prefix" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define SSID_PREFIX2 "wlan2-Prefix" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
#define SSID_PASSWORD "wlan-passwort" // <<<<<<<<<<<<<<<<<<<<<<< anpassen
|
||||
|
||||
// zur Berechnung von Sonnenauf- und -untergang
|
||||
#define geoBreite 49.2866
|
||||
#define geoLaenge 7.3416
|
||||
|
||||
|
||||
#endif
|
55
tools/NRF24_SendRcv/Sonne.h
Normal file
55
tools/NRF24_SendRcv/Sonne.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef __SONNE_H
|
||||
#define __SONNE_H
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Debug.h"
|
||||
|
||||
|
||||
long SunDown, SunUp;
|
||||
|
||||
void calcSunUpDown (time_t date) {
|
||||
//SunUpDown res = new SunUpDown();
|
||||
boolean isSummerTime = false; // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
|
||||
|
||||
//- Bogenma<6D>
|
||||
double brad = geoBreite / 180.0 * PI;
|
||||
// - H<>he Sonne -50 Bogenmin.
|
||||
double h0 = -50.0 / 60.0 / 180.0 * PI;
|
||||
//- Deklination dek, Tag des Jahres d0
|
||||
int tage = 30 * month(date) - 30 + day(date);
|
||||
double dek = 0.40954 * sin (0.0172 * (tage - 79.35));
|
||||
double zh1 = sin (h0) - sin (brad) * sin(dek);
|
||||
double zh2 = cos(brad) * cos(dek);
|
||||
double zd = 12*acos (zh1/zh2) / PI;
|
||||
double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939);
|
||||
//-Sonnenuntergang
|
||||
double tsu = 12 + zd - zgl;
|
||||
double su = (tsu + (15.0 - geoLaenge) / 15.0);
|
||||
int std = (int)su;
|
||||
int minute = (int) ((su - std)*60);
|
||||
if (isSummerTime) std++;
|
||||
SunDown = (100*std + minute) * 100;
|
||||
|
||||
//- Sonnenaufgang
|
||||
double tsa = 12 - zd - zgl;
|
||||
double sa = (tsa + (15.0 - geoLaenge) /15.0);
|
||||
std = (int) sa;
|
||||
minute = (int) ((sa - std)*60);
|
||||
if (isSummerTime) std++;
|
||||
SunUp = (100*std + minute) * 100;
|
||||
DEBUG_OUT.print("Sonnenaufgang :"); DEBUG_OUT.println(SunUp);
|
||||
DEBUG_OUT.print("Sonnenuntergang:"); DEBUG_OUT.println(SunDown);
|
||||
}
|
||||
|
||||
boolean isDayTime() {
|
||||
//-----------------
|
||||
// 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
|
||||
const int offset=60*15;
|
||||
time_t no = getNow();
|
||||
long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100;
|
||||
long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100;
|
||||
|
||||
return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown));
|
||||
}
|
||||
|
||||
#endif
|
142
tools/NRF24_SendRcv/hm_crc.cpp
Normal file
142
tools/NRF24_SendRcv/hm_crc.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "hm_crc.h"
|
||||
//#define OUTPUT_DEBUG_INFO
|
||||
|
||||
/* Table of CRC values for high-order byte */
|
||||
static const uint8_t auchCRCHi[] = {
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40};
|
||||
|
||||
/* Table of CRC values for low-order byte */
|
||||
static const uint8_t auchCRCLo[] = {
|
||||
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
|
||||
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
||||
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
|
||||
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
||||
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
|
||||
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
||||
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
|
||||
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
||||
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
|
||||
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
||||
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
|
||||
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
||||
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
|
||||
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
||||
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
|
||||
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
||||
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
||||
0x40};
|
||||
|
||||
uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen)
|
||||
{
|
||||
uint8_t uchCRCHi = 0xFF; /* high byte of CRC initialized */
|
||||
uint8_t uchCRCLo = 0xFF; /* low byte of CRC initialized */
|
||||
uint16_t uIndex; /* will index into CRC lookup table */
|
||||
while (usDataLen--) /* pass through message buffer */
|
||||
{
|
||||
uIndex = uchCRCLo ^ *puchMsg++; /* calculate the CRC */
|
||||
uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex];
|
||||
uchCRCHi = auchCRCLo[uIndex];
|
||||
}
|
||||
return (uchCRCHi << 8 | uchCRCLo);
|
||||
}
|
||||
|
||||
// Hoymiles CRC8 calculation with poly 0x01, Initial value 0x00 and final XOR 0x00
|
||||
uint8_t crc8(uint8_t *buf, const uint16_t bufLen)
|
||||
{
|
||||
uint32_t crc;
|
||||
uint16_t i, bit;
|
||||
|
||||
crc = 0x00;
|
||||
for (i = 0; i < bufLen; i++)
|
||||
{
|
||||
crc ^= buf[i];
|
||||
for (bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if ((crc & 0x80) != 0)
|
||||
{
|
||||
crc <<= 1;
|
||||
crc ^= 0x01;
|
||||
}
|
||||
else
|
||||
{
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (crc & 0xFF);
|
||||
}
|
||||
|
||||
// NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits)
|
||||
{
|
||||
uint16_t crc = startCRC;
|
||||
if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen)))
|
||||
{
|
||||
// The length of the data might not be a multiple of full bytes.
|
||||
// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
|
||||
// calculate the CRC.
|
||||
uint16_t data;
|
||||
uint8_t byte, shift;
|
||||
uint16_t bitoffs = startBit;
|
||||
|
||||
// Get a new byte for the next 8 bits.
|
||||
byte = buf[bitoffs >> 3];
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf("\nStart CRC %04X, %u bits:", startCRC, len_bits);
|
||||
printf("\nbyte %02X:", byte);
|
||||
#endif
|
||||
while (bitoffs < len_bits + startBit)
|
||||
{
|
||||
shift = bitoffs & 7;
|
||||
// Shift the active bit to the position of bit 15
|
||||
data = ((uint16_t)byte) << (8 + shift);
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf(" bit %u %u,", shift, data & 0x8000 ? 1 : 0);
|
||||
#endif
|
||||
// Assure all other bits are 0
|
||||
data &= 0x8000;
|
||||
crc ^= data;
|
||||
if (crc & 0x8000)
|
||||
{
|
||||
crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
||||
}
|
||||
else
|
||||
{
|
||||
crc = (crc << 1);
|
||||
}
|
||||
++bitoffs;
|
||||
if (0 == (bitoffs & 7))
|
||||
{
|
||||
// Get a new byte for the next 8 bits.
|
||||
byte = buf[bitoffs >> 3];
|
||||
#ifdef OUTPUT_DEBUG_INFO
|
||||
printf("crc %04X:", crc);
|
||||
if (bitoffs < len_bits + startBit)
|
||||
printf("\nbyte %02X:", byte);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
8
tools/NRF24_SendRcv/hm_crc.h
Normal file
8
tools/NRF24_SendRcv/hm_crc.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
#define BITS_TO_BYTES(x) (((x)+7)>>3)
|
||||
#define BYTES_TO_BITS(x) ((x)<<3)
|
||||
|
||||
extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen);
|
||||
extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen);
|
||||
extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits);
|
74
tools/NRF24_SendRcv/hm_packets.cpp
Normal file
74
tools/NRF24_SendRcv/hm_packets.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include "Arduino.h"
|
||||
|
||||
#include "hm_crc.h"
|
||||
#include "hm_packets.h"
|
||||
|
||||
void HM_Packets::SetUnixTimeStamp(uint32_t ts)
|
||||
{
|
||||
unixTimeStamp = ts;
|
||||
}
|
||||
|
||||
void HM_Packets::UnixTimeStampTick()
|
||||
{
|
||||
unixTimeStamp++;
|
||||
}
|
||||
|
||||
void HM_Packets::prepareBuffer(uint8_t *buf)
|
||||
{
|
||||
// minimal buffer size of 32 bytes is assumed
|
||||
memset(buf, 0x00, 32);
|
||||
}
|
||||
|
||||
void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val)
|
||||
{
|
||||
buf[0]= (uint8_t)(val >> 24);
|
||||
buf[1]= (uint8_t)(val >> 16);
|
||||
buf[2]= (uint8_t)(val >> 8);
|
||||
buf[3]= (uint8_t)(val & 0xFF);
|
||||
}
|
||||
|
||||
void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val)
|
||||
{
|
||||
memcpy(buf, &val, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
|
||||
int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr)
|
||||
{
|
||||
prepareBuffer(buf);
|
||||
|
||||
buf[0] = 0x15;
|
||||
copyToBufferBE(&buf[1], wrAdr);
|
||||
copyToBufferBE(&buf[5], dtuAdr);
|
||||
buf[9] = 0x80;
|
||||
|
||||
buf[10] = 0x0B; // cid
|
||||
buf[11] = 0x00;
|
||||
|
||||
copyToBuffer(&buf[12], unixTimeStamp);
|
||||
|
||||
buf[19] = 0x05;
|
||||
|
||||
// CRC16
|
||||
uint16_t crc16 = crc16_modbus(&buf[10], 14);
|
||||
buf[24] = crc16 >> 8;
|
||||
buf[25] = crc16 & 0xFF;
|
||||
|
||||
// crc8
|
||||
buf[26] = crc8(&buf[0], 26);
|
||||
|
||||
return 27;
|
||||
}
|
||||
|
||||
int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd)
|
||||
{
|
||||
buf[0] = mid;
|
||||
copyToBufferBE(&buf[1], wrAdr);
|
||||
copyToBufferBE(&buf[5], dtuAdr);
|
||||
buf[9] = cmd;
|
||||
|
||||
// crc8
|
||||
buf[10] = crc8(&buf[0], 10);
|
||||
|
||||
return 11;
|
||||
}
|
18
tools/NRF24_SendRcv/hm_packets.h
Normal file
18
tools/NRF24_SendRcv/hm_packets.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
class HM_Packets
|
||||
{
|
||||
private:
|
||||
uint32_t unixTimeStamp;
|
||||
|
||||
void prepareBuffer(uint8_t *buf);
|
||||
void copyToBuffer(uint8_t *buf, uint32_t val);
|
||||
void copyToBufferBE(uint8_t *buf, uint32_t val);
|
||||
|
||||
public:
|
||||
void SetUnixTimeStamp(uint32_t ts);
|
||||
void UnixTimeStampTick();
|
||||
|
||||
int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr);
|
||||
int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd);
|
||||
};
|
345
tools/NRF24_SendRcv/wifi.h
Normal file
345
tools/NRF24_SendRcv/wifi.h
Normal file
|
@ -0,0 +1,345 @@
|
|||
#ifndef __WIFI_H
|
||||
#define __WIFI_H
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Debug.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <Pinger.h> // von url=https://www.technologytourist.com
|
||||
|
||||
String SSID = ""; // bestes WLan
|
||||
|
||||
// Prototypes
|
||||
time_t getNow ();
|
||||
boolean setupWifi ();
|
||||
boolean checkWifi();
|
||||
|
||||
|
||||
String findWifi () {
|
||||
//----------------
|
||||
String ssid;
|
||||
int32_t rssi;
|
||||
uint8_t encryptionType;
|
||||
uint8_t* bssid;
|
||||
int32_t channel;
|
||||
bool hidden;
|
||||
int scanResult;
|
||||
|
||||
String best_ssid = "";
|
||||
int32_t best_rssi = -100;
|
||||
|
||||
DEBUG_OUT.println(F("Starting WiFi scan..."));
|
||||
|
||||
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);
|
||||
|
||||
if (scanResult == 0) {
|
||||
DEBUG_OUT.println(F("keine WLans"));
|
||||
} else if (scanResult > 0) {
|
||||
DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult);
|
||||
|
||||
// Print unsorted scan results
|
||||
for (int8_t i = 0; i < scanResult; i++) {
|
||||
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
|
||||
|
||||
DEBUG_OUT.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"),
|
||||
i,
|
||||
channel,
|
||||
bssid[0], bssid[1], bssid[2],
|
||||
bssid[3], bssid[4], bssid[5],
|
||||
rssi,
|
||||
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
|
||||
hidden ? 'H' : 'V',
|
||||
ssid.c_str());
|
||||
delay(1);
|
||||
boolean check;
|
||||
#ifdef SSID_PREFIX1
|
||||
check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1);
|
||||
#else
|
||||
check = true;
|
||||
#endif
|
||||
#ifdef SSID_PREFIX2
|
||||
check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2);
|
||||
#endif
|
||||
if (check) {
|
||||
if (rssi > best_rssi) {
|
||||
best_rssi = rssi;
|
||||
best_ssid = ssid;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult);
|
||||
}
|
||||
|
||||
if (! best_ssid.equals("")) {
|
||||
SSID = best_ssid;
|
||||
DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str());
|
||||
return SSID;
|
||||
}
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
void IP2string (IPAddress IP, char * buf) {
|
||||
sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]);
|
||||
}
|
||||
|
||||
void connectWifi() {
|
||||
//------------------
|
||||
// if (SSID.equals(""))
|
||||
String s = findWifi();
|
||||
|
||||
if (!SSID.equals("")) {
|
||||
DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID);
|
||||
//while (WiFi.status() != WL_CONNECTED) {
|
||||
WiFi.begin (SSID, SSID_PASSWORD);
|
||||
int versuche = 20;
|
||||
while (WiFi.status() != WL_CONNECTED && versuche > 0) {
|
||||
delay(1000);
|
||||
versuche--;
|
||||
DEBUG_OUT.print(versuche); DEBUG_OUT.print(' ');
|
||||
}
|
||||
//}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
char buffer[30];
|
||||
IP2string (WiFi.localIP(), buffer);
|
||||
String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer);
|
||||
DEBUG_OUT.println (out);
|
||||
}
|
||||
else
|
||||
DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean setupWifi () {
|
||||
//------------------
|
||||
int count=5;
|
||||
while (count-- && WiFi.status() != WL_CONNECTED)
|
||||
connectWifi();
|
||||
return (WiFi.status() == WL_CONNECTED);
|
||||
}
|
||||
|
||||
|
||||
Pinger pinger;
|
||||
IPAddress ROUTER = IPAddress(192,168,1,1);
|
||||
|
||||
boolean checkWifi() {
|
||||
//---------------
|
||||
boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER);
|
||||
if (NotConnected) {
|
||||
setupWifi();
|
||||
if (WiFi.status() == WL_CONNECTED)
|
||||
getNow();
|
||||
}
|
||||
return (WiFi.status() == WL_CONNECTED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ################ Clock #################
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
#include <TimeLib.h>
|
||||
|
||||
IPAddress timeServer;
|
||||
unsigned int localPort = 8888;
|
||||
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
|
||||
byte packetBuf[NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets
|
||||
const int timeZone = 1; // Central European Time = +1
|
||||
long SYNCINTERVALL = 0;
|
||||
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
|
||||
|
||||
// prototypes
|
||||
time_t getNtpTime ();
|
||||
void sendNTPpacket (IPAddress &address);
|
||||
time_t getNow ();
|
||||
char* getDateTimeStr (time_t no = getNow());
|
||||
time_t offsetDayLightSaving (uint32_t local_t);
|
||||
bool isDayofDaylightChange (time_t local_t);
|
||||
|
||||
|
||||
void _setSyncInterval (long intervall) {
|
||||
//----------------------------------------
|
||||
SYNCINTERVALL = intervall;
|
||||
setSyncInterval (intervall);
|
||||
}
|
||||
|
||||
void setupClock() {
|
||||
//-----------------
|
||||
WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
|
||||
|
||||
Udp.begin(localPort);
|
||||
|
||||
getNtpTime();
|
||||
|
||||
setSyncProvider (getNtpTime);
|
||||
while(timeStatus()== timeNotSet)
|
||||
delay(1); //
|
||||
|
||||
_setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
|
||||
|
||||
//lastClock = now();
|
||||
//Serial.print("[NTP] get time from NTP server ");
|
||||
getNow();
|
||||
//char buf[20];
|
||||
DEBUG_OUT.print ("[NTP] get time from NTP server ");
|
||||
DEBUG_OUT.print (timeServer);
|
||||
//sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
DEBUG_OUT.print (": got ");
|
||||
DEBUG_OUT.println (getDateTimeStr());
|
||||
}
|
||||
|
||||
//*-------- NTP code ----------*/
|
||||
|
||||
|
||||
time_t getNtpTime() {
|
||||
//-------------------
|
||||
sendNTPpacket(timeServer); // send an NTP packet to a time server
|
||||
//uint32_t beginWait = millis();
|
||||
//while (millis() - beginWait < 1500) {
|
||||
int versuch = 0;
|
||||
while (versuch < 5) {
|
||||
int wait = 150; // results in max 1500 ms waitTime
|
||||
while (wait--) {
|
||||
int size = Udp.parsePacket();
|
||||
if (size >= NTP_PACKET_SIZE) {
|
||||
//Serial.println("Receive NTP Response");
|
||||
Udp.read(packetBuf, NTP_PACKET_SIZE); // read packet into the buffer
|
||||
unsigned long secsSince1900;
|
||||
// convert four bytes starting at location 40 to a long integer
|
||||
secsSince1900 = (unsigned long)packetBuf[40] << 24;
|
||||
secsSince1900 |= (unsigned long)packetBuf[41] << 16;
|
||||
secsSince1900 |= (unsigned long)packetBuf[42] << 8;
|
||||
secsSince1900 |= (unsigned long)packetBuf[43];
|
||||
// time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
|
||||
|
||||
time_t utc = secsSince1900 - 2208988800UL;
|
||||
time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR;
|
||||
|
||||
if (isDayofDaylightChange (utc) && hour(utc) <= 4)
|
||||
_setSyncInterval (SECS_PER_HOUR);
|
||||
else
|
||||
_setSyncInterval (SECS_PER_DAY / 2);
|
||||
|
||||
return now;
|
||||
}
|
||||
else
|
||||
delay(10);
|
||||
}
|
||||
versuch++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// send an NTP request to the time server at the given address
|
||||
void sendNTPpacket(IPAddress& address) {
|
||||
//------------------------------------
|
||||
memset(packetBuf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
|
||||
// Initialize values needed to form NTP request
|
||||
packetBuf[0] = B11100011; // LI, Version, Mode
|
||||
packetBuf[1] = 0; // Stratum
|
||||
packetBuf[2] = 6; // Max Interval between messages in seconds
|
||||
packetBuf[3] = 0xEC; // Clock Precision
|
||||
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
packetBuf[12] = 49; // four-byte reference ID identifying
|
||||
packetBuf[13] = 0x4E;
|
||||
packetBuf[14] = 49;
|
||||
packetBuf[15] = 52;
|
||||
// send the packet requesting a timestamp:
|
||||
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
||||
Udp.write(packetBuf,NTP_PACKET_SIZE);
|
||||
Udp.endPacket();
|
||||
|
||||
}
|
||||
|
||||
int getTimeTrials = 0;
|
||||
|
||||
bool isValidDateTime (time_t no) {
|
||||
return (year(no) > 2020 && year(no) < 2038);
|
||||
}
|
||||
|
||||
bool isDayofDaylightChange (time_t local_t) {
|
||||
//-----------------------------------------
|
||||
int jahr = year (local_t);
|
||||
int monat = month (local_t);
|
||||
int tag = day (local_t);
|
||||
bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) ||
|
||||
(monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7)));
|
||||
DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
|
||||
// übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
time_t offsetDayLightSaving (uint32_t local_t) {
|
||||
//--------------------------------------------
|
||||
int monat = month (local_t);
|
||||
if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
int jahr = year (local_t);
|
||||
int std = hour (local_t);
|
||||
//int tag = day (local_t);
|
||||
int stundenBisHeute = (std + 24 * day(local_t));
|
||||
if ( (monat == 3 && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) ||
|
||||
(monat == 10 && stundenBisHeute < (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) )
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
/*
|
||||
int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7));
|
||||
if (monat == 3 && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute < stundenBisWechsel)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
time_t getNow () {
|
||||
//---------------
|
||||
time_t jetzt = now();
|
||||
while (!isValidDateTime(jetzt) && getTimeTrials < 10) { // ungültig, max 10x probieren
|
||||
if (getTimeTrials) {
|
||||
//Serial.print (getTimeTrials);
|
||||
//Serial.println(". Versuch für getNtpTime");
|
||||
}
|
||||
jetzt = getNtpTime ();
|
||||
if (isValidDateTime(jetzt)) {
|
||||
setTime (jetzt);
|
||||
getTimeTrials = 0;
|
||||
}
|
||||
else
|
||||
getTimeTrials++;
|
||||
}
|
||||
//return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
|
||||
return jetzt;
|
||||
}
|
||||
|
||||
|
||||
char _timestr[24];
|
||||
|
||||
char* getNowStr (time_t no = getNow()) {
|
||||
//------------------------------------
|
||||
sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
char* getTimeStr (time_t no = getNow()) {
|
||||
//------------------------------------
|
||||
return getNowStr (no);
|
||||
}
|
||||
|
||||
char* getDateTimeStr (time_t no) {
|
||||
//------------------------------
|
||||
sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
char* getDateStr (time_t no) {
|
||||
//------------------------------
|
||||
sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no));
|
||||
return _timestr;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
157
tools/esp8266/CircularBuffer.h
Normal file
157
tools/esp8266/CircularBuffer.h
Normal file
|
@ -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
|
60
tools/esp8266/README.md
Normal file
60
tools/esp8266/README.md
Normal file
|
@ -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`
|
||||
|
611
tools/esp8266/app.cpp
Normal file
611
tools/esp8266/app.cpp
Normal file
|
@ -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);
|
||||
}
|
94
tools/esp8266/app.h
Normal file
94
tools/esp8266/app.h
Normal file
|
@ -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__*/
|
44
tools/esp8266/config.h
Normal file
44
tools/esp8266/config.h
Normal file
|
@ -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__*/
|
43
tools/esp8266/crc.cpp
Normal file
43
tools/esp8266/crc.cpp
Normal file
|
@ -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;
|
||||
}
|
16
tools/esp8266/crc.h
Normal file
16
tools/esp8266/crc.h
Normal file
|
@ -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__*/
|
39
tools/esp8266/debug.h
Normal file
39
tools/esp8266/debug.h
Normal file
|
@ -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__*/
|
92
tools/esp8266/defines.h
Normal file
92
tools/esp8266/defines.h
Normal file
|
@ -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__*/
|
133
tools/esp8266/eep.h
Normal file
133
tools/esp8266/eep.h
Normal file
|
@ -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__*/
|
33
tools/esp8266/esp8266.ino
Normal file
33
tools/esp8266/esp8266.ino
Normal file
|
@ -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();
|
||||
}
|
159
tools/esp8266/hmDefines.h
Normal file
159
tools/esp8266/hmDefines.h
Normal file
|
@ -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__*/
|
213
tools/esp8266/hmInverter.h
Normal file
213
tools/esp8266/hmInverter.h
Normal file
|
@ -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__*/
|
272
tools/esp8266/hmRadio.h
Normal file
272
tools/esp8266/hmRadio.h
Normal file
|
@ -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__*/
|
82
tools/esp8266/hmSystem.h
Normal file
82
tools/esp8266/hmSystem.h
Normal file
|
@ -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__*/
|
28
tools/esp8266/html/convert.py
Normal file
28
tools/esp8266/html/convert.py
Normal file
|
@ -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")
|
4
tools/esp8266/html/h/hoymiles_html.h
Normal file
4
tools/esp8266/html/h/hoymiles_html.h
Normal file
|
@ -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__*/
|
4
tools/esp8266/html/h/index_html.h
Normal file
4
tools/esp8266/html/h/index_html.h
Normal file
|
@ -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__*/
|
4
tools/esp8266/html/h/setup_html.h
Normal file
4
tools/esp8266/html/h/setup_html.h
Normal file
|
@ -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__*/
|
4
tools/esp8266/html/h/style_css.h
Normal file
4
tools/esp8266/html/h/style_css.h
Normal file
|
@ -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__*/
|
42
tools/esp8266/html/hoymiles.html
Normal file
42
tools/esp8266/html/hoymiles.html
Normal file
|
@ -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>
|
55
tools/esp8266/html/index.html
Normal file
55
tools/esp8266/html/index.html
Normal file
|
@ -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>
|
79
tools/esp8266/html/setup.html
Normal file
79
tools/esp8266/html/setup.html
Normal file
|
@ -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>
|
190
tools/esp8266/html/style.css
Normal file
190
tools/esp8266/html/style.css
Normal file
|
@ -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;
|
||||
}
|
432
tools/esp8266/main.cpp
Normal file
432
tools/esp8266/main.cpp
Normal file
|
@ -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;
|
||||
}
|
125
tools/esp8266/main.h
Normal file
125
tools/esp8266/main.h
Normal file
|
@ -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__*/
|
87
tools/esp8266/mqtt.h
Normal file
87
tools/esp8266/mqtt.h
Normal file
|
@ -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_*/
|
31
tools/esp8266/test/hmClassTest/hmClassTest.sln
Normal file
31
tools/esp8266/test/hmClassTest/hmClassTest.sln
Normal file
|
@ -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
|
147
tools/esp8266/test/hmClassTest/hmClassTest/hmClassTest.vcxproj
Normal file
147
tools/esp8266/test/hmClassTest/hmClassTest/hmClassTest.vcxproj
Normal file
|
@ -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>
|
105
tools/esp8266/test/hmClassTest/src/main.cpp
Normal file
105
tools/esp8266/test/hmClassTest/src/main.cpp
Normal file
|
@ -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);
|
||||
}
|
|
@ -109,6 +109,7 @@ int main(int argc, char** argv)
|
|||
dstaddrs.push_back(string("1Node"));
|
||||
dstaddrs.push_back(string("2Node"));
|
||||
dstaddrs.push_back(serno2shockburstaddrbytes(114174608145));
|
||||
dstaddrs.push_back("\x45\x81\x60\x74\x01");
|
||||
dstaddrs.push_back(serno2shockburstaddrbytes(114174608177));
|
||||
|
||||
// channels that we will scan
|
||||
|
@ -127,7 +128,7 @@ int main(int argc, char** argv)
|
|||
cout << " - ";
|
||||
}
|
||||
cout << " " << flush;
|
||||
delay(10);
|
||||
//delay(10);
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <sstream>
|
||||
#include <time.h> // CLOCK_MONOTONIC_RAW, timespec, clock_gettime()
|
||||
#include <RF24/RF24.h> // RF24, RF24_PA_LOW, delay()
|
||||
#include <unistd.h> // usleep()
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -45,7 +46,7 @@ void receiveForever(int ch, string myaddr)
|
|||
while (true)
|
||||
{
|
||||
uint8_t pipe;
|
||||
delay(500);
|
||||
usleep(500000);
|
||||
if (radio.failureDetected) {
|
||||
cout << "!f! " << flush;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue