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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
| /* Arduino 'slimme meter' P1-port reader.
This sketch reads data from a Dutch smart meter that is equipped with a P1-port.
Connect 'RTS' from meter to Arduino pin 5V
Connect 'GND' from meter to Arduino GND
Connect 'RxD' from meter to Arduino pin 8 (RX)
Baudrate 115200, 8N1.
A BS170 transistor & 10k resistor, or an 7404 IC inverter is needed to make data readable if meter spits out inverted data
see: http://domoticx.com/p1-poort-slimme-meter-uitlezen-hardware/ for connection examples
I used the 7404 IC inverter, this requires no resistors.
The TM1638 7 segment display will have 8 "menu's". Switch between menu's using the buttons:
Menu 1: Show currently use electricity in Watt
Menu 2: Show used electricity today in Wh
Menu 3: Show used gas today in liters
Menu 4: Show seconds left for day counter
Menu 5: Show total low Tariff in kWh
Menu 6: Show total high Tariff in kWh
Menu 7: Show total gas in m3
Menu 8: Show days running (uptime)
If you do not have a TM1638 7 segment display you can disable the following methods in the loop():
- fillDayReport();
- buttonPress();
- displayMenu();
Modify the sendHTTPRequest() method to the required information to your needs.
created by 'The_FrankO' @ Tweakers.net, march 2016
Inspired by the sketch created by 'ThinkPad' @ Tweakers.net, september 2014
\[TOPIC=1601301]Hulp bij slimme meter uitlezen met Arduino naar MySQL\[/TOPIC]
*/
// Required for Ethernet
#include <Ethernet.h>
// Required for Display Module
#include <TM1638.h>
// Required for non-hardware Serial
// TODO: remove when using hardware Serial
#include <AltSoftSerial.h>
AltSoftSerial altSerial;
// For Ethernet
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress ip(192, 168, 2, 177); // IP Address for Arduino
IPAddress server(192, 168, 2, 7); // IP Address of server where to send data to
EthernetClient client; // Initialize the library instance:
// For the TM1638 Display
const byte dataPin = 7;
const byte clockPin = 6;
const byte strobePin = 9;
TM1638 module(dataPin, clockPin, strobePin);
// Data object containing the last telegram readout of the SmartMeter
struct Telegram {
unsigned int telegramCount; // How many telegrams received
String content; // The entire Telegram received from the Smart Meter
unsigned long elecLowTariff; // Meter reading Electrics - consumption low tariff in Wh
unsigned long elecHighTariff; // Meter reading Electrics - consumption high tariff in Wh
unsigned int elecConsumption; // Meter reading Electrics - actual consumed in W
unsigned long gasConsumption; // Meter reading Gas - consumed gas in liters
String gasLastReadOut; // Last Gas meter reading
boolean isElecHighTariff; // Meter reading Electrics - currently in low or high tariff
boolean isNew; // Will reset to false after once read, turn to true after new telegram
boolean isReady; // Will be set to true when entire telegram has been received
boolean isNewToDisplay; // Will reset to true after a new telegram has been decoded. Will be set to false by display
boolean isNewToSend; // Will reset to true after a new telegram has been decoded. Will be set to false by httprequest
};
Telegram telegram;
// Data object containg the report of the day, will reset after every day
struct DayReport {
unsigned int dayReportCount; // How many days
unsigned long millisInDayLeft; // millis left in this day report
unsigned long elecLowTariffBeginOfDay; // Meter reading Electrics - consumption low tariff in Wh
unsigned long elecHighTariffBeginOfDay; // Meter reading Electrics - consumption high tariff in Wh
unsigned long gasConsumptionBeginOfDay; // Meter reading Gas - consumed gas in liters
};
DayReport dayReport;
// For menu
byte menu = 1;
const unsigned long millisInDayTotal = 86400000; // total millis in one day
unsigned long previousMillis = 0;
void setup() {
// Set day countdown
dayReport.millisInDayLeft = millisInDayTotal;
// Start Serial to connect P1 Smart Meter
Serial.begin(9600);
altSerial.begin(115200);
// Disable the SD Card
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);
// Start the Ethernet connection
delay(1000);
Ethernet.begin(mac, ip);
// Set brightness to lowest for the display module
module.setupDisplay(true, 0);
}
void loop() {
receiveTelegram();
decodeTelegram();
fillDayReport();
buttonPress();
displayMenu();
sendHTTPRequest();
}
// Will fill the dayReport, if first day or new day will use data from the telegram
void fillDayReport() {
// No telegram received? no need to create the day report yet
if (telegram.telegramCount == 0) {
return;
}
unsigned long currentMillis = millis();
unsigned long progressedMillis = currentMillis - previousMillis;
previousMillis = currentMillis;
boolean isNewDay = dayReport.millisInDayLeft <= progressedMillis;
if (dayReport.dayReportCount == 0 || isNewDay) {
// first time here or new day?, take values from telegram
dayReport.dayReportCount++;
dayReport.millisInDayLeft = millisInDayTotal;
dayReport.elecLowTariffBeginOfDay = telegram.elecLowTariff;
dayReport.elecHighTariffBeginOfDay = telegram.elecHighTariff;
//dayReport.elecLowTariffReturnBeginOfDay = telegram.elecLowTariffReturn;
//dayReport.elecHighTariffReturnBeginOfDay = telegram.elecHighTariffReturn;
dayReport.gasConsumptionBeginOfDay = telegram.gasConsumption;
} else {
dayReport.millisInDayLeft = dayReport.millisInDayLeft - progressedMillis;
}
}
// Display the data, based on which menu
void displayMenu() {
// If no new data is to be displayed
// And we are not looking at menu 4 (contains countdown)
// TODO: staat uit omdat anders twee keer een waarde zetten op display niet goed gaat, blijft namelijk alleen de laatste over
// if (!telegram.isNewToDisplay && menu != 4) {
// // nothing to do here, so we are done
// return;
// }
// Turn LED 8 on/off based on tariff
module.setLED(telegram.isElecHighTariff, 7);
switch (menu) {
case 1: // Show currently use electricity in Watt
module.setDisplayToString(F("Cur"), 0b00100000);
module.setDisplayToDecNumber(telegram.elecConsumption, 0, false);
break;
case 2: // Show used electricity today in Wh
module.setDisplayToString(F("tot"), 0b00101010);
module.setDisplayToDecNumber(((telegram.elecLowTariff - dayReport.elecLowTariffBeginOfDay) + (telegram.elecHighTariff - dayReport.elecHighTariffBeginOfDay)), 0, false); // will show high and low together
break;
case 3: // Show used gas today in liters
module.setDisplayToString(F("Gas"));
module.setDisplayToDecNumber(telegram.gasConsumption - dayReport.gasConsumptionBeginOfDay, 0, false);
break;
case 4: // Show seconds left for day counter
module.setDisplayToDecNumber(dayReport.millisInDayLeft / 1000, 0, false);
break;
case 5: // Show total low Tariff in kWh
module.setDisplayToString(F("L"));
module.setDisplayToDecNumber(telegram.elecLowTariff, 0b10001000, false);
break;
case 6: // Show total high Tariff in kWh
module.setDisplayToString(F("H"));
module.setDisplayToDecNumber(telegram.elecHighTariff, 0b10001000, false);
break;
case 7: // Show total gas in m3
module.setDisplayToString(F("G"));
module.setDisplayToDecNumber(telegram.gasConsumption, 0b10001000, false);
break;
case 8: // Days running
module.setDisplayToDecNumber(dayReport.dayReportCount, 0, false);
break;
}
// Now we've send the data to the display, it no longer is new to us
telegram.isNewToDisplay = false;
}
char previousChar;
// Receive the entire telegram from the Smart Meter and store it in memory
void receiveTelegram() {
while (altSerial.available()) {
char inChar = (char) altSerial.read();
if (inChar == '/') {
telegram.isReady = false;
telegram.isNew = true;
telegram.content = "";
}
// Add char to the Telegram String
telegram.content += inChar;
// Check to see if end of telegram
if (inChar == '!' && previousChar == '\n') {
telegram.isReady = true;
// TODO: prints weggooien
Serial.println(F("\n\nData received!\n\n\n"));
Serial.println(telegram.content);
}
// Save the current char as previous char, will be used to better handle corrupt messages
previousChar = inChar;
}
}
// Decode the received telegram and extract the required information
int telegramStringSearchPosition;
void decodeTelegram() {
// If telegram is not complete yet, or if no new telegram has been reported...
if (!telegram.isReady || !telegram.isNew) {
// ... we will not try to decode it
return;
}
// Fill the telegram object
telegramStringSearchPosition = 0;
telegram.telegramCount++;
telegram.elecLowTariff = retrieveValue(F("1-0:1.8.1"), 10, 1);
telegram.elecHighTariff = retrieveValue(F("1-0:1.8.2"), 10, 1);
telegram.isElecHighTariff = retrieveValue(F("0-0:96.14.0"), 1, 4) - 1;
telegram.elecConsumption = retrieveValue(F("1-0:1.7.0"), 6, 1);
telegram.gasLastReadOut = retrieveStringValue(F("0-1:24.2.1"), 13, 1);
telegram.gasConsumption = retrieveValue(F("0-1:24.2.1"), 9, 16);
// We have decoded this telegram, so no longer new to us
telegram.isNew = false;
// But it is new to the display
telegram.isNewToDisplay = true;
// Also it is new so we can send it
telegram.isNewToSend = true;
// Clear the content to save memory
telegram.content = "";
}
// Convert to long value, kWh will be in Wh and m3 will be in liters.
long retrieveValue(String property, int valueLength, int valueOffset) {
float value = retrieveStringValue(property, valueLength, valueOffset).toFloat();
return (long) (value * 1000.0);
}
String retrieveStringValue(String property, int valueLength, int valueOffset) {
int propertyStartIndex = telegram.content.indexOf(property, telegramStringSearchPosition);
// Only when found we will do something to search the value
if (propertyStartIndex > 0) {
// Set this position, we know for the next property where we can start
telegramStringSearchPosition = propertyStartIndex;
// Extract the actual value and return it
int valueStartIndex = propertyStartIndex + property.length() + valueOffset;
return telegram.content.substring(valueStartIndex, valueStartIndex + valueLength);
}
return "";
}
void buttonPress() {
byte keys = module.getButtons();
// If a button is pressed, switch of all LED first and clear the data
if (keys > 0) {
module.setLEDs(0x00); // turn of all LEDs
module.clearDisplay(); // clear display
} else {
// If no button is pressed, we are done here
return;
}
switch (keys) {
case 0:
break;
case 1:
menu = 1;
break;
case 2:
menu = 2;
break;
case 4:
menu = 3;
break;
case 8:
menu = 4;
break;
case 16:
menu = 5;
break;
case 32:
menu = 6;
break;
case 64:
menu = 7;
break;
case 128:
menu = 8;
break;
default:
break;
}
module.setLED(TM1638_COLOR_RED, menu - 1);
// When the button is pressed, we have something new to display
telegram.isNewToDisplay = true;
}
// Will make HTTP Connection to the server and will send the data
void sendHTTPRequest() {
// Only send if telegram is new to us
if (!telegram.isNewToSend) {
// nothing to do here, so we are done
return;
}
// Close any connection before send a new request.
client.stop();
// If there is a successful connection:
if (client.connect(server, 80)) {
Serial.println(F("connecting..."));
// send the HTTP GET request:
client.print(F("GET /P1Reading.php?telegramCount="));
client.print(telegram.telegramCount);
client.print(F("&elecLowTariff="));
client.print(telegram.elecLowTariff);
client.print(F("&elecHighTariff="));
client.print(telegram.elecHighTariff);
client.print(F("&elecConsumption="));
client.print(telegram.elecConsumption);
client.print(F("&isElecHighTariff="));
client.print(telegram.isElecHighTariff);
client.print(F("&gasConsumption="));
client.print(telegram.gasConsumption);
client.print(F("&gasLastReadOut="));
client.print(telegram.gasLastReadOut);
client.println(F(" HTTP/1.1"));
client.println(F("Host: 192.168.2.4"));
client.println(F("User-Agent: arduino-ethernet"));
client.println(F("Connection: close"));
client.println();
} else {
// if you couldn't make a connection:
Serial.println(F("connection failed"));
}
// We've send it now, no need to send this again
telegram.isNewToSend = false;
} |