1 // *********************************************************************
    2 //   Custom DPA Handler code example - PIR controlled lighting demo    *
    3 // *********************************************************************
    4 // Copyright (c) IQRF Tech s.r.o.
    5 //
    6 // File:    $RCSfile: CustomDpaHandler-PIRlighting.c,v $
    7 // Version: $Revision: 1.21 $
    8 // Date:    $Date: 2019/05/06 07:13:14 $
    9 //
   10 // Revision history:
   11 //   2017/03/13  Release for DPA 3.00
   12 //   2015/08/05  Release for DPA 2.20
   13 //
   14 // *********************************************************************
   15 
   16 // Online DPA documentation http://www.iqrf.org/DpaTechGuide/
   17 
   18 // Default IQRF include (modify the path according to your setup)
   19 #include "IQRF.h"
   20 
   21 // Default DPA header (modify the path according to your setup)
   22 #include "DPA.h"
   23 // Default Custom DPA Handler header (modify the path according to your setup)
   24 #include "DPAcustomHandler.h"
   25 
   26 // This is an example of PIR controlled lighting. The application can be configured and controlled by EEPROM and RAM DPA peripherals respectively.
   27 // 
   28 // When a PIR input is active the device sends non-network Peer2Peer packet that contains the address of the device.
   29 // PIR input is then inactive for next PIRdelay seconds in order not to jam with many same packets.
   30 // Other nodes that receive the Peer2Peer packet will set the LIGHT output if the address of the sender matches any of the condition bytes 
   31 // from the 16 byte long list stored at EEPROM peripheral from address 1. The light will stay on for number of second stored at EEPROM peripheral at address 0.
   32 // Condition bytes:
   33 //  0         End of the condition bytes list
   34 //  255       Any node will switch the light on. This condition byte overrides condition bytes described below.
   35 //  1 - 239   Only node having address equal this value switches the light on.
   36 //  240 - 254 Only node having address that differs ( ConditionByte - 239 ) from my address maximum switches the light on. 
   37 //            E.g. when my address is 10 and condition byte is 241, then only nodes with address 8-12 would switch my light on.
   38 //
   39 // If bit PeripheralRam[0].0 is set, PIR input is inactive. This allows to disable PIR detectors e.g. during the day.
   40 //
   41 // ! Please not that Peer2Peer packets must be enabled at the HWP configuration
   42 // This example works only at STD mode, not at LP mode
   43 
   44 // Initialize the EEPROM peripheral area
   45 #pragma cdata[ __EESTART + PERIPHERAL_EEPROM_START ] = \
   46   /* Set light timeout to 5 seconds. */ \
   47   5, \
   48   /* Allow switching the light on only by nodes with address 1 and address 2 and with address that differs 2 maximum. */ \
   49   1, 2, MAX_ADDRESS + 2, \
   50   /* End of the list. */ \
   51   0
   52 // Note: symbol MAX_ADDRESS equals 239
   53 
   54 //############################################################################################
   55 
   56 // PIR input
   57 #define PIR   buttonPressed
   58 // Light output
   59 #define LIGHT _LEDG
   60 
   61 // Fixed PIR repetition delay [s]
   62 #define PIRdelay    2
   63 
   64 //############################################################################################
   65 
   66 // Length of the address list
   67 #define ADDR_LIST_LEN 16
   68 
   69 // 40 ms counter used to measure 1 s
   70 static uns8 tmrCounter40ms;
   71 
   72 // Light on counter
   73 static uns8 lightTimeout;
   74 
   75 // Initial light timeout counter value, read from EEPROM peripheral at address 0
   76 static uns8 lightTimeoutSetting;
   77 
   78 // Switches the light on, initializes timeout 
   79 void LightOn();
   80 // Starts new second
   81 void StartSecond();
   82 
   83 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location!
   84 //############################################################################################
   85 bit CustomDpaHandler()
   86 //############################################################################################
   87 {
   88   // Handler presence mark
   89   clrwdt();
   90 
   91   // Place for local static variables used only within CustomDpaHandler() among more events
   92 
   93   // If TRUE, PIR was activated and after timeout
   94   static bit PIRactivated;
   95 
   96   // Detect DPA event to handle
   97   switch ( GetDpaEvent() )
   98   {
   99     // -------------------------------------------------
  100     case DpaEvent_Interrupt:
  101       // Do an extra quick background interrupt work
  102       // ! The time spent handling this event is critical.If there is no interrupt to handle return immediately otherwise keep the code as fast as possible.
  103       // ! Make sure the event is the 1st case in the main switch statement at the handler routine.This ensures that the event is handled as the 1st one.
  104       // ! It is desirable that this event is handled with immediate return even if it is not used by the custom handler because the Interrupt event is raised on every MCU interrupt and the "empty" return handler ensures the shortest possible interrupt routine response time.
  105       // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy.
  106       // ! Make sure race condition does not occur when accessing those variables at other places.
  107       // ! Make sure( inspect.lst file generated by C compiler ) compiler does not create any hidden temporary local variable( occurs when using division, multiplication or bit shifts ) at the event handler code.The name of such variable is usually Cnumbercnt.
  108       // ! Do not call any OS functions except setINDFx().
  109       // ! Do not use any OS variables especially for writing access.
  110       // ! All above rules apply also to any other function being called from the event handler code, although calling any function from Interrupt event is not recommended because of additional MCU stack usage.
  111 
  112       // PIR timeout counter
  113       static uns8 PIRtimeout;
  114 
  115       //  If TMR6 interrupt occurred
  116       if ( TMR6IF )
  117       {
  118         // Unmask interrupt
  119         TMR6IF = 0;
  120         // Decrement count
  121         if ( --tmrCounter40ms == 0 )
  122         {
  123           // 1 s is over
  124           StartSecond();
  125 
  126           // Decrement PIR counter
  127           if ( PIRtimeout != 0 )
  128             PIRtimeout--;
  129 
  130           // Decrement light counter
  131           if ( lightTimeout != 0 )
  132           {
  133             lightTimeout--;
  134             if ( lightTimeout == 0 )
  135               // Switch off the light
  136               LIGHT = 0;
  137           }
  138         }
  139       }
  140 
  141       // Last state of PIR input
  142       static bit lastPIR;
  143 
  144       // PIR is on?
  145       if ( PIR )
  146       {
  147         // Was PIR off and PIR timeout is over and not disabled?
  148         if ( !lastPIR && PIRtimeout == 0 && !PeripheralRam[0].0 )
  149         {
  150           // Initializes PIR timeout counter
  151           PIRtimeout = PIRdelay;
  152           // PIR is activated
  153           PIRactivated = TRUE;
  154         }
  155 
  156         lastPIR = TRUE;
  157       }
  158       else
  159         lastPIR = FALSE;
  160 
  161 DpaHandleReturnTRUE:
  162       return TRUE;
  163 
  164       // -------------------------------------------------
  165     case DpaEvent_Idle:
  166       // Do a quick background work when RF packet is not received
  167 
  168       // PIR was activated, send the P2P packet
  169       if ( PIRactivated )
  170       {
  171         // Note: Here Listen before Talk technique should be implemented to avoid collisions
  172 
  173         // PIR not activated any more
  174         PIRactivated = FALSE;
  175 
  176         // Save RF settings and set new ones
  177         setNonetMode();
  178         setRFmode( _TX_STD | _STDL );
  179 
  180         // Prepare P2P packet
  181         // Header
  182         bufferRF[0] = 'P';
  183         // Address
  184         bufferRF[1] = ntwADDR;
  185         // Packet length
  186         DLEN = 2;
  187         PIN = 0;
  188         // Transmit the prepared packet
  189         RFTXpacket();
  190 
  191         // Restore RF settings
  192         DpaApiSetRfDefaults();
  193         setNodeMode();
  194 
  195         LightOn();
  196       }
  197 
  198       break;
  199 
  200       // -------------------------------------------------
  201     case DpaEvent_PeerToPeer:
  202       // Called when peer-to-peer (non-networking) packet is received
  203 
  204       // Note: peer-to-peer packet should be better protected when not used at this example
  205 
  206       // Is my peer-to-peer packet (check length and content)?
  207       if ( DLEN == 2 && bufferRF[0] == 'P' )
  208       {
  209         // Read list of condition bytes from EEPROM[1..x]
  210         eeReadData( PERIPHERAL_EEPROM_START + 1, ADDR_LIST_LEN );
  211         // Force list end
  212         bufferINFO[ADDR_LIST_LEN] = 0;
  213         // Pointer before 1st address
  214         FSR0 = bufferINFO - 1;
  215         // Loop the list
  216         while ( *++FSR0 != 0 )
  217         {
  218           // Any address or address match?
  219           if ( *FSR0 == 0xff || *FSR0 == bufferRF[1] )
  220           {
  221             LightOn();
  222             break;
  223           }
  224 
  225           if ( *FSR0 > MAX_ADDRESS )
  226           {
  227             // Compute absolute address difference
  228             int8 addrDiff = bufferRF[1] - ntwADDR;
  229             if ( addrDiff < 0 )
  230               addrDiff = -addrDiff;
  231 
  232             // Get maximum difference
  233             uns8 absDelta = *FSR0 - MAX_ADDRESS;
  234             // Is max. difference met?
  235             if ( (uns8)addrDiff <= absDelta )
  236             {
  237               LightOn();
  238               break;
  239             }
  240           }
  241         }
  242       }
  243 
  244       break;
  245 
  246       // -------------------------------------------------
  247     case DpaEvent_Init:
  248       // Do a one time initialization work before main loop starts
  249 
  250       // Setup TMR6 to generate ticks on the background (ticks every 10ms)
  251 #if F_OSC == 16000000
  252       PR6 = 250 - 1;
  253       T6CON = 0b0.1001.1.11;    // Prescaler 64, Postscaler 10, 64 * 10 * 250 = 160000 = 4MHz * 40ms
  254 #else
  255 #error Unsupported oscillator frequency
  256 #endif
  257 
  258       TMR6IE = TRUE;
  259 
  260       // Initialize 1 s timer
  261       StartSecond();
  262 
  263 RefreshLightDelay:
  264       // Read light delay from EEPROM[0]
  265       lightTimeoutSetting = eeReadByte( PERIPHERAL_EEPROM_START + 0 );
  266       break;
  267 
  268       // -------------------------------------------------
  269     case DpaEvent_Notification:
  270       // Called after DPA request was processed and after DPA response was sent
  271 
  272       // Anything written to the EEPROM?
  273       // (could be optimized by checking the EEPROM address that was written to)
  274       if ( _PNUM == PNUM_EEPROM && _PCMD == CMD_EEPROM_WRITE )
  275         goto RefreshLightDelay;
  276 
  277       break;
  278 
  279       // -------------------------------------------------
  280     case DpaEvent_AfterSleep:
  281       // Called on wake-up from sleep
  282       TMR6IE = TRUE;
  283       TMR6ON = TRUE;
  284       break;
  285 
  286       // -------------------------------------------------
  287     case DpaEvent_BeforeSleep:
  288       // Called before going to sleep   (the same handling as DpaEvent_DisableInterrupts event)
  289 
  290       // -------------------------------------------------
  291     case DpaEvent_DisableInterrupts:
  292       // Called when device needs all hardware interrupts to be disabled (before Reset, Restart, LoadCode, Remove bond, and Run RFPGM)
  293       // Must not use TMR6 any more
  294       TMR6ON = FALSE;
  295       TMR6IE = FALSE;
  296       break;
  297   }
  298 
  299   return FALSE;
  300 }
  301 
  302 //############################################################################################
  303 void LightOn()
  304 //############################################################################################
  305 {
  306   lightTimeout = lightTimeoutSetting;
  307   LIGHT = 1;
  308   StartSecond();
  309 }
  310 
  311 //############################################################################################
  312 void StartSecond()
  313 //############################################################################################
  314 {
  315   tmrCounter40ms = 1000 / 40;
  316 }
  317 
  318 //############################################################################################
  319 // Default Custom DPA Handler header; 2nd include to implement Code bumper to detect too long code of the Custom DPA Handler (modify the path according to your setup) 
  320 #include "DPAcustomHandler.h"
  321 //############################################################################################