Things used in this project

Hardware components:
102110017%206
MediaTek Labs LinkIt Smart 7688 Duo
Mediatek LinkIt Smart 7688 Duo
×1
11026 02
Jumper wires (generic)
×1
12002 04
Breadboard (generic)
×1
Adafruit industries ada592 image 75px
USB-A to Micro-USB Cable
Programming MediaTeK LinkIt smart 7688 Duo Board
×1
SeeedStudio Grove Breakout for LinkIt Smart 7688 Duo
×1
SeeedStudio Grove - Touch Sensor
×1
SeeedStudio Gear Stepper Motor with Driver
×1
Software apps and online services:
Ide web
Arduino Arduino IDE
Programming Atmega ATmega32U4 part
Atom
Simpleicon sns
Amazon Web Services AWS SNS
Amazon web services for SNS configuration
Self-Signed Certificate Generator
Amazon Dash Replenishment service

Schematics

Smart Vendy Architecture
Img 20170223 143913 hdr 3 iegrtirkvu

Code

MediaTek DRS JavaScript
Main Node JS file
var express = require('express');
var app = express();
var http = require('http').Server(app);

var io = require('socket.io')(http);
var io_https = require('socket.io')(https);

var https = require('https');
var fs = require('fs');


var debug = require('debug')('rest-server:server');
var request = require('request');
var port = 3000;
var Secport = 3443;
var request = require('request');
var Authorization_code;
var access_token_DRS;
var refresh_token_DRS;
var slotID = 'bf3f0b71-7851-4b8c-9c6c-7364118d130f';
var serialPort = require('serialport');
var SerialPort = serialPort.SerialPort;
var Last_purchase_time; // Date is is stored whwn the last purchase made from vending machine
var remainingQuantity;// To know quantity of product left in slot
var originalQuantity;//quantity of full refilled slot

var Slot_status_import = require('./slot_status.js');
var DRS_call_import = require('./DRS_call.js');
var Subscription_import = require('./Suscribe_info.js');
var DeviceStatus_import = require('./Device_status.js');
var Deregister_import = require('./Deregistration.js');

//Configuring Serial port with MCU
var portName = '/dev/ttyS0';
serialPort = new SerialPort(portName, {
    baudrate: 57600,
    // def  aults for Arduino serial communication
    dataBits: 8,
    parity: 'none',
    stopBits: 1,
    flowControl: false
});

//loading initial page
app.get('/', function(req, res) {
    res.sendfile('index_testing.html');
});



//Handling Socket io connection of http
io.on('connection', function(socket) {

    console.log('new client connected');

    //Socket event for input count of quantity at the time of refillment if user enter before login
    socket.on('KitKatQuantityEvent', function(data) {
        console.log("Number of KitKat quantity =" + data);

        serialPort.write(data);
    });
    /*    socket.on('OreoQuatityEvent', function(data){
              console.log("Number of Oreo quantity ="+ data);
       serialPort.write(data);
     });*/



});

//Initializing http server
http.listen(port, function() {
    console.log('listening on *:3000');
});


//Options for creating HTTPS server and reading file of PRIVATE  KEY and CERTIFICATE
var options = {

    key: fs.readFileSync(__dirname + '/25827299-localhost_3000.key'),
    cert: fs.readFileSync(__dirname + '/25827299-localhost_3000.cert')
};


//creating secure server
var secureServer = https.createServer(options, app)

//Initializing secure Socket IO events
var io_sec = require('socket.io')(secureServer);
//Initializing secure server
secureServer.listen(Secport, function() {
    console.log('Server listening on port ', Secport);

});

//Handling  secure Socket IO events
io_sec.on('connection', function(socket) {
    console.log('new secure client connected');

    //Socket event for input count of quantity at the time of refillment
    socket.on('KitKatQuantityEvent', function(data) {
        console.log('\n');
        console.log('\n');
        console.log("Number of KitKat quantity =" + data);

        console.log('\n');
        remainingQuantity = data;
        originalQuantity= data;
        serialPort.write(data);
    });

    //Authorization code socket event generted from HTML page
    socket.on('Auth_code', function(data) {
        console.log(data);
        Authorization_code = data;

        // calling access token function to get access_token

        access_token_function();
    });


    //Subscription_event
    socket.on('Subscription_event', function(data) {
        console.log(data);

        //Subscr_function call in Suscribe_info module
        Subscription_import.Subscr_function(access_token_DRS); //


    });

    //Socket of event DeviceStatus_event
    socket.on('DeviceStatus_event', function(data) {
        console.log(data);

        //deviceStatus_function call in Device_status.js module
        DeviceStatus_import.deviceStatus_function(access_token_DRS);


    });

    //Socket of event SlotStatus_event
    socket.on('SlotStatus_event', function(data) {
        console.log(data);

        //Slot_Status_function call in SlotStatus.js with Last purchase time parameter
        Slot_status_import.Slot_Status_function(access_token_DRS, Last_purchase_time ,remainingQuantity,originalQuantity);


    });
    socket.on('Deregister_device_event', function(data) {
        console.log(data);

        //Slot_Status_function call in SlotStatus.js
        Deregister_import.deregistration_function(access_token_DRS);


    });


});

//Access Token function
function access_token_function() {
    console.log("Calling Access Token Function");
    var headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    // Configure the request
    var options = {
        url: 'https://api.amazon.com/auth/o2/token',
        port: 443,
        method: 'POST',
        headers: headers,
        form: {
            'grant_type': 'authorization_code',
            'code': Authorization_code,
            'client_id': 'amzn1.application-oa2-client.ad6e919aadf242b88ee0951a7e5a9399',
            'client_secret': '4030d7b62041b4a0cb887c5d9d013f11824dea8a3e241cb43e869c1e0bfea7bd',
            'redirect_uri': 'https://192.168.1.133:3443'
        }
    }

    // Start the request
    request(options, function(error, response, body) {
        console.log('statusCode:', response.statusCode);
        console.log('headers:', response.headers);
        if (!error && response.statusCode == 200) {
            // Print out the response body
            console.log(body);

            var Extract_access_token = JSON.parse(body);
            access_token_DRS = Extract_access_token.access_token;
            refresh_token_DRS = Extract_access_token.refresh_token;
            console.log('\n');
            console.log("Access token = " + access_token_DRS);
            console.log('\n');
            console.log("Refresh token = " + refresh_token_DRS);
            //    time_check_access_token_1 = new Date();

        }
    })


}


// Refresh Token fuction  Before placing order
function refresh_token_function() {
    var headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    // Configure the request
    var options = {
        url: 'https://api.amazon.com/auth/o2/token',
        port: 443,
        method: 'POST',
        headers: headers,
        form: {
            'grant_type': 'refresh_token',
            'refresh_token': refresh_token_DRS,
            'client_id': 'amzn1.application-oa2-client.ad6e919aadf242b88ee0951a7e5a9399',
            'client_secret': '4030d7b62041b4a0cb887c5d9d013f11824dea8a3e241cb43e869c1e0bfea7bd',
            'redirect_uri': 'https://192.168.1.133:3443'
        }
    }

    // Start the request
    request(options, function(error, response, body) {
        console.log('statusCode:', response.statusCode);
        console.log('headers:', response.headers);
        if (!error && response.statusCode == 200) {
            // Print out the response body
            console.log(body);
            var Extract_access_token = JSON.parse(body);

            //assgning acess token value to  variable

            access_token_DRS = Extract_access_token.access_token;
            refresh_token_DRS = Extract_access_token.refresh_token;
            console.log('\n');
            console.log("Access token = " + access_token_DRS);
            console.log('\n');
            console.log("Refresh token = " + refresh_token_DRS);
        }
    })

    //calling DRS function to place order
    DRS_call_import.DRS_call_function(access_token_DRS);
}




//listening Serial Port from MCU ATMega32U4
serialPort.on('data', function(data) {

    //Assigning date of last purchase
    Last_purchase_time = new Date();
    remainingQuantity = remainingQuantity - data; // Decrease quantity by 1
    // checking threshold level of quantity , so that it  can order before it get out of stock
    if (remainingQuantity == "2") {
        console.log('\n');
        console.log("products are running out of stock");
        console.log('\n');
        console.log("Ordering using Amazon DRS")
        console.log('\n');

        //Calling refresh_token_function before placing order to avoid issue with access_token expire
        refresh_token_function();
    }

    console.log("Remaining Quantity of KitKat =" + remainingQuantity)

});
DRS functionJavaScript
Dash Replenishment Service function
var request = require('request');
var access_token_DRS;

var DRS_call_function = function DRS_function(access_token_DRS) {
    var headers = {
        'Authorization': 'Bearer ' + access_token_DRS,
        'x-amzn-accept-type': 'com.amazon.dash.replenishment.DrsReplenishResult@1.0',
        'x-amzn-type-version': 'com.amazon.dash.replenishment.DrsReplenishInput@1.0'

    }
    var options = {

        url: 'https://dash-replenishment-service-na.amazon.com/replenish/bf3f0b71-7851-4b8c-9c6c-7364118d130f',
        port: 443,
        method: 'POST',
        headers: headers,
    }

    // Start the request
    request(options, function(error, response, body) {
        console.log('statusCode:', response.statusCode);
        console.log('headers:', response.headers);
        // Print out the response body
        //  console.log("HI");
        console.log(body);
        console.log("sucessfully called replenish");


        if (error) {
            console.log("error = " + error);
        }
    })


}
exports.DRS_call_function = DRS_call_function;
Slot Status APIJavaScript
//sucessfull

var request = require('request');
var access_token_DRS;
var Last_purchase_time_slot;//Date is is stored whwn the last purchase made from vending machine
var remainingQuantity_slot;//To know quantity of product left in slot and updated through Main JS
var originalQuantity_slot;//quantity of full refilled slot and updated through Main JS

var Slot_Status_function = function access_token_function(access_token_DRS ,Last_purchase_time_slot,remainingQuantity_slot,originalQuantity_slot ) {
var https = require('https');
var today = new Date();
var Expected = new Date(today.getTime() + (1000*60*60*24));
var few_time_ago = Last_purchase_time_slot //new Date(today.getTime() - (1000*60*60));
var remainingQuantity_slot_int=parseInt(remainingQuantity_slot);
var originalQuantity_slot_int=parseInt(originalQuantity_slot);

var data = JSON.stringify({
  expectedReplenishmentDate : Expected,
  remainingQuantityInUnit : remainingQuantity_slot_int,
  originalQuantityInUnit : 10,
  totalQuantityOnHand : 20,
  lastUseDate : few_time_ago
  });

var options = {
  hostname: 'dash-replenishment-service-na.amazon.com',
  port: 443,
  path: '/slotStatus/bf3f0b71-7851-4b8c-9c6c-7364118d130f' ,
  method: 'POST',

  headers: {'Authorization' : 'Bearer ' + access_token_DRS,
          'x-amzn-accept-type': 'com.amazon.dash.replenishment.DrsSlotStatusResult@1.0',
          'x-amzn-type-version': 'com.amazon.dash.replenishment.DrsSlotStatusInput@1.0',
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(data)
  }
};

var req = https.request(options, function(res) {
      res.setEncoding('utf8');
      res.on('data', function (chunk) {
          console.log('\n');
          console.log(today);
        console.log(data);
        console.log('\n');
          console.log('statusCode:', res.statusCode);
          console.log("body: " + chunk);
            console.log("sucessfully called slot status");


      });
  });

  req.write(data);
  req.end();

}

exports.Slot_Status_function = Slot_Status_function;
Subscription Info APIJavaScript
//sucessfull

var request = require('request');
var access_token_DRS;

var Subscr_function = function Sub_function(access_token_DRS ) {
//sucessfull
var options = { method: 'GET',
  url: 'https://dash-replenishment-service-na.amazon.com/subscriptionInfo',
  headers:
   { 'Authorization': 'Bearer ' + access_token_DRS,
     'cache-control': 'no-cache',
     'x-amzn-type-version': 'com.amazon.dash.replenishment.DrsSubscriptionInfoInput@1.0',
     'x-amzn-accept-type': 'com.amazon.dash.replenishment.DrsSubscriptionInfoResult@1.0'
        } };

request(options, function (error, response, body) {
  if (error) throw new Error(error);
  console.log('\n');
  console.log(body);
    console.log('\n');
  console.log("sucessfully called subscriptionInfo")
});

}
exports.Subscr_function = Subscr_function;
Device Status APIJavaScript
//sucessfull
var request = require('request');
var access_token_DRS;
var https = require('https');
var deviceStatus_function = function Device_function(access_token_DRS) {

    var today = new Date();
    //var yesterday = new Date(today.getTime() - (1000*60*60*24));
    //var hourago = new Date(today.getTime() - (1000 * 60 * 60));

    var isoDate = new Date();
    var data = JSON.stringify({
        mostRecentlyActiveDate: isoDate
    });

    var options = {
        hostname: 'dash-replenishment-service-na.amazon.com',
        port: 443,
        path: '/deviceStatus',
        method: 'POST',
        headers: {
            'Authorization': 'Bearer ' + access_token_DRS,
            'x-amzn-accept-type': 'com.amazon.dash.replenishment.DrsDeviceStatusResult@1.0',
            'x-amzn-type-version': 'com.amazon.dash.replenishment.DrsDeviceStatusInput@1.0',
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(data)
        }
    };

    var req = https.request(options, function(res) {
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
              console.log('\n');
            console.log(isoDate);
            console.log(data);
    console.log('\n');
           console.log(today);
          //  console.log(yesterday);
          //  console.log(hourago);
            console.log('statusCode:', res.statusCode);
            console.log("body: " + chunk);
            console.log("Called Device status successfully");
    console.log('\n');

        });
    });

    req.write(data);
    req.end();
}
exports.deviceStatus_function = deviceStatus_function;
De-Registration APIJavaScript
var request = require("request");

var deregistration_function = function DeReg_function(access_token_DRS) {
    var options = {
        method: 'DELETE',
        url: 'https://dash-replenishment-service-na.amazon.com/registration',
        headers: {
            'Authorization': 'Bearer ' + access_token_DRS,
            'cache-control': 'no-cache',
            'x-amzn-type-version': 'com.amazon.dash.replenishment.DrsDeregisterInput@2.0',
            'x-amzn-accept-type': 'com.amazon.dash.replenishment.DrsDeregisterResult@1.0',
            
        }
    };

    request(options, function(error, response, body) {
        if (error) throw new Error(error);
    console.log('\n');
        console.log(body);
            console.log('\n');
            console.log("Device registration done successfully");
    });
}

exports.deregistration_function = deregistration_function;
Index HTML pageHTML
<!DOCTYPE html>
<html>
<head>
  <!-- Include meta tag to ensure proper rendering and touch zooming -->
  <meta name="viewport" content="width=device-width, initial-scale=1" >
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <!-- Include jQuery Mobile stylesheets -->
  <link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css">

<!-- Include the jQuery library -->
  <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>

<!-- Include the jQuery Mobile library -->
  <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>

  <script src="socket.io/socket.io.js"></script>
  <title> Testing</title>

</head>
<body>
  <div data-role="page" id="pageone">
  <div data-role="header" data-theme="b">
  <h1>Registraton page for Smart Vendy</h1>
  </div>

    <div data-role="main" class="ui-content">

      <div id="pageone">
  <button onclick="LoginWithAmazon()" type="button" value="Login with Amazon" id="LoginWithAmazonID">Login with Amazon</button/>
  <input onclick="Slot_status()" type="button" value="Check Slot status" id="SlotStatus"/>
  <input onclick="Subscription()" type="button" value="Check Subscription status" id="Subscription"/>
<input onclick="DeviceStatus()" type="button" value="Check Device status" id="Device"/>
<input onclick="Deregister()" type="button" value="Deregister Device" id="Deregister_Device"/>
<!--<input onclick="Auth()" type="button" value="Get Auth Code" />-->
<!--<input onclick="DRS_call()" type="button" value="Order DRS" />-->
<a href="#myPopup" data-rel="popup" class="ui-btn ui-btn-inline ui-corner-all">Re-Fill the products</a>
<div data-role="popup" id="myPopup" class="ui-content" data-overlay-theme="b">
<!--<form method="get" action="/contents">
    <button type="submit">Re-Fill the products</button>
</form>-->
<!--<button><a href="http://localhost:3000/contents">Re-Fill the products</a></button>-->
<!--<input onclick="Re_fill_page()" type="button" value="Re-Fill the products"/>-->
<div class="ui-grid-b ">


    <form action="">
    Quantity of KitKat:
    <input type="number" name="firstname" placeholder="Enter quantity" id="KiKatQuantity" >
    <br>
  <!-- Quantity of Oreo:
   <input type="number" name="lastname" placeholder="Enter quantity" id="OreoQuatity">-->
    <input type="submit" value="Submit" id="AllValueSubmit">
  </form>
        </div>
  </div>

      </div>
  </div>
  <script>
  var URL_SERVER = 'https://localhost:3443';
  var socket = io();

  //var socket_sec = io(URL_SERVER);
  function LoginWithAmazon() {
    window.open("https://www.amazon.com/ap/oa?client_id=amzn1.application-oa2-client.ad6e919aadf242b88ee0951a7e5a9399&scope=dash%3Areplenish&scope_data=%7B%22dash%3Areplenish%22%3A%7B%22device_model%22%3A%22Smart_Vendy%22%2C%22serial%22%3A%22arduino123456789%22%2C%22is_test_device%22%3Atrue%7D%7D&response_type=code&redirect_uri=https%3A%2F%2F192.168.1.133%3A3443");
  }

  function Auth() {

    var str = String(window.location);
    var check = str.indexOf("code=");
    if(check != -1)
    {document.getElementById("LoginWithAmazonID").style.display = 'none';
LoginWithAmazonID.style.display = "none";
       var str = String(window.location);
       var res_auth = str.substring(check+5,check+25);

socket.emit('Auth_code', res_auth);


  //alert(res_auth);
      }
}
function Slot_status() {
  socket.emit("SlotStatus_event","Checking  Slot status");
}
function Subscription() {
    socket.emit('Subscription_event','Checking Subscription Status');

}
function DeviceStatus() {
  socket.emit('DeviceStatus_event', 'Checking  Device Status');
}
function Deregister(){
  socket.emit('Deregister_device_event', ' Deregistering  the Device ');
}

var KitKat,Oreo;
$('#AllValueSubmit').on("click", function() {
  $("[name='firstname']").val(function(n, c){
            KitKat =c;

        });
         $("[name='lastname']").val(function(n, c){
           Oreo =c;

        });
 alert("KitKat =" +KitKat.toString());//+"  " +"Oreo ="+Oreo.toString()

          socket.emit('KitKatQuantityEvent',KitKat);
            //socket.emit('OreoQuatityEvent',Oreo);

              });
Auth();


</script>
</body>
</html>
Arduino code Stepper Motor Arduino
 
#define IN1  8
#define IN2  9
#define IN3  10
#define IN4  11

int Quantity; // Reading quantity during refillment process via serially from MediaTek_DRS_2.js  
int Steps = 0; //Initializing Step count
boolean Direction = true;// 
unsigned long last_time; // Timing variable to allow stepper for rotation
unsigned long currentMillis ; //Timing variable to allow stepper for rotation
int steps_left;// variable to store number of steps to move stepper 
long time;

// fuction  to set direction of stepper motor shaft movement( clockwise or anticlockwise)
void SetDirection(){
  if(Direction==1){ Steps++;} // clockwise
  if(Direction==0){ Steps--; }// anticlockwise
  if(Steps>7){Steps=0;} 
  if(Steps<0){Steps=7; }
}

// Function to control phase sequense of stepper in half stepping , for reference http://www.nmbtc.com/step-motors/engineering/full-half-and-microstepping/
void stepper(int xw){
  for (int x=0;x<xw;x++){
    SetDirection();
    switch(Steps){
       case 0:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, HIGH);
       break; 
       case 1:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, HIGH);
         digitalWrite(IN4, HIGH);
       break; 
       case 2:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, HIGH);
         digitalWrite(IN4, LOW);
       break; 
       case 3:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, HIGH);
         digitalWrite(IN3, HIGH);
         digitalWrite(IN4, LOW);
       break; 
       case 4:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, HIGH);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, LOW);
       break; 
       case 5:
         digitalWrite(IN1, HIGH); 
         digitalWrite(IN2, HIGH);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, LOW);
       break; 
         case 6:
         digitalWrite(IN1, HIGH); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, LOW);
       break; 
       case 7:
         digitalWrite(IN1, HIGH); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, HIGH);
       break; 
       default:
         digitalWrite(IN1, LOW); 
         digitalWrite(IN2, LOW);
         digitalWrite(IN3, LOW);
         digitalWrite(IN4, LOW);       break; 

    }
  }  
} 


void customStepRev(int movestep){
   steps_left=(-1)*movestep;
   Direction==-1;
   while(steps_left<0){
      currentMillis = micros();
      if(currentMillis-last_time>=1000){
      stepper(1); 
      time=time+micros()-last_time;
      last_time=micros();
      steps_left++;
      }
    }
   //Serial.println(time);
   delay(2000);
} 
// Stpper part
const int buttonPin = 4;     // the number of the TouchSensor pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
int TouchSensor = 0;         // variable for reading the TouchSensor status

void setup() {
  // initialize the LED pin as an output:
 // Serial.begin(9600);
  pinMode(IN1, OUTPUT); 
  pinMode(IN2, OUTPUT); 
  pinMode(IN3, OUTPUT); 
  pinMode(IN4, OUTPUT); 
  pinMode(ledPin, OUTPUT);
  
  pinMode(ledPin, OUTPUT);
  // initialize the TouchSensor pin as an input:
  pinMode(buttonPin, INPUT);
  Serial1.begin(57600);
}

void loop() {
  // read the state of the TouchSensor value:
   if (Serial1.available() > 0) 
   {
  Quantity = Serial1.readString().toInt();
   }

  // check if the TouchSensor is pressed.
  // if it is, the TouchSensor is HIGH:
  TouchSensor = digitalRead(buttonPin);
  
  // set the LED:
  

  if (TouchSensor == HIGH) {
     digitalWrite(ledPin, HIGH);
     Serial1.print(1); // Serial write to MediaTek_DRS.js to decrement value by 1
    customStepRev(4096);// Passing parameter is complete one revolution of stepper motor
    // turn LED on:
     
  }else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
  }
}

Credits

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

Linkit One: Blowing an LED
Intermediate
  • 976
  • 27

Full instructions

In this tutorial I Will show you how to turn on an led with your mouth. The Led will turn on when start to blow the sound sensor.

Linkit One: Water Level Indicator
Intermediate
  • 1,095
  • 26

In this project we will learn on making water level indicator using linkit one. This project could help you save water more efficiently.

Understanding LinkIt ONE ScanNetworks
Intermediate
  • 921
  • 18

Protip

I found a sketch called "ScanNetworks" in the LWiFi library and decided to give it a try.

Vertical Hydroponic Farm
Intermediate
  • 32,819
  • 202

Work in progress

IoT Enabled Hydroponic Farm

IoT with LinkIt ONE and Microsoft Azure
Intermediate
  • 1,061
  • 23

Full instructions

This is an example of a very basic a telemetry project which may be classified as a classic “Internet of Things” (IoT) application.

Creating the Tweeting Viking Hat
Intermediate
  • 1,074
  • 20

Full instructions

I made a tweeting viking hat to show off that anyone could hear iBeacons.

Add projectSign up / Login