Develop an Alexa skill hosted locally for fast testing and development
2018-01-15
Previous article
Introduction
This is a demonstration of my experimentation with running a Bluetooth LE peripheral on my Mac, as a proof of concept and part of my endeavor to learn about BLE. Hopefully you will find it useful for the same reasons, or maybe will help kick-start your next BLE peripheral implementation.
Protocol flow
Without going too deeply into the BLE stack’s protocol, a subject for entire books, here’s the flow for this command-line POC:
- Set up a run-loop that allocates CPU to our routines
- Start up Mac OS X BLE service
- Set up a service and associated characteristic of the POC
- Start advertising the service
- Handle incoming I/O requests
Implementation
Set up a run-loop that allocates CPU to our routines
I did not want to be distracted by coding a UI for this POC, so I chose to implement this POC as a command-line-tool. This means we need to give ourselves CPU time using a loop in main.m:
while([manager running] && [runLoop runMode:NSDefaultRunLoopMode beforeDate:distantFuture]) {
[manager advertize];
}
Without this loop, nothing will work.
Start up Mac OS X BLE service
By instantiating CBPeripheralManager, Mac OS loads and starts the BLE framework, allowing us to interact with it as a peripheral.
This is done in the constructor of manager.m:
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
Set up a service and associated characteristic of the POC
To serve data, we need to create a service that will be advertised to potential clients. Doing so is a matter of creating a characteristic, which will expose the peripheral’s data, and adding it to a service that clients can look for. Both these objects (service and characteristic) need a UUID to distinguish them from other vendors and their services. I used the Mac’s built-in UUID generator (uuidgen) to create both for the POC and assigned them to instance variables:
_charUUID = [CBUUID UUIDWithString:@"DDCA9B49-A6F5-462F-A89A-C2144083CA7F"];
_myUUID = [CBUUID UUIDWithString:@"BD0F6577-4A38-4D71-AF1B-4E8F57708080"];
I wanted to be able to read and write the data value of the peripheral, to test out those functions, and used the following flags when creating the characteristic:
CBMutableCharacteristic *myCharacteristic = [[CBMutableCharacteristic alloc]
initWithType:_charUUID properties:CBCharacteristicPropertyRead|CBCharacteristicPropertyIndicate|CBCharacteristicPropertyWriteWithoutResponse value:nil permissions:CBAttributePermissionsReadable|CBAttributePermissionsWriteable];
An important point is not to set the initial value (see value:nil); if you set the initial value, it’s taken as a static characteristic, and writes won’t be routed to our callback.
After adding the characteristic to the service, we add the service to the list of services the peripheral supports to the manager object, putting us in a state where we can advertise:
Start advertising the service
To have Centrals connect to our Peripheral, it needs to advertise itself:
[_peripheralManager startAdvertising:@{
CBAdvertisementDataLocalNameKey: @"ITAMAR-MAC-BOOK-PRO",
CBAdvertisementDataServiceUUIDsKey: @[_myUUID]
}];
The name ‘ITAMAR-MAC-BOOK-PRO’ is the one your Bluetooth scanner might display. I used BLE Scanner.
Handle incoming I/O requests
At this stage, your scanner should pick up the device (your mac) and service (Itamar etc). Characteristics can be read or written only if the Central is connected to them. This is not pairing, but connecting. Once connected, your scanner can read the value exposed by querying the specific characteristic’s UUID, which, in our case, is
DDCA9B49-A6F5-462F-A89A-C2144083CA7F
Reading, as well as writing, will trigger our callbacks to be called by CBPeripheralManager:
Read callback:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request
{
if ([request.characteristic.UUID isEqual:_charUUID]) {
request.value = [_peripheralData dataUsingEncoding:NSUTF8StringEncoding];
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
NSLog(@"didReceiveReadRequest: %@ %@. Returning %@", request.central, request.characteristic.UUID, _peripheralData);
} else
{
NSLog(@"didReceiveReadRequest: %@ %@. Ignoring!", request.central, request.characteristic.UUID);
}
}
Write callback:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests
{
CBATTRequest *request = requests[0];
if ([request.characteristic.UUID isEqual:_charUUID]) {
_peripheralData =[NSString stringWithUTF8String:[request.value bytes]];
NSLog(@"didReceiveWriteRequest: Wrote: %@", _peripheralData);
} else
{
NSLog(@"didReceiveWriteRequest: %@ %@. Ignoring!", request.central, request.characteristic.UUID);
}
}
In this POC, I do some basic checking and handle the request, but most of the code is to translate NSData to NSString and vice versa.
See it run
Access the example
Please feel free to use, fork and improve this snippet, posted on github.
I hope you find the example useful!
Happy hacking!
Previous article
Filed under
BLE
Mac OS X
- Cross-compiling for Raspberry Pi on a Mac and debugging using NetBeans
- Drobo will not mount in Finder
- Quickie - ssh dynamic port forwarding to avoid unsecured public networks
- Remote compilation, execution and debugging Raspberry Pi from a Mac using NetBeans
- Weekend warrior - MacRuby and rSpec, Mac OS X Lion, Xcode V4.3.2
Objective-C
Other Tags
API GW
AWS
- Programming ESP32 using MQTT with AWS and FreeRTOS
- Quick AWS IoT Setup and test
- Set up AWS API GW with a Typescript authorizer and logging
- Use AWS CodePipline to execute CloudFormation templates
- Use GitHub Actions to deploy your SPA hosted on Amazon S3
- Use an AWS CloudFormation script to create and host an SPA on S3 with SSL and apex/subdomain redirection using CloudFront
- Writing an Alexa skill using Ruby and AWS Lambda (Part 0)
ActiveRecord
Agile
- A review of software development metrics
- Agile programme management brief
- An alternative to current product development metrics
- An alternative to the current product development governance model
- Command & Control Management - The Party Killer
- Document Driven Development
- Inceptions revisited
- Managing multiple stakeholders
- Returns Driven Development
- The tip of the (good) iceberg
Alexa
Analysis
Ansible
BDD
BLE
C
CAB
CloudFormation
- Set up AWS API GW with a Typescript authorizer and logging
- Use AWS CodePipline to execute CloudFormation templates
- Use GitHub Actions to deploy your SPA hosted on Amazon S3
- Use an AWS CloudFormation script to create and host an SPA on S3 with SSL and apex/subdomain redirection using CloudFront
- Writing an Alexa skill using Ruby and AWS Lambda (Part 0)
CloudFront
CloudWatch
Cross-compile
Cucumber
DevOps
Devops
DotNet
Embedded
Fitbit
GNU
GitHub Actions
Governance
How-to
Inception
IoT
Javascript
Jest
Lambda
Mac OS X
- Bluetooth Low Energy (BLE) Implementing a peripheral on Mac OS X
- Cross-compiling for Raspberry Pi on a Mac and debugging using NetBeans
- Drobo will not mount in Finder
- Quickie - ssh dynamic port forwarding to avoid unsecured public networks
- Remote compilation, execution and debugging Raspberry Pi from a Mac using NetBeans
- Weekend warrior - MacRuby and rSpec, Mac OS X Lion, Xcode V4.3.2
MacRuby
Metrics
MySQL
NetBeans
Objective-C
PMO
Product Management
- A path to accelerating value realization
- A review of software development metrics
- Agile programme management brief
- An alternative to current product development metrics
- An alternative to the current product development governance model
- Express initiative kickoff formula
- Inceptions revisited
- Managing multiple stakeholders
- Plan for value delivery
- Pre-prod activity - Futurespective
- Value Stream Mapping
- When planning, it's not only about relative complexity
Programme management
Project Management
- A path to accelerating value realization
- A review of software development metrics
- Agile programme management brief
- An alternative to current product development metrics
- An alternative to the current product development governance model
- Command & Control Management - The Party Killer
- Express initiative kickoff formula
- Inceptions revisited
- Managing multiple stakeholders
- Plan for value delivery
- Pre-prod activity - Futurespective
- Value Stream Mapping
- When planning, it's not only about relative complexity
Quality Assurance
Rails
Raspberry Pi
Remote compilation
Remote debugging
Remote execution
Risk Assessment
Route 53
Ruby
- Alexa on Rails - how to develop and test Alexa skills using Rails
- Arduino programming using Ruby, Cucumber & rSpec
- How to reconnect to a database when its connection was lost
- Oh, the places you'll go...
- Quick AWS IoT Setup and test
- Weekend warrior - MacRuby and rSpec, Mac OS X Lion, Xcode V4.3.2
- Writing an Alexa skill using Ruby and AWS Lambda (Part 0)