-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathrenogy_rs232.ino
357 lines (282 loc) · 12.6 KB
/
renogy_rs232.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
Reads the data from the Renogy charge controller via it's RS232 port using an ESP32 or similar. Tested with Wanderer 30A (CTRL-WND30-LI) and Wanderer 10A
See my Github repo for notes on building the cable:
https://github.com/wrybread/ESP32ArduinoRenogy
Notes:
- I don't think can power the ESP32 from the Renogy's USB port.. Maybe it's so low power that it shuts off?
To do:
- find out how much of a load the load port can handle...
- test with an Arduino
*/
// https://github.com/syvic/ModbusMaster
#include <ModbusMaster.h>
ModbusMaster node;
/*
A note about which pins to use:
- I was originally using pins 17 and 18 (aka RX2 and TX2 on some ESP32 devboards) for RX and TX,
which worked on an ESP32 Wroom but not an ESP32 Rover. So I switched to pins 13 and 14, which works on both.
I haven't tested on an Arduino board though.
*/
#define RXD2 13
#define TXD2 14
/*
Number of registers to check. I think all Renogy controllers have 35
data registers (not all of which are used) and 17 info registers.
*/
const uint32_t num_data_registers = 35;
const uint32_t num_info_registers = 17;
// if you don't have a charge controller to test with, can set this to true to get non 0 voltage readings
bool simulator_mode = false;
// A struct to hold the controller data
struct Controller_data {
uint8_t battery_soc; // percent
float battery_voltage; // volts
float battery_charging_amps; // amps
uint8_t battery_temperature; // celcius
uint8_t controller_temperature; // celcius
float load_voltage; // volts
float load_amps; // amps
uint8_t load_watts; // watts
float solar_panel_voltage; // volts
float solar_panel_amps; // amps
uint8_t solar_panel_watts; // watts
float min_battery_voltage_today; // volts
float max_battery_voltage_today; // volts
float max_charging_amps_today; // amps
float max_discharging_amps_today; // amps
uint8_t max_charge_watts_today; // watts
uint8_t max_discharge_watts_today; // watts
uint8_t charge_amphours_today; // amp hours
uint8_t discharge_amphours_today; // amp hours
uint8_t charge_watthours_today; // watt hours
uint8_t discharge_watthours_today; // watt hours
uint8_t controller_uptime_days; // days
uint8_t total_battery_overcharges; // count
uint8_t total_battery_fullcharges; // count
// convenience values
float battery_temperatureF; // fahrenheit
float controller_temperatureF; // fahrenheit
float battery_charging_watts; // watts. necessary? Does it ever differ from solar_panel_watts?
long last_update_time; // millis() of last update time
bool controller_connected; // bool if we successfully read data from the controller
};
Controller_data renogy_data;
// A struct to hold the controller info params
struct Controller_info {
uint8_t voltage_rating; // volts
uint8_t amp_rating; // amps
uint8_t discharge_amp_rating; // amps
uint8_t type;
uint8_t controller_name;
char software_version[40];
char hardware_version[40];
char serial_number[40];
uint8_t modbus_address;
float wattage_rating;
long last_update_time; // millis() of last update time
};
Controller_info renogy_info;
void setup()
{
Serial.begin(115200);
Serial.println("Started!");
// create a second serial interface for modbus
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
// my Renogy Wanderer has an (slave) address of 255! Not in docs???
// Do all Renogy charge controllers use this address?
int modbus_address = 255;
node.begin(modbus_address, Serial2);
}
void loop()
{
static uint32_t i;
i++;
// set word 0 of TX buffer to least-significant word of counter (bits 15..0)
node.setTransmitBuffer(0, lowWord(i));
// set word 1 of TX buffer to most-significant word of counter (bits 31..16)
node.setTransmitBuffer(1, highWord(i));
renogy_read_data_registers();
renogy_read_info_registers();
Serial.println("Battery voltage: " + String(renogy_data.battery_voltage));
Serial.println("Battery charge level: " + String(renogy_data.battery_soc) + "%");
Serial.println("Panel wattage: " + String(renogy_data.solar_panel_watts));
Serial.println("controller_temperatureF=" + String(renogy_data.controller_temperatureF));
Serial.println("battery_temperatureF=" + String(renogy_data.battery_temperatureF));
Serial.println("---");
// turn the load on for 10 seconds
//renogy_control_load(1)
//delay(10000);
//renogy_control_load(0)
delay(1000);
}
void renogy_read_data_registers()
{
uint8_t j, result;
uint16_t data_registers[num_data_registers];
char buffer1[40], buffer2[40];
uint8_t raw_data;
// prints data about each read to the console
bool print_data=0;
result = node.readHoldingRegisters(0x100, num_data_registers);
if (result == node.ku8MBSuccess)
{
if (print_data) Serial.println("Successfully read the data registers!");
renogy_data.controller_connected = true;
for (j = 0; j < num_data_registers; j++)
{
data_registers[j] = node.getResponseBuffer(j);
if (print_data) Serial.println(data_registers[j]);
}
renogy_data.battery_soc = data_registers[0];
renogy_data.battery_voltage = data_registers[1] * .1; // will it crash if data_registers[1] doesn't exist?
renogy_data.battery_charging_amps = data_registers[2] * .1;
renogy_data.battery_charging_watts = renogy_data.battery_voltage * renogy_data.battery_charging_amps;
//0x103 returns two bytes, one for battery and one for controller temp in c
uint16_t raw_data = data_registers[3]; // eg 5913
renogy_data.controller_temperature = raw_data/256;
renogy_data.battery_temperature = raw_data%256;
// for convenience, fahrenheit versions of the temperatures
renogy_data.controller_temperatureF = (renogy_data.controller_temperature * 1.8)+32;
renogy_data.battery_temperatureF = (renogy_data.battery_temperature * 1.8)+32;
renogy_data.load_voltage = data_registers[4] * .1;
renogy_data.load_amps = data_registers[5] * .01;
renogy_data.load_watts = data_registers[6];
renogy_data.solar_panel_voltage = data_registers[7] * .1;
renogy_data.solar_panel_amps = data_registers[8] * .01;
renogy_data.solar_panel_watts = data_registers[9];
//Register 0x10A - Turn on load, write register, unsupported in wanderer - 10
renogy_data.min_battery_voltage_today = data_registers[11] * .1;
renogy_data.max_battery_voltage_today = data_registers[12] * .1;
renogy_data.max_charging_amps_today = data_registers[13] * .01;
renogy_data.max_discharging_amps_today = data_registers[14] * .1;
renogy_data.max_charge_watts_today = data_registers[15];
renogy_data.max_discharge_watts_today = data_registers[16];
renogy_data.charge_amphours_today = data_registers[17];
renogy_data.discharge_amphours_today = data_registers[18];
renogy_data.charge_watthours_today = data_registers[19];
renogy_data.discharge_watthours_today = data_registers[20];
renogy_data.controller_uptime_days = data_registers[21];
renogy_data.total_battery_overcharges = data_registers[22];
renogy_data.total_battery_fullcharges = data_registers[23];
renogy_data.last_update_time = millis();
// Add these registers:
//Registers 0x118 to 0x119- Total Charging Amp-Hours - 24/25
//Registers 0x11A to 0x11B- Total Discharging Amp-Hours - 26/27
//Registers 0x11C to 0x11D- Total Cumulative power generation (kWH) - 28/29
//Registers 0x11E to 0x11F- Total Cumulative power consumption (kWH) - 30/31
//Register 0x120 - Load Status, Load Brightness, Charging State - 32
//Registers 0x121 to 0x122 - Controller fault codes - 33/34
if (print_data) Serial.println("---");
}
else
{
if (result == 0xE2)
{
Serial.println("Timed out reading the data registers!");
}
else
{
Serial.print("Failed to read the data registers... ");
Serial.println(result, HEX); // E2 is timeout
}
// Reset some values if we don't get a reading
renogy_data.controller_connected = false;
renogy_data.battery_voltage = 0;
renogy_data.battery_charging_amps = 0;
renogy_data.battery_soc = 0;
renogy_data.battery_charging_amps = 0;
renogy_data.controller_temperature = 0;
renogy_data.battery_temperature = 0;
renogy_data.solar_panel_amps = 0;
renogy_data.solar_panel_watts = 0;
renogy_data.battery_charging_watts = 0;
if (simulator_mode) {
renogy_data.battery_voltage = 13.99;
renogy_data.battery_soc = 55;
}
}
}
void renogy_read_info_registers()
{
uint8_t j, result;
uint16_t info_registers[num_info_registers];
char buffer1[40], buffer2[40];
uint8_t raw_data;
// prints data about the read to the console
bool print_data=0;
result = node.readHoldingRegisters(0x00A, num_info_registers);
if (result == node.ku8MBSuccess)
{
if (print_data) Serial.println("Successfully read the info registers!");
for (j = 0; j < num_info_registers; j++)
{
info_registers[j] = node.getResponseBuffer(j);
if (print_data) Serial.println(info_registers[j]);
}
// read and process each value
//Register 0x0A - Controller voltage and Current Rating - 0
// Not sure if this is correct. I get the correct amp rating for my Wanderer 30 (30 amps), but I get a voltage rating of 0 (should be 12v)
raw_data = info_registers[0];
renogy_info.voltage_rating = raw_data/256;
renogy_info.amp_rating = raw_data%256;
renogy_info.wattage_rating = renogy_info.voltage_rating * renogy_info.amp_rating;
//Serial.println("raw ratings = " + String(raw_data));
//Serial.println("Voltage rating: " + String(renogy_info.voltage_rating));
//Serial.println("amp rating: " + String(renogy_info.amp_rating));
//Register 0x0B - Controller discharge current and type - 1
raw_data = info_registers[1];
renogy_info.discharge_amp_rating = raw_data/256; // not sure if this should be /256 or /100
renogy_info.type = raw_data%256; // not sure if this should be /256 or /100
//Registers 0x0C to 0x13 - Product Model String - 2-9
// Here's how the nodeJS project handled this:
/*
let modelString = '';
for (let i = 0; i <= 7; i++) {
rawData[i+2].toString(16).match(/.{1,2}/g).forEach( x => {
modelString += String.fromCharCode(parseInt(x, 16));
});
}
this.controllerModel = modelString.replace(' ','');
*/
//Registers 0x014 to 0x015 - Software Version - 10-11
itoa(info_registers[10],buffer1,10);
itoa(info_registers[11],buffer2,10);
strcat(buffer1, buffer2); // should put a divider between the two strings?
strcpy(renogy_info.software_version, buffer1);
//Serial.println("Software version: " + String(renogy_info.software_version));
//Registers 0x016 to 0x017 - Hardware Version - 12-13
itoa(info_registers[12],buffer1,10);
itoa(info_registers[13],buffer2,10);
strcat(buffer1, buffer2); // should put a divider between the two strings?
strcpy(renogy_info.hardware_version, buffer1);
//Serial.println("Hardware version: " + String(renogy_info.hardware_version));
//Registers 0x018 to 0x019 - Product Serial Number - 14-15
// I don't think this is correct... Doesn't match serial number printed on my controller
itoa(info_registers[14],buffer1,10);
itoa(info_registers[15],buffer2,10);
strcat(buffer1, buffer2); // should put a divider between the two strings?
strcpy(renogy_info.serial_number, buffer1);
//Serial.println("Serial number: " + String(renogy_info.serial_number)); // (I don't think this is correct)
renogy_info.modbus_address = info_registers[16];
renogy_info.last_update_time = millis();
if (print_data) Serial.println("---");
}
else
{
if (result == 0xE2)
{
Serial.println("Timed out reading the info registers!");
}
else
{
Serial.print("Failed to read the info registers... ");
Serial.println(result, HEX); // E2 is timeout
}
// anything else to do if we fail to read the info reisters?
}
}
// control the load pins on Renogy charge controllers that have them
void renogy_control_load(bool state) {
if (state==1) node.writeSingleRegister(0x010A, 1); // turn on load
else node.writeSingleRegister(0x010A, 0); // turn off load
}