/
BLE_Adv_Callback.ino
143 lines (119 loc) · 6.76 KB
/
BLE_Adv_Callback.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Some understanding of this stuff might be found by digging around in the ESP32 library's
// file BLEAdvertisedDevice.h
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
#define manDataSizeMax 31 // BLE specs say no more than 31 bytes, but see comments below!
// See if we have manufacturer data and then look to see if it's coming from a Victron device.
if (advertisedDevice.haveManufacturerData() == true) {
// Note: This comment (and maybe some code?) needs to be adjusted so it's not so
// specific to String-vs-std:string. I'll leave it as-is for now so you at least
// understand why I have an extra byte added to the manCharBuf array.
//
// Here's the thing: BLE specs say our manufacturer data can be a max of 31 bytes.
// But: The library code puts this data into a String, which we will then copy to
// a character (i.e., byte) buffer using String.toCharArray(). Assuming we have the
// full 31 bytes of manufacturer data allowed by the BLE spec, we'll need to size our
// buffer with an extra byte for a null terminator. Our toCharArray() call will need
// to specify *32* bytes so it will copy 31 bytes of data with a null terminator
// at the end.
uint8_t manCharBuf[manDataSizeMax+1];
#ifdef USE_String
String manData = advertisedDevice.getManufacturerData(); // lib code returns String.
#else
std::string manData = advertisedDevice.getManufacturerData(); // lib code returns std::string
#endif
int manDataSize=manData.length(); // This does not count the null at the end.
// Copy the data from the String to a byte array. Must have the +1 so we
// don't lose the last character to the null terminator.
#ifdef USE_String
manData.toCharArray((char *)manCharBuf,manDataSize+1);
#else
manData.copy((char *)manCharBuf, manDataSize+1);
#endif
// Now let's setup a pointer to a struct to get to the data more cleanly.
victronManufacturerData * vicData=(victronManufacturerData *)manCharBuf;
// ignore this packet if the Vendor ID isn't Victron.
if (vicData->vendorID!=0x02e1) {
return;
}
// ignore this packet if it isn't type 0x01 (Solar Charger).
if (vicData->victronRecordType != 0x01) {
return;
}
// Not all packets contain a device name, so if we get one we'll save it and use it from now on.
if (advertisedDevice.haveName()) {
// This works the same whether getName() returns String or std::string.
strcpy(savedDeviceName,advertisedDevice.getName().c_str());
}
if (vicData->encryptKeyMatch != key[0]) {
Serial.printf("Packet encryption key byte 0x%2.2x doesn't match configured key[0] byte 0x%2.2x\n",
vicData->encryptKeyMatch, key[0]);
return;
}
uint8_t inputData[16];
uint8_t outputData[16]={0}; // i don't really need to initialize the output.
// The number of encrypted bytes is given by the number of bytes in the manufacturer
// data as a whole minus the number of bytes (10) in the header part of the data.
int encrDataSize=manDataSize-10;
for (int i=0; i<encrDataSize; i++) {
inputData[i]=vicData->victronEncryptedData[i]; // copy for our decrypt below while I figure this out.
}
esp_aes_context ctx;
esp_aes_init(&ctx);
auto status = esp_aes_setkey(&ctx, key, keyBits);
if (status != 0) {
Serial.printf(" Error during esp_aes_setkey operation (%i).\n",status);
esp_aes_free(&ctx);
return;
}
// construct the 16-byte nonce counter array by piecing it together byte-by-byte.
uint8_t data_counter_lsb=(vicData->nonceDataCounter) & 0xff;
uint8_t data_counter_msb=((vicData->nonceDataCounter) >> 8) & 0xff;
u_int8_t nonce_counter[16] = {data_counter_lsb, data_counter_msb, 0};
u_int8_t stream_block[16] = {0};
size_t nonce_offset=0;
status = esp_aes_crypt_ctr(&ctx, encrDataSize, &nonce_offset, nonce_counter, stream_block, inputData, outputData);
if (status != 0) {
Serial.printf("Error during esp_aes_crypt_ctr operation (%i).",status);
esp_aes_free(&ctx);
return;
}
esp_aes_free(&ctx);
// Now do our same struct magic so we can get to the data more easily.
victronPanelData * victronData = (victronPanelData *) outputData;
// Getting to these elements is easier using the struct instead of
// hacking around with outputData[x] references.
uint8_t deviceState=victronData->deviceState;
uint8_t errorCode=victronData->errorCode;
float batteryVoltage=float(victronData->batteryVoltage)*0.01;
float batteryCurrent=float(victronData->batteryCurrent)*0.1;
float todayYield=float(victronData->todayYield)*0.01*1000;
uint16_t inputPower=victronData->inputPower; // this is in watts; no conversion needed
// Getting the output current takes some magic because of the way they have the
// 9-bit value packed into two bytes. The first byte has the low 8 bits of the count
// and the second byte has the upper (most significant) bit of the 9-bit value plus some
// There's some other junk in the remaining 7 bits - i'm not sure if it's useful for
// anything else but we can't use it here! - so we will mask them off. Then combine the
// two bye components to get an integer value in 0.1 Amp increments.
int integerOutputCurrent=((victronData->outputCurrentHi & 0x01)<<9) | victronData->outputCurrentLo;
float outputCurrent=float(integerOutputCurrent)*0.1;
// I don't know why, but every so often we'll get half-corrupted data from the Victron. As
// far as I can tell it's not a decryption issue because we (usually) get voltage data that
// agrees with non-corrupted records.
//
// Towards the goal of filtering out this noise, I've found that I've rarely (or never) seen
// corrupted data when the 'unused' bits of the outputCurrent MSB equal 0xfe. We'll use this
// as a litmus test here.
uint8_t unusedBits=victronData->outputCurrentHi & 0xfe;
if (unusedBits != 0xfe) {
return;
}
Serial.printf("%-31s Battery: %6.2f Volts %6.2f Amps Solar: %6d Watts Yield: %6.0f Wh Load: %6.1f Amps State: %3d\n",
savedDeviceName,
batteryVoltage, batteryCurrent,
inputPower, todayYield,
outputCurrent, deviceState
);
}
}
};