From 3696d4674ba58bbc5796794628e19584033f0489 Mon Sep 17 00:00:00 2001 From: Frank Voorburg Date: Wed, 26 Jul 2017 09:59:15 +0000 Subject: [PATCH] Refs #316. Completed implementation of the Peak PCAN-USB interface module. git-svn-id: https://svn.code.sf.net/p/openblt/code/trunk@312 5dc33758-31d5-4daf-9ae8-b24bf3d40d73 --- Host/Source/LibOpenBLT/candriver.c | 442 ++++--- Host/Source/LibOpenBLT/candriver.h | 9 +- .../port/windows/canif/peak/pcanusb.c | 1142 ++++++++++++++--- Host/libopenblt.dll | Bin 81408 -> 98304 bytes 4 files changed, 1178 insertions(+), 415 deletions(-) diff --git a/Host/Source/LibOpenBLT/candriver.c b/Host/Source/LibOpenBLT/candriver.c index 9b570ae4..b1857c06 100644 --- a/Host/Source/LibOpenBLT/candriver.c +++ b/Host/Source/LibOpenBLT/candriver.c @@ -1,208 +1,234 @@ -/************************************************************************************//** -* \file candriver.c -* \brief Generic CAN driver source file. -* \ingroup CanDriver -* \internal -*---------------------------------------------------------------------------------------- -* C O P Y R I G H T -*---------------------------------------------------------------------------------------- -* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved -* -*---------------------------------------------------------------------------------------- -* L I C E N S E -*---------------------------------------------------------------------------------------- -* This file is part of OpenBLT. OpenBLT 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. -* -* OpenBLT 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 have received a copy of the GNU General Public License along with OpenBLT. It -* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. -* -* \endinternal -****************************************************************************************/ - -/**************************************************************************************** -* Include files -****************************************************************************************/ -#include /* for assertions */ -#include /* for standard integer types */ -#include /* for NULL declaration */ -#include /* for boolean type */ -#include "candriver.h" /* Generic CAN driver module */ - - -/**************************************************************************************** -* Local data declarations -****************************************************************************************/ -/** \brief Pointer to the CAN interface that is linked. */ -static tCanInterface const * canIfPtr; - -/** \brief Flag to store the connection status. */ -static bool canConnected; - - -/************************************************************************************//** -** \brief Initializes the CAN module. Typically called once at program startup. -** \param settings Pointer to the CAN module settings. -** \param interface Pointer to the CAN interface to link. -** -****************************************************************************************/ -void CanInit(tCanSettings const * settings, tCanInterface const * const interface) -{ - /* Initialize locals. */ - canIfPtr = NULL; - canConnected = false; - - /* Check parameters. */ - assert(settings != NULL); - assert(interface != NULL); - - /* Only continue with valid parameters. */ - if ( (settings != NULL) && (interface != NULL) ) /*lint !e774 */ - { - /* Link the CAN interface. */ - canIfPtr = interface; - /* Initialize the CAN interface. */ - canIfPtr->Init(settings); - } -} /*** end of CanInit ***/ - - -/************************************************************************************//** -** \brief Terminates the CAN module. Typically called once at program cleanup. -** -****************************************************************************************/ -void CanTerminate(void) -{ - /* Make sure a valid CAN interface is linked. */ - assert(canIfPtr != NULL); - - /* Only continue with a valid CAN interface. */ - if (canIfPtr != NULL) /*lint !e774 */ - { - /* Make sure to disconnect first. */ - CanDisconnect(); - /* Terminate the CAN interface. */ - canIfPtr->Terminate(); - /* Unlink the CAN interface. */ - canIfPtr = NULL; - } -} /*** end of CanTerminate ***/ - - -/************************************************************************************//** -** \brief Connects the CAN module. -** \return True if connected, false otherwise. -** -****************************************************************************************/ -bool CanConnect(void) -{ - bool result = false; - - /* Make sure a valid CAN interface is linked. */ - assert(canIfPtr != NULL); - - /* Only continue with a valid CAN interface. */ - if (canIfPtr != NULL) /*lint !e774 */ - { - /* Make sure that the CAN module is in the disconnected state. */ - CanDisconnect(); - /* Connect the CAN interface. */ - result = canIfPtr->Connect(); - /* Update the connection state. */ - canConnected = result; - } - /* Give the result back to the caller. */ - return result; -} /*** end of CanConnect ***/ - - -/************************************************************************************//** -** \brief Disconnects the CAN module. -** -****************************************************************************************/ -void CanDisconnect(void) -{ - /* Make sure a valid CAN interface is linked. */ - assert(canIfPtr != NULL); - - /* Only continue with a valid CAN interface. */ - if (canIfPtr != NULL) /*lint !e774 */ - { - /* Only disconnect if actually connected. */ - if (canConnected) - { - /* Disconnect CAN interface. */ - canIfPtr->Disconnect(); - /* Update the connection state. */ - canConnected = false; - } - } -} /*** end of CanDisconnect ***/ - - -/************************************************************************************//** -** \brief Obtains the connection state of the CAN module. -** \return True if connected, false otherwise. -** -****************************************************************************************/ -bool CanIsConnected(void) -{ - /* Obtain and return the connection state to the caller. */ - return canConnected; -} /*** end of CanIsConnected ***/ - - -/************************************************************************************//** -** \brief Submits a message for transmission on the CAN bus. -** \param msg Pointer to CAN message structure. -** \return True if successful, false otherwise. -** -****************************************************************************************/ -bool CanTransmit(tCanMsg const * msg) -{ - bool result = false; - - /* Make sure a valid CAN interface is linked. */ - assert(canIfPtr != NULL); - - /* Only continue with a valid CAN interface. */ - if (canIfPtr != NULL) /*lint !e774 */ - { - /* Only transmit the message if connected. */ - if (canConnected) - { - result = canIfPtr->Transmit(msg); - } - } - /* Give the result back to the caller. */ - return result; -} /*** end of CanTransmit ***/ - - -/************************************************************************************//** -** \brief Registers the event callback functions that should be called by the CAN -** module. -** \param events Pointer to structure with event callback function pointers. -** -****************************************************************************************/ -void CanRegisterEvents(tCanEvents const * events) -{ - /* Make sure a valid CAN interface is linked. */ - assert(canIfPtr != NULL); - - /* Only continue with a valid CAN interface. */ - if (canIfPtr != NULL) /*lint !e774 */ - { - /* Register the events with the CAN interface. */ - canIfPtr->RegisterEvents(events); - } -} /*** end of CanRegisterEvents ***/ - - -/*********************************** end of candriver.c ********************************/ +/************************************************************************************//** +* \file candriver.c +* \brief Generic CAN driver source file. +* \ingroup CanDriver +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT 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. +* +* OpenBLT 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 have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ + +/**************************************************************************************** +* Include files +****************************************************************************************/ +#include /* for assertions */ +#include /* for standard integer types */ +#include /* for NULL declaration */ +#include /* for boolean type */ +#include "candriver.h" /* Generic CAN driver module */ + + +/**************************************************************************************** +* Local data declarations +****************************************************************************************/ +/** \brief Pointer to the CAN interface that is linked. */ +static tCanInterface const * canIfPtr; + +/** \brief Flag to store the connection status. */ +static bool canConnected; + + +/************************************************************************************//** +** \brief Initializes the CAN module. Typically called once at program startup. +** \param settings Pointer to the CAN module settings. +** \param interface Pointer to the CAN interface to link. +** +****************************************************************************************/ +void CanInit(tCanSettings const * settings, tCanInterface const * const interface) +{ + /* Initialize locals. */ + canIfPtr = NULL; + canConnected = false; + + /* Check parameters. */ + assert(settings != NULL); + assert(interface != NULL); + + /* Only continue with valid parameters. */ + if ( (settings != NULL) && (interface != NULL) ) /*lint !e774 */ + { + /* Link the CAN interface. */ + canIfPtr = interface; + /* Initialize the CAN interface. */ + canIfPtr->Init(settings); + } +} /*** end of CanInit ***/ + + +/************************************************************************************//** +** \brief Terminates the CAN module. Typically called once at program cleanup. +** +****************************************************************************************/ +void CanTerminate(void) +{ + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Make sure to disconnect first. */ + CanDisconnect(); + /* Terminate the CAN interface. */ + canIfPtr->Terminate(); + /* Unlink the CAN interface. */ + canIfPtr = NULL; + } +} /*** end of CanTerminate ***/ + + +/************************************************************************************//** +** \brief Connects the CAN module. +** \return True if connected, false otherwise. +** +****************************************************************************************/ +bool CanConnect(void) +{ + bool result = false; + + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Make sure that the CAN module is in the disconnected state. */ + CanDisconnect(); + /* Connect the CAN interface. */ + result = canIfPtr->Connect(); + /* Update the connection state. */ + canConnected = result; + } + /* Give the result back to the caller. */ + return result; +} /*** end of CanConnect ***/ + + +/************************************************************************************//** +** \brief Disconnects the CAN module. +** +****************************************************************************************/ +void CanDisconnect(void) +{ + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Only disconnect if actually connected. */ + if (canConnected) + { + /* Disconnect CAN interface. */ + canIfPtr->Disconnect(); + /* Update the connection state. */ + canConnected = false; + } + } +} /*** end of CanDisconnect ***/ + + +/************************************************************************************//** +** \brief Obtains the connection state of the CAN module. +** \return True if connected, false otherwise. +** +****************************************************************************************/ +bool CanIsConnected(void) +{ + /* Obtain and return the connection state to the caller. */ + return canConnected; +} /*** end of CanIsConnected ***/ + + +/************************************************************************************//** +** \brief Submits a message for transmission on the CAN bus. +** \param msg Pointer to CAN message structure. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool CanTransmit(tCanMsg const * msg) +{ + bool result = false; + + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Only transmit the message if connected. */ + if (canConnected) + { + result = canIfPtr->Transmit(msg); + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of CanTransmit ***/ + + +/************************************************************************************//** +** \brief Checks if a bus off or bus heavy situation occurred. +** \return True if a bus error situation was detected, false otherwise. +** +****************************************************************************************/ +bool CanIsBusError(void) +{ + bool result = false; + + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Only check for bus error if connected. */ + if (canConnected) + { + result = canIfPtr->IsBusError(); + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of CanIsBusError ***/ + + +/************************************************************************************//** +** \brief Registers the event callback functions that should be called by the CAN +** module. +** \param events Pointer to structure with event callback function pointers. +** +****************************************************************************************/ +void CanRegisterEvents(tCanEvents const * events) +{ + /* Make sure a valid CAN interface is linked. */ + assert(canIfPtr != NULL); + + /* Only continue with a valid CAN interface. */ + if (canIfPtr != NULL) /*lint !e774 */ + { + /* Register the events with the CAN interface. */ + canIfPtr->RegisterEvents(events); + } +} /*** end of CanRegisterEvents ***/ + + +/*********************************** end of candriver.c ********************************/ diff --git a/Host/Source/LibOpenBLT/candriver.h b/Host/Source/LibOpenBLT/candriver.h index 21d2f69a..88b527c2 100644 --- a/Host/Source/LibOpenBLT/candriver.h +++ b/Host/Source/LibOpenBLT/candriver.h @@ -90,13 +90,13 @@ typedef struct t_can_msg * .mask = 0x00000000 * Example 2: Receive only CAN identifier 0x124 (11-bit or 29-bit) * .code = 0x00000124 - * .mask = 0x00000124 + * .mask = 0x1fffffff * Example 3: Receive only CAN identifier 0x124 (11-bit) * .code = 0x00000124 - * .mask = 0x80000124 + * .mask = 0x9fffffff * Example 4: Receive only CAN identifier 0x124 (29-bit) * .code = 0x80000124 - * .mask = 0x80000124 + * .mask = 0x9fffffff */ typedef struct t_can_settings { @@ -129,6 +129,8 @@ typedef struct t_can_interface void (*Disconnect) (void); /** \brief Submits a CAN message for transmission. */ bool (*Transmit) (tCanMsg const * msg); + /** \brief Check if a bus off and/or bus heavy situation occurred. */ + bool (*IsBusError) (void); /** \brief Registers the event callback functions. */ void (*RegisterEvents) (tCanEvents const * events); } tCanInterface; @@ -143,6 +145,7 @@ bool CanConnect(void); void CanDisconnect(void); bool CanIsConnected(void); bool CanTransmit(tCanMsg const * msg); +bool CanIsBusError(void); void CanRegisterEvents(tCanEvents const * events); #ifdef __cplusplus diff --git a/Host/Source/LibOpenBLT/port/windows/canif/peak/pcanusb.c b/Host/Source/LibOpenBLT/port/windows/canif/peak/pcanusb.c index baba99ee..a41acce4 100644 --- a/Host/Source/LibOpenBLT/port/windows/canif/peak/pcanusb.c +++ b/Host/Source/LibOpenBLT/port/windows/canif/peak/pcanusb.c @@ -33,51 +33,140 @@ #include /* for standard integer types */ #include /* for NULL declaration */ #include /* for boolean type */ -#include /* for standard library */ -#include /* for string library */ +#include /* for standard library */ +#include /* for string library */ #include "candriver.h" /* Generic CAN driver module */ #include "pcanusb.h" /* Peak PCAN-USB interface */ #include /* for Windows API */ #include "PCANBasic.h" /* for PCAN-Basic API */ -/*************************************************************************************** -* Function prototypes -****************************************************************************************/ -static void PCanUsbInit(tCanSettings const * settings); -static void PCanUsbTerminate(void); -static bool PCanUsbConnect(void); -static void PCanUsbDisconnect(void); -static bool PCanUsbTransmit(tCanMsg const * msg); -static void PCanUsbRegisterEvents(tCanEvents const * events); +/*************************************************************************************** +* Type definitions +****************************************************************************************/ +/* Type definitions of the function in the PCAN-Basic API that this CAN interface uses.*/ +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncInitialize)(TPCANHandle, TPCANBaudrate, TPCANType, DWORD, WORD); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncpUninitialize)(TPCANHandle); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncGetStatus)(TPCANHandle); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncSetValue)(TPCANHandle, TPCANParameter, void*, DWORD); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncRead)(TPCANHandle, TPCANMsg*, TPCANTimestamp*); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncWrite)(TPCANHandle, TPCANMsg*); +typedef TPCANStatus (__stdcall * tPCanUsbLibFuncFilterMessages)(TPCANHandle, DWORD, DWORD, TPCANMode); - -/**************************************************************************************** -* Local constant declarations -****************************************************************************************/ -/** \brief CAN interface structure filled with Peak PCAN-USB specifics. */ -static const tCanInterface pCanUsbInterface = -{ - PCanUsbInit, - PCanUsbTerminate, - PCanUsbConnect, - PCanUsbDisconnect, - PCanUsbTransmit, - PCanUsbRegisterEvents -}; - -/**************************************************************************************** -* Local data declarations -****************************************************************************************/ -/** \brief The settings to use in this CAN interface. */ -static tCanSettings pCanUsbSettings; - -/** \brief List with callback functions that this driver should use. */ -static tCanEvents * pCanUsbEventsList; - -/** \brief Total number of event entries into the \ref pCanUsbEventsList list. */ -static uint32_t pCanUsbEventsEntries; +/*************************************************************************************** +* Function prototypes +****************************************************************************************/ +/* CAN interface functions. */ +static void PCanUsbInit(tCanSettings const * settings); +static void PCanUsbTerminate(void); +static bool PCanUsbConnect(void); +static void PCanUsbDisconnect(void); +static bool PCanUsbTransmit(tCanMsg const * msg); +static bool PCanUsbIsBusError(void); +static void PCanUsbRegisterEvents(tCanEvents const * events); +/* CAN message reception thread. */ +static DWORD WINAPI PCanUsbReceptionThread(LPVOID pv); +/* PCAN-Basic library handling. */ +static void PCanUsbLibLoadDll(void); +static void PCanUsbLibUnloadDll(void); +static TPCANStatus PCanUsbLibFuncInitialize(TPCANHandle Channel, TPCANBaudrate Btr0Btr1, + TPCANType HwType, DWORD IOPort, + WORD Interrupt); +static TPCANStatus PCanUsbLibFuncpUninitialize(TPCANHandle Channel); +static TPCANStatus PCanUsbLibFuncGetStatus(TPCANHandle Channel); +static TPCANStatus PCanUsbLibFuncSetValue(TPCANHandle Channel, TPCANParameter Parameter, + void * Buffer, DWORD BufferLength); +static TPCANStatus PCanUsbLibFuncRead(TPCANHandle Channel, TPCANMsg * MessageBuffer, + TPCANTimestamp * TimestampBuffer); +static TPCANStatus PCanUsbLibFuncWrite(TPCANHandle Channel, TPCANMsg * MessageBuffer); +static TPCANStatus PCanUsbLibFuncFilterMessages(TPCANHandle Channel, DWORD FromID, + DWORD ToID, TPCANMode Mode); + + +/**************************************************************************************** +* Local constant declarations +****************************************************************************************/ +/** \brief CAN interface structure filled with Peak PCAN-USB specifics. */ +static const tCanInterface pCanUsbInterface = +{ + PCanUsbInit, + PCanUsbTerminate, + PCanUsbConnect, + PCanUsbDisconnect, + PCanUsbTransmit, + PCanUsbIsBusError, + PCanUsbRegisterEvents +}; + +/** \brief PCAN-USB channel handle lookup table. The pCanUsbSettings.channel value can + * be used as the index. + */ +static const TPCANHandle pCanUsbChannelLookup[] = +{ + PCAN_USBBUS1, /* PCAN-USB interface, channel 1. */ + PCAN_USBBUS2, /* PCAN-USB interface, channel 2. */ + PCAN_USBBUS3, /* PCAN-USB interface, channel 3. */ + PCAN_USBBUS4, /* PCAN-USB interface, channel 4. */ + PCAN_USBBUS5, /* PCAN-USB interface, channel 5. */ + PCAN_USBBUS6, /* PCAN-USB interface, channel 6. */ + PCAN_USBBUS7, /* PCAN-USB interface, channel 7. */ + PCAN_USBBUS8, /* PCAN-USB interface, channel 8. */ + PCAN_USBBUS9, /* PCAN-USB interface, channel 9. */ + PCAN_USBBUS10, /* PCAN-USB interface, channel 10. */ + PCAN_USBBUS11, /* PCAN-USB interface, channel 11. */ + PCAN_USBBUS12, /* PCAN-USB interface, channel 12. */ + PCAN_USBBUS13, /* PCAN-USB interface, channel 13. */ + PCAN_USBBUS14, /* PCAN-USB interface, channel 14. */ + PCAN_USBBUS15, /* PCAN-USB interface, channel 15. */ + PCAN_USBBUS16 /* PCAN-USB interface, channel 16. */ +}; + + +/**************************************************************************************** +* Local data declarations +****************************************************************************************/ +/** \brief The settings to use in this CAN interface. */ +static tCanSettings pCanUsbSettings; + +/** \brief List with callback functions that this driver should use. */ +static tCanEvents * pCanUsbEventsList; + +/** \brief Total number of event entries into the \ref pCanUsbEventsList list. */ +static uint32_t pCanUsbEventsEntries; + +/** \brief Handle to the PCAN-Basic dynamic link library. */ +static HINSTANCE pCanUsbDllHandle; + +/** \brief Function pointer to the PCAN-Basic Initialize function. */ +static tPCanUsbLibFuncInitialize pCanUsbLibFuncInitializePtr; + +/** \brief Function pointer to the PCAN-Basic Uninitialize function. */ +static tPCanUsbLibFuncpUninitialize pCanUsbLibFuncpUninitializePtr; + +/** \brief Function pointer to the PCAN-Basic GetStatus function. */ +static tPCanUsbLibFuncGetStatus pCanUsbLibFuncGetStatusPtr; + +/** \brief Function pointer to the PCAN-Basic SetValue function. */ +static tPCanUsbLibFuncSetValue pCanUsbLibFuncSetValuePtr; + +/** \brief Function pointer to the PCAN-Basic Read function. */ +static tPCanUsbLibFuncRead pCanUsbLibFuncReadPtr; + +/** \brief Function pointer to the PCAN-Basic Write function. */ +static tPCanUsbLibFuncWrite pCanUsbLibFuncWritePtr; + +/** \brief Function pointer to the PCAN-Basic FilterMessages function. */ +static tPCanUsbLibFuncFilterMessages pCanUsbLibFuncFilterMessagesPtr; + +/** \brief Handle for the event to terminate the reception thread. */ +static HANDLE pCanUsbTerminateEvent; + +/** \brief Handle for a CAN related event. */ +static HANDLE pCanUsbCanEvent; + +/** \brief Handle for the CAN reception thread. */ +static HANDLE pCanUsbRxThreadHandle; /***********************************************************************************//** @@ -92,172 +181,817 @@ tCanInterface const * PCanUsbGetInterface(void) } /*** end of PCanUsbGetInterface ***/ -/************************************************************************************//** -** \brief Initializes the CAN interface. -** \param settings Pointer to the CAN interface settings. -** -****************************************************************************************/ -static void PCanUsbInit(tCanSettings const * settings) -{ - char * canDeviceName; - - /* Initialize locals. */ - pCanUsbEventsList = NULL; - pCanUsbEventsEntries = 0; - /* Reset CAN interface settings. */ - pCanUsbSettings.devicename = ""; - pCanUsbSettings.channel = 0; - pCanUsbSettings.baudrate = CAN_BR500K; - pCanUsbSettings.code = 0x00000000u; - pCanUsbSettings.mask = 0x00000000u; - - /* Check parameters. */ - assert(settings != NULL); - - /* Only continue with valid parameters. */ - if (settings != NULL) /*lint !e774 */ - { - /* Shallow copy the CAN interface settings for later usage. */ - pCanUsbSettings = *settings; - /* The devicename is a pointer and it is not gauranteed that it stays valid so we need - * to deep copy this one. note the +1 for '\0' in malloc. - */ - assert(settings->devicename != NULL); - if (settings->devicename != NULL) /*lint !e774 */ - { - canDeviceName = malloc(strlen(settings->devicename) + 1); - assert(canDeviceName != NULL); - if (canDeviceName != NULL) /*lint !e774 */ - { - strcpy(canDeviceName, settings->devicename); - pCanUsbSettings.devicename = canDeviceName; - } - } - } - /* ##Vg TODO Perform initialization of PCAN-Basic API. */ -} /*** end of PCanUsbInit ***/ - - -/************************************************************************************//** -** \brief Terminates the CAN interface. -** -****************************************************************************************/ -static void PCanUsbTerminate(void) -{ - /* ##Vg TODO Perform termination of PCAN-Basic API. */ - /* Release memory that was allocated for storing the device name. */ - if (pCanUsbSettings.devicename != NULL) - { - free((char *)pCanUsbSettings.devicename); - } - /* Reset CAN interface settings. */ - pCanUsbSettings.devicename = ""; - pCanUsbSettings.channel = 0; - pCanUsbSettings.baudrate = CAN_BR500K; - pCanUsbSettings.code = 0x00000000u; - pCanUsbSettings.mask = 0x00000000u; - /* Release memory that was allocated for CAN events and reset the entry count. */ - if ( (pCanUsbEventsList != NULL) && (pCanUsbEventsEntries != 0) ) - { - free(pCanUsbEventsList); - pCanUsbEventsEntries = 0; - } -} /*** end of PCanUsbTerminate ***/ - - -/************************************************************************************//** -** \brief Connects the CAN interface. -** \return True if connected, false otherwise. -** -****************************************************************************************/ -static bool PCanUsbConnect(void) -{ - bool result = false; - - /* ##Vg TODO Check CAN interface settings. */ - - /* ##Vg TODO Connect to CAN interface, if settings are valid. */ - - /* Give the result back to the caller. */ - return result; -} /*** end of PCanUsbConnect ***/ - - -/************************************************************************************//** -** \brief Disconnects the CAN interface. -** -****************************************************************************************/ -static void PCanUsbDisconnect(void) -{ - /* ##Vg TODO Disconnect from the CAN interface. */ -} /*** end of PCanUsbDisconnect ***/ - - -/************************************************************************************//** -** \brief Submits a message for transmission on the CAN bus. -** \param msg Pointer to CAN message structure. -** \return True if successful, false otherwise. -** -****************************************************************************************/ -static bool PCanUsbTransmit(tCanMsg const * msg) -{ - bool result = false; - - /* Check parameters. */ - assert(msg != NULL); - - /* Only continue with valid parameters. */ - if (msg != NULL) /*lint !e774 */ - { - /* ##Vg TODO Submit CAN message for transmission */ - } - - /* Give the result back to the caller. */ - return result; -} /*** end of PCanUsbTransmit ***/ - - -/************************************************************************************//** -** \brief Registers the event callback functions that should be called by the CAN -** interface. -** \param events Pointer to structure with event callback function pointers. -** -****************************************************************************************/ -static void PCanUsbRegisterEvents(tCanEvents const * events) -{ - /* Check parameters. */ - assert(events != NULL); - - /* ##Vg TODO Test with multiple event entries to check that realloc approach works. */ - - /* Only continue with valid parameters. */ - if (events != NULL) /*lint !e774 */ - { - /* Increase length of the list to make space for one more event entry. Note that - * it is okay to call realloc with a NULL pointer. In this case it simply behaves - * as malloc. - */ - pCanUsbEventsList = realloc(pCanUsbEventsList, - (sizeof(tCanEvents) * (pCanUsbEventsEntries + 1))); - /* Assert reallocation. */ - assert(pCanUsbEventsList != NULL); - /* Only continue if reallocation was successful. */ - if (pCanUsbEventsList != NULL) - { - /* Increment events entry count. */ - pCanUsbEventsEntries++; - /* Store the events in the new entry. */ - pCanUsbEventsList[pCanUsbEventsEntries - 1] = *events; - } - /* Reallocation failed. */ - else - { - /* Reset events entry count. */ - pCanUsbEventsEntries = 0; - } - } -} /*** end of PCanUsbRegisterEvents ***/ - - +/************************************************************************************//** +** \brief Initializes the CAN interface. +** \param settings Pointer to the CAN interface settings. +** +****************************************************************************************/ +static void PCanUsbInit(tCanSettings const * settings) +{ + char * canDeviceName; + + /* Initialize locals. */ + pCanUsbEventsList = NULL; + pCanUsbEventsEntries = 0; + pCanUsbTerminateEvent = NULL; + pCanUsbCanEvent = NULL; + pCanUsbRxThreadHandle = NULL; + pCanUsbDllHandle = NULL; + /* Reset library function pointers. */ + pCanUsbLibFuncInitializePtr = NULL; + pCanUsbLibFuncpUninitializePtr = NULL; + pCanUsbLibFuncGetStatusPtr = NULL; + pCanUsbLibFuncSetValuePtr = NULL; + pCanUsbLibFuncReadPtr = NULL; + pCanUsbLibFuncWritePtr = NULL; + pCanUsbLibFuncFilterMessagesPtr = NULL; + /* Reset CAN interface settings. */ + pCanUsbSettings.devicename = ""; + pCanUsbSettings.channel = 0; + pCanUsbSettings.baudrate = CAN_BR500K; + pCanUsbSettings.code = 0x00000000u; + pCanUsbSettings.mask = 0x00000000u; + + /* Check parameters. */ + assert(settings != NULL); + + /* Only continue with valid parameters. */ + if (settings != NULL) /*lint !e774 */ + { + /* Shallow copy the CAN interface settings for later usage. */ + pCanUsbSettings = *settings; + /* The devicename is a pointer and it is not gauranteed that it stays valid so we need + * to deep copy this one. note the +1 for '\0' in malloc. + */ + assert(settings->devicename != NULL); + if (settings->devicename != NULL) /*lint !e774 */ + { + canDeviceName = malloc(strlen(settings->devicename) + 1); + assert(canDeviceName != NULL); + if (canDeviceName != NULL) /*lint !e774 */ + { + strcpy(canDeviceName, settings->devicename); + pCanUsbSettings.devicename = canDeviceName; + } + } + /* Perform initialization of PCAN-Basic API. */ + PCanUsbLibLoadDll(); + } +} /*** end of PCanUsbInit ***/ + + +/************************************************************************************//** +** \brief Terminates the CAN interface. +** +****************************************************************************************/ +static void PCanUsbTerminate(void) +{ + /* Perform termination of PCAN-Basic API. */ + PCanUsbLibUnloadDll(); + /* Release memory that was allocated for storing the device name. */ + if (pCanUsbSettings.devicename != NULL) + { + free((char *)pCanUsbSettings.devicename); + } + /* Reset CAN interface settings. */ + pCanUsbSettings.devicename = ""; + pCanUsbSettings.channel = 0; + pCanUsbSettings.baudrate = CAN_BR500K; + pCanUsbSettings.code = 0x00000000u; + pCanUsbSettings.mask = 0x00000000u; + /* Release memory that was allocated for CAN events and reset the entry count. */ + if ( (pCanUsbEventsList != NULL) && (pCanUsbEventsEntries != 0) ) + { + free(pCanUsbEventsList); + pCanUsbEventsEntries = 0; + } +} /*** end of PCanUsbTerminate ***/ + + +/************************************************************************************//** +** \brief Connects the CAN interface. +** \return True if connected, false otherwise. +** +****************************************************************************************/ +static bool PCanUsbConnect(void) +{ + bool result = false; + bool baudrateSupported = true; + bool channelSupported; + bool libInitialized = false; + TPCANBaudrate baudrate = PCAN_BAUD_500K; + uint32_t iBuffer; + + /* Convert the baudrate to a value supported by the PCAN-Basic API. */ + switch (pCanUsbSettings.baudrate) + { + case CAN_BR10K: + baudrate = PCAN_BAUD_10K; + break; + case CAN_BR20K: + baudrate = PCAN_BAUD_20K; + break; + case CAN_BR50K: + baudrate = PCAN_BAUD_50K; + break; + case CAN_BR100K: + baudrate = PCAN_BAUD_100K; + break; + case CAN_BR125K: + baudrate = PCAN_BAUD_125K; + break; + case CAN_BR250K: + baudrate = PCAN_BAUD_250K; + break; + case CAN_BR500K: + baudrate = PCAN_BAUD_500K; + break; + case CAN_BR800K: + baudrate = PCAN_BAUD_800K; + break; + case CAN_BR1M: + baudrate = PCAN_BAUD_1M; + break; + default: + baudrateSupported = false; + break; + } + + /* Validate channel index. For this CAN interface, the channel index specifies the + * PCAN-USB device, in case multiple are connected. + */ + channelSupported = (pCanUsbSettings.channel < (sizeof(pCanUsbChannelLookup) / + sizeof(pCanUsbChannelLookup[0]))); + + /* Note that the device name itself is not needed anymore at this point, it was only + * needed by the CAN driver to link the correct interface (this one). Check settings. + */ + assert(baudrateSupported); + assert(channelSupported); + + /* Only continue with valid settings. */ + if ( (baudrateSupported) && (channelSupported) ) + { + /* Init result code to success and only negate it on detection of error. */ + result = true; + /* Connect to the CAN hardware interface. */ + if (PCanUsbLibFuncInitialize(pCanUsbChannelLookup[pCanUsbSettings.channel], + baudrate, 0, 0, 0) != PCAN_ERROR_OK) + { + result = false; + } + else + { + libInitialized = true; + } + + /* Create the terminate event handle used in the reception thread. */ + if (result) + { + pCanUsbTerminateEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (pCanUsbTerminateEvent == NULL) + { + result = false; + } + } + + /* Create the CAN event handle used in the reception thread. */ + if (result) + { + pCanUsbCanEvent = CreateEvent(NULL, FALSE, FALSE, ""); + if (pCanUsbCanEvent == NULL) + { + result = false; + } + } + + /* Enable the bus off auto reset. */ + if (result) + { + iBuffer = PCAN_PARAMETER_ON; + if (PCanUsbLibFuncSetValue(pCanUsbChannelLookup[pCanUsbSettings.channel], + PCAN_BUSOFF_AUTORESET, &iBuffer, + sizeof(iBuffer)) != PCAN_ERROR_OK) + { + result = false; + } + } + + /* Configure reception acceptance filter, if it is not supposed to be fully open. */ + if ( (result) && (pCanUsbSettings.mask != 0x00000000u) ) + { + /* Configuration of the reception acceptance filter in the PCAN-Basic library does + * not use a mask and code approach. Instead it has an API function for setting + * the filter by specifing an identifier range. This means the mask and code pair + * should be used to calculate the lowest and highest identifier that should pass + * the filter. It is not 100% accurate. In some cases more identifiers can pass + * the filter than desired, but there is no other option: + * Lowest CAN ID = (code & mask) & 0x1fffffff + * Highest CAN ID = (code | ~mask) & 0x1fffffff + */ + uint32_t lowestCanID = (pCanUsbSettings.code & pCanUsbSettings.mask) & 0x1fffffffu; + uint32_t highestCanID = (pCanUsbSettings.code | ~pCanUsbSettings.mask) & 0x1fffffffu; + /* Use bit logic to determine if the filter should accept standard 11-bit and/or + * extended 29-bit identifiers: + * acceptStdId = ((mask & code & CAN_MSG_EXT_ID_MASK) == 0) + * acceptExtId = ((mask & code & CAN_MSG_EXT_ID_MASK) != 0) || + * ((mask & CAN_MSG_EXT_ID_MASK) == 0) + */ + bool acceptStdID = ((pCanUsbSettings.mask & pCanUsbSettings.code & CAN_MSG_EXT_ID_MASK) == 0); + bool acceptExtID = ((pCanUsbSettings.mask & pCanUsbSettings.code & CAN_MSG_EXT_ID_MASK) != 0) || + ((pCanUsbSettings.mask & CAN_MSG_EXT_ID_MASK) == 0); + /* Configure acceptance filter for standard 11-bit identifiers. */ + if (acceptStdID) + { + if (PCanUsbLibFuncFilterMessages(pCanUsbChannelLookup[pCanUsbSettings.channel], + lowestCanID, highestCanID, + PCAN_MODE_STANDARD) != PCAN_ERROR_OK) + { + result = false; + } + } + /* Configure acceptance filter for extended 29-bit identifiers. */ + if ( (acceptExtID) && (result) ) + { + if (PCanUsbLibFuncFilterMessages(pCanUsbChannelLookup[pCanUsbSettings.channel], + lowestCanID, highestCanID, + PCAN_MODE_EXTENDED) != PCAN_ERROR_OK) + { + result = false; + } + } + } + + /* Register the event reception handle. */ + if (result) + { + if (PCanUsbLibFuncSetValue(pCanUsbChannelLookup[pCanUsbSettings.channel], + PCAN_RECEIVE_EVENT, &pCanUsbCanEvent, + sizeof(pCanUsbCanEvent)) != PCAN_ERROR_OK) + { + result = false; + } + } + + /* Start the reception thread as the last step. */ + if (result) + { + pCanUsbRxThreadHandle = CreateThread(NULL, 0, PCanUsbReceptionThread, + NULL, 0, NULL); + if (pCanUsbRxThreadHandle == NULL) + { + result = false; + } + } + } + + /* Clean-up in case an error occurred. */ + if (!result) + { + if (libInitialized) + { + /* Uninitialize the library. */ + (void)PCanUsbLibFuncpUninitialize(pCanUsbChannelLookup[pCanUsbSettings.channel]); + } + if (pCanUsbTerminateEvent != NULL) + { + /* Close the event handle. */ + (void)CloseHandle(pCanUsbTerminateEvent); + pCanUsbTerminateEvent = NULL; + } + if (pCanUsbCanEvent != NULL) + { + /* Close the event handle. */ + (void)CloseHandle(pCanUsbCanEvent); + pCanUsbCanEvent = NULL; + } + } + + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbConnect ***/ + + +/************************************************************************************//** +** \brief Disconnects the CAN interface. +** +****************************************************************************************/ +static void PCanUsbDisconnect(void) +{ + /* Stop the reception thread. */ + if (pCanUsbRxThreadHandle != NULL) + { + /* Trigger event to request the reception thread to stop. */ + (void)SetEvent(pCanUsbTerminateEvent); + /* Wait for the thread to signal termination. */ + (void)WaitForSingleObject(pCanUsbRxThreadHandle, INFINITE); + /* Close the thread handle. */ + (void)CloseHandle(pCanUsbRxThreadHandle); + } + + /* Close the terminate event handle. */ + if (pCanUsbTerminateEvent != NULL) + { + (void)CloseHandle(pCanUsbTerminateEvent); + pCanUsbTerminateEvent = NULL; + } + /* Close the CAN event handle. */ + if (pCanUsbCanEvent != NULL) + { + (void)CloseHandle(pCanUsbCanEvent); + pCanUsbCanEvent = NULL; + } + /* Disconnect from the CAN interface. */ + (void)PCanUsbLibFuncpUninitialize(pCanUsbChannelLookup[pCanUsbSettings.channel]); +} /*** end of PCanUsbDisconnect ***/ + + +/************************************************************************************//** +** \brief Submits a message for transmission on the CAN bus. +** \param msg Pointer to CAN message structure. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +static bool PCanUsbTransmit(tCanMsg const * msg) +{ + bool result = false; + TPCANMsg msgBuf; + tCanEvents const * pEvents; + + /* Check parameters. */ + assert(msg != NULL); + + /* Only continue with valid parameters. */ + if (msg != NULL) /*lint !e774 */ + { + /* Convert the message to a type supported by the PCAN-Basic API. */ + if ((msg->id & CAN_MSG_EXT_ID_MASK) == 0) + { + msgBuf.ID = msg->id & 0x7ffu; + msgBuf.MSGTYPE = PCAN_MESSAGE_STANDARD; + } + else + { + msgBuf.ID = msg->id & 0x1fffffffu; + msgBuf.MSGTYPE = PCAN_MESSAGE_EXTENDED; + } + msgBuf.LEN = msg->dlc; + for (uint8_t idx = 0; idx < msgBuf.LEN; idx++) + { + msgBuf.DATA[idx] = msg->data[idx]; + } + + /* Submit CAN message for transmission. */ + if (PCanUsbLibFuncWrite(pCanUsbChannelLookup[pCanUsbSettings.channel], + &msgBuf) == PCAN_ERROR_OK) + { + /* Update result value to success. */ + result = true; + /* Trigger transmit complete event(s). */ + pEvents = pCanUsbEventsList; + for (uint32_t idx = 0; idx < pCanUsbEventsEntries; idx++) + { + if (pEvents != NULL) + { + if (pEvents->MsgTxed != NULL) + { + pEvents->MsgTxed(msg); + } + /* Move on to the next entry in the list. */ + pEvents++; + } + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbTransmit ***/ + + +/************************************************************************************//** +** \brief Checks if a bus off or bus heavy situation occurred. +** \return True if a bus error situation was detected, false otherwise. +** +****************************************************************************************/ +static bool PCanUsbIsBusError(void) +{ + bool result = false; + TPCANStatus status; + + /* Obtain the status information. */ + status = PCanUsbLibFuncGetStatus(pCanUsbChannelLookup[pCanUsbSettings.channel]); + + /* Process the status to detect bus off or bus heavy. */ + if ((status == PCAN_ERROR_BUSOFF) || (status == PCAN_ERROR_BUSHEAVY)) + { + result = true; + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbIsBusError ***/ + + +/************************************************************************************//** +** \brief Registers the event callback functions that should be called by the CAN +** interface. +** \param events Pointer to structure with event callback function pointers. +** +****************************************************************************************/ +static void PCanUsbRegisterEvents(tCanEvents const * events) +{ + /* Check parameters. */ + assert(events != NULL); + + /* Only continue with valid parameters. */ + if (events != NULL) /*lint !e774 */ + { + /* Increase length of the list to make space for one more event entry. Note that + * it is okay to call realloc with a NULL pointer. In this case it simply behaves + * as malloc. + */ + pCanUsbEventsList = realloc(pCanUsbEventsList, + (sizeof(tCanEvents) * (pCanUsbEventsEntries + 1))); + /* Assert reallocation. */ + assert(pCanUsbEventsList != NULL); + /* Only continue if reallocation was successful. */ + if (pCanUsbEventsList != NULL) + { + /* Increment events entry count. */ + pCanUsbEventsEntries++; + /* Store the events in the new entry. */ + pCanUsbEventsList[pCanUsbEventsEntries - 1] = *events; + } + /* Reallocation failed. */ + else + { + /* Reset events entry count. */ + pCanUsbEventsEntries = 0; + } + } +} /*** end of PCanUsbRegisterEvents ***/ + + +/************************************************************************************//** +** \brief CAN message reception thread. +** \param pv Pointer to thread parameters. +** \return Thread exit code. +** +****************************************************************************************/ +static DWORD WINAPI PCanUsbReceptionThread(LPVOID pv) +{ + DWORD waitResult; + HANDLE handles[] = + { + pCanUsbCanEvent, + pCanUsbTerminateEvent + }; + bool running = true; + TPCANMsg rxLibMsg; + tCanMsg rxMsg; + tCanEvents const * pEvents; + + /* Parameter not used. */ + (void)pv; + + /* Enter thread's infinite loop. */ + while (running) + { + waitResult = WaitForMultipleObjects(sizeof(handles)/sizeof(handles[0]), handles, + FALSE, INFINITE); + switch (waitResult) + { + /* CAN reception event. */ + case WAIT_OBJECT_0 + 0: /*lint !e835 */ + /* Empty out the queue with received events. */ + while (PCanUsbLibFuncRead(pCanUsbChannelLookup[pCanUsbSettings.channel], + &rxLibMsg, NULL) == PCAN_ERROR_OK) + { + /* Only process events that contain a CAN message with a 11-bit or 29-bit + * identifier. + */ + if ((rxLibMsg.MSGTYPE == PCAN_MESSAGE_STANDARD) || + (rxLibMsg.MSGTYPE == PCAN_MESSAGE_EXTENDED)) + { + /* Convert CAN mesage from PCAN-Basic format to the one of the CAN driver + * module. + */ + rxMsg.id = rxLibMsg.ID; + if (rxLibMsg.MSGTYPE == PCAN_MESSAGE_EXTENDED) + { + rxMsg.id |= CAN_MSG_EXT_ID_MASK; + } + rxMsg.dlc = rxLibMsg.LEN; + for (uint8_t idx = 0; idx < rxMsg.dlc; idx++) + { + rxMsg.data[idx] = rxLibMsg.DATA[idx]; + } + + /* Trigger message reception event(s). */ + pEvents = pCanUsbEventsList; + for (uint32_t idx = 0; idx < pCanUsbEventsEntries; idx++) + { + if (pEvents != NULL) + { + if (pEvents->MsgRxed != NULL) + { + pEvents->MsgRxed(&rxMsg); + } + /* Move on to the next entry in the list. */ + pEvents++; + } + } + } + } + break; + + /* Termination event. */ + default: + case WAIT_OBJECT_0 + 1: /*lint !e835 */ + /* Stop thread. */ + running = false; + break; + } + } + /* Exit thread. */ + return 0; +} /*** end of PCanUsbReceptionThread ***/ + + +/************************************************************************************//** +** \brief Loads the PCAN-Basic DLL and initializes the API function pointers. +** +****************************************************************************************/ +static void PCanUsbLibLoadDll(void) +{ + /* Start out by resetting the API function pointers. */ + pCanUsbLibFuncInitializePtr = NULL; + pCanUsbLibFuncpUninitializePtr = NULL; + pCanUsbLibFuncGetStatusPtr = NULL; + pCanUsbLibFuncSetValuePtr = NULL; + pCanUsbLibFuncReadPtr = NULL; + pCanUsbLibFuncWritePtr = NULL; + pCanUsbLibFuncFilterMessagesPtr = NULL; + + /* Attempt to load the library and obtain a handle to it. */ + pCanUsbDllHandle = LoadLibrary("PCANBasic"); + + /* Assert libary handle. */ + assert(pCanUsbDllHandle != NULL); + + /* Only continue if the library was successfully loaded */ + if (pCanUsbDllHandle != NULL) /*lint !e774 */ + { + /* Set CAN_Initialize function pointer. */ + pCanUsbLibFuncInitializePtr = (tPCanUsbLibFuncInitialize)GetProcAddress(pCanUsbDllHandle, "CAN_Initialize"); + /* Set CAN_Uninitialize function pointer. */ + pCanUsbLibFuncpUninitializePtr = (tPCanUsbLibFuncpUninitialize)GetProcAddress(pCanUsbDllHandle, "CAN_Uninitialize"); + /* Set CAN_GetStatus function pointer. */ + pCanUsbLibFuncGetStatusPtr = (tPCanUsbLibFuncGetStatus)GetProcAddress(pCanUsbDllHandle, "CAN_GetStatus"); + /* Set CAN_SetValue function pointer. */ + pCanUsbLibFuncSetValuePtr = (tPCanUsbLibFuncSetValue)GetProcAddress(pCanUsbDllHandle, "CAN_SetValue"); + /* Set CAN_Read function pointer. */ + pCanUsbLibFuncReadPtr = (tPCanUsbLibFuncRead)GetProcAddress(pCanUsbDllHandle, "CAN_Read"); + /* Set CAN_Write function pointer. */ + pCanUsbLibFuncWritePtr = (tPCanUsbLibFuncWrite)GetProcAddress(pCanUsbDllHandle, "CAN_Write"); + /* Set CAN_FilterMessages function pointer. */ + pCanUsbLibFuncFilterMessagesPtr = (tPCanUsbLibFuncFilterMessages)GetProcAddress(pCanUsbDllHandle, "CAN_FilterMessages"); + } +} /*** end of PCanUsbLibLoadDll ***/ + + +/************************************************************************************//** +** \brief Unloads the PCAN-Basic DLL and resets the API function pointers. +** +****************************************************************************************/ +static void PCanUsbLibUnloadDll(void) +{ + /* Reset the API function pointers. */ + pCanUsbLibFuncInitializePtr = NULL; + pCanUsbLibFuncpUninitializePtr = NULL; + pCanUsbLibFuncGetStatusPtr = NULL; + pCanUsbLibFuncSetValuePtr = NULL; + pCanUsbLibFuncReadPtr = NULL; + pCanUsbLibFuncWritePtr = NULL; + pCanUsbLibFuncFilterMessagesPtr = NULL; + + /* Unload the library and invalidate its handle. */ + if (pCanUsbDllHandle != NULL) + { + (void)FreeLibrary(pCanUsbDllHandle); + pCanUsbDllHandle = NULL; + } +} /*** end of PCanUsbLibUnloadDll ***/ + + +/************************************************************************************//** +** \brief Initializes a PCAN Channel. +** \param Channel The handle of a PCAN Channel. +** \param Btr0Btr1 The speed for the communication (BTR0BTR1 code). +** \param HwType The type of the Non-Plug-and-Play hardware and its operation mode. +** \param IOPort The I/O address for the parallel port of the Non-Plug-and-Play +** hardware. +** \param Interrupt The Interrupt number of the parallel port of the Non-Plug- +** and-Play hardware. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncInitialize(TPCANHandle Channel, TPCANBaudrate Btr0Btr1, + TPCANType HwType, DWORD IOPort, + WORD Interrupt) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncInitializePtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncInitializePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncInitializePtr(Channel, Btr0Btr1, HwType, IOPort, Interrupt); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncInitialize ***/ + + +/************************************************************************************//** +** \brief Uninitializes a PCAN Channel. +** \param Channel The handle of a PCAN Channel. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncpUninitialize(TPCANHandle Channel) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncpUninitializePtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncpUninitializePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncpUninitializePtr(Channel); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncpUninitialize ***/ + + +/************************************************************************************//** +** \brief Gets the current BUS status of a PCAN Channel. +** \param Channel The handle of a PCAN Channel. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncGetStatus(TPCANHandle Channel) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncGetStatusPtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncGetStatusPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncGetStatusPtr(Channel); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncGetStatus ***/ + + +/************************************************************************************//** +** \brief Sets a configuration or information value within a PCAN Channel. +** \param Channel The handle of a PCAN Channel. +** \param Parameter The code of the value to be set . +** \param Buffer The buffer containing the value to be set. +** \param BufferLength The length in bytes of the given buffer. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncSetValue(TPCANHandle Channel, TPCANParameter Parameter, + void * Buffer, DWORD BufferLength) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncSetValuePtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncSetValuePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncSetValuePtr(Channel, Parameter, Buffer, BufferLength); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncSetValue ***/ + + +/************************************************************************************//** +** \brief Reads a CAN message from the receive queue of a PCAN Channel. +** \param Channel The handle of a PCAN Channel. +** \param MessageBuffer A TPCANMsg buffer to store the CAN message. +** \param TimestampBuffer A TPCANTimestamp buffer to get the reception time of the +** message. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncRead(TPCANHandle Channel, TPCANMsg * MessageBuffer, + TPCANTimestamp * TimestampBuffer) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncReadPtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncReadPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncReadPtr(Channel, MessageBuffer, TimestampBuffer); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncRead ***/ + + +/************************************************************************************//** +** \brief Transmits a CAN message. +** \param Channel The handle of a PCAN Channel. +** \param MessageBuffer A TPCANMsg buffer containing the CAN message to be sent. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncWrite(TPCANHandle Channel, TPCANMsg * MessageBuffer) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncWritePtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncWritePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncWritePtr(Channel, MessageBuffer); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncWrite ***/ + + +/************************************************************************************//** +** \brief Configures the reception filter. +** \param Channel The handle of a PCAN Channel. +** \param FromID The lowest CAN ID wanted to be received. +** \param ToID The highest CAN ID wanted to be received. +** \param Mode The type of the filter being set. +** \return The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on +** success. +** +****************************************************************************************/ +static TPCANStatus PCanUsbLibFuncFilterMessages(TPCANHandle Channel, DWORD FromID, + DWORD ToID, TPCANMode Mode) +{ + /* set result to error. */ + TPCANStatus result = PCAN_ERROR_INITIALIZE; + + /* Check function pointer and library handle. */ + assert(pCanUsbLibFuncFilterMessagesPtr != NULL); + assert(pCanUsbDllHandle != NULL); + + /* Only continue with valid function pointer and library handle. */ + if ((pCanUsbLibFuncFilterMessagesPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */ + { + /* Call library function. */ + result = pCanUsbLibFuncFilterMessagesPtr(Channel, FromID, ToID, Mode); + } + /* Give the result back to the caller. */ + return result; +} /*** end of PCanUsbLibFuncFilterMessages ***/ + + /*********************************** end of pcanusb.c **********************************/ diff --git a/Host/libopenblt.dll b/Host/libopenblt.dll index 37dbbbb751785240b1eea68568b665dcaef22b20..e1ad8195bdd7d5e001d2e919787b627175f7e7fe 100644 GIT binary patch literal 98304 zcmeEP4M0>?_J0F2IO@=h1xBekr5NQ$B-W}(p*U*g=l~8{`Gqj}Km;0Q{3t14z!)Z4 z+pKlB+BB=bwYBT+no?N{Mqs6dWv*@RE}7Y*p<eNuj#I*UJRDbxU;0aC{+YdYBwsM}r3<)iefM5otC+d>@;Q8N zvA)P!xYU}tT%VO$P*7;oFR|#Y_5yuwfqv@jbp7(eY|EI)$bQ2m)K5QAm2&vCqdM7r z`9Pws1U$-r?l6%m|e>8e}hv=%Px^8N}9$L&PS9x-<7Z}CgHo7uCDg~K>5LQMAl za$6N5^AI1-CF22^SgG=_^mv@(vccqoWeRQ*;aG=XVtVl3Mg-*d~S zK{6^;a0gNP>HfypEURot9Q_hJqL1ntRKA|$7LT!JXWBAB>(xkah8qP}2j};fjH1Vg zV4SUl5>T)eKW!BD{Usykm|PL?o_>f2jU4L6Ees?SaLHq=#nvn&F`j@ojKO(5!CCSP z0cf}Yz#EZHbt&owHwCewzq8=L9{OwDKA7WL?|>a>eeQC2P0;ezFbXZu!#hZi!!CpO z>ZR}|K;*5;8{qx1FT6Wf!KvONvWYJFfLyulReL#bbp_t=&2VkqQ5^J{&Z z%FP`LZ<`w4zaYofzxRRn^>BE)5%5O#hj;WIcn=T-Gh^XhOsUo(@Rk#TRg2)IpmDbT zj;hdlF}(GZ1BFv5aMuLe^1i~DHzzL7-DuT2PbD1^HXe!6m;WDUAu1FRh;Ca{4 zpajYWfGqP*0MCPNoJG#WfIQdV|8-$G^23k$4iU%#@43ljs%Rkd?`xgJA;b-74phe9--YHNti3&unls_ zT%MxwG^65qfRp4(wZ0vXeg&ew8!4w8b-MZ!Ezc1x`f$*)0iM4?MEKDX=c8o^WNlWb zZDOLHq4Vq$Wql9^OJs+{h9*QvY&cW-jZ}Uel|Nn?SpHfdka=;1ul$RByoh4FSQuQg z(O0slb12`dZ&c@hQJITT9DgvV%nSX>9E6G*)u~c(Q(`>LVokqTBWm?g{K^~_%lWzb z;2)az=7UhFCe0mAI0iS~vfUCiF7(JJO3Uw{s#AR@3SG7yYlXR6t(rP7VliLI%XNFR46Aj1|K+j(g(K9`cHg%Jslc4hIt^VeW2d8D;WT!-%w4W26yqrA;_QOU(CNjD`T-v-ULe9QLMSZi^mry1 z)K;qRZba}iP*uJzsJ?wcsW46iEzXgtSm26eWEi_7^4xu0=1#{hh11*#0ul}C;)yO} zmn&80G>ddmDHvsQyiSc$s^IyXn9Zw+s~|tj9p-+)W$1$KP(ycIi1*fJ&z|n`(r_yY za|dCrCCu@C12NykFzYxwfqhM2$$;Q%gTSs}U>a`4L`R8^vyGEAOQ<~V@qiZX_T4bQ zLtJJZ6i#Czdmy}u(G0pw;V4E^=uH$p%fBCEGa{SuKPID=#OX8$PD7j1(C##JI1QbU z7xh3A`=b`i#KXj6X<(n)(ISXEjh$pQ~NF6flxJ?0u>HRx@H;Mh&cTjjf+Y z_#Fm}iS5>Yf(p|CNP2|`bq$~-B@&tp?NqMID7Xf=3_`d`Il-(hR^u6p<&JTg1*Tep z%h=A8t=&-~aQ0?l7;!~Vn1q+VCiW;%IYBGNp^_jV+^hk<2|5W^hlEQ5K$o%2(F(KNwW=doVm?z;_U^ZJEo^mS|`%j-aZ$7oa&L8ar%* zp@twi(a>QVfG8+nny{V8N$EZ!OAat;LJLN(kRH?44qFuXmP*~3%g|vG8pJd~gCVU? zvN2G|Z&&y$WRLAjl8LtBK5*98TDt<9YNuG;XQ{g5`=h#B;rTa-bts8dcRaPMJ2BR} zga%igt{Li7PjqP+4cBOF#S=xagq>;rv1u$f8q`{!p_bazFML(#nSN*&Q^I$Mwicpo zo+gmC4m;5%HL7L;;{n091cB{kV5F-<0Ye$I710P`>>1^WUgNF8Q-G807d2CwTJu~a zFs_2<&y>3u#xCI5OKJ;#XiWZ?Zbv%oj&rgaR*{&-nmx^Q_>F`w=7K#Ur55GI8 zQW-!&VA_g*QUSVKxG84^eZRIl*iLPT6#{2Q8m}4~v9YoYk(KRKDH4^Q3 zG^inBx5CnDZ z3Q%VUV%--p6yP80QM8rgJsx?eokh61F%d;xruTZiOYjHX6S3@s=mv z3dGyR+CYW*7Ar5L$gF|n5?Co9_|71(_ZS$7;Loz=uiZi+Ss0Jt!NpjL4ifc3k79IID);Hmbu@m5IBo1IT^}yD>$9 zWoVs1@CKN2O`>|`Tx1J311XK)TtK1@zga2b7`fYNr17oG+%^(@fEoe`f759^;xZp$ z#t`N@Re0|^LJHBLfTdCa2bt=0Cu1_8Tw&A=Flt8*aGGgS2)w~qOZ>17kg%3Bte_Dc z1R*kLMgkWjFjeg|lMP43SvNv;H>lBPp^o83-N=1Tb34l-j0~f)Q`O=LE^yR{UFOjiR zp;&BcIyYk1|eyav)0S1be*L01C^9cyosnyn|h9 zcg|1}I7TK@SPC1lwPvGfwcZHN(GUebD2qfs^w?hr15q_r^%zuIIC#qAk$XL{dJ3kw z%(Q4LXq>|;1~S4Yv$Aj2B`UO)%%J-bPIB#l21*uU73(c%aKEyUVIX?Ra7i}4Ke}vQ ztwUQZh3DUAOc&vq%~>q*B2_73){&tX%K%Ym6RE=Q04`a5Q3O+zL??ABj9MCMcfx9? z3JaOuP$WqXL)SqhaFQHM6b*=_ae3?4V2NbjUn&qe$;^{zpH4GA&*OxJZA=+a9t{Lo zS2m5RSx40zKOnH0i-DPq7a+z?7aARz6{*yzQmH-4PcneQY$}b$bf3Dr6}y;<#yKa;ex0S+T5(Q`XGs=LsO-H9+-XpG7W&{169195@#^0Q*p@!={0}nnlm6q5{?r z9c{zq@zf!wq30nLTe6`QW;`zrtI{R>mVtmCd~qP&1q>SVjf$69CyLo7y11?!*MJ;0azJQCSdPege>JjwNQ3%gRcvV)>%)-oUi#6S1v*i{nEylhH5=Qcd>yNQdFVgkANFi;WLip%6lxp36_ePq*@9C*juCf7z*^8ghytSq?e^x%cl|jh5%?PN_R#;G ziQdsf2oDAmQz=!FEq5lo$FK01>KDFqFD zXAmKc77@&@7IHr(ji*MLDhy;KC?yVU0#G*&k$xRI%=EVm>1U^3(tyxL^q2;Lo(6dS zt@L_{9vB{J(MMQ|Lf9%_km5_kDJ9~1l$TTB#^kP7;%{WlMMe#U^4*0?+gM9 zV_;HKsw1!#0-JMLAhr*)sH)fzOkpi0N%;07MnrJ}W@;v(22W_Q(twunAB!WHEGk?^ zOn0zyr=%d3?J(8z8^2ZnE%BsB{!0zvC=pczdiWzjL``EvNwj}TV4Vb(KQxf`p%U#Y z(7w^i1Kal-EoW@sA20C<&1+o)*QlfH}x zr7(%9i0VmSy!J7n4&5JCQY7__CvtRpkW&HAzq$00$YJL3S_0chU{RL`g0*DITFz*X z!bBZYZ0LKYNIR95BTh-kWd8}^vYLB279?NUzA)XqnM#gBq5PvkCFih`B{jH(z-kF> z)D?jQ-Y64z2_x_#?-Kv*B7tO=*o=wDyRawn*#3jmUkZJYOUxqnK`o2BR|xgd?jXhA z@=vDtK>Q-Wy2-`MfKVXBf&U-}pnn;xh0_pw0?);wS=iwDH}ANG&|9o;N48@~>PRAA z+-kty>Lz+}spW0VlMBQR@2~O8beQ3nvRotrcerTV6U#Ks0BTY(bZEzPIN?<}o41Dk z?VWbRMB1 zS>LA!Y%75czcP@lB@7Ib8^w&<2wFn-ZB@FWnINa&oCF4`pfDkr!Yd>Sy`o`(?);JH zttUdpg9iS&AVNO4UE~j=cNc-}Ca_6yf%N{Ffqfsn?5G9N>i~EFN3DqHK>-5EKj)vo z-`ea0od5ZpvzcFusv~vWWyIr7u$Q-870BaRw~0NnwxXW`-0I*r0)V3U zNt7hPn7l>33K&d^%SsLrisCT0N_d3CXgt(dM)fdpJfJ!l=JVgEQ>W(=LjKbE{fsd2mcjs!Nf#E)zim_UXk5_Hz?3 z!QMU3Z-PAt$)r{_H*p}5+XjGk-~ka_Gr~0Artltw^DPBjW_GsKQcN>#@elHJw2jM{ zTT`;<#Yn)hoq}V(CyKl)3Rq>u0U3h;ks$I&1b!L~@94b)vHghPw-En^UmYmVyFn>| zX)7)X$4ve410D}pzE*<=2-gJYVByY04zF;JphhG{EG(;#m5swYy!-9~6}t{N_&Gtv z_F=_hdtT%$R^2;jccxvt-R!A+S6kskPT@krNQ=kMzTXY?NkTniBv5PM`S(4~&7~T7 zQBT1(4ejYrA0jRxx0RI;zql1xGZ9cv;us0QeKAYmxD^*5fkcG_6@IW=2Bz2s^Xe5M zJA{plAROSp%-pUdQAjXz8H_NGEK!5f#GdlFJZPZ}YcrSkq39?W9qo3b2WLOxz++0` z8&C`T0%lxbD9q^#z8XsvAx4~h?*TG&DebZC19$J?^GE- zgvj2RU~Vpss5UfL8SrTQ5L;mTu>*1lV|5`_tojJ(q+E3pF?Kd)%ayMHO=JZ;(Z0$b zHM7c#>Jdvw!;OcW#zQFW!*KI{r+Gg({-Mj*Ok0i3txF-$);4U3hzewz4VE)$q0zgH zM313}YeMHN1p{q7c#|#O7T?DN3GfD{MM!4u#qJ!4KC~ zeuiS$X70^sQ~+WNYubwUDB7rY)Q34~?-rZAzh%upS!Fnmj0$@yrtywCY(!#{2~)c@ zVk+8J{bAawb&>`0%ROICucIwm1!i2%FzCW@hY((6IMI3`iX*D*D+05{db8EH0HX$L z0%o;iSD2IbgE5;@$?3c6KcWrs@P1S_k>po)3)5n%S#a5?&572BS;O(qKx-gACM6yVa#S0? zF0EHo8NUVuoZ&=bbIIk5`=?kFK@H7cyNpp)O4|Pyx#WJ60I{axRFK6N52k6*AQIC8 z8U$+(J?uY%zrJ?RK`I$@4OFr%NF_f{6~VL>yO7~Uz;Vm=(V+wFs~VgH$2eoEx8toR z*v@oq<*xzt5`oqq4gj6$1$`Kxu;F(EL9z2wosQ4qamEl@(|6@fFhri=mD?tB=pDL% zk#i?Wt1Rwu2tfZi_ecb&<8WVr0P8v~L4djnH|2sPI5(|yQnox?IqBmgTVdHieB2gfD4sIOe7v8i2vFloKr^(m z%5VxIJ>}>Ow=Rc;#e?}|_`WLhNw2mrm2k_=xf#$W!*^8~PF5RFm9|2EPIeI0FQJm1)8_`=5l8zjQV#DfFb zK{jwRaD$qd$~W%5(!0;HlE60*cvld(4dCKP=r@2N6|ZH=x0{Swqp}|)Nhb{*B|?Pu z-ffJhf@gTmxM>`$G>j{&>#ga;S#_h~l&w#M$51p`+eUA+&V3aTmQcsK zqAkRUI6(1(;rVwSd9#Rd=2P{bg+r_{H8aB?YRJXOc*Wgt^N6rCVKX_;Qq@%J3!7eF-rwK-!lzn)h)s*yA$j z-V>l|R1%`UAjL$n1{7=JpQB=N{Kmi7ZN|W2K^5axBN>SXwfn(F!95C_gn4)i#_4BB|Ap>A}>IGCnd zc>bp8{^?|zqV{S(G-uszB-!` z;q?ntE)2(9>OYG~*o8Szf~Kamepg@|=89fsLM-n}X8yQbf0iEMfK+%N20_svwfC$N6lZ&LR zsjg^-DolQ-^0$C$Qc)5RvHhAV#K;?_s6Y01e5KLq7rzw+@o^IaOH1=BZJ00)gDjUw zwD*04C5BgjCN*gZS$3 zbAV(^FY>}9TWP!oBYzr+nNOAOQdF6xiJz+YVqvF>;OR}YzM|L_cwO#q+U?P#0$8ZK zcoX9$)=$MfubIG4%@<=itmD~qf~$W(f#Sf6_PehDWcQ{J%5_4-S-p7M>JF_yh!_$aI z#vGat-vxza%n4dt*H+NHndsE6eTe0cB!h(a{=l?^cTfDAskQkObQaS%6-yH^yM(UO z+D0^s_)L-bX7MmgE-_CqQ9>%yUs;o+{fG(f!4in@3ez(zi23$`20`^tq0EuHoCciE zGoC`XkNHSzCT3frpr|7lexoR;vzE_RVrM(ClfN;L@gLkE)|<)aPXty42p*T!1c>p^ z1m-87zYwGjKzvmYWEDe5@*yiCvXtH-@yh2h#$T^|XrSbk&xhdvq~+Zp`Ph~*&9`WG zM6&*&RiWLEaX~lH>`<{}uV~Pj>M*uQ6Ru!_N0Y9rtdj&0L+nLoOYh-;)rM0L8@gck zLq0z5ObToe%)UTLn9kFV7{8lXkUTk%S(V7fDqisvt7{~6bufan&`n{0Bdz^}gZ`>0 zS_Q6e)v>k1eTOLuOOX_%_AMt;oNu*KQ*Bfu!aXwp8!aIPVq@!-LFnWXBy2>2h>fh+ z+mn1fm$yGDYl{ivIBXHVBH)du7uTXf(k|hq4TTC_!s{4%vUkz^l%qd#@Qc9|{$LPi zmZ1o*B9wxG^a`0=wZ@9*4s5e{7=P+pxW)<?bVy_5Tw`8Y;oG{HEw`AzERs_7)O`wLfU;QLRI#S2i38`ND{pHJK7y|Qw2d7 zx35J8b9|)03(IW0qHy<-1}+k=@M;lNRV0mLs-wxTV&(v5f{DG(HilKO{v&K9tdFE} zB?VAvw^y)dEWL*}=QyKDcikf;c2H_GK?mo?j39iMI8H(tRA3TSV+AOq2anW7#LBIZ zD#r*zZ`NNcHW9P}mQro_PTX6Lnun#D{E4*WvuGj4?fg)lKGi&3cwDuJnbm_v|npKc;qdfWtJ@geFqO z(`qUSk4YLVrRwMw8PdqRC-@CsNW+ZZ(Y~y9`$J44zg5^}Q-Cy4Gs?d8X{1*)TW^JE z_Ko)!jRO|OE1H2QheYG@kZfuJEz9Qb2+Oi*M)*v!AsI3G3Yt*M&m(RA1rZ>Puw>(5!^Jn1BwE;=i=|c!D6ti@HY%GMrU+9TD?g=n!UwMdR3Id{km64_TLUAdG30DC0UqANJn6jcDUu9wq>_9}gb|xXi;>5tezl4dFBK z@Q2wqj25L?Kv-h^J=#DuAd7b$@il%L___t2zkza~DrmoIaeq)6Fpeki8Pfq?2hSgT z4#52_Um`)a5ahfWfsj{Ah`n=;u*5gDzoa*orD+MXGunHLNqxSrznnFeYC!{ZY0&Z``^%Qa zP^L}S`YTg9%!1bx9z~L`zZ3v1>r%f=pDu9-pGlXz?eT2;OJ+92p&BWVon*WL;^lQW z1+X!FbI`8J%Dwe z7I`i7xNNX`CXmhZ&JRwZy?QP514trPv-!a!0L#2yh_KA-IS8MQ*VLXue<2QpI1u7M zhy&*a2hji3?*2!>{+)M!sY$gMy$?D&?0;oTAvS8Ecei-H)QBNVCp`agZho9RK%q@$ zOl5I;elqSGAWWMH38HXSCcEF(%IjwV#Rho(D6Rqu?3FskbGCejAWfJy3RoMI*mcGE zE&toqPF_j-vf_P?MAZtOn+=q!;Q6CG05H1w2lt2(MOg6ZG6`CS6sxmc&PXCpxb`XU z?OjhH1NC*ZUXmenAiirI9dwtQgF7u($x@jc9agtqQZEM0kQ2Q zL5gjs*lI&NHZ`Q$uvPA75X)~Nw&_iQY})I!$#!CZ z%(-z$wv$GAbV#<7YC=`nVk6H9X>lvH8mW;TBEzL`aLMI8h?nK04hAWvc7lk9!u zdPMsKBQ-~)JKA)ze1dPr?4aVj1NYDIz;mgjZ}9x}EeEJ+qQ&_81%YiOu$Z(!up1c| zt!Ukf3W@s(vk{`r5_!l5g{^*omJF-b@u(p?s?zD!9|dr`yiV zn#OWM&j}k5kJ~JZN9EDhnTjG!!qH8P@8VW4j*eoxg@ISr-Axy$zKMh9+U=|!LGQdQ z!TJN2@CMaGm?mMKlBb{=;Kt50p+-iGS)=Vr&_j33`tT?2roEnH3ik#69C2@h+8&cj z5_fxa(1f>#`846&u)pU!71V#UoVNeiwp#8#YQ3RD-cXA-bi^Cl=?&F+Lk-^0Zf|Ih zH`MG6?e~TbdP5r#dK9in^`tj$3qldl+9uU=-ngw&$lJIN0)UTCN2}cr&%bdyFBffq zw&GpDunxr;P354Y+f1dwN#~5D5-`PcOcbpGt=RCLg#Xbwf%vZi zeqf~wr3;b46=o8qk)i0Z|I_>WKQhrr3F|>1h8Hiabcr<( z_V&XO7nFpy@&q!-R@0}YtUvq(j!QU_sLCAc4=EJTALbx$lZv7rgF9>9Coz4&3&TlF zU2utihbxleaK6gd+4g0~Wrd_~620%(@S>LU?rc!siZd*D_oR!f9gKLK zr|Wb@l92FPn4{#7dgXGL`H*M$LsM6By4q`&iV*JkFJ6;QL0piU`qDMiDQNC=PpiEq zMFL2CaWHx(1`z+!VAyU35bG_&ed&t>S#iLieJP3sfdR{3r@%te7F{v==!3|~Iy6!{ z$mt;Kx%blpb#wbEx!SN_JXMaKm31sPA(r0)IrB3!0)>6_GU|d2C$Z2*T5&T076FEj zni~lC3In7bUR$vSy{ni>TTz5C^+I%5CXXYliyac8R=Y!K?m~Mv34eyF;B`1yW$~y! zs?>!&dv;!E6bk(vpph9UJkgp`g$~vn%RYisN@<8i7Y;K)HRBiJYi8oxLk}dr zJ(OH+-cx1%m?0e|qC{k={bCWLh>HesyX9QFDVL*UH^bE+$2N$#8mi2PA*_#4wYWT( ziz8S*64Ew_BGMR0M5)4!?DUCW_05N|K^%{Kw1E|uhdW7o%-5$|K02#)WuB5J5#)sj z6|`3O)slE@E`sr?Q}sS9IAd`uxw;S_yI zm`X`^Y@d|L)+I58VSv#T#EDJ`u#o|>WB_7(AnuUIq3sJd zBaK$T91W9TS{}s{SwZ4@Jd)*iuO}g#JH?)pW3h74#hgwLHxll8xXExc;pW0Efm;qI zTTrr}Qmt(%m`V__Fr7-V0%NGOjy!x!g!qDrsm1t#EZ!AOvA$Yxh?Ti9)Q3x|D#N0GhV|6+gesK=dTZ>QQ1W`!RQ8^j+9 zGDPEmo4~Xc9|H^PRLiJ~EVbj>9<@td_K^pK;JV&MgTT25uzpc)lZc|*b*odARp!=4 z!-pKb8uTF+yT$j3F%)b~e6u7PYR}#;M&9bFVXCUBk)OH$RzocyNYUmoMN`9g-bNc{ zVi$yN0(KY_;T$AhP-dr`_qz=H6IIj`7006w!98raIe_}@i|I9gy2ce3?8#S^q+Nn~ znGZR3;UcL+?w#Ic*Db4|bnnObFT375gm^v*EFVIdXw8-MZz~|_eSeyg??iZc@lt4@ z_f>zUVoV-E-)kV#7t~}e82_LxiAS1sblH8UnptCdyAJEmDEbMI)N}vu73b|F&Q22N zqw@m$6$>(p;#?16U6I72ny9^Mdgw^oM14B6-gkrUb%5VYu?}$zz`CuaguV`#`RrQ* z(MJ$^d9>h)ChQ~-7GUb)GATDvtm`^L>YUlw>73Kp<;Gp%6bmZYLEgV!+)bew|K^iE zKlvh3yX*5NYAAHQF|$3mbfD4jDY`xLr__ZGf|#iN=5aNKHd9`hCwj2hfX%=q9{zS3 zpLCQw$=Ot`p8!1o&CS7PJB|rQgQJa=e+3MX?}t6bsYJtmycwshd+-P&HHaiu(gs zG~<$aYm;FMu#kouwy-1aTU^F1iA;LMV;v=1Ios7P^H#^5_^t@nL23-OCYNz5`{EIl z2?n?TV;z@atD_Dl-M6}5Y8?V?rLoSZTK{Z)QFwf~0s(7US% z$0rMRDLD+RxF-Qh_brg-{0-q|N&nwQxK;}9M%cQ_SiF?8u9}&%|dxnYfCM6@FbEzyhgb(9`W=`i|IdVfl%S|I&Nq;H~Rv?VFI4MWV$R5c`T z>jC{9O5Y=)_krL>GZ+(-!7Nc||G4)_3=St`t-;Rp$8*UGj--AHmFX=df zYyl8oF+UJ8mq9X|wgj2Vt7(ti9D^<)iVhCzQt_loV^^_xq6rqQCmtEa``e@`*ib|S zncyDjXG+cJpF&jCf*^kJfMsFr{NPDY*X^AZ8O3h#weCyhB(qz}vC| zskkVJ-dcjJ1rR^q5(wFfdA}ox;ly>$qH)H;KP_R(4UQ5)>t6vGKL)A-=wC#9FQt;4X8 z&c7ES&x^zH(BrCwbW>VFo&QIgnhZN>P?_+C(^%&;HaLyAKx_~05TiTJqK6k?YWR>1 zSU1KYSXH?S!MsFF@@iHigzxjQg*6SEK+|^h5iHzWGX8fMAOu5tvV~H3+=- zS5rfnQ-uVE1TQJzmakYdk`AHLP%q8-|M`;eTSAJXV_a< zCW*uhJRLD7u_)vt#&(+|Gu*%js^|v3Z1&SGKDE&kO28F&a;1{R0$u)05+bR( zzSXfuyd}`vL$)N$t2k7Yc`}l5B9fxhBFP9ulHg-%oS5~x(`FqcW%aHX9dcpv{1lc5 ze8=?WV&$!@fwUTPozIX&V;C2^yX6R=as4*?CWHPyPPY?b61omyQm15u)d&|AT5WW> zOLo?hbc;1NGvAbH&BVf`EqA%4&~EcyD2eui^-HEqwrF29B;6=L*}mpTGq3rG@o$94 zf7bi!e+O+mzYGne3!Z-ii2zpGE@!Vaen?<3`2c$koXRAM`_;S-v8t9BeU8M z+tygPpzr5OIv+1e;g5jF!Z^OK*O6$@c-Op7&4wE}18wgQsecz$Y&TVG{_?2qs{p}kg6zn@Kn>ZE z@2&qRss4aJef{sJ`UApOe>`~Wk5q5{5zbzjX35NE>o48DABz<_ap6(9@)tP9fNQXJ zBL^<2pmQ(mdNev1!mdZ7gCTVCql*rYuz0>0ICd*$-C-Ze|iSE(!e`nThN0%gh9&)2awN=QULXqSs*vNGAgYRKycY#>W#& zTPU7b+A#6N(te{SmUf#wv9u`UiKXQ+Pb{s*dSdm=r!LYHOZL|jOZxALB`e^GC1dW1 zrC!hzOWltrmih=!ED__0CAvMa6=e!ZyFhPxhU?$1zgHf&4Xyl;K2)hjHxV^=>mJuB z*IUP)#it~uDV+;7&asOY-cGBU0mQTUWxDI{_&i(2Tx2&U9G{SI_w)phJEGrVb8v75+lN<^VlZf8cL-}_(B{SOmj485);L1lMGvA~68cgL_m-?F<0Q^0lsu+7ad32&ogp)b!( z)0C}=XT^Jy{vsu-ft5zq%u^m-`C=D{V{Dpd%TEEj}` ziyQ6{!;U0MQVGuc?hq^HmOViRp33~O_aDZ`rxwut z`_^;I+UN)d_>+unGw?@+uKmBwog?7R59JAUgN!X=b$4N4+^xFed_^zG9|N~us$a|Z z){l*_BH48z=$+93Y3QN3Us*J5UL*Ncb|m1*3BpiK={b%G31p_4hw(802UXWv#sufL<{z-Ura`xgt~U^p9bIoy|fQV_x`_aTvMM&ie;l4>y{u|qrxl~BjXxL4sw;#o15^YknHQ!EL%aI{XBh661y zv-p+((()8IRRJ+#M(T=W^{-brS)g?jC?XEw=3j)Q1Jc5u$=sNfxEmY?c39<&`(N@_ zz8}7d=O}5X${X4f4g0C`>{t&Dw@Q@{vuRQJ=UKm~Y@GzuqOdWStb+K(AukR%^re(& zQUJ`h#gr}_A!0?+{UidNNH?~y1HiPY{5(>c3~iK-39Sg$m^VgoGV26 zIAcSq1`HDvxFr1m5)#dw_^<)KO^*-pdP!;aLgMIOn8)0_iPRFT6Y0V3Hqqw=KZ3(W ziRLytMj0)Mh9lVgHMTj80%CC-&PCs_Zozf(7{s(rg9_m=fd{nUvN!r_1IL7*mJ8fW zh&PeP+Jh)*s+6N-4^BEIyuo;WZ$H*7pz6Cc0V5{TZ|JREC8W`kH?~V?b({`c2v8jw ztOVu&eBLQOz5@!j6|E?H6BT80G_+&1z^*sB*3g;ox(T0m$fj1zIV}R13<-hPH2*>o zn$6o_0`N$&tYt+@Ez7Qv1k(DIn53YS6q+!5iX$l=<^TuuVYfh?22)N_Op3&65`8qn z7UiELOoefgQtYZ$A|)W%fXcFDR6U8Z+c9&}rQ+ll)%;3{6?6~`S0HuL7o{Zv#CCyN z$i-6y7?t7n$?7XnL-PY_vx~9q^t9E@6inw4uKEguvrivOzO{ z&BYjs`p67LCbTpU`$U4ff0K%si)YVk&?9{PIopZx9>?7(jq^nL%?p-a+wjxNkB%Eq z6MMANbUN1SG`C# zqZDT+KJ8AJ)CGw2t@;u4g?Z_Nih(}pveR&o(TeXT;9H$7KLy30x2$ajG}(8z512I9 zm9&+{UX?q^2!fo-S_B+8|6ef{u<5QLYrv-ro`X=qy5_^U@b^7eI|lw&;AAe69G!Eu zm0v<`85w@5+KNMn;st zJo6m{!go?0q)7R?C=wrLCn#l2g^G}&jS_hxgegxs*Yg6gM2KZjaSLx>1k;L(b3QpC zjy(8I)XfKWiWRBDT!^3+39m|E0pkVm*U_n5In1T0+z6(-qAC}|EI7XL*v6!pQa$)4 zZ0>b5Ytpm_q5rv_u{0C_qq$p&oN!erWkV7hAo3WYdd z0jknO^ggkH?zkCe`Uxa>>K`&b9R@H6cBF5UZi- zUx_;5_{hIG@r(v!>~eJKtg2eHGcK{cIJSE5MD6z1O#E&jN6$ub1~Qu7nsw8vhC1rQ zr@Ncb7#1}uBgpL6D2ph>Cbsf)koXOg>)KVVP6jK!8M$kQ|7=Z?$0XQee|hM@{-_Mh z#^+`@E1U6#xAUW7lQ~bb}5aoD6{GsCkg!kX2}CXnn0% z@bkQvM4tmH9tLIJ4?UHQs1D3%Of}QKGYQ+-I4!2qW4k)t^8#@|%)rNxpgk{8F5Iz1@E*Iy^S9P&Aa)J0 zp~5wlEx6E~c)<6=Gzb+H0slIW>4?k@R4Jn7l>kt1=4+4V)qDmg6=?GEhh=zyAVkKA zWbvhd&JYQjOQbwR^>;K3rh85ld!R3i7AEYyUHIgfsD5JmQwOxaP0z7rM@kF1t68(# zhWe0|`xSoEg&nAQ>%^7ALLuR9Dsg(Dv5+e{XN6@lqMBH`KO~jI=)q|yvU_|h}lzookhb!3tn^VraeJV>=7uEC$@^s z?b=wlo%tQi?_@qr*|3(#U})kX+HBD0Xv2np8U%#Tn1mNWq{$Knx7r<3Fo^4{nWKoo zw_)8EV%vaCUGWvZE=PSF43G#EF#&JO4MQXbf{CdbD~20gxDOR$^YEM16EMeBA;PV6 z|21fCM--rZj9)hdk1raUPe1+<2XOL?8lx(_od6lb1>gW?dZj&OOg)Ul<1^vIsmjL5w55@$!+ z9Rtd~HQt7|OO_rz*Gn;WImR}e=RSeaZGZ>UW#F4BfaAb1Y%&xhS`!%$kv22W$d5Wo zKzYhMJ{6=&l|+nE6R&X6OKV? z#3leu#bgTylW7-;5e+8GtHexyqNFx1y6(pzdkCQDogoj&1?g`Nm`>j>()GuDo-LSdCYk{xlPA@X z>PPeEi=>&I9UrP9Su6$xOwR+S&wDant^J(Sxf+w&zT|YpQR`e0=s4+a>_%nTDT=dx zzR=H{1HPl3!N(oz4o`6m9{V)VBJ9(^mn9|sf<8VT(iUc8-y8Ds^&H-GIzsC|bLI8F zrU?P6E9hCK7{5%wSJoC4&_9j4Xawj>b*h?4ww^r8da2_5Y%Y-G`bNW&UpVBSnIIz3?ABaVm!Hyg^~Fu!da&p~6c11$}74p12m zl;NF1*$3odoy0HQzLe+iauo(GTR#OA$YD48plA3Gy=Z+!q9eX1I$(^)j&B3hT+ab#KEujBhN!bfEaeQdmf^~ zCCr%mKH*>&Xe#W#an%sc|=PN%TY^O5_E$(4^V@B|dpxkf+X&H4{^R zv>aC>*o(372ZEo{Fn!Trf5xXTFfh1e6U1T$q~YljS$t3tJ|e08$l-4g&I_mSGMR)t zjPxB1!y)oSDnPy^HlyM8AQ4ysGoo4im^$#p_~U~e34Qxo10dsj>hX;hy#ILE2J zrN*$+lzUHwH_Sj=Q_BwBiJnAym)zUB|2g|Y_xF%1+wFvK)O;XeF zBco=V+NNG8<<;((7B%t%?T(BHN8Rx7#)RY8FU5ZH#V&Q?2iCr>VSgTB zSu#t;gIG_be?)w#$YLo+&>N&SO=Q8dquy*aj#ib$%*yWGAnTQ?R9o7Q8&rB3;0Kf@lwQwG;wF^FaidGG zEWLuuD$U{Yx!lrKTuJH5QY&ZT%%zJ;Z{uz$oyAS%CYQce+Q{wU-Y7lD{iF16rSEd> z+}EX^(r-C;=@;A)?xWI;+*750EPa-Hx%5?T8~3Nu2TLF39_4;hTE*3r-dkGEIm*^5 z?o&9+HYgq|`*qo`6k8Q9l>N2rCB^T{{-AhLv8k*@@k!a2WuGa&D?6p=Q2e{>y|M#} zLy8Z|b}ROlHI?mBBr6PMv&(K)++Ma&F-MVFR$R71u|~1Fj8`l#TUxe6F|2HaVwB>l zvhj+fvR{;q#j%^{vY};{D*BgQpin9lW#Nv9a7}n$M@;w-$Hk5d!{fuRbKK~d7(UW5 zCVY7K<&MSS7Du5YFZ?dY-Ql+II~?hbjPUv4w>oBo8y(XfQ^M=QUw6FYcr*M%$3MgO zhwpP7a~u!v3jf9-gn#b%)Nwd`v*YRT=fa_?XEh{ew%MQyd z*N4THUsHZ{SYr7NVdKKCEx)LIVA$ZWOUl(@k>!2L!$^g=9YgAs1&QT-KNyh`d1!_5 zqGiiozxdC_wU??)(=V(2Ts=bD{>Bvt4pd*0`PMBXvmeo1_uksQ*Y?|Z|D;!5nLWPf z!vQm!8k=Wa*7Bg?nUiUgpL;%f(V(sSZ{PLsuQTkfjI`a?Uy^sv$b$t>M?R7@KzG~H zsN^B5H!nC;a_3!-7wyeiXlvO%Y{T5&AAa=ynoSQ(Shx70Np)AcT>m)w%e3#FD*xnY z_S$*3k9_sZX`j7x{o-eKl$G#*sTh6D^9k`^JoDMW=bv2AKY!!8S#+%VRL0hS z+;qzwAIuzdebaXlCtkZkzwPaZlI(BBl}`NX@yEV9KJDe&@BaPtYEQ@U8$L>Ldp`N? z-krke=90FRMQg{^EuS8L$t}vHKV-Z<;r9{cR}LPOGVJvvZj4o5fAu?$-GAY#(%Azy zJ*^ou@a4TjFLGBao;-ews-#)1esk{|nm)h(^{TDgGuGs;i@xKZHT&%!{^P-A`E_Z9 zFB~1PB<#E9oWD+ce4+aGg}3c188SC#@uBpr_(zQUN8ff+WdBR1zBKS)@~<18dQSgg z_Lgr>Uis9lmZO_)*}CbkUC%FmIo367`>!58{O4;%K3~1z`uzJR>#lJekNo1^V+)@7 z&D3Q6vBz>ouDklK&(?oBWLt}gvmbGexW4s<#1o$T3Vzu79lF+_?DqNA4&&@%Wb8xBY79H2cGs4jfzceE(zj z*+y?IdoBKs%HOQ3UNENSmRnE!UOgvy`x_?1(fen8_1)~7NB%=I{n5I;DVLX&-}Ug~ zlzV1RQ`PZ5iUwl~q)~1hF99aD6AKQn07W4VxFNVDG zRQt0Vvpb5{UinSKBS%m6Jz48~wPkh9(#DCU%^!YO@xE*4ZZ9D&dm%TQ8!Syd}OFr=@YX70XAI1UGEKZj^o6~J2 zXX146spw17INio{xlhG?Zh80y#WLLE2G^d?>2%A*G*c1at++QGt_a_(NM407z8w&M zCtiXCl5}nkXXY}vx!gRiud1J_ze=NuR9&Fbs&uLVDsk-wUH(FGITPn+XU|`eWh*Sq zFP@)ewUJwyFm`@+etxkH-^8G2gvVq7_+RMz;c$PKP7gN^t^$q@I?^Bg`nz;nX4W!& zrnS&skgd1z7X6A$+(x$~-=e>gyHdY0vsj;1XtmmlY?kaXlz&bj2w-xH^-C)=i4p%!W_LJ-NcG0D75K|tc67uYyN6|apnr( z$;`4A78mO?^*Q!}EL(12fj%n}HPq|p@F)=f?ZtLPtOf)=e{0XT5k3MTO8jzVTe9*q ztrB7S!UBsID66nwg{6R@CZhc_6&2JDF^&V#faxrJR>bLf0TZ8pJzGb3b z-!HOn7N2R=XX%slak=^t`uJ7Trd^fTFERx;`7b3NGQ5S^7SJNowHngR&)1W*GQo%} zR1?MB0XgK_R#Q#rQIrN@C@jKdh9r8ZP_{+lkEn0UEz1k7KG9=~>!ZUI#Z+*s5l8#Lx479XB0h6WbcawfAU9I}?|j3rYxHnMai zU4Y(Ltkm+CtQPgn+wjpQy!7DZ9FFTZ6)k3>ei92$W-?~YxWK-A37Q)<1X*ciBopZ z6W;F4huZ+R748t6NVjaTBtF4hQW+_dC6etBGeKm2sCD{VAHa%uBw6^OnHks`16rbN z+%t*$UK~M<&nr5jh~wh8Mfg{U_F~~yL8onKH5RmlT*T$3NdT&Pyan-B>kW=0->*Bbr4$|3vxN!L~cH)$%elI_JP`dA=*GOp0m&{ zY~J6Mtc?&XwGrI(inf!3fAi6{DVL8UV~~^nzQ5nkw4p~^m1|=?Kg*+lPm{iIIJ||& zI{PJzyYpdr(lNiE%1z;}MQe>md!5SoOkk9j0sE9DrA_r~&9u&dTbAgr6mMAJr~MxK zr7uKH296B)ChjK52KaEMS(eT&vJ~86S#4M~7rF49YPVQdXJn%P7o#XH1*&KP5a9J+LMs*#Jn+j1SjiI_NU20i@9KYFEdYnyMZ z_9M}RvV}PCv*SQo`qcE_L_WK9)XUmg4=#Hqe?r;P^JDkK1`MLB_alLsOely|Z_?>=m5sz`Y zOW^$T`KQ++?X95nt$z9KPviRzaQ^xC_@z^P?G`be!lA#j*1b-tAl$V&JFi1Tx2K54S^d4mkc)tE)T90?m@Wc;OgM0 zCSh20P;x3Rg6o5eOZ#Hot3Ri~`sf9m7D_UJyO6sGXAq(}zfn1MEx0N9wrRQ6~&#ie6QVoqT&>>P`Ad2Rs~00==k))TP`#;7&2)os66xpWDLzSJTYAw^;N&B_S$ zJ{K1GA?T?n`reI~QnM{LKgF7L?e!^{`C0aSi6|*HVQjZpS^}PG$zN$)NCh%OtWHwQJK8umps!*;Wlb8q5kS7^t7wc7fga9&RUToOJF+dacLLQ2cN=vaLO6SiF?Vt#qTUOlAsX>Q4>gI zPd#sM(Mh^WCj)MPi-(f-79H6bmDu2V;tjQ(-lFSV;ZGbJqtY1m`?QJ9X^7`K;+Sl+ zzeoFex19vG_c>_4I4BIddux1pI=ghP=)t+#PUv*a%J0#%-fYJ`(Ki}a?_A|)V7usU z6?@Abo~v}7EA8`4{rPbELyEiLMd-5&F_$dFxgvTu zx2K-FH|Xk(ezGV2^k#q16Fnim(78bW^)FG6d*aLaVh4Wu_C@3Jtblpuk2wQF?{U*5 zAmr-rTB)f;?`{+Mc`7YBh z|9;=R_FS!pY!8Fyz2_>u=c+!Z<6u>9+Pm~?d%66vyeTIq*~KmmS(oyBhKs z`b>Qnar(bakKFgmYs~-c@MO;8D6YtYriZ({AZ8Uo~!T;@E{}vetOD&f0iSKL7#ED+IM_fGROJ{ylxLfcv zhn@MoiMtKqMK~8c8SvuU388i!JP$u#_3Wq6k#pJZvrZ}Gg*Xu6K(BD%zcU`R;T#fu zor2zV6n9W)SJQt5vgg&t|5m@&oxV8s#^OA-$~ys zH9CDSa6Z)MY;5lNP>(FE;Za+oGs^Tmubz62^PwKgVH1{~re`@H=%;oe-Urxw_XGbY z>~Rs+k4T@)%tjmhYBdvUJAJH@VA3r%_Srrf3eYp%VK0Z-P4rSc zF3HE2bB(wSKXa)Cna>Bkr+NhH4C%=doMobKz-HlA0vZR$%$M8lxyWZ(tIxC_B-4Ms z{LVU>{PLeJ`Rh~#2o84mxc@ z3%4F_3*0uiI=DS>hv3@aPQgV~D!Bbs3a$a}&u~w}{TA+SxNNvtaO2@FgHywOb+3YJ zfoq0)748pkzk@4>D}eh9F#oB)PSDl@r(dh!Cc>q`Wy4wFO5yH<+X(j@+)Hq~;P$}% z6Rr*JTR0_Xii4X1Hw$hd+yUU+1GfuqJKS?{Prz+}yC1Fs?oPNOI38{>Tn5}sxG8Ys z;o{+j!}UWM|H;3oav#2dz|WN`xK6*g&k%P6aXtNobmHf!6Zdc_`Gv)n8JPvy_{wy< z5_@Qd74*$&?)R#>nYp%Uh1PU@KRMqrdr6)p%f|hMOR-w;>1GBrnd1cMc@B?g?6&<* zG0keR%*(B55jk1JYJ878 zB4?=uCA}1pQ&?mv;C4pj6yYP)wj3@U`Bvr@fRB8iVkxemPqEFKm5FjzQz#!9I6I3g zE-K)TMC2A{=Pt#U!@osnl^EoH*T+_9FDkO&Hgg6^<(E;&n#EhWkwjCm1sIL#87XPk zj*XxCzwKRVbW~NgzJxFY1OwOzN<)Z1fF|Ug;ZA3|gd`9!V1OXQ11d#Q8JbL1Rbt|k zm}XLFR0g$ap9sjHHiIbGFZfVsCP4#+!3aD+wCM;m4T#VW8GBa|hF+`RdjH=_twrkI zbEkd2{q67DA+_p+CpD&tCuwAcXJkf_XUOm&8Dn52_XtmxKj_K!O)kyN_4z${#l=%J zZC?{lXn1r{UYIr6Ddkzdk{~R>t?j*Y}!O+`vmPw0=qaGOGH(EV+X zPy4c|2k@Vwxy?MKMd7W9;p>wp=Z^H16#IiPr+w_We=S4?2`w%P2NH<%G;FO4 z%>TeX-E#*BKlNk*(*TEEEuPH^K-N9L z(&NCCLhlq`i8q)R+P@9^fs%s!V8cLe(;% zl2Vjaob5|54o9Nh(vp1-?hl9@>CF%L?k7b{4>38-8wjTO{l)O*f$u(8X({D^$^NF{ zavWF@gf-;HhqQ)hAWGNw?k^9Y43#Y*6_&C8Jf1d+0*&{V5zqt9IU2gh0YjOoLySr3q5H#q!Q+x zdw=wf0>W71s}X@l1nwiyMC$}Q@UQCrSZJ$>76CuuYx$}|d-q>Ww5Ui8KH~$QSDD{L zoBFTY$@`}_ZfU|TFD!^DgPsOFRK~$FSaC7Fkj6vfCF4eC42rkoVgf-hTW`pyWL$+W z5a+5ktwg8;DV$tT5d$|U3dp!pe-R60<@pM|f%wAwEPrvJI42knro_B~!v19_E(QcU zKgSmcKK3HK%kmy3kExWP^%{~8f!MBtwhh}N`d(Cso@h3Dbd@qgfr zcoW`&PvEn-BZ(z(B%Vm}B=L|18BL~RX^<|U%jhb) zhHj?2=-0Hp(cS1}^fQDp$VfM48Lt~}8cU7!#unpqbFX>UeB6q$dRf9svBq2LtPR#C z>nrQ9b;go*iv6~|+pAqJ;QEU$D&Eq-YKk%RU zqoRw56+=a)C=(k*wfIaN72k@#h&HmHMADLDN5$b6*L6xcL>Ql8- zb#@cnweCl5wR^}t;(qIDG4R_PAkL%>YS@iQ`U<0XK8FKYsWkCF8nuq7N5)C<7@c_j)W}>l5d$zsC{=%-c&)Dbf%l2bV zj1%t+cFLVso%POUXSdS_=1AEDmcy2^m25S;&NSYfPvNif#e5at%6Iawcz;2JBbJFz z#WqnRI?G;ifJ~83$&ciJ%1`AHc|x9&nrfz6t2ot9VFi;LplKK3?Gt^AzEl5E?}A37 zji?%JMqAMiv>Vl+T672gEAPPv@D%UiQ168fIsy*s}I;?(B z=hP(?;kI!faeKRi+|llMH`6U~E8PX|Hg~^U=N@y zy;eV|pVeEVZfH0fg~p+LRF0;i>FD=pIqHuIcJOFC9%teb`~seV=iqgCGroa4kv?P} zIY^F>E94qr5KX($9<(nVPAAaU=my|HoDpxNLjL1n_KD_TbBwvzY-4q_`dQerE!Qfx z-m;ck>#b_*ORLWMtJTTwVe590J=LCT&$oYPzY9D_aAr9hovlud)0V|D%q%9^I5vr8 zvml$!UShAXP~}NSnPh_ZVh3Vsc9?VZK&=6 z+)n8i^&7eZ#Jng66`+^U%V;55fwrLAs1G*qP`nh^;KTS7?h1Oglx!resSUc7P9s@Y z)|+uQfDK}KtdLd0JZ7_x*?v~Xj|3}UsU&|QyGRW=OD+Hl`cX>9(;Vuj<@6Q$W>}kR>0x@EwlvxrU5p+EHf-Y= z<5^>d@s;t7(ZYP#OfXGTnb~F$XhRk7W2L##++ywml{jvmHZPbrfHD1mEy-4@m1d<| z8P-^9f|Y4yTV+<2^`f=NS^{eRv9-;ru?|`%t+Un@>xLC&cd#F~$JznA+^)2nJ8hkw zPLeatS>seYXPh6Mfow1v#&TI1o66?01#B5x#n!ORY$w~t{+oTregv(@obyc3(pPv3 z(NmO)rD6>z{SHvki{hGylx<}v87t!@kuIoAh8!n7vRF=))8!obhFmO{%Rk6J0^7F9 zJ@RXLOnxuV$Sd-Oj8IYP7pjZuu6iq~xJp#1Dg)f1O3hKPskha8>LayT?NWQyQPm9U zxwG31IH}wucZfUAo$StZcQ!c31@~5nmsOh95^~;DkJ3Bqu{zdE^%?qX{Z)MlWPO9a zN#6sS{k`53QRE;OJ%uKrEU57cGz~35U!zm#47vcUZHqhL&bSZ8z}=bnHT-+L1b=`( z!`ty5d<1`w|BelkOoouBz?q9kfK-s#;LsnCopxJim^0R?0>AvN^NsUoK)5w)&w8+5 zu|;eN`;b+$pFyoV@W;3UtsBcdynP2ow--n5Sv$0f%yZvA!d$YIXWC znD=S@Jh1(muA%1WA=Dmq0@n9HeUXk#bO|>jCJ|&HNhPDncv40-lWk--=-MH2lzc}{ z1F!#1Zjwmans%U%(%w{EWcF_)v(e! zVbxpbtyp`g?XmOhLVKn?$6f^8&4>0T`)m8SU2k8oyMecQoE)bDvUULK;f52%qFHAa z!zP0w`PsAVc{YtLWcBQ?>?Z5UZ9a|{@uU22ygOuS8sum-xU2>W^rV~!{(4PD0h;rANi~B7aX|yt;jgH1D zW3}OoFsarEYn(L+@F}(`t+{~Dd)C@;?^h3ga}yk=n~m)epcI*QuKm2d09i=KX=+ zv!M6d$oKNTfK(Fr&_q!wri<6byPzFMMF&acTk<`*QEmkvIt(4jMR`+pSK|Szy(-1^ zxVzl1z^&`u^X^aXwGdA0!?vxZ>g|BLi=bv}Q5`ylzDK9gd2|_FLmF<5AHwZ%C)^eH zzAB${TMdFBd;W{q4A5mn30P-8W+ zRvv=LPC{&_p-a3dFUza4H*`#yklQ>}ph}Xg2GiU)Vp>ydx=;znd+R(|3uSNtK K5%_>R_OOBSkK6 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 UvD&9$<(j1T=nM4NH+?SsUy5ATZU6uP