private void CharacteristicEditorControl_Loaded(object sender, RoutedEventArgs e) { if (NC == null) { return; } var vps = ValueParserSplit.ParseLine(NC.Type); if (vps.Count != 1) { return; // if there are multiple values, give up and let the user struggle with HEX } DisplayFormat = vps[0].DisplayFormatPrimary; DisplayFormatSecondary = vps[0].Get(1, 1); foreach (var item in uiConvertType.Items) { var tag = (item as ComboBoxItem).Tag as string; if (tag == DisplayFormat) { uiConvertType.SelectedItem = item; } } if (DisplayFormat == "ASCII" && DisplayFormatSecondary == "LONG") { var h = uiEditBox.ActualHeight; uiEditBox.MinHeight = h * 3; uiEditBox.MinWidth = 300; uiEditBox.AcceptsReturn = true; } }
private void AddButtons() { // e.g. the WilliamWeillerEngineering Skoobot has "buttons" -- there are enums // defined for specific commands like forward and reverse. When buttonType is set to // "standard" then we add buttons for those specific commands. //OTHERWISE we just have the string command to send. if (NC?.UI?.buttonType != "standard") { return; } uiButtons.Children.Clear(); // What's the name of the value? var parse = ValueParserSplit.ParseLine(NC.Type); if (parse.Count != 1) { return; // We can only do buttons when there is only one choice. } var value1 = parse[0]; if (value1.ByteFormatPrimary != "U8") { return; // can only handle enums for now } var name = value1.NamePrimary; // Get the corresponding enums if (!NC.EnumValues.ContainsKey(name)) { return; // no enums means no buttons. } var enums = NC.EnumValues[name]; var margin = new Thickness(2); foreach (var(enumName, enumValue) in enums) { var b = new Button() { Content = enumName, Tag = enumValue, MinWidth = 120, Margin = margin, }; b.Click += OnEnum_Click; uiButtons.Children.Add(b); if (enumName.Length >= 15) { // Spill to two columns VariableSizedWrapGrid.SetColumnSpan(b, 2); } } }
// Sets up all of the flags, values, etc. private async Task SetupAsync(NameDevice device, GattDeviceService service, GattCharacteristic characteristic) { Service = service; Characteristic = characteristic; NC = BleNames.Get(device, service, characteristic); #if NEVER_EVER_DEFINED // Setting it up differently now via the UILIst + uiUI Commands.Clear(); foreach (var(_, command) in NC.Commands) { command.WriteCharacteristic = this; Commands.Add(command); } #endif var props = characteristic.CharacteristicProperties; uiNotifyFlag.Visibility = props.HasFlag(GattCharacteristicProperties.Notify) ? Visibility.Visible : Visibility.Collapsed; uiNotifyOnFlag.Visibility = Visibility.Collapsed; uiReadFlag.Visibility = props.HasFlag(GattCharacteristicProperties.Read) ? Visibility.Visible : Visibility.Collapsed; uiWriteFlag.Visibility = props.HasFlag(GattCharacteristicProperties.WriteWithoutResponse) ? Visibility.Visible : Visibility.Collapsed; uiIncrementWriteFlag.Visibility = uiWriteFlag.Visibility; uiWriteWithResponseFlag.Visibility = props.HasFlag(GattCharacteristicProperties.Write) ? Visibility.Visible : Visibility.Collapsed; uiIncrementWriteWithResponseFlag.Visibility = uiWriteWithResponseFlag.Visibility; uiIndicateFlag.Visibility = props.HasFlag(GattCharacteristicProperties.Indicate) ? Visibility.Visible : Visibility.Collapsed; uiIndicateOnFlag.Visibility = Visibility.Collapsed; // The less common flags uiBroadcastFlag.Visibility = props.HasFlag(GattCharacteristicProperties.Broadcast) ? Visibility.Visible : Visibility.Collapsed; uiAuthenticatedSignedWritesFlag.Visibility = props.HasFlag(GattCharacteristicProperties.AuthenticatedSignedWrites) ? Visibility.Visible : Visibility.Collapsed; uiExtendedPropertiesFlag.Visibility = props.HasFlag(GattCharacteristicProperties.ExtendedProperties) ? Visibility.Visible : Visibility.Collapsed; uiReliableWritesFlag.Visibility = props.HasFlag(GattCharacteristicProperties.ReliableWrites) ? Visibility.Visible : Visibility.Collapsed; uiWritableAuxilariesFlag.Visibility = props.HasFlag(GattCharacteristicProperties.WritableAuxiliaries) ? Visibility.Visible : Visibility.Collapsed; // Set the little edit marker uiEditFlag.Visibility = (props.HasFlag(GattCharacteristicProperties.Write) || props.HasFlag(GattCharacteristicProperties.WriteWithoutResponse)) ? Visibility.Visible : Visibility.Collapsed; await DoReadAsync(true); // update the value field even if it's not readable var namestr = ""; if (!String.IsNullOrEmpty(NC?.Name)) { namestr = NC.Name; } else { var uidname = service.Uuid.ToString().GetCommon(characteristic.Uuid.ToString()); if (uidname.Length < 8) { namestr = uidname; if (namestr == "ffa2") { ; // something handy to put a debugger on! } if (namestr == "ffa3") { ; // a handy line to put a debugger on! } if (!String.IsNullOrWhiteSpace(characteristic.UserDescription)) { namestr += " " + characteristic.UserDescription; } else { ; } } } // Either we have a good name (yay!) or we use the alternate route of using the // uuid info area. if (namestr != "") { // Don't need the UUID area or the line break uiName.Text = namestr; uiInfo.Visibility = Visibility.Collapsed; } else { var infostr = ""; if (!string.IsNullOrWhiteSpace(NC?.Name)) { infostr = NC.Name + " "; } infostr += characteristic.Uuid.ToString(); uiInfo.Text = infostr; uiInfo.Visibility = Visibility.Visible; } // If there's a readable value with displayFormatPrimary=ASCII and secondary of LONG (ASCII^LONG) // then make the text area multi-line var vps = ValueParserSplit.ParseLine(NC?.Type ?? ""); if (vps.Count == 1) { var displayFormat = vps[0].DisplayFormatPrimary; var displayFormatSecondary = vps[0].Get(1, 1); switch (displayFormat) { case "ASCII": switch (displayFormatSecondary) { case "LONG": var h = uiValueShow.ActualHeight; uiValueShowScroll.MinHeight = h * 3; uiValueShowScroll.MaxHeight = h * 7; CurrValueShowMethod = ValueShowMethod.Append; break; } break; } } try { SetupUI(); } catch (Exception) { ; } if (NC != null && NC.Commands != null) { foreach (var(_, command) in NC.Commands) { command.InitVariables(); command.WriteCharacteristic = this; } } }
private static ValueParserResult ConvertHelper(DataReader dr, string decodeCommands) { var str = ""; var vps = ValueParserSplit.ParseLine(decodeCommands); var valueList = new BCBasic.BCValueList(); bool isOptional = false; for (int i = 0; i < vps.Count; i++) { var stritem = ""; var command = vps[i]; var readcmd = command.ByteFormatPrimary; var readindicator = readcmd[0]; var displayFormat = command.DisplayFormatPrimary; var displayFormatSecondary = command.Get(1, 1); var name = command.NamePrimary; if (string.IsNullOrEmpty(name)) { name = $"param{i}"; } var units = command.UnitsPrimary; var resultState = ResultState.IsDouble; // the most common result double dvalue = double.NaN; try { switch (readindicator) { case 'F': // FLOAT if (dr.UnconsumedBufferLength == 0) { if (isOptional) { dvalue = double.NaN; stritem = ""; break; } else { return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i+1}")); } } switch (readcmd) { case "F32": { dvalue = dr.ReadSingle(); switch (displayFormat) { case "": case "FIXED": displayFormat = (displayFormatSecondary == "") ? "N3" : displayFormatSecondary; break; case "DEC": displayFormat = (displayFormatSecondary == "") ? "N0" : displayFormatSecondary; break; case "HEX": return(ValueParserResult.CreateError(decodeCommands, $"Float displayFormat unrecognized; should be FIXED {displayFormat}")); } stritem = dvalue.ToString(displayFormat); // e.g. N3 for 3 digits } break; case "F64": { dvalue = dr.ReadDouble(); switch (displayFormat) { case "": case "FIXED": displayFormat = (displayFormatSecondary == "") ? "N3" : displayFormatSecondary; break; case "DEC": displayFormat = (displayFormatSecondary == "") ? "N0" : displayFormatSecondary; break; case "HEX": return(ValueParserResult.CreateError(decodeCommands, $"Float displayFormat unrecognized; should be FIXED {displayFormat}")); } stritem = dvalue.ToString(displayFormat); // e.g. N3 for 3 digits } break; default: return(ValueParserResult.CreateError(decodeCommands, $"Float command unrecognized; should be F32 or F64 not {readcmd}")); } break; case 'I': if (dr.UnconsumedBufferLength == 0) { if (isOptional) { dvalue = double.NaN; stritem = ""; break; } else { return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}")); } } switch (readcmd) { case "I8": case "I16": case "I24": case "I32": { string floatFormat = "F2"; string intFormat = "X6"; switch (readcmd) { case "I8": { var value = (sbyte)dr.ReadByte(); dvalue = (double)value; } break; case "I16": { var value = dr.ReadInt16(); dvalue = (double)value; } break; case "I24": { var b0 = dr.ReadByte(); var b1 = dr.ReadByte(); var b2 = dr.ReadByte(); var msb = (sbyte)(dr.ByteOrder == ByteOrder.BigEndian ? b0 : b2); var lsb = dr.ByteOrder == ByteOrder.BigEndian ? b2 : b0; int value = (int)(msb << 16) + (b1 << 8) + (lsb); dvalue = (double)value; } break; case "I32": { var value = dr.ReadInt32(); dvalue = (double)value; intFormat = "X8"; } break; } string calculateCommand = command.Get(0, 1); // e.g. for I24^100_/ for TI 1350 barometer values if (!string.IsNullOrEmpty(calculateCommand)) { dvalue = ValueCalculate.Calculate(calculateCommand, dvalue).D; if (double.IsNaN(dvalue)) { return(ValueParserResult.CreateError(decodeCommands, $"Calculation failed for {calculateCommand} in {readcmd}")); } else { // Everything worked and got a value stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary); if (stritem == null) { return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}")); } } } else { if (displayFormat == "") { displayFormat = "HEX"; } stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary, floatFormat, intFormat); if (stritem == null) { return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}")); } } } break; default: return(ValueParserResult.CreateError(decodeCommands, $"Integer command unrecognized; should be I8/16/24/32 not {readcmd}")); } break; case 'O': switch (readcmd) { case "OEB": resultState = ResultState.NoResult; dr.ByteOrder = ByteOrder.LittleEndian; break; case "OEL": resultState = ResultState.NoResult; dr.ByteOrder = ByteOrder.LittleEndian; break; case "OOPT": isOptional = true; break; default: return(ValueParserResult.CreateError(decodeCommands, $"Optional command unrecognized; should be OEL or OEB not {readcmd}")); } break; case 'Q': if (dr.UnconsumedBufferLength == 0) { if (isOptional) { dvalue = double.NaN; stritem = ""; break; } else { return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}")); } } // e.g. Q12Q4.Fixed { var subtypes = readcmd.Split(new char[] { 'Q' }); if (subtypes.Length != 3) // Actually 2, but first is blank { return(ValueParserResult.CreateError(decodeCommands, $"Q command unrecognized; wrong number of Qs {readcmd}")); } stritem = FixedQuotientHelper(dr, subtypes[1], subtypes[2], displayFormat, out dvalue); //NOTE: fail on failure } break; case 'U': if (dr.UnconsumedBufferLength == 0) { if (isOptional) { dvalue = double.NaN; stritem = ""; break; } else { return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}")); } } switch (readcmd) { case "U8": case "U16": case "U24": case "U32": string xfmt = "X2"; switch (readcmd) { case "U8": { var value = dr.ReadByte(); dvalue = (double)value; xfmt = "X2"; } break; case "U16": { var value = dr.ReadUInt16(); dvalue = (double)value; xfmt = "X4"; } break; case "U24": { var b0 = dr.ReadByte(); var b1 = dr.ReadByte(); var b2 = dr.ReadByte(); var msb = (byte)(dr.ByteOrder == ByteOrder.BigEndian ? b0 : b2); var lsb = dr.ByteOrder == ByteOrder.BigEndian ? b2 : b0; int value = (int)(msb << 16) + (b1 << 8) + (lsb); dvalue = (double)value; } break; case "U32": { var value = dr.ReadUInt32(); dvalue = (double)value; xfmt = "X8"; } break; } string calculateCommand = command.Get(0, 1); // e.g. for I24^100_/ for TI 1350 barometer values if (!string.IsNullOrEmpty(calculateCommand)) { dvalue = ValueCalculate.Calculate(calculateCommand, dvalue).D; if (double.IsNaN(dvalue)) { return(ValueParserResult.CreateError(decodeCommands, $"Calculation failed for {calculateCommand} in {readcmd}")); } else { stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary); if (stritem == null) { return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}")); } } } else { if (displayFormat == "") { displayFormat = "HEX"; } stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary, "F2", xfmt); if (stritem == null) { return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized;\nshould be FIXED or HEX or DEC not {displayFormat} in {readcmd}")); } } break; default: return(ValueParserResult.CreateError(decodeCommands, $"UInteger command unrecognized;\nshould be U8/U16/U24/U32 not {readcmd}")); } break; case '/': // e.g. /U8/I16|Fixed if (dr.UnconsumedBufferLength == 0) { if (isOptional) { dvalue = double.NaN; stritem = ""; break; } else { return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}")); } } { var subtypes = readcmd.Split(new char[] { '/' }); if (subtypes.Length != 3) // Actually 2, but first is blank { return(ValueParserResult.CreateError(decodeCommands, $"/ command unrecognized; wrong number of slashes {readcmd}")); } stritem = FixedFractionHelper(dr, subtypes[1], subtypes[2], displayFormat, out dvalue); // NOTE: fail on failure } break; default: if (readcmd != readcmd.ToUpper()) { System.Diagnostics.Debug.WriteLine("ERROR: readcmd {readcmd} but should be uppercase"); } switch (readcmd.ToUpper()) //NOTE: should be all-uppercase; any lowercase is wrong { case "STRING": { try { stritem = dr.ReadString(dr.UnconsumedBufferLength); switch (displayFormat) { case "": case "ASCII": stritem = EscapeString(stritem, displayFormatSecondary); break; case "Eddystone": stritem = BluetoothDefinitionLanguage.Eddystone.EddystoneUrlToString(stritem); break; default: return(ValueParserResult.CreateError(decodeCommands, $"Unknown string format type {displayFormat}")); } } catch (Exception) { // The string can't be read. Let's try reading as a buffer instead. IBuffer buffer = dr.ReadBuffer(dr.UnconsumedBufferLength); for (uint ii = 0; ii < buffer.Length; ii++) { if (ii != 0) { stritem += " "; } stritem += buffer.GetByte(ii).ToString("X2"); } } resultState = ResultState.IsString; } break; case "BYTES": { IBuffer buffer = dr.ReadBuffer(dr.UnconsumedBufferLength); for (uint ii = 0; ii < buffer.Length; ii++) { if (ii != 0) { stritem += " "; } stritem += buffer.GetByte(ii).ToString("X2"); } resultState = ResultState.IsString; } break; default: return(ValueParserResult.CreateError(decodeCommands, $"Other command unrecognized; should be String or Bytes {readcmd}")); } break; } } catch (Exception e) { stritem = $"EXCEPTION reading data {e} index {i} command {command} len {dr.UnconsumedBufferLength}"; return(ValueParserResult.CreateError(str + stritem, stritem)); } switch (resultState) { case ResultState.IsDouble: valueList.SetProperty(name, new BCBasic.BCValue(dvalue)); break; case ResultState.IsString: valueList.SetProperty(name, new BCBasic.BCValue(stritem)); break; } if (str != "") { str += " "; } str += stritem; if (dr.UnconsumedBufferLength <= 0) { break; } } return(ValueParserResult.CreateOk(str, valueList)); }