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