mirror of https://github.com/lumapu/ahoy.git
Browse Source
https://www.mikrocontroller.net/topic/525778?page=4#7048605 ersetzt NRF24_SendRcv Kommentare dazu: - Projekt jetzt umgenannt in HoyDtuSim (Hoymiles DTU Simulation) -Läuft auf Arduino (bei mir auf Pro Mini) und ESP (Wemos D1 mini), je nachdem wie man kompiliert - Channel hopping für senden und Empfangen (poor man's ...) ist eingebaut und bringt konstante Antworten; obige Erkenntnisse über Kanäle abwärts sind noch nicht eingebaut - da manchmal ein Abbruch der RF-Verbindung vorkam (auch schon oben erwähnt) wird jetzt nach ca 50 Sekunden ohne Empfang das RF-Modul neu initialisiert und es geht problemlos weiter - Definitionen für HM-600 und HM-1200 sind implementiert, andere können anhand der beiden Beispiele sicher leicht impl. werden - Anpassungen sind in der Settings.h zu machenpull/16/head
Marcus
3 years ago
12 changed files with 1959 additions and 0 deletions
@ -0,0 +1,158 @@ |
|||
/*
|
|||
CircularBuffer - An Arduino circular buffering library for arbitrary types. |
|||
|
|||
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2.1 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library; if not, write to the Free Software |
|||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|||
*/ |
|||
|
|||
#ifndef CircularBuffer_h |
|||
#define CircularBuffer_h |
|||
|
|||
#ifdef ESP8266 |
|||
#define DISABLE_IRQ noInterrupts() |
|||
#define RESTORE_IRQ interrupts() |
|||
#else |
|||
#define DISABLE_IRQ \ |
|||
uint8_t sreg = SREG; \ |
|||
cli(); |
|||
|
|||
#define RESTORE_IRQ \ |
|||
SREG = sreg; |
|||
#endif |
|||
|
|||
template <class T> class CircularBuffer |
|||
{ |
|||
public: |
|||
/** Constructor
|
|||
* @param buffer Preallocated buffer of at least size records. |
|||
* @param size Number of records available in the buffer. |
|||
*/ |
|||
CircularBuffer(T* buffer, const uint8_t size ) |
|||
: m_size(size), m_buff(buffer) |
|||
{ |
|||
clear(); |
|||
} |
|||
|
|||
/** Clear all entries in the circular buffer. */ |
|||
void clear(void) |
|||
{ |
|||
m_front = 0; |
|||
m_fill = 0; |
|||
} |
|||
|
|||
/** Test if the circular buffer is empty */ |
|||
inline bool empty(void) const |
|||
{ |
|||
return !m_fill; |
|||
} |
|||
|
|||
/** Return the number of records stored in the buffer */ |
|||
inline uint8_t available(void) const |
|||
{ |
|||
return m_fill; |
|||
} |
|||
|
|||
/** Test if the circular buffer is full */ |
|||
inline bool full(void) const |
|||
{ |
|||
return m_fill == m_size; |
|||
} |
|||
|
|||
/** Aquire record on front of the buffer, for writing.
|
|||
* After filling the record, it has to be pushed to actually |
|||
* add it to the buffer. |
|||
* @return Pointer to record, or NULL when buffer is full. |
|||
*/ |
|||
T* getFront(void) const |
|||
{ |
|||
DISABLE_IRQ; |
|||
T* f = NULL; |
|||
if (!full()) |
|||
f = get(m_front); |
|||
RESTORE_IRQ; |
|||
return f; |
|||
} |
|||
|
|||
/** Push record to front of the buffer
|
|||
* @param record Record to push. If record was aquired previously (using getFront) its |
|||
* data will not be copied as it is already present in the buffer. |
|||
* @return True, when record was pushed successfully. |
|||
*/ |
|||
bool pushFront(T* record) |
|||
{ |
|||
bool ok = false; |
|||
DISABLE_IRQ; |
|||
if (!full()) |
|||
{ |
|||
T* f = get(m_front); |
|||
if (f != record) |
|||
*f = *record; |
|||
m_front = (m_front+1) % m_size; |
|||
m_fill++; |
|||
ok = true; |
|||
} |
|||
RESTORE_IRQ; |
|||
return ok; |
|||
} |
|||
|
|||
/** Aquire record on back of the buffer, for reading.
|
|||
* After reading the record, it has to be pop'ed to actually |
|||
* remove it from the buffer. |
|||
* @return Pointer to record, or NULL when buffer is empty. |
|||
*/ |
|||
T* getBack(void) const |
|||
{ |
|||
T* b = NULL; |
|||
DISABLE_IRQ; |
|||
if (!empty()) |
|||
b = get(back()); |
|||
RESTORE_IRQ; |
|||
return b; |
|||
} |
|||
|
|||
/** Remove record from back of the buffer.
|
|||
* @return True, when record was pop'ed successfully. |
|||
*/ |
|||
bool popBack(void) |
|||
{ |
|||
bool ok = false; |
|||
DISABLE_IRQ; |
|||
if (!empty()) |
|||
{ |
|||
m_fill--; |
|||
ok = true; |
|||
} |
|||
RESTORE_IRQ; |
|||
return ok; |
|||
} |
|||
|
|||
protected: |
|||
inline T * get(const uint8_t idx) const |
|||
{ |
|||
return &(m_buff[idx]); |
|||
} |
|||
inline uint8_t back(void) const |
|||
{ |
|||
return (m_front - m_fill + m_size) % m_size; |
|||
} |
|||
|
|||
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
|||
T* const m_buff; // Ptr to buffer holding all records.
|
|||
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
|||
volatile uint8_t m_fill; // Amount of records currently pushed.
|
|||
}; |
|||
|
|||
#endif // CircularBuffer_h
|
@ -0,0 +1,23 @@ |
|||
#ifndef __DEBUG_H |
|||
|
|||
#define __DEBUG_H |
|||
|
|||
#ifdef DEBUG |
|||
#define DEBUG_OUT Serial |
|||
#else |
|||
//---
|
|||
// disable Serial DEBUG output
|
|||
#define DEBUG_OUT DummySerial |
|||
static class { |
|||
public: |
|||
void begin(...) {} |
|||
void print(...) {} |
|||
void println(...) {} |
|||
void flush() {} |
|||
bool available() { return false;} |
|||
int readBytes(...) { return 0;} |
|||
int printf (...) {return 0;} |
|||
} DummySerial; |
|||
#endif |
|||
|
|||
#endif |
@ -0,0 +1,38 @@ |
|||
#ifndef __HM1200_H |
|||
#define __HM1200_H |
|||
|
|||
#define HM1200 |
|||
|
|||
const measureDef_t hm1200_measureDef[] = { |
|||
{ IDX_UDC, UNIT_V, CH1, CMD01, 14, BYTES2, DIV10 }, |
|||
{ IDX_IDC, UNIT_A, CH1, CMD01, 16, BYTES2, DIV100 }, |
|||
{ IDX_PDC, UNIT_W, CH1, CMD01, 20, BYTES2, DIV10 }, |
|||
{ IDX_E_TAG, UNIT_WH, CH1, CMD02, 16, BYTES2, DIV1 }, |
|||
{ IDX_E_TOTAL, UNIT_KWH, CH1, CMD01, 24, BYTES4, DIV1000 }, |
|||
{ IDX_UDC, UNIT_V, CH2, CMD02, 20, BYTES2, DIV10 }, |
|||
{ IDX_IDC, UNIT_A, CH2, CMD01, 18, BYTES2, DIV100 }, |
|||
{ IDX_PDC, UNIT_W, CH2, CMD01, 22, BYTES2, DIV10 }, |
|||
{ IDX_E_TAG, UNIT_WH, CH2, CMD02, 18, BYTES2, DIV1 }, |
|||
{ IDX_E_TOTAL, UNIT_KWH, CH2, CMD02, 12, BYTES4, DIV1000 }, |
|||
{ IDX_IDC, UNIT_A, CH3, CMD02, 22, BYTES2, DIV100 }, |
|||
{ IDX_PDC, UNIT_W, CH3, CMD02, 26, BYTES2, DIV10 }, |
|||
{ IDX_E_TAG, UNIT_WH, CH3, CMD03, 22, BYTES2, DIV1 }, |
|||
{ IDX_E_TOTAL, UNIT_KWH, CH3, CMD03, 14, BYTES4, DIV1000 }, |
|||
{ IDX_IDC, UNIT_A, CH4, CMD02, 24, BYTES2, DIV100 }, |
|||
{ IDX_PDC, UNIT_W, CH4, CMD03, 12, BYTES2, DIV10 }, |
|||
{ IDX_E_TAG, UNIT_WH, CH4, CMD03, 24, BYTES2, DIV1 }, |
|||
{ IDX_E_TOTAL, UNIT_KWH, CH4, CMD03, 18, BYTES4, DIV1000 }, |
|||
{ IDX_UAC, UNIT_V, CH0, CMD03, 26, BYTES2, DIV10 }, |
|||
{ IDX_IPV, UNIT_A, CH0, CMD84, 18, BYTES2, DIV100 }, |
|||
{ IDX_PAC, UNIT_W, CH0, CMD84, 14, BYTES2, DIV10 }, |
|||
{ IDX_FREQ, UNIT_HZ, CH0, CMD84, 12, BYTES2, DIV100 }, |
|||
{ IDX_PERCNT, UNIT_PCT, CH0, CMD84, 20, BYTES2, DIV10 }, |
|||
{ IDX_WR_TEMP, UNIT_C, CH0, CMD84, 22, BYTES2, DIV10 } |
|||
}; |
|||
|
|||
measureCalc_t hm1200_measureCalc[] = {}; |
|||
|
|||
#define HM1200_MEASURE_LIST_LEN sizeof(hm1200_measureDef)/sizeof(measureDef_t) |
|||
#define HM1200_CALCED_LIST_LEN 0 |
|||
|
|||
#endif |
@ -0,0 +1,37 @@ |
|||
#ifndef __HM600_H |
|||
#define __HM600_H |
|||
|
|||
#define HM600 |
|||
#define HM700 |
|||
|
|||
|
|||
float calcEheute (float *measure) { return measure[8] + measure[9]; } |
|||
float calcIpv (float *measure) { return (measure[10] != 0 ? measure[12]/measure[10] : 0); } |
|||
|
|||
const measureDef_t hm600_measureDef[] = { |
|||
{ IDX_UDC, CH1, UNIT_V, CMD01, 14, BYTES2, DIV10}, |
|||
{ IDX_IDC, CH1, UNIT_A, CMD01, 16, BYTES2, DIV100}, |
|||
{ IDX_PDC, CH1, UNIT_W, CMD01, 18, BYTES2, DIV10}, |
|||
{ IDX_UDC, CH2, UNIT_V, CMD01, 20, BYTES2, DIV10}, |
|||
{ IDX_IDC, CH2, UNIT_A, CMD01, 22, BYTES2, DIV100}, |
|||
{ IDX_PDC, CH2, UNIT_W, CMD01, 24, BYTES2, DIV10}, |
|||
{ IDX_E_WOCHE,CH0, UNIT_WH, CMD02, 12, BYTES2, DIV1}, |
|||
{ IDX_E_TOTAL,CH0, UNIT_WH, CMD02, 14, BYTES4, DIV1}, |
|||
{ IDX_E_TAG, CH1, UNIT_WH, CMD02, 18, BYTES2, DIV1}, |
|||
{ IDX_E_TAG, CH2, UNIT_WH, CMD02, 20, BYTES2, DIV1}, |
|||
{ IDX_UAC, CH0, UNIT_V, CMD02, 22, BYTES2, DIV10}, |
|||
{ IDX_FREQ, CH0, UNIT_HZ, CMD02, 24, BYTES2, DIV100}, |
|||
{ IDX_PAC, CH0, UNIT_W, CMD02, 26, BYTES2, DIV10}, |
|||
{ IDX_WR_TEMP,CH0, UNIT_C, CMD83, 18, BYTES2, DIV10} |
|||
}; |
|||
|
|||
|
|||
measureCalc_t hm600_measureCalc[] = { |
|||
{ IDX_E_HEUTE, UNIT_WH, DIV1, &calcEheute}, |
|||
{ IDX_IPV, UNIT_A, DIV100, &calcIpv} |
|||
}; |
|||
|
|||
#define HM600_MEASURE_LIST_LEN sizeof(hm600_measureDef)/sizeof(measureDef_t) |
|||
#define HM600_CALCED_LIST_LEN sizeof(hm600_measureCalc)/sizeof(measureCalc_t) |
|||
|
|||
#endif |
@ -0,0 +1,605 @@ |
|||
#include <Arduino.h> |
|||
#include <SPI.h> |
|||
#include "CircularBuffer.h" |
|||
#include <RF24.h> |
|||
#include "printf.h" |
|||
#include <RF24_config.h> |
|||
#include "hm_crc.h" |
|||
#include "hm_packets.h" |
|||
|
|||
#include "Settings.h" // Header für Einstellungen |
|||
#include "Debug.h" |
|||
#include "Inverters.h" |
|||
|
|||
const char VERSION[] PROGMEM = "0.1.6"; |
|||
|
|||
|
|||
#ifdef ESP8266 |
|||
#define DISABLE_EINT noInterrupts() |
|||
#define ENABLE_EINT interrupts() |
|||
#else // für AVR z.B. ProMini oder Nano
|
|||
#define DISABLE_EINT EIMSK = 0x00 |
|||
#define ENABLE_EINT EIMSK = 0x01 |
|||
#endif |
|||
|
|||
|
|||
#ifdef ESP8266 |
|||
#define PACKET_BUFFER_SIZE (30) |
|||
#else |
|||
#define PACKET_BUFFER_SIZE (20) |
|||
#endif |
|||
|
|||
// Startup defaults until user reconfigures it
|
|||
//#define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles
|
|||
//#define DEFAULT_SEND_CHANNEL (75) // 40 = Default channel for Hoymiles, 61
|
|||
|
|||
static HM_Packets hmPackets; |
|||
static uint32_t tickMillis; |
|||
|
|||
// Set up nRF24L01 radio on SPI bus plus CE/CS pins
|
|||
// If more than one RF24 unit is used the another CS pin than 10 must be used
|
|||
// This pin is used hard coded in SPI library
|
|||
static RF24 Radio (RF1_CE_PIN, RF1_CS_PIN); |
|||
|
|||
static NRF24_packet_t bufferData[PACKET_BUFFER_SIZE]; |
|||
|
|||
static CircularBuffer<NRF24_packet_t> packetBuffer(bufferData, sizeof(bufferData) / sizeof(bufferData[0])); |
|||
|
|||
static Serial_header_t SerialHdr; |
|||
|
|||
#define CHECKCRC 1 |
|||
static uint16_t lastCRC; |
|||
static uint16_t crc; |
|||
|
|||
uint8_t channels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
|||
uint8_t channelIdx = 2; // fange mit 40 an
|
|||
uint8_t DEFAULT_SEND_CHANNEL = channels[channelIdx]; // = 40
|
|||
|
|||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
|||
uint8_t rcvChannelIdx = 0; |
|||
uint8_t rcvChannels[] = {3, 23, 40, 61, 75}; //{1, 3, 6, 9, 11, 23, 40, 61, 75}
|
|||
uint8_t DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx]; //3;
|
|||
uint8_t intvl = 4; // Zeit für poor man hopping
|
|||
int hophop; |
|||
#else |
|||
uint8_t DEFAULT_RECV_CHANNEL = 3; |
|||
#endif |
|||
|
|||
boolean valueChanged = false; |
|||
|
|||
static unsigned long timeLastPacket = millis(); |
|||
static unsigned long timeLastIstTagCheck = millis(); |
|||
static unsigned long timeLastRcvChannelSwitch = millis(); |
|||
|
|||
// Function forward declaration
|
|||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len); |
|||
|
|||
|
|||
static const char BLANK = ' '; |
|||
|
|||
static boolean istTag = true; |
|||
|
|||
char CHANNELNAME_BUFFER[15]; |
|||
|
|||
#ifdef ESP8266 |
|||
#include "wifi.h" |
|||
#include "ModWebserver.h" |
|||
#include "Sonne.h" |
|||
#endif |
|||
|
|||
|
|||
inline static void dumpData(uint8_t *p, int len) { |
|||
//-----------------------------------------------
|
|||
while (len > 0){ |
|||
if (*p < 16) |
|||
DEBUG_OUT.print(F("0")); |
|||
DEBUG_OUT.print(*p++, HEX); |
|||
len--; |
|||
} |
|||
DEBUG_OUT.print(BLANK); |
|||
} |
|||
|
|||
|
|||
float extractValue2 (uint8_t *p, int divisor) { |
|||
//-------------------------------------------
|
|||
uint16_t b1 = *p++; |
|||
return ((float) (b1 << 8) + *p) / (float) divisor; |
|||
} |
|||
|
|||
|
|||
float extractValue4 (uint8_t *p, int divisor) { |
|||
//-------------------------------------------
|
|||
uint32_t ret = *p++; |
|||
for (uint8_t i = 1; i <= 3; i++) |
|||
ret = (ret << 8) + *p++; |
|||
return (ret / divisor); |
|||
} |
|||
|
|||
void outChannel (uint8_t wr, uint8_t i) { |
|||
//------------------------------------
|
|||
DEBUG_OUT.print(getMeasureName(wr, i)); |
|||
DEBUG_OUT.print(F("\t:")); |
|||
DEBUG_OUT.print(getMeasureValue(wr,i)); |
|||
DEBUG_OUT.println(BLANK); |
|||
} |
|||
|
|||
|
|||
void analyseWords (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
|||
//----------------------------------
|
|||
//uint16_t val;
|
|||
DEBUG_OUT.print (F("analyse words:")); |
|||
p++; |
|||
for (int i = 0; i <12;i++) { |
|||
DEBUG_OUT.print(extractValue2(p,1)); |
|||
DEBUG_OUT.print(BLANK); |
|||
p++; |
|||
} |
|||
DEBUG_OUT.println(); |
|||
} |
|||
|
|||
void analyseLongs (uint8_t *p) { // p zeigt auf 01 hinter 2. WR-Adr
|
|||
//----------------------------------
|
|||
//uint16_t val;
|
|||
DEBUG_OUT.print (F("analyse longs:")); |
|||
p++; |
|||
for (int i = 0; i <12;i++) { |
|||
DEBUG_OUT.print(extractValue4(p,1)); |
|||
DEBUG_OUT.print(BLANK); |
|||
p++; |
|||
} |
|||
DEBUG_OUT.println(); |
|||
} |
|||
|
|||
|
|||
void analyse (NRF24_packet_t *p) { |
|||
//------------------------------
|
|||
uint8_t wrIdx = findInverter (&p->packet[3]); |
|||
//DEBUG_OUT.print ("wrIdx="); DEBUG_OUT.println (wrIdx);
|
|||
if (wrIdx == 0xFF) return; |
|||
uint8_t cmd = p->packet[11]; |
|||
float val = 0; |
|||
if (cmd == 0x01 || cmd == 0x02 || cmd == 0x83) { |
|||
const measureDef_t *defs = inverters[wrIdx].measureDef; |
|||
|
|||
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasures; i++) { |
|||
if (defs[i].teleId == cmd) { |
|||
uint8_t pos = defs[i].pos; |
|||
if (defs[i].bytes == 2) |
|||
val = extractValue2 (&p->packet[pos], getDivisor(wrIdx, i) ); |
|||
else if (defs[i].bytes == 4) |
|||
val = extractValue4 (&p->packet[pos], getDivisor(wrIdx, i) ); |
|||
valueChanged = valueChanged ||(val != inverters[wrIdx].values[i]); |
|||
inverters[wrIdx].values[i] = val; |
|||
} |
|||
} |
|||
// calculated funstions
|
|||
for (uint8_t i = 0; i < inverters[wrIdx].anzMeasureCalculated; i++) { |
|||
val = inverters[wrIdx].measureCalculated[i].f (inverters[wrIdx].values); |
|||
int idx = inverters[wrIdx].anzMeasures + i; |
|||
valueChanged = valueChanged ||(val != inverters[wrIdx].values[idx]); |
|||
inverters[wrIdx].values[idx] = val; |
|||
} |
|||
} |
|||
else if (cmd == 0x81) { |
|||
; |
|||
} |
|||
else { |
|||
DEBUG_OUT.print (F("---- neues cmd=")); DEBUG_OUT.println(cmd, HEX); |
|||
analyseWords (&p->packet[11]); |
|||
analyseLongs (&p->packet[11]); |
|||
DEBUG_OUT.println(); |
|||
} |
|||
if (p->packetsLost > 0) { |
|||
DEBUG_OUT.print(F(" Lost: ")); |
|||
DEBUG_OUT.println(p->packetsLost); |
|||
} |
|||
} |
|||
|
|||
#ifdef ESP8266 |
|||
IRAM_ATTR |
|||
#endif |
|||
void handleNrf1Irq() { |
|||
//-------------------------
|
|||
static uint8_t lostPacketCount = 0; |
|||
uint8_t pipe; |
|||
|
|||
DISABLE_EINT; |
|||
|
|||
// Loop until RX buffer(s) contain no more packets.
|
|||
while (Radio.available(&pipe)) { |
|||
if (!packetBuffer.full()) { |
|||
NRF24_packet_t *p = packetBuffer.getFront(); |
|||
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
|||
p->packetsLost = lostPacketCount; |
|||
p->rcvChannel = DEFAULT_RECV_CHANNEL; |
|||
uint8_t packetLen = Radio.getPayloadSize(); |
|||
if (packetLen > MAX_RF_PAYLOAD_SIZE) |
|||
packetLen = MAX_RF_PAYLOAD_SIZE; |
|||
|
|||
Radio.read(p->packet, packetLen); |
|||
packetBuffer.pushFront(p); |
|||
lostPacketCount = 0; |
|||
} |
|||
else { |
|||
// Buffer full. Increase lost packet counter.
|
|||
bool tx_ok, tx_fail, rx_ready; |
|||
if (lostPacketCount < 255) |
|||
lostPacketCount++; |
|||
// Call 'whatHappened' to reset interrupt status.
|
|||
Radio.whatHappened(tx_ok, tx_fail, rx_ready); |
|||
// Flush buffer to drop the packet.
|
|||
Radio.flush_rx(); |
|||
} |
|||
} |
|||
ENABLE_EINT; |
|||
} |
|||
|
|||
|
|||
static void activateConf(void) { |
|||
//-----------------------------
|
|||
Radio.begin(); |
|||
// Disable shockburst for receiving and decode payload manually
|
|||
Radio.setAutoAck(false); |
|||
Radio.setRetries(0, 0); |
|||
Radio.setChannel(DEFAULT_RECV_CHANNEL); |
|||
Radio.setDataRate(DEFAULT_RF_DATARATE); |
|||
Radio.disableCRC(); |
|||
Radio.setAutoAck(0x00); |
|||
Radio.setPayloadSize(MAX_RF_PAYLOAD_SIZE); |
|||
Radio.setAddressWidth(5); |
|||
Radio.openReadingPipe(1, DTU_RADIO_ID); |
|||
|
|||
// We want only RX irqs
|
|||
Radio.maskIRQ(true, true, false); |
|||
|
|||
// Use lo PA level, as a higher level will disturb CH340 DEBUG_OUT usb adapter
|
|||
Radio.setPALevel(RF24_PA_MAX); |
|||
Radio.startListening(); |
|||
|
|||
// Attach interrupt handler to NRF IRQ output. Overwrites any earlier handler.
|
|||
attachInterrupt(digitalPinToInterrupt(RF1_IRQ_PIN), handleNrf1Irq, FALLING); // NRF24 Irq pin is active low.
|
|||
|
|||
// Initialize SerialHdr header's address member to promiscuous address.
|
|||
uint64_t addr = DTU_RADIO_ID; |
|||
for (int8_t i = sizeof(SerialHdr.address) - 1; i >= 0; --i) { |
|||
SerialHdr.address[i] = addr; |
|||
addr >>= 8; |
|||
} |
|||
|
|||
//Radio.printDetails();
|
|||
//DEBUG_OUT.println();
|
|||
tickMillis = millis() + 200; |
|||
} |
|||
|
|||
#define resetRF24() activateConf() |
|||
|
|||
|
|||
void setup(void) { |
|||
//--------------
|
|||
#ifndef DEBUG |
|||
#ifndef ESP8266 |
|||
Serial.begin(SER_BAUDRATE); |
|||
#endif |
|||
#endif |
|||
printf_begin(); |
|||
DEBUG_OUT.begin(SER_BAUDRATE); |
|||
DEBUG_OUT.flush(); |
|||
|
|||
DEBUG_OUT.println(F("-- Hoymiles DTU Simulation --")); |
|||
|
|||
// Configure nRF IRQ input
|
|||
pinMode(RF1_IRQ_PIN, INPUT); |
|||
|
|||
activateConf(); |
|||
|
|||
#ifdef ESP8266 |
|||
setupWifi(); |
|||
setupClock(); |
|||
setupWebServer(); |
|||
setupUpdateByOTA(); |
|||
calcSunUpDown (getNow()); |
|||
istTag = isDayTime(); |
|||
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); |
|||
hmPackets.SetUnixTimeStamp (getNow()); |
|||
#else |
|||
hmPackets.SetUnixTimeStamp(0x62456430); |
|||
#endif |
|||
|
|||
setupInverts(); |
|||
} |
|||
|
|||
uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE]; |
|||
|
|||
void isTime2Send () { |
|||
//-----------------
|
|||
// Second timer
|
|||
static const uint8_t warteZeit = 1; |
|||
static uint8_t tickSec = 0; |
|||
if (millis() >= tickMillis) { |
|||
static uint8_t tel = 0; |
|||
tickMillis += warteZeit*1000; //200;
|
|||
tickSec++; |
|||
|
|||
if (++tickSec >= 1) { // 5
|
|||
for (uint8_t c=0; c < warteZeit; c++) hmPackets.UnixTimeStampTick(); |
|||
tickSec = 0; |
|||
} |
|||
|
|||
int32_t size = 0; |
|||
uint64_t dest = 0; |
|||
for (uint8_t wr = 0; wr < anzInv; wr++) { |
|||
dest = inverters[wr].RadioId; |
|||
|
|||
if (tel > 1) |
|||
tel = 0; |
|||
|
|||
if (tel == 0) { |
|||
#ifdef ESP8266 |
|||
hmPackets.SetUnixTimeStamp (getNow()); |
|||
#endif |
|||
size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8); |
|||
//DEBUG_OUT.print ("Timepacket mit cid="); DEBUG_OUT.println(sendBuf[10], HEX);
|
|||
} |
|||
else if (tel <= 1) |
|||
size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x80 + tel - 1); |
|||
|
|||
SendPacket (dest, (uint8_t *)&sendBuf, size); |
|||
} // for wr
|
|||
|
|||
tel++; |
|||
|
|||
/* for (uint8_t warte = 0; warte < 2; warte++) {
|
|||
delay(1000); |
|||
hmPackets.UnixTimeStampTick(); |
|||
}*/ |
|||
} |
|||
} |
|||
|
|||
|
|||
void outputPacket(NRF24_packet_t *p, uint8_t payloadLen) { |
|||
//-----------------------------------------------------
|
|||
|
|||
// Write timestamp, packets lost, address and payload length
|
|||
//printf(" %09lu ", SerialHdr.timestamp);
|
|||
char _buf[20]; |
|||
sprintf_P(_buf, PSTR("rcv CH:%d "), p->rcvChannel); |
|||
DEBUG_OUT.print (_buf); |
|||
dumpData((uint8_t *)&SerialHdr.packetsLost, sizeof(SerialHdr.packetsLost)); |
|||
dumpData((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address)); |
|||
|
|||
// Trailing bit?!?
|
|||
dumpData(&p->packet[0], 2); |
|||
|
|||
// Payload length from PCF
|
|||
dumpData(&payloadLen, sizeof(payloadLen)); |
|||
|
|||
// Packet control field - PID Packet identification
|
|||
uint8_t val = (p->packet[1] >> 1) & 0x03; |
|||
DEBUG_OUT.print(val); |
|||
DEBUG_OUT.print(F(" ")); |
|||
|
|||
if (payloadLen > 9) { |
|||
dumpData(&p->packet[2], 1); |
|||
dumpData(&p->packet[3], 4); |
|||
dumpData(&p->packet[7], 4); |
|||
|
|||
uint16_t remain = payloadLen - 2 - 1 - 4 - 4 + 4; |
|||
|
|||
if (remain < 32) { |
|||
dumpData(&p->packet[11], remain); |
|||
printf_P(PSTR("%04X "), crc); |
|||
|
|||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) |
|||
DEBUG_OUT.print(0); |
|||
else |
|||
DEBUG_OUT.print(1); |
|||
} |
|||
else { |
|||
DEBUG_OUT.print(F("Ill remain ")); |
|||
DEBUG_OUT.print(remain); |
|||
} |
|||
} |
|||
else { |
|||
dumpData(&p->packet[2], payloadLen + 2); |
|||
printf_P(PSTR("%04X "), crc); |
|||
} |
|||
DEBUG_OUT.println(); |
|||
DEBUG_OUT.flush(); |
|||
} |
|||
|
|||
void writeArduinoInterface() { |
|||
//--------------------------
|
|||
if (valueChanged) { |
|||
for (uint8_t wr = 0; wr < anzInv; wr++) { |
|||
if (anzInv > 1) { |
|||
Serial.print(wr); Serial.print('.'); |
|||
} |
|||
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
|||
Serial.print(getMeasureName(wr,i)); // Schnittstelle bei Arduino
|
|||
Serial.print('='); |
|||
Serial.print(getMeasureValue(wr,i), getDigits(wr,i)); // Schnittstelle bei Arduino
|
|||
Serial.print (BLANK); |
|||
Serial.println (getUnit(wr, i)); |
|||
} // for i
|
|||
|
|||
} // for wr
|
|||
Serial.println(F("-----------------------")); |
|||
valueChanged = false; |
|||
} |
|||
} |
|||
|
|||
boolean doCheckCrc (NRF24_packet_t *p, uint8_t payloadLen) { |
|||
//--------------------------------------------------------
|
|||
crc = 0xFFFF; |
|||
crc = crc16((uint8_t *)&SerialHdr.address, sizeof(SerialHdr.address), crc, 0, BYTES_TO_BITS(sizeof(SerialHdr.address))); |
|||
// Payload length
|
|||
// Add one byte and one bit for 9-bit packet control field
|
|||
crc = crc16((uint8_t *)&p->packet[0], sizeof(p->packet), crc, 7, BYTES_TO_BITS(payloadLen + 1) + 1); |
|||
|
|||
if (CHECKCRC) { |
|||
// If CRC is invalid only show lost packets
|
|||
if (((crc >> 8) != p->packet[payloadLen + 2]) || ((crc & 0xFF) != p->packet[payloadLen + 3])) { |
|||
if (p->packetsLost > 0) { |
|||
DEBUG_OUT.print(F(" Lost: ")); |
|||
DEBUG_OUT.println(p->packetsLost); |
|||
} |
|||
packetBuffer.popBack(); |
|||
return false; |
|||
} |
|||
|
|||
// Dump a decoded packet only once
|
|||
if (lastCRC == crc) { |
|||
packetBuffer.popBack(); |
|||
return false; |
|||
} |
|||
lastCRC = crc; |
|||
} |
|||
|
|||
// Don't dump mysterious ack packages
|
|||
if (payloadLen == 0) { |
|||
packetBuffer.popBack(); |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void poorManChannelHopping() { |
|||
//--------------------------
|
|||
if (hophop <= 0) return; |
|||
if (millis() >= timeLastRcvChannelSwitch + intvl) { |
|||
rcvChannelIdx++; |
|||
if (rcvChannelIdx >= sizeof(rcvChannels)) |
|||
rcvChannelIdx = 0; |
|||
DEFAULT_RECV_CHANNEL = rcvChannels[rcvChannelIdx]; |
|||
DISABLE_EINT; |
|||
Radio.stopListening(); |
|||
Radio.setChannel (DEFAULT_RECV_CHANNEL); |
|||
Radio.startListening(); |
|||
ENABLE_EINT; |
|||
timeLastRcvChannelSwitch = millis(); |
|||
hophop--; |
|||
} |
|||
|
|||
} |
|||
void loop(void) { |
|||
//=============
|
|||
// poor man channel hopping on receive
|
|||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
|||
poorManChannelHopping(); |
|||
#endif |
|||
|
|||
if (millis() > timeLastPacket + 50000UL) { |
|||
DEBUG_OUT.println (F("Reset RF24")); |
|||
resetRF24(); |
|||
timeLastPacket = millis(); |
|||
} |
|||
|
|||
while (!packetBuffer.empty()) { |
|||
timeLastPacket = millis(); |
|||
// One or more records present
|
|||
NRF24_packet_t *p = packetBuffer.getBack(); |
|||
|
|||
// Shift payload data due to 9-bit packet control field
|
|||
for (int16_t j = sizeof(p->packet) - 1; j >= 0; j--) { |
|||
if (j > 0) |
|||
p->packet[j] = (byte)(p->packet[j] >> 7) | (byte)(p->packet[j - 1] << 1); |
|||
else |
|||
p->packet[j] = (byte)(p->packet[j] >> 7); |
|||
} |
|||
|
|||
SerialHdr.timestamp = p->timestamp; |
|||
SerialHdr.packetsLost = p->packetsLost; |
|||
|
|||
uint8_t payloadLen = ((p->packet[0] & 0x01) << 5) | (p->packet[1] >> 3); |
|||
// Check CRC
|
|||
if (! doCheckCrc(p, payloadLen) ) |
|||
continue; |
|||
|
|||
#ifdef DEBUG |
|||
uint8_t cmd = p->packet[11]; |
|||
//if (cmd != 0x01 && cmd != 0x02 && cmd != 0x83 && cmd != 0x81)
|
|||
outputPacket (p, payloadLen); |
|||
#endif |
|||
|
|||
analyse (p); |
|||
|
|||
#ifndef ESP8266 |
|||
writeArduinoInterface(); |
|||
#endif |
|||
|
|||
// Remove record as we're done with it.
|
|||
packetBuffer.popBack(); |
|||
} |
|||
|
|||
if (istTag) |
|||
isTime2Send(); |
|||
|
|||
#ifdef ESP8266 |
|||
checkWifi(); |
|||
webserverHandle(); |
|||
checkUpdateByOTA(); |
|||
if (hour() == 0 && minute() == 0) { |
|||
calcSunUpDown(getNow()); |
|||
delay (60*1000); |
|||
} |
|||
|
|||
if (millis() > timeLastIstTagCheck + 15UL * 60UL * 1000UL) { // alle 15 Minuten neu berechnen ob noch hell
|
|||
istTag = isDayTime(); |
|||
DEBUG_OUT.print (F("Es ist ")); DEBUG_OUT.println (istTag?F("Tag"):F("Nacht")); |
|||
timeLastIstTagCheck = millis(); |
|||
} |
|||
#endif |
|||
/*
|
|||
if (millis() > timeLastPacket + 60UL*SECOND) { // 60 Sekunden
|
|||
channelIdx++; |
|||
if (channelIdx >= sizeof(channels)) channelIdx = 0; |
|||
DEFAULT_SEND_CHANNEL = channels[channelIdx]; |
|||
DEBUG_OUT.print (F("\nneuer DEFAULT_SEND_CHANNEL: ")); DEBUG_OUT.println(DEFAULT_SEND_CHANNEL); |
|||
timeLastPacket = millis(); |
|||
} |
|||
*/ |
|||
} |
|||
|
|||
|
|||
static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len) { |
|||
//--------------------------------------------------------------
|
|||
//DEBUG_OUT.print (F("Sende: ")); DEBUG_OUT.println (buf[9], HEX);
|
|||
//dumpData (buf, len); DEBUG_OUT.println();
|
|||
DISABLE_EINT; |
|||
Radio.stopListening(); |
|||
|
|||
#ifdef CHANNEL_HOP |
|||
static uint8_t hop = 0; |
|||
#if DEBUG_SEND |
|||
DEBUG_OUT.print(F("Send... CH")); |
|||
DEBUG_OUT.println(channels[hop]); |
|||
#endif |
|||
Radio.setChannel(channels[hop++]); |
|||
if (hop >= sizeof(channels) / sizeof(channels[0])) |
|||
hop = 0; |
|||
#else |
|||
Radio.setChannel(DEFAULT_SEND_CHANNEL); |
|||
#endif |
|||
|
|||
Radio.openWritingPipe(dest); |
|||
Radio.setCRCLength(RF24_CRC_16); |
|||
Radio.enableDynamicPayloads(); |
|||
Radio.setAutoAck(true); |
|||
Radio.setRetries(3, 15); |
|||
|
|||
bool res = Radio.write(buf, len); |
|||
// Try to avoid zero payload acks (has no effect)
|
|||
Radio.openWritingPipe(DUMMY_RADIO_ID); |
|||
|
|||
Radio.setAutoAck(false); |
|||
Radio.setRetries(0, 0); |
|||
Radio.disableDynamicPayloads(); |
|||
Radio.setCRCLength(RF24_CRC_DISABLED); |
|||
|
|||
Radio.setChannel(DEFAULT_RECV_CHANNEL); |
|||
Radio.startListening(); |
|||
ENABLE_EINT; |
|||
#if USE_POOR_MAN_CHANNEL_HOPPING_RCV |
|||
hophop = 5 * sizeof(rcvChannels); |
|||
#endif |
|||
} |
@ -0,0 +1,283 @@ |
|||
#ifndef __INVERTERS_H |
|||
#define __INVERTERS_H |
|||
|
|||
// Ausgabe von Debug Infos auf der seriellen Console
|
|||
|
|||
#include "Settings.h" |
|||
#include "Debug.h" |
|||
|
|||
|
|||
typedef struct _NRF24_packet_t { |
|||
uint32_t timestamp; |
|||
uint8_t packetsLost; |
|||
uint8_t rcvChannel; |
|||
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; |
|||
} NRF24_packet_t; |
|||
|
|||
|
|||
typedef struct _Serial_header_t { |
|||
unsigned long timestamp; |
|||
uint8_t packetsLost; |
|||
uint8_t address[RF_MAX_ADDR_WIDTH]; // MSB first, always RF_MAX_ADDR_WIDTH bytes.
|
|||
} Serial_header_t; |
|||
|
|||
|
|||
// structs für Inverter und Kanalwerte
|
|||
|
|||
// Liste der Einheiten
|
|||
enum UNITS {UNIT_V = 0, UNIT_HZ, UNIT_A, UNIT_W, UNIT_WH, UNIT_C, UNIT_KWH, UNIT_MA, UNIT_PCT}; |
|||
const char* const units[] = {"V", "Hz", "A", "W", "Wh", "°C", "KWh", "mA", "%"}; |
|||
|
|||
// CH0 is default channel (freq, ac, temp)
|
|||
enum CHANNELS {CH0 = 0, CH1, CH2, CH3, CH4}; |
|||
enum CMDS {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84}; |
|||
enum DIVS {DIV1 = 0, DIV10, DIV100, DIV1000}; |
|||
|
|||
#define BYTES2 2 |
|||
#define BYTES4 4 |
|||
|
|||
const char UDC[] PROGMEM = "Udc"; |
|||
const char IDC[] PROGMEM = "Idc"; |
|||
const char PDC[] PROGMEM = "Pdc"; |
|||
const char E_WOCHE[] PROGMEM = "E-Woche"; |
|||
const char E_TOTAL[] PROGMEM = "E-Total"; |
|||
const char E_TAG[] PROGMEM = "E-Tag"; |
|||
const char UAC[] PROGMEM = "Uac"; |
|||
const char FREQ[] PROGMEM = "Freq.ac"; |
|||
const char PAC[] PROGMEM = "Pac"; |
|||
const char E_HEUTE[] PROGMEM = "E-heute"; |
|||
const char IPV[] PROGMEM = "Ipv"; |
|||
const char WR_TEMP[] PROGMEM = "WR-Temp"; |
|||
const char PERCNT[] PROGMEM = "Pct"; |
|||
|
|||
#define IDX_UDC 0 |
|||
#define IDX_IDC 1 |
|||
#define IDX_PDC 2 |
|||
#define IDX_E_WOCHE 3 |
|||
#define IDX_E_TOTAL 4 |
|||
#define IDX_E_TAG 5 |
|||
#define IDX_UAC 6 |
|||
#define IDX_FREQ 7 |
|||
#define IDX_PAC 8 |
|||
#define IDX_E_HEUTE 9 |
|||
#define IDX_IPV 10 |
|||
#define IDX_WR_TEMP 11 |
|||
#define IDX_PERCNT 12 |
|||
|
|||
const char* const NAMES[] |
|||
= {UDC, IDC, PDC, E_WOCHE, E_TOTAL, E_TAG, UAC, FREQ, PAC, E_HEUTE, IPV, WR_TEMP, PERCNT}; |
|||
|
|||
typedef float (*calcValueFunc)(float *); |
|||
|
|||
struct measureDef_t { |
|||
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
|||
uint8_t channel; // 0..4,
|
|||
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
|||
uint8_t teleId; // Telegramm ID, das was hinter der 2. WR Nummer im Telegramm, 02, 03, 83
|
|||
uint8_t pos; // ab dieser POsition beginnt der Wert (Big Endian)
|
|||
uint8_t bytes; // Anzahl der Bytes
|
|||
uint8_t digits; |
|||
}; |
|||
|
|||
struct measureCalc_t { |
|||
uint8_t nameIdx; //const char* name; // Zeiger auf den Messwertnamen
|
|||
uint8_t unitIdx; // Index in die Liste der Einheiten 'units'
|
|||
uint8_t digits; |
|||
calcValueFunc f; // die Funktion zur Berechnung von Werten, zb Summe von Werten
|
|||
}; |
|||
|
|||
|
|||
struct inverter_t { |
|||
uint8_t ID; // Inverter-ID = Index
|
|||
char name[20]; // Name des Inverters zb HM-600.1
|
|||
uint64_t serialNo; // dier Seriennummer wie im Barcode auf dem WR, also 1141.....
|
|||
uint64_t RadioId; // die gespiegelte (letzte 4 "Bytes") der Seriennummer
|
|||
const measureDef_t *measureDef; // aus Include HMxxx.h : Liste mit Definitionen der Messwerte, wie Telgramm, offset, länge, ...
|
|||
uint8_t anzMeasures; // Länge der Liste
|
|||
measureCalc_t *measureCalculated; // Liste mit Defintion für berechnete Werte
|
|||
uint8_t anzMeasureCalculated; // Länge der Liste
|
|||
uint8_t anzTotalMeasures; // Gesamtanzahl Messwerte
|
|||
float values[MAX_MEASURE_PER_INV]; // DIE Messewerte
|
|||
}; |
|||
|
|||
|
|||
char _buffer[20]; |
|||
|
|||
uint8_t anzInv = 0; |
|||
inverter_t inverters[MAX_ANZ_INV]; |
|||
|
|||
union longlongasbytes { |
|||
uint64_t ull; |
|||
uint32_t ul[2]; |
|||
uint8_t bytes[8]; |
|||
}; |
|||
|
|||
char *uint64toa (uint64_t s) { |
|||
//--------------------------------
|
|||
//0x1141 72607952ULL
|
|||
sprintf(_buffer, "%lX%08lX", (unsigned long)(s>>32), (unsigned long)(s&0xFFFFFFFFULL)); |
|||
return _buffer; |
|||
} |
|||
|
|||
|
|||
uint64_t Serial2RadioID (uint64_t sn) { |
|||
//----------------------------------
|
|||
longlongasbytes llsn; |
|||
longlongasbytes res; |
|||
llsn.ull = sn; |
|||
res.ull = 0; |
|||
res.bytes[4] = llsn.bytes[0]; |
|||
res.bytes[3] = llsn.bytes[1]; |
|||
res.bytes[2] = llsn.bytes[2]; |
|||
res.bytes[1] = llsn.bytes[3]; |
|||
res.bytes[0] = 0x01; |
|||
return res.ull; |
|||
} |
|||
|
|||
|
|||
void addInverter (uint8_t _ID, const char * _name, uint64_t _serial, |
|||
const measureDef_t * liste, int anzMeasure, |
|||
measureCalc_t * calcs, int anzMeasureCalculated) { |
|||
//-------------------------------------------------------------------------------------
|
|||
if (anzInv >= MAX_ANZ_INV) { |
|||
DEBUG_OUT.println(F("ANZ_INV zu klein!")); |
|||
return; |
|||
} |
|||
inverter_t *p = &(inverters[anzInv]); |
|||
p->ID = _ID; |
|||
strcpy (p->name, _name); |
|||
p->serialNo = _serial; |
|||
p->RadioId = Serial2RadioID(_serial); |
|||
p->measureDef = liste; |
|||
p->anzMeasures = anzMeasure; |
|||
p->anzMeasureCalculated = anzMeasureCalculated; |
|||
p->measureCalculated = calcs; |
|||
p->anzTotalMeasures = anzMeasure + anzMeasureCalculated; |
|||
memset (p->values, 0, sizeof(p->values)); |
|||
|
|||
DEBUG_OUT.print (F("WR : ")); DEBUG_OUT.println(anzInv); |
|||
DEBUG_OUT.print (F("Type : ")); DEBUG_OUT.println(_name); |
|||
DEBUG_OUT.print (F("Serial : ")); DEBUG_OUT.println(uint64toa(_serial)); |
|||
DEBUG_OUT.print (F("Radio-ID : ")); DEBUG_OUT.println(uint64toa(p->RadioId)); |
|||
|
|||
anzInv++; |
|||
} |
|||
|
|||
|
|||
static uint8_t toggle = 0; // nur für Test, ob's auch für mehere WR funzt
|
|||
uint8_t findInverter (uint8_t *fourbytes) { |
|||
//---------------------------------------
|
|||
for (uint8_t i = 0; i < anzInv; i++) { |
|||
longlongasbytes llb; |
|||
llb.ull = inverters[i].serialNo; |
|||
if (llb.bytes[3] == fourbytes[0] && |
|||
llb.bytes[2] == fourbytes[1] && |
|||
llb.bytes[1] == fourbytes[2] && |
|||
llb.bytes[0] == fourbytes[3] ) |
|||
{ |
|||
return i; |
|||
//if (toggle) toggle = 0; else toggle = 1; return toggle; // Test ob mehr WR auch geht
|
|||
} |
|||
} |
|||
return 0xFF; // nicht gefunden
|
|||
} |
|||
|
|||
|
|||
char * error = {"error"}; |
|||
|
|||
char *getMeasureName (uint8_t wr, uint8_t i){ |
|||
//------------------------------------------
|
|||
inverter_t *p = &(inverters[wr]); |
|||
if (i >= p->anzTotalMeasures) return error; |
|||
uint8_t idx, channel = 0; |
|||
if (i < p->anzMeasures) { |
|||
idx = p->measureDef[i].nameIdx; |
|||
channel = p->measureDef[i].channel; |
|||
} |
|||
else { |
|||
idx = p->measureCalculated[i - p->anzMeasures].nameIdx; |
|||
} |
|||
char tmp[20]; |
|||
strcpy_P (_buffer, NAMES[idx]); |
|||
if (channel) { |
|||
sprintf_P (tmp, PSTR(".CH%d"), channel); |
|||
strcat(_buffer,tmp); |
|||
} |
|||
return _buffer; |
|||
} |
|||
|
|||
const char *getUnit (uint8_t wr, uint8_t i) { |
|||
//------------------------------------------
|
|||
inverter_t *p = &(inverters[wr]); |
|||
if (i >= p->anzTotalMeasures) return error; |
|||
uint8_t idx; |
|||
if (i < p->anzMeasures) |
|||
idx = p->measureDef[i].unitIdx; |
|||
else |
|||
idx = p->measureCalculated[i-p->anzMeasures].unitIdx; |
|||
|
|||
//strcpy (_buffer, units[i]);
|
|||
//return _buffer;
|
|||
return units[idx]; |
|||
} |
|||
|
|||
|
|||
float getMeasureValue (uint8_t wr, uint8_t i) { |
|||
//------------------------------------------
|
|||
if (i >= inverters[wr].anzTotalMeasures) return 0.0; |
|||
return inverters[wr].values[i]; |
|||
} |
|||
|
|||
|
|||
int getDivisor (uint8_t wr, uint8_t i) { |
|||
//------------------------------------
|
|||
inverter_t *p = &(inverters[wr]); |
|||
if (i >= p->anzTotalMeasures) return 1; |
|||
if (i < p->anzMeasures) { |
|||
uint8_t digits = p->measureDef[i].digits; |
|||
if (digits == DIV1) return 1; |
|||
if (digits == DIV10) return 10; |
|||
if (digits == DIV100) return 100; |
|||
if (digits == DIV1000) return 1000; |
|||
return 1; |
|||
} |
|||
else |
|||
return p->measureCalculated[i].digits; |
|||
} |
|||
|
|||
|
|||
uint8_t getDigits (uint8_t wr, uint8_t i) { |
|||
//---------------------------------------
|
|||
inverter_t *p = &(inverters[wr]); |
|||
if (i >= p->anzTotalMeasures) return 0; |
|||
if (i < p->anzMeasures) |
|||
return p->measureDef[i].digits; |
|||
else |
|||
return p->measureCalculated[i-p->anzMeasures].digits; |
|||
} |
|||
|
|||
// +++++++++++++++++++++++++++++++++++ Inverter ++++++++++++++++++++++++++++++++++++++++++++++
|
|||
|
|||
#include "HM600.h" // für HM-600 und HM-700 |
|||
|
|||
#include "HM1200.h" |
|||
|
|||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|||
|
|||
|
|||
void setupInverts() { |
|||
//-----------------
|
|||
|
|||
addInverter (0,"HM-600", 0x114172607952ULL, |
|||
hm600_measureDef, HM600_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
|||
hm600_measureCalc, HM600_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
|||
|
|||
/*
|
|||
addInverter (1,"HM-1200", 0x114172607952ULL, |
|||
hm1200_measureDef, HM1200_MEASURE_LIST_LEN, // Tabelle der Messwerte
|
|||
hm1200_measureCalc, HM1200_CALCED_LIST_LEN); // Tabelle berechnete Werte
|
|||
*/ |
|||
} |
|||
|
|||
|
|||
#endif |
@ -0,0 +1,151 @@ |
|||
// ################# WebServer #################
|
|||
|
|||
#ifndef __MODWEBSERVER_H |
|||
#define __MODWEBSERVER_H |
|||
#define MODWEBSERVER |
|||
|
|||
#include <ESP8266WebServer.h> |
|||
#include "Debug.h" |
|||
#include "Settings.h" |
|||
|
|||
ESP8266WebServer server (WEBSERVER_PORT); |
|||
|
|||
|
|||
void returnOK () { |
|||
//--------------
|
|||
server.send(200, F("text/plain"), ""); |
|||
} |
|||
|
|||
|
|||
void returnFail(String msg) { |
|||
//-------------------------
|
|||
server.send(500, F("text/plain"), msg + "\r\n"); |
|||
} |
|||
|
|||
void handleHelp () { |
|||
//-----------------
|
|||
String out = "<html>"; |
|||
out += "<body><h2>Hilfe</h2>"; |
|||
out += "<br><br><table>"; |
|||
out += "<tr><td>/</td><td>zeigt alle Messwerte in einer Tabelle; refresh alle 10 Sekunden</td></tr>"; |
|||
out += "<tr><td>/data</td><td>zum Abruf der Messwerte in der Form Name=wert</td></tr>"; |
|||
out += "<tr><td>:{port+1}/update</td><td>OTA</td></tr>"; |
|||
out += "<tr><td>/reboot</td><td>startet neu</td></tr>"; |
|||
out += "</table></body></html>"; |
|||
server.send (200, "text/html", out); |
|||
} |
|||
|
|||
|
|||
void handleReboot () { |
|||
//-------------------
|
|||
returnOK (); |
|||
ESP.reset(); |
|||
} |
|||
|
|||
|
|||
void handleRoot() { |
|||
//----------------
|
|||
String out = "<html><head><meta http-equiv=\"refresh\" content=\"10\":URL=\"" + server.uri() + "\"></head>"; |
|||
out += "<body>"; |
|||
out += "<h2>Hoymiles Micro-Inverters</h2>"; |
|||
char floatString[20]; |
|||
char line[100]; |
|||
for (uint8_t wr = 0; wr < anzInv; wr++) { |
|||
out += "<h3>" + String(inverters[wr].name) + "</h3>"; |
|||
out += "<h3>S/N " + String (getSerialNoTxt(wr)) + "</h3>"; |
|||
out += "<br><br><table border='1'>"; |
|||
out += "<tr><th>Kanal</th><th>Wert</th><th>Einheit</th></tr>"; |
|||
for (uint8_t i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
|||
dtostrf (getMeasureValue(wr, i),1, getDigits(wr,i), floatString); |
|||
sprintf(line, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>", getMeasureName(wr, i), floatString, getUnit(wr, i)); |
|||
//DEBUG_OUT.println(line);
|
|||
out += String(line); |
|||
/* out += "<tr><td>" + getMeasureName(i) + "</td>";
|
|||
out += "<td>" + String(getMeasureValue(i)) + "</td></tr>"; |
|||
out += "<td>" + String(getUnit(i)) + "</td></tr>"; */ |
|||
} |
|||
out += "</table>"; |
|||
} |
|||
int pos = out.indexOf("°"); |
|||
do { |
|||
if (pos>1) { |
|||
out = out.substring (0, pos) + "°" + out.substring(pos+2); |
|||
} |
|||
pos = out.indexOf("°"); |
|||
} while (pos>1); |
|||
|
|||
out += "</body></html>"; |
|||
server.send (200, "text/html", out); |
|||
//DEBUG_OUT.println (out);
|
|||
} |
|||
|
|||
|
|||
void handleData () { |
|||
//-----------------
|
|||
String out = ""; |
|||
for (uint8_t wr = 0; wr < anzInv; wr++) { |
|||
for (int i = 0; i < inverters[wr].anzTotalMeasures; i++) { |
|||
out += (anzInv <= 1 ? "" : String (wr) + ".") + String(getMeasureName(wr,i)) + '=' |
|||
+ String (getMeasureValue(wr,i)) /*+ ' ' + String(getUnit(wr,i))*/ + '\n'; |
|||
} |
|||
} |
|||
server.send(200, "text/plain", out); |
|||
} |
|||
|
|||
|
|||
void handleNotFound() { |
|||
//--------------------
|
|||
String message = "URI: "; |
|||
message += server.uri(); |
|||
message += "\nMethod: "; |
|||
message += (server.method() == HTTP_GET) ? "GET" : "POST"; |
|||
message += "\nArguments: "; |
|||
message += server.args(); |
|||
message += "\n"; |
|||
for (uint8_t i = 0; i < server.args(); i++) { |
|||
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; |
|||
} |
|||
server.send(404, "text/plain", message); |
|||
} |
|||
|
|||
|
|||
void setupWebServer (void) { |
|||
//-------------------------
|
|||
server.begin(); |
|||
server.on("/", handleRoot); |
|||
server.on("/reboot", handleReboot); |
|||
server.on("/data", handleData); |
|||
server.on("/help", handleHelp); |
|||
//server.onNotFound(handleNotFound); wegen Spiffs-Dateimanager
|
|||
|
|||
DEBUG_OUT.println ("[HTTP] installed"); |
|||
} |
|||
|
|||
void webserverHandle() { |
|||
//====================
|
|||
server.handleClient(); |
|||
} |
|||
|
|||
|
|||
// ################# OTA #################
|
|||
|
|||
#ifdef WITH_OTA |
|||
#include <ESP8266HTTPUpdateServer.h> |
|||
|
|||
ESP8266WebServer httpUpdateServer (UPDATESERVER_PORT); |
|||
ESP8266HTTPUpdateServer httpUpdater; |
|||
|
|||
void setupUpdateByOTA () { |
|||
//------------------------
|
|||
httpUpdater.setup (&httpUpdateServer, UPDATESERVER_DIR, UPDATESERVER_USER, UPDATESERVER_PW); |
|||
httpUpdateServer.begin(); |
|||
DEBUG_OUT.println ("[OTA] installed"); |
|||
} |
|||
|
|||
void checkUpdateByOTA() { |
|||
//---------------------
|
|||
httpUpdateServer.handleClient(); |
|||
} |
|||
#endif |
|||
|
|||
#endif |
@ -0,0 +1,69 @@ |
|||
#ifndef __SETTINGS_H |
|||
#define __SETTINGS_H |
|||
|
|||
// Ausgabe von Debug Infos auf der seriellen Console
|
|||
#define DEBUG |
|||
#define SER_BAUDRATE (115200) |
|||
|
|||
#include "Debug.h" |
|||
|
|||
// Ausgabe was gesendet wird; 0 oder 1
|
|||
#define DEBUG_SEND 0 |
|||
|
|||
// soll zwichen den Sendekanälen 23, 40, 61, 75 ständig gewechselt werden
|
|||
#define CHANNEL_HOP |
|||
|
|||
// mit OTA Support, also update der Firmware über WLan mittels IP/update
|
|||
#define WITH_OTA |
|||
|
|||
// Hardware configuration
|
|||
#ifdef ESP8266 |
|||
#define RF1_CE_PIN (D4) |
|||
#define RF1_CS_PIN (D8) |
|||
#define RF1_IRQ_PIN (D3) |
|||
#else |
|||
#define RF1_CE_PIN (9) |
|||
#define RF1_CS_PIN (10) |
|||
#define RF1_IRQ_PIN (2) |
|||
#endif |
|||
|
|||
// WR und DTU
|
|||
#define RF_MAX_ADDR_WIDTH (5) |
|||
#define MAX_RF_PAYLOAD_SIZE (32) |
|||
#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
|
|||
|
|||
#define USE_POOR_MAN_CHANNEL_HOPPING_RCV 1 // 0 = not use
|
|||
|
|||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
|||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
|||
#define MAX_ANZ_INV 2 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#define MAX_MEASURE_PER_INV 25 // hier statisch, könnte auch dynamisch erzeugt werden, aber Overhead für dyn. Speicher?
|
|||
|
|||
// Webserver
|
|||
#define WEBSERVER_PORT 80 |
|||
|
|||
// Time Server
|
|||
//#define TIMESERVER_NAME "pool.ntp.org"
|
|||
#define TIMESERVER_NAME "fritz.box" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
|
|||
#ifdef WITH_OTA |
|||
// OTA Einstellungen
|
|||
#define UPDATESERVER_PORT WEBSERVER_PORT+1 |
|||
#define UPDATESERVER_DIR "/update" |
|||
#define UPDATESERVER_USER "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#define UPDATESERVER_PW "?????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#endif |
|||
|
|||
// internes WLan
|
|||
// PREFIXE dienen dazu, die eigenen WLans (wenn mehrere) von fremden zu unterscheiden
|
|||
// gehe hier davon aus, dass alle WLans das gleiche Passwort haben. Wenn nicht, dann mehre Passwörter hinterlegen
|
|||
#define SSID_PREFIX1 "pre1" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#define SSID_PREFIX2 "pre2" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#define SSID_PASSWORD "?????????????????" // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
|
|||
// zur Berechnung von Sonnenauf- und -untergang
|
|||
#define geoBreite 49.2866 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
#define geoLaenge 7.3416 // <<<<<<<<<<<<<<<<<<<<<<<< anpassen
|
|||
|
|||
|
|||
#endif |
@ -0,0 +1,55 @@ |
|||
#ifndef __SONNE_H |
|||
#define __SONNE_H |
|||
|
|||
#include "Settings.h" |
|||
#include "Debug.h" |
|||
|
|||
|
|||
long SunDown, SunUp; |
|||
|
|||
void calcSunUpDown (time_t date) { |
|||
//SunUpDown res = new SunUpDown();
|
|||
boolean isSummerTime = false; // TODO TimeZone.getDefault().inDaylightTime(new Date(date));
|
|||
|
|||
//- Bogenmass
|
|||
double brad = geoBreite / 180.0 * PI; |
|||
// - Höhe Sonne -50 Bogenmin.
|
|||
double h0 = -50.0 / 60.0 / 180.0 * PI; |
|||
//- Deklination dek, Tag des Jahres d0
|
|||
int tage = 30 * month(date) - 30 + day(date); |
|||
double dek = 0.40954 * sin (0.0172 * (tage - 79.35)); |
|||
double zh1 = sin (h0) - sin (brad) * sin(dek); |
|||
double zh2 = cos(brad) * cos(dek); |
|||
double zd = 12*acos (zh1/zh2) / PI; |
|||
double zgl = -0.1752 * sin (0.03343 * tage + 0.5474) - 0.134 * sin (0.018234 * tage - 0.1939); |
|||
//-Sonnenuntergang
|
|||
double tsu = 12 + zd - zgl; |
|||
double su = (tsu + (15.0 - geoLaenge) / 15.0); |
|||
int std = (int)su; |
|||
int minute = (int) ((su - std)*60); |
|||
if (isSummerTime) std++; |
|||
SunDown = (100*std + minute) * 100; |
|||
|
|||
//- Sonnenaufgang
|
|||
double tsa = 12 - zd - zgl; |
|||
double sa = (tsa + (15.0 - geoLaenge) /15.0); |
|||
std = (int) sa; |
|||
minute = (int) ((sa - std)*60); |
|||
if (isSummerTime) std++; |
|||
SunUp = (100*std + minute) * 100; |
|||
DEBUG_OUT.print(F("Sonnenaufgang :")); DEBUG_OUT.println(SunUp); |
|||
DEBUG_OUT.print(F("Sonnenuntergang:")); DEBUG_OUT.println(SunDown); |
|||
} |
|||
|
|||
boolean isDayTime() { |
|||
//-----------------
|
|||
// 900 = 15 Minuten, vor Sonnenaufgang und nach -untergang
|
|||
const int offset=60*15; |
|||
time_t no = getNow(); |
|||
long jetztMinuteU = (100 * hour(no+offset) + minute(no+offset)) * 100; |
|||
long jetztMinuteO = (100 * hour(no-offset) + minute(no-offset)) * 100; |
|||
|
|||
return ((jetztMinuteU >= SunUp) &&(jetztMinuteO <= SunDown)); |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,102 @@ |
|||
#ifndef __HM_CRC_H |
|||
#define __HM_CRC_H |
|||
|
|||
#define BITS_TO_BYTES(x) (((x)+7)>>3) |
|||
#define BYTES_TO_BITS(x) ((x)<<3) |
|||
|
|||
extern uint16_t crc16_modbus(uint8_t *puchMsg, uint16_t usDataLen); |
|||
extern uint8_t crc8(uint8_t *buf, const uint16_t bufLen); |
|||
extern uint16_t crc16(uint8_t* buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits); |
|||
|
|||
//#define OUTPUT_DEBUG_INFO
|
|||
|
|||
#define CRC8_INIT 0x00 |
|||
#define CRC8_POLY 0x01 |
|||
|
|||
#define CRC16_MODBUS_POLYNOM 0xA001 |
|||
|
|||
uint8_t crc8(uint8_t buf[], uint16_t len) { |
|||
uint8_t crc = CRC8_INIT; |
|||
for(uint8_t i = 0; i < len; i++) { |
|||
crc ^= buf[i]; |
|||
for(uint8_t b = 0; b < 8; b ++) { |
|||
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); |
|||
} |
|||
} |
|||
return crc; |
|||
} |
|||
|
|||
uint16_t crc16_modbus(uint8_t buf[], uint16_t len) { |
|||
uint16_t crc = 0xffff; |
|||
uint8_t lsb; |
|||
|
|||
for(uint8_t i = 0; i < len; i++) { |
|||
crc = crc ^ buf[i]; |
|||
for(int8_t b = 7; b >= 0; b--) { |
|||
lsb = (crc & 0x0001); |
|||
if(lsb == 0x01) |
|||
crc--; |
|||
crc = crc >> 1; |
|||
if(lsb == 0x01) |
|||
crc = crc ^ CRC16_MODBUS_POLYNOM; |
|||
} |
|||
} |
|||
|
|||
return crc; |
|||
} |
|||
|
|||
// NRF24 CRC16 calculation with poly 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
|||
uint16_t crc16(uint8_t *buf, const uint16_t bufLen, const uint16_t startCRC, const uint16_t startBit, const uint16_t len_bits) |
|||
{ |
|||
uint16_t crc = startCRC; |
|||
if ((len_bits > 0) && (len_bits <= BYTES_TO_BITS(bufLen))) |
|||
{ |
|||
// The length of the data might not be a multiple of full bytes.
|
|||
// Therefore we proceed over the data bit-by-bit (like the NRF24 does) to
|
|||
// calculate the CRC.
|
|||
uint16_t data; |
|||
uint8_t byte, shift; |
|||
uint16_t bitoffs = startBit; |
|||
|
|||
// Get a new byte for the next 8 bits.
|
|||
byte = buf[bitoffs >> 3]; |
|||
#ifdef OUTPUT_DEBUG_INFO |
|||
printf_P(PSTR("\nStart CRC %04X, %u bits:"), startCRC, len_bits); |
|||
printf_P(PSTR("\nbyte %02X:"), byte); |
|||
#endif |
|||
while (bitoffs < len_bits + startBit) |
|||
{ |
|||
shift = bitoffs & 7; |
|||
// Shift the active bit to the position of bit 15
|
|||
data = ((uint16_t)byte) << (8 + shift); |
|||
#ifdef OUTPUT_DEBUG_INFO |
|||
printf_P(PSTR(" bit %u %u,"), shift, data & 0x8000 ? 1 : 0); |
|||
#endif |
|||
// Assure all other bits are 0
|
|||
data &= 0x8000; |
|||
crc ^= data; |
|||
if (crc & 0x8000) |
|||
{ |
|||
crc = (crc << 1) ^ 0x1021; // 0x1021 = (1) 0001 0000 0010 0001 = x^16+x^12+x^5+1
|
|||
} |
|||
else |
|||
{ |
|||
crc = (crc << 1); |
|||
} |
|||
++bitoffs; |
|||
if (0 == (bitoffs & 7)) |
|||
{ |
|||
// Get a new byte for the next 8 bits.
|
|||
byte = buf[bitoffs >> 3]; |
|||
#ifdef OUTPUT_DEBUG_INFO |
|||
printf_P(PSTR("crc %04X:"), crc); |
|||
if (bitoffs < len_bits + startBit) |
|||
printf_P(PSTR("\nbyte %02X:"), byte); |
|||
#endif |
|||
} |
|||
} |
|||
} |
|||
return crc; |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,93 @@ |
|||
#ifndef __HM_PACKETS_H |
|||
#define __HM_PACKETS_H |
|||
|
|||
#include "hm_crc.h" |
|||
|
|||
class HM_Packets |
|||
{ |
|||
private: |
|||
uint32_t unixTimeStamp; |
|||
|
|||
void prepareBuffer(uint8_t *buf); |
|||
void copyToBuffer(uint8_t *buf, uint32_t val); |
|||
void copyToBufferBE(uint8_t *buf, uint32_t val); |
|||
|
|||
public: |
|||
void SetUnixTimeStamp(uint32_t ts); |
|||
void UnixTimeStampTick(); |
|||
|
|||
int32_t GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr); |
|||
int32_t GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd); |
|||
}; |
|||
|
|||
void HM_Packets::SetUnixTimeStamp(uint32_t ts) |
|||
{ |
|||
unixTimeStamp = ts; |
|||
} |
|||
|
|||
void HM_Packets::UnixTimeStampTick() |
|||
{ |
|||
unixTimeStamp++; |
|||
} |
|||
|
|||
void HM_Packets::prepareBuffer(uint8_t *buf) |
|||
{ |
|||
// minimal buffer size of 32 bytes is assumed
|
|||
memset(buf, 0x00, 32); |
|||
} |
|||
|
|||
void HM_Packets::copyToBuffer(uint8_t *buf, uint32_t val) |
|||
{ |
|||
buf[0]= (uint8_t)(val >> 24); |
|||
buf[1]= (uint8_t)(val >> 16); |
|||
buf[2]= (uint8_t)(val >> 8); |
|||
buf[3]= (uint8_t)(val & 0xFF); |
|||
} |
|||
|
|||
void HM_Packets::copyToBufferBE(uint8_t *buf, uint32_t val) |
|||
{ |
|||
memcpy(buf, &val, sizeof(uint32_t)); |
|||
} |
|||
|
|||
static uint8_t cid = 0; |
|||
|
|||
int32_t HM_Packets::GetTimePacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr) |
|||
{ |
|||
prepareBuffer(buf); |
|||
|
|||
buf[0] = 0x15; |
|||
copyToBufferBE(&buf[1], wrAdr); |
|||
copyToBufferBE(&buf[5], dtuAdr); |
|||
buf[9] = 0x80; |
|||
buf[10] = 0x0B; //0x0B; 0x03 0x11
|
|||
buf[11] = 0x00; |
|||
|
|||
copyToBuffer(&buf[12], unixTimeStamp); |
|||
|
|||
buf[19] = 0x05; |
|||
|
|||
// CRC16
|
|||
uint16_t crc16 = crc16_modbus(&buf[10], 14); |
|||
buf[24] = crc16 >> 8; |
|||
buf[25] = crc16 & 0xFF; |
|||
|
|||
// crc8
|
|||
buf[26] = crc8(&buf[0], 26); |
|||
|
|||
return 27; |
|||
} |
|||
|
|||
int32_t HM_Packets::GetCmdPacket(uint8_t *buf, uint32_t wrAdr, uint32_t dtuAdr, uint8_t mid, uint8_t cmd) |
|||
{ |
|||
buf[0] = mid; |
|||
copyToBufferBE(&buf[1], wrAdr); |
|||
copyToBufferBE(&buf[5], dtuAdr); |
|||
buf[9] = cmd; |
|||
|
|||
// crc8
|
|||
buf[10] = crc8(&buf[0], 10); |
|||
|
|||
return 11; |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,345 @@ |
|||
#ifndef __WIFI_H |
|||
#define __WIFI_H |
|||
|
|||
#include "Settings.h" |
|||
#include "Debug.h" |
|||
#include <ESP8266WiFi.h> |
|||
#include <Pinger.h> // von url=https://www.technologytourist.com |
|||
|
|||
String SSID = ""; // bestes WLan
|
|||
|
|||
// Prototypes
|
|||
time_t getNow (); |
|||
boolean setupWifi (); |
|||
boolean checkWifi(); |
|||
|
|||
|
|||
String findWifi () { |
|||
//----------------
|
|||
String ssid; |
|||
int32_t rssi; |
|||
uint8_t encryptionType; |
|||
uint8_t* bssid; |
|||
int32_t channel; |
|||
bool hidden; |
|||
int scanResult; |
|||
|
|||
String best_ssid = ""; |
|||
int32_t best_rssi = -100; |
|||
|
|||
DEBUG_OUT.println(F("Starting WiFi scan...")); |
|||
|
|||
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true); |
|||
|
|||
if (scanResult == 0) { |
|||
DEBUG_OUT.println(F("keine WLans")); |
|||
} else if (scanResult > 0) { |
|||
DEBUG_OUT.printf(PSTR("%d WLans gefunden:\n"), scanResult); |
|||
|
|||
// Print unsorted scan results
|
|||
for (int8_t i = 0; i < scanResult; i++) { |
|||
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden); |
|||
|
|||
DEBUG_OUT.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"), |
|||
i, |
|||
channel, |
|||
bssid[0], bssid[1], bssid[2], |
|||
bssid[3], bssid[4], bssid[5], |
|||
rssi, |
|||
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*', |
|||
hidden ? 'H' : 'V', |
|||
ssid.c_str()); |
|||
delay(1); |
|||
boolean check; |
|||
#ifdef SSID_PREFIX1 |
|||
check = ssid.substring(0,strlen(SSID_PREFIX1)).equals(SSID_PREFIX1); |
|||
#else |
|||
check = true; |
|||
#endif |
|||
#ifdef SSID_PREFIX2 |
|||
check = check || ssid.substring(0,strlen(SSID_PREFIX2)).equals(SSID_PREFIX2); |
|||
#endif |
|||
if (check) { |
|||
if (rssi > best_rssi) { |
|||
best_rssi = rssi; |
|||
best_ssid = ssid; |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
DEBUG_OUT.printf(PSTR("WiFi scan error %d"), scanResult); |
|||
} |
|||
|
|||
if (! best_ssid.equals("")) { |
|||
SSID = best_ssid; |
|||
DEBUG_OUT.printf ("Bestes Wifi unter: %s\n", SSID.c_str()); |
|||
return SSID; |
|||
} |
|||
else |
|||
return ""; |
|||
} |
|||
|
|||
void IP2string (IPAddress IP, char * buf) { |
|||
sprintf (buf, "%d.%d.%d.%d", IP[0], IP[1], IP[2], IP[3]); |
|||
} |
|||
|
|||
void connectWifi() { |
|||
//------------------
|
|||
// if (SSID.equals(""))
|
|||
String s = findWifi(); |
|||
|
|||
if (!SSID.equals("")) { |
|||
DEBUG_OUT.print("versuche zu verbinden mit "); DEBUG_OUT.println(SSID); |
|||
//while (WiFi.status() != WL_CONNECTED) {
|
|||
WiFi.begin (SSID, SSID_PASSWORD); |
|||
int versuche = 20; |
|||
while (WiFi.status() != WL_CONNECTED && versuche > 0) { |
|||
delay(1000); |
|||
versuche--; |
|||
DEBUG_OUT.print(versuche); DEBUG_OUT.print(' '); |
|||
} |
|||
//}
|
|||
if (WiFi.status() == WL_CONNECTED) { |
|||
char buffer[30]; |
|||
IP2string (WiFi.localIP(), buffer); |
|||
String out = "\n[WiFi]Verbunden; meine IP:" + String (buffer); |
|||
DEBUG_OUT.println (out); |
|||
} |
|||
else |
|||
DEBUG_OUT.print("\nkeine Verbindung mit SSID "); DEBUG_OUT.println(SSID); |
|||
} |
|||
} |
|||
|
|||
|
|||
boolean setupWifi () { |
|||
//------------------
|
|||
int count=5; |
|||
while (count-- && WiFi.status() != WL_CONNECTED) |
|||
connectWifi(); |
|||
return (WiFi.status() == WL_CONNECTED); |
|||
} |
|||
|
|||
|
|||
Pinger pinger; |
|||
IPAddress ROUTER = IPAddress(192,168,1,1); |
|||
|
|||
boolean checkWifi() { |
|||
//---------------
|
|||
boolean NotConnected = (WiFi.status() != WL_CONNECTED) || !pinger.Ping(ROUTER); |
|||
if (NotConnected) { |
|||
setupWifi(); |
|||
if (WiFi.status() == WL_CONNECTED) |
|||
getNow(); |
|||
} |
|||
return (WiFi.status() == WL_CONNECTED); |
|||
} |
|||
|
|||
|
|||
|
|||
// ################ Clock #################
|
|||
|
|||
#include <WiFiUdp.h> |
|||
#include <TimeLib.h> |
|||
|
|||
IPAddress timeServer; |
|||
unsigned int localPort = 8888; |
|||
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
|
|||
byte packetBuf[NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets
|
|||
const int timeZone = 1; // Central European Time = +1
|
|||
long SYNCINTERVALL = 0; |
|||
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
|
|||
|
|||
// prototypes
|
|||
time_t getNtpTime (); |
|||
void sendNTPpacket (IPAddress &address); |
|||
time_t getNow (); |
|||
char* getDateTimeStr (time_t no = getNow()); |
|||
time_t offsetDayLightSaving (uint32_t local_t); |
|||
bool isDayofDaylightChange (time_t local_t); |
|||
|
|||
|
|||
void _setSyncInterval (long intervall) { |
|||
//----------------------------------------
|
|||
SYNCINTERVALL = intervall; |
|||
setSyncInterval (intervall); |
|||
} |
|||
|
|||
void setupClock() { |
|||
//-----------------
|
|||
WiFi.hostByName (TIMESERVER_NAME,timeServer); // at this point the function works
|
|||
|
|||
Udp.begin(localPort); |
|||
|
|||
getNtpTime(); |
|||
|
|||
setSyncProvider (getNtpTime); |
|||
while(timeStatus()== timeNotSet) |
|||
delay(1); //
|
|||
|
|||
_setSyncInterval (SECS_PER_DAY / 2); // Set seconds between re-sync
|
|||
|
|||
//lastClock = now();
|
|||
//Serial.print("[NTP] get time from NTP server ");
|
|||
getNow(); |
|||
//char buf[20];
|
|||
DEBUG_OUT.print ("[NTP] get time from NTP server "); |
|||
DEBUG_OUT.print (timeServer); |
|||
//sprintf (buf, ": %02d:%02d:%02d", hour(no), minute(no), second(no));
|
|||
DEBUG_OUT.print (": got "); |
|||
DEBUG_OUT.println (getDateTimeStr()); |
|||
} |
|||
|
|||
//*-------- NTP code ----------*/
|
|||
|
|||
|
|||
time_t getNtpTime() { |
|||
//-------------------
|
|||
sendNTPpacket(timeServer); // send an NTP packet to a time server
|
|||
//uint32_t beginWait = millis();
|
|||
//while (millis() - beginWait < 1500) {
|
|||
int versuch = 0; |
|||
while (versuch < 5) { |
|||
int wait = 150; // results in max 1500 ms waitTime
|
|||
while (wait--) { |
|||
int size = Udp.parsePacket(); |
|||
if (size >= NTP_PACKET_SIZE) { |
|||
//Serial.println("Receive NTP Response");
|
|||
Udp.read(packetBuf, NTP_PACKET_SIZE); // read packet into the buffer
|
|||
unsigned long secsSince1900; |
|||
// convert four bytes starting at location 40 to a long integer
|
|||
secsSince1900 = (unsigned long)packetBuf[40] << 24; |
|||
secsSince1900 |= (unsigned long)packetBuf[41] << 16; |
|||
secsSince1900 |= (unsigned long)packetBuf[42] << 8; |
|||
secsSince1900 |= (unsigned long)packetBuf[43]; |
|||
// time_t now = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
|
|||
|
|||
time_t utc = secsSince1900 - 2208988800UL; |
|||
time_t now = utc + (timeZone +offsetDayLightSaving(utc)) * SECS_PER_HOUR; |
|||
|
|||
if (isDayofDaylightChange (utc) && hour(utc) <= 4) |
|||
_setSyncInterval (SECS_PER_HOUR); |
|||
else |
|||
_setSyncInterval (SECS_PER_DAY / 2); |
|||
|
|||
return now; |
|||
} |
|||
else |
|||
delay(10); |
|||
} |
|||
versuch++; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
// send an NTP request to the time server at the given address
|
|||
void sendNTPpacket(IPAddress& address) { |
|||
//------------------------------------
|
|||
memset(packetBuf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
|
|||
// Initialize values needed to form NTP request
|
|||
packetBuf[0] = B11100011; // LI, Version, Mode
|
|||
packetBuf[1] = 0; // Stratum
|
|||
packetBuf[2] = 6; // Max Interval between messages in seconds
|
|||
packetBuf[3] = 0xEC; // Clock Precision
|
|||
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
|||
packetBuf[12] = 49; // four-byte reference ID identifying
|
|||
packetBuf[13] = 0x4E; |
|||
packetBuf[14] = 49; |
|||
packetBuf[15] = 52; |
|||
// send the packet requesting a timestamp:
|
|||
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
|||
Udp.write(packetBuf,NTP_PACKET_SIZE); |
|||
Udp.endPacket(); |
|||
|
|||
} |
|||
|
|||
int getTimeTrials = 0; |
|||
|
|||
bool isValidDateTime (time_t no) { |
|||
return (year(no) > 2020 && year(no) < 2038); |
|||
} |
|||
|
|||
bool isDayofDaylightChange (time_t local_t) { |
|||
//-----------------------------------------
|
|||
int jahr = year (local_t); |
|||
int monat = month (local_t); |
|||
int tag = day (local_t); |
|||
bool ret = ( (monat ==3 && tag == (31 - (5 * jahr /4 + 4) % 7)) || |
|||
(monat==10 && tag == (31 - (5 * jahr /4 + 1) % 7))); |
|||
DEBUG_OUT.print ("isDayofDaylightChange="); DEBUG_OUT.println (ret); |
|||
return ret; |
|||
} |
|||
|
|||
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC (!)
|
|||
// übernommen von Jurs, see : https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
|||
time_t offsetDayLightSaving (uint32_t local_t) { |
|||
//--------------------------------------------
|
|||
int monat = month (local_t); |
|||
if (monat < 3 || monat > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
|||
if (monat > 3 && monat < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
|||
int jahr = year (local_t); |
|||
int std = hour (local_t); |
|||
//int tag = day (local_t);
|
|||
int stundenBisHeute = (std + 24 * day(local_t)); |
|||
if ( (monat == 3 && stundenBisHeute >= (1 + timeZone + 24 * (31 - (5 * jahr /4 + 4) % 7))) || |
|||
(monat == 10 && stundenBisHeute < (1 + timeZone + 24 * (31 - (5 * jahr /4 + 1) % 7))) ) |
|||
return 1; |
|||
else |
|||
return 0; |
|||
/*
|
|||
int stundenBisWechsel = (1 + 24 * (31 - (5 * year(local_t) / 4 + 4) % 7)); |
|||
if (monat == 3 && stundenBisHeute >= stundenBisWechsel || monat == 10 && stundenBisHeute < stundenBisWechsel) |
|||
return 1; |
|||
else |
|||
return 0; |
|||
*/ |
|||
} |
|||
|
|||
|
|||
time_t getNow () { |
|||
//---------------
|
|||
time_t jetzt = now(); |
|||
while (!isValidDateTime(jetzt) && getTimeTrials < 10) { // ungültig, max 10x probieren
|
|||
if (getTimeTrials) { |
|||
//Serial.print (getTimeTrials);
|
|||
//Serial.println(". Versuch für getNtpTime");
|
|||
} |
|||
jetzt = getNtpTime (); |
|||
if (isValidDateTime(jetzt)) { |
|||
setTime (jetzt); |
|||
getTimeTrials = 0; |
|||
} |
|||
else |
|||
getTimeTrials++; |
|||
} |
|||
//return jetzt + offsetDayLightSaving(jetzt)*SECS_PER_HOUR;
|
|||
return jetzt; |
|||
} |
|||
|
|||
|
|||
char _timestr[24]; |
|||
|
|||
char* getNowStr (time_t no = getNow()) { |
|||
//------------------------------------
|
|||
sprintf (_timestr, "%02d:%02d:%02d", hour(no), minute(no), second(no)); |
|||
return _timestr; |
|||
} |
|||
|
|||
char* getTimeStr (time_t no = getNow()) { |
|||
//------------------------------------
|
|||
return getNowStr (no); |
|||
} |
|||
|
|||
char* getDateTimeStr (time_t no) { |
|||
//------------------------------
|
|||
sprintf (_timestr, "%04d-%02d-%02d+%02d:%02d:%02d", year(no), month(no), day(no), hour(no), minute(no), second(no)); |
|||
return _timestr; |
|||
} |
|||
|
|||
char* getDateStr (time_t no) { |
|||
//------------------------------
|
|||
sprintf (_timestr, "%04d-%02d-%02d", year(no), month(no), day(no)); |
|||
return _timestr; |
|||
} |
|||
|
|||
|
|||
#endif |
Loading…
Reference in new issue