MyPlace customizations to the Low Power Lab Moteino Gateway Dashboard Application

// ******************************************************************************************************************************************
// myplace customizations to Metrics.js
// These changes really belong in a JSON file like settings with an app feature for maintaining the entries, but the initial goal was
// to quickly get something up and running and this offered easy access to these relatively static definitions.
// These items are used to populate the RF device select list on the myplace page.
// ******************************************************************************************************************************************
exports.myplaceOutlets = {
    camera1: {
        name:'camera1',
        label:'Camera 1',
        pi:true
    },
    camera2: {
        name:'camera2',
        label:'Camera 2',
        pi:true
    },
    camera3: {
        name:'camera3',
        label:'Camera 3',
        pi:true
    },
    outlet4: {
        name:'outlet4',
        label:'Living Room Lamp',
        pi:false
    },
    outlet5: {
        name: 'outlet5',
        label: 'Family Room Hobby Lamp',
        pi:false
    },
    outlet6: {
        name: 'outlet6',
        label: 'Upstairs Hall Lamp',
        pi:false
    },
    outlet7: {
        name: 'outlet7',
        label: 'Left Bedroom Lamp',
        pi:false
    },
    outlet8: {
        name: 'outlet8',
        label: 'Master Bedroom Lamp',
        pi:false
    },
    outlet9: {
        name: 'outlet9',
        label: 'Unused(9)',
        pi:false
    },
    outlet10: {
        name: 'outlet10',
        label: 'Unused(10)',
        pi:false
    },
    outlet11: {
        name: 'outlet11',
        label: 'Unused(11)',
        pi:false
    },
    osmcfam: {
        name: 'osmcfam',
        label: 'OSMC Family Room',
        pi:true
    },
    dustcoll: {
        name: 'dustcoll',
        label: 'Dust Collector',
        pi:false
    }
};

// ******************************************************************************************************************************************
// MyPlace additions to gateway.js
// ******************************************************************************************************************************************
// place at the beginning with other require entries
var PythonShell = require('python-shell');

// place with other "DEF" entries like settings
socket.emit('MYPLACEDEF', metricsDef.myplaceOutlets);

// place at the end of io.socket.on function definitions
  socket.on('DEVICECOMMAND', function (devSelected, device, action) {
    var options = {
      mode: 'text',
      pythonPath: '/usr/bin/python',
      pythonOptions: ['-u'],
      scriptPath: '/home/pi/gateway',
      args: [device, action]
    };

    PythonShell.run('callrfoutlet.py', options, function (err, results) {
      if (err) throw err;
      // results is an array consisting of messages collected during execution
      console.log('DEVICECOMMAND ' + device + ' ' + action + ' ' + address);
      socket.emit('DEVCMDRESPONSE', devSelected, action);
    });
  });
// END gateway.js modifications *************************************************************************************************************

// ******************************************************************************************************************************************
// MyPlace additions to index.html
// ******************************************************************************************************************************************

// add with other button definitions following settings
        <a href="#myplacepage" data-role="button" data-icon="fa-gears" data-iconpos="notext" title="myplace">MyPlace</a>

// this is the My Place page where I will add my ciustomizations. This goes with the other page definitions behind settings
 <div data-role="page" id="myplacepage">
    <div data-role="header">
      <a href="#homepage" class="ui-btn ui-corner-all ui-shadow ui-icon-home ui-btn-icon-left">Home</a>
      <h1>My Place</h1>
    </div>
    <div data-role="main" class="ui-content">
      <div class="ui-field-contain">
        <label for="deviceSelect" class="labelbold">Device:</label>
        <select id="deviceSelect" data-mini="false"></select>
      </div>
      <div data-role="controlgroup" data-type="horizontal">
        <a id="deviceCommand-On"  style="margin-left:6px;width:30px" data-role="button" data-icon="fa-chevron-up" title="poweron">On</a>
        <a id="deviceCommand-Off" style="width:30px" data-role="button" data-icon="fa-chevron-down" title="poweroff">Off</a>
      </div>
      <div data-role="controlgroup" data-type="horizontal">
        <a id="viewCamera3"  href="http://##.###.###.###:####" style="margin-left:6px;width:150px" data-role="button" data-icon="fa-chevron-right" title="viewcamera3">View Camera 3</a>
      </div>
    </div>
    <div data-role="footer">
      <a href="#homepage" class="ui-btn ui-btn-right ui-corner-all ui-shadow ui-btn-icon-left ui-icon-delete" style="background-color:#FF9B9B;margin:.5em;">Cancel</a>
    </div>
  </div>

// place with other DEF emits behind settings
  socket.on('MYPLACEDEF', function(deviceDefinition) {
      deviceDef = deviceDefinition;
      $("#deviceSelect").empty();
      $('#deviceSelect').append('<option value="">Select device...</option>');
      for(var device in deviceDef)
        $('#deviceSelect').append('<option value="' + deviceDef[device].name + '">' + deviceDef[device].label + '</option>');
      $("#deviceSelect").selectmenu();
    });

// place with other button action functions
   $("#deviceCommand-On").click("tap", function(event) {
      var devSelected = $('#deviceSelect option:selected').text();
      var device = $('#deviceSelect').val();
      var action = 'on';
      $("button,html,body").css("cursor", "progress");
      socket.emit('DEVICECOMMAND', devSelected, device, action);
    });

    $("#deviceCommand-Off").click("tap", function(event) {
      var devSelected = $('#deviceSelect option:selected').text();
      var device = $('#deviceSelect').val();
      var action = 'off';
      $("button,html,body").css("cursor", "progress");
      socket.emit('DEVICECOMMAND', devSelected, device, action);
    });

    socket.on('DEVCMDRESPONSE', function(devSelected, action) {
      $("button,html,body").css("cursor", "default");
      alert(devSelected + ' ' + action + ' completed.');
    });

// END index.html modifications -------------------------------------------------------------

//*****************************************************************************************************************
// python script to call the RF Outlet program on the Raspberry PI - pimote.
// Required on the gateway PI  -  sudo apt-get install pyro
// Note: Pyro code is based on the examples in the Pyro documentation
// callrfoutlet.py
//*****************************************************************************************************************
! /usr/bin/python
import Pyro.core
import sys

# you have to change the URI below to match your own host/port.
rfOutlet= Pyro.core.getProxyForURI("PYROLOC://xxx.xxx.x.xxx:xxxx/rfoutlet")

if (len(sys.argv) != 3):
    print "usage: callrfoutlet.py device action"
    exit(0)

rfOutlet.execRFOutletCommand(sys.argv[1], sys.argv[2])
// end callrfoutlet.py ----------------------------------------------------


// pimote-serial sketch
//**********************************************************************************************************
// PIMotei-serial sketch to use USB serial communication between a pi and moteinoUSB.
// The PI will act as a sensor for programatical input such as e-mail scraping of
// a home security system events, a PI garage door sensor, and RF Outlet automation
// using remote controlled electrical outlets. This one moteinoUSB will control several
// nodes on the gateway.
// For use with MoteinoMEGA you will have to revisit the pin definitions defined belw
// http://www.LowPowerLab.com/WeatherShield
// Used in this project: http://lowpowerlab.com/blog/2015/07/24/attic-fan-cooling-tests/
// 2015-07-23 (C) Felix Rusu of http://www.LowPowerLab.com/
// **********************************************************************************************************
// License
// **********************************************************************************************************
// This program is free software; you can redistribute it 
// and/or modify it under the terms of the GNU General    
// Public License as published by the Free Software       
// Foundation; either version 3 of the License, or        
// (at your option) any later version.                    
//                                                        
// This program is distributed in the hope that it will   
// be useful, but WITHOUT ANY WARRANTY; without even the  
// implied warranty of MERCHANTABILITY or FITNESS FOR A   
// PARTICULAR PURPOSE. See the GNU General Public        
// License for more details.                              
//                                                        
// You should have received a copy of the GNU General    
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//                                                        
// Licence can be viewed at                               
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// ***************************************************************************************************************************
#include <RFM69.h>         //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h>      //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h>           //comes with Arduino
#include <Wire.h>          //comes with Arduino

#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
                      //writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/

//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/TRANSCEIVER SETTINGS/REQUIREMENTS
//*****************************************************************************************************************************
#define GATEWAYID   1
#define NODEID      30
#define NETWORKID   100
//#define FREQUENCY     RF69_433MHZ
//#define FREQUENCY     RF69_868MHZ
#define FREQUENCY       RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY      "losHAXEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
//#define IS_RFM69HW    //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define SEND_LOOPS   15 //send data this many sleep loops (15 loops of 8sec cycles = 120sec ~ 2 minutes)
//*********************************************************************************************
#define SLEEP_FASTEST SLEEP_15MS
#define SLEEP_FAST SLEEP_250MS
#define SLEEP_SEC SLEEP_1S
#define SLEEP_LONG SLEEP_2S
#define SLEEP_LONGER SLEEP_4S
#define SLEEP_LONGEST SLEEP_8S
period_t sleepTime = SLEEP_LONGEST; //period_t is an enum type defined in the LowPower library (LowPower.h)
//*********************************************************************************************
#define BATT_MONITOR_EN A3 //enables battery voltage divider to get a reading from a battery, disable it to save power
#define BATT_MONITOR  A7   //through 1Meg+470Kohm and 0.1uF cap from battery VCC - this ratio divides the voltage to bring it below 3.3V where it is scaled to a readable range
#define BATT_CYCLES   2    //read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475  // >>> fine tune this parameter to match your voltage when fully charged
#define BATT_LOW      3.6  //(volts)
#define BATT_READ_LOOPS  SEND_LOOPS*10  // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cycles you would get ~1 hour intervals between readings
//*****************************************************************************************************************************
#define SERIAL_READ_LOOPS 7 // 15 sleep loops of 8secs = ~2m
#define LED                  9   //pin connected to onboard LED on regular Moteinos
#define BLINK_EN                 //uncomment to blink LED on every send
//#define SERIAL_EN                //comment out if you don't want any serial output
#ifdef SERIAL_EN
  #define SERIAL_BAUD   115200
  #define DEBUG(input)   {Serial.print(input);}
  #define DEBUGln(input) {Serial.println(input);}
  #define SERIALFLUSH() {Serial.flush();}
#else
  #define DEBUG(input);
  #define DEBUGln(input);
  #define SERIALFLUSH();
#endif
//*****************************************************************************************************************************

struct GARAGE_DOOR_STRUCT {
  int addr;
  int state;
  int alert;
};

struct SIMPLISAFE_STRUCT {
  int addr;
  int event;
  int state;
  int metric;
  int alert;
};

struct RFOUTLET_STRUCT {
  int addr;
  int state;
};

GARAGE_DOOR_STRUCT gd;
SIMPLISAFE_STRUCT ss;
RFOUTLET_STRUCT rfo;

int rcvStatus = 0;

RFM69 radio;
char Pstr[10];
char buffer[50];
char serbuf[50];
String inputstr;
String pimotecmd;
byte inputLen=0;
char temp[64];
 
SPIFlash flash(8, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
 
void setup(void)
{
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  while (!Serial) {
    
  }
  Serial.println("pimote-serial");
  
  gd.addr = 30;
  gd.state = 0;
  gd.alert = 0;
  ss.addr = 32;
  ss.state = 0;
  ss.metric = 0;
  ss.alert = 0;
  rfo.addr = 101;
  rfo.state = 0;
  
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
  radio.setHighPower(); //uncomment only for RFM69HW!
#endif
  radio.encrypt(ENCRYPTKEY);
  
  // Send start for all gateway nodes
  // restrict serial activity to the pimote
  //sprintf(buffer, "PIMote - transmitting at: %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  radio.setAddress(30);                    // Left Door
  radio.sendWithRetry(GATEWAYID, "START", 6);   
  radio.setAddress(31);                         // Right Door
  radio.sendWithRetry(GATEWAYID, "START", 6);
  radio.setAddress(32);                         // SimpliSafe
  radio.sendWithRetry(GATEWAYID, "START", 6);

  // RF Outlets (13 of them)
  for (int i = 101; i<114; i++) {
    radio.setAddress(i);
    radio.sendWithRetry(GATEWAYID, "START", 6);   
  }
  
  Serial.flush();
  readBattery();
}

byte sendLoops=0;
byte battReadLoops=0;
byte serialReadLoops=0;
float batteryVolts = 5;
char* BATstr="BAT:5.00v"; //longest battery voltage reading message = 9chars
byte sendLen;


void loop()
{
  
  if (battReadLoops--<=0) //only read battery every BATT_READ_LOOPS cycles
  {
    readBattery();
    battReadLoops = BATT_READ_LOOPS-1;
  }

  handleSerialInput();

/* DONT SEND NODE STATUS UNTIL AN EVENT TAKES PLACE  
  if (sendLoops--<=0)   //send readings every SEND_LOOPS
  {
    sendLoops = SEND_LOOPS-1;
    
    sprintf(buffer, "BAT:%sv DS:%i LA:%i", BATstr, leftDoor.state, leftDoor.alert);
    sendLen = strlen(buffer);
   radio.setAddress(leftDoor.addr);
    radio.sendWithRetry(GATEWAYID, buffer, sendLen, 1); //retry one time
    DEBUG(buffer); DEBUG(" (packet length:"); DEBUG(sendLen); DEBUGln(")");
    
    sprintf(buffer, "BAT:%sv DS:%i LA:%i", BATstr, rightDoor.state, rightDoor.alert);
    sendLen = strlen(buffer);
    radio.setAddress(rightDoor.addr);
    radio.sendWithRetry(GATEWAYID, buffer, sendLen, 1); //retry one time
    DEBUG(buffer); DEBUG(" (packet length:"); DEBUG(sendLen); DEBUGln(")");

 //   #ifdef BLINK_EN
 //     Blink(LED, 5);
 //   #endif  
 }
*/
  
  //When this sketch is on a node where you can afford the power to keep the radio awake all the time
  //   you can make it receive messages and also make it wirelessly programmable
  //   otherwise this section can be removed
  if (radio.receiveDone())
  {
    boolean reportStatusRequest=false;
    DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
    for (byte i = 0; i < radio.DATALEN; i++)
      DEBUG((char)radio.DATA[i]);

    // wireless programming token check - this only works when radio is kept awake to listen for WP tokens
    CheckForWirelessHEX(radio, flash, true);

    //first send any ACK to request
    DEBUG("   [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
    if (radio.ACKRequested())
    {
      radio.sendACK();
      DEBUG(" - ACK sent.");
    }
    DEBUGln();
  }
  
  Serial.flush();
  radio.sleep(); //you can comment out this line if you want this node to listen for wireless programming requests
  LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF);
  // DEBUGln("WAKEUP");
}    

String stripColon(String inputstr) {  
  byte colonIndex = inputstr.indexOf(":"); //find position of first colon
  inputstr = inputstr.substring(colonIndex+1); 
  return inputstr; 
}

void readGDData(byte targetId, String istr)
{
   char buf[64];
   
   istr.getBytes((byte*)buf, 59);
   //int n =  sscanf(buf, "%d:%d:", newState, newAlert);
   gd.state = (byte) istr.toInt();
   istr = stripColon(istr);
   gd.alert = istr.toInt();
   
//   sprintf(serbuf, "%i%i\n", newState, newAlert );
//   Serial.print(serbuf);  
}

void readSSData(byte targetId, String istr)
{ 
  char buf[64];
  
  istr.getBytes((byte*)buf, 59);
  ss.event = (byte)istr.toInt();
  istr = stripColon(istr);
  istr.getBytes((byte*)buf, 59);
  ss.state = (byte)istr.toInt();
  istr = stripColon(istr);
  istr.getBytes((byte*)buf, 59);
  ss.metric = (byte)istr.toInt();
  istr = stripColon(istr);
  istr.getBytes((byte*)buf, 59);
  ss.alert = (byte)istr.toInt();
}  

void readRFOData(byte targetId, String istr)
{
   char buf[64];
   
   istr.getBytes((byte*)buf, 59);
   rfo.state = (byte) istr.toInt();
}

//parse through any serial commands from the host (Pi)
void handleSerialInput() {
  bool valid;
  delay(4);
  Serial.flush();
  int i = 0;
  if (Serial.available() > 0)
  { 
/*
    Blink(LED, 500);
    Blink(LED, 500);
    Blink(LED, 500);
*/
    while ( Serial.available() > 0 ) {
      serbuf[i] = Serial.read();
      i++;
    }
    if (i > 0) {   
      inputstr = String(serbuf);

      pimotecmd = String(">_");
     if (inputstr.substring(0,2).equals(pimotecmd))
     {

 /*     
      Blink(LED, 200);
      Blink(LED, 200);
      Blink(LED, 200);
      Blink(LED, 200);
      Blink(LED, 200);
*/        
        valid = false;
        inputstr = inputstr.substring(2);
        byte targetId = inputstr.toInt();
        inputstr = stripColon(inputstr);
        
        sprintf(serbuf, "%i\n", targetId );
        Serial.print(serbuf);    
        switch (targetId)
        {
            case 30:
            case 31:
               readGDData(targetId, inputstr);
 
               sprintf(buffer, "BAT:%sv DS:%i LA:%i", BATstr, gd.state, gd.alert); 
                  
               valid = true;
               break;
            case 32:
               readSSData(targetId, inputstr);
               sprintf(buffer, "BAT:%sv SSS:%i SSM:%i SSA:%i", BATstr, ss.state, ss.metric, ss.alert); 
               valid = true;
               break;
            case 101:
            case 102:
            case 103:
            case 104:
            case 105:
            case 106:
            case 107:
            case 108:
            case 109:
            case 110:
            case 111:
            case 112:
            case 113: 
              readRFOData(targetId, inputstr);
              sprintf(buffer, "BAT:%sv RFO:%i", BATstr, rfo.state); 
              valid = true;
              break;
            default:
               valid = false;
               break;            
        }    

        sendLen = strlen(buffer);
        radio.setAddress(targetId);
        radio.sendWithRetry(GATEWAYID, buffer, sendLen, 1); //retry one time
        //Serial.println(buffer);
     }    
   }   
  }
}  

void readBattery()
{
  unsigned int readings=0;
  
  //enable battery monitor on WeatherShield (via mosfet controlled by A3)
  pinMode(BATT_MONITOR_EN, OUTPUT);
  digitalWrite(BATT_MONITOR_EN, LOW);

  for (byte i=0; i<5; i++) //take several samples, and average
    readings+=analogRead(BATT_MONITOR);
  
  //disable battery monitor
  pinMode(BATT_MONITOR_EN, INPUT); //highZ mode will allow p-mosfet to be pulled high and disconnect the voltage divider on the weather shield
    
  batteryVolts = BATT_FORMULA(readings / 5.0);
  dtostrf(batteryVolts,3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
  if (batteryVolts <= BATT_LOW) BATstr = "LOW";
}

void Blink(byte PIN, byte DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN,HIGH);
  delay(DELAY_MS/2);
  digitalWrite(PIN,LOW);
  delay(DELAY_MS/2);  
}
// end pimote-serial sketch

// primate pyro server
#! /usr/bin/python

import os
import sys
import time
import serial
import subprocess
import shlex
from subprocess import Popen, PIPE
import Pyro.core
import Pyro.naming

class PIMote(Pyro.core.ObjBase):
    def __init__(self):
        Pyro.core.ObjBase.__init__(self)
        
    def sendCommand(self, data):
        print ("sendCommand")
        ser = serial.Serial("/dev/ttyUSB0", baudrate=115200, xonxoff=True, timeout=3.0)
        out = ser.readline()
        print ('initial read...' + out)
        print(data)
        ser.write(data.encode('ascii')+'\n')
        limit = 1
        n = 0
        while n < limit:
            out = ser.readline()
            if len(out) > 0:
               print('rc= ' + out)
               exit
            time.sleep(.1)
            n+=1
        print("tries={0}".format(n))
        ser.close()
#--------------------------------------------------
# Pyro remote procedure calls
#--------------------------------------------------
    def GDEvent(self, addr, state, alert):
        data = ">_{0}:{1}:{2}".format(addr, state, alert)
        self.sendCommand(data)
    def SSEvent(self, addr, ss_event, state, ss_metric, alert):
        data = ">_{0}:{1}:{2}:{3}:{4}".format(addr, ss_event, state, ss_metric, alert)
        self.sendCommand(data)
    def RFOEvent(self, addr, state):
        data = ">_{0}:{1}".format(addr, state)
        self.sendCommand(data)

FNULL = open(os.devnull, 'w')
if __name__ == "__main__":
    # Create a Pyro server and register our module with it
    #ser = serial.Serial("/dev/ttyUSB0", baudrate=115200, xonxoff=True, timeout=3.0)
    #print(ser.name)
    #out = ser.readline()
    #print ('initial read...' + out)

    Pyro.core.initServer()
    daemon = Pyro.core.Daemon(host=“gateway1.pngxxx.xxx.xxx.xxx", port=xxxx)
    uri = daemon.connect(PIMote(),"pimote")
    print "pimote port:", daemon.port
    print "pimote uri:", uri
    daemon.requestLoop()
# end of primate.py

## rfoutlet.py
#! /usr/bin/python

import os
import sys
import time
import shlex
import glob
import smbus
import struct
import binascii
import Pyro.core
import Pyro.naming
from subprocess import Popen, PIPE 
from datetime import datetime
#---------------------------------------------
# global variables
#---------------------------------------------
USAGE = "usage: rfoutlet.py device action (on|off)"

#---------------------------------------------
# allowable PIs
#---------------------------------------------
devices = ['camera1', 'camera2', 'camera3', 
           'outlet4', 'outlet5', 'outlet6',
           'outlet7', 'outlet8', 'outlet9',
           'outlet10', 'outlet11', 'osmcfam', 'dustcoll']

pidevices = ['camera1', 'camera2', 'camera3']

#---------------------------------------------
# actions
#---------------------------------------------
actions = ['on', 'off']
actioncodes = {
    'on' : '1',
    'off' : '0'
}

#---------------------------------------------
# outlet on/off  codes
# 0327 = ETEKCITY #0327
# f3 = Foval f3
# f5 = Foval f5
#---------------------------------------------

rfoutletcodes = { 
    '0327-1-on'  : '267571 192',
    '0327-1-off' : '267580 191',
    '0327-2-on'  : '267715 191',
    '0327-2-off' : '267724 192',
    '0327-3-on'  : '268035 191',
    '0327-3-off' : '268044 192',
    '0327-4-on'  : '269571 192',
    '0327-4-off' : '269580 193',
    '0327-5-on'  : '275715 192',
    '0327-5-off' : '275724 192',
    'f3-1-on'    : '1270287 309',
    'f3-1-off'   : '1270286 309',
    'f3-2-on'    : '1270285 309',
    'f3-2-off'   : '1270284 313',
    'f3-3-on'    : '1270283 312',
    'f3-3-off'   : '1270282 313',
    'f3-4-on'    : '1270279 313',
    'f3-4-off'   : '1270278 313',
    'f3-5-on'    : '1270277 313',
    'f3-5-off'   : '1270276 313',
    'f5-1-on'    : '1233423 309',
    'f5-1-off'   : '1233422 312',
    'f5-2-on'    : '1233421 309',
    'f5-2-off'   : '1233420 309',
    'f5-3-on'    : '1233419 309',
    'f5-3-off'   : '1233418 312',
    'f5-4-on'    : '1233415 312',
    'f5-4-off'   : '1233414 312',
    'f5-5-on'    : '1233413 309',
    'f5-5-off'   : '1233412 309'
}

# --------------------------------------------
# device outlet assignments
# --------------------------------------------
deviceoutlets = {
    'camera1'  : '0327-1',
    'camera2'  : '0327-2',
    'camera3'  : '0327-3',
    'outlet4'  : '0327-4',
    'outlet5'  : '0327-5',
    'outlet6'  : 'f5-1',
    'outlet7'  : 'f5-2',
    'outlet8'  : 'f5-3',
    'outlet9'  : 'f5-4',
    'outlet10' : 'f5-5',
    'dustcoll' : 'f3-1',
    'osmcfam'  : 'f3-2',
    'outlet11' : 'f3-3'
}

device_nodes = {
    'camera1'  : '101',
    'camera2'  : '102',
    'camera3'  : '103',
    'outlet4'  : '104',
    'outlet5'  : '105',
    'outlet6'  : '106',
    'outlet7'  : '107',
    'outlet8'  : '108',
    'outlet9'  : '109',
    'outlet10' : '110',
    'dustcoll' : '111',
    'osmcfam'  : '112',
    'outlet11' : '113'
}

#---------------------------------------------
# coupled devices
# when this device is powered on/off,
# also include the associated device(s)
# multiples are separated by colon character
#---------------------------------------------
coupleddevices = {
    'camera1' : 'irspot1'
}


class RFOutlet(Pyro.core.ObjBase):
    def __init__(self):
        Pyro.core.ObjBase.__init__(self)
        self.master = smbus.SMBus(1)
        
    #---------------------------------------------
    # execCmd
    # usage exitcode, out, err = exec_cmd(cmd)
    #---------------------------------------------
    def execCmd(self, cmd):
        """
        Execute the external command and get its exitcode, stdout and stderr.
        """
        args = shlex.split(cmd)
 
        proc = Popen(args, stdout=PIPE, stderr=PIPE)
        out, err = proc.communicate()
        exitcode = proc.returncode
        return exitcode, out, err

    def rfOutletArgs(self, device, action):
        return rfoutletcodes[deviceoutlets[device] + '-' + action]

    def rfOutletCommand(self, device, action, delay):
        cmd = '/home/pi/rfoutlet/runrfoutlet.sh ' + self.rfOutletArgs(device, action) + ' ' + delay 
        exitcode, out, err = self.execCmd(cmd)
        return exitcode

    def piDown(self, device):
        cmd = '/home/pi/rfoutlet/pidown.sh ' + device
        exitcode, out, err = self.execCmd(cmd)
        if exitcode == 0:
            exitcode = self.rfOutletCommand(device, 'off', '1')
        return exitcode
     
    def performAction(self, device, action):
        if action == 'off':
            if device in pidevices:
                self.piDown(device) 
            else:
                self.rfOutletCommand(device, action, '0')
        else:
            self.rfOutletCommand(device, action, '0')
       # update gateway node state
        pimote.RFOEvent(int(device_nodes[device]), int(actioncodes[action]))

#--------------------------------------------------
# Pyro remote procedure calls
#--------------------------------------------------
    def execRFOutletCommand(self, device, action):
        validDevice = device in devices
        validAction = action in actions
        if validDevice and validAction:
            self.performAction(device, action)

# ==============================
# MAIN
# ==============================
if __name__ == "__main__":
    pimote = Pyro.core.getProxyForURI("PYROLOC://xxx.xxx.xxx.xxx:xxxx/pimote")
    # Create a Pyro server and register our module with it
    Pyro.core.initServer()
    daemon = Pyro.core.Daemon(host=“xxx.xxx.xxx.xxx”, port=xxxx)
    uri = daemon.connect(RFOutlet(),"rfoutlet")
    print "rfoutlet port:", daemon.port
    print "rfoutlet uri:", uri
    daemon.requestLoop()
# end of rfoutlet.py
