1 #include "stdafx.h"
2 #include "P1MeterBase.h"
3 #include "hardwaretypes.h"
4 #include "../main/SQLHelper.h"
5 #include "../main/localtime_r.h"
6 #include "../main/Logger.h"
7
8 #define CRC16_ARC 0x8005
9 #define CRC16_ARC_REFL 0xA001
10
11 enum class _eP1MatchType {
12 ID = 0,
13 EXCLMARK,
14 STD,
15 DEVTYPE,
16 GAS,
17 LINE17,
18 LINE18
19 };
20
21 #define P1SMID "/" // Smart Meter ID. Used to detect start of telegram.
22 #define P1VER "1-3:0.2.8" // P1 version
23 #define P1VERBE "0-0:96.1.4" // P1 version + e-MUCS version (Belgium)
24 #define P1TS "0-0:1.0.0" // Timestamp
25 #define P1PUSG "1-0:1.8." // total power usage (excluding tariff indicator)
26 #define P1PDLV "1-0:2.8." // total delivered power (excluding tariff indicator)
27 #define P1TIP "0-0:96.14.0" // tariff indicator power
28 #define P1PUC "1-0:1.7.0" // current power usage
29 #define P1PDC "1-0:2.7.0" // current power delivery
30 #define P1VOLTL1 "1-0:32.7.0" // voltage L1 (DSMRv5)
31 #define P1VOLTL2 "1-0:52.7.0" // voltage L2 (DSMRv5)
32 #define P1VOLTL3 "1-0:72.7.0" // voltage L3 (DSMRv5)
33 #define P1AMPEREL1 "1-0:31.7.0" // amperage L1 (DSMRv5)
34 #define P1AMPEREL2 "1-0:51.7.0" // amperage L2 (DSMRv5)
35 #define P1AMPEREL3 "1-0:71.7.0" // amperage L3 (DSMRv5)
36 #define P1POWUSL1 "1-0:21.7.0" // Power used L1 (DSMRv5)
37 #define P1POWUSL2 "1-0:41.7.0" // Power used L2 (DSMRv5)
38 #define P1POWUSL3 "1-0:61.7.0" // Power used L3 (DSMRv5)
39 #define P1POWDLL1 "1-0:22.7.0" // Power delivered L1 (DSMRv5)
40 #define P1POWDLL2 "1-0:42.7.0" // Power delivered L2 (DSMRv5)
41 #define P1POWDLL3 "1-0:62.7.0" // Power delivered L3 (DSMRv5)
42 #define P1GTS "0-n:24.3.0" // DSMR2 timestamp gas usage sample
43 #define P1GUDSMR2 "(" // DSMR2 gas usage sample
44 #define P1GUDSMR4 "0-n:24.2." // DSMR4 gas usage sample (excluding 'tariff' indicator)
45 #define P1MBTYPE "0-n:24.1.0" // M-Bus device type
46 #define P1EOT "!" // End of telegram.
47
48 enum _eP1Type {
49 P1TYPE_SMID = 0,
50 P1TYPE_END,
51 P1TYPE_VERSION,
52 P1TYPE_POWERUSAGE,
53 P1TYPE_POWERDELIV,
54 P1TYPE_USAGECURRENT,
55 P1TYPE_DELIVCURRENT,
56 P1TYPE_VOLTAGEL1,
57 P1TYPE_VOLTAGEL2,
58 P1TYPE_VOLTAGEL3,
59 P1TYPE_AMPERAGEL1,
60 P1TYPE_AMPERAGEL2,
61 P1TYPE_AMPERAGEL3,
62 P1TYPE_POWERUSEL1,
63 P1TYPE_POWERUSEL2,
64 P1TYPE_POWERUSEL3,
65 P1TYPE_POWERDELL1,
66 P1TYPE_POWERDELL2,
67 P1TYPE_POWERDELL3,
68 P1TYPE_MBUSDEVICETYPE,
69 P1TYPE_GASUSAGEDSMR4,
70 P1TYPE_GASTIMESTAMP,
71 P1TYPE_GASUSAGE
72 };
73
74 typedef struct {
75 _eP1MatchType matchtype;
76 _eP1Type type;
77 const char* key;
78 const char* topic;
79 int start;
80 int width;
81 } P1Match;
82
83 P1Match p1_matchlist[] = {
84 {_eP1MatchType::ID, P1TYPE_SMID, P1SMID, "", 0, 0},
85 {_eP1MatchType::EXCLMARK, P1TYPE_END, P1EOT, "", 0, 0},
86 {_eP1MatchType::STD, P1TYPE_VERSION, P1VER, "version", 10, 2},
87 {_eP1MatchType::STD, P1TYPE_VERSION, P1VERBE, "versionBE", 11, 5},
88 {_eP1MatchType::STD, P1TYPE_POWERUSAGE, P1PUSG, "powerusage", 10, 9},
89 {_eP1MatchType::STD, P1TYPE_POWERDELIV, P1PDLV, "powerdeliv", 10, 9},
90 {_eP1MatchType::STD, P1TYPE_USAGECURRENT, P1PUC, "powerusagec", 10, 7},
91 {_eP1MatchType::STD, P1TYPE_DELIVCURRENT, P1PDC, "powerdelivc", 10, 7},
92 {_eP1MatchType::STD, P1TYPE_VOLTAGEL1, P1VOLTL1, "voltagel1", 11, 5},
93 {_eP1MatchType::STD, P1TYPE_VOLTAGEL2, P1VOLTL2, "voltagel2", 11, 5},
94 {_eP1MatchType::STD, P1TYPE_VOLTAGEL3, P1VOLTL3, "voltagel3", 11, 5},
95 {_eP1MatchType::STD, P1TYPE_AMPERAGEL1, P1AMPEREL1, "amperagel1", 11, 3},
96 {_eP1MatchType::STD, P1TYPE_AMPERAGEL2, P1AMPEREL2, "amperagel2", 11, 3},
97 {_eP1MatchType::STD, P1TYPE_AMPERAGEL3, P1AMPEREL3, "amperagel3", 11, 3},
98 {_eP1MatchType::STD, P1TYPE_POWERUSEL1, P1POWUSL1, "powerusel1", 11, 6},
99 {_eP1MatchType::STD, P1TYPE_POWERUSEL2, P1POWUSL2, "powerusel2", 11, 6},
100 {_eP1MatchType::STD, P1TYPE_POWERUSEL3, P1POWUSL3, "powerusel3", 11, 6},
101 {_eP1MatchType::STD, P1TYPE_POWERDELL1, P1POWDLL1, "powerdell1", 11, 6},
102 {_eP1MatchType::STD, P1TYPE_POWERDELL2, P1POWDLL2, "powerdell2", 11, 6},
103 {_eP1MatchType::STD, P1TYPE_POWERDELL3, P1POWDLL3, "powerdell3", 11, 6},
104 {_eP1MatchType::DEVTYPE, P1TYPE_MBUSDEVICETYPE, P1MBTYPE, "mbusdevicetype", 11, 3},
105 {_eP1MatchType::GAS, P1TYPE_GASUSAGEDSMR4, P1GUDSMR4, "gasusage", 26, 8},
106 {_eP1MatchType::LINE17, P1TYPE_GASTIMESTAMP, P1GTS, "gastimestamp", 11, 12},
107 {_eP1MatchType::LINE18, P1TYPE_GASUSAGE, P1GUDSMR2, "gasusage", 1, 9}
108 }; // must keep DEVTYPE, GAS, LINE17 and LINE18 in this order at end of p1_matchlist
109
P1MeterBase(void)110 P1MeterBase::P1MeterBase(void)
111 {
112 m_bDisableCRC = true;
113 m_ratelimit = 0;
114 Init();
115 }
116
117
~P1MeterBase(void)118 P1MeterBase::~P1MeterBase(void)
119 {
120 }
121
Init()122 void P1MeterBase::Init()
123 {
124 m_p1version = 0;
125 m_linecount = 0;
126 m_exclmarkfound = 0;
127 m_CRfound = 0;
128 m_bufferpos = 0;
129 m_lastgasusage = 0;
130 m_lastSharedSendGas = 0;
131 m_lastUpdateTime = 0;
132
133 l_exclmarkfound = 0;
134 l_bufferpos = 0;
135
136 m_voltagel1 = -1;
137 m_voltagel2 = -1;
138 m_voltagel3 = -1;
139
140 m_bReceivedAmperage = false;
141 m_amperagel1 = 0;
142 m_amperagel2 = 0;
143 m_amperagel3 = 0;
144
145 m_powerusel1 = -1;
146 m_powerusel2 = -1;
147 m_powerusel3 = -1;
148
149 m_powerdell1 = -1;
150 m_powerdell2 = -1;
151 m_powerdell3 = -1;
152
153 memset(&m_buffer, 0, sizeof(m_buffer));
154 memset(&l_buffer, 0, sizeof(l_buffer));
155
156 memset(&m_power, 0, sizeof(m_power));
157 memset(&m_gas, 0, sizeof(m_gas));
158
159 m_power.len = sizeof(P1Power) - 1;
160 m_power.type = pTypeP1Power;
161 m_power.subtype = sTypeP1Power;
162 m_power.ID = 1;
163
164 m_gas.len = sizeof(P1Gas) - 1;
165 m_gas.type = pTypeP1Gas;
166 m_gas.subtype = sTypeP1Gas;
167 m_gas.ID = 1;
168 m_gas.gasusage = 0;
169
170 m_gasmbuschannel = 0;
171 m_gasprefix = "0-n";
172 m_gastimestamp = "";
173 m_gasclockskew = 0;
174 m_gasoktime = 0;
175
176 std::vector<std::vector<std::string> > result;
177 result = m_sql.safe_query("SELECT Value FROM UserVariables WHERE (Name='P1GasMeterChannel')");
178 if (!result.empty())
179 {
180 std::string s_gasmbuschannel = result[0][0];
181 if ((s_gasmbuschannel.length() == 1) && (s_gasmbuschannel[0] > 0x30) && (s_gasmbuschannel[0] < 0x35)) // value must be a single digit number between 1 and 4
182 {
183 m_gasmbuschannel = (char)s_gasmbuschannel[0];
184 m_gasprefix[2] = m_gasmbuschannel;
185 _log.Log(LOG_STATUS, "P1 Smart Meter: Gas meter M-Bus channel %c enforced by 'P1GasMeterChannel' user variable", m_gasmbuschannel);
186 }
187 }
188 }
189
MatchLine()190 bool P1MeterBase::MatchLine()
191 {
192 if ((strlen((const char*)& l_buffer) < 1) || (l_buffer[0] == 0x0a))
193 return true; //null value (startup)
194 uint8_t i;
195 uint8_t found = 0;
196 P1Match* t;
197 char value[20] = "";
198 std::string vString;
199
200 for (i = 0; (i < sizeof(p1_matchlist) / sizeof(P1Match)) & (!found); i++)
201 {
202 t = &p1_matchlist[i];
203 switch (t->matchtype)
204 {
205 case _eP1MatchType::ID:
206 // start of data
207 if (strncmp(t->key, (const char*)& l_buffer, strlen(t->key)) == 0)
208 {
209 m_linecount = 1;
210 found = 1;
211 }
212 continue; // we do not process anything else on this line
213 break;
214 case _eP1MatchType::EXCLMARK:
215 // end of data
216 if (strncmp(t->key, (const char*)& l_buffer, strlen(t->key)) == 0)
217 {
218 l_exclmarkfound = 1;
219 found = 1;
220 }
221 break;
222 case _eP1MatchType::STD:
223 if (strncmp(t->key, (const char*)& l_buffer, strlen(t->key)) == 0)
224 found = 1;
225 break;
226 case _eP1MatchType::DEVTYPE:
227 if (m_gasmbuschannel == 0)
228 {
229 vString = (const char*)t->key + 3;
230 if (strncmp(vString.c_str(), (const char*)& l_buffer + 3, strlen(t->key) - 3) == 0)
231 found = 1;
232 else
233 i += 100; // skip matches with any other gas lines - we need to find the M0-Bus channel first
234 }
235 break;
236 case _eP1MatchType::GAS:
237 if (strncmp((m_gasprefix + (t->key + 3)).c_str(), (const char*)& l_buffer, strlen(t->key)) == 0)
238 {
239 // verify that 'tariff' indicator is either 1 (Nld) or 3 (Bel)
240 if ((l_buffer[9] & 0xFD) == 0x31)
241 found = 1;
242 }
243 if (m_p1version >= 4)
244 i += 100; // skip matches with any DSMR v2 gas lines
245 break;
246 case _eP1MatchType::LINE17:
247 if (strncmp((m_gasprefix + (t->key + 3)).c_str(), (const char*)& l_buffer, strlen(t->key)) == 0)
248 {
249 m_linecount = 17;
250 found = 1;
251 }
252 break;
253 case _eP1MatchType::LINE18:
254 if ((m_linecount == 18) && (strncmp(t->key, (const char*)& l_buffer, strlen(t->key)) == 0))
255 found = 1;
256 break;
257 } //switch
258
259 if (!found)
260 continue;
261
262 if (l_exclmarkfound)
263 {
264 if (m_p1version == 0)
265 {
266 _log.Log(LOG_STATUS, "P1 Smart Meter: Meter is pre DSMR 4.0 - using DSMR 2.2 compatibility");
267 m_p1version = 2;
268 }
269 time_t atime = mytime(NULL);
270 if (difftime(atime, m_lastUpdateTime) >= m_ratelimit)
271 {
272 m_lastUpdateTime = atime;
273 sDecodeRXMessage(this, (const unsigned char*)& m_power, "Power", 255);
274 if (m_voltagel1 != -1) {
275 SendVoltageSensor(0, 1, 255, m_voltagel1, "Voltage L1");
276 }
277 if (m_voltagel2 != -1) {
278 SendVoltageSensor(0, 2, 255, m_voltagel2, "Voltage L2");
279 }
280 if (m_voltagel3 != -1) {
281 SendVoltageSensor(0, 3, 255, m_voltagel3, "Voltage L3");
282 }
283 /* The ampere is rounded to whole numbers and therefor not accurate enough
284 //we could calculate this ourselfs I=P/U I1=(m_power.powerusage1/m_voltagel1)
285 if (m_bReceivedAmperage) {
286 SendCurrentSensor(1, 255, m_amperagel1, m_amperagel2, m_amperagel3, "Amperage" );
287 }
288 */
289 if (m_powerusel1 != -1) {
290 SendWattMeter(0, 1, 255, m_powerusel1, "Usage L1");
291 }
292 if (m_powerusel2 != -1) {
293 SendWattMeter(0, 2, 255, m_powerusel2, "Usage L2");
294 }
295 if (m_powerusel3 != -1) {
296 SendWattMeter(0, 3, 255, m_powerusel3, "Usage L3");
297 }
298
299 if (m_powerdell1 != -1) {
300 SendWattMeter(0, 4, 255, m_powerdell1, "Delivery L1");
301 }
302 if (m_powerdell2 != -1) {
303 SendWattMeter(0, 5, 255, m_powerdell2, "Delivery L2");
304 }
305 if (m_powerdell3 != -1) {
306 SendWattMeter(0, 6, 255, m_powerdell3, "Delivery L3");
307 }
308
309 if ((m_gas.gasusage > 0) && ((m_gas.gasusage != m_lastgasusage) || (difftime(atime, m_lastSharedSendGas) >= 300)))
310 {
311 //only update gas when there is a new value, or 5 minutes are passed
312 if (m_gasclockskew >= 300)
313 {
314 // just accept it - we cannot sync to our clock
315 m_lastSharedSendGas = atime;
316 m_lastgasusage = m_gas.gasusage;
317 sDecodeRXMessage(this, (const unsigned char*)& m_gas, "Gas", 255);
318 }
319 else if (atime >= m_gasoktime)
320 {
321 struct tm ltime;
322 localtime_r(&atime, <ime);
323 char myts[80];
324 sprintf(myts, "%02d%02d%02d%02d%02d%02dW", ltime.tm_year % 100, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec);
325 if (ltime.tm_isdst)
326 myts[12] = 'S';
327 if ((m_gastimestamp.length() > 13) || (strncmp((const char*)& myts, m_gastimestamp.c_str(), m_gastimestamp.length()) >= 0))
328 {
329 m_lastSharedSendGas = atime;
330 m_lastgasusage = m_gas.gasusage;
331 m_gasoktime += 300;
332 sDecodeRXMessage(this, (const unsigned char*)& m_gas, "Gas", 255);
333 }
334 else // gas clock is ahead
335 {
336 struct tm gastm;
337 gastm.tm_year = atoi(m_gastimestamp.substr(0, 2).c_str()) + 100;
338 gastm.tm_mon = atoi(m_gastimestamp.substr(2, 2).c_str()) - 1;
339 gastm.tm_mday = atoi(m_gastimestamp.substr(4, 2).c_str());
340 gastm.tm_hour = atoi(m_gastimestamp.substr(6, 2).c_str());
341 gastm.tm_min = atoi(m_gastimestamp.substr(8, 2).c_str());
342 gastm.tm_sec = atoi(m_gastimestamp.substr(10, 2).c_str());
343 if (m_gastimestamp.length() == 12)
344 gastm.tm_isdst = -1;
345 else if (m_gastimestamp[12] == 'W')
346 gastm.tm_isdst = 0;
347 else
348 gastm.tm_isdst = 1;
349
350 time_t gtime = mktime(&gastm);
351 m_gasclockskew = difftime(gtime, atime);
352 if (m_gasclockskew >= 300)
353 {
354 _log.Log(LOG_ERROR, "P1 Smart Meter: Unable to synchronize to the gas meter clock because it is more than 5 minutes ahead of my time");
355 }
356 else {
357 m_gasoktime = gtime;
358 _log.Log(LOG_STATUS, "P1 Smart Meter: Gas meter clock is %i seconds ahead - wait for my clock to catch up", (int)m_gasclockskew);
359 }
360 }
361 }
362 }
363 }
364 m_linecount = 0;
365 l_exclmarkfound = 0;
366 }
367 else
368 {
369 vString = (const char*)& l_buffer + t->start;
370 size_t ePos = t->width;
371 ePos = vString.find_first_of("*)");
372
373 if (ePos == std::string::npos)
374 {
375 // invalid message: value not delimited
376 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - value is not delimited in line \"%s\"", l_buffer);
377 return false;
378 }
379
380 if (ePos > 19)
381 {
382 // invalid message: line too long
383 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - value in line \"%s\" is oversized", l_buffer);
384 return false;
385 }
386
387 if (ePos > 0)
388 {
389 strcpy(value, vString.substr(0, ePos).c_str());
390 #ifdef _DEBUG
391 _log.Log(LOG_NORM, "P1 Smart Meter: Key: %s, Value: %s", t->topic, value);
392 #endif
393 }
394
395 unsigned long temp_usage = 0;
396 float temp_volt = 0;
397 float temp_ampere = 0;
398 float temp_power = 0;
399 char* validate = value + ePos;
400
401 switch (t->type)
402 {
403 case P1TYPE_VERSION:
404 if (m_p1version == 0)
405 {
406 m_p1version = value[0] - 0x30;
407 char szVersion[12];
408 if (t->width == 5)
409 {
410 // Belgian meter
411 sprintf(szVersion, "ESMR %c.%c.%c", value[0], value[1], value[2]);
412 }
413 else // if (t->width == 2)
414 {
415 // Dutch meter
416 sprintf(szVersion, "ESMR %c.%c", value[0], value[1]);
417 if (m_p1version < 5)
418 szVersion[0] = 'D';
419 }
420 _log.Log(LOG_STATUS, "P1 Smart Meter: Meter reports as %s", szVersion);
421 }
422 break;
423 case P1TYPE_MBUSDEVICETYPE:
424 temp_usage = (unsigned long)(strtod(value, &validate));
425 if (temp_usage == 3)
426 {
427 m_gasmbuschannel = (char)l_buffer[2];
428 m_gasprefix[2] = m_gasmbuschannel;
429 _log.Log(LOG_STATUS, "P1 Smart Meter: Found gas meter on M-Bus channel %c", m_gasmbuschannel);
430 }
431 break;
432 case P1TYPE_POWERUSAGE:
433 temp_usage = (unsigned long)(strtod(value, &validate) * 1000.0f);
434 if ((l_buffer[8] & 0xFE) == 0x30)
435 {
436 // map tariff IDs 0 (Lux) and 1 (Bel, Nld) both to powerusage1
437 if (!m_power.powerusage1 || m_p1version >= 4)
438 m_power.powerusage1 = temp_usage;
439 else if (temp_usage - m_power.powerusage1 < 10000)
440 m_power.powerusage1 = temp_usage;
441 }
442 else if (l_buffer[8] == 0x32)
443 {
444 if (!m_power.powerusage2 || m_p1version >= 4)
445 m_power.powerusage2 = temp_usage;
446 else if (temp_usage - m_power.powerusage2 < 10000)
447 m_power.powerusage2 = temp_usage;
448 }
449 break;
450 case P1TYPE_POWERDELIV:
451 temp_usage = (unsigned long)(strtod(value, &validate) * 1000.0f);
452 if ((l_buffer[8] & 0xFE) == 0x30)
453 {
454 // map tariff IDs 0 (Lux) and 1 (Bel, Nld) both to powerdeliv1
455 if (!m_power.powerdeliv1 || m_p1version >= 4)
456 m_power.powerdeliv1 = temp_usage;
457 else if (temp_usage - m_power.powerdeliv1 < 10000)
458 m_power.powerdeliv1 = temp_usage;
459 }
460 else if (l_buffer[8] == 0x32)
461 {
462 if (!m_power.powerdeliv2 || m_p1version >= 4)
463 m_power.powerdeliv2 = temp_usage;
464 else if (temp_usage - m_power.powerdeliv2 < 10000)
465 m_power.powerdeliv2 = temp_usage;
466 }
467 break;
468 case P1TYPE_USAGECURRENT:
469 temp_usage = (unsigned long)(strtod(value, &validate) * 1000.0f); //Watt
470 if (temp_usage < 17250)
471 m_power.usagecurrent = temp_usage;
472 break;
473 case P1TYPE_DELIVCURRENT:
474 temp_usage = (unsigned long)(strtod(value, &validate) * 1000.0f); //Watt;
475 if (temp_usage < 17250)
476 m_power.delivcurrent = temp_usage;
477 break;
478 case P1TYPE_VOLTAGEL1:
479 temp_volt = strtof(value, &validate);
480 if (temp_volt < 300)
481 m_voltagel1 = temp_volt; //Voltage L1;
482 break;
483 case P1TYPE_VOLTAGEL2:
484 temp_volt = strtof(value, &validate);
485 if (temp_volt < 300)
486 m_voltagel2 = temp_volt; //Voltage L2;
487 break;
488 case P1TYPE_VOLTAGEL3:
489 temp_volt = strtof(value, &validate);
490 if (temp_volt < 300)
491 m_voltagel3 = temp_volt; //Voltage L3;
492 break;
493 case P1TYPE_AMPERAGEL1:
494 temp_ampere = strtof(value, &validate);
495 if (temp_ampere < 100)
496 {
497 m_amperagel1 = temp_ampere; //Amperage L1;
498 m_bReceivedAmperage = true;
499 }
500 break;
501 case P1TYPE_AMPERAGEL2:
502 temp_ampere = strtof(value, &validate);
503 if (temp_ampere < 100)
504 {
505 m_amperagel2 = temp_ampere; //Amperage L2;
506 m_bReceivedAmperage = true;
507 }
508 break;
509 case P1TYPE_AMPERAGEL3:
510 temp_ampere = strtof(value, &validate);
511 if (temp_ampere < 100)
512 {
513 m_amperagel3 = temp_ampere; //Amperage L3;
514 m_bReceivedAmperage = true;
515 }
516 break;
517 case P1TYPE_POWERUSEL1:
518 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
519 if (temp_power < 10000)
520 m_powerusel1 = temp_power; //Power Used L1;
521 break;
522 case P1TYPE_POWERUSEL2:
523 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
524 if (temp_power < 10000)
525 m_powerusel2 = temp_power; //Power Used L2;
526 break;
527 case P1TYPE_POWERUSEL3:
528 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
529 if (temp_power < 10000)
530 m_powerusel3 = temp_power; //Power Used L3;
531 break;
532 case P1TYPE_POWERDELL1:
533 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
534 if (temp_power < 10000)
535 m_powerdell1 = temp_power; //Power Used L1;
536 break;
537 case P1TYPE_POWERDELL2:
538 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
539 if (temp_power < 10000)
540 m_powerdell2 = temp_power; //Power Used L2;
541 break;
542 case P1TYPE_POWERDELL3:
543 temp_power = static_cast<float>(strtod(value, &validate) * 1000.0f);
544 if (temp_power < 10000)
545 m_powerdell3 = temp_power; //Power Used L3;
546 break;
547 case P1TYPE_GASTIMESTAMP:
548 m_gastimestamp = std::string(value);
549 break;
550 case P1TYPE_GASUSAGE:
551 case P1TYPE_GASUSAGEDSMR4:
552 temp_usage = (unsigned long)(strtod(value, &validate) * 1000.0f);
553 if (!m_gas.gasusage || m_p1version >= 4)
554 m_gas.gasusage = temp_usage;
555 else if (temp_usage - m_gas.gasusage < 20000)
556 m_gas.gasusage = temp_usage;
557 break;
558 }
559
560 if (ePos > 0 && ((validate - value) != ePos))
561 {
562 // invalid message: value is not a number
563 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - value in line \"%s\" is not a number", l_buffer);
564 return false;
565 }
566
567 if (t->type == P1TYPE_GASUSAGEDSMR4)
568 {
569 // need to get timestamp from this line as well
570 vString = (const char*)& l_buffer + 11;
571 m_gastimestamp = vString.substr(0, 13);
572 #ifdef _DEBUG
573 _log.Log(LOG_NORM, "P1 Smart Meter: Key: gastimestamp, Value: %s", m_gastimestamp.c_str());
574 #endif
575 }
576 }
577 }
578 return true;
579 }
580
581
582 /*
583 / GB3: DSMR 4.0 defines a CRC checksum at the end of the message, calculated from
584 / and including the message starting character '/' upto and including the message
585 / end character '!'. According to the specs the CRC is a 16bit checksum using the
586 / polynomial x^16 + x^15 + x^2 + 1, however input/output are reflected.
587 */
588
CheckCRC()589 bool P1MeterBase::CheckCRC()
590 {
591 // sanity checks
592 if (l_buffer[1] == 0)
593 {
594 if (m_p1version == 0)
595 {
596 _log.Log(LOG_STATUS, "P1 Smart Meter: Meter is pre DSMR 4.0 and does not send a CRC checksum - using DSMR 2.2 compatibility");
597 m_p1version = 2;
598 }
599 // always return true with pre DSMRv4 format message
600 return true;
601 }
602
603 if (l_buffer[5] != 0)
604 {
605 // trailing characters after CRC
606 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - CRC value in message has trailing characters");
607 return false;
608 }
609
610 if (!m_CRfound)
611 {
612 _log.Log(LOG_NORM, "P1 Smart Meter: You appear to have middleware that changes the message content - skipping CRC validation");
613 return true;
614 }
615
616 // retrieve CRC from the current line
617 char crc_str[5];
618 strncpy(crc_str, (const char*)& l_buffer + 1, 4);
619 crc_str[4] = 0;
620 uint16_t m_crc16 = (uint16_t)strtoul(crc_str, NULL, 16);
621
622 // calculate CRC
623 const unsigned char* c_buffer = m_buffer;
624 uint8_t i;
625 uint16_t crc = 0;
626 int m_size = m_bufferpos;
627 while (m_size--)
628 {
629 crc = crc ^ *c_buffer++;
630 for (i = 0; i < 8; i++)
631 {
632 if ((crc & 0x0001))
633 {
634 crc = (crc >> 1) ^ CRC16_ARC_REFL;
635 }
636 else {
637 crc = crc >> 1;
638 }
639 }
640 }
641 if (crc != m_crc16)
642 {
643 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - CRC failed");
644 }
645 return (crc == m_crc16);
646 }
647
648
649 /*
650 / GB3: ParseP1Data() can be called with either a complete message (P1MeterTCP) or individual
651 / lines (P1MeterSerial).
652 /
653 / While it is technically possible to do a CRC check line by line, we like to keep
654 / things organized and assemble the complete message before running that check. If the
655 / message is DSMR 4.0+ of course.
656 /
657 / Because older DSMR standard does not contain a CRC we still need the validation rules
658 / in Matchline(). In fact, one of them is essential for keeping Domoticz from crashing
659 / in specific cases of bad data. In essence this means that a CRC check will only be
660 / done if the message passes all other validation rules
661 */
662
ParseP1Data(const unsigned char * pData,const int Len,const bool disable_crc,int ratelimit)663 void P1MeterBase::ParseP1Data(const unsigned char* pData, const int Len, const bool disable_crc, int ratelimit)
664 {
665 int ii = 0;
666 m_ratelimit = ratelimit;
667 // a new message should not start with an empty line, but just in case it does (crude check is sufficient here)
668 while ((m_linecount == 0) && (pData[ii] < 0x10))
669 {
670 ii++;
671 }
672
673 // re enable reading pData when a new message starts, empty buffers
674 if (pData[ii] == 0x2f)
675 {
676 if ((l_buffer[0] == 0x21) && !l_exclmarkfound && (m_linecount > 0))
677 {
678 _log.Log(LOG_STATUS, "P1 Smart Meter: WARNING: got new message but buffer still contains unprocessed data from previous message.");
679 l_buffer[l_bufferpos] = 0;
680 if (disable_crc || CheckCRC())
681 {
682 MatchLine();
683 }
684 }
685 m_linecount = 1;
686 l_bufferpos = 0;
687 m_bufferpos = 0;
688 m_exclmarkfound = 0;
689 }
690
691 // assemble complete message in message buffer
692 while ((ii < Len) && (m_linecount > 0) && (!m_exclmarkfound) && (m_bufferpos < sizeof(m_buffer)))
693 {
694 const unsigned char c = pData[ii];
695 m_buffer[m_bufferpos] = c;
696 m_bufferpos++;
697 if (c == 0x21)
698 {
699 // stop reading at exclamation mark (do not include CRC)
700 ii = Len;
701 m_exclmarkfound = 1;
702 }
703 else {
704 ii++;
705 }
706 }
707
708 if (m_bufferpos == sizeof(m_buffer))
709 {
710 // discard oversized message
711 if ((Len > 400) || (pData[0] == 0x21))
712 {
713 // 400 is an arbitrary chosen number to differentiate between full messages and single line commits
714 _log.Log(LOG_NORM, "P1 Smart Meter: Dismiss incoming - message oversized");
715 }
716 m_linecount = 0;
717 return;
718 }
719
720 // read pData, ignore/stop if there is a message validation failure
721 ii = 0;
722 while ((ii < Len) && (m_linecount > 0))
723 {
724 const unsigned char c = pData[ii];
725 ii++;
726 if (c == 0x0d)
727 {
728 m_CRfound = 1;
729 continue;
730 }
731
732 if (c == 0x0a)
733 {
734 // close string, parse line and clear it.
735 m_linecount++;
736 if ((l_bufferpos > 0) && (l_bufferpos < sizeof(l_buffer)))
737 {
738 // don't try to match empty or oversized lines
739 l_buffer[l_bufferpos] = 0;
740 if (l_buffer[0] == 0x21 && !disable_crc)
741 {
742 if (!CheckCRC())
743 {
744 m_linecount = 0;
745 return;
746 }
747 }
748 if (!MatchLine())
749 {
750 // discard message
751 m_linecount = 0;
752 }
753 }
754 l_bufferpos = 0;
755 }
756 else if (l_bufferpos < sizeof(l_buffer))
757 {
758 l_buffer[l_bufferpos] = c;
759 l_bufferpos++;
760 }
761 }
762 }
763