/* Copyright (C) 2011 James Coliz, Jr. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. */ /** * Example RF Radio Ping Pair * * This is an example of how to use the RF24 class. Write this sketch to two different nodes, * connect the role_pin to ground on one. The ping node sends the current time to the pong node, * which responds by sending the value back. The ping node can then see how long the whole cycle * took. */ #include #include #include "nRF24L01.h" #include #include "printf.h" #include #include "CRC8.h" #include "CRC16.h" #include #include "time.h" // // Hardware configuration // // Set up nRF24L01 radio on SPI bus plus pins 8 & 9 // Replace with your network credentials const char* ssid = "ZuHause"; const char* password = "GeHeim"; // NTP server to request epoch time const char* ntpServer = "pool.ntp.org"; // Hardware configuration #define RF1_CE_PIN (4) #define RF1_CS_PIN (5) #define SER_BAUDRATE (115200) #define MAX_RF_PAYLOAD_SIZE (32) #define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate #define DEFAULT_RECV_CHANNEL (3) // 3 = Default channel for Hoymiles #define DEFAULT_SEND_CHANNEL (40) // 40 = Default channel for Hoymiles const int SYNC_PIN = 33; RF24 radio(RF1_CE_PIN, RF1_CS_PIN); CRC8 crc8; CRC16 crc16; // HM-400 Serial No: 1121 73109025 // uint8_t addrWR[5] = {0x25, 0x90, 0x10, 0x73, 0x01}; uint8_t addrWR[4] = {0x73, 0x10, 0x90, 0x25}; uint8_t addrDTU[4] = {0x72, 0x81, 0x90, 0x05}; typedef struct Hoymiles_Data_95_t { uint32_t timestamp; float pv_voltage; float pv_ampere; float pv_power; float ac_voltage; float frequency; float temperature; float daily_production; float weekly_production; float total_production; } Hoymiles_Data_95_t; // const uint64_t pipes[3] = { 0x7281883201, 0x52090137, 0x73109025 }; inline static void dumpData(char * was, uint8_t *p, int len) { Serial.print (was); while (len--) { if (*p < 16) Serial.print(F("0")); Serial.print(*p++, HEX); Serial.print(' '); } Serial.print(F(" ")); } void dump(char * was, byte * data, int l) { //============================================== Serial.print (was); for (byte i = 0; i < l; i++) { Serial.print(data[i], HEX); Serial.print(' '); } Serial.println(); } // Initialize WiFi void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } static void activateConf(void) { // Match MySensors' channel & datarate radio.setChannel(DEFAULT_RECV_CHANNEL); radio.setDataRate(DEFAULT_RF_DATARATE); radio.disableCRC(); // radio.setAutoAck(0x00); radio.setCRCLength(RF24_CRC_16); radio.enableDynamicPayloads(); radio.setPayloadSize(MAX_RF_PAYLOAD_SIZE); radio.setRetries(3, 15); // {0x73, 0x10, 0x90, 0x25}; uint8_t readAddress[5] = {0x01, addrDTU[0], addrDTU[1], addrDTU[2], addrDTU[3]}; // Configure listening pipe with the 'promiscuous' address and start listening radio.setAddressWidth(5); radio.openReadingPipe(1, readAddress); // Wen wan't only RX irqs radio.maskIRQ(true, true, false); // Use lo PA level, as a higher level will disturb CH340 serial usb adapter radio.setPALevel(RF24_PA_LOW); radio.startListening(); } boolean eventRaised = false; void IRAM_ATTR Ext_INT1_ISR() { eventRaised = true; } #define LISTEN_ONLY 0 void setup(void) { Serial.begin(SER_BAUDRATE); initWiFi(); configTime(0, 0, ntpServer); radio.begin(); #if LISTEN_ONLY // Setup Sync with 2nd ESP to listen pinMode(SYNC_PIN, INPUT_PULLUP); attachInterrupt(SYNC_PIN, Ext_INT1_ISR, HIGH); #else pinMode(SYNC_PIN, OUTPUT); digitalWrite(SYNC_PIN, LOW); #endif // Disable shockburst for receiving and decode payload manually radio.setAutoAck(false); radio.setRetries(0, 0); activateConf(); // // Dump the configuration of the rf unit for debugging // radio.printDetails(); crc8.setPolynome(0x01); crc8.setStartXOR(0); crc8.setEndXOR(0); crc16.setPolynome((uint16_t)0x18005); crc16.setStartXOR(0xFFFF); crc16.setEndXOR(0x0000); crc16.setReverseIn(true); crc16.setReverseOut(true); } unsigned long getTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { return 0; } time(&now); return now; } Hoymiles_Data_95_t parseResult(uint8_t *data) { Hoymiles_Data_95_t DATA {}; if (data[0] == 0x95 && data[9] == 0x01) { // Received: 95 73 10 90 25 73 10 90 25 01 00 01 01 7C 01 33 04 90 00 00 40 75 03 73 09 09 0B DATA.pv_voltage = float(data[12] << 8 | data[13]) / 10; DATA.pv_ampere = float(data[14] << 8 | data[15]) / 100; DATA.pv_power = float(data[16] << 8 | data[17]) / 10; DATA.total_production = float(data[20] << 8 | data[21]) / 1000; DATA.daily_production = float(data[22] << 8 | data[23]); DATA.ac_voltage = float(data[24] << 8 | data[25]) / 10; } else if (data[0] == 0x95 && data[9] == 0x82) { // Received: 95 73 10 90 25 73 10 90 25 82 13 88 04 57 00 01 00 30 03 E8 01 5B 00 1F 2E 2A 44 DATA.frequency = float(data[10] << 8 | data[11]) / 100; DATA.temperature = float(data[20] << 8 | data[21]) / 10; } return DATA; } uint8_t getPowerMetrics(uint8_t *data) { uint8_t datalen = 0; memset(data, 0x00, 32); // uint8_t i = 0; // data[i++] = 0x7E; data[0] = 0x15; data[1] = addrWR[0]; data[2] = addrWR[1]; data[3] = addrWR[2]; data[4] = addrWR[3]; data[5] = addrDTU[0]; data[6] = addrDTU[1]; data[7] = addrDTU[2]; data[8] = addrDTU[3]; data[9] = 0x80; // 0x81 data[10] = 0x0B; // 0x11 data[11] = 0x00; // // time unsigned long epochTime = getTime(); data[12] = ((epochTime >> 24) & 0xFF); data[13] = ((epochTime >> 16) & 0xFF); data[14] = ((epochTime >> 8) & 0xFF); data[15] = ((epochTime & 0xFF)); data[16] = 0x00; data[17] = 0x00; data[18] = 0x00; data[19] = 0x05; crc16.restart(); crc16.add(&data[10], 14); data[24] = (crc16.getCRC() >> 8) & 0xFF; data[25] = crc16.getCRC() & 0xFF; crc8.restart(); crc8.add (&data[0], 26); data[26] = crc8.getCRC(); //0x07; datalen = 27; return datalen; } uint8_t getUnknown(uint8_t *data) { uint8_t datalen = 0; memset(data, 0x00, 32); data[0] = 0x15; data[1] = addrWR[0]; data[2] = addrWR[1]; data[3] = addrWR[2]; data[4] = addrWR[3]; data[5] = addrDTU[0]; data[6] = addrDTU[1]; data[7] = addrDTU[2]; data[8] = addrDTU[3]; data[9] = 0x80; // 0x81 data[10] = 0x0B; // 0x11 data[11] = 0x00; // time unsigned long epochTime = getTime(); data[12] = ((epochTime >> 24) & 0xFF); data[13] = ((epochTime >> 16) & 0xFF); data[14] = ((epochTime >> 8) & 0xFF); data[15] = ((epochTime & 0xFF)); data[16] = 0x00; data[17] = 0x00; data[18] = 0x00; data[19] = 0x05; crc16.restart(); crc16.add(&data[10], 14); data[24] = (crc16.getCRC() >> 8) & 0xFF; data[25] = crc16.getCRC() & 0xFF; crc8.restart(); crc8.add (&data[0], 26); data[26] = crc8.getCRC(); //0x07; datalen = 27; return datalen; } int channelCounter = 0; void loop(void) { // // only send to sharp at every 10 sec // time_t epochTime = getTime(); // tm* now = std::localtime(&epochTime); // int sec = now->tm_sec; // int syncTime = (((1+(int)sec/10)*10)-sec); // delay(syncTime * 1000); int channels[9] = {1, 3, 6, 9, 11, 23, 40, 61, 75}; #if LISTEN_ONLY if (eventRaised) { eventRaised = false; int listen_time = 3000; // if (channelCounter > (sizeof(channels)/sizeof(channels[0]))) { // channelCounter = 0; // } // radio.setChannel(channels[channelCounter]); // channelCounter++; // for (int channel = 0; channel < 9; channel ++) { if (channelCounter > 128) { channelCounter = 0; } radio.setChannel(channelCounter); channelCounter++; #else digitalWrite(SYNC_PIN, HIGH); delay(100); int listen_time = 3000; radio.setChannel(DEFAULT_SEND_CHANNEL); uint8_t writeAddress[5] = {0x01, addrWR[0], addrWR[1], addrWR[2], addrWR[3]}; radio.openWritingPipe(writeAddress); Serial.print("Send to channel: "); Serial.println(radio.getChannel()); // First, stop listening so we can talk. radio.stopListening(); uint8_t data[32]; uint8_t datalen = getPowerMetrics(data); time_t time = (data[12] << 24 | data[13] << 16 | data[14] << 8 | data[15]); printf("Time %s\r\n", asctime(gmtime(&time))); printf("Sending packet of size: %d\r\n", datalen); dumpData("Now sending: ", data, datalen); bool ok = radio.write( data, datalen ); // last param is multicast if (ok) printf("ok...\r\n"); else printf("no ack, failed...\r\n"); // wait for reply radio.setChannel(DEFAULT_RECV_CHANNEL); digitalWrite(SYNC_PIN, LOW); #endif radio.startListening(); Serial.print("Listen channel: "); Serial.println(radio.getChannel()); // radio.printDetails(); unsigned long time_started_waiting = millis(); uint8_t pipe_num; bool timeout = false; while( !timeout ) { if (radio.available(&pipe_num)) { uint8_t len = radio.getDynamicPayloadSize(); uint8_t backPayload[len]; radio.read( backPayload, len ); backPayload[len] = 0; // printf("Got reply \"%s\" from pipe \"%d\".\r\n", backPayload, pipe_num); dumpData("Received: ", backPayload, len); Hoymiles_Data_95_t DATA = parseResult(backPayload); printf("\r\nGot reply: \r\n"); printf("\tVoltage: %.2lfV, Ampere: %.2lfA, Power: %.2lfW\r\n", DATA.pv_voltage, DATA.pv_ampere, DATA.pv_power); printf("\tDaily production: %.3lf W, AC Voltage: %.1lf V, Total production: %.3lf kW\r\n", DATA.daily_production, DATA.ac_voltage, DATA.total_production); printf("\tFrequency: %.3lf, Temperature: %.1lf °C\r\n", DATA.frequency, DATA.temperature); } if ( (millis() - time_started_waiting) > listen_time ) { timeout = true; printf("This round is over.\r\n\r\n"); } } #if LISTEN_ONLY } #else // Try again 1s later delay(3000); #endif }