Skip to content

Commit 20bcd21

Browse files
committed
Implement the rest of the IZAR protocol to read all the data contained in the frame.
Improves the whole code a bit in the process.
1 parent e4317ff commit 20bcd21

File tree

9 files changed

+1735
-623
lines changed

9 files changed

+1735
-623
lines changed

Diff for: ST-STEVAL-FKI868V1/EWARM/ST-STEVAL-FKI868V1.dep

+547-548
Large diffs are not rendered by default.

Diff for: ST-STEVAL-FKI868V1/EWARM/settings/IZAR-PRIOS-ST-STEVAL-FKI868V1.wsdt

+149-30
Large diffs are not rendered by default.

Diff for: ST-STEVAL-FKI868V1/EWARM/settings/ST-STEVAL-FKI868V1.dbgdt

+873-2
Large diffs are not rendered by default.

Diff for: ST-STEVAL-FKI868V1/EWARM/settings/ST-STEVAL-FKI868V1.dnx

+13-13
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
<StLinkDriver>
1515
<stlinkserialNo>0670FF495355878281223610</stlinkserialNo>
1616
<stlinkfoundProbes />
17-
<stlinkResetStyle>4</stlinkResetStyle>
18-
<stlinkResetStrategy>2</stlinkResetStrategy>
1917
<CStepIntDis>_ 0</CStepIntDis>
2018
<LeaveTargetRunning>_ 0</LeaveTargetRunning>
19+
<stlinkResetStyle>0</stlinkResetStyle>
20+
<stlinkResetStrategy>2</stlinkResetStrategy>
2121
</StLinkDriver>
2222
<DebugChecksum>
23-
<Checksum>2347194289</Checksum>
23+
<Checksum>2385112967</Checksum>
2424
</DebugChecksum>
2525
<Exceptions>
2626
<StopOnUncaught>_ 0</StopOnUncaught>
@@ -36,6 +36,12 @@
3636
<Fmt0>{W}1:A_Id 4 0</Fmt0>
3737
<Fmt1>{W}1:cRxData 3 0</Fmt1>
3838
</watch_formats>
39+
<DisassembleMode>
40+
<mode>0</mode>
41+
</DisassembleMode>
42+
<Breakpoints2>
43+
<Count>0</Count>
44+
</Breakpoints2>
3945
<TermIOLog>
4046
<LoggingEnabled>_ 0</LoggingEnabled>
4147
<LogFile>_ ""</LogFile>
@@ -45,20 +51,14 @@
4551
<LogFile>_ ""</LogFile>
4652
<Category>_ 0</Category>
4753
</LogFile>
54+
<Aliases>
55+
<Count>0</Count>
56+
<SuppressDialog>0</SuppressDialog>
57+
</Aliases>
4858
<CallStackLog>
4959
<Enabled>0</Enabled>
5060
</CallStackLog>
5161
<CallStackStripe>
5262
<ShowTiming>1</ShowTiming>
5363
</CallStackStripe>
54-
<DisassembleMode>
55-
<mode>0</mode>
56-
</DisassembleMode>
57-
<Breakpoints2>
58-
<Count>0</Count>
59-
</Breakpoints2>
60-
<Aliases>
61-
<Count>0</Count>
62-
<SuppressDialog>0</SuppressDialog>
63-
</Aliases>
6464
</settings>

Diff for: ST-STEVAL-FKI868V1/Inc/PRIOS.h

+37-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,43 @@
22
#define __PRIOS_H
33

44
#include <stdint.h>
5+
#include <stdbool.h>
56

6-
uint8_t decodePRIOSPayload(uint8_t *in, uint8_t payload_len, uint32_t key, uint8_t *out);
7-
uint8_t getMetricsFromPRIOSWMBusFrame(uint8_t *frame, uint32_t *total_consumption, uint32_t *last_month_total_consumption);
7+
/** Contains all the booleans required to store the alarms of a PRIOS device. */
8+
typedef struct _izar_alarms {
9+
bool general_alarm;
10+
bool leakage_currently;
11+
bool leakage_previously;
12+
bool meter_blocked;
13+
bool back_flow;
14+
bool underflow;
15+
bool overflow;
16+
bool submarine;
17+
bool sensor_fraud_currently;
18+
bool sensor_fraud_previously;
19+
bool mechanical_fraud_currently;
20+
bool mechanical_fraud_previously;
21+
} izar_alarms;
22+
23+
/** Describe the various unit types we can encounter */
24+
typedef enum unit_type {UNKNOWN_UNIT, VOLUME_CUBIC_METER} unit_type_t;
25+
26+
/** Can store everything a PRIOS frame can offer */
27+
typedef struct _izar_reading {
28+
izar_alarms alarms;
29+
uint8_t random_generator;
30+
uint8_t radio_interval;
31+
float remaining_battery_life;
32+
unit_type_t unit_type;
33+
float current_reading;
34+
float h0_reading;
35+
uint16_t h0_year;
36+
uint8_t h0_month;
37+
uint8_t h0_day;
38+
} izar_reading;
39+
40+
void printIZARReadingAsCSV(const uint32_t A_Id, const izar_reading * const reading);
41+
uint8_t decodePRIOSPayload(const uint8_t * const in, const uint8_t payload_len, const uint32_t key, uint8_t *out);
42+
uint8_t getMetricsFromPRIOSWMBusFrame(const uint8_t * const frame, izar_reading * const reading);
843

944
#endif

Diff for: ST-STEVAL-FKI868V1/Inc/WMBus.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#ifndef __WMBUS_H
22
#define __WMBUS_H
33

4-
uint8_t CheckWMBusFrame(uint8_t *frame, uint8_t len, uint8_t *LField, uint8_t *CField, uint16_t *Manufacturer, uint32_t *A_Id, uint8_t *A_Ver, uint8_t *A_Type);
4+
uint8_t CheckWMBusFrame(const uint8_t * const frame, const uint8_t len, uint8_t * const LField, uint8_t * const CField, uint16_t * const Manufacturer, uint32_t * const A_Id, uint8_t * const A_Ver, uint8_t * const A_Type);
55

66
#endif

Diff for: ST-STEVAL-FKI868V1/Src/PRIOS.c

+109-21
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@
55
* @author : Erwan Martin <[email protected]>
66
******************************************************************************
77
*/
8+
#include <math.h>
9+
#include <stdio.h>
10+
811
#include "PRIOS.h"
912

13+
const char * const unit_displays[] = {
14+
[UNKNOWN_UNIT] = "banana",
15+
[VOLUME_CUBIC_METER] = "m3",
16+
};
17+
1018
/* The default encryption keys of IZAR devices */
1119
uint8_t PRIOS_DEFAULT_KEY1[8] = {0x39, 0xBC, 0x8A, 0x10, 0xE6, 0x6D, 0x83, 0xF8};
1220
uint8_t PRIOS_DEFAULT_KEY2[8] = {0x51, 0x72, 0x89, 0x10, 0xE6, 0x6D, 0x83, 0xF8};
@@ -18,7 +26,7 @@ uint8_t PRIOS_DEFAULT_KEY2[8] = {0x51, 0x72, 0x89, 0x10, 0xE6, 0x6D, 0x83, 0xF8}
1826
* @param int offset Offset from the start of the location.
1927
* @retval uint32_t
2028
*/
21-
uint32_t read_uint32_le(uint8_t *data, int offset) {
29+
uint32_t read_uint32_le(const uint8_t * const data, int offset) {
2230
uint32_t result = *(data + offset + 3) << 24;
2331
result |= *(data + offset + 2) << 16;
2432
result |= *(data + offset + 1) << 8;
@@ -33,7 +41,7 @@ uint32_t read_uint32_le(uint8_t *data, int offset) {
3341
* @param int offset Offset from the start of the location.
3442
* @retval uint32_t
3543
*/
36-
uint32_t read_uint32_be(uint8_t *data, int offset) {
44+
uint32_t read_uint32_be(const uint8_t * const data, int offset) {
3745
uint32_t result = *(data + offset) << 24;
3846
result |= *(data + offset + 1) << 16;
3947
result |= *(data + offset + 2) << 8;
@@ -47,7 +55,7 @@ uint32_t read_uint32_be(uint8_t *data, int offset) {
4755
* @param uint8_t *bytes Location of where to extract the key from.
4856
* @retval uint32_t
4957
*/
50-
uint32_t preparePRIOSKey(uint8_t *bytes) {
58+
uint32_t preparePRIOSKey(const uint8_t * const bytes) {
5159
uint32_t key1 = read_uint32_be(bytes, 0);
5260
uint32_t key2 = read_uint32_be(bytes, 4);
5361
uint32_t key = key1 ^ key2;
@@ -68,7 +76,7 @@ uint32_t preparePRIOSKey(uint8_t *bytes) {
6876
* @param uint8_t *out Buffer to store the decoded data
6977
* @retval uint8_t
7078
*/
71-
uint8_t decodePRIOSPayload(uint8_t *in, uint8_t payload_len, uint32_t key, uint8_t *out) {
79+
uint8_t decodePRIOSPayload(const uint8_t * const in, const uint8_t payload_len, uint32_t key, uint8_t *out) {
7280
// modify seed key with header values
7381
key ^= read_uint32_be(in, 2); // manufacturer + address[0-1]
7482
key ^= read_uint32_be(in, 6); // address[2-3] + version + type
@@ -95,27 +103,105 @@ uint8_t decodePRIOSPayload(uint8_t *in, uint8_t payload_len, uint32_t key, uint8
95103
}
96104

97105
/**
98-
* @brief Get the water metrics from a decoded PRIOS payload.
99-
* @param uint8_t *payload The location of the decoded PRIOS payload
100-
* @param uint32_t *total_consumption Where to store the current total
101-
consumption
102-
* @param uint32_t *last_month_total_consumption Where to store last month's
103-
consumption
104-
*/
105-
void getMetricsFromPriosPayload(uint8_t *payload, uint32_t *total_consumption, uint32_t *last_month_total_consumption) {
106-
*total_consumption = read_uint32_le(payload, 1);
107-
*last_month_total_consumption = read_uint32_le(payload, 5);
106+
* @brief Extract the data from a PRIOS frame.
107+
* @param uint8_t *header_data The start of the wmbus frame application data.
108+
* @param uint8_t *decoded_data The data that was decrypted from the frame.
109+
* @param izar_reading *reading Where to store the extracted data.
110+
*/
111+
void parsePRIOSFrame(const uint8_t * const header_data, const uint8_t * const decoded_data, izar_reading * const reading) {
112+
// Extract the header values:
113+
reading->radio_interval = 1 << ((header_data[0] & 0x0F) + 2);
114+
reading->random_generator = (header_data[0] >> 4) & 0x3;
115+
116+
// Extract the battery remaining life:
117+
reading->remaining_battery_life = (header_data[1] & 0x1F) / 2.0;
118+
119+
// Read the alarms:
120+
reading->alarms.general_alarm = header_data[0] >> 7;
121+
reading->alarms.leakage_currently = header_data[1] >> 7;
122+
reading->alarms.leakage_previously = header_data[1] >> 6 & 0x1;
123+
reading->alarms.meter_blocked = header_data[1] >> 5 & 0x1;
124+
reading->alarms.back_flow = header_data[2] >> 7;
125+
reading->alarms.underflow = header_data[2] >> 6 & 0x1;
126+
reading->alarms.overflow = header_data[2] >> 5 & 0x1;
127+
reading->alarms.submarine = header_data[2] >> 4 & 0x1;
128+
reading->alarms.sensor_fraud_currently = header_data[2] >> 3 & 0x1;
129+
reading->alarms.sensor_fraud_previously = header_data[2] >> 2 & 0x1;
130+
reading->alarms.mechanical_fraud_currently = header_data[2] >> 1 & 0x1;
131+
reading->alarms.mechanical_fraud_previously = header_data[2] & 0x1;
132+
133+
// Read the readings:
134+
reading->current_reading = read_uint32_le(decoded_data, 1);
135+
reading->h0_reading = read_uint32_le(decoded_data, 5);
136+
137+
// Apply the multiplier to the values:
138+
int8_t multiplier_exponent = (header_data[3] & 0x07) - 6;
139+
if (multiplier_exponent > 0) {
140+
reading->current_reading *= pow(10, multiplier_exponent);
141+
reading->h0_reading *= pow(10, multiplier_exponent);
142+
} else if (multiplier_exponent < 0) {
143+
reading->current_reading /= pow(10, -multiplier_exponent);
144+
reading->h0_reading /= pow(10, -multiplier_exponent);
145+
}
146+
147+
// Extract the measurement unit:
148+
uint8_t unit_type = header_data[3] >> 3;
149+
if (unit_type == 0x02) {
150+
reading->unit_type = VOLUME_CUBIC_METER;
151+
} else {
152+
reading->unit_type = UNKNOWN_UNIT;
153+
}
154+
155+
// Extract the date:
156+
reading->h0_year = ((decoded_data[10] & 0xF0) >> 1) + ((decoded_data[9] & 0xE0) >> 5);
157+
if (reading->h0_year > 80) {
158+
reading->h0_year += 1900;
159+
} else {
160+
reading->h0_year += 2000;
161+
}
162+
reading->h0_month = decoded_data[10] & 0xF;
163+
reading->h0_day = decoded_data[9] & 0x1F;
164+
}
165+
166+
/**
167+
* @brief Print an entire IZAR reading to the standard output device, as CSV data.
168+
* @param uint32_t A_Id The identifier of the device the reading was from.
169+
* @param izar_reading *reading The reading to print
170+
*/
171+
void printIZARReadingAsCSV(const uint32_t A_Id, const izar_reading * const reading) {
172+
printf(
173+
"%.6x,%f,%f,%s,%.2d,%.2d,%.2d,%.1f,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\r\n",
174+
A_Id,
175+
reading->current_reading,
176+
reading->h0_reading,
177+
unit_displays[reading->unit_type],
178+
reading->h0_year,
179+
reading->h0_month,
180+
reading->h0_day,
181+
reading->remaining_battery_life,
182+
reading->radio_interval,
183+
reading->random_generator,
184+
reading->alarms.general_alarm,
185+
reading->alarms.leakage_currently,
186+
reading->alarms.leakage_previously,
187+
reading->alarms.meter_blocked,
188+
reading->alarms.back_flow,
189+
reading->alarms.underflow,
190+
reading->alarms.overflow,
191+
reading->alarms.submarine,
192+
reading->alarms.sensor_fraud_currently,
193+
reading->alarms.sensor_fraud_previously,
194+
reading->alarms.mechanical_fraud_currently,
195+
reading->alarms.mechanical_fraud_previously
196+
);
108197
}
109198

110199
/**
111200
* @brief Get the water metrics from a WMBus Prios frame.
112201
* @param uint8_t *payload The location of the WMBus frame
113-
* @param uint32_t *total_consumption Where to store the current total
114-
consumption
115-
* @param uint32_t *last_month_total_consumption Where to store last month's
116-
consumption
202+
* @param izar_reading *reading Where to store the extracted data
117203
*/
118-
uint8_t getMetricsFromPRIOSWMBusFrame(uint8_t *frame, uint32_t *total_consumption, uint32_t *last_month_total_consumption) {
204+
uint8_t getMetricsFromPRIOSWMBusFrame(const uint8_t * const frame, izar_reading * const reading) {
119205
/* Decode the payload */
120206
uint32_t key = preparePRIOSKey(PRIOS_DEFAULT_KEY1);
121207
uint8_t decodedPayload[32];
@@ -124,7 +210,9 @@ uint8_t getMetricsFromPRIOSWMBusFrame(uint8_t *frame, uint32_t *total_consumptio
124210
return 0;
125211
}
126212

127-
/* Get the data from the decoded payload */
128-
getMetricsFromPriosPayload(decodedPayload, total_consumption, last_month_total_consumption);
213+
/* Extract the data from the frame and the decoded data: */
214+
parsePRIOSFrame(frame + 13, decodedPayload, reading);
215+
216+
/* Return: */
129217
return 1;
130218
}

Diff for: ST-STEVAL-FKI868V1/Src/S2LP_WMBus.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,13 @@ void S2LP_HandleGPIOInterrupt() {
8080

8181
/* The frame is an IZAR meter reporting data, let's handle it */
8282
if (LField == 0x19 && CField == 0x44 && MField == 0x4C30 && A_Ver == 0xD4 && A_Type == 0x01) {
83-
uint32_t total_consumption, last_month_total_consumption;
84-
if (!getMetricsFromPRIOSWMBusFrame(s2lpRxData, &total_consumption, &last_month_total_consumption)) {
83+
izar_reading reading;
84+
if (!getMetricsFromPRIOSWMBusFrame(s2lpRxData, &reading)) {
8585
return;
8686
}
8787

8888
/* Output the data on the COM port */
89-
printf("%.6x,%u,%u\r\n", A_Id, total_consumption, last_month_total_consumption);
89+
printIZARReadingAsCSV(A_Id, &reading);
9090
}
9191
}
9292
}

Diff for: ST-STEVAL-FKI868V1/Src/WMBus.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ uint16_t crcCalc(uint16_t crcReg, uint8_t crcData) {
4848
* LINK_STATUS_INVALID_SIZE - The data-field buffer is too large
4949
*
5050
*/
51-
uint8_t CRCCheck(uint8_t *pStart, uint8_t *pStop) {
51+
uint8_t CRCCheck(const uint8_t * pStart, const uint8_t * const pStop) {
5252
uint16_t seed=0x0000;
5353

5454
while (pStart != pStop) {
@@ -75,7 +75,7 @@ uint8_t CRCCheck(uint8_t *pStart, uint8_t *pStop) {
7575
* @param uint8_t *A_Type Where to store the type part of the A-field
7676
* @retval uint8_t 1 if the frame is valid, 0 otherwise
7777
*/
78-
uint8_t CheckWMBusFrame(uint8_t *frame, uint8_t len, uint8_t *LField, uint8_t *CField, uint16_t *MField, uint32_t *A_Id, uint8_t *A_Ver, uint8_t *A_Type) {
78+
uint8_t CheckWMBusFrame(const uint8_t *frame, const uint8_t len, uint8_t * const LField, uint8_t * const CField, uint16_t * const MField, uint32_t * const A_Id, uint8_t * const A_Ver, uint8_t * const A_Type) {
7979
if (len < 13) {
8080
/* The frame is too short to be what we want */
8181
return 0;
@@ -96,7 +96,7 @@ uint8_t CheckWMBusFrame(uint8_t *frame, uint8_t len, uint8_t *LField, uint8_t *C
9696
}
9797

9898
if((len-12)%18!=0) {
99-
crc &= CRCCheck(&frame[len-((len-12)%18)],&frame[len-2]);
99+
crc &= CRCCheck(&frame[len-((len-12)%18)], &frame[len-2]);
100100
if (!crc) {
101101
return 0;
102102
}

0 commit comments

Comments
 (0)