From a7e73f6ae8423276a949f8e7d763c7fc48bd9e1b Mon Sep 17 00:00:00 2001
From: Martin Grill <martin.grill@gmx.de>
Date: Sun, 27 Mar 2022 18:17:52 +0200
Subject: [PATCH 1/2] Added current version of format description. Added 'first
 contact' code example by of22.

---
 doc/hoymiles-format-description.txt           | 299 ++++++++++
 .../NRF24_SendRcv/include/CircularBuffer.h    | 153 +++++
 tools/nano/NRF24_SendRcv/include/hm_crc.h     |   8 +
 tools/nano/NRF24_SendRcv/include/hm_packets.h |  18 +
 tools/nano/NRF24_SendRcv/include/stdinout.cpp |  47 ++
 tools/nano/NRF24_SendRcv/include/stdinout.h   |  17 +
 tools/nano/NRF24_SendRcv/lib/README           |  46 ++
 tools/nano/NRF24_SendRcv/platformio.ini       |  41 ++
 tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp  | 543 ++++++++++++++++++
 .../NRF24_SendRcv/src/NRF24_sniff_types.h     |  55 ++
 tools/nano/NRF24_SendRcv/src/hm_crc.cpp       | 142 +++++
 tools/nano/NRF24_SendRcv/src/hm_packets.cpp   |  74 +++
 tools/nano/NRF24_SendRcv/src/stdinout.cpp     |  48 ++
 tools/nano/NRF24_SendRcv/test/README          |  11 +
 14 files changed, 1502 insertions(+)
 create mode 100644 doc/hoymiles-format-description.txt
 create mode 100644 tools/nano/NRF24_SendRcv/include/CircularBuffer.h
 create mode 100644 tools/nano/NRF24_SendRcv/include/hm_crc.h
 create mode 100644 tools/nano/NRF24_SendRcv/include/hm_packets.h
 create mode 100644 tools/nano/NRF24_SendRcv/include/stdinout.cpp
 create mode 100644 tools/nano/NRF24_SendRcv/include/stdinout.h
 create mode 100644 tools/nano/NRF24_SendRcv/lib/README
 create mode 100644 tools/nano/NRF24_SendRcv/platformio.ini
 create mode 100644 tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp
 create mode 100644 tools/nano/NRF24_SendRcv/src/NRF24_sniff_types.h
 create mode 100644 tools/nano/NRF24_SendRcv/src/hm_crc.cpp
 create mode 100644 tools/nano/NRF24_SendRcv/src/hm_packets.cpp
 create mode 100644 tools/nano/NRF24_SendRcv/src/stdinout.cpp
 create mode 100644 tools/nano/NRF24_SendRcv/test/README

diff --git a/doc/hoymiles-format-description.txt b/doc/hoymiles-format-description.txt
new file mode 100644
index 00000000..3123ae3f
--- /dev/null
+++ b/doc/hoymiles-format-description.txt
@@ -0,0 +1,299 @@
+Ziel dieses Projekts
+====================
+
+Anstelle der DTU wollen wir direkt von einem Arduino/RaspberryPi o.ä.
+die aktuellen Betriebsdaten der Wechselrichter auslesen.
+
+Ohne Umweg über die "Cloud".
+
+
+
+Systemaufbau
+============
+
+- Eine "DTU" kommuniziert mit vielen Wechselrichtern.
+- Die Kommunikation geht immer von der DTU aus:
+  DTU stellt Anfrage und erwartet eine Antwort vom WR.
+- Dafür muss die DTU die Adressen aller WR kennen.
+  
+
+                 Nordic
+              "Shockburst"
+                 2.4 GHz
+      \|/ <-----------------> \|/
+       |                       |
+   +-------+                +-----------+
+   |  DTU  |                | MI-600    |
+   +-------+                +-----------+-+
+                              | MI-600    |
+                              +-----------+-+
+                                | MI-1500   |
+                                +-----------+
+                                    :
+                                    :
+        ABBILDUNG 1: Systemübersicht
+     
+     
+
+                                Nordic
+      WLAN                   "Shockburst"
+                                2.4 GHz
+      \|/                        \|/
+       |                          |
+  +---------+               +-----------+
+  | ESP8266 |               | NRF24LE1E |
+  +---------+               +-----------+
+       ^                          ^
+       |                          |
+       |       +----------+       |
+       +-----> | GD32F303 | <-----+
+         (B)   +----------+   (C)
+              
+     ABBILDUNG 2: Innerer Aufbau "DTU"
+     
+                        
+                        
+                                             Nordic
+                                          "Shockburst"
+                           NRF24LE1E         2.4 GHz
+                       +------------------+      \|/
+   +----------+        |      |           |       |
+   | GD32F303 | <----->|  µC  | NRF24L01+ |-------+
+   +----------+   (C)  |      |           |
+                       +------+-----------+
+              
+   ABBILDUNG 3: Detailansicht GD32F303 - NRF24LE1E
+     
+
+
+Adressierung
+============
+
+Die Seriennummern der DTU und der WR werden intern wie folgt
+als Adresse für die Kommunikation verwendet:
+
+Beispiel: Seriennummer ....72818832
+
+Innerhalb der Pakete auf (C) wird daraus die 4-Byte-Adresse
+0x72, 0x81, 0x88, 0x32 gebildet. Das ist die BCD-Darstellung
+der letzen 8 Dezimalziffern.
+
+Die zugehörige Shockburst Zieladresse ist dieselbe, aber
+die Byte-Reihenfolge wird umgedreht, und eine 0x01 ergänzt
+(Shockburst ist auf 5-Byte-Adressen eingestellt).
+
+Um eine Nachricht an das Gerät mit o.g. Seriennummer zu senden
+lautet die Zieladresse also (0x32, 0x88, 0x81, 0x72, 0x01).
+
+
+
+Nachrichten
+===========
+
+Nachricht: DTU an WR: "Init" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+             7E   07   00 00 00 00      00 00 00 00      00    07    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^            ^^    ^^
+Bedeutung    SOF  MID  WR ser#          WR ser#           ?    CRC8  EOF
+                                                                ?
+
+
+Nachricht: DTU an WR: "Init 2" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+             7E   07   72 81 88 32      72 81 88 32      00    07    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^            ^^    ^^
+Bedeutung    SOF  MID  DTU ser#         DTU ser#          ?    CRC8  EOF
+Einheit                BCD (letzte 8)   BCD (letzte 8)    ?     ?
+Beispiel               72818832         72818832          ?   
+                                                               
+
+
+Nachricht 0x80: DTU an WR: "Zeit setzen" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+                                                            |<-------------CRC16 'modbus' für CRC_M----------------->|
+             7E   15   72 22 02 00      72 22 02 00      80 0B 00     62 09 04 9b      00 00     00 00   00 00   00 00     F2 68    F0    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^      ^^           ^^^^^^^^^^^                                          ^^^^^    ^^    ^^
+Bedeutung    SOF  MID  WR ser#          WR ser#          CMD  ?       TIME (UTC)                                           CRC_M    CRC8  EOF
+Einheit                BCD (letzte 8)   BCD (letzte 8)        ?       [s]                                                  HI LO   
+Beispiel               72220200         72220200              ?       2022-02-13                                              
+                                                                      13:16:11                                                
+
+
+Nachricht 0x81: DTU an WR: "Anfrage DC-Daten" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+GD->NRF     7E    15   70 51 43 68    70 51 43 68      81     xx       7F     ...... (NOCH NICHT VERIFIZIERT / GESEHEN)
+                                      ^^^^^^^^^^^      ^^     ^^       ^^
+                                       | (wird von     CMD    CRC8     EOF
+                                       | NRF ersetzt)         | (wird von NRF
+                                       v                      v neu berechnet)
+                                                             
+on-air            15   70 51 43 68    70 53 54 53      81     BA
+(payload)              ^^^^^^^^^^^    ^^^^^^^^^^^
+                       WR ser #       DTU ser #
+
+
+Nachricht 0x82: DTU an WR: "Anfrage AC-Daten" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+GD->NRF     7E    15   70 51 43 68    70 51 43 68      82     xx       7F     ...... (NOCH NICHT VERIFIZIERT / GESEHEN)
+                                      ^^^^^^^^^^^      ^^     ^^       ^^
+                                       | (wird von     CMD    CRC8     EOF
+                                       | NRF ersetzt)         | (wird von NRF
+                                       v                      v neu berechnet)
+                                                             
+on-air            15   70 51 43 68    70 53 54 53      82     B9
+(payload)              ^^^^^^^^^^^    ^^^^^^^^^^^
+                       WR ser #       DTU ser #
+
+
+Nachricht 0x83: DTU an WR: "Anfrage DC-Daten" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+GD->NRF     7E    15   70 51 43 68    70 51 43 68      83     xx       7F     ...... (NOCH NICHT VERIFIZIERT / GESEHEN)
+                                      ^^^^^^^^^^^      ^^     ^^       ^^
+                                       | (wird von     CMD    CRC8     EOF
+                                       | NRF ersetzt)         | (wird von NRF
+                                       v                      v neu berechnet)
+                                                             
+on-air            15   70 51 43 68    70 53 54 53      83     B8
+(payload)              ^^^^^^^^^^^    ^^^^^^^^^^^
+                       WR ser #       DTU ser #
+
+
+Nachricht 0x85: DTU an WR: "???" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+GD->NRF     7E    15   70 51 43 68    70 51 43 68      85     xx       7F     ...... (NOCH NICHT VERIFIZIERT / GESEHEN)
+                                      ^^^^^^^^^^^      ^^     ^^       ^^
+                                       | (wird von     CMD    CRC8     EOF
+                                       | NRF ersetzt)         | (wird von NRF
+                                       v                      v neu berechnet)
+                                                             
+on-air            15   70 51 43 68    70 53 54 53      85     BE
+(payload)              ^^^^^^^^^^^    ^^^^^^^^^^^
+                       WR ser #       DTU ser #
+
+
+Nachricht 0xFF: DTU an WR: "???" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+GD->NRF     7E    15   70 51 43 68    70 51 43 68      FF     xx       7F     ...... (NOCH NICHT VERIFIZIERT / GESEHEN)
+                                      ^^^^^^^^^^^      ^^     ^^       ^^
+                                       | (wird von     CMD    CRC8     EOF
+                                       | NRF ersetzt)         | (wird von NRF
+                                       v                      v neu berechnet)
+                                                             
+on-air            15   70 51 43 68    70 53 54 53      FF     C4
+(payload)              ^^^^^^^^^^^    ^^^^^^^^^^^
+                       WR ser #       DTU ser #
+
+
+Nachricht 0x01: WR an DTU: "Aktuelle DC Daten" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+             7E   95   72 22 02 00      72 22 02 00      01 00 01      01 4c   03 bd   0c 46     00 b5   00 03   00 05     00 00    BD    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^      ^^            ^^^^^   ^^^^^   ^^^^^     ^^^^^   ^^^^^   ^^^^^              ^^    ^^
+Bedeutung    SOF  MID  WR ser#          WR ser#          CMD  ?        PV1.u   PV1.i   PV1.p     PV2.u   PV2.i   PV2.p       ?      CRC8  EOF
+Einheit                BCD (letzte 8)   BCD (letzte 8)        ?        [0.1V]  [0.01A] [.1W]     [0.1V]  [0.01A] [.1W]       ?     
+Beispiel               72220200         72220200              ?        33.2V   9.57A   317.2W    18.1V   0.03A   0.5W        ?     
+
+
+Nachricht 0x02: WR an DTU: "Aktuelle AC Daten" (?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+             7E   95   72 22 02 00      72 22 02 00      02 28 23      00 00   24 44   00 3C     00 00   09 0F   13 88     0B D5   83    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^      ^^                                              ^^^^^   ^^^^^     ^^^^^   ^^    ^^
+Bedeutung    SOF  MID  WR ser#          WR ser#          CMD  ?                  ?       ?               AC.u    AC.f      AC.p    CRC8  EOF
+Einheit                BCD (letzte 8)   BCD (letzte 8)        ?                                          [0.1V]  [0.01Hz]  [0.1W]
+Beispiel               72220200         72220200              ?                9284    60                231.9V  50.00Hz   302.9W
+
+
+Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?)
+----------------------------------------------------------------------------------------------------------------------------------------------
+
+             7E   95   72 22 02 00      72 22 02 00      83 00 03      00 83     03 E8     00 B2     00 0A     FD 26               1E    7F
+             ^^   ^^   ^^^^^^^^^^^      ^^^^^^^^^^^      ^^                                                                        ^^    ^^
+Bedeutung    SOF  MID  WR ser#          WR ser#          CMD  ?         ?          ?         ?         ?         ?                 CRC8  EOF
+Einheit                BCD (letzte 8)   BCD (letzte 8)        ?                                                   
+Beispiel               72220200         72220200              ?        131       1000      178       10                                           
+
+
+
+Hinweise
+========
+
+Die "on-air (payload)" Bytes geben nur die Nutzlast der gesendeten Shockburst-Pakete an.
+Intern enthalten diese Pakete auch die Zieladresse, die Länge, eine CRC.
+
+
+Legende
+=======
+
+MID: Message-ID. Antworten haben Bit 7 gesetzt, 
+     z.B. Frage 0x15 --> Antwort 0x95.
+     z.B. Frage 0x07 --> Antwort 0x87.
+     Für Kommunikation GD <--> NRF
+     
+CMD:
+     Befehl an den WR hat Bit 7 gesetzt
+       0x80 "Zeit setzen"
+       0x81 "Anfrage DC-Daten", erwartete Antwort: 0x01
+       0x82 "Anfrage AC-Daten", erwartete Antwort: 0x02
+       0x83 "?"
+       0x85 "?"
+       0xFF "?"
+     Antworten vom WR haben Bit 7 gelöscht:
+       0x01 "Aktuelle DC-Daten"
+       0x02 "Aktuelle AC-Daten"
+
+SOF: Start-of-Frame 0x7e
+EOF: End-of-Frame 0x7f
+CRC8: CRC8 mit poly=1 init=0 xor=0, für alle Bytes zwischen SOF und CRC8.
+  Beispiel in Python:
+    >>> import crcmod
+    >>> f = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0)
+    >>> payload = bytes((0x95,0x72,0x22,0x02,0x00,0x72,0x22,0x02,0x00,0x83,0x00,0x03,0x00,0x83,0x03,0xE8,0x00,0xB2,0x00,0x0A,0xFD,0x26))
+    >>> hex(f(payload))
+    '0x1e'
+    
+CRC_M: CRC16 wie für "Modbus"-Protokoll, High-Byte gefolgt von Low-Byte
+  Beispiel in Python:
+    >>> import crcmod
+    >>> f = crcmod.predefined.mkPredefinedCrcFun('modbus')
+    >>> payload = bytes((0x0B,0x00,0x62,0x2F,0x45,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00))
+    >>> hex(f(payload))
+    '0x3bd6'
+    
+TIME: Aktuelle (DTU-)Zeit als Unix "time_t" (Sekunden seit 1970-01-01)
+
+
+Glossar
+=======
+
+WR: Wechselrichter
+DTU: Data Terminal Unit (?). Die Hoymiles-Bezeichnung für den Kommunikations-Master.
+BCD: Binary Coded Decimal
+
+
+Notizen
+=======
+
+0x014c = 332    
+0x03bd = 957   
+0x0c64 = 3172
+0x6209049b = 1644758171
+datetime.datetime.utcfromtimestamp(0x6209049b): datetime.datetime(2022, 2, 13, 13, 16, 11)
+
+
+Historie
+========
+
+2022-03-09 / Petersilie / erste Version
+2022-03-10 / Petersilie / r2 / Nachrichten "02 28 23" und "82 00 03" ergänzt. Sauberer ausgerichtet. Python Beispiel für CRC.
+2022-03-12 / Petersilie / r3 / Erste on-air Formate hinzu. CMD-IDs hinzu. Neue Nachrichten von arnaldo_g hinzu. Übersicht hinzu.
+2022-03-15 / Petersilie / r4 / Nachricht 0x80: Mystery-Bytes am Ende "dechiffriert"
+2022-03-16 / Petersilie / r5 / ESP ist ein ESP8266, nicht ESP32 (danke an @tbnobody)
+2022-03-27 / Petersilie / Versionierung ab jetzt via Github.
diff --git a/tools/nano/NRF24_SendRcv/include/CircularBuffer.h b/tools/nano/NRF24_SendRcv/include/CircularBuffer.h
new file mode 100644
index 00000000..d0392093
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/include/CircularBuffer.h
@@ -0,0 +1,153 @@
+/*
+  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
+
+#define DISABLE_IRQ       \
+  uint8_t sreg = SREG;    \
+  cli();
+
+#define RESTORE_IRQ        \
+  SREG = sreg;
+
+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
diff --git a/tools/nano/NRF24_SendRcv/include/hm_crc.h b/tools/nano/NRF24_SendRcv/include/hm_crc.h
new file mode 100644
index 00000000..7f3c32e3
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/include/hm_crc.h
@@ -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);
\ No newline at end of file
diff --git a/tools/nano/NRF24_SendRcv/include/hm_packets.h b/tools/nano/NRF24_SendRcv/include/hm_packets.h
new file mode 100644
index 00000000..1c8aca45
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/include/hm_packets.h
@@ -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);
+};
diff --git a/tools/nano/NRF24_SendRcv/include/stdinout.cpp b/tools/nano/NRF24_SendRcv/include/stdinout.cpp
new file mode 100644
index 00000000..932e07ef
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/include/stdinout.cpp
@@ -0,0 +1,47 @@
+#if ARDUINO >= 100
+#include "Arduino.h"
+#else
+#include "WProgram.h"
+#endif
+#include <stdio.h>
+#include "stdinout.h"
+
+// Function that printf and related will use to print
+static int serial_putchar(char c, FILE *f)
+{
+        if(c == '\n') {
+                serial_putchar('\r', f);
+        }
+
+        return Serial.write(c) == 1 ? 0 : 1;
+}
+// Function that scanf and related will use to read
+static int serial_getchar(FILE *)
+{
+        // Wait until character is avilable
+        while(Serial.available() <= 0) { ; }
+
+        return Serial.read();
+}
+
+static FILE serial_stdinout;
+
+static void setup_stdin_stdout()
+{
+        // Set up stdout and stdin
+        fdev_setup_stream(&serial_stdinout, serial_putchar, serial_getchar, _FDEV_SETUP_RW);
+        stdout = &serial_stdinout;
+        stdin  = &serial_stdinout;
+        stderr = &serial_stdinout;
+}
+
+// Initialize the static variable to 0
+size_t initializeSTDINOUT::initnum = 0;
+
+// Constructor that calls the function to set up stdin and stdout
+initializeSTDINOUT::initializeSTDINOUT()
+{
+        if(initnum++ == 0) {
+                setup_stdin_stdout();
+        }
+}
\ No newline at end of file
diff --git a/tools/nano/NRF24_SendRcv/include/stdinout.h b/tools/nano/NRF24_SendRcv/include/stdinout.h
new file mode 100644
index 00000000..56f43301
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/include/stdinout.h
@@ -0,0 +1,17 @@
+#ifndef _STDINOUT_H
+#define _STDINOUT_H
+
+// no need to make an instance of this yourself
+class initializeSTDINOUT
+{
+        static size_t initnum;
+public:
+        // Constructor
+        initializeSTDINOUT();
+};
+
+// Call the constructor in each compiled file this header is included in
+// static means the names won't collide
+static initializeSTDINOUT initializeSTDINOUT_obj;
+
+#endif
\ No newline at end of file
diff --git a/tools/nano/NRF24_SendRcv/lib/README b/tools/nano/NRF24_SendRcv/lib/README
new file mode 100644
index 00000000..8c9c29ce
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/lib/README
@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html
diff --git a/tools/nano/NRF24_SendRcv/platformio.ini b/tools/nano/NRF24_SendRcv/platformio.ini
new file mode 100644
index 00000000..18dfbcbe
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/platformio.ini
@@ -0,0 +1,41 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:uno]
+platform = atmelavr
+board = uno
+framework = arduino
+upload_protocol = avrispmkII
+upload_flags = 
+	-v
+	-C
+	$PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
+	-p
+	$BOARD_MCU
+	-c
+	avrispmkII
+upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
+lib_deps = nrf24/RF24@^1.4.2
+
+[env:nano]
+platform = atmelavr
+board = nanoatmega328
+framework = arduino
+upload_protocol = avrispmkII
+upload_flags = 
+	-v
+	-C
+	$PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
+	-p
+	$BOARD_MCU
+	-c
+	avrispmkII
+upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
+lib_deps = nrf24/RF24@^1.4.2
diff --git a/tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp b/tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp
new file mode 100644
index 00000000..726b32a0
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp
@@ -0,0 +1,543 @@
+#include <Arduino.h>
+#include <SPI.h>
+#include <CircularBuffer.h>
+#include <RF24.h>
+#include <RF24_config.h>
+
+#include "hm_crc.h"
+#include "hm_packets.h"
+
+// Macros
+#define DISABLE_EINT EIMSK = 0x00
+#define ENABLE_EINT EIMSK = 0x01
+
+// Hardware configuration
+#define RF1_CE_PIN (9)
+#define RF1_CS_PIN (6)
+#define RF1_IRQ_PIN (2)
+
+#define LED_PIN_STATUS (A0)
+
+#define RF_MAX_ADDR_WIDTH (5) // Maximum address width, in bytes. MySensors use 5 bytes for addressing, where lowest byte is for node addressing.
+#define MAX_RF_PAYLOAD_SIZE (32)
+#define SER_BAUDRATE (115200)
+#define PACKET_BUFFER_SIZE (20) // Maximum number of packets that can be buffered between reception by NRF and transmission over serial port.
+
+// Startup defaults until user reconfigures it
+#define DEFAULT_RECV_CHANNEL (3)           // 3 = Default channel for Hoymiles
+#define DEFAULT_SEND_CHANNEL (40)          // 40 = Default channel for Hoymiles
+#define DEFAULT_RF_DATARATE (RF24_250KBPS) // Datarate
+
+#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) 
+#define WR1_RADIO_ID ((uint64_t)0x1946107301ULL) // 0x1946107300ULL = WR1
+#define WR2_RADIO_ID ((uint64_t)0x3944107301ULL) // 0x3944107301ULL = WR2
+#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
+
+#include "NRF24_sniff_types.h"
+
+unsigned long previousMillis = 0; // will store last time LED was updated
+const long interval = 250;        // interval at which to blink (milliseconds)
+int ledState = LOW;               // ledState used to set the LED
+
+static HM_Packets hmPackets;
+static uint32_t tickMillis;
+static uint16_t tickSec;
+
+static uint8_t sendBuf[MAX_RF_PAYLOAD_SIZE];
+
+// 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;
+
+static uint16_t lastCRC;
+
+static bool bOperate = true;
+static uint8_t checkCRC = 1;
+
+int channels[] = {3, 23, 40, 61, 75};
+
+// Function forward declaration
+static void DumpConfig();
+static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len);
+
+inline static void dumpData(uint8_t *p, int len)
+{
+  while (len--)
+  {
+    if (*p < 16)
+      Serial.print(F("0"));
+    Serial.print(*p++, HEX);
+  }
+  Serial.print(F(" "));
+}
+
+static void handleNrf1Irq()
+{
+  static uint8_t lostPacketCount = 0;
+  uint8_t pipe;
+
+  // 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();
+    }
+  }
+}
+
+static void activateConf(void)
+{
+  // Match MySensors' channel & datarate
+  radio1.setChannel(DEFAULT_RECV_CHANNEL);
+  radio1.setDataRate(DEFAULT_RF_DATARATE);
+
+  radio1.disableCRC();
+  radio1.setAutoAck(0x00);
+
+  radio1.setPayloadSize(MAX_RF_PAYLOAD_SIZE);
+
+  // Configure listening pipe with the 'promiscuous' address and start listening
+  radio1.setAddressWidth(5);
+  radio1.openReadingPipe(1, DTU_RADIO_ID);
+
+  // Wen wan't only RX irqs
+  radio1.maskIRQ(true, true, false);
+
+  // Use lo PA level, as a higher level will disturb CH340 serial usb adapter
+  radio1.setPALevel(RF24_PA_LOW);
+
+  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 serial 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;
+  }
+
+  DumpConfig();
+
+  tickMillis = millis() + 200;
+}
+
+static void DumpConfig()
+{
+  Serial.println(F("\nRadio 1:"));
+  radio1.printDetails();
+
+  Serial.println("");
+}
+
+static void DumpMenu()
+{
+  Serial.println(F("\n\nConfiguration:"));
+  Serial.println(F("  d: Dump current configuration"));
+  Serial.println(F("  f: Filter packets for valid CRC"));
+  Serial.println(F("  s: Start listening"));
+}
+
+static void DumpPrompt()
+{
+  Serial.print(F("\n?: "));
+}
+
+static void Config(int cmd)
+{
+  int i;
+
+  if (cmd == 'd')
+  {
+    activateConf();
+    DumpConfig();
+  }
+  else if (cmd == 'f')
+  {
+    Serial.println(F("Filter for valid CRC: "));
+    Serial.println(F(" 0: disable"));
+    Serial.println(F(" 1: enable"));
+    DumpPrompt();
+    while (!Serial.available())
+      ;
+    i = Serial.read() - 0x30;
+    if ((i < 0) || (i > 1))
+    {
+      Serial.println(F("\nIllegal selection."));
+    }
+    else
+    {
+      Serial.print(F("\nCRC check changed to "));
+      Serial.println(i);
+      checkCRC = i;
+    }
+  }
+  else if (cmd == 'h')
+  {
+    DumpMenu();
+  }
+  else if (cmd == 's')
+  {
+    activateConf();
+    bOperate = true;
+    return;
+  }
+  else
+  {
+    Serial.println(F("Unknown command. Type 'h' for command list."));
+  }
+
+  DumpPrompt();
+}
+
+void setup(void)
+{
+  pinMode(LED_PIN_STATUS, OUTPUT);
+
+  Serial.begin(SER_BAUDRATE);
+  Serial.flush();
+
+  hmPackets.SetUnixTimeStamp(0x623C8EA3);
+
+  Serial.println(F("-- Hoymiles test --"));
+
+  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();
+}
+
+void loop(void)
+{
+  while (!packetBuffer.empty())
+  {
+    if (!bOperate)
+    {
+      packetBuffer.popBack();
+      continue;
+    }
+
+    // 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
+    uint16_t 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)
+        {
+          Serial.print(F(" Lost: "));
+          Serial.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;
+    }
+
+    // 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;
+    Serial.print(val);
+    Serial.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]))
+          Serial.print(0);
+        else
+          Serial.print(1);
+      }
+      else
+      {
+        Serial.print(F("Ill remain "));
+        Serial.print(remain);
+      }
+    }
+    else
+    {
+      dumpData(&p->packet[2], payloadLen + 2);
+      printf_P(PSTR("%04X "), crc);
+    }
+
+    if (p->packetsLost > 0)
+    {
+      Serial.print(F(" Lost: "));
+      Serial.print(p->packetsLost);
+    }
+    Serial.println(F(""));
+
+    // Remove record as we're done with it.
+    packetBuffer.popBack();
+  }
+
+  // Configuration using terminal commands
+  if (Serial.available())
+  {
+    int cmd = Serial.read();
+    if (bOperate && (cmd == 'c'))
+    {
+      bOperate = false;
+      DumpMenu();
+      DumpPrompt();
+    }
+    else if (bOperate && ((cmd == 's') || (cmd == 'w')))
+    {
+      uint64_t dest = cmd == 's' ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      int32_t size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, DTU_RADIO_ID >> 8, DTU_RADIO_ID >> 8, 0x07, 0x00);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+    }
+    else if (bOperate && ((cmd == 'd') || (cmd == 'e')))
+    {
+      uint64_t dest = cmd == 'd' ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      int32_t size = hmPackets.GetTimePacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+    }
+    else if (bOperate && ((cmd == 'f') || (cmd == 'r')))
+    {
+      uint64_t dest = cmd == 'f' ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      int32_t size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0xFF);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+    }
+    else if (bOperate && ((cmd == 'g') || (cmd == 't')))
+    {
+      uint64_t dest = cmd == 'g' ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      int32_t size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x81);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+    }
+    else if (bOperate && ((cmd == 'h') || (cmd == 'z')))
+    {
+      uint64_t dest = cmd == 'h' ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      int32_t size = hmPackets.GetCmdPacket((uint8_t *)&sendBuf, dest >> 8, DTU_RADIO_ID >> 8, 0x15, 0x82);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+    }
+    else if (bOperate && (cmd == 'q'))
+    {
+      Serial.println(F("\nRadio1:"));
+      radio1.printPrettyDetails();
+    }
+    else if (!bOperate)
+    {
+      Serial.println((char)cmd);
+      Config(cmd);
+    }
+  }
+
+  // Status LED
+  unsigned long currentMillis = millis();
+  if ((currentMillis - previousMillis) >= interval)
+  {
+    // save the last time you blinked the LED
+    previousMillis = currentMillis;
+    // if the LED is off turn it on and vice-versa:
+    ledState = not(ledState);
+    // set the LED with the ledState of the variable:
+    digitalWrite(LED_PIN_STATUS, ledState);
+  }
+
+  // Second timer
+  if (millis() >= tickMillis)
+  {
+    static uint8_t toggle = 0;
+    static uint8_t tel = 0;
+    tickMillis += 200;
+    if (++tickSec >= 5)
+    {
+      hmPackets.UnixTimeStampTick();
+      tickSec = 0;
+    }
+
+    if (bOperate)
+    {
+      int32_t size;
+      uint64_t dest = toggle ? WR1_RADIO_ID : WR2_RADIO_ID;
+
+      if (tel > 3)
+        tel = 0;
+      if (tel == 0)
+        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);
+
+      SendPacket(dest, (uint8_t *)&sendBuf, size);
+
+      toggle = !toggle;
+      if (!toggle)
+        tel++;
+    }
+  }
+}
+
+#define DEBUG_SEND 0
+static void SendPacket(uint64_t dest, uint8_t *buf, uint8_t len)
+{
+#if DEBUG_SEND  
+  Serial.print(F("Send... CH"));
+#endif  
+  DISABLE_EINT;
+  radio1.stopListening();
+
+#ifdef CHANNEL_HOP
+  static uint8_t hop = 0;
+#if DEBUG_SEND    
+  if (channels[hop] < 10)
+    Serial.print(F("0"));
+  Serial.print(channels[hop]);
+#endif  
+  radio1.setChannel(channels[hop++]);
+  if (hop >= sizeof(channels) / sizeof(channels[0]))
+    hop = 0;
+#else
+#if DEBUG_SEND  
+  if (DEFAULT_SEND_CHANNEL < 10)
+    Serial.print(F("0"));
+  Serial.print(DEFAULT_SEND_CHANNEL);
+#endif  
+  radio1.setChannel(DEFAULT_SEND_CHANNEL);
+#endif
+
+  radio1.openWritingPipe(dest);
+#if DEBUG_SEND    
+  if (dest == WR1_RADIO_ID)
+    Serial.print(F(" WR1 "));
+  else
+    Serial.print(F(" WR2 "));
+#endif    
+
+  radio1.setCRCLength(RF24_CRC_16);
+  radio1.enableDynamicPayloads();
+  radio1.setAutoAck(true);
+  radio1.setRetries(3, 15);
+
+#if DEBUG_SEND    
+  uint32_t st = micros();
+  bool res = 
+#endif  
+  radio1.write(buf, len);
+#if DEBUG_SEND    
+  Serial.print(res);
+  Serial.print(F(" "));
+  Serial.print(micros());
+  Serial.print(F(" "));
+  Serial.println(micros() - st);
+#endif
+
+  // 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;
+}
diff --git a/tools/nano/NRF24_SendRcv/src/NRF24_sniff_types.h b/tools/nano/NRF24_SendRcv/src/NRF24_sniff_types.h
new file mode 100644
index 00000000..c51609cf
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/src/NRF24_sniff_types.h
@@ -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
+{
+  uint32_t 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
diff --git a/tools/nano/NRF24_SendRcv/src/hm_crc.cpp b/tools/nano/NRF24_SendRcv/src/hm_crc.cpp
new file mode 100644
index 00000000..74d41ce3
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/src/hm_crc.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/tools/nano/NRF24_SendRcv/src/hm_packets.cpp b/tools/nano/NRF24_SendRcv/src/hm_packets.cpp
new file mode 100644
index 00000000..3f2eb5d0
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/src/hm_packets.cpp
@@ -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;
+	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;
+}
+
diff --git a/tools/nano/NRF24_SendRcv/src/stdinout.cpp b/tools/nano/NRF24_SendRcv/src/stdinout.cpp
new file mode 100644
index 00000000..d89e9313
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/src/stdinout.cpp
@@ -0,0 +1,48 @@
+#if ARDUINO >= 100
+#include "Arduino.h"
+#else
+#include "WProgram.h"
+#endif
+#include <stdio.h>
+#include "stdinout.h"
+
+// Function that printf and related will use to print
+static int serial_putchar(char c, FILE *f)
+{
+        if(c == '\n') {
+                serial_putchar('\r', f);
+        }
+
+        return Serial.write(c) == 1 ? 0 : 1;
+}
+// Function that scanf and related will use to read
+static int serial_getchar(FILE *)
+{
+        // Wait until character is avilable
+        while(Serial.available() <= 0) { ; }
+        int ch = Serial.read();
+        Serial.write(ch);
+        return ch;
+}
+
+static FILE serial_stdinout;
+
+static void setup_stdin_stdout()
+{
+        // Set up stdout and stdin
+        fdev_setup_stream(&serial_stdinout, serial_putchar, serial_getchar, _FDEV_SETUP_RW);
+        stdout = &serial_stdinout;
+        stdin  = &serial_stdinout;
+        stderr = &serial_stdinout;
+}
+
+// Initialize the static variable to 0
+size_t initializeSTDINOUT::initnum = 0;
+
+// Constructor that calls the function to set up stdin and stdout
+initializeSTDINOUT::initializeSTDINOUT()
+{
+        if(initnum++ == 0) {
+                setup_stdin_stdout();
+        }
+}
\ No newline at end of file
diff --git a/tools/nano/NRF24_SendRcv/test/README b/tools/nano/NRF24_SendRcv/test/README
new file mode 100644
index 00000000..e7d15886
--- /dev/null
+++ b/tools/nano/NRF24_SendRcv/test/README
@@ -0,0 +1,11 @@
+
+This directory is intended for PlatformIO Unit Testing and project tests.
+
+Unit Testing is a software testing method by which individual units of
+source code, sets of one or more MCU program modules together with associated
+control data, usage procedures, and operating procedures, are tested to
+determine whether they are fit for use. Unit testing finds problems early
+in the development cycle.
+
+More information about PlatformIO Unit Testing:
+- https://docs.platformio.org/page/plus/unit-testing.html

From 82ce2c9d8864dcc465af804ab0130dd7a7322a6b Mon Sep 17 00:00:00 2001
From: grindylow <martin.grill@gmx.de>
Date: Sun, 27 Mar 2022 18:27:20 +0200
Subject: [PATCH 2/2] Update README.md

---
 tools/rpi/discover/README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tools/rpi/discover/README.md b/tools/rpi/discover/README.md
index 955a7d30..6f92adf8 100644
--- a/tools/rpi/discover/README.md
+++ b/tools/rpi/discover/README.md
@@ -5,3 +5,8 @@ This tool will continuously scan all channels.
 Requires NRF24 library from https://github.com/nRF24/RF24/ and an NRF24
 module connected to the Raspberry Pi's GPIO header as described in
 https://nrf24.github.io/RF24/index.html.
+
+UPDATE: Note that this tool relies on the NRF24's AutoAck functionality.
+It has since been conclusively proven that the inverters do not enable
+AutoAck. Therefore this tool works quite well for discovering NRF24s,
+just not the ones build into the inverters :-(