DNE ENGJoey HamadEric Hill
Published © GPL3+

Cloud Hosted Intelligent Sprinkler System

Current sprinkler systems leave much to be desired, so we've designed a sprinkler system that works smarter and saves water!

ExpertShowcase (no instructions)20 hours3,684

Things used in this project

Hardware components

LAUNCHXL-CC1310 SimpleLink CC1310 Sub-1GHz LaunchPad
Texas Instruments LAUNCHXL-CC1310 SimpleLink CC1310 Sub-1GHz LaunchPad
Microcontroller for wireless sensor nodes.
×4
LAUNCHXL-CC1310 SimpleLink CC1310 Sub-1GHz LaunchPad
Texas Instruments LAUNCHXL-CC1310 SimpleLink CC1310 Sub-1GHz LaunchPad
Microcontroller as part of the sprinkler relay hub.
×1
BeagleBone Black
BeagleBoard.org BeagleBone Black
Microcontroller for the logical portion of the sprinkler hub.
×1
SparkFun Snappable Protoboard
SparkFun Snappable Protoboard
Assembly structure for relays and connections.
×1
Rhino PSA-CA004 2Amp 12V DC Adapter
Delivers power to the solenoids.
×1
LDO Voltage Regulator
Regulates power from the charging circuit to the CC1310.
×4
Adafruit Lithium Ion Polymer Battery
Power supply for wireless sensor nodes.
×4
Adafruit USB / DC / Solar Lithium Ion/Polymer charger - v2
Regulated power from the solar panel to the battery and to the CC1310.
×4
Gravity: Analog Capacitive Soil Moisture Sensor- Corrosion Resistant
DFRobot Gravity: Analog Capacitive Soil Moisture Sensor- Corrosion Resistant
Moisture sensor for reading ground moisture levels in backyard.
×4
Adafruit Medium 6V 2W Solar panel - 2.0 Watt
Source of charging power for the Lithium Ion Battery to maintain extended periods of run time.
×4
Adafruit Power Relay FeatherWing
Switched power to solenoids.
×4
2N3904 NPN BJT
Allowed digital pin control of relays.
×4

Software apps and online services

Code Composer Studio
Texas Instruments Code Composer Studio
Programming for CC1310 hub and sensor nodes.
SimpleLink SDK
Texas Instruments SimpleLink SDK
Texas Instruments Sensor Controller Studio
Used to read values from the moisture sensors.
Texas Instruments SmartRF Studio
Used to set up RF communications.
Android Studio
Android Studio
Used to create smartphone app for user sprinkler controls.
AWS EC2
Amazon Web Services AWS EC2
Service used to host website to access user controls.
Creo
Used for 3D modeling of sensor node casing and hub casing.
Debian Linux
Operating system for Beagle Bone Black
Python
Development software for data management and weather logic.
Wordpress
Site development platform for easy-to-use user interface.
OpenWeatherMap API
Weather data provider to use with sprinkler logic.

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Used to put together relays, transistor circuit, and CC1310 connections onto protoboard.
3D Printer (generic)
3D Printer (generic)
Used to fabricate sensor and hub casings.
Mastech MS8217 Autorange Digital Multimeter
Digilent Mastech MS8217 Autorange Digital Multimeter
Used during testing phase to measure current consumption and voltage I/O for the system.

Story

Read more

Code

sender.c

C/C++
Code for the outdoor sensor nodes to enable low power capabilities and send moisture data, sensor IDs, and error codes to the main hub.
// Frequency: 869.52501 MHz @625 bps, Legacy Long Range (10 kchips/s, 2-FSK, conv. FEC r=1/2 K=7, DSSS SF=8, Tx dev.: 5 kHz, RX BW: 40 kHz
int sensorID=0;
#include <stdio.h>
#include <xdc/std.h>
#include <stdlib.h>
#include <xdc/runtime/System.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/drivers/PIN.h>
#include <ti/sysbios/knl/Swi.h>
#include <ti/drivers/rf/RF.h>
#include "Board.h"
#include "smartrf_settings.h"
#include "scif.h"

#define BV(x)    (1 << (x))
#define TASKSTACKSIZE   512
#define TX_TASK_STACK_SIZE 1024
#define TX_TASK_PRIORITY   2
#define PAYLOAD_LENGTH     30
#define PACKET_INTERVAL    (uint32_t)(4000000*0.5f) /* Set packet interval to 500ms */

static void senderFxn(UArg arg0, UArg arg1);

/***** Variable declarations *****/
static Task_Params txTaskParams;
Task_Struct txTask;    /* not static so you can see in ROV */
static uint8_t txTaskStack[TX_TASK_STACK_SIZE];
Task_Struct task0Struct;
Char task0Stack[TASKSTACKSIZE];
uint32_t capSensorMoisture1 = 0;

static RF_Object rfObject;
static RF_Handle rfHandle;
static PIN_Handle ledPinHandle;
static PIN_State ledPinState;
static Task_Params txTaskParams;
Task_Struct txTask;    /* not static so you can see in ROV */
static uint8_t txTaskStack[TX_TASK_STACK_SIZE];

uint32_t time;
static uint8_t packet[PAYLOAD_LENGTH];
static uint16_t seqNumber;
uint32_t time;
uint8_t errorState=0;
static PIN_Handle pinHandle;

PIN_Config pinTable[] =
{
    Board_LED0 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    Board_LED1 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    PIN_TERMINATE
};

void scCtrlReadyCallback(void)
{

}

void scTaskAlertCallback(void)
{
    processTaskAlert();
}

void processTaskAlert(void)
{
    // Clear the ALERT interrupt source
    scifClearAlertIntSource();

    // Acknowledge the ALERT event
    scifAckAlertEvents();
}

void TxTask_init()
{
    Task_Params_init(&txTaskParams);
    txTaskParams.stackSize = TX_TASK_STACK_SIZE;
    txTaskParams.priority = TX_TASK_PRIORITY;
    txTaskParams.stack = &txTaskStack;
    txTaskParams.arg0 = (UInt)1000000;
    Task_construct(&txTask, senderFxn, &txTaskParams, NULL);
}

Void senderFxn(UArg arg0, UArg arg1)
{
    uint32_t time;
    RF_Params rfParams;
    RF_Params_init(&rfParams);
    RF_cmdPropTx.pktLen = PAYLOAD_LENGTH;
    RF_cmdPropTx.pPkt = packet;
    RF_cmdPropTx.startTrigger.triggerType = TRIG_ABSTIME;
    RF_cmdPropTx.startTrigger.pastTrig = 1;
    RF_cmdPropTx.startTime = 0;

    uint32_t standbyDurationUs = 0;

   // Initialize the Sensor Controller
   scifOsalInit();
   scifOsalRegisterCtrlReadyCallback(scCtrlReadyCallback);
   scifOsalRegisterTaskAlertCallback(scTaskAlertCallback);
   scifInit(&scifDriverSetup);

   // Set the Sensor Controller task tick interval to 1 second
   uint32_t rtc_Hz = 1;  // 1Hz RTC
   scifStartRtcTicksNow(0x00010000 / rtc_Hz);

   // Start Sensor Controller task
   scifStartTasksNbl(BV(SCIF_ADC_LEVEL_TRIGGER_TASK_ID));

    while(1)
    {
        //Small delay to allow reset
        standbyDurationUs = 0.5*1000000;
        Task_sleep(standbyDurationUs / Clock_tickPeriod);
        //Reset Variables
        capSensorMoisture1 = 0;

        // Sensor Value Manipulation
        capSensorMoisture1 = scifTaskData.adcLevelTrigger.output.adcValue;


        capSensorMoisture1 = capSensorMoisture1/5; //Wet=89, Dry=207

        capSensorMoisture1 = 207-capSensorMoisture1; //Wet=118, Dry=0

        capSensorMoisture1 = capSensorMoisture1*0.834; //Dry=0, Wet=99


        //Error Check (2.8V Reg.)
        if(capSensorMoisture1 > 150 || capSensorMoisture1 < -50){
            errorState=1;
        }else{
            errorState=0;
        }

        //Clipping (2.8V Reg.)
        if(capSensorMoisture1 > 99){
            capSensorMoisture1 = 99;
        }
        if(capSensorMoisture1 < 0){
            capSensorMoisture1 = 0;
        }


        /* Request access to the radio */
        rfHandle = RF_open(&rfObject, &RF_prop, (RF_RadioSetup*)&RF_cmdPropRadioDivSetup, &rfParams);

        /* Set the frequency */
        RF_postCmd(rfHandle, (RF_Op*)&RF_cmdFs, RF_PriorityNormal, NULL, 0);

        /* Get current time */
        time = RF_getCurrentTime();

        /* Create packet with incrementing sequence number and random payload */
        packet[0] = (uint8_t)(seqNumber >> 8);
        packet[1] = (uint8_t)(seqNumber++);
        packet[2] = sensorID;
        packet[3] = errorState;
        uint8_t i;
        for (i = 4; i < PAYLOAD_LENGTH; i++)
        {
            packet[i] = capSensorMoisture1;
        }

        /* Set absolute TX time to utilize automatic power management */
        time += PACKET_INTERVAL;
        RF_cmdPropTx.startTime = time;

        /* Send packet */
        RF_EventMask result = RF_runCmd(rfHandle, (RF_Op*)&RF_cmdPropTx, RF_PriorityNormal, NULL, 0);
        if (!(result & RF_EventLastCmdDone))
        {
            /* Error */
            while(1);
        }
        //Make sure all LED's are off before going into low power mode
        PIN_setOutputValue(pinHandle, Board_LED0, 0);
        PIN_setOutputValue(pinHandle, Board_LED1, 0);

        //Close RF Communication to reduce current consumption
        RF_close(rfHandle);

        // Stop task to reduce current consumption
        while (scifWaitOnNbl(0) != SCIF_SUCCESS);
        scifStopTasksNbl(BV(SCIF_ADC_LEVEL_TRIGGER_TASK_ID));
        scifStopRtcTicks();

        standbyDurationUs = (1800)*1000000; //seconds******************1800s for 30 min
        Task_sleep(standbyDurationUs / Clock_tickPeriod);

        // Restart task
        scifResetTaskStructs(BV(SCIF_ADC_LEVEL_TRIGGER_TASK_ID), 0);
        scifStartRtcTicksNow(0x00010000 / rtc_Hz);
        scifStartTasksNbl(BV(SCIF_ADC_LEVEL_TRIGGER_TASK_ID));
    }
}

/*
 *  ======== main ========
 */
int main(void)
{
    Board_initGeneral();

    TxTask_init();

    /* Start BIOS */
    BIOS_start();

    return (0);
}

receiver.c

C/C++
Code for the CC1310 Hub to receive data from sensor nodes
// Frequency: 869.52501 MHz @625 bps, Legacy Long Range (10 kchips/s, 2-FSK, conv. FEC r=1/2 K=7, DSSS SF=8, Tx dev.: 5 kHz, RX BW: 40 kHz
#include <stdlib.h>
#include <xdc/std.h>
#include <xdc/cfg/global.h>
#include <xdc/runtime/System.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/drivers/rf/RF.h>
#include <ti/drivers/PIN.h>
#include <ti/drivers/UART.h>
#include <driverlib/rf_prop_mailbox.h>
#include "Board.h"
#include "smartrf_settings.h"
#include "RFQueue.h"
#include "smartrf_settings.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

static PIN_Handle ledPinHandle;
static PIN_State ledPinState;

PIN_Config pinTable[] =
{
    Board_LED0  | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    Board_LED1  | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    Board_DIO8  | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, //TL
    Board_DIO9  | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, //TR
    Board_DIO13 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, //Not used
    Board_DIO14 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, //BL
    Board_DIO15 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, //BR
    PIN_TERMINATE
};

#define TASKSTACKSIZE   768
Task_Struct task0Struct;
Char task0Stack[TASKSTACKSIZE];

#define RX_TASK_STACK_SIZE 1024
#define RX_TASK_PRIORITY   2
#define SOLENOID_TASK_STACK_SIZE     1024
#define SOLENOID_TASK_PRIORITY  3

#define DATA_ENTRY_HEADER_SIZE 8  /* Constant header size of a Generic Data Entry */
#define MAX_LENGTH             30 /* Max length byte the radio will accept */
#define NUM_DATA_ENTRIES       2  /* NOTE: Only two data entries supported at the moment */
#define NUM_APPENDED_BYTES     2  /* The Data Entries data field will contain:
                                   * 1 Header byte (RF_cmdPropRx.rxConf.bIncludeHdr = 0x1)
                                   * Max 30 payload bytes
                                   * 1 status byte (RF_cmdPropRx.rxConf.bAppendStatus = 0x1) */

static void rxTaskFunction(UArg arg0, UArg arg1);
static void solenoidTaskFunction(UArg arg0, UArg arg1);
static void callback(RF_Handle h, RF_CmdHandle ch, RF_EventMask e);

static Task_Params rxTaskParams;
static Task_Params solenoidTaskParams;
Task_Struct rxTask;    /* not static so you can see in ROV */
Task_Struct solenoidTask;
static uint8_t rxTaskStack[RX_TASK_STACK_SIZE];
static uint8_t solenoidTaskStack[SOLENOID_TASK_STACK_SIZE];

static RF_Object rfObject;
static RF_Handle rfHandle;

#if defined(__TI_COMPILER_VERSION__)
    #pragma DATA_ALIGN (rxDataEntryBuffer, 4);
        static uint8_t rxDataEntryBuffer[RF_QUEUE_DATA_ENTRY_BUFFER_SIZE(NUM_DATA_ENTRIES,
                                                                 MAX_LENGTH,
                                                                 NUM_APPENDED_BYTES)];
#elif defined(__IAR_SYSTEMS_ICC__)
    #pragma data_alignment = 4
        static uint8_t rxDataEntryBuffer[RF_QUEUE_DATA_ENTRY_BUFFER_SIZE(NUM_DATA_ENTRIES,
                                                                 MAX_LENGTH,
                                                                 NUM_APPENDED_BYTES)];
#elif defined(__GNUC__)
        static uint8_t rxDataEntryBuffer [RF_QUEUE_DATA_ENTRY_BUFFER_SIZE(NUM_DATA_ENTRIES,
            MAX_LENGTH, NUM_APPENDED_BYTES)] __attribute__ ((aligned (4)));
#else
    #error This compiler is not supported.
#endif

static dataQueue_t dataQueue;
static rfc_dataEntryGeneral_t* currentDataEntry;
static uint8_t packetLength;
static uint8_t* packetDataPointer;

static PIN_Handle pinHandle;

static uint8_t packet[MAX_LENGTH + NUM_APPENDED_BYTES - 1]; /* The length byte is stored in a separate variable */

void RxTask_init(PIN_Handle ledPinHandle) {
    pinHandle = ledPinHandle;
    Task_Params_init(&rxTaskParams);
    rxTaskParams.stackSize = RX_TASK_STACK_SIZE;
    rxTaskParams.priority = RX_TASK_PRIORITY;
    rxTaskParams.stack = &rxTaskStack;
    rxTaskParams.arg0 = (UInt)1000000;
    Task_Params_init(&solenoidTaskParams);
    solenoidTaskParams.stackSize = SOLENOID_TASK_STACK_SIZE;
    solenoidTaskParams.priority = SOLENOID_TASK_PRIORITY;
    solenoidTaskParams.stack = &solenoidTaskStack;
    solenoidTaskParams.arg0 = (UInt)1000000;
    Task_construct(&rxTask, rxTaskFunction, &rxTaskParams, NULL);
    Task_construct(&solenoidTask, solenoidTaskFunction, &solenoidTaskParams, NULL);
}

int sensorID=0;
int packetData0=0;
int packetData1=0;
int packetData2=0;
int packetData3=0;
int senderErrorState=0;
int receiverErrorState=0;
int solenoid0=0;
int solenoid1=0;
int solenoid2=0;
int solenoid3=0;
int i=0;

static void solenoidTaskFunction(UArg arg0, UArg arg1){
    //UART SETUP
    char input;
    char value[5];
    char output[4];
    UART_Handle uart;
    UART_Params uartParams;
    UART_Params_init(&uartParams);
    uartParams.writeDataMode = UART_DATA_BINARY;
    uartParams.readDataMode = UART_DATA_BINARY;
    uartParams.readReturnMode = UART_RETURN_FULL;
    uartParams.readEcho = UART_ECHO_OFF;
    uartParams.baudRate = 9600;
    uart = UART_open(Board_UART0, &uartParams);

    if(uart == NULL){
        System_abort("Error opening the UART");
    }
    while(1){
        //Wait for serial command
        for(i=0; i<5; i++){
            UART_read(uart, &input, 1);
            value[i]=input;
            if(input == '9')
                i=0;
            if((value[i]!='0' && value[i]!='1' && value[i]!='9') || ((input!='9' && i==0))){
                i=-1;
            }
        }

        //Distribute
        solenoid0=value[1]-'0';
        solenoid1=value[2]-'0';
        solenoid2=value[3]-'0';
        solenoid3=value[4]-'0';

        PIN_setOutputValue(pinHandle, Board_DIO8, solenoid0);
        PIN_setOutputValue(pinHandle, Board_DIO9, solenoid1);
        PIN_setOutputValue(pinHandle, Board_DIO14, solenoid2);
        PIN_setOutputValue(pinHandle, Board_DIO15, solenoid3);

        //Response
        if(packetData0 < 10){
            sprintf(output, "z00%d", packetData0);
            UART_write(uart, output, sizeof(output));
        }
        else{
            sprintf(output, "z0%d", packetData0);
            UART_write(uart, output, sizeof(output));
        }
        if(packetData1 < 10){
            sprintf(output, "z10%d", packetData1);
            UART_write(uart, output, sizeof(output));
        }
        else{
            sprintf(output, "z1%d", packetData1);
                    UART_write(uart, output, sizeof(output));
        }
        if(packetData2 < 10){
            sprintf(output, "z20%d", packetData2);
            UART_write(uart, output, sizeof(output));
        }
        else{
            sprintf(output, "z2%d", packetData2);
            UART_write(uart, output, sizeof(output));
        }
        if(packetData3 < 10){
            sprintf(output, "z30%d", packetData3);
            UART_write(uart, output, sizeof(output));
        }
        else{
            sprintf(output, "z3%d", packetData3);
                    UART_write(uart, output, sizeof(output));
        }
    }//endwhile
}

static void rxTaskFunction(UArg arg0, UArg arg1)
{
    RF_Params rfParams;
    RF_Params_init(&rfParams);

    if( RFQueue_defineQueue(&dataQueue,
                            rxDataEntryBuffer,
                            sizeof(rxDataEntryBuffer),
                            NUM_DATA_ENTRIES,
                            MAX_LENGTH + NUM_APPENDED_BYTES))
    {
        /* Failed to allocate space for all data entries */
        while(1);
    }

    /* Modify CMD_PROP_RX command for application needs */
    RF_cmdPropRx.pQueue = &dataQueue;           /* Set the Data Entity queue for received data */
    RF_cmdPropRx.rxConf.bAutoFlushIgnored = 1;  /* Discard ignored packets from Rx queue */
    RF_cmdPropRx.rxConf.bAutoFlushCrcErr = 1;   /* Discard packets with CRC error from Rx queue */
    RF_cmdPropRx.maxPktLen = MAX_LENGTH;        /* Implement packet length filtering to avoid PROP_ERROR_RXBUF */
    RF_cmdPropRx.pktConf.bRepeatOk = 1;
    RF_cmdPropRx.pktConf.bRepeatNok = 1;

    /* Request access to the radio */
    rfHandle = RF_open(&rfObject, &RF_prop, (RF_RadioSetup*)&RF_cmdPropRadioDivSetup, &rfParams);

    /* Set the frequency */
    RF_postCmd(rfHandle, (RF_Op*)&RF_cmdFs, RF_PriorityNormal, NULL, 0);

    /* Enter RX mode and stay forever in RX */
    RF_runCmd(rfHandle, (RF_Op*)&RF_cmdPropRx, RF_PriorityNormal, &callback, IRQ_RX_ENTRY_DONE);

    while(1);
}

void callback(RF_Handle h, RF_CmdHandle ch, RF_EventMask e)
{
    if (e & RF_EventRxEntryDone)
    {
        currentDataEntry = RFQueue_getDataEntry();
        packetLength      = *(uint8_t*)(&currentDataEntry->data);
        packetDataPointer = (uint8_t*)(&currentDataEntry->data + 1);
        memcpy(packet, packetDataPointer, (packetLength + 1));
        RFQueue_nextEntry();

        //Remove bad data
        int maxCount=0;
        int index=-1; //sentinels
        int i,j;
        for(i=4; i<MAX_LENGTH; i++){
            int count=0;
            for(j=4; j<MAX_LENGTH; j++){
                if(packet[i] == packet[j])
                    count++;
            }
            if(count > maxCount){
                maxCount=count;
                index=i;
            }
        }//End Sort

        //Report sender error
        if(packet[3] != 0){
            senderErrorState=1;
        }

         //Report receiver error
        if(sensorID != 0 && sensorID != 1 && sensorID != 2 && sensorID != 3){
            receiverErrorState=1;
        }


        if(receiverErrorState == 0){
            //Distribute Data to the Correct Sensor IDs
            sensorID=packet[2];
            if(sensorID == 0){
                packetData0=packet[index];
            }
            if(sensorID == 1){
                packetData1=packet[index];
            }
            if(sensorID == 2){
                packetData2=packet[index];
            }
            if(sensorID == 3){
                packetData3=packet[index];
            }
        }

        //Green LED on if moisture > 20% detected
        PIN_setOutputValue(pinHandle, Board_LED1, packet[index]>20);

        //Turn on Red LED if sender or receiver error is generated
        PIN_setOutputValue(pinHandle, Board_LED0, receiverErrorState==1 || senderErrorState==1);
    }
}

/*
 *  ======== main ========
 */
int main(void)
{
    Board_initGeneral();

    /* Open pins */
    ledPinHandle = PIN_open(&ledPinState, pinTable);
    if(!ledPinHandle)
    {
        System_abort("Error initializing board LED pins\n");
    }

    /* Initialize task */
    RxTask_init(ledPinHandle);

    /* Start BIOS */
    BIOS_start();

    return (0);
}

smartrf_settings.c

C/C++
Setup configuration for RF communication between CC1310 devices. Used by the sender and receiver.
//*********************************************************************************
// Generated by SmartRF Studio version 2.12.1 (build#160)
// The applied template is compatible with CC13x0 SDK 2.30.xx.xx
// Device: CC1310 Rev. B (2.1)
//
//*********************************************************************************


//*********************************************************************************
// Parameter summary
// RX Address0: 0xAA 
// RX Address1: 0xBB 
// RX Address Mode: No address check 
// Frequency: 869.52501 MHz
// Data Format: Serial mode disable 
// Deviation: 5.000 kHz
// Packet Length Config: Variable 
// Max Packet Length: 255 
// Packet Length: 20 
// Packet Data: 255 
// RX Filter BW: 39 kHz
// Symbol Rate: 10.00061 kBaud
// Sync Word Length: 32 Bits 
// TX Power: 14 dBm (requires define CCFG_FORCE_VDDR_HH = 1 in ccfg.c, see CC13xx/CC26xx Technical Reference Manual)
// Whitening: No whitening 

#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(driverlib/rf_mailbox.h)
#include DeviceFamily_constructPath(driverlib/rf_common_cmd.h)
#include DeviceFamily_constructPath(driverlib/rf_prop_cmd.h)
#include <ti/drivers/rf/RF.h>
#include DeviceFamily_constructPath(rf_patches/rf_patch_cpe_lrm.h)
#include DeviceFamily_constructPath(rf_patches/rf_patch_rfe_lrm.h)
#include "smartrf_settings.h"


// TI-RTOS RF Mode Object
RF_Mode RF_prop =
{
    .rfMode = RF_MODE_PROPRIETARY_SUB_1,
    .cpePatchFxn = &rf_patch_cpe_lrm,
    .mcePatchFxn = 0,
    .rfePatchFxn = &rf_patch_rfe_lrm
};


// Overrides for CMD_PROP_RADIO_DIV_SETUP
uint32_t pOverrides[] =
{
    // override_use_patch_prop_lrm.xml
    // PHY: Use MCE ROM bank 3, RFE RAM patch
    MCE_RFE_OVERRIDE(0,3,0,1,0,0),
    // override_synth_prop_863_930_div5.xml
    // Synth: Set recommended RTRIM to 7
    HW_REG_OVERRIDE(0x4038,0x0037),
    // Synth: Set Fref to 4 MHz
    (uint32_t)0x000684A3,
    // Synth: Configure fine calibration setting
    HW_REG_OVERRIDE(0x4020,0x7F00),
    // Synth: Configure fine calibration setting
    HW_REG_OVERRIDE(0x4064,0x0040),
    // Synth: Configure fine calibration setting
    (uint32_t)0xB1070503,
    // Synth: Configure fine calibration setting
    (uint32_t)0x05330523,
    // Synth: Set loop bandwidth after lock to 20 kHz
    (uint32_t)0x0A480583,
    // Synth: Set loop bandwidth after lock to 20 kHz
    (uint32_t)0x7AB80603,
    // Synth: Configure VCO LDO (in ADI1, set VCOLDOCFG=0x9F to use voltage input reference)
    ADI_REG_OVERRIDE(1,4,0x9F),
    // Synth: Configure synth LDO (in ADI1, set SLDOCTL0.COMP_CAP=1)
    ADI_HALFREG_OVERRIDE(1,7,0x4,0x4),
    // Synth: Use 24 MHz XOSC as synth clock, enable extra PLL filtering
    (uint32_t)0x02010403,
    // Synth: Configure extra PLL filtering
    (uint32_t)0x00108463,
    // Synth: Increase synth programming timeout (0x04B0 RAT ticks = 300 us)
    (uint32_t)0x04B00243,
    // override_phy_rx_aaf_bw_0xd.xml
    // Rx: Set anti-aliasing filter bandwidth to 0xD (in ADI0, set IFAMPCTL3[7:4]=0xD)
    ADI_HALFREG_OVERRIDE(0,61,0xF,0xD),
    // override_phy_gfsk_rx.xml
    // Rx: Set LNA bias current trim offset to 3
    (uint32_t)0x00038883,
    // Rx: Freeze RSSI on sync found event
    HW_REG_OVERRIDE(0x6084,0x35F1),
    // override_phy_gfsk_pa_ramp_agc_reflevel_0x1a.xml
    // Tx: Configure PA ramping setting (0x41). Rx: Set AGC reference level to 0x1A.
    HW_REG_OVERRIDE(0x6088,0x411A),
    // Tx: Configure PA ramping setting
    HW_REG_OVERRIDE(0x608C,0x8213),
    // override_phy_lrm_rom_dsss8.xml
    // PHY: Configure DSSS=8
    HW_REG_OVERRIDE(0x505C,0x073C),
    // override_phy_rx_rssi_offset_5db.xml
    // Rx: Set RSSI offset to adjust reported RSSI by +5 dB (default: 0), trimmed for external bias and differential configuration
    (uint32_t)0x00FB88A3,
    // TX power override
    // Tx: Set PA trim to max (in ADI0, set PACTL0=0xF8)
    ADI_REG_OVERRIDE(0,12,0xF8),
    (uint32_t)0xFFFFFFFF
};


// CMD_PROP_RADIO_DIV_SETUP
// Proprietary Mode Radio Setup Command for All Frequency Bands
rfc_CMD_PROP_RADIO_DIV_SETUP_t RF_cmdPropRadioDivSetup =
{
    .commandNo = 0x3807,
    .status = 0x0000,
    .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
    .startTime = 0x00000000,
    .startTrigger.triggerType = 0x0,
    .startTrigger.bEnaCmd = 0x0,
    .startTrigger.triggerNo = 0x0,
    .startTrigger.pastTrig = 0x0,
    .condition.rule = 0x1,
    .condition.nSkip = 0x0,
    .modulation.modType = 0x0,
    .modulation.deviation = 0x14,
    .symbolRate.preScale = 0xF,
    .symbolRate.rateWord = 0x199A,
    .symbolRate.decimMode = 0x0,
    .rxBw = 0x20,
    .preamConf.nPreamBytes = 0x5,
    .preamConf.preamMode = 0x0,
    .formatConf.nSwBits = 0x20,
    .formatConf.bBitReversal = 0x0,
    .formatConf.bMsbFirst = 0x0,
    .formatConf.fecMode = 0x8,
    .formatConf.whitenMode = 0x0,
    .config.frontEndMode = 0x0,
    .config.biasMode = 0x1,
    .config.analogCfgMode = 0x0,
    .config.bNoFsPowerUp = 0x0,
    .txPower = 0xA73F,
    .pRegOverride = pOverrides,
    .centerFreq = 0x0365,
    .intFreq = 0x8000,
    .loDivider = 0x05
};


// CMD_FS
// Frequency Synthesizer Programming Command
rfc_CMD_FS_t RF_cmdFs =
{
    .commandNo = 0x0803,
    .status = 0x0000,
    .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
    .startTime = 0x00000000,
    .startTrigger.triggerType = 0x0,
    .startTrigger.bEnaCmd = 0x0,
    .startTrigger.triggerNo = 0x0,
    .startTrigger.pastTrig = 0x0,
    .condition.rule = 0x1,
    .condition.nSkip = 0x0,
    .frequency = 0x0365,
    .fractFreq = 0x8667,
    .synthConf.bTxMode = 0x0,
    .synthConf.refFreq = 0x0,
    .__dummy0 = 0x00,
    .__dummy1 = 0x00,
    .__dummy2 = 0x00,
    .__dummy3 = 0x0000
};


// CMD_PROP_TX
// Proprietary Mode Transmit Command
rfc_CMD_PROP_TX_t RF_cmdPropTx =
{
    .commandNo = 0x3801,
    .status = 0x0000,
    .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
    .startTime = 0x00000000,
    .startTrigger.triggerType = 0x0,
    .startTrigger.bEnaCmd = 0x0,
    .startTrigger.triggerNo = 0x0,
    .startTrigger.pastTrig = 0x0,
    .condition.rule = 0x1,
    .condition.nSkip = 0x0,
    .pktConf.bFsOff = 0x0,
    .pktConf.bUseCrc = 0x1,
    .pktConf.bVarLen = 0x1,
    .pktLen = 0x14,
    .syncWord = 0x00000000,
    .pPkt = 0 // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
};


// CMD_PROP_RX
// Proprietary Mode Receive Command
rfc_CMD_PROP_RX_t RF_cmdPropRx =
{
    .commandNo = 0x3802,
    .status = 0x0000,
    .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
    .startTime = 0x00000000,
    .startTrigger.triggerType = 0x0,
    .startTrigger.bEnaCmd = 0x0,
    .startTrigger.triggerNo = 0x0,
    .startTrigger.pastTrig = 0x0,
    .condition.rule = 0x1,
    .condition.nSkip = 0x0,
    .pktConf.bFsOff = 0x0,
    .pktConf.bRepeatOk = 0x0,
    .pktConf.bRepeatNok = 0x0,
    .pktConf.bUseCrc = 0x1,
    .pktConf.bVarLen = 0x1,
    .pktConf.bChkAddress = 0x0,
    .pktConf.endType = 0x0,
    .pktConf.filterOp = 0x0,
    .rxConf.bAutoFlushIgnored = 0x0,
    .rxConf.bAutoFlushCrcErr = 0x0,
    .rxConf.bIncludeHdr = 0x1,
    .rxConf.bIncludeCrc = 0x0,
    .rxConf.bAppendRssi = 0x0,
    .rxConf.bAppendTimestamp = 0x0,
    .rxConf.bAppendStatus = 0x1,
    .syncWord = 0x00000000,
    .maxPktLen = 0xFF,
    .address0 = 0xAA,
    .address1 = 0xBB,
    .endTrigger.triggerType = 0x1,
    .endTrigger.bEnaCmd = 0x0,
    .endTrigger.triggerNo = 0x0,
    .endTrigger.pastTrig = 0x0,
    .endTime = 0x00000000,
    .pQueue = 0, // INSERT APPLICABLE POINTER: (dataQueue_t*)&xxx
    .pOutput = 0 // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
};

sprinkler_hub.py

Python
Persistently running script for BeagleBoard. Upkeeps the system and handles data, weather, and logic.
import requests
import json
import datetime
import time
import subprocess
try:
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

import re
from bs4 import BeautifulSoup

# API Information
zipCode = "75035"
apiKey = "7bb3876c673b5e44af8873139d59fadd"
openWeatherCurrent = "http://192.241.245.161/data/2.5/weather?zip=" + zipCode + ",us&APPID=" + apiKey
openWeather5Day = "http://192.241.245.161/data/2.5/forecast?zip=" + zipCode + ",us&APPID=" + apiKey

# Site parser resources
url = 'http://23.22.156.78/tables/'
solenoids = None
sprinklerStatus = [None] * 4

# Parameters

# Desired Moisture Levels
dm1 = 50
dm2 = 50
dm3 = 50
dm4 = 50

## Day dictionary
dayDict =   {
    "Sunday": 0,
    "Monday": 1,
    "Tuesday": 2,
    "Wednesday": 3,
    "Thursday": 4,
    "Friday": 5,
    "Saturday": 6
    }
    
## Schedule Class
class Schedule: 
    def __init__(self, day, start, end): 
        self.day = day
        self.start = start 
        self.end = end 

## Schedule Class
class Solenoids: 
    def __init__(self, s1, s2, s3, s4): 
        self.s1 = s1
        self.s2 = s2
        self.s3 = s3
        self.s4 = s4    
        
########### INITIAL PARAMETERS ############
timeMatched = False
moistureLevel = False
isNotRaining = True
isNotForecastedToRain = True
sprinklerOn = False
runProgram = True

lev1 = 0
lev2 = 0
lev3 = 0
lev4 = 0

s1 = False
s2 = False
s3 = False
s4 = False

webRefereshInterval = 5
weatherRefereshInterval = 3600

## Main Program
while runProgram:
    
    # Gather moisture levels from Text file
    moistureLevels = open("sensor.txt", "r")
    mLevels = moistureLevels.readlines()    
    lev1 = int(mLevels[0],10)
    lev2 = int(mLevels[1],10) - 100
    lev3 = int(mLevels[2],10) - 200
    lev4 = int(mLevels[3],10) - 300
    
    # Check if ready to referesh weather
    if weatherRefereshInterval == 3600:
        curTime = datetime.datetime.now().strftime('%H:%M')
        curDay = datetime.datetime.now().strftime("%A")
    
        # Get JSON for current weather and 5 day weather
        try:
            currentInfo = requests.get(url = openWeatherCurrent)
            # Load JSON
            currentWeather = json.loads(currentInfo.text)
            # Parse JSON
            currentConditions = currentWeather["weather"][0]["main"]            
        except requests.exceptions.ConnectionError:
            print("Current Weather connection refused. ");
            webRefereshInterval = 3570
        try:
            fiveDayInfo = requests.get(url = openWeather5Day)
            fiveDay = json.loads(fiveDayInfo.text)  
            # Store each day for reference
            day1 = fiveDay["list"][0]["weather"][0]["main"]
            day2 = fiveDay["list"][1]["weather"][0]["main"]
            day3 = fiveDay["list"][2]["weather"][0]["main"]
            day4 = fiveDay["list"][3]["weather"][0]["main"]
            day5 = fiveDay["list"][4]["weather"][0]["main"]
        except requests.exceptions.ConnectionError:
            print("5-Day Weather connection refused. ")
            webRefereshInterval = 3570
        
        # Reset Weather interval
        weatherRefereshInterval = 0
    
    # Check if ready to refresh from website
    try:
        if webRefereshInterval == 5:
            soup = BeautifulSoup(urlopen(url), "html.parser")
            data = []
            schedules = [None] * 7
            
            # Parse scheduling table and add to class
            for rows in soup.find_all('tr'):
                for td in rows.find_all('td'):
                    data.append(td.text)
            
            # Add schedule values       
            schedules[0] = Schedule(data[1], data[2], data[3])
            schedules[1] = Schedule(data[5], data[6], data[7])
            schedules[2] = Schedule(data[9], data[10], data[11])
            schedules[3] = Schedule(data[13], data[14], data[15])
            schedules[4] = Schedule(data[17], data[18], data[19])
            schedules[5] = Schedule(data[21], data[22], data[23])
            schedules[6] = Schedule(data[25], data[26], data[27])
            
            # Add manual override values
            solenoids = Solenoids(data[29], data[30], data[31], data[32])
                        
            # Reset Web Refresh Interval            
            webRefereshInterval = 0
    except Exception:
        print("Unable to retrieve schedule. ")

    ## Flag current conditions ##
    if currentConditions == "Rain" or currentConditions == "Shower Rain" or currentConditions == "Thunderstorm" or currentConditions == "Snow" or currentConditions == "Drizzle":
        isNotRaining = False
    else:
        isNotRaining = True
        
    ## Flag forecast ##
    if day1 == "Rain" or day1 == "Shower Rain" or day1 == "Thunderstorm" or day1 == "Snow" or day1 == "Drizzle":
        isNotForecastedToRain = False
    else:
        if day2 == "Rain" or day2 == "Shower Rain" or day2 == "Thunderstorm" or day2 == "Snow" or day2 == "Drizzle":
            isNotForecastedToRain = False
        else:
            if day3 == "Rain" or day3 == "Shower Rain" or day3 == "Thunderstorm" or day3 == "Snow" or day3 == "Drizzle":
                isNotForecastedToRain = False
            else:
                isNotForecastedToRain = True
    
    ## Get time ##
    curTime = datetime.datetime.now().strftime('%H:%M')
    curDay = datetime.datetime.now().strftime("%A")
    dayNum = dayDict[curDay]
    
    ## Get Schedule ## 
    todayStart = schedules[dayNum].start
    todayStart = todayStart[:-3]
    todayEnd = schedules[dayNum].end
    todayEnd = todayEnd[:-3]
    
    ## Check schedule ##
    if curTime == todayStart or (curTime > todayStart and curTime < todayEnd):
        timeMatched = True
        
    if timeMatched == True and curTime == todayEnd:
        timeMatched = False
        
    statusFile = open("status.txt", "w+")
    
    # Flag solenoids
    s1 = (lev1 < dm1) or (int(solenoids.s1) == 1)
    s2 = (lev2 < dm2) or (int(solenoids.s2) == 1)
    s3 = (lev3 < dm3) or (int(solenoids.s3) == 1)
    s4 = (lev4 < dm4) or (int(solenoids.s4) == 1)
    
    ## Decision Tree ## 
    # If these conditions are met, write the elgibility of each solenoid (based on moisture) to the file
    if timeMatched and isNotRaining and isNotForecastedToRain:
        print("Scheduled Mode")
        print("S1: " + str(s1==True) + " S2: " + str(s2==True) + " S3: " + str(s3==True) + " S4: " + str(s4==True))
        statusString = str(int(s1 == True)) + str(int(s2 == True)) + str(int(s3 == True)) + str(int(s4 == True))
        statusFile.write(statusString)
        
    # If the conditions are not met, just worry about manual override
    else:
        print("Manual Override Mode")
        print(str(solenoids.s1) + str(solenoids.s2) + str(solenoids.s3) + str(solenoids.s4))
        statusString = str(solenoids.s1) + str(solenoids.s2) + str(solenoids.s3) + str(solenoids.s4)
        statusFile.write(statusString)

    print("Calling irrigation script");
    subprocess.call(["python3", "irrigation2.py"])
    
    # Update timers 
    weatherRefereshInterval = weatherRefereshInterval + 5           
    webRefereshInterval = webRefereshInterval + 5

    # Sleep for 5 seconds
    time.sleep(5)
    

smartrf_settings.h

C/C++
Header file for smartrf_settings.c
#ifndef _SMARTRF_SETTINGS_H_
#define _SMARTRF_SETTINGS_H_

//*********************************************************************************
// Generated by SmartRF Studio version 2.12.1 (build#160)
// The applied template is compatible with CC13x0 SDK 2.30.xx.xx
// Device: CC1310 Rev. B (2.1)
//
//*********************************************************************************
#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(driverlib/rf_mailbox.h)
#include DeviceFamily_constructPath(driverlib/rf_common_cmd.h)
#include DeviceFamily_constructPath(driverlib/rf_prop_cmd.h)
#include <ti/drivers/rf/RF.h>


// TI-RTOS RF Mode Object
extern RF_Mode RF_prop;

// RF Core API commands
extern rfc_CMD_PROP_RADIO_DIV_SETUP_t RF_cmdPropRadioDivSetup;
extern rfc_CMD_FS_t RF_cmdFs;
extern rfc_CMD_PROP_TX_t RF_cmdPropTx;
extern rfc_CMD_PROP_RX_t RF_cmdPropRx;

// RF Core API Overrides
extern uint32_t pOverrides[];

#endif // _SMARTRF_SETTINGS_H_

Execution code

C/C++
Execution code for Sensor Controller Studio
// Enable the ADC
adcEnableSync(ADC_REF_FIXED, ADC_SAMPLE_TIME_2P7_US, ADC_TRIGGER_MANUAL);
// Set DIO3 High
gpioSetOutput(AUXIO_O_DIGITAL03);
output.a = 0;
while(output.a < 65535){
    output.a = output.a + 1;
}


// Sample the analog sensor
adcGenManualTrigger();
adcReadFifo(output.adcValue);

// Disable the ADC
adcDisable();

// Set DIO3 Low
gpioClearOutput(AUXIO_O_DIGITAL03);

// Schedule the next execution
fwScheduleTask(1);

irrigation2.py

Python
Script for handling serial communication between CC1310 and Beagle Bone Black.
import time
import datetime
import serial
import os
from os import path
import sys


# public variables
sensors = []  # list of sensor readings
wait = True
sensor_count = 10 # the zero based count of sensors


def pwr_solenoid(solenoid0=0, solenoid1=0, solenoid2=0, solenoid3=0):
    # Defaults are for low signal values

    # compile output
    output = '9{solenoid0}{solenoid1}{solenoid2}{solenoid3}' \
        .format(solenoid0=solenoid0, solenoid1=solenoid1, solenoid2=solenoid2, solenoid3=solenoid3).encode()

    with serial.Serial('/dev/ttyACM0', baudrate=9600) as ser:
        print("created connection to '/dev/ttyACM0'\n")
        print("going to send:\t'{}'".format(output))
        ser.write(output)
        ser.reset_output_buffer()

        # for testing to console
        print("Value sent to the uC:\t'{}'".format(output.decode()))

        # ***** READ from UART *****

        #ser.in_waiting >= 12:
        val = str(ser.read(16).decode()) # (3).decode()[2:])
        
        #exit()

        raw_sensors = val.split('z')
        if raw_sensors[0] == '':
            raw_sensors = raw_sensors[1:]
        # raw_sensors = str(val).split('z')

        # update the sensor values
        sensor_count = len(raw_sensors)
        data = ''

        for i in range(0, sensor_count):
            data += "{}\n".format(raw_sensors[i])
            # data = "{sensor}{humidity}\n".format(sensor=raw_sensors[i][1:1], humidity=raw_sensors[i][2:1])

        # output to file
        filename = "sensor.txt"
        with open(filename, "w") as file:
            file.write("{input}\n".format(input=data))
            print("data received:\n{data}".format(data=data))

        ser.close()


class SensorReading(object):

    # _sensorID = 'X'
    # _humidity = 0

    def __init__(self, sensorID, humidity):
        self._sensorID = sensorID
        self._humidity = humidity

    def __str__(self):
        return "Sensor ID:\t{sensorID}\tHumidity:\t{humidity}%\tDTS:\t{dts}" \
            .format(sensorID=self._sensorID, humidity=self._humidity,
                    dts=datetime.datetime.utcnow().strftime("%a%d%m%Y"))

    @property
    def humidity(self):
        return self._humidity

    @humidity.setter
    def humidity(self, value):
        self._humidity = value

    @property
    def sensorID(self):
        return self._sensorID

    @sensorID.setter
    def sensorID(self, value):
        self._sensorID = value


def main():
    os.system("clear")

    # check arguments for  path to the file
    if len(sys.argv) > 1:
        filepath = sys.argv[1]
    else:
        filepath = "{basepath}/home/debian/status.txt".format(basepath=os.path.dirname(sys.argv[0]))

    print("path used: '{path}'".format(path=filepath))

    # create the list of sensors (hardwired to 10 at the moment)

    # make sure that the list of sensor values is clear
    sensors.clear()

    for i in range(0, sensor_count):
        sensors.append(SensorReading(sensorID=i, humidity=0))
    '''
    print("Enter solenoid values in form of 1/0 (1 == HIGH, 0 == LOW) or 'exit' to exit\n")
    print('\n')
    print("default value will remain '0000' until altered...\n")
    print("Note, after altered values are input, it will wait for input from UART||USB\n")
    print('\n')
    '''

    solenoid = '0000'
    # while wait:
    # solenoid = input("please input solenoid signal values.\n")
    with open(filepath, "r") as file:
        solenoid = file.readline()
    solenoid = solenoid.rstrip()
    print (solenoid)

    if solenoid.lower() == "exit":
        #break
        exit(1)
    elif solenoid.lower() == "ports":
        import serial.tools.list_ports
        ports = [tuple(p) for p in list(serial.tools.list_ports.comports())]
        print(ports)
    elif solenoid.isdigit():
        # checks to make sure all characters are digits (does not check for correctness of
        # individual characters
        print("isdigit()")
        pwr_solenoid(solenoid0=solenoid[0:1], solenoid1=solenoid[1:2], solenoid2=solenoid[2:3], solenoid3=solenoid[3:4])
        # time.sleep(3)
        # read_from_uart()
    # else:
    #    continue


if __name__ == "__main__":
    main()

Credits

DNE ENG

DNE ENG

1 project • 4 followers
Joey Hamad

Joey Hamad

1 project • 4 followers
Eric Hill

Eric Hill

1 project • 3 followers
Thanks to Saman Gharagozloo, Justin Dy, and Neil Mustafa.

Comments