Skip to content

Commit

Permalink
Extended comments for methods and classes of the library.
Browse files Browse the repository at this point in the history
  • Loading branch information
andijakl committed Aug 11, 2015
1 parent 9f7ea56 commit e722775
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 11 deletions.
56 changes: 51 additions & 5 deletions UniversalBeaconLibrary/Beacon/Beacon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
Expand All @@ -30,6 +28,14 @@

namespace UniversalBeaconLibrary.Beacon
{
/// <summary>
/// Represents a single unique beacon that has a specified Bluetooth MAC address.
/// Construction and updates are usually handled by the BeaconManager.
///
/// Construct this class based on a Bluetooth advertisement received from the
/// Windows Bluetooth API. When further advertisements are received for this beacon,
/// call its UpdateBeacon() method to update the frames.
/// </summary>
public class Beacon : INotifyPropertyChanged
{
private readonly Guid _eddystoneGuid = new Guid("0000FEAA-0000-1000-8000-00805F9B34FB");
Expand All @@ -53,11 +59,25 @@ public enum BeaconTypeEnum
iBeacon
}

/// <summary>
/// Type of this beacon.
/// Defines how the beacon will parse the individual frames to extract information from the
/// advertisements.
/// </summary>
public BeaconTypeEnum BeaconType { get; set; } = BeaconTypeEnum.Unknown;

/// <summary>
/// List of all the different frames that have been observed for this beacon so far.
/// If a new frame with the same type is collected, it replaces the previous frame.
/// </summary>
public ObservableCollection<BeaconFrameBase> BeaconFrames { get; set; } = new ObservableCollection<BeaconFrameBase>();

private short _rssi;
/// <summary>
/// Raw signal strength in dBM.
/// If a new advertisement is received for the same beacon (with the same
/// Bluetooth MAC address), always the latest signal strength is recorded.
/// </summary>
public short Rssi
{
get { return _rssi; }
Expand All @@ -70,6 +90,11 @@ public short Rssi
}

private ulong _bluetoothAddress;
/// <summary>
/// The Bluetooth MAC address.
/// Used to cluster the different received Bluetooth advertisements and to
/// collect multiple advertisements for unique beacons.
/// </summary>
public ulong BluetoothAddress
{
get { return _bluetoothAddress; }
Expand All @@ -82,6 +107,9 @@ public ulong BluetoothAddress
}
}

/// <summary>
/// Retrieves the Bluetooth MAC address formatted as a hex string.
/// </summary>
public string BluetoothAddressAsString
{
get
Expand All @@ -91,6 +119,9 @@ public string BluetoothAddressAsString
}

private DateTimeOffset _timestamp;
/// <summary>
/// Timestamp when the last advertisement was received for this beacon.
/// </summary>
public DateTimeOffset Timestamp
{
get { return _timestamp; }
Expand All @@ -102,17 +133,34 @@ public DateTimeOffset Timestamp
}
}

/// <summary>
/// Construct a new Bluetooth beacon based on the received advertisement.
/// Tries to find out if it's a known type, and then parses the contents accordingly.
/// </summary>
/// <param name="btAdv">Bluetooth advertisement to parse, as received from
/// the Windows Bluetooth LE API.</param>
public Beacon(BluetoothLEAdvertisementReceivedEventArgs btAdv)
{
BluetoothAddress = btAdv.BluetoothAddress;
UpdateBeacon(btAdv);
}

/// <summary>
/// Manually create a new Beacon instance.
/// </summary>
/// <param name="beaconType">Beacon type to use for this manually constructed beacon.</param>
public Beacon(BeaconTypeEnum beaconType)
{
BeaconType = beaconType;
}

/// <summary>
/// Received a new advertisement for this beacon.
/// If the Bluetooth address of the new advertisement matches this beacon,
/// it will parse the contents of the advertisement and extract known frames.
/// </summary>
/// <param name="btAdv">Bluetooth advertisement to parse, as received from
/// the Windows Bluetooth LE API.</param>
public void UpdateBeacon(BluetoothLEAdvertisementReceivedEventArgs btAdv)
{
if (btAdv.BluetoothAddress != BluetoothAddress)
Expand Down Expand Up @@ -193,8 +241,6 @@ public void UpdateBeacon(BluetoothLEAdvertisementReceivedEventArgs btAdv)
}
}
}


}

private void ParseEddystoneData(BluetoothLEAdvertisementReceivedEventArgs btAdv)
Expand All @@ -212,7 +258,7 @@ private void ParseEddystoneData(BluetoothLEAdvertisementReceivedEventArgs btAdv)
// 0x16 = 0xAA 0xFE [type] [data]
if (dataSection.DataType == 0x16)
{
var beaconFrame = dataSection.Data.ToArray().CreateBeaconFrame();
var beaconFrame = dataSection.Data.ToArray().CreateEddystoneBeaconFrame();
if (beaconFrame == null) continue;

var found = false;
Expand Down
9 changes: 9 additions & 0 deletions UniversalBeaconLibrary/Beacon/BeaconFrameBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@

namespace UniversalBeaconLibrary.Beacon
{
/// <summary>
/// Abstract class for every Bluetooth Beacon frame.
/// Common is that every frame has a payload / data, which derived classes can
/// further parse and make it more accessible through custom properties depending
/// on the beacon specification.
/// </summary>
public abstract class BeaconFrameBase : INotifyPropertyChanged
{
protected byte[] _payload;

/// <summary>
/// The raw byte payload of this beacon frame.
/// Derived classes can add additional functionality to parse or update the payload.
///
/// Note: directly setting the payload does not lead to the class re-parsing the payload
/// into its instance variables (if applicable in the derived class).
/// Call ParsePayload() manually from the derived class if you wish to enable this behavior.
Expand Down
34 changes: 33 additions & 1 deletion UniversalBeaconLibrary/Beacon/BeaconFrameHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ public enum EddystoneFrameType : byte
TelemetryFrameType = 0x20
}

public static BeaconFrameBase CreateBeaconFrame(this byte[] payload)
/// <summary>
/// Analyzes the payload of the Bluetooth Beacon frame and instantiates
/// the according specialized Bluetooth frame class.
/// Currently handles Eddystone frames.
/// </summary>
/// <param name="payload"></param>
/// <returns>Base class for Bluetooth frames, which is either a specialized
/// class or an UnknownBeaconFrame.</returns>
public static BeaconFrameBase CreateEddystoneBeaconFrame(this byte[] payload)
{
if (!payload.IsEddystoneFrameType()) return null;
switch (payload.GetEddystoneFrameType())
Expand All @@ -55,6 +63,18 @@ public static BeaconFrameBase CreateBeaconFrame(this byte[] payload)
}
}

/// <summary>
/// Analyzes the header of the payload to check if the frame is
/// a valid Eddystone frame.
/// It needs to start with 0xAA 0xFE and then as the third byte
/// have the Eddystone frame type according to the specification.
///
/// Does not analyze if the rest of the contents are valid
/// according to the specification - this task is up to the
/// spezialized handler classes.
/// </summary>
/// <param name="payload">Frame payload to analyze.</param>
/// <returns>True if this is an Eddystone frame, false if not.</returns>
public static bool IsEddystoneFrameType(this byte[] payload)
{
if (payload == null || payload.Length < 3) return false;
Expand All @@ -65,12 +85,24 @@ public static bool IsEddystoneFrameType(this byte[] payload)
return Enum.IsDefined(typeof(EddystoneFrameType), frameTypeByte);
}

/// <summary>
/// Retrieve the Eddystone frame type for this frame.
/// </summary>
/// <param name="payload">Frame payload to analyze.</param>
/// <returns>The Eddystone frame type that has been determined.
/// If it is not a valid or known Eddystone frame type, returns null.</returns>
public static EddystoneFrameType? GetEddystoneFrameType(this byte[] payload)
{
if (!IsEddystoneFrameType(payload)) return null;
return (EddystoneFrameType)payload[2];
}

/// <summary>
/// Create the first three bytes of the Eddystone payload.
/// 0xAA 0xFE [Eddystone type].
/// </summary>
/// <param name="eddystoneType">Eddystone type to use for this frame.</param>
/// <returns>Byte array with the size of 3 containing the Eddystone frame header.</returns>
public static byte[] CreateEddystoneHeader(EddystoneFrameType eddystoneType)
{
return new byte[] {0xAA, 0xFE, (byte)eddystoneType};
Expand Down
24 changes: 22 additions & 2 deletions UniversalBeaconLibrary/Beacon/BeaconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Windows.Devices.Bluetooth.Advertisement;

namespace UniversalBeaconLibrary.Beacon
{
/// <summary>
/// Manages multiple beacons and distributes received Bluetooth LE
/// Advertisements based on unique Bluetooth beacons.
///
/// Whenever your app gets a callback that it has received a new Bluetooth LE
/// advertisement, send it to the ReceivedAdvertisement() method of this class,
/// which will handle the data and either add a new Bluetooth beacon to the list
/// of beacons observed so far, or update a previously known beacon with the
/// new advertisement information.
/// </summary>
public class BeaconManager
{
/// <summary>
/// List of known beacons so far, which all have a unique Bluetooth MAC address
/// and can have multiple data frames.
/// </summary>
public ObservableCollection<Beacon> BluetoothBeacons { get; set; } = new ObservableCollection<Beacon>();

/// <summary>
/// Analyze the received Bluetooth LE advertisement, and either add a new unique
/// beacon to the list of known beacons, or update a previously known beacon
/// with the new information.
/// </summary>
/// <param name="btAdv">Bluetooth advertisement to parse, as received from
/// the Windows Bluetooth LE API.</param>
public void ReceivedAdvertisement(BluetoothLEAdvertisementReceivedEventArgs btAdv)
{
// Check if we already know this bluetooth address
Expand All @@ -42,6 +61,7 @@ public void ReceivedAdvertisement(BluetoothLEAdvertisementReceivedEventArgs btAd
}
}

// Beacon was not yet known - add it to the list.
var newBeacon = new Beacon(btAdv);
BluetoothBeacons.Add(newBeacon);
}
Expand Down
45 changes: 44 additions & 1 deletion UniversalBeaconLibrary/Beacon/TlmEddystoneFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

namespace UniversalBeaconLibrary.Beacon
{
/// <summary>
/// An Eddystone Telemetry frame, according to the Google Specification from
/// https://github.com/google/eddystone/tree/master/eddystone-tlm
/// </summary>
public class TlmEddystoneFrame : BeaconFrameBase
{
private byte _version;
Expand All @@ -31,6 +35,10 @@ public class TlmEddystoneFrame : BeaconFrameBase
private uint _advertisementFrameCount;
private uint _timeSincePowerUp;

/// <summary>
/// Version of the Eddystone Telemetry (TLM) frame.
/// Current and supported value is 0.
/// </summary>
public byte Version
{
get { return _version; }
Expand All @@ -43,6 +51,10 @@ public byte Version
}
}

/// <summary>
/// The current battery voltage in millivolts [mV].
/// If not supported (e.g., USB powerd beacon) = 0.
/// </summary>
public ushort BatteryInMilliV
{
get { return _batteryInMilliV; }
Expand All @@ -55,6 +67,10 @@ public ushort BatteryInMilliV
}
}

/// <summary>
/// Beacon temperature in degrees Celsius.
/// If not supported, -128 °C.
/// </summary>
public float TemperatureInC
{
get { return _temperatureInC; }
Expand All @@ -67,6 +83,10 @@ public float TemperatureInC
}
}

/// <summary>
/// Running count of advertisement frames of all types emitted
/// by the beacon since the last power up / reboot.
/// </summary>
public uint AdvertisementFrameCount
{
get { return _advertisementFrameCount; }
Expand All @@ -79,6 +99,9 @@ public uint AdvertisementFrameCount
}
}

/// <summary>
/// Time in 0.1 second resolution since the last beacon power up / reboot.
/// </summary>
public uint TimeSincePowerUp
{
get { return _timeSincePowerUp; }
Expand All @@ -97,6 +120,10 @@ public TlmEddystoneFrame(byte[] payload) : base(payload)
ParsePayload();
}

/// <summary>
/// Parse the current payload into the properties exposed by this class.
/// Has to be called if manually modifying the raw payload.
/// </summary>
public void ParsePayload()
{
using (var ms = new MemoryStream(Payload, false))
Expand Down Expand Up @@ -171,18 +198,34 @@ public void ParsePayload()
}
}


/// <summary>
/// Update the raw payload when properties have changed.
/// </summary>
private void UpdatePayload()
{
// TODO
}

/// <summary>
/// Update the information stored in this frame with the information from the other frame.
/// Useful for example when binding the UI to beacon information, as this will emit
/// property changed notifications whenever a value changes - which would not be possible if
/// you would overwrite the whole frame.
/// </summary>
/// <param name="otherFrame">Frame to use as source for updating the information in this beacon
/// frame.</param>
public override void Update(BeaconFrameBase otherFrame)
{
base.Update(otherFrame);
ParsePayload();
}

/// <summary>
/// Check if the contents of this frame are generally valid.
/// Does not currently perform a deep analysis, but checks the header as well
/// as the frame length.
/// </summary>
/// <returns>True if the frame is a valid Eddystone TLM frame.</returns>
public override bool IsValid()
{
if (!base.IsValid()) return false;
Expand Down
Loading

0 comments on commit e722775

Please sign in to comment.