Note: full Juniper code for this tutorial is available here: https://github.com/calebh/cwatch/tree/clockonly/jun
In this section of the tutorial we will discuss the implementation of the Bluetooth Low Energy functionality. This will allow the CLUE board to determine the current time by communicating with an Android smartphone app. The BLE spec contains a lot of details, so we will just focus on what's needed to get this part of the project working. BLE allows a device to set up what is essentially a shared "chalkboard" between two devices. Devices may advertise their existence, pair, and read and write to the shared chalkboard.
BLE devices may advertise multiple services, which can contain multiple characteristics. There is a list of common service and characteristic identifiers published in the BLE standards. In our project, the CLUE board will start one BLE service: SVC_CURRENT_TIME, under which we will have two characteristics: CHR_DAY_DATE_TIME and CHR_DAY_OF_WEEK.
We begin by creating a new module in a file called Ble.jun. The library that we are wrapping is called bluefruit, so we will import the required C/C++ header files. We start by defining a wrapper type for a characteristic and service. These will hold a ptr, which is compiled to void *. Inside of our CWatch.jun we will define different services and characteristics as global C++ objects and wrap them in these types. We also define wrapper types for other BLE configuration options. Note that we could have made a value constructor for every configuration option instead of wrapping around an integer, however the bluefruit library expects these numbers.
We now define a few functions for starting services and characteristics and configuring them. Note that we use pattern matching inside the let expressions to pull out the ptrs, then cast them appropriately. The readGeneric function is particularly notable as it is polymorphic in its return type. By constraining the return type, we can change how many bytes are read into the variable and what is therefore returned by the function.
We now add a few more functions for controlling the top level BLE device on the CLUE board. These functions will things like advertising names, intervals, and power levels.
We now return to the CWatch.jun file and add some inline C++ in the top level module. We define the service object, characteristics and handlers for when the CLUE board receives data from the smartphone. We also define two packed record types. These types are packed, which means that no members of the record will be padded. On the Android app side, we will ensure that the data we will write is packed as well. When Juniper transpiles to C++, these record types will be defined as packed structs.
We now define a few more global constants, namely the wrappers around the services and characteristics. Recall that in Juniper, curly braces containing expressions separated by newlines are sequence expressions, and the return result of a sequence expression is determined by the final expression in the sequence. We also add a bunch of BLE initialization code to the setup function. This includes setting the characteristics to be writable, disabling security/encryption, setting the size of the characteristics via the sizeof expression, and setting up the advertising information.
We are now ready to write a function that will update the clockState global variable. Recall that clockState is a mutable record. This means that we are allowed to mutate the fields of the record via the record field access operator ..
The only thing left to do is call the processBluetoothUpdates function inside of loop. This will potentially mutate the global clock state just before we start updating it and drawing the time.