Sam Machin
Created February 24, 2018

Alexa My Notifications

Alexa will read your iPhone notifications to you even when you phone is in another room.

160
Alexa My Notifications

Things used in this project

Hardware components

Arduino Ethernet Rev. 3
Arduino Ethernet Rev. 3
×1
Adafruit Feather nrf52 Bluefruit
×1
iPhone
Apple iPhone
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit

Story

Read more

Schematics

Wiring

Simple description of connecting the parts

Code

serialtopost.ino

Arduino
This is the code for the Arduino Ethernet which recieves the string of JSON over a soft serial interface from the Bluefruit and then posts it to my web app.
#include <SPI.h>
#include <Ethernet.h>
#include <SoftwareSerial.h>


byte mac[] = {
  0x90, 0xA2, 0xDA, 0x00, 0x6A, 0xF0
};

char server[] = "example.com";
char incomingSerialData[300]; // A character array to store received bytes
char c ;
int incomingSerialDataIndex = 0;
EthernetClient client;
SoftwareSerial mySerial(2, 3);

void setup() {
 // start the Ethernet connection:
  Ethernet.begin(mac);
  Serial.begin(115200);
  mySerial.begin(9600);
  Serial.println(Ethernet.localIP());
  postToServer();
}


void postToServer() {
  Serial.println("POSTing...");
  String PostData=incomingSerialData;
  Serial.println(PostData);
  if (client.connect(server, 80)) {
    Serial.println("connected");
  client.println("POST /event HTTP/1.1");
  client.println("Host:  sammachin.ngrok.io");
  client.println("User-Agent: Arduino/1.0");
  client.println("Connection: close");
  client.println("Content-Type: application/json;");
  client.print("Content-Length: ");
  client.println(PostData.length());
  client.println();
  client.println(PostData);
  Serial.println("sent");
  client.stop();
  } else {
    Serial.println("connection failed");
  }
}


void loop() {
  while(mySerial.available() > 0)
  {
    c = mySerial.read();
    incomingSerialData[incomingSerialDataIndex] = c; // Add the incoming byte to the array
    incomingSerialDataIndex++; // Ensure the next byte is added in the next position
    Serial.print(c);
    switch (c){
            case '\r':
            case '\n':
                    Serial.println('CRLF Received');
                    postToServer();
                    mySerial.flush();
                    incomingSerialData[incomingSerialDataIndex] = '\0';
                    break;
    }
  }

}

ancstojson.ino

Arduino
This is the code running on the Bluefruit it implements the ancs protocol deals with pairing and bonding to the iPhone then when a notificaiton is recieved on the iPhone it takes reads that using BLE and writes it out as a JSON string over the serial interface to the Arduino Ethernet
#include <bluefruit.h>

// BLE Client Service
BLEClientDis  bleClientDis;
BLEAncs       bleancs;

char buffer[128];

// Check BLEAncs.h for AncsNotification_t
const char* EVENT_STR[] = { "Added", "Modified", "Removed" };
const char* CAT_STR  [] =
{
  "Other"             , "Incoming Call"       , "Missed Call", "Voice Mail"   ,
  "Social"            , "Schedule"            , "Email"      , "News"         ,
  "Health and Fitness", "Business and Finance", "Location"   , "Entertainment"
};

void setup()
{
  Serial.begin(9600);


  Bluefruit.begin();
  // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
  Bluefruit.setTxPower(4);
  Bluefruit.setName("Bluefruit52");
  Bluefruit.setConnectCallback(connect_callback);
  Bluefruit.setDisconnectCallback(disconnect_callback);

  // Configure DIS client
  bleClientDis.begin();

  // Configure ANCS client
  bleancs.begin();
  bleancs.setNotificationCallback(ancs_notification_callback);

  // Set up and start advertising
  startAdv();
}

void startAdv(void)
{
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();

  // Include ANCS 128-bit uuid
  Bluefruit.Advertising.addService(bleancs);

  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();
  
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds
}

void loop()
{
  // Not connected, wait for a connection
  if ( !Bluefruit.connected() ) return;

  // If service is not yet discovered
  if ( !bleancs.discovered() ) return;

  // Your code here
}

void connect_callback(uint16_t conn_handle)
{
  
  Serial.print("Discovering DIS ... ");
  if ( bleClientDis.discover(conn_handle) )
  {
    
    // Read and print Manufacturer string
    memset(buffer, 0, sizeof(buffer));
    if ( bleClientDis.getManufacturer(buffer, sizeof(buffer)) )
    {
      Serial.print('{"Manufacturer" : "');
      Serial.print(buffer);
    Serial.print('"},' );
    
    }

    // Read and print Model Number string
    memset(buffer, 0, sizeof(buffer));
    if ( bleClientDis.getModel(buffer, sizeof(buffer)) )
    {
        Serial.print('{"Model" : "');
        Serial.print(buffer);
        Serial.print('"},' );
    }

    
  }

  if ( bleancs.discover(conn_handle) )
  {

    // ANCS requires pairing to work, it makes sense to request security here as well
    if ( Bluefruit.requestPairing() )
    {
      bleancs.enableNotification();
      Serial.print('{"status" : "connected"}');
    Serial.println();
   
    }
  }
}

void ancs_notification_callback(AncsNotification_t* notif)
{
  int n;
  Serial.printf("{ \"Event\" :  \"%s\", ", EVENT_STR[notif->eventID]);

  // Print Category with padding
  n = Serial.printf("\"Category\" : \"%s-%d\", ", CAT_STR[notif->categoryID], notif->categoryCount);

  // Get notification Title
  memset(buffer, 0, sizeof(buffer));
  bleancs.getAttribute(notif->uid, ANCS_ATTR_TITLE, buffer, sizeof(buffer));
  Serial.printf("\"title\" : \"%s\", ", buffer);
  
  // Get notification Message
  memset(buffer, 0, sizeof(buffer));
  bleancs.getAttribute(notif->uid, ANCS_ATTR_MESSAGE, buffer, sizeof(buffer));
  Serial.printf("\"message\" : \"%s\", ", buffer);
  
  // Get App ID and store in the app_id variable
  char app_id[64] = { 0 };
  memset(buffer, 0, sizeof(buffer));
  bleancs.getAttribute(notif->uid, ANCS_ATTR_APP_IDENTIFIER, buffer, sizeof(buffer));
  strcpy(app_id, buffer);
  Serial.printf(" \"app_id\" : \"%s\", ", app_id);

  // Get Application Name
  memset(buffer, 0, sizeof(buffer));
  bleancs.getAppAttribute(app_id, ANCS_APP_ATTR_DISPLAY_NAME, buffer, sizeof(buffer));
  Serial.printf("\"appname\" : \"%s\" }", buffer);

  Serial.println();

  
}

void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) reason;

}

server.py

Python
This is the server which both receives the POSTed alert data from the arudio and then returns an appropriate response to the Alexa Skills Kit to read the latest message, its a simple server which stores the latest unread alert in a local pickle file.
You can run it on a local machine exposed to the internet via ngrok
from flask import Flask, request, jsonify
import json
import pickle

app = Flask(__name__)

alert = None


@app.route('/notifications',  methods=['POST'])
def alexaHandler():
    with open('alert.pkl', "r") as f:
        alert = pickle.load(f)
    resp = {
	'version': '1.0',
	'response': {
		'outputSpeech': {
			'text': 'You have no new notifications',
			'type': 'PlainText'
		},
		'shouldEndSession': True
	},
	'sessionAttributes': {}
    }
    if alert != None:
        resp['response']['outputSpeech']['text'] = "You have a new {} notification, from, {}, {}".format(alert['appname'], alert['title'], alert['message'])
    return json.dumps(resp)


@app.route('/event',  methods=['POST'])
def eventHandler():
    if request.is_json:
        data = request.get_json()
        if data['Event'] == "Added":
            with open('alert.pkl', 'w') as f:
                pickle.dump(data, f)
        else:
            data = None
            with open('alert.pkl', 'w') as f:
                pickle.dump(data, f)
    return 'ok'
    
    
if __name__ == '__main__':
    app.run()

Credits

Sam Machin

Sam Machin

1 project • 0 followers

Comments