Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Celia Garrido HidalgoLuis Roda Sánchez
Published © GPL3+

Logifox: The End of E-Waste Has Arrived

A low-power IoT platform for reverse logistics based on a smart pallet and a real-time monitoring app.

AdvancedFull instructions provided14 hours13,202

Things used in this project

Hardware components

Arduino MKR Fox 1200
Arduino MKR Fox 1200
Microcontroller-based board acting as core of the prototype
×1
Android device
Android device
LG G3 D855 with Android 5.0
×1
SparkFun IMU Breakout - MPU-9250
SparkFun IMU Breakout - MPU-9250
Accelerometer to generate events on new part recognition
×1
Load cell (3 kg)
Load cell to measure weight of parts
×1
HX711 load cell amplifier
Amplifier with ADC integrated to get the digital output of weight
×1
Elegoo Double-sided PCB
To solder connections on the prototype
×1
Jumper wires (generic)
Jumper wires (generic)
To solder connections on the double-sided PCBs
×20
3.7 V LiPo Battery
680 mAh LiPo battery
×1

Software apps and online services

Arduino IDE
Arduino IDE
For the development of Arduino codes for the MKR 1200 FOX board
Sigfox
Sigfox
Sigfox technology and back-end connectivity
Firebase
Google Firebase
Database to receive Sigfox uplink messages
Apache Cordova
To develop a multiplatform app based on HTML5, CSS and JavaScript
JQuery Mobile
Plugin for Apache Cordova

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
AOYUE soldering station
Methacrylate sheet
Base of the prtotype
Dremel workstation
To make hollows on the surface of the methacrylate sheet
Pallet (miniature)
Upper-side of the load cell prototype
Screws
4x load cell and 4x double-sided PCB

Story

Read more

Schematics

Logifox schematics

Schematics of Logifox using a MKR FOX 1200, 5kg load cell and MPU 9250 accelerometer (previous to soldering connections)

Code

JS file of the Cordova project

JavaScript
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/* Programming code developed by:
 * Celia Garrido-Hidalgo
 * Luis Roda-Snchez
 */

/*
 * HTTP connection between Sigfox backend and Firebase Realtime Database (Google).
 * Source: https://firebase.google.com/
 */

document.addEventListener('deviceready', function onDeviceReady() {
    var db = firebase.database();
    var ref = db.ref("itemsReport");
    var databaseInfo;

    // Attach an asynchronous callback to read the data at our reference
    ref.on("value", function(snapshot) {
        databaseInfo = snapshot.val();
        decode(databaseInfo);
    }, function(errorObject) {
        alert("The read failed: " + errorObject.code);
    });
});

function decode(data) {
    var dataPayload = data.ram.payload;
    var dataID = data.ram.ID;
    var dataSector;
    var dataWarehouse;
    callback(function segment() {
            dataItems = dataPayload.charCodeAt(1) - 48; // To decode the hex value from the string received from Sigfox
            dataSector = dataPayload.charCodeAt(3) - 48;
            dataWarehouse = dataPayload.charCodeAt(5) - 48;
        },
        function refresh() {
            document.getElementById("idItems").innerHTML = dataItems; // Linking application elements with variables
            document.getElementById("idPallet").innerHTML = dataID;
            document.getElementById("idSector").innerHTML = dataSector;
            document.getElementById("idWarehouse").innerHTML = dataWarehouse;
        });
}

function callback(first, second) {
    first();
    second();
}

HTML file of the Cordova project

HTML
<!DOCTYPE html>
<!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
-->
<!--
    Programming code developed by:
    Celia Garrido-Hidalgo
    Luis Roda-Snchez
 -->

<html>
    <head>
        <meta charset="utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="msapplication-tap-highlight" content="no" />
        <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <title>LogiFox</title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />

        <link rel="stylesheet" type="text/css" href="css/index.css" />
        
        <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
        <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
        
        <script src="https://www.gstatic.com/firebasejs/4.0.0/firebase.js"></script>
        <script src="https://www.gstatic.com/firebasejs/4.0.0/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/4.0.0/firebase-auth.js"></script>
        <script src="https://www.gstatic.com/firebasejs/4.0.0/firebase-database.js"></script>
        <script>
          // Initialize Firebase
          // TODO: Replace with your project's customized code snippet
          var config = {
            apiKey: "AAAA9YcEPyw:APA91bGNRJbXEN6kZ69dwTvnNopInCRE0wRYCvsEV4aowQHU8oU6EW-TBOeykpVlOkOZzk6i0dmd47hW0XdUwj0LW2hMwEt3MCwmQn-TgnJFhXsOt1wOp-TvzXdHtE_Q3oldrIkcjEOvZrltXf0rhxyo_CVr7T4_DA",
            authDomain: "logifox-ba2b8.firebaseio.com",
            databaseURL: "https://logifox-ba2b8.firebaseio.com",
            storageBucket: "",
            messagingSenderId: "1054532189996",
          };

          firebase.initializeApp(config);
          var ref = firebase.database();
        </script>

       <style type="text/css">
            html, body 
            {
                height: auto;
                margin: 0px;
            }
            .container 
            {
                height: auto;
                background: #ffffff;
            }
        </style>

        <style>

            p.inventory
            {
                line-height: 10%;
                color: rgb(186, 37, 37);
                font-size: 50px;
                text-align:center;
                margin-top:25px;
                margin-bottom:25px;
            }
            p.normal 
            {
                font-weight:bold;
                line-height: 70%;
                color: rgb(0, 0, 0);
                font-size: 20px;
                margin-top:18px;
                margin-bottom:18px;
                margin-left:10px;
            }
            p.dato
            {
                color: rgb(156, 37, 37);
                font-size: 20px;
                margin-top:18px;
                margin-bottom:18px;
            }

            p.title 
            {
                font-weight:normal;
                margin:10px;
                overflow:visible;
                padding:0px;
                text-align:center;
                color: rgb(21, 127, 87);
                font-size: 16px;
            }
            img
            {
                margin:0px;
                padding:0px;
                width: 100%;
            }
        </style>

    </head>
    <body>
        <div class="container" data-role="page" id="pageone">
            <div data-role="header">
                <p class = "title"> <b>LOGIFOX</b> </p>
            </div>
            
            <div role="main" class="ui-content">
                <img src="../www/img/main.png" style="width: 100%" alt="">
                <ul data-role="listview" data-ajax="false" data-inset="true" data-theme="a">
                    <li> <a href="#harddisk">HARD DISK<img src="../www/img/harddisklogo.png" style="width: 100%" alt=""></a> </li>
                    <li> <a href="#rammemory">RAM MEMORY<img src="../www/img/ramlogo.png" style="width: 100%" alt=""></a></li>
                    <li> <a href="#graphiccard">GRAPHIC CARD<img src="../www/img/graphiccardlogo.png" style="width: 100%" alt=""></a></li>
                </ul>
                </select>
            </div>
        </div>

        <div class="container" data-role="page" id="information">
            <div data-role="header">
                <p class = "title"> <b>GENERAL</b> </p>
            </div>
            
            <div style="margin:20px;" data-role="main" class="ui-content">
                
                <img src="../www/img/about.png" style="width: 100%" alt="">
            </div>
        </div>

        <div class="container" data-role="page" id="harddisk">
            <div data-role="header">
                <p class = "title"><b>HARD DISK</b></p>
                <a href="#pageone" class="ui-btn-left ui-btn-inline ui-btn ui-icon-home ui-btn-icon-left">Home</a>
                <a href="#sector" class="ui-btn-right ui-btn-inline ui-btn ui-icon-back ui-btn-icon-right">Back</a>
            </div>

            <div data-role="main" class="ui-content">
                <img src="../www/img/harddisk1.png" style="width: 100%" alt="">   
                <div data-role="controlgroup">                    
                    <p class = "inventory"><b>0</b></p>
                </div>
                <img src="../www/img/2.png" style="width: 100%" alt="">
                <form>
                  <table style="width: 100%">
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Pallet</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>1F3035</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Sector</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>1</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Warehouse</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>7</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                    </table>
                </form>
            </div>
        </div>

        <div class="container" data-role="page" id="rammemory">
            <div data-role="header">
                <p class = "title"><b>RAM MEMORY</b></p>
                <a href="#pageone" class="ui-btn-left ui-btn-inline ui-btn ui-icon-home ui-btn-icon-left">Home</a>
                <a href="#sector" class="ui-btn-right ui-btn-inline ui-btn ui-icon-back ui-btn-icon-right">Back</a>
            </div>

            <div data-role="main" class="ui-content">
                <img src="../www/img/ram1.png" style="width: 100%" alt="">
                <div data-role="controlgroup">                    
                    <p class = "inventory"><b><span id="idItems"></span></b></p>
                </div>
                <img src="../www/img/2.png" style="width: 100%" alt="">
                <form>
                  <table style="width: 100%">
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Pallet</p></td>
                            <td align="left" width="10%"><p class = "dato"><b><span id="idPallet"></span></b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Sector</p></td>
                            <td align="left" width="10%"><p class = "dato"><b><span id="idSector"></span></b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Warehouse</p></td>
                            <td align="left" width="10%"><p class = "dato"><b><span id="idWarehouse"></span></b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                    </table>
                </form>
                
            </div>
        </div>

        <div class="container" data-role="page" id="graphiccard">
            <div data-role="header">
                <p class = "title"><b>GRAPHIC CARD</b></p>
                <a href="#pageone" class="ui-btn-left ui-btn-inline ui-btn ui-icon-home ui-btn-icon-left">Home</a>
                <a href="#sector" class="ui-btn-right ui-btn-inline ui-btn ui-icon-back ui-btn-icon-right">Back</a>
            </div>

            <div data-role="main" class="ui-content">
                <img src="../www/img/graphiccard1.png" style="width: 100%" alt="">
                <div data-role="controlgroup">
                    <p class = "inventory"><b>0</b></p>
                </div>
                <img src="../www/img/2.png" style="width: 100%" alt="">
                <form>
                  <table style="width: 100%">
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Pallet</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>1D2035</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Sector</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>2</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                        <tr style= "line-height:5%">
                            <td align="left" width="60%"><p class = "normal">Warehouse</p></td>
                            <td align="left" width="10%"><p class = "dato"><b>7</b></p></td>
                            <td align="right" width="30%"><p class = "normal"></p></td>
                        </tr>
                    </table>
                </form>
                
            </div>
        </div>
            </div>
        </div>
    </body>
</html>

CSS file of the Cordova project

CSS
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
* {
    -webkit-tap-highlight-color: rgb(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
}

body {
    -webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
    -webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
    -webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
    background-color:#E4E4E4;
    background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-webkit-gradient(
        linear,
        left top,
        left bottom,
        color-stop(0, #A7A7A7),
        color-stop(0.51, #E4E4E4)
    );
    background-attachment:fixed;
    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
    font-size:15px;
    height:100%;
    margin:0px;
    padding:0px;
    width:100%;
}

/* Portrait layout (default) */
.app {
    background:url(../img/foto.png) no-repeat center top; /* 170px x 200px */
    position:absolute;             /* position in the center of the screen */
    left:50%;
    top:50%;
    height:50px;                   /* text area height */
    width:225px;                   /* text area width */
    text-align:center;
    padding:180px 0px 0px 0px;     /* image height is 200px (bottom 20px are overlapped with text) */
    margin:-115px 0px 0px -112px;  /* offset vertical: half of image height and text area height */
                                   /* offset horizontal: half of text area width */
}
.ui-content {
    margin: 0; padding: 0;
}

/* Landscape layout (with min-width) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
    .app {
        background-position:left center;
        padding:75px 0px 75px 170px;  /* padding-top + padding-bottom + text area = image height */
        margin:-90px 0px 0px -198px;  /* offset vertical: half of image height */
                                      /* offset horizontal: half of image width and text area width */
    }
}

.event {
    border-radius:4px;
    -webkit-border-radius:4px;
    color:#FFFFFF;
    font-size:12px;
    margin:0px 30px;
    padding:2px 0px;
}

.event.listening {
    background-color:#333333;
    display:block;
}

.event.received {
    background-color:#4B946A;
    display:none;
}

@keyframes fade {
    from { opacity: 1.0; }
    50% { opacity: 0.4; }
    to { opacity: 1.0; }
}
 
@-webkit-keyframes fade {
    from { opacity: 1.0; }
    50% { opacity: 0.4; }
    to { opacity: 1.0; }
}
 
.blink {
    animation:fade 3000ms infinite;
    -webkit-animation:fade 3000ms infinite;
}

Logifox code (Arduino MKR FOX 1200)

Arduino
/*
  Based on :
  [1]: Calibration sketch of HX711 library - https://circuits4you.com/2016/11/25/hx711-arduino-load-cell/
  [2]: Official documentation of MKR FOX 1200 - https://www.arduino.cc/en/Main.ArduinoBoardMKRFox1200
  [3]: SparkFun documentation of the MPU 9250 - https://www.sparkfun.com/products/13762

  Programmed by Luis Roda-Sanchez and Celia Garrido-Hidalgo on 13/05/2017
*/

#include <SigFox.h>
#include "HX711.h"
#include <Wire.h>
#include <MPU9250.h>

#define AHRS false         // Set to false for basic data read in the accelerometer
#define DOUT 3
#define CLK 2

HX711 scale(DOUT, CLK);

float accelX, accelY, accelZ, absMovX, absMovY, absMovZ, motion;

MPU9250 IMU;

float calibration_factor = 671; // Obtained from the calibration routine

bool motionDetected = false;

bool hardDisk = false; // Variables to store the type of products received by the pallet:
bool networkCard = false;
bool graphicCard = false;
bool ramMemory = true;

int typeOfPart [4] = {hardDisk, networkCard, graphicCard, ramMemory};
int minWeight [4] = {642, 55, 220, 8}; // To define the threshold for new part detection

int numberOfParts;
int typePosition;
int weight;

int previousWeight = 0;
bool firstTime = true, auxPlus = false, auxLess = false;

typedef struct __attribute__ ((packed)) sigfox_message { // Structure to store information to be sent to sigfox-backend
  uint8_t numberItems = 0; // Initialization
  uint8_t sector = 3; // Default
  uint8_t warehouseID = 7; // Default
} SigfoxMessage;

SigfoxMessage msg; // Stub for message which will be sent


void setup() {
  Serial.begin(38400);
  Wire.begin();
  IMU.initMPU9250(); // Initialization of the accelerometer
  scale.set_scale(); // Reference: [1]
  scale.tare(); // Reset the scale to 0

  for (typePosition = 0; typePosition < 4; typePosition++) { // Finding the kind of parts stored by the pallet
    if (typeOfPart[typePosition] == true) {
      break;
    }
  }

  long zero_factor = scale.read_average(); // Getting a baseline reading

  scale.set_scale(calibration_factor); // Adjust to this calibration factor
  scale.tare();

  if (!SigFox.begin()) {
    // Something is wrong
  }

  SigFox.end(); // Send module to standby until we need to send a message
  SigFox.debug(); // For more information check the Forum http://forum.arduino.cc/index.php?topic=478950.0;nowap
  delay(20);
}


void loop() {
  IMU.readAccelData(IMU.accelCount);  // // Read the x/y/z adc values
  IMU.getAres(); // Conversion to gs.

  accelX = (float)IMU.accelCount[0] * IMU.aRes * 1000;
  accelY = (float)IMU.accelCount[1] * IMU.aRes * 1000;
  accelZ = (float)IMU.accelCount[2] * IMU.aRes * 1000;

  absMovX = abs(accelX); // To facilitate further comparison
  absMovY = abs(accelY);
  absMovZ = abs(accelZ);

  motion = absMovX + absMovY; // We sum the accelerations to gain more accuracy

  Serial.print(absMovX);
  Serial.print("  ");
  Serial.print(absMovY);
  Serial.print("  ");
  Serial.println(absMovZ);
  if (motion < 130 && absMovZ > 970 && absMovZ < 1030) { // Only in case the pallet is not being transported will it update inventory (manually-adusted thresholds)
    Serial.println("Entro");

    weight = (int)scale.get_units(10);

    if (firstTime == true && weight != 0) { // New part on the pallet
      delay(2000); // Wait ultil weight is stable
      if ((int)scale.get_units(10) > (minWeight[typePosition] - minWeight[typePosition] * 0.2)) { // Identifies as new part
        Serial.println("First item inside, sending to Sigfox back-end...");

        firstTime = false;
        previousWeight = (int)scale.get_units(10);
        numberOfParts = 1;

        Serial.println("Number of items inside updated, sending to Sigfox back-end...:");
        Serial.println(numberOfParts);
        msg.numberItems = numberOfParts;
        SigFox.begin(); // Wait at least 30ms after first configuration (100ms before)
        delay(100);
        SigFox.status();
        delay(1);
        SigFox.beginPacket(); // Sending packet via Sigfox
        SigFox.write((uint8_t*)&msg, 3);
        SigFox.endPacket(); // We finish the packet
        SigFox.end();
      }
    }
    else if (firstTime == false && weight != 0) {
      delay(2000); // Wait ultil weight is stable
      if (((int)scale.get_units(10) - previousWeight) > (minWeight[typePosition] - minWeight[typePosition] * 0.2)) { // To increment the number of parts
        numberOfParts ++;
        auxPlus = true;
      }
      else if (((int)scale.get_units(10) - previousWeight) < -(minWeight[typePosition] - minWeight[typePosition] * 0.2)) { // To decrement the number of parts
        numberOfParts--;
        auxLess = true;
      }

      if (auxPlus == true || auxLess == true) {
        auxLess = false; // Reset
        auxPlus = false;
        Serial.println("Number of items updated: "); // New part
        Serial.println(numberOfParts);
        msg.numberItems = numberOfParts;
        SigFox.begin(); // Wait at least 30ms after first configuration (100ms before)
        delay(100);
        SigFox.status();
        delay(1);
        SigFox.beginPacket(); // Sending packet via Sigfox
        SigFox.write((uint8_t*)&msg, 3);
        SigFox.endPacket(); // We finish the packet
        SigFox.end();
      }
      previousWeight = (int)scale.get_units(10); // Store previousWeight for further comparison
    }
  }
}

Credits

Celia Garrido Hidalgo
8 projects • 16 followers
Ph.D. Student at the University of Castilla-La Mancha (Albacete, Spain)
Luis Roda Sánchez
3 projects • 15 followers
IoT developer at the Albacete Reseach Institute of Informatics. Technology, electronics, science and sport.

Comments