Luis Ortiz
Published © CC BY-SA

Remote Home Monitoring with Hologram Nova

Keep your home safe and sound even while you're away! Remotely monitor and manage your home gadgets with Hologram Nova and a Raspberry Pi.

AdvancedFull instructions provided6 hours3,490

Things used in this project

Hardware components

Hologram Nova
Hologram Nova
×1
Hologram Global IoT SIM Card
Hologram Global IoT SIM Card
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
Or any other RaspberryPi
×1
Raspberry Pi Touch Display
Raspberry Pi Touch Display
A screen is "optional", and only necessary when installing a nice touchscreen management at home.
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
An Ultrasonic Sensor HC-SR05 may be used instead
×1
SCT013 Non-invasive AC Current Sensor
Also known as a Split core current transformer
×1
Texas Instruments LM35 Precision Centigrade Temperature Sensor
A different temperature sensor may be used but, source code has to be addapted accordingly.
×1
Microchip MCP3008 - 8-Channel 10-Bit ADC With SPI
ADC is required to add analog inputs to the Raspberry Pi
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
A push button is used to simulate the sump pump ON/OFF status.
×1
Resistor 2k ohm
Used to create two voltage dividers (5V to 3.3V)
×2
Resistor 3.9k ohm
Used to create two voltage dividers (5V to 3.3V)
×1
Capacitor 0.01 µF
Used for decoupling the LM35DZ
×1
Capacitor 10µF
Decoupling the CT Sensor readings
×1
Resistor 10k ohm
Resistor 10k ohm
Pull-down resistor for the push-button switch.
×1
Resistor 3.3k ohm
Burden resistor for the CT sensor
×1
Resistor 470k ohm
Resistor divider for the CT sensor
×2
Jumper wires (generic)
Jumper wires (generic)
a bunch of them
×1
Breadboard (generic)
Breadboard (generic)
×1

Software apps and online services

Hologram Data Router
Hologram Data Router
Hologram - Python SDK

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
You know, a Soldering iron is "always" required.

Story

Read more

Custom parts and enclosures

Raspberry Pi 7" Touchscreen Light Stand

Raspberry Pi 7" Touchscreen light stand 60 degrees

Schematics

Remote home monitor fritzing

Fritzing diagram

Code

Remote Home Monitor dashboard

PHP
The home monitor dashboard displays the status of the different sensors connected to the Remote Home Monitor in a nice and friendly way.
<?php
    // Remote Home Monitor with Hologram Nova
    // Main Page: this is the main console which will display the status of the
    //            different sensors connected.
    //            This web page will work on most web browsers or from the
    //            Raspberry Pi using Chromium web browser.
    // Luis Ortiz - luislab.com
    // January 5, 2018
?>
<!DOCTYPE html>
<!--[if lt IE 9]> <html class="oldie"> <![endif]-->
<!--[if gt IE 10]><!--> <html lang="en-US"> <!--<![endif]-->
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Remote Home Monitor with Hologram Nova - LuisLab.com</title>
    <link type="text/css" rel="stylesheet" href="styles.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
    <script>
        $(function() {
            startRefresh();
        });
        
        //http://www.jacklmoore.com/notes/rounding-in-javascript/
        function round(value, decimals) { 
            return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
        }
        
        function channelMix(chA, chB, ratio ) {
            return parseInt((chB - chA) * ratio + chA);
        }
        
        function colorMix(colorA, colorB, ratio ) {
            var aR = colorA[0];
            var aG = colorA[1];
            var aB = colorA[2];
            
            var bR = colorB[0];
            var bG = colorB[1];
            var bB = colorB[2];
            
            return "rgb(" + channelMix(aR, bR, ratio) + "," + channelMix(aG, bG, ratio) + "," + channelMix(aB, bB, ratio) + ")";
        }
        
        function formatDate(inDate) {
            return inDate.toLocaleString(undefined, {
                        year: 'numeric',
                        month: 'short',
                        day: 'numeric',
                        hour: 'numeric',
                        minute: '2-digit' //, second: 'numeric'
                    });
        }

        function startRefresh() {
            setTimeout(startRefresh,30000);
            $.get( "./publish_payload_json.php?t=t", function( data ) {
                    $("#spanSS").html(data.stoveStatus);
                    $("#spanAP").html(data.apparentPower + " VA");
                    $("#spanSL").html(data.sumpLevel + "%");
                    $("#spanPS").html(data.sumpPump);
                    $("#spanTC").html(round(data.tempC, 1) + "&deg;C");
                    $("#spanTF").html(round(data.tempF, 1) + "&deg;F");
                    
                    if (data.stoveStatus == "ON" ) {
                        $('li#stove p.statusB img').css('object-position','-126px 0');
                        $('li#stove span#spanSS').css('color','#f00');
                    }else {
                        $('li#stove p.statusB img').css('object-position','0 0');
                        $('li#stove span#spanSS').css('color','#090');
                    }
        
                    if ( data.sumpLevel < 34 )
                        $('li#sump span#spanSL').css('color','#090');
                    else if ( data.sumpLevel < 67 )
                        $('li#sump span#spanSL').css('color','#fb0');
                    else {
                        $('li#sump span#spanSL').css('color','#f00');
                        if ( data.sumpLevel > 100 )
                            $("#spanSL").html("TIMEOUT");
                    }

                    var ratio = 1;
                    if ( data.tempF <= 32 )
                        ratio = 0;
                    else if ( data.tempF < 91 )
                        ratio = (data.tempF - 32) / (91 - 32);
                    
                    // 32F to 91F: cold (#39f)  to hot(#f00) color mix
                    $('li#temp span#spanTF, li#temp span#spanTC').css('color', colorMix([51, 153, 255], [255, 0, 0], ratio) );
                    
                    var termThreadYPos = 9 - Math.round(data.tempF * 112 / 100);
                    $('li#temp p.statusR img').css('background-position', '0 ' +  termThreadYPos.toString() + 'px');

                    var date = new Date(data.dtUTC);
                    $("#spanLU").html(formatDate(date));
            }, "json" );
        }
    </script>
    <div id="main-content">
        <h1>REMOTE HOME MONITORING<br /><span style="font-style: italic; font-size: 50%; letter-spacing: initial;"> with Hologram Nova</span></h1>
        <ul id="widgets">
            <li id="stove"><h2>Stove</h2>
                <p class="desc">Stove power consumption.</p>
                <p class="statusB"><img src="./home_monitor_stove.png" alt="Remote Home Monitor - Stove"/>
                Burners status: <strong><span id="spanSS">-</span></strong><br/>
                Apparent Power: <strong><span id="spanAP">VA</span></strong></p>
            </li>
            <li id="sump"><h2>Sump</h2>
                <p class="desc">Sump water level.</p>
                <p class="statusB"><img src="./home_monitor_sump_ultrasonic.png" alt="Remote Home Monitor - Sump"/>
                Water level: <strong><span id="spanSL">%</span></strong><br/>
                Pump status: <strong><span id="spanPS">-</span></strong></p>
            </li>
            <li id="temp"><h2>Temperature</h2>
                <p class="desc">Room temperature in &#8457; and &#8451;.</p>
                <p class="statusR"><img src="./home_monitor_thermometer.png" alt="Remote Home Monitor - Thermometer"/>
                <strong><span id="spanTF"> &#8457;</span><br /><span id="spanTC">&deg;C</span></strong></p>
            </li>
        </ul>
        <p class="last-update">Last update: <span id="spanLU"></span></p>
        <p class="desc">Make your home more secure by remotely monitoring and managing an array of sensors using Hologram Nova and a Raspberry Pi.</p>
        <p class="desc">Source code, schematics and more information available in the <a href="https://www.hackster.io/LuisLabMO/remote-home-monitoring-with-hologram-nova-32e5e8" title="Remote Home Monitoring with Hologram Nova" target="_blank">Remote Home Monitoring with Hologram Nova</a> project page.</p>
    </div>
    <div id="site-info">Protected under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/" title="Creative Commons Attribution-ShareAlike 4.0 International License" target="_blank">CC BY-SA 4.0</a> license.</div>
</body>
</html>

Dashboard external CSS

CSS
Dashboard External Style Sheet
* {
    margin: 0;
    padding: 0;
}

body {
    display: block;
    margin-top: 8px;
    background-color: #99c9ff;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 16px;
    color: #777;
    text-rendering: optimizeLegibility;
}

h1 {
    padding: 7px 0;
    text-align: right;
    font-size: 18px;
    letter-spacing: -1px;
    color: #fc0;
    text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3);
    line-height: 55%;
}

a {
    color: inherit;
}

a:hover {
    text-decoration: none;
}

#main-content {
    display: block;
    margin: 0 auto 5px auto;
    width: 239px;
    text-align: left;
}

#main-content p.desc {
    padding: 7px 0;
    font-size: 14px;
    font-style: italic;
}

#main-content .last-update {
    font-size: 12px;
    text-align: right;
    margin-bottom: 10px;
    font-style: italic;
}

#site-info {
    bottom:0;
    padding: 15px 7px;
    height: auto;
    font-size: 12px;
    color: #aaa;
    background-color: #383a42;
    min-width: 239px;
    text-align: center;
}

#widgets {
    clear: both;
    float: left;
    padding: 2px;
}

#widgets li {
    float: left;
    width: 200px;
    height: 296px;
    padding: 10px;
    margin: 6px;
    border: 2px solid #777;
    list-style-type: none;
    background-color: #ffc;
    border-radius: 10px;
    box-shadow: 3px 3px 4px 0px rgba(0, 0, 0, 0.2);
    -webkit-border-radius: 10px;
    -webkit-box-shadow: 3px 3px 4px 0 rgba(0, 0, 0, 0.2);
    -moz-box-shadow: 3px 3px 4px 0 rgba(0, 0, 0, 0.2);
}

#widgets li h2 {
    display: block;
    text-align: center;
    text-transform: uppercase;
    width: 200px;
    height: auto;
    margin: 0 auto;
}

#widgets li p.desc {
    height: 24px;
    padding: 3px 0;
    font-size: 12px;
    vertical-align: bottom;
}

#widgets li p.statusB {
    margin: 0;
    /* padding-top: 12px; */
    font-size: 14px;
    color: #444;
    line-height: 24px;
    vertical-align: bottom;
}

p.statusB strong {
    font-size: 16px;
}

#widgets li p.statusR {
    font-size: 34px;
    color: #444;
    text-align: right;
    line-height: 150%;
}

p.statusB img {
    display: block;
    width: 126px;
    height: 183px;
    margin: 0 auto 12px auto;
    object-fit: none;
    object-position: 0 0;
}

p.statusR img {
    float: left;
    width: 72px;
    height: 235px;
    object-fit: none;
    object-position: 0 0;
}

#temp p.statusR img {
    background: url(./home_monitor_thermometer-thread.png);
    background-repeat: no-repeat;
    background-position: 0 9px;
}

@media screen and (min-width: 479px) {
    #main-content { width: 478px; }
    h1 { font-size: 30px; }
}

@media screen and (min-width: 718px) {
    #main-content { width: 717px; }
    h1 { font-size: 34px; }
}

Webhook receiver

PHP
Receives the webhook data from the Hologram router and stores the payload in its original format (base64) in a in a .txt file.
<?php
    // Remote Home Monitor with Hologram Nova
    // Webhook receiver
    // Receives the webhook data from the Hologram router and stores the
    // payload in its original format (base64) in a in a .txt file.
    //
    // Luis Ortiz - luislab.com
    // January 5, 2018

    // For validation and safety purposes
    $hologram_wh_UA  = "HologramCloud (https://hologram.io)";  // Hologram Webhook User-Agent
    $hologram_wh_Key = "PayloadKey"; // Hologram Webhook key

    $input = $_POST[$hologram_wh_Key];
    if ( empty($input) ) {
        $input = file_get_contents('php://input');
        if ( empty($input) ) $input = stripslashes("addTestPayloadHere");
        parse_str($input, $output);

        $key = $output['key'];
        $payload = json_decode($output['payload'], false);
    }else
        $payload = json_decode($input, false);

    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $clientIP = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        $clientIP = $_SERVER['REMOTE_ADDR'];
    }

    $statusMessage = "OK";

    //Adding some safety measures
    if ( $hologram_wh_UA != trim($_SERVER['HTTP_USER_AGENT']) )
        $statusMessage = "ACCESS DENIED";

    if ( $hologram_wh_Key != trim($key) )
        $statusMessage = "ACCESS DENIED";

    if ( $statusMessage == "OK" ) {
        $data = $payload->data;
        //var_dump(base64_decode($data));

        $whFileName = "./payload.txt"; // Hologram payload, saved as is (base64)

        $whFileHandle = fopen($whFileName, 'w') or die("1810");
        $bytesWritten = fwrite($whFileHandle, trim($data));

        // Uncomment for debug purposes
        /*$bytesWritten = fwrite($whFileHandle, "\r\n" . trim($clientIP));
        $bytesWritten = fwrite($whFileHandle, "\r\n" . trim($_SERVER['HTTP_USER_AGENT']));
        $bytesWritten = fwrite($whFileHandle, "\r\n" . trim($key));*/

        fclose($whFileHandle);

        header('HTTP/1.1 200 OK'); // When everything ok, return 200
    }

    // In case of security issues, return a 503 error.
    if ( $statusMessage == "ACCESS DENIED" ) {
        header('HTTP/1.1 503 Service Temporarily Unavailable');
        header('Status: 503 Service Temporarily Unavailable');
    }
?>

Publish payload in JSON

PHP
Decodes Hologram payload previously saved in a .txt file and publishes it in JSON format. JSON data is later loaded by the Remote Home Monitor dashboard with JQuery.
<?php
    // Remote Home Monitor with Hologram Nova
    // Publish JSON payload
    // Decodes Hologram payload and publishes it in JSON format. JSON data is
    // later loaded by the Remote Home Monitor dashboard with JQuery.
    //
    // Luis Ortiz - luislab.com
    // January 5, 2018
    
    $whFileName = "./payload.txt";   // Home Monitor payload
    
    if ( is_readable($whFileName) ) {             // better than file_exists()
        $whFileHandle = fopen($whFileName, 'r') or die("1815");
        
        $data = trim(fgets($whFileHandle));
        $data = base64_decode($data);
        
        fclose($whFileHandle);
        
        if ( $_SERVER['REQUEST_METHOD'] == 'GET' && !empty($_SERVER['QUERY_STRING']) ) {
            header('HTTP/1.1 200 OK');
            header('Content-Type: application/json');
            echo $data;
        }else {
            header('HTTP/1.1 503 Service Temporarily Unavailable');
            header('Status: 503 Service Temporarily Unavailable');
        }
    }
?>

Raspberry main python program

Python
Main program for the Raspberry Pi. Gathers all sensors data and will publish the data collected to the Hologram Data Engine in JSON format using the Hologram Nova.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Remote Home Monitor with Hologram Nova
# Main program for the Raspberry Pi (home_monitor.py)
# Gathers all sensors data and will publish the data collected to 
# the Hologram Data Engine in JSON format using the Hologram Nova.
#
# Luis Ortiz - luislab.com
# January 5, 2018

import time
import datetime
import math
import subprocess
import RPi.GPIO as GPIO

from Hologram.HologramCloud import HologramCloud

GPIO.setwarnings(False)

GPIO.setmode(GPIO.BCM)
DEBUG = 1   # When DEBUG = 1, Modem connection is NOT used, only console output

# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
def readadc(adcnum, clockpin, mosipin, misopin, cspin):
  if ((adcnum > 7) or (adcnum < 0)):
    return -1
  GPIO.output(cspin, True)

  GPIO.output(clockpin, False)  # start clock low
  GPIO.output(cspin, False)     # bring CS low

  commandout = adcnum
  commandout |= 0x18  # start bit + single-ended bit
  commandout <<= 3    # we only need to send 5 bits here
  for i in range(5):
    if (commandout & 0x80):
      GPIO.output(mosipin, True)
    else:
      GPIO.output(mosipin, False)
    commandout <<= 1
    GPIO.output(clockpin, True)
    GPIO.output(clockpin, False)

  adcout = 0
  # read in one empty bit, one null bit and 10 ADC bits
  for i in range(12):
    GPIO.output(clockpin, True)
    GPIO.output(clockpin, False)
    adcout <<= 1
    if (GPIO.input(misopin)):
      adcout |= 0x1

  GPIO.output(cspin, True)
        
  adcout >>= 1       # first bit is 'null' so drop it
  return adcout

if not DEBUG:
  command = "lsusb | grep \"1546:1102 U-Blox AG\""
  try:
    proc = subprocess.check_output(command, shell=True)
  except:
    raise SystemExit("Modem NOT detected.")

# change these as desired - they're the pins connected from the
# SPI port on the ADC to the Cobbler
SPICLK  = 18
SPIMISO = 23
SPIMOSI = 24
SPICS   = 25

# set up the SPI interface pins
GPIO.setup(SPIMOSI, GPIO.OUT)
GPIO.setup(SPIMISO, GPIO.IN)
GPIO.setup(SPICLK,  GPIO.OUT)
GPIO.setup(SPICS,   GPIO.OUT)

def readCTSensor(ADC_CH):
  vRMS       = 116.0  # Assumed or measured
  offset     = 1.65   # Half the ADC max voltage
  numTurns   = 2000   # 1:2000 transformer turns
  rBurden    = 3300   # Burden resistor value
  numSamples = 1000   # Number of samples before calculating RMS

#  if DEBUG:
#    print "\nCT Sensor number of turns:", numTurns
#    print "Actual burden resistor:", rBurden, "ohm"
#    print "Number of samples:", numSamples
#    print "AC (RMS Voltage):", vRMS, "V"

  voltage       = 0.0
  iPrimary      = 0.0
  acc           = 0.0
  apparentPower = 0.0
  stoveStatus   = "OFF"

  # Take a number of samples and calculate RMS current
  for i in range(0, numSamples):
    # Read ADC, convert to voltage, remove offset
    sample = readadc(ADC_CH, SPICLK, SPIMOSI, SPIMISO, SPICS)
    voltage = (sample * offset * 2) / 1024
    voltage = voltage - offset

    iPrimary = (voltage / rBurden) * numTurns

    acc += pow(iPrimary, 2)
    time.sleep(0.001)

  iRMS = math.sqrt(acc / numSamples)  # Calculate RMS from accumulated values
  apparentPower = vRMS * iRMS         # Calculate apparent power

  if ( apparentPower >= 1.5 ):
    stoveStatus = "ON"
  else:
    stoveStatus = "OFF"

  if DEBUG:
    print "Current sensor -", \
          "%.2fA," % iRMS, \
          "%.2fVA," % apparentPower, \
          "Status:", stoveStatus

  return iRMS, apparentPower, stoveStatus

# Texas Instruments LM35DZ
def readTempSensor(ADC_CH):
  analogReading  = 0.0
  tempC          = 0.0
  tempF          = 0.0
  numSamples     = 100     # Number of samples before calculating temperature

  # Take a number of samples and calculate Temperature
  for i in range(0, numSamples):
    analogReading += readadc(ADC_CH, SPICLK, SPIMOSI, SPIMISO, SPICS)
    time.sleep(0.001)

  # Vout = 10mv/C * TempC
  # TempC = t/ numSamples * (5V / 1024) / 10mV
  tempC = ( analogReading / float(numSamples) /  1024.0 ) * 5.0 / 0.01
  tempF = tempC * 1.8 + 32

  if DEBUG:
    print "Temperature -", \
          "Analog reading: %.2f," % (analogReading / numSamples), \
          "%.1fC," % tempC, \
          "%.1fF" % tempF

  return tempC, tempF

# This is a cheap version I've made of the pulseIN function in arduino
# https://www.arduino.cc/reference/en/language/functions/advanced-io/pulsein/
#
# PIN               : the pin on which you want to read the pulse.
# value             : type of pulse to read: HIGH or LOW.
# timeout (optional): the number of seconds to wait for the pulse to be 
#                      completed
# Returns the length of the pulse (in seconds) or 0 if no pulse is completed 
#   before the timeout

def pulseIn(PIN, value, timeout = None):
  if ( timeout is None):          # timeout in seconds
    timeout = 1
  
  startTime = time.time()
  while GPIO.input(PIN) != value and (time.time() - startTime) < timeout:
    continue
  pulseStart = time.time()

  while GPIO.input(PIN) == value and (time.time() - startTime) < timeout:
    continue
  pulseEnd = time.time()

  pulseDuration = pulseEnd - pulseStart
  if (pulseEnd - startTime) >= timeout or pulseDuration >= timeout:
    pulseDuration = 0.0
    
  return pulseDuration

# the distances from the ultrasonic sensor (top of tank) 
# to the min and max water level must be provided in inches.
# distance to 0% water level should be greater than distance to 100% water level
def sumpWaterLevel(dist0PercIN, dist100PercIN):
  TRIG_PIN = 5
  ECHO_PIN = 6

  GPIO.setup(TRIG_PIN, GPIO.OUT)
  GPIO.setup(ECHO_PIN, GPIO.IN)

  numSamples          = 50   # Number of samples before calculating water Level
  actualSamples       = numSamples
  pulseDuration       = 0.0
  totalPulseDuration  = 0.0

  # Take a number of samples and calculate distance
  i = 0

  while (i < numSamples):
    # Give a short LOW pulse beforehand to ensure a clean HIGH pulse
    GPIO.output(TRIG_PIN, False)
    time.sleep(0.002)
    GPIO.output(TRIG_PIN, True)
    time.sleep(0.000010)
    GPIO.output(TRIG_PIN, False)

    pulseDuration      = pulseIn(ECHO_PIN, GPIO.HIGH, 2)   # 2 seconds timeout
    if (pulseDuration > 0):
      totalPulseDuration += pulseDuration
    else:
      actualSamples -= 1    # discard reading as timed out
    i += 1

  # if less than half of the readings were unsuccessful, send timeout
  if actualSamples >= ( numSamples / 2 ):
    totalPulseDuration = pulseDuration / actualSamples

    # At 20 C the speed of sound is 343
    # Also the ultrasonic burst travels out & back
    distCM = pulseDuration * 343.0 / 2.0 * 100.0
    distIN = distCM / 2.54

    # Ultrasonic sensor is positioned on top of tank, the greater the distance 
    # is measured, the water level is lower
    waterLevel = (dist0PercIN - distIN)/(dist0PercIN - dist100PercIN)
    if ( waterLevel < 0 ):
      waterLevel = 0.0
    elif ( waterLevel > 1 ):
      waterLevel = 1.01

    if DEBUG:
      print "Ultrasonic -", \
            "Pulse duration: %.5s," % pulseDuration, \
            "Distance: %.2fcm," % distCM, \
            "%.2fin," % distIN, \
            "Level: %.2f" % waterLevel
      print numSamples
  else:
    waterLevel = 1.01      # waterLevel > 1 (100%) timeout

    if DEBUG:
      print "Ultrasonic - Pulse duration: TIMEOUT"

  return waterLevel

# Sump Pump status is simulated with a pushbutton
def sumpPumpStatus(PUMPPIN):   
  GPIO.setup(PUMPPIN, GPIO.IN)
  pumpStatusN = 0;
  numSamples = 10                 # Number of samples for sofware decoupling

  for i in range(0, numSamples):           # added some software decoupling
    pumpStatusN += GPIO.input(PUMPPIN)
    time.sleep(0.001)

  if ( pumpStatusN == 10 ):
    sumpPumpStatus = "ON"
  else:
    sumpPumpStatus = "OFF"

  if DEBUG:
    print "Sump pump: " + sumpPumpStatus

  return sumpPumpStatus

lastAP    = 0.0  # keeps track of the last apparent power value
lastSS    = "x"  # keeps track of the last Stove status
lastSL    = 0    # keeps track of the last Sump water level
lastSP    = "x"  # keeps track of the last Sump pump status
lastTF    = 0.0  # keeps track of the last F temperature value

apTolerance = 2.0  # publish to Hologram the apparent power value when 
                   # it changes more than 2 VA
tfTolerance = 0.5  # publish to Hologram the temperature value when
                   # it changes more than 0.5 F
sumpCriticalLevel = 90  # will add a route to send an email when the Sump Water
                        # level is at 90% or more

try:
  while True:
    CHANGES    = False         # we'll assume that no values have changed
    changesSTR = ""

    dUTC = datetime.datetime.utcnow()
    if DEBUG:                            # Start all readings with a blank line
      print "\n---------------------------------------------------", \
            dUTC

    # Non-invasive current sensor connected to ADC #0
    iRMS, apparentPower, stoveStatus = readCTSensor(0)

    # distance from ultrasonic sensor to bottom of sump tank: 40in
    # distance from ultrasonic sensor to water when tank full: 5in
    sumpLevel = round(100 * sumpWaterLevel(40, 5))
    sumpPump  = sumpPumpStatus(26)

    # Temperature sensor connected to ADC #1
    tempC, tempF = readTempSensor(1)

    if ( abs(apparentPower - lastAP) > apTolerance ):
      CHANGES = True
      changesSTR += "Apparent Power from %.2fVA" % lastAP + \
                    " to %.2fVA" % apparentPower + "\n"
    if ( stoveStatus != lastSS ):
      CHANGES = True
      changesSTR += "Stove Status from " + lastSS + \
                    " to " + stoveStatus + "\n"
    if ( sumpLevel - lastSL != 0 ):
      CHANGES = True
      changesSTR += "Sump water level from %.0f" % lastSL + "%" + \
                    " to %.0f" % sumpLevel + "%\n"
    if ( sumpPump != lastSP ):
      CHANGES = True
      changesSTR += "Sump pump from " + lastSP + \
                    " to " + sumpPump + "\n"
    if ( abs(tempF - lastTF) > tfTolerance ):
      CHANGES = True
      changesSTR += "Temperature from %.1f" % lastTF + u'\u00B0' + "F" + \
                    " to %.1f" % tempF + u'\u00B0' + "F" + "\n"

    json = "{\"dtUTC\":\"" + dUTC.isoformat('T') + "Z\"" + \
           ",\"iRMS\":%.2f" % iRMS + \
           ",\"apparentPower\":%.2f" % apparentPower + \
           ",\"stoveStatus\":\"" + stoveStatus + "\"" + \
           ",\"sumpLevel\":" + str(int(sumpLevel)) + \
           ",\"sumpPump\":\"" + sumpPump + "\"" + \
           ",\"tempC\":%.1f" % tempC + \
           ",\"tempF\":%.1f" % tempF + \
           "}"

    jsonEmail = "{\"dtUTC\":\"" + dUTC.isoformat('T') + "Z\"" + \
                ",\"sumpLevel\":" + str(int(sumpLevel)) + \
                ",\"sumpPump\":\"" + sumpPump + "\"" + \
                "}"

    if DEBUG:
      if CHANGES:   # there are changes, print a blank line
        print ""
      print "******************************** CHANGES = " + str(CHANGES)

    if CHANGES:
      if DEBUG:
        print changesSTR.strip()
        print json
        print jsonEmail
      else:                     # publish to Hologram console
        hologram = HologramCloud(dict(), network='cellular')

        hologram.network.disconnect()
        result = hologram.network.connect()
        if result == False:
          hologram.network.disconnect()
          raise SystemExit(" Failed to connect to cell network")

        response_code = hologram.sendMessage(json,
                                             topics = ["power-usage"])
        # Prints 'Message sent successfully'.
        print hologram.getResultString(response_code) 

        # Send an email when the Sump level is critical (or timed out)
        # and when its level has increased
        if ( sumpLevel >= sumpCriticalLevel and sumpLevel > lastSL):
          response_code = hologram.sendMessage(jsonEmail,
                                               topics = ["email-sump-level"])
          print "email-sump-level: " + hologram.getResultString(response_code) 
        
        hologram.network.disconnect()

      # save sensors reading for the next loop
      lastAP = apparentPower
      lastSS = stoveStatus
      lastSL = sumpLevel
      lastSP = sumpPump
      lastTF = tempF

    time.sleep(60) # hang out and do nothing for some time
except KeyboardInterrupt:
  #pass
  GPIO.cleanup()
  print "Quitter!"

Credits

Luis Ortiz

Luis Ortiz

1 project • 29 followers
Systems Engineer by day, avid tinkerer at night. I spend free time in DIY projects involving electronics, 3-D Printing and automation.

Comments