#include #include #include "CircularBuffer.h" #include #include #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 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; }