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, &ltime);
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