Bluetooth Low Energy (BLE)

Bluetooth Low Energy also works on the same 2.4 GHz ISM frequency band. What this means is that a single antenna can be used for Wi-Fi and both the versions of Bluetooth.

A BLE device can have 5 possible states:

  • Standby
  • Advertising
  • Scanning
  • Initiating
  • Connected

Important Terms in BLE

Let us briefly see some of the important terms associated with BLE.

  • GATT: It is short for Generic Attribute Profile. It defines the specifications for data transfer between BLE devices using Service and Characteristics.
  • Characteristic: Characteristic is a group of information called Attribute and Attribute is a group of information transferred between devices. A characteristic usually contains the following attributes:
  • Value: Data value of the characteristic
  • Declaration: Properties of the characteristic (location, type like read, write, notify, indicate etc.)
  • Description: ASCII String describing the characteristic.
  • Service: A collection of characteristics is called a Service. Each Service has a unique 16-bit or 128-bit ID called UUID.
  • UUID: Universally Unique Identifier is a 128-bit ID given to each service and characteristic in a profile. Use the website UUIDGenerator to generate unique IDs. Each service and characteristic has a unique 16-bit or 128-bit ID called UUID. A sample UUID looks something like this:
    •  583f8b30-74b4-4757-8143-56048fd88b25 

ESP32 BLE Server

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLEDevice::init("ESP32-BLE-Server");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                CHARACTERISTIC_UUID,
                BLECharacteristic::PROPERTY_READ |
                BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("Hello, World!");
  pService->start();
  //BLEAdvertising *pAdvertising = pServer->getAdvertising();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();

  //std::string value = pCharacteristic->getValue();

Callbacks

Characteristic Callbacks

class BLECharacteristicCallbacks {
public:
	typedef enum {
		SUCCESS_INDICATE,
		SUCCESS_NOTIFY,
		ERROR_INDICATE_DISABLED,
		ERROR_NOTIFY_DISABLED,
		ERROR_GATT,
		ERROR_NO_CLIENT,
		ERROR_INDICATE_TIMEOUT,
		ERROR_INDICATE_FAILURE
	}Status;

	virtual ~BLECharacteristicCallbacks();
	virtual void onRead(BLECharacteristic* pCharacteristic);
	virtual void onWrite(BLECharacteristic* pCharacteristic);
	virtual void onNotify(BLECharacteristic* pCharacteristic);
	virtual void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code);
};

class MyCallbacks: public BLECharacteristicCallbacks
{
  void onWrite(BLECharacteristic *pCharacteristic) override
  {
    std::string value = pCharacteristic->getValue();

    if (value.length() > 0)
    {
      Serial.print("New value: ");
      for (int i = 0; i < value.length(); i++)
      {
        Serial.print(value[i]);
      }
      Serial.println();
    }
  }
  
  void onRead(BLECharacteristic *pCharacteristic)override{
    Serial.print("client read value : ");
    Serial.println(pCharacteristic->getValue().c_str());
  }
};

pCharacteristic->setCallbacks(new MyCallbacks());

server callbacks

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      Serial.println("device connected");
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      Serial.println("device disconnected");
      deviceConnected = false;
    }
};

pServer->setCallbacks(new MyServerCallbacks());

Descriptors

GATT characteristic descriptors (commonly called simply descriptors) are mostly used to provide the client with metadata (additional information about the characteristic and its value). They are always placed within the characteristic definition and after the characteristic value attribute. Descriptors are always made of a single attribute, the characteristic descriptor declaration, whose UUID is always the descriptor type and whose value contains whatever is defined by that particular descriptor type.

descriptor=new BLEDescriptor("CADE");
descriptor->setValue("new descriptor");
pCharacteristic->addDescriptor(descriptor);

BLE notify

BLE standard defines two ways to transfer data for the server to the client: notification and indication. Notifications and indications are initiated by the Server but enabled by the Client. Notification doesn’t need to be acknowledged, so they are faster and an efficient way to read data continuously. Hence, a server does not know if the message reaches to the client. Indication needs to be acknowledged for communicating. The client sent a confirmation message back to the server, this way server knows that message reached the client. Notification process similar to the reading of data.

class MyCallbacks: public BLECharacteristicCallbacks
{
  void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code)override{
    Serial.print("character status changed : ");
    pCharacteristic->getDescriptorByUUID("")->setCallbacks(new BLEDescriptorCallbacks());
    switch (s)
    {
    case Status::SUCCESS_INDICATE:
      Serial.println("Status::SUCCESS_INDICATE");
      break;
      case Status::ERROR_INDICATE_FAILURE:
      Serial.println("Status::ERROR_INDICATE_FAILURE");
      break;
      case Status::ERROR_INDICATE_TIMEOUT:
      Serial.println("Status::ERROR_INDICATE_TIMEOUT");
      break;
      case Status::ERROR_INDICATE_DISABLED:
      Serial.println("Status::ERROR_INDICATE_DISABLED");
      break;
    }
  } 
};
pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );
pCharacteristic->addDescriptor(new BLE2902());//add indications and notifications
pCharacteristic->setCallback(new MyCallbacks());
if (deviceConnected) {

   pCharacteristic->setValue(&value, 1); 
   pCharacteristic->notify(); 
   //pCharacteristic->indicate(); 
   value++; 
}

Reflections and Attributes

The .NET reflection services allow you to discover, using APIs from the System.Reflection namespace, the same information that you can see with the tools mentioned earlier. The key to this process is the type called System.Type, which contains members that expose all of a type’s metadata. This is done with the help of other types from the System.Reflection namespace

class A
{
    public int val;
    public int value
    {
        private set
        {
            val = value;
        }
        get
        {
            return val;
        }
    }
    public A(int i)
    {
        this.val = i;   
    }
}

A a=new A(12);
Type t1 =typeof(A);
Type t2 = a.GetType();
Type t3=Type.GetType("A");
Console.WriteLine((t1 == t2) && (t1 == t3));//true
A aa=(A)Activator.CreateInstance(t1, 12);
t1.GetProperty("value").GetSetMethod(true)?.Invoke(aa, new object[] { 44 });
Console.WriteLine(t1.GetField("val").GetValue(aa));//44

Attributes

Attributes provide meta-information about assemblies, types, and members. This meta-information is consumed by the compiler, the CLR, or tools that use reflection services to read them. Attributes are actually types that derive from the System.Attribute abstract class.

class DescriptionAttribute : Attribute
{
    public string Text { get; private set; }
    public bool Required { get; set; }
    public DescriptionAttribute(string description)
    {
        Text = description;
    }
}
[Description("Main component of the car", Required = true)]
class Engine
{
}
[AttributeUsage(AttributeTargets.Class|
                AttributeTargets.Struct|
                AttributeTargets.Method|
                AttributeTargets.Property|
                AttributeTargets.Field,
                AllowMultiple = true,
                Inherited = true)]
class DescriptionAttribute : Attribute
{
    public string Text { get; private set; }
    public bool Required { get; set; }
    public DescriptionAttribute(string description)
    {
        Text = description;
    }
}
[Serializable]
[Description("Main component of the car")]
[ComVisible(false)]
class Engine
{
}
[Serializable, 
 Description("Main component of the car"), 
 ComVisible(false)]
class Engine
{
}

Attributes In Reflections

var e = new Engine("M270 Turbo", 1600, 75.0);
var type = e.GetType();
var attributes = type.GetCustomAttributes(typeof(DescriptionAttribute), 
                                          true);
if (attributes != null)
{
    foreach (DescriptionAttribute attr in attributes)
    {
        Console.WriteLine(attr.Text);
    }
}
var properties = type.GetProperties();
foreach (var property in properties)
{
    var pattributes = 
      property.GetCustomAttributes(
         typeof(DescriptionAttribute), false);
    if (attributes != null)
    {
        foreach (DescriptionAttribute attr in pattributes)
        {
            Console.WriteLine(
              $"{property.Name} [{attr.Text}]");
        }
    }
}

Delgates , Events And Lambda expressions

Delegates

delegate is defined using the delegate keyword. The declaration looks like a function signature, but the compiler actually introduces a class that can hold references to methods whose signatures match the signature of the delegate. A delegate can hold references to either static or instance methods.

public enum Status { Started, Stopped }
public delegate void StatusChange(Status status);
public class Engine
{
    private StatusChange statusChangeHandler;// is Delegate return true
    public void RegisterStatusChangeHandler(StatusChange handler)
    {
        statusChangeHandler = handler;
    }
    public void Start()
    {
        // start the engine
        if (statusChangeHandler != null)
            statusChangeHandler(Status.Started);
    }
    public void Stop()
    {
        // stop the engine
        if (statusChangeHandler != null)
            statusChangeHandler(Status.Stopped);
    }
}

Combining delegates

ExpressionResult
null + d1d1
d1 + nulld1
d1 + d2[d1, d2]
d1 + [d2, d3][d1, d2, d3]
[d1, d2] + [d2, d3][d1, d2, d2, d3]
[d1, d2] – d1d2
[d1, d2] – d2d1
[d1, d2, d1] – d1[d1, d2]
[d1, d2, d3] – [d1, d2]d3
[d1, d2, d3] – [d2, d1][d1, d2, d3]
[d1, d2, d3, d1, d2] – [d1, d2][d1, d2, d3]
[d1, d2] – [d1, d2]null

Events

Events aren’t Delegate instances.

using System;

class Test
{
    public event EventHandler MyEvent
    {
        add
        {
            Console.WriteLine ("add operation");
        }
        
        remove
        {
            Console.WriteLine ("remove operation");
        }
    }  
    static void Main()
    {
        Test t = new Test();
        
        t.MyEvent += new EventHandler (t.DoNothing);
        t.MyEvent -= null;
    }
    void DoNothing (object sender, EventArgs e)
    {
    }
}
private EventHandler _myEvent;
    
public event EventHandler MyEvent
{
    add
    {
        lock (this)
        {
            _myEvent += value;
        }
    }
    remove
    {
        lock (this)
        {
            _myEvent -= value;
        }
    }        
}

Lambda expressions

  • The variables that are introduced in a lambda expression are not visible outside the lambda (for instance, in the enclosing method).
  • A lambda cannot capture inref, or out parameters from the enclosing method.
  • Variables that are captured by a lambda expression are not garbage collected, even if they would otherwise go out of scope until the delegate that the lambda is assigned to is garbage collected.
bool IsOdd(int n) { return n % 2 == 1; }
var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list.RemoveAll(IsOdd);
var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list.RemoveAll(delegate (int n) { return n % 2 == 1; });
var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list.RemoveAll(n => n % 2 == 1);

Collections

IEnumerable and IEnumerator

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface.
This works for readonly access to a collection that implements that IEnumerable can be used with a foreach statement.
IEnumerator has two methods MoveNext and Reset. It also has a property called Current

class Demo : IEnumerable, IEnumerator {
   // IEnumerable method GetEnumerator()
   IEnumerator IEnumerable.GetEnumerator() {
      throw new NotImplementedException();
   }
   public object Current {
      get { throw new NotImplementedException(); }
   }
   // IEnumertor method
   public bool MoveNext() {
      throw new NotImplementedException();
   }
   // IEnumertor method
      public void Reset() {
      throw new NotImplementedException();
   }
}

List<T> collection

List<T> uses an array to store the elements. When the number of elements exceeds the size of the array, a new and larger array is allocated, and the content of the previous array is copied to the new one. 

var numbers = new List<int> {1, 2, 3}; // 1 2 3
numbers.Add(5);                        // 1 2 3 5
numbers.AddRange(new int[] { 7, 11 }); // 1 2 3 5 7 11
numbers.Insert(5, 1);                  // 1 2 3 5 7 1 11
numbers.Insert(5, 1);                  // 1 2 3 5 7 1 1 11
numbers.InsertRange(                   // 1 13 17 19 2 3 5..
    1, new int[] {13, 17, 19});        // ..7 1 1 11

numbers.Remove(1);              // 13 17 19  2  3  5  7  1  
                                // 1 11
numbers.RemoveRange(2, 3);      // 13 17  5  7  1  1 11
numbers.RemoveAll(e => e < 10); // 13 17 11
numbers.RemoveAt(1);            // 13 11
numbers.Clear();                // empty

var numbers = new List<int> { 1, 2, 3, 5, 7, 11 };
var a = numbers.Find(e => e < 10);      // 1
var b = numbers.FindLast(e => e < 10);  // 7
var c = numbers.FindAll(e => e < 10);   // 1 2 3 5 7

var numbers = new List<int> { 1, 1, 2, 3, 5, 8, 11 };
var a = numbers.FindIndex(e => e < 10);     // 0
var b = numbers.FindLastIndex(e => e < 10); // 5
var c = numbers.IndexOf(5);                 // 4
var d = numbers.LastIndexOf(1);             // 1
var e = numbers.BinarySearch(8);            // 5

var numbers = new List<int> { 1, 5, 3, 11, 8, 1, 2 };
numbers.Sort();     // 1 1 2 3 5 8 11
numbers.Reverse();  // 11 8 5 3 2 1 1

Sort() sorts the list according to a default or specified criteria. There are several overloads that allow us to specify either a comparison delegate or an IComparer<T> object, or even a sub-range of the list to sort. This operation is performed in O(n log n) in most cases but O (n2) in the worst-case scenario.

The Dictionary<TKey, TValue> collection

var languages = new Dictionary<int, string>()
{
    {1, "C#"}, 
    {2, "Java"}, 
    {3, "Python"}, 
    {4, "C++"}
};
languages.Add(5, "JavaScript");
languages.TryAdd(5, "JavaScript");
languages[6] = "F#";
languages[5] = "TypeScript";
Console.WriteLine($"Has 5: {languages.ContainsKey(5)}");
Console.WriteLine($"Has C#: {languages.ContainsValue("C#")}");

We can also iterate through the elements of a dictionary using an enumerator, in which case the key-value pairs are retrieved as KeyValuePair<TKey, TValue> objects:

foreach(var kvp in languages)
{
    Console.WriteLine($"[{kvp.Key}] = {kvp.Value}");
}

LINQ

class A : IComparable
{
    public int value{get;private set;}
    public A(int v)=>this.value = v;
    public int CompareTo(object obj) => this.value - (obj as A).value;
}


List<A> values2 = new List<A> { new A(v:34), new A(v: 45), new A(v: 76), new A(v: 23), new A(v: 54), new A(v: 33), new A(v: 65),new A(v:33), new A(v:23) };
var res = from x in values2 where x.value>50 orderby x ascending select x;
foreach(A v in res){ Console.WriteLine(v.value);}

var res = from x in values2 group x by x.value into gg where gg.Key>30  orderby gg.Key ascending select gg.Key ;
foreach(var v in res){ Console.WriteLine(v);}//33,34,45.....
int[] arr=new int[]{1,23,21,4,34,21,44,2,31,53,21,123};
var res323=arr.TakeLast(3).OrderBy(x=>x).Distinct().Zip( arr.TakeLast(3), (x,y)=>(x,y));

foreach (var (x,y) in res323)
{
    Console.WriteLine(x+" "+y);
}

Generic

Generic types

class Pair<T, U>
{
    public T Item1 { get; private set; }
    public U Item2 { get; private set; }
    public Pair(T item1, U item2)
    {
        Item1 = item1;
        Item2 = item2;
    }
}
var p1 = new Pair<int, int>(1, 2);
var p2 = new Pair<int, double>(1, 42.99);
var p3 = new Pair<string, bool>("true", true);
public abstract class Shape<T>
{
    public abstract T Area { get; }
}

public class Square : Shape<int>
{
    public int Length { get; set; }
    public Square(int length)
    {
        Length = length;
    }
    public override int Area => Length * Length;
}

public class Square<T> : Shape<T>
{
    public T Length { get; set; }
    public Square(T length)
    {
        Length = length;
    }
    /* ERROR: Operator '*' cannot be applied to operands 
    of type 'T' and 'T' */
    public override T Area => Length * Length;
}

Variant generic interfaces

  • covariant type parameter is declared with the out keyword and allows an interface method to have a return type that is more derived than the specified type parameter.
  • contravariant type parameter is declared with the in keyword and allows an interface method to have a parameter that is less derived than the specified type parameter.
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

IEnumerable<string> names = 
   new List<string> { "Marius", "Ankit", "Raffaele" };
IEnumerable<object> objects = names;
public interface F
{}
public class FF : F
{}
F<object> str = new FF<string>();

Generic methods

class CompareObjects
{
    public bool Compare<T>(T input1, T input2)
    {
        return input1.Equals(input2);
    }
}
struct Point<T>
    where T : struct, 
              IComparable, IComparable<T>,
              IConvertible,
              IEquatable<T>,
              IFormattable
{
    public T X { get; }
    public T Y { get; }
    public Point(T x, T y)
    {
        X = x;
        Y = y;
    }
}

Object-Oriented Programming

Virtual members

In the preceding example, we have seen a virtual method. This is a method that has an implementation in a base class but can be overridden in derived classes, which is helpful for changing implementation details. Methods are non-virtual by default. A virtual method in a base class is declared with the virtual keyword. An overridden implementation of a virtual method in a derived class is defined with the override keyword, instead of the virtual keyword. The method signature of the virtual and overridden methods must match.

Methods are not the only class members that can be virtual. The virtual keyword can be applied to properties, indexers, and events. However, the virtual modifier cannot be used together with staticabstractprivate, or override modifiers.

A virtual member that is overridden in a derived class can be further overridden in a class derived from the derived class. This chain of virtual inheritance continues indefinitely unless explicitly stopped with the use of the sealed keyword

class GameUnit
{
    public Position Position { get; protected set; }
    public GameUnit(Position position)
    {
        Position = position;
    }
    public void Draw(Surface surface)
    {
        surface.DrawAt(Image, Position);
    }
    protected virtual char Image => ' ';
}
class Terrain : GameUnit
{
    public Terrain(Position position) : base(position) { }
}
class Water : Terrain
{
    public Water(Position position) : base(position) { }
    protected override char Image => '░';
}
class Hill : Terrain
{
    public Hill(Position position) : base(position) { }
    protected override char Image => '≡';
}

abstract classes and members

An abstract class is declared using the abstract keyword. An abstract class cannot be instantiated, which means we cannot create the object of an abstract class. 

abstract class GameUnit
{
    public Position Position { get; protected set; }
    public GameUnit(Position position)
    {
        Position = position;
    }
    public void Draw(Surface surface)
    {
        surface.DrawAt(Image, Position);
    }
    protected abstract char Image { get; }
}
abstract class Terrain : GameUnit
{
    public Terrain(Position position) : base(position) { }
}
class Water : Terrain
{
    public Water(Position position) : base(position) { }
    protected override char Image => '░';
}
class Hill : Terrain
{
    public Hill(Position position) : base(position) { }
    protected override char Image => '≡';
}
  • An abstract class can have both abstract and non-abstract members.
  • If a class contains an abstract member, then the class must be marked abstract.
  • An abstract member cannot be private.
  • An abstract member cannot have an implementation.
  • An abstract class must provide an implementation for all of the members of all of the interfaces it implements (if any).
  • An abstract method is implicitly a virtual method.
  • Members declared abstract cannot be static or virtual.
  • The implementation in a derived class must specify the override keyword in the declaration of the member.

Sealed classes and members

sealed class Water : Terrain
{
   public Water(Position position) : base(position) { }
   protected override char Image => '░';
}
class Lake : Water  // ERROR: cannot derived from sealed type
{
   public Lake(Position position) : base(position) { }
}
class Water : Terrain
{
    public Water(Position position) : base(position) { }
    protected sealed override char Image => '░';
}
      
class Lake : Water
{
    public Lake(Position position) : base(position) { }
    protected sealed override char Image => '░';  // ERROR
}

Hiding base class members

class Base
{
    public int Get() { return 42; }
}
class Derived : Base
{
    public new int Get() { return 10; }
}
Derived d = new Derived();
Console.WriteLine(d.Get()); // prints 10
Base b = d;
Console.WriteLine(b.Get()); // prints 42

Interfaces

  • An interface can contain only methods, properties, indexers, and events. They cannot contain fields.
  • If a type implements an interface, then it must provide an implementation for all of the members of the interface. The method signature and return type of the method of an interface cannot be altered by the type that is implementing the interface.
  • When an interface defines properties or indexers, an implementation can provide extra accessors for them. For instance, if a property in an interface has only the get accessor, the implementation can also provide a set accessor.
  • An interface cannot have constructors or operators.
  • An interface cannot have static members.
  • The interface members are implicitly defined as public. If you try to use an access modifier with a member of an interface, it will result in a compile-time error.
  • An interface can be implemented by multiple types. A type can implement multiple interfaces.
  • If a class is inheriting from another class and simultaneously implementing an interface, then the base class name must come before the name of the interface separated by a comma.
  • Typically, an interface name starts with the letter I, such as IEnumerableIList<T>, and so on.
interface ISurface
{
    void BeginDraw();
    void EndDraw();
    void DrawAt(char c, Position position);
}

class Surface : ISurface
{
    private int left;
    private int top;
    public void BeginDraw()
    {
        Console.Clear();
        left = Console.CursorLeft;
        top = Console.CursorTop;
    }
    public void EndDraw()
    {
        Console.WriteLine();
    }
    public void DrawAt(char c, Position position)
    {
        try
        {
            Console.SetCursorPosition(left + position.X, 
                                      top + position.Y);
            Console.Write(c);
        }
        catch (ArgumentOutOfRangeException e)
        {
            Console.Clear();
            Console.WriteLine(e.Message);
        }
    }
}
interface A
{
    public void fun1();
    int data { set; get; }
}
public class B :A{ 
    void A.fun1(){}
    public int data {set; get;}
}


B b=new B();
b.data = 12;
//b.fun1() error
(b as A).fun1();