1 /**********************************************************************************************
2    Copyright (C) 2015 Ivo Kronenberg <>
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 **********************************************************************************************/
18 
19 #include "gis/fit/decoder/CFitFieldBuilder.h"
20 #include "gis/fit/decoder/CFitFieldDataState.h"
21 #include "gis/fit/defs/CFitBaseType.h"
22 #include "gis/fit/defs/CFitProfileLookup.h"
23 #include "gis/fit/defs/fit_const.h"
24 #include "gis/fit/defs/fit_enums.h"
25 #include "gis/fit/defs/fit_fields.h"
26 
reset()27 void CFitFieldDataState::reset()
28 {
29     fieldDataIndex = 0;
30     fieldIndex = 0;
31     devFieldIndex = 0;
32 }
33 
34 
process(quint8 & dataByte)35 decode_state_e CFitFieldDataState::process(quint8& dataByte)
36 {
37     CFitMessage& mesg = *latestMessage();
38     CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());
39 
40     // add the read byte to the data array
41     fieldData[fieldDataIndex++] = dataByte;
42 
43     handleFitField();
44     bool allFieldRead = fieldIndex >= defMesg->getNrOfFields();
45     if(allFieldRead)
46     {
47         handleDevField();
48     }
49     bool allDevFielRead = devFieldIndex >= defMesg->getNrOfDevFields();
50 
51     if (allFieldRead && allDevFielRead)
52     {
53         // Now that the entire message is decoded we may evaluate subfields and expand components
54         CFitFieldBuilder::evaluateSubfieldsAndExpandComponents(mesg);
55 
56         devProfile(mesg);
57 
58         reset();
59         FITDEBUG(2, qDebug() << mesg.messageInfo())
60         // after all fields read, go to next record header
61         return eDecoderStateRecord;
62     }
63 
64     // there are more fields to read for the current message
65     return eDecoderStateFieldData;
66 }
67 
68 
handleFitField()69 bool CFitFieldDataState::handleFitField()
70 {
71     CFitMessage& mesg = *latestMessage();
72     CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());
73 
74     if (fieldIndex < defMesg->getNrOfFields())
75     {
76         const CFitFieldDefinition& fieldDef = defMesg->getFieldByIndex(fieldIndex);
77         if (fieldDataIndex >= fieldDef.getSize())
78         {
79             // all bytes are read for current field
80 
81             // new field with data
82             CFitField f = CFitFieldBuilder::buildField(fieldDef, fieldData, mesg);
83             mesg.addField(f);
84 
85             // The special case time record.
86             // timestamp has always the same value for all enums. it does not matter against which we're comparing.
87             if (fieldDef.getDefNr() == eRecordTimestamp)
88             {
89                 setTimestamp(f.getValue().toUInt());
90             }
91 
92             // new field follows, reset
93             fieldDataIndex = 0;
94             fieldIndex++;
95         }
96     }
97     return fieldIndex >= defMesg->getNrOfFields();
98 }
99 
handleDevField()100 bool CFitFieldDataState::handleDevField()
101 {
102     CFitMessage& mesg = *latestMessage();
103     CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());
104 
105     if (devFieldIndex < defMesg->getNrOfDevFields())
106     {
107         const CFitFieldDefinition& fieldDef = defMesg->getDevFieldByIndex(devFieldIndex);
108         if (fieldDataIndex >= fieldDef.getSize())
109         {
110             // handling developer data for mapping the field data to its definitions:
111             // part 2, reading field data and attach dynamic profile
112             CFitFieldProfile* fieldProfile = devFieldProfile(fieldDef.getDevProfileId());
113             if (fieldProfile->getBaseType().nr() == eBaseTypeNrInvalid)
114             {
115                 // test if profile exists
116                 throw tr("Missing field definition for development field.");
117             }
118 
119             CFitField f = CFitFieldBuilder::buildField(*fieldProfile, fieldDef, fieldData, mesg);
120             mesg.addField(f);
121 
122             // new field follows, reset
123             fieldDataIndex = 0;
124             devFieldIndex++;
125         }
126     }
127     return devFieldIndex >= defMesg->getNrOfDevFields();
128 }
129 
devProfile(CFitMessage & mesg)130 void CFitFieldDataState::devProfile(CFitMessage& mesg)
131 {
132     // handling developer data for mapping the field data to its definitions:
133     // part 1, dynamic profiles creation
134     if (mesg.getGlobalMesgNr() == eMesgNumDeveloperDataId)
135     {
136         // Get developer ID
137         quint8 devDataIdx = fitDevDataIndexInvalid;
138         const QList<CFitField>& fields = mesg.getFields();
139         for (const CFitField& field : fields)
140         {
141             if (field.isValidValue() && field.getFieldDefNr() == eDeveloperDataIdDeveloperDataIndex)
142             {
143                 devDataIdx = (quint8) field.getValue().toUInt();
144                 break;
145             }
146         }
147         // Delete all developer field profiles for this ID
148         if ( devDataIdx != fitDevDataIndexInvalid )
149         {
150             clearDevFieldProfiles(devDataIdx);
151         }
152     }
153     if (mesg.getGlobalMesgNr() == eMesgNumFieldDescription)
154     {
155         CFitFieldProfile devFieldProfile = buildDevFieldProfile(mesg);
156         FITDEBUG(2, qDebug() << devFieldProfile.fieldProfileInfo());
157         addDevFieldProfile(devFieldProfile);
158     }
159 }
160 
buildDevFieldProfile(CFitMessage & mesg)161 CFitFieldProfile CFitFieldDataState::buildDevFieldProfile(CFitMessage& mesg)
162 {
163     QString fieldName;
164     quint8 fieldDefNr = 0;
165     quint8 devDataIdx = 0;
166     quint8 baseType = eBaseTypeNrInvalid;
167     qreal scale = 0;
168     quint8 array = 0;
169     QString components;
170     qint16 offset = 0;
171     QString units;
172     QString bits;
173     QString accumulate;
174     quint8 baseUnitId = 0;
175     quint8 natvieMesgNum = 0;
176     quint8 nativeFieldNum = 0;
177 
178     const QList<CFitField>& fields = mesg.getFields();
179     for (const CFitField& field : fields)
180     {
181         if (field.isValidValue())
182         {
183             switch (field.getFieldDefNr())
184             {
185             case eFieldDescriptionDeveloperDataIndex:
186                 devDataIdx = (quint8) field.getValue().toUInt();
187                 break;
188 
189             case eFieldDescriptionFieldDefinitionNumber:
190                 fieldDefNr = (quint8) field.getValue().toUInt();
191                 break;
192 
193             case eFieldDescriptionFitBaseTypeId:
194                 baseType = (quint8) field.getValue().toUInt();     // enum
195                 break;
196 
197             case eFieldDescriptionFieldName:
198                 fieldName = field.getValue().toString();
199                 break;
200 
201             case eFieldDescriptionArray:
202                 array = (quint8) field.getValue().toUInt();
203                 break;
204 
205             case eFieldDescriptionComponents:
206                 components = field.getValue().toString();
207                 break;
208 
209             case eFieldDescriptionScale:
210                 scale = (qreal) field.getValue().toDouble();
211                 break;
212 
213             case eFieldDescriptionOffset:
214                 offset = (qint16) field.getValue().toInt();
215                 break;
216 
217             case eFieldDescriptionUnits:
218                 units = field.getValue().toString();
219                 break;
220 
221             case eFieldDescriptionBits:
222                 bits = field.getValue().toString();
223                 break;
224 
225             case eFieldDescriptionAccumulate:
226                 accumulate = field.getValue().toString();
227                 break;
228 
229             case eFieldDescriptionFitBaseUnitId:
230                 baseUnitId = (quint8) field.getValue().toUInt();     // enum
231                 break;
232 
233             case eFieldDescriptionNativeMesgNum:
234                 natvieMesgNum = (quint8) field.getValue().toUInt();     // enum
235                 break;
236 
237             case eFieldDescriptionNativeFieldNum:
238                 nativeFieldNum = (quint8) field.getValue().toUInt();
239                 break;
240 
241             default:
242                 throw tr("FIT decoding error: invalid field def nr %1 while creating dev field profile.")
243                       .arg(field.getFieldDefNr());
244                 break;
245             }
246         }
247     }
248 
249     Q_UNUSED(array)
250     Q_UNUSED(baseUnitId)
251 
252     if (natvieMesgNum && nativeFieldNum)
253     {
254         const CFitFieldProfile* nativeFieldProfile = CFitProfileLookup::getFieldForProfile(natvieMesgNum, nativeFieldNum);
255         if (nativeFieldProfile->getBaseType().nr() == eBaseTypeNrInvalid)
256         {
257             qWarning() << "DEV field" << fieldName << " field profile for mesg num" << natvieMesgNum << "and field num" << nativeFieldNum << "does not exist.";
258         }
259         if (nativeFieldProfile->getUnits() != units)
260         {
261             qWarning() << "DEV field" << fieldName << "units" << units << " do not match existing profile units" << nativeFieldProfile->getUnits();
262         }
263         // scale and offset not allowed if a fit field is overwritten.
264         scale = 0;
265         offset = 0;
266     }
267 
268     CFitFieldProfile devFieldProfile = CFitFieldProfile(nullptr, fieldName, *CFitBaseTypeMap::get(baseType), fieldDefNr, devDataIdx,
269                                                         scale, offset, units, eFieldTypeDevelopment);
270     // for documentation: the development field profile may contain more fields in the future as can be adumbrated by
271     // the description field enumeration. According to the FIT specification Rev 2.3  table 4-9 - Field Description Messages
272     // those fields are not yet used.
273 
274     return devFieldProfile;
275 }
276