mirror of https://github.com/lumapu/ahoy.git
Martin Grill
3 years ago
14 changed files with 1502 additions and 0 deletions
@ -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. |
@ -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
|
@ -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); |
@ -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); |
|||
}; |
@ -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(); |
|||
} |
|||
} |
@ -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 |
@ -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 |
@ -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 |
@ -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; |
|||
} |
@ -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
|
@ -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; |
|||
} |
@ -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; |
|||
} |
|||
|
@ -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(); |
|||
} |
|||
} |
@ -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…
Reference in new issue