Browse Source

Added current version of format description. Added 'first contact' code example by of22.

pull/2/head
Martin Grill 3 years ago
parent
commit
a7e73f6ae8
  1. 299
      doc/hoymiles-format-description.txt
  2. 153
      tools/nano/NRF24_SendRcv/include/CircularBuffer.h
  3. 8
      tools/nano/NRF24_SendRcv/include/hm_crc.h
  4. 18
      tools/nano/NRF24_SendRcv/include/hm_packets.h
  5. 47
      tools/nano/NRF24_SendRcv/include/stdinout.cpp
  6. 17
      tools/nano/NRF24_SendRcv/include/stdinout.h
  7. 46
      tools/nano/NRF24_SendRcv/lib/README
  8. 41
      tools/nano/NRF24_SendRcv/platformio.ini
  9. 543
      tools/nano/NRF24_SendRcv/src/NRF24_sniff.cpp
  10. 55
      tools/nano/NRF24_SendRcv/src/NRF24_sniff_types.h
  11. 142
      tools/nano/NRF24_SendRcv/src/hm_crc.cpp
  12. 74
      tools/nano/NRF24_SendRcv/src/hm_packets.cpp
  13. 48
      tools/nano/NRF24_SendRcv/src/stdinout.cpp
  14. 11
      tools/nano/NRF24_SendRcv/test/README

299
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.

153
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

8
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);

18
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);
};

47
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();
}
}

17
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

46
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

41
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

543
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;
}

55
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

142
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;
}

74
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;
}

48
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();
}
}

11
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
Loading…
Cancel
Save