1 // ********************************************************************* 2 // Custom DPA Handler - Beaming template * 3 // ********************************************************************* 4 // Copyright (c) IQRF Tech s.r.o. 5 // 6 // File: $RCSfile: CustomDpaHandler-SensorBeaming-Temperature.c,v $ 7 // Version: $Revision: 1.30 $ 8 // Date: $Date: 2023/11/24 10:36:39 $ 9 // 10 // Revision history: 11 // 2023/03/07 Release for DPA 4.30 12 // 2022/10/05 Release for DPA 4.18 13 // 2022/02/24 Release for DPA 4.17 14 // 2021/08/20 Release for DPA 4.16 15 // 16 // ********************************************************************* 17 18 // Online DPA documentation https://doc.iqrf.org/DpaTechGuide/ 19 20 // Default IQRF include (modify the path according to your setup) 21 #include "IQRF.h" 22 23 // Default DPA header (modify the path according to your setup) 24 #include "DPA.h" 25 // Default Custom DPA Handler header (modify the path according to your setup) 26 #include "DPAcustomHandler.h" 27 28 // IQRF standards header (modify the path according to your setup) 29 #include "standard/IQRFstandard.h" 30 #include "standard/IQRF_HWPID.h" 31 32 // Uncomment the following includes if the respective library is needed 33 //#include "NFC.c" 34 35 //############################################################################################ 36 37 // Unused pins are set (during the Reset event) as outputs to minimize power consumption. Modify the code according to your needs. 38 39 // Define to pulse LEDG on every Idle event and after beaming has been transmitted. Also regular beaming is every 5 s instead of "default" 60 s and shorter go2beaming timeout 40 #define DEBUGBeaming 41 42 #define _HWPID_ 0x5E7F 43 #define _HWPIDver_ 0x0300 // 3.00 44 45 // Timeout in seconds to start beaming in case of network inactivity (655 seconds is maximum) 46 #ifndef DEBUGBeaming 47 #define BEAMING_TIMEOUT 120 48 #else 49 #define BEAMING_TIMEOUT 20 50 #endif 51 52 // Variable to store sensor value at Get?_????() methods. 53 uns16 sensorValue; 54 55 // Beaming function 56 void Beaming(); 57 58 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location! 59 //############################################################################################ 60 // https://doc.iqrf.org/DpaTechGuide/pages/custom-dpa-handler.html 61 bit CustomDpaHandler() 62 //############################################################################################ 63 { 64 #ifndef DpaEvent_MenuActivated 65 static bit buttonOnInit; 66 #else 67 static bit startBeamingAtIdle; 68 #endif 69 70 // Handler presence mark 71 clrwdt(); 72 73 // Detect DPA event to handle (unused event handlers can be commented out or even deleted) 74 switch ( GetDpaEvent() ) 75 { 76 // ------------------------------------------------- 77 case DpaEvent_Interrupt: 78 // Do an extra quick background interrupt work 79 // ! 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. 80 // ! 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. 81 // ! 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. 82 // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy. 83 // ! Make sure race condition does not occur when accessing those variables at other places. 84 // ! 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. 85 // ! Do not call any OS functions except setINDFx(). 86 // ! Do not use any OS variables especially for writing access. 87 // ! 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. 88 // https://doc.iqrf.org/DpaTechGuide/pages/EventInterrupt.html 89 return Carry; 90 91 // ------------------------------------------------- 92 case DpaEvent_Idle: 93 // Do a quick background work when RF packet is not received 94 // https://doc.iqrf.org/DpaTechGuide/pages/idle.html 95 96 #ifdef DEBUGBeaming 97 // On-line mode indication 98 pulseLEDG(); 99 #endif 100 101 #ifndef DpaEvent_MenuActivated 102 if ( buttonPressed ) 103 { 104 if ( !buttonOnInit && ntwADDR != TEMPORARY_ADDRESS && buttonPressed ) 105 // Go to Beaming mode 106 Beaming(); 107 } 108 else 109 buttonOnInit = FALSE; 110 #else 111 if ( startBeamingAtIdle ) 112 { 113 startBeamingAtIdle = FALSE; 114 // Go to Beaming mode 115 Beaming(); 116 } 117 #endif 118 119 // Start beaming after a defined inactivity time 120 captureTicks(); 121 if ( param3 > (uns16)BEAMING_TIMEOUT * 100 ) 122 { 123 if ( ntwADDR != TEMPORARY_ADDRESS ) 124 Beaming(); 125 else 126 goto _startCapture; 127 } 128 129 break; 130 131 // ------------------------------------------------- 132 case DpaEvent_Reset: 133 // Called after module is reset 134 // https://doc.iqrf.org/DpaTechGuide/pages/ResetEvent.html 135 { 136 static bit wasReset; 137 if ( !wasReset ) 138 { 139 wasReset = TRUE; 140 141 // To minimize the power consumption, no MCU pin must be left as a digital input without defined input level value. 142 // So, unused pins in given hardware should be set as outputs: 143 #if defined( TR72G ) || defined( TR72D ) // Symbols are generated by IQRF IDE according to the TR selection in a project 144 // TR pin C1 (MCU pin RA0): should be set as an output 145 LATA.0 = 0; // Low level 146 TRISA.0 = 0; // Output 147 148 // TR pin C2 (MCU pin RC2): should be set as an output 149 LATC.2 = 0; // Low level 150 TRISC.2 = 0; // Output 151 152 // TR pin C5 (MCU pins RA5, RB4 and RC6 connected in parallel): 153 // All MCU pins can be set as an input, but pin RB4 must be configured with internal pull-up (default activated). 154 TRISA.5 = 1; // Input 155 TRISB.4 = 1; // Input 156 TRISC.6 = 1; // Input 157 158 // TR pin C6 (MCU pin RC3): should be set as an output 159 LATC.3 = 0; // Low level 160 TRISC.3 = 0; // Output 161 162 // TR pin C7 (MCU pin RC4): should be set as an output 163 LATC.4 = 0; // Low level 164 TRISC.4 = 0; // Output 165 166 // TR pin C8 (MCU pins RC5 and RC7 connected in parallel): 167 // Only one MCU pin should be set as an output 168 LATC.5 = 0; // Low level 169 TRISC.5 = 0; // Output 170 TRISC.7 = 1; // Input 171 #elif defined( TR76G ) || defined( TR76D ) 172 // TR pin Q14 (MCU pin RA0): should be set as an output 173 LATA.0 = 0; // Low level 174 TRISA.0 = 0; // Output 175 176 // TR pin Q15 (MCU pin RC2): should be set as an output 177 LATC.2 = 0; // Low level 178 TRISC.2 = 0; // Output 179 180 // TR pin Q4 (MCU pin RC6): should be set as an output 181 LATC.6 = 0; // Low level 182 TRISC.6 = 0; // Output 183 184 // TR pin Q5 (MCU pin RC7): should be set as an output 185 LATC.7 = 0; // Low level 186 TRISC.7 = 0; // Output 187 188 // TR pin Q6 (MCU pin RC3): should be set as an output 189 LATC.3 = 0; // Low level 190 TRISC.3 = 0; // Output 191 192 // TR pin Q7 (MCU pin RC4): should be set as an output 193 LATC.4 = 0; // Low level 194 TRISC.4 = 0; // Output 195 196 // TR pin Q8 (MCU pin RC5): should be set as an output 197 LATC.5 = 0; // Low level 198 TRISC.5 = 0; // Output 199 200 // TR pin Q9 (MCU pin RA5): should be set as an output 201 LATA.5 = 0; // Low level 202 TRISA.5 = 0; // Output 203 204 // TR LED pins Q10 and Q11 (MCU pins RB7 and RA2) are set as outputs by OS. 205 // TR pin Q12 (MCU pin RB4) is set as an input with internal pull-up activated as default. 206 #else 207 #warning Low power consumption by treating unused pins is not implemented for the selected TR module 208 #endif 209 } 210 211 //goto DpaHandleReturnTRUE; // return TRUE only if you handle node bonding/unbonding at TRxD 212 break; 213 } 214 215 // ------------------------------------------------- 216 #ifdef DpaEvent_BondingButton 217 case DpaEvent_BondingButton: 218 // Called to allow a bonding button customization 219 // https://doc.iqrf.org/DpaTechGuide/pages/bondingbutton.html 220 //goto DpaHandleReturnTRUE; // return TRUE to handle bonding button 221 break; 222 #endif 223 224 // ------------------------------------------------- 225 case DpaEvent_Indicate: 226 // Called to allow a customization of the device indication 227 // https://doc.iqrf.org/DpaTechGuide/pages/IndicateEvent.html 228 //goto DpaHandleReturnTRUE; // return TRUE to skip default indication 229 break; 230 231 // ------------------------------------------------- 232 case DpaEvent_AfterSleep: 233 // Called after woken up after sleep 234 // https://doc.iqrf.org/DpaTechGuide/pages/aftersleep.html 235 236 // ! Fall through ! 237 238 // ------------------------------------------------- 239 case DpaEvent_Init: 240 // Do a one time initialization before main loop starts 241 // https://doc.iqrf.org/DpaTechGuide/pages/init.html 242 243 #ifndef DpaEvent_MenuActivated 244 if ( buttonPressed ) 245 buttonOnInit = TRUE; 246 #endif 247 248 goto _startCapture; 249 250 // ------------------------------------------------- 251 case DpaEvent_ReceiveDpaRequest: 252 // Called after DPA request was received 253 // https://doc.iqrf.org/DpaTechGuide/pages/receivedparequest.html 254 255 // Start inactivity timer 256 if ( _ROUTEF ) 257 { 258 _startCapture: 259 startCapture(); 260 } 261 262 //goto DpaHandleReturnTRUE; // return TRUE to skip default processing 263 break; 264 265 // ------------------------------------------------- 266 case DpaEvent_BeforeSendingDpaResponse: 267 // Called before sending DPA response back to originator of DPA response 268 // https://doc.iqrf.org/DpaTechGuide/pages/beforesendingdparesponse.html 269 break; 270 271 // ------------------------------------------------- 272 case DpaEvent_Notification: 273 // Called after DPA request was processed and after DPA response was sent 274 // https://doc.iqrf.org/DpaTechGuide/pages/notification.html 275 break; 276 277 // ------------------------------------------------- 278 case DpaEvent_AfterRouting: 279 // Called after Notification and after routing of the DPA response was finished 280 // https://doc.iqrf.org/DpaTechGuide/pages/afterrouting.html 281 break; 282 283 // ------------------------------------------------- 284 case DpaEvent_FrcValue: 285 // Called to get FRC value 286 // https://doc.iqrf.org/DpaTechGuide/pages/frcvalue.html 287 break; 288 289 // ------------------------------------------------- 290 case DpaEvent_FrcResponseTime: 291 // Called to get FRC response time 292 // https://doc.iqrf.org/DpaTechGuide/pages/frcresponsetime.html 293 break; 294 295 // ------------------------------------------------- 296 case DpaEvent_BeforeSleep: 297 // Called before going to sleep 298 // https://doc.iqrf.org/DpaTechGuide/pages/beforesleep.html 299 break; 300 301 // ------------------------------------------------- 302 case DpaEvent_DisableInterrupts: 303 // Called when device needs all hardware interrupts to be disabled (before Reset, Restart, LoadCode, Remove bond and run RFPGM) 304 // https://doc.iqrf.org/DpaTechGuide/pages/eventDisableInterrupts.html 305 break; 306 307 // ------------------------------------------------- 308 case DpaEvent_PeerToPeer: 309 // Called when peer-to-peer (non-networking) packet is received 310 // https://doc.iqrf.org/DpaTechGuide/pages/peertopeer.html 311 break; 312 313 // ------------------------------------------------- 314 case DpaEvent_UserDpaValue: 315 // Called when DPA is required to return User defined DPA value in the response 316 // https://doc.iqrf.org/DpaTechGuide/pages/userdpavalue.html 317 break; 318 319 // ------------------------------------------------- 320 case DpaEvent_VerifyLocalFrc: 321 // Called to verify local FRC command 322 // https://doc.iqrf.org/DpaTechGuide/pages/verifylocalfrc.html 323 324 //goto DpaHandleReturnTRUE; // return TRUE allow FRC command 325 break; 326 327 // ------------------------------------------------- 328 #ifdef DpaEvent_MenuActivated 329 case DpaEvent_MenuActivated: 330 // Called to customize DPA menu 331 // https://doc.iqrf.org/DpaTechGuide/pages/menuactivated.html 332 333 switch ( userReg1 ) 334 { 335 case DMENU_Online: 336 userReg1 = DMENU_Item_Implemented_Beaming; 337 goto DpaHandleReturnTRUE; // return TRUE to allow customizing menu specified by userReg1 338 } 339 break; 340 #endif 341 342 // ------------------------------------------------- 343 #ifdef DpaEvent_MenuItemSelected 344 case DpaEvent_MenuItemSelected: 345 // Called to indicate "OK" or "Error" for selected menu item 346 // https://doc.iqrf.org/DpaTechGuide/pages/menuitemselected.html 347 348 switch ( userReg1 ) 349 { 350 case MakeDMenuAndItem( DMENU_Online, DMENU_Item_Beaming ): 351 if ( amIBonded() ) 352 { 353 startBeamingAtIdle = TRUE; 354 goto DpaHandleReturnTRUE; // return TRUE to indicate "OK" for menu item specified by userReg1, otherwise to indicate Error 355 } 356 break; 357 } 358 break; 359 #endif 360 361 // ------------------------------------------------- 362 #ifdef DpaEvent_MenuItemFinalize 363 case DpaEvent_MenuItemFinalize: 364 // Called to finalize menu item execution 365 // https://doc.iqrf.org/DpaTechGuide/pages/menuitemfinalize.html 366 367 break; 368 #endif 369 370 // ------------------------------------------------- 371 case DpaEvent_DpaRequest: 372 // Called to interpret DPA request for peripherals 373 // https://doc.iqrf.org/DpaTechGuide/pages/EventDpaRequest.html 374 IfDpaEnumPeripherals_Else_PeripheralInfo_Else_PeripheralRequest() 375 { 376 // ------------------------------------------------- 377 // Peripheral enumeration 378 // https://doc.iqrf.org/DpaTechGuide/pages/enumerate-peripherals.html 379 380 // We implement 1 standard peripheral 381 _DpaMessage.EnumPeripheralsAnswer.UserPerNr |= 1; 382 FlagUserPer( _DpaMessage.EnumPeripheralsAnswer.UserPer, PNUM_STD_SENSORS ); 383 _DpaMessage.EnumPeripheralsAnswer.HWPID |= _HWPID_; 384 _DpaMessage.EnumPeripheralsAnswer.HWPIDver |= _HWPIDver_; 385 386 DpaHandleReturnTRUE: 387 return TRUE; 388 } 389 else 390 { 391 // ------------------------------------------------- 392 // Get information about peripheral 393 // https://doc.iqrf.org/DpaTechGuide/pages/get-peripheral-info.html 394 395 if ( _PNUM == PNUM_STD_SENSORS ) 396 { 397 _DpaMessage.PeripheralInfoAnswer.PerT = PERIPHERAL_TYPE_STD_SENSORS; 398 _DpaMessage.PeripheralInfoAnswer.PerTE = PERIPHERAL_TYPE_EXTENDED_READ; 399 // Set standard version 400 _DpaMessage.PeripheralInfoAnswer.Par1 = STD_SENSORS_VERSION; 401 goto DpaHandleReturnTRUE; 402 } 403 404 break; 405 } 406 407 // ------------------------------------------------- 408 // Handle peripheral command 409 // https://doc.iqrf.org/DpaTechGuide/pages/handle-peripheral-request.html 410 411 // Supported peripheral number? 412 if ( _PNUM == PNUM_STD_SENSORS ) 413 { 414 // Supported commands? 415 switch ( _PCMD ) 416 { 417 // Invalid command 418 default: 419 // Return error 420 W = ERROR_PCMD; 421 _ERROR_W: 422 DpaApiReturnPeripheralError( W ); 423 break; 424 425 // Sensor enumeration 426 case PCMD_STD_ENUMERATE: 427 if ( _DpaDataLength != 0 ) 428 { 429 W = ERROR_DATA_LEN; 430 goto _ERROR_W; 431 } 432 433 // Then just enumerate their types 434 // ------------------------------------------------- 435 _DpaMessage.Response.PData[0] = STD_SENSOR_TYPE_EXTRA_LOW_VOLTAGE; 436 _DpaMessage.Response.PData[1] = STD_SENSOR_TYPE_TEMPERATURE; 437 _DpaDataLength |= 2; 438 // ------------------------------------------------- 439 goto DpaHandleReturnTRUE; 440 } 441 } 442 443 break; 444 } 445 446 DpaHandleReturnFALSE: 447 return FALSE; 448 } 449 450 //############################################################################################ 451 void RandomWait() 452 //############################################################################################ 453 { 454 FirstDpaApiSleep = TRUE; 455 // Time 32.768 ms * ( 30 + rand(0...31) ) i.e. 983 - 1999 ms 456 uns8 loop = ( DpaApiRandom() & 0x1F ) + 30; 457 do 458 { 459 _DpaApiSleep( WDTCON_32ms ); 460 } while ( --loop != 0 ); 461 _DpaApiAfterSleep(); 462 } 463 464 //############################################################################################ 465 void Get_Temperature() 466 //############################################################################################ 467 { 468 // https://doc.iqrf.org/IQRF-Standards/StandardSensor/pages/0x01-temperature.html 469 470 // Return standard error value if there was an error 471 sensorValue = FRC_STD_FRC_ERROR_VALUE; 472 // Error reading temperature? Note: param3 holds finer temperature value after calling getTemperature() 473 if ( getTemperature() != -128 ) 474 { 475 // Extent minus sign bit #11 to the bits #12-15 476 if ( param3.11 ) 477 param3 |= 0xF000; 478 // Make a FRC value from the raw value 479 sensorValue = param3 ^ 0x8000; 480 } 481 } 482 483 //############################################################################################ 484 void Get_ExtraLowVoltage() 485 //############################################################################################ 486 { 487 // https://doc.iqrf.org/IQRF-Standards/StandardSensor/pages/0x04-extra-low-voltage.html 488 489 // Voltage [V] = 261.12 / (127 - getSupplyVoltage) 490 uns8 div = 127 - getSupplyVoltage(); 491 // For expected voltage ~3.0V, the divider is 127-40=87, so we will add 1/2 of it for more precise rounding 492 sensorValue = (uns24)( 261.120 * 1000 + 87.0 / 2 ) / div; 493 // Make a FRC value from the raw value 494 sensorValue ^= 0x8000; 495 } 496 497 //############################################################################################ 498 void ReleaseButton() 499 //############################################################################################ 500 { 501 do 502 { 503 clrwdt(); 504 } while ( buttonPressed ); 505 } 506 507 //############################################################################################ 508 void Beaming() 509 //############################################################################################ 510 { 511 // Indicate start of beaming 512 setLEDR(); 513 514 // Wait for button release 515 ReleaseButton(); 516 // 1st voltage readout 517 Get_ExtraLowVoltage(); 518 519 for ( ;; ) 520 { 521 // Do beaming 522 clrwdt(); 523 524 // Check for the locked Spirit1 RF chip 525 if ( wasRFICrestarted() ) 526 DpaApiSetRfDefaults(); 527 528 // Required time for the temperature sensor after wake-up from sleep 529 waitDelay( 31 ); // Note: in real application do sleep instead of active waiting to lower the consumption 530 531 // Prepare beaming data 532 // Voltage was read 1st time when the device starts beaming and then after every TX in the beaming when the power consumption was the highest 533 _DpaMessage.Response.PData[0] = STD_SENSOR_TYPE_EXTRA_LOW_VOLTAGE; 534 _DpaMessage.Response.PData[1] = sensorValue.low8; 535 _DpaMessage.Response.PData[2] = sensorValue.high8; 536 537 Get_Temperature(); 538 _DpaMessage.Response.PData[3] = STD_SENSOR_TYPE_TEMPERATURE; 539 _DpaMessage.Response.PData[4] = sensorValue.low8; 540 _DpaMessage.Response.PData[5] = sensorValue.high8; 541 542 // Do simple LBT 543 uns8 loop = 3; 544 do 545 { 546 if ( !checkRF( RxFilter + 10 ) ) 547 break; 548 549 RandomWait(); 550 } while ( --loop != 0 ); 551 552 553 #ifdef DEBUGBeaming 554 // Indicate every beaming 555 setLEDG(); 556 #endif 557 558 // STD TX 559 // !!! Note: we expect that the aggregating repeaters can RX at STD mode !!! 560 setRFmode( _TX_STD ); 561 562 // Force not routed packet to be sent from N by DPA API 563 NonroutedRfTxDpaPacket = TRUE; 564 565 // Prepare off-line sensor beaming packet 566 567 // HW profile ID 568 _HWPID = _HWPID_; 569 570 // No DPA Params used 571 _DpaParams = 0; 572 573 // Beaming is broadcast 574 _NADR = BROADCAST_ADDRESS; 575 _NADRhigh = 0; 576 577 // Prepare sensor packet type and quantity data 578 _PNUM = PNUM_STD_SENSORS; 579 _PCMD = PCMD_STD_SENSORS_READ_TYPES_AND_FRC_VALUES | RESPONSE_FLAG; 580 581 _DpaDataLength = 1 + 2/*EXTRA_LOW_VOLTAGE*/ + 1 + 2/*TEMPERATURE*/; 582 583 // TX DPA message with zero DPA Value and asynchronous 584 // Note: Use DpaValue = 0x01 to indicate asynchronous i.e. non-regular beaming 585 DpaApiRfTxDpaPacket( 0x00 /*DpaValue*/, 0 ); 586 587 // Measure voltage just after TX 588 Get_ExtraLowVoltage(); 589 590 // Do a sleep between two beamings 591 #ifndef DEBUGBeaming 592 loop = 60; 593 #else 594 loop = 5; 595 #endif 596 FirstDpaApiSleep = TRUE; 597 do 598 { 599 _DpaApiSleep( WDTCON_1s ); 600 601 #ifndef DpaEvent_MenuActivated 602 if ( buttonPressed ) 603 { 604 // Button indication 605 setLEDG(); 606 // No more sleeps 607 _DpaApiAfterSleep(); 608 // Restore RF settings 609 DpaApiSetRfDefaults(); 610 // Wait for button release 611 ReleaseButton(); 612 // Start inactivity timer 613 startCapture(); 614 // End of button indication 615 stopLEDG(); 616 // Abort beaming 617 return; 618 } 619 #else 620 uns8 menuAndItem = DpaApiMenu( DMENU_Beaming, 0/*Beaming DPA menu is not customized*/ ); 621 switch ( menuAndItem ) 622 { 623 // ConnectivityCheck 624 case MakeDMenuAndItem( DMENU_Beaming, DMENU_Item_ConnectivityCheck ): 625 // Stop beaming sleeps 626 _DpaApiAfterSleep(); 627 // Voluntary indication of the TestRange execution 628 pulsingLEDR(); 629 // Repeaters to test 630 clearBufferINFO(); 631 bufferINFO[0 / 8] = 0b1111.1110; // 1...7 632 // Do the test and result indication 633 DpaApiMenuIndicateResult( DpaApiLocalFrc( FRC_Ping, TX_POWER_MAX ) ); 634 // Continue regular beaming sleeps 635 FirstDpaApiSleep = TRUE; 636 break; 637 638 // Other menu items 639 default: 640 // Stop beaming sleeps 641 _DpaApiAfterSleep(); 642 // Execute menu 643 DpaApiMenuExecute( menuAndItem ); 644 // Continue regular beaming sleeps 645 FirstDpaApiSleep = TRUE; 646 break; 647 648 // No menu item was selected 649 case MakeDMenuAndItem( DMENU_Beaming, DMENU_Item_None ): 650 break; 651 } 652 #endif 653 } while ( --loop != 0 ); 654 // No more sleeps 655 _DpaApiAfterSleep(); 656 } 657 } 658 659 //############################################################################################ 660 // Uncomment the following includes if the respective library is needed 661 //#include "NFC.c" 662 663 // Default Custom DPA Handler header; 2nd include implementing a Code bumper to detect too long code of the Custom DPA Handler (modify the path according to your setup) 664 #include "DPAcustomHandler.h" 665 //############################################################################################