#region Copyright //////////////////////////////////////////////////////////////////////////////// // The following FIT Protocol software provided may be used with FIT protocol // devices only and remains the copyrighted property of Dynastream Innovations Inc. // The software is being provided on an "as-is" basis and as an accommodation, // and therefore all warranties, representations, or guarantees of any kind // (whether express, implied or statutory) including, without limitation, // warranties of merchantability, non-infringement, or fitness for a particular // purpose, are specifically disclaimed. // // Copyright 2016 Dynastream Innovations Inc. //////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. // Profile Version = 16.60Release // Tag = production-akw-16.60.00-0-g5d3d436 //////////////////////////////////////////////////////////////////////////////// #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.IO; using Dynastream.Utility; namespace Dynastream.Fit { /// /// /// public class Mesg { #region Fields protected byte localNum = 0; protected uint systemTimeOffset = 0; public List fields = new List(); #endregion #region Properties public string Name { get; set; } public ushort Num { get; set; } public byte LocalNum { get { return localNum; } set { if (value > Fit.LocalMesgNumMask) { throw new FitException("Mesg:LocalNum - Invalid Local message number " + value + ". Local message number must be < " + Fit.LocalMesgNumMask); } else { localNum = value; } } } #endregion #region Constructors public Mesg(Mesg mesg) { if (mesg == null) { this.Name = "unknown"; this.Num = (ushort)MesgNum.Invalid; return; } this.Name = mesg.Name; this.Num = mesg.Num; this.LocalNum = mesg.LocalNum; this.systemTimeOffset = mesg.systemTimeOffset; foreach (Field field in mesg.fields) { if (field.GetNumValues() > 0) { this.fields.Add(new Field(field)); } } } public Mesg(string name, ushort num) { this.Name = name; this.Num = num; } internal Mesg(ushort mesgNum) : this(Profile.GetMesg(mesgNum)) { } public Mesg(Stream fitStream, MesgDefinition defnMesg) : this(defnMesg.GlobalMesgNum) { Read(fitStream, defnMesg); } #endregion #region Methods public void Read(Stream inStream, MesgDefinition defnMesg) { inStream.Position = 1; EndianBinaryReader mesgReader = new EndianBinaryReader(inStream, defnMesg.IsBigEndian); LocalNum = defnMesg.LocalMesgNum; foreach (FieldDefinition fieldDef in defnMesg.GetFields()) { // It's possible the field type found in the field definition may // not agree with the type defined in the profile. The profile // type will be preferred for decode. Field field = GetField(fieldDef.Num); if (field == null) { // We normally won't have fields attached to our skeleton message, // as we add values we need to add the fields too based on the mesg,field // combo in the profile. Must derive from the profile so the scale etc // is correct field = new Field(Profile.GetMesg(this.Num).GetField(fieldDef.Num)); if (field.Num == Fit.FieldNumInvalid) { // If there was no info in the profile the FieldNum will get set to invalid // so preserve the unknown fields info while we know it field.Num = fieldDef.Num; field.Type = fieldDef.Type; } SetField(field); } object value; // strings may be an array and are of variable length if ((field.Type & Fit.BaseTypeNumMask) == Fit.String) { List utf8Bytes = new List(); byte b = new byte(); for (int i = 0; i < fieldDef.Size; i++) { b = mesgReader.ReadByte(); if (b == 0x00) { field.AddValue(utf8Bytes.ToArray()); utf8Bytes.Clear(); } else { utf8Bytes.Add(b); } } if (utf8Bytes.Count != 0) { field.AddValue(utf8Bytes.ToArray()); utf8Bytes.Clear(); } } else { int numElements = (int)fieldDef.Size / Fit.BaseType[field.Type & Fit.BaseTypeNumMask].size; for (int i = 0; i < numElements; i++) { bool invalid = true; switch (field.Type & Fit.BaseTypeNumMask) { case Fit.Enum: case Fit.Byte: case Fit.UInt8: case Fit.UInt8z: value = mesgReader.ReadByte(); if ((byte)value != (byte)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.SInt8: value = mesgReader.ReadSByte(); if ((sbyte)value != (sbyte)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.SInt16: value = mesgReader.ReadInt16(); if ((short)value != (short)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.UInt16: case Fit.UInt16z: value = mesgReader.ReadUInt16(); if ((ushort)value != (ushort)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.SInt32: value = mesgReader.ReadInt32(); if ((int)value != (int)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.UInt32: case Fit.UInt32z: value = mesgReader.ReadUInt32(); if ((uint)value != (uint)Fit.BaseType[field.Type & Fit.BaseTypeNumMask].invalidValue) invalid = false; break; case Fit.Float32: value = mesgReader.ReadSingle(); // TODO! Unless we find a different way to store floats, we dont have a good way to compare if (!float.IsNaN((float)value)) invalid = false; break; case Fit.Float64: value = mesgReader.ReadDouble(); // TODO! Unless we find a different way to store floats, we dont have a good way to compare if (!double.IsNaN((double)value)) invalid = false; break; default: value = mesgReader.ReadBytes(fieldDef.Size); break; } if (!invalid || numElements > 1) field.SetRawValue(i, value); } } } } public void Write(Stream outStream) { Write(outStream, null); } public void Write(Stream outStream, MesgDefinition mesgDef) { if (mesgDef == null) { mesgDef = new MesgDefinition(this); } EndianBinaryWriter bw = new EndianBinaryWriter(outStream, mesgDef.IsBigEndian); bw.Write(LocalNum); foreach (FieldDefinition fieldDef in mesgDef.GetFields()) { Field field = GetField(fieldDef.Num); if (null == field) { field = Profile.GetField(this.Num, fieldDef.Num); if( null != field ) { fields.Add(field); } else { //Field does not exist in profile, continue to next field continue; } } // The field could be blank, correctly formed or partially filled while (field.GetSize() < fieldDef.Size) { if ((field.GetNumValues() == 1) && ((fieldDef.Type & Fit.BaseTypeNumMask) == Fit.String)) { //Has to be a string. try { byte[] value = (byte[])field.GetValue(); List temp = new List(value); while (temp.Count < fieldDef.Size) temp.Add(Convert.ToByte(Fit.BaseType[fieldDef.Type & Fit.BaseTypeNumMask].invalidValue)); field.SetValue(temp.ToArray()); } catch (Exception) { throw new FitException("Exception occurred while resizing field to match definition."); } } else { field.AddValue(Fit.BaseType[fieldDef.Type & Fit.BaseTypeNumMask].invalidValue); } } for (int i = 0; i < field.GetNumValues(); i++) { object value = field.GetRawValue(i); if (value == null) { value = Fit.BaseType[fieldDef.Type & Fit.BaseTypeNumMask].invalidValue; } switch (fieldDef.Type & Fit.BaseTypeNumMask) { case Fit.Enum: case Fit.Byte: case Fit.UInt8: case Fit.UInt8z: bw.Write((byte)value); break; case Fit.SInt8: bw.Write((sbyte)value); break; case Fit.SInt16: bw.Write((short)value); break; case Fit.UInt16: case Fit.UInt16z: bw.Write((ushort)value); break; case Fit.SInt32: bw.Write((int)value); break; case Fit.UInt32: case Fit.UInt32z: bw.Write((uint)value); break; case Fit.Float32: bw.Write((float)value); break; case Fit.Float64: bw.Write((double)value); break; case Fit.String: bw.Write((byte[])value); // Write the null terminator bw.Write((byte)0x00); break; default: break; } } } } #region FieldList Manipulation Functions public bool HasField(byte fieldNum) { foreach (Field field in fields) { if (field.Num == fieldNum) { return true; } } return false; } /// /// Replace an existing field, otherwise add a reference to fields list /// /// Caller allocated field public void SetField(Field field) { for (int i = 0; i < fields.Count; i++) { if (fields[i].Num == field.Num) { fields[i] = field; return; } } fields.Add(field); } /// /// Insert a field at the desired index. If the field already exists in the mesg it is first removed. /// /// Index to insert the field, if index is out of range, the field is added to the end of the list /// Caller allocated field public void InsertField(int index, Field field) { // if message already contains this field, remove it for (int i = 0; i < fields.Count; i++) { if (fields[i].Num == field.Num) { fields.RemoveAt(i); } } // if the index is out of range, add to the end if (index < 0 || index > fields.Count) { fields.Add(field); } // insert the new field at desired index else { fields.Insert(index, field); } } public void SetFields(Mesg mesg) { if (mesg.Num != Num) { return; } foreach (Field field in mesg.fields) { SetField(new Field(field)); } } public int GetNumFields() { return fields.Count; } public Field GetField(byte fieldNum) { foreach (Field field in fields) { if (field.Num == fieldNum) { return field; } } return null; } public Field GetField(string fieldName) { return GetField(fieldName, true); } public Field GetField(string fieldName, bool checkMesgSupportForSubFields) { foreach (Field field in fields) { if (field.Name == fieldName) { return field; } foreach (Subfield subfield in field.subfields) { if ((subfield.Name == fieldName) && (!checkMesgSupportForSubFields || (subfield.CanMesgSupport(this)))) { return field; } } } return null; } public ushort GetActiveSubFieldIndex(byte fieldNum) { Field testField = new Field(this.GetField(fieldNum)); if (testField == null) { return Fit.SubfieldIndexMainField; } for (ushort i = 0; i < testField.subfields.Count; i++) { if (testField.subfields[i].CanMesgSupport(this)) { return i; } } return Fit.SubfieldIndexMainField; } public string GetActiveSubFieldName(byte fieldNum) { Field testField = new Field(this.GetField(fieldNum)); if (testField == null) { return Fit.SubfieldNameMainField; } foreach (Subfield subfield in testField.subfields) { if (subfield.CanMesgSupport(this)) { return subfield.Name; } } return Fit.SubfieldNameMainField; } #endregion public int GetNumFieldValues(byte fieldNum) { Field field = GetField(fieldNum); if (field != null) { return field.GetNumValues(); } return 0; } public int GetNumFieldValues(String fieldName) { Field field = GetField(fieldName); if (field != null) { return field.GetNumValues(); } return 0; } public int GetNumFieldValues(byte fieldNum, ushort subfieldIndex) { Field field = GetField(fieldNum); if (field == null) { return 0; } if (subfieldIndex == Fit.SubfieldIndexActiveSubfield) { return field.GetNumValues(); } Subfield subfield = field.GetSubfield(subfieldIndex); if ((subfield == null) || (subfield.CanMesgSupport(this))) { return field.GetNumValues(); } else { return 0; } } public int GetNumFieldValues(byte fieldNum, string subfieldName) { Field field = GetField(fieldNum); if (field == null) { return 0; } Subfield subfield = field.GetSubfield(subfieldName); if ((subfield == null) || (subfield.CanMesgSupport(this))) { return field.GetNumValues(); } else { return 0; } } public object GetFieldValue(byte fieldNum) { return GetFieldValue(fieldNum, 0, Fit.SubfieldIndexActiveSubfield); } public object GetFieldValue(byte fieldNum, int fieldArrayIndex) { return GetFieldValue(fieldNum, fieldArrayIndex, Fit.SubfieldIndexActiveSubfield); } public object GetFieldValue(byte fieldNum, int fieldArrayIndex, ushort subFieldIndex) { Field field = GetField(fieldNum); if (field == null) { return null; } if (subFieldIndex == Fit.SubfieldIndexActiveSubfield) { return field.GetValue(fieldArrayIndex, GetActiveSubFieldIndex(fieldNum)); } else { Subfield subfield = field.GetSubfield(subFieldIndex); if ((subfield == null) || (subfield.CanMesgSupport(this))) { return field.GetValue(fieldArrayIndex, subFieldIndex); } else { return null; } } } public object GetFieldValue(byte fieldNum, int fieldArrayIndex, string subfieldName) { Field field = GetField(fieldNum); if (field == null) { return null; } Subfield subfield = field.GetSubfield(subfieldName); if ((subfield == null) || (subfield.CanMesgSupport(this))) { return field.GetValue(fieldArrayIndex, subfieldName); } else { return null; } } public object GetFieldValue(string name) { return GetFieldValue(name, 0); } public object GetFieldValue(string name, int fieldArrayIndex) { Field field = GetField(name, false); if (field == null) { return null; } Subfield subfield = field.GetSubfield(name); if ((subfield == null) || (subfield.CanMesgSupport(this))) { return field.GetValue(fieldArrayIndex, name); } else { return null; } } public bool GetIsFieldAccumulated(byte num) { Field field = GetField(num); if (field != null) { return field.IsAccumulated; } return false; } public void SetFieldValue(byte fieldNum, Object value) { SetFieldValue(fieldNum, 0, value, Fit.SubfieldIndexActiveSubfield); } public void SetFieldValue(byte fieldNum, int fieldArrayIndex, Object value) { SetFieldValue(fieldNum, fieldArrayIndex, value, Fit.SubfieldIndexActiveSubfield); } public void SetFieldValue(byte fieldNum, int fieldArrayIndex, Object value, ushort subfieldIndex) { if (subfieldIndex == Fit.SubfieldIndexActiveSubfield) { subfieldIndex = GetActiveSubFieldIndex(fieldNum); } else { Field testField = new Field(this.GetField(fieldNum)); Subfield subfield = testField.GetSubfield(subfieldIndex); if ((subfield != null) && !(subfield.CanMesgSupport(this))) { return; } } Field field = GetField(fieldNum); if (field == null) { // We normally won't have fields attached to our skeleton message, // as we add values we need to add the fields too based on the mesg,field // combo in the profile. field = new Field(Profile.GetMesg(this.Num).GetField(fieldNum)); if (field.Num == Fit.FieldNumInvalid) { // If there was no info in the profile our FieldNum will get set to invalid, // at least preserve FieldNum while we know it field.Num = fieldNum; } SetField(field); } field.SetValue(fieldArrayIndex, value, subfieldIndex); } public void SetFieldValue(byte fieldNum, int fieldArrayIndex, Object value, String subfieldName) { Field testField = new Field(this.GetField(fieldNum)); Subfield subfield = testField.GetSubfield(subfieldName); if ((subfield != null) && !(subfield.CanMesgSupport(this))) { return; } Field field = GetField(fieldNum); if (field == null) { // We normally won't have fields attached to our skeleton message, // as we add values we need to add the fields too based on the mesg,field // combo in the profile. field = new Field(Profile.GetMesg(this.Num).GetField(fieldNum)); if (field.Num == Fit.FieldNumInvalid) { // If there was no info in the profile our FieldNum will get set to invalid, // at least preserve FieldNum while we know it field.Num = fieldNum; } SetField(field); } field.SetValue(fieldArrayIndex, value, subfieldName); } public void SetFieldValue(String name, Object value) { SetFieldValue(name, 0, value); } public void SetFieldValue(String name, int fieldArrayIndex, Object value) { Field testField = new Field(this.GetField(name)); Subfield subfield = testField.GetSubfield(name); if ((subfield != null) && !(subfield.CanMesgSupport(this))) { return; } Field field = GetField(name, false); if (field == null) { field = new Field(Profile.GetMesg(this.Num).GetField(name)); SetField(field); } field.SetValue(fieldArrayIndex, value, name); } public DateTime TimestampToDateTime(uint timestamp) { DateTime dateTime = new DateTime(timestamp); dateTime.ConvertSystemTimeToUTC(systemTimeOffset); return dateTime; } public DateTime TimestampToDateTime(uint? timestamp) { DateTime dateTime = null; if (timestamp != null) { dateTime = new DateTime(timestamp ?? 0); dateTime.ConvertSystemTimeToUTC(systemTimeOffset); } return dateTime; } private IEnumerable ExpandComponentsInList(List componentList, Field currentField, int offset, Accumulator accumulator) { // When components.Count > 0 a field will be created and appended to the field list if ((componentList != null) && (componentList.Count > 0)) { foreach (FieldComponent fC in componentList) { if (fC.fieldNum != Fit.FieldNumInvalid) { //Create a new field to expand into Field newField = new Field(Profile.GetMesg(this.Num).GetField(fC.fieldNum)); //cache a field that we use to set properties on Field f = this.GetField(newField.Num); // GetBitsValue will not return more bits than the componentField type can hold. // This means strings are built one letter at a time when using components // which is a bad idea to start with) long? bitsValue = currentField.GetBitsValue(offset, fC.bits, newField.Type); if (bitsValue == null) { break; } if (true == fC.accumulate) { bitsValue = accumulator.Accumulate(this.Num, fC.fieldNum, bitsValue.Value, fC.bits); } if (newField.IsNumeric()) { double fbitsValue = Convert.ToDouble(bitsValue); fbitsValue = ((double)fbitsValue / fC.scale) - fC.offset; if (true == this.HasField(newField.Num)) { f.SetValue(f.values.Count, fbitsValue); } else { newField.SetValue(fbitsValue); } } // Shouldn't apply scale/offset to string or enum else { object nonNumericBitsValue; // Ensure strings are added as byte[] if ((newField.Type & Fit.BaseTypeNumMask) == Fit.String) { nonNumericBitsValue = new byte[] { (byte)bitsValue }; } else { nonNumericBitsValue = bitsValue; } if (true == this.HasField(newField.Num)) { f.SetValue(f.values.Count, nonNumericBitsValue); } else { newField.SetValue(nonNumericBitsValue); } } offset += fC.bits; //Return each field as we iterate yield return new FieldComponentExpansion(newField, offset); } } } } public void ExpandComponents() { ExpandComponents(null); } public void ExpandComponents(Accumulator accumulator) { // Traverse the field list // Change to for loop so we can add items as we iterate for (int i = 0; i < fields.Count; ++i) { List componentList = null; // Determine the active subfield ushort activeSubfield = GetActiveSubFieldIndex(fields[i].Num); if (activeSubfield == Fit.SubfieldIndexMainField) { componentList = fields[i].components; } else { componentList = fields[i].GetSubfield(activeSubfield).Components; } // Traverse the component list int offset = 0; foreach (FieldComponentExpansion f in ExpandComponentsInList(componentList, fields[i], offset, accumulator)) { //Add the new field fields.Add(f.GetField()); //update offset offset = f.GetOffset(); } } } #endregion } internal class FieldComponentExpansion { private int offset; private Field field; public FieldComponentExpansion(Field f, int offset) { field = f; this.offset = offset; } public int GetOffset() { return offset; } public Field GetField() { return field; } } } // namespace