fork
Embed:
(wiki syntax)
Show/hide line numbers
UbloxATCellularInterface.h
00001 /* Copyright (c) 2017 ARM Limited 00002 * 00003 * Licensed under the Apache License, Version 2.0 (the "License"); 00004 * you may not use this file except in compliance with the License. 00005 * You may obtain a copy of the License at 00006 * 00007 * http://www.apache.org/licenses/LICENSE-2.0 00008 * 00009 * Unless required by applicable law or agreed to in writing, software 00010 * distributed under the License is distributed on an "AS IS" BASIS, 00011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00012 * See the License for the specific language governing permissions and 00013 * limitations under the License. 00014 */ 00015 00016 #ifndef _UBLOX_AT_CELLULAR_INTERFACE_ 00017 #define _UBLOX_AT_CELLULAR_INTERFACE_ 00018 00019 #include "UbloxCellularBase.h" 00020 #include "CellularInterface.h" 00021 #include "NetworkStack.h" 00022 00023 /** UbloxATCellularInterface class. 00024 * 00025 * This uses the cellular-tuned IP stack that 00026 * is on-board the cellular modem instead of the 00027 * LWIP stack on the mbed MCU. 00028 * 00029 * There are three advantages to using this mechanism: 00030 * 00031 * 1. Since the modem interface remains in AT mode 00032 * throughout, it is possible to continue using 00033 * any AT commands (e.g. send SMS, use the module's 00034 * file system, etc.) while the connection is up. 00035 * 00036 * 2. The UbloxATCellularInterfaceExt class can 00037 * be used to perform very simple HTTP and FTP 00038 * operations using the modem's on-board clients. 00039 * 00040 * 3. LWIP is not required (and hence RAM is saved). 00041 * 00042 * The disadvantage is that some additional parsing 00043 * (at the AT interface) has to go on in order to exchange 00044 * IP packets, so this is less efficient under heavy loads. 00045 * Also TCP Server and getting/setting of socket options is 00046 * currently not supported. 00047 */ 00048 00049 // Forward declaration 00050 class NetworkStack; 00051 00052 /* 00053 * NOTE: order is important in the inheritance below! PAL takes this class 00054 * and casts it to CellularInterface and so CellularInterface has to be first 00055 * in the last for that to work. 00056 */ 00057 00058 /** UbloxATCellularInterface class. 00059 * 00060 * This class implements the network stack interface into the cellular 00061 * modems on the C030 and C027 boards for 2G/3G/4G modules using 00062 * the IP stack running on the cellular module. 00063 */ 00064 class UbloxATCellularInterface : public CellularInterface, public NetworkStack, virtual public UbloxCellularBase { 00065 00066 public: 00067 /** Constructor. 00068 * 00069 * @param tx the UART TX data pin to which the modem is attached. 00070 * @param rx the UART RX data pin to which the modem is attached. 00071 * @param baud the UART baud rate. 00072 * @param debug_on true to switch AT interface debug on, otherwise false. 00073 */ 00074 UbloxATCellularInterface(PinName tx = MDMTXD, 00075 PinName rx = MDMRXD, 00076 int baud = MBED_CONF_UBLOX_CELL_BAUD_RATE, 00077 bool debug_on = false, 00078 osPriority priority = osPriorityNormal); 00079 00080 /* Destructor. 00081 */ 00082 virtual ~UbloxATCellularInterface(); 00083 00084 /** The amount of extra space needed in terms of AT interface 00085 * characters to get a chunk of user data (i.e. one UDP packet 00086 * or a portion of a TCP packet) across the AT interface. 00087 */ 00088 #define AT_PACKET_OVERHEAD 77 00089 00090 /** The profile to use (on board the modem). 00091 */ 00092 #define PROFILE "0" 00093 00094 /** Translates a host name to an IP address with specific IP version. 00095 * 00096 * The host name may be either a domain name or an IP address. If the 00097 * host name is an IP address, no network transactions will be performed. 00098 * 00099 * If no stack-specific DNS resolution is provided, the host name 00100 * will be resolved using a UDP socket on the stack. 00101 * 00102 * @param host Host name to resolve. 00103 * @param address Destination for the host SocketAddress. 00104 * @param version IP version of address to resolve, NSAPI_UNSPEC indicates 00105 * version is chosen by the stack (defaults to NSAPI_UNSPEC). 00106 * @return 0 on success, negative error code on failure. 00107 */ 00108 virtual nsapi_error_t gethostbyname(const char *host, 00109 SocketAddress *address, 00110 nsapi_version_t version = NSAPI_UNSPEC); 00111 00112 /** Set the authentication scheme. 00113 * 00114 * @param auth The authentication scheme, chose from 00115 * NSAPI_SECURITY_NONE, NSAPI_SECURITY_PAP, 00116 * NSAPI_SECURITY_CHAP or NSAPI_SECURITY_UNKNOWN; 00117 * use NSAPI_SECURITY_UNKNOWN to try all of none, 00118 * PAP and CHAP. 00119 */ 00120 virtual void set_authentication(nsapi_security_t auth); 00121 00122 /** Set the cellular network credentials. 00123 * 00124 * Please check documentation of connect() for default behaviour of APN settings. 00125 * 00126 * @param apn Access point name. 00127 * @param uname Optionally, user name. 00128 * @param pwd Optionally, password. 00129 */ 00130 virtual void set_credentials(const char *apn, const char *uname = 0, 00131 const char *pwd = 0); 00132 00133 /** Set the PIN code for the SIM card. 00134 * 00135 * @param sim_pin PIN for the SIM card. 00136 */ 00137 virtual void set_sim_pin(const char *sim_pin); 00138 00139 /** Connect to the cellular network and start the interface. 00140 * 00141 * Attempts to connect to a cellular network. Note: if init() has 00142 * not been called beforehand, connect() will call it first. 00143 * 00144 * @param sim_pin PIN for the SIM card. 00145 * @param apn Optionally, access point name. 00146 * @param uname Optionally, user name. 00147 * @param pwd Optionally, password. 00148 * @return NSAPI_ERROR_OK on success, or negative error code on failure. 00149 */ 00150 virtual nsapi_error_t connect(const char *sim_pin, const char *apn = 0, 00151 const char *uname = 0, const char *pwd = 0); 00152 00153 /** Attempt to connect to the cellular network. 00154 * 00155 * Brings up the network interface. Connects to the cellular radio 00156 * network and then brings up IP stack on the cellular modem to be used 00157 * indirectly via AT commands, rather than LWIP. Note: if init() has 00158 * not been called beforehand, connect() will call it first. 00159 * NOTE: even a failed attempt to connect will cause the modem to remain 00160 * powered up. To power it down, call deinit(). 00161 * 00162 * For APN setup, default behaviour is to use 'internet' as APN string 00163 * and assuming no authentication is required, i.e., user name and password 00164 * are not set. Optionally, a database lookup can be requested by turning 00165 * on the APN database lookup feature. The APN database is by no means 00166 * exhaustive (feel free to submit a pull request with additional values). 00167 * It contains a short list of some public APNs with publicly available 00168 * user names and passwords (if required) in some particular countries only. 00169 * Lookup is done using IMSI (International mobile subscriber identifier). 00170 * 00171 * The preferred method is to setup APN using 'set_credentials()' API. 00172 * 00173 * If you find that the AT interface returns "CONNECT" but shortly afterwards 00174 * drops the connection then 99% of the time this will be because the APN 00175 * is incorrect. 00176 * 00177 * @return 0 on success, negative error code on failure. 00178 */ 00179 virtual nsapi_error_t connect(); 00180 00181 /** Attempt to disconnect from the network. 00182 * 00183 * Brings down the network interface. 00184 * Does not bring down the Radio network. 00185 * 00186 * @return 0 on success, negative error code on failure. 00187 */ 00188 virtual nsapi_error_t disconnect(); 00189 00190 /** Adds or removes a SIM facility lock. 00191 * 00192 * Can be used to enable or disable SIM PIN check at device startup. 00193 * 00194 * @param set Can be set to true if the SIM PIN check is supposed 00195 * to be enabled and vice versa. 00196 * @param immediate If true, change the SIM PIN now, else set a flag 00197 * and make the change only when connect() is called. 00198 * If this is true and init() has not been called previously, 00199 * it will be called first. 00200 * @param sim_pin The current SIM PIN, must be a const. If this is not 00201 * provided, the SIM PIN must have previously been set by a 00202 * call to set_sim_pin(). 00203 * @return 0 on success, negative error code on failure. 00204 */ 00205 nsapi_error_t set_sim_pin_check(bool set, bool immediate = false, 00206 const char *sim_pin = NULL); 00207 00208 /** Change the PIN for the SIM card. 00209 * 00210 * Provide the new PIN for your SIM card with this API. It is ONLY possible to 00211 * change the SIM PIN when SIM PIN checking is ENABLED. 00212 * 00213 * @param new_pin New PIN to be used in string format, must be a const. 00214 * @param immediate If true, change the SIM PIN now, else set a flag 00215 * and make the change only when connect() is called. 00216 * If this is true and init() has not been called previously, 00217 * it will be called first. 00218 * @param old_pin Old PIN, must be a const. If this is not provided, the SIM PIN 00219 * must have previously been set by a call to set_sim_pin(). 00220 * @return 0 on success, negative error code on failure. 00221 */ 00222 nsapi_error_t set_new_sim_pin(const char *new_pin, bool immediate = false, 00223 const char *old_pin = NULL); 00224 00225 /** Check if the connection is currently established or not. 00226 * 00227 * @return True if connected to a data network, otherwise false. 00228 */ 00229 virtual bool is_connected(); 00230 00231 /** Get the local IP address 00232 * 00233 * @return Null-terminated representation of the local IP address 00234 * or null if no IP address has been received. 00235 */ 00236 virtual const char *get_ip_address(); 00237 00238 /** Get the local network mask. 00239 * 00240 * @return Null-terminated representation of the local network mask 00241 * or null if no network mask has been received. 00242 */ 00243 virtual const char *get_netmask(); 00244 00245 /** Get the local gateways. 00246 * 00247 * @return Null-terminated representation of the local gateway 00248 * or null if no network mask has been received. 00249 */ 00250 virtual const char *get_gateway(); 00251 00252 /** Call back in case connection is lost. 00253 * 00254 * @param cb The function to call. 00255 */ 00256 void connection_status_cb(Callback<void(nsapi_error_t)> cb); 00257 00258 /** Set the plmn. PLMN controls to what network device registers. 00259 * 00260 * @param plmn user to force what network to register. 00261 */ 00262 virtual void set_plmn(const char *plmn); 00263 00264 protected: 00265 00266 /** Socket "unused" value. 00267 */ 00268 #define SOCKET_UNUSED -1 00269 00270 /** Socket timeout value in milliseconds. 00271 * Note: the sockets layer above will retry the 00272 * call to the functions here when they return NSAPI_ERROR_WOULD_BLOCK 00273 * and the user has set a larger timeout or full blocking. 00274 */ 00275 #define SOCKET_TIMEOUT 1000 00276 00277 /** The maximum number of bytes in a packet that can be written 00278 * to the AT interface in one go. 00279 */ 00280 #define MAX_WRITE_SIZE 1024 00281 00282 /** The maximum number of bytes in a packet that can be read from 00283 * from the AT interface in one go. 00284 */ 00285 #define MAX_READ_SIZE 1024 00286 00287 /** Management structure for sockets. 00288 */ 00289 typedef struct { 00290 int modem_handle; //!< The modem's handle for the socket. 00291 volatile nsapi_size_t pending; //!< The number of received bytes pending. 00292 void (*callback)(void *); //!< A callback for events. 00293 void *data; //!< A data pointer that must be passed to the callback. 00294 } SockCtrl; 00295 00296 /** Sockets storage. 00297 */ 00298 SockCtrl _sockets[7]; 00299 00300 /** Storage for a single IP address. 00301 */ 00302 char *_ip; 00303 00304 /** The APN to use. 00305 */ 00306 const char *_apn; 00307 00308 /** The user name to use. 00309 */ 00310 const char *_uname; 00311 00312 /** The password to use. 00313 */ 00314 const char *_pwd; 00315 00316 /** The type of authentication to use. 00317 */ 00318 nsapi_security_t _auth; 00319 00320 /** Get the next set of credentials from the database. 00321 */ 00322 virtual void get_next_credentials(const char ** config); 00323 00324 /** Activate one of the on-board modem's connection profiles. 00325 * 00326 * @param apn The APN to use. 00327 * @param username The user name to use. 00328 * @param password The password to use. 00329 * @param auth The authentication method to use 00330 * (NSAPI_SECURITY_NONE, NSAPI_SECURITY_PAP, 00331 * NSAPI_SECURITY_CHAP or NSAPI_SECURITY_UNKNOWN). 00332 * @return True if successful, otherwise false. 00333 */ 00334 virtual bool activate_profile(const char* apn, const char* username, 00335 const char* password, nsapi_security_t auth); 00336 00337 /** Activate a profile using the existing external connection. 00338 * 00339 * @return true if successful, otherwise false. 00340 */ 00341 virtual bool activate_profile_reuse_external(void); 00342 00343 /** Activate a profile based on connection ID. 00344 * 00345 * @param cid The connection ID. 00346 * @param apn The APN to use. 00347 * @param username The user name to use. 00348 * @param password The password to use. 00349 * @param auth The authentication method to use. 00350 * @return True if successful, otherwise false. 00351 */ 00352 virtual bool activate_profile_by_cid(int cid, const char* apn, const char* username, 00353 const char* password, nsapi_security_t auth); 00354 00355 /** Connect the on board IP stack of the modem. 00356 * 00357 * @return True if successful, otherwise false. 00358 */ 00359 virtual bool connect_modem_stack(); 00360 00361 /** Disconnect the on board IP stack of the modem. 00362 * 00363 * @return True if successful, otherwise false. 00364 */ 00365 virtual bool disconnect_modem_stack(); 00366 00367 /** Provide access to the NetworkStack object 00368 * 00369 * @return The underlying NetworkStack object. 00370 */ 00371 virtual NetworkStack *get_stack(); 00372 00373 protected: 00374 00375 /** Open a socket. 00376 * 00377 * Creates a network socket and stores it in the specified handle. 00378 * The handle must be passed to following calls on the socket. 00379 * 00380 * @param handle Destination for the handle to a newly created socket. 00381 * @param proto Protocol of socket to open, NSAPI_TCP or NSAPI_UDP. 00382 * @return 0 on success, negative error code on failure. 00383 */ 00384 virtual nsapi_error_t socket_open(nsapi_socket_t *handle, 00385 nsapi_protocol_t proto); 00386 00387 /** Close a socket. 00388 * 00389 * Closes any open connection and deallocates any memory associated 00390 * with the socket. 00391 * 00392 * @param handle Socket handle. 00393 * @return 0 on success, negative error code on failure. 00394 */ 00395 virtual nsapi_error_t socket_close(nsapi_socket_t handle); 00396 00397 /** Bind a specific port to a socket. 00398 * 00399 * Binding a socket specifies port on which to receive 00400 * data. The IP address is ignored. Note that binding 00401 * a socket involves closing it and reopening and so the 00402 * bind operation should be carried out before any others. 00403 * 00404 * @param handle Socket handle. 00405 * @param address Local address to bind (of which only the port is used). 00406 * @return 0 on success, negative error code on failure. 00407 */ 00408 virtual nsapi_error_t socket_bind(nsapi_socket_t handle, 00409 const SocketAddress &address); 00410 00411 /** Connects TCP socket to a remote host. 00412 * 00413 * Initiates a connection to a remote server specified by the 00414 * indicated address. 00415 * 00416 * @param handle Socket handle. 00417 * @param address The SocketAddress of the remote host. 00418 * @return 0 on success, negative error code on failure. 00419 */ 00420 virtual nsapi_error_t socket_connect(nsapi_socket_t handle, 00421 const SocketAddress &address); 00422 00423 /** Send data over a TCP socket. 00424 * 00425 * The socket must be connected to a remote host. Returns the number of 00426 * bytes sent from the buffer. This class sets no upper buffer limit on 00427 * buffer size and the maximum packet size is not connected with the 00428 * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size 00429 * definitions. 00430 * 00431 * @param handle Socket handle. 00432 * @param data Buffer of data to send to the host. 00433 * @param size Size of the buffer in bytes. 00434 * @return Number of sent bytes on success, negative error 00435 * code on failure. 00436 */ 00437 virtual nsapi_size_or_error_t socket_send(nsapi_socket_t handle, 00438 const void *data, nsapi_size_t size); 00439 00440 /** Send a packet over a UDP socket. 00441 * 00442 * Sends data to the specified address. Returns the number of bytes 00443 * sent from the buffer. 00444 * 00445 * PACKET SIZES: the maximum packet size that can be sent in a single 00446 * UDP packet is limited by the configuration value 00447 * platform.buffered-serial-txbuf-size (defaults to 256). 00448 * The maximum UDP packet size is: 00449 * 00450 * platform.buffered-serial-txbuf-size - AT_PACKET_OVERHEAD 00451 * 00452 * ...with a limit of 1024 bytes (at the AT interface). So, to allow sending 00453 * of a 1024 byte UDP packet, edit your mbed_app.json to add a target override 00454 * setting platform.buffered-serial-txbuf-size to 1101. However, for 00455 * UDP packets, 508 bytes is considered a more realistic size, taking into 00456 * account fragmentation sizes over the public internet, which leads to a 00457 * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size 00458 * setting of 585. 00459 * 00460 * If size is larger than this limit, the data will be split across separate 00461 * UDP packets. 00462 * 00463 * @param handle Socket handle. 00464 * @param address The SocketAddress of the remote host. 00465 * @param data Buffer of data to send to the host. 00466 * @param size Size of the buffer in bytes. 00467 * @return Number of sent bytes on success, negative error 00468 * code on failure. 00469 */ 00470 virtual nsapi_size_or_error_t socket_sendto(nsapi_socket_t handle, 00471 const SocketAddress &address, 00472 const void *data, 00473 nsapi_size_t size); 00474 00475 /** Receive data over a TCP socket. 00476 * 00477 * The socket must be connected to a remote host. Returns the number of 00478 * bytes received into the buffer. This class sets no upper limit on the 00479 * buffer size and the maximum packet size is not connected with the 00480 * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size 00481 * definitions. 00482 * 00483 * @param handle Socket handle. 00484 * @param data Destination buffer for data received from the host. 00485 * @param size Size of the buffer in bytes. 00486 * @return Number of received bytes on success, negative error 00487 * code on failure. 00488 */ 00489 virtual nsapi_size_or_error_t socket_recv(nsapi_socket_t handle, 00490 void *data, nsapi_size_t size); 00491 00492 /** Receive a packet over a UDP socket. 00493 * 00494 * Receives data and stores the source address in address if address 00495 * is not NULL. Returns the number of bytes received into the buffer. 00496 * 00497 * PACKET SIZES: the maximum packet size that can be retrieved in a 00498 * single call to this method is limited by the configuration value 00499 * platform.buffered-serial-rxbuf-size (default 256). The maximum 00500 * UDP packet size is: 00501 * 00502 * platform.buffered-serial-rxbuf-size - AT_PACKET_OVERHEAD 00503 * 00504 * ...with a limit of 1024 (at the AT interface). So to allow reception of a 00505 * 1024 byte UDP packet in a single call, edit your mbed_app.json to add a 00506 * target override setting platform.buffered-serial-rxbuf-size to 1101. 00507 * 00508 * If the received packet is larger than this limit, any remainder will 00509 * be returned in subsequent calls to this method. Once a single UDP 00510 * packet has been received, this method will return. 00511 * 00512 * @param handle Socket handle. 00513 * @param address Destination for the source address or NULL. 00514 * @param data Destination buffer for data received from the host. 00515 * @param size Size of the buffer in bytes. 00516 * @return Number of received bytes on success, negative error 00517 * code on failure. 00518 */ 00519 virtual nsapi_size_or_error_t socket_recvfrom(nsapi_socket_t handle, 00520 SocketAddress *address, 00521 void *data, nsapi_size_t size); 00522 00523 /** Register a callback on state change of the socket. 00524 * 00525 * The specified callback will be called on state changes such as when 00526 * the socket can recv/send/accept successfully and on when an error 00527 * occurs. The callback may also be called spuriously without reason. 00528 * 00529 * The callback may be called in an interrupt context and should not 00530 * perform expensive operations such as recv/send calls. 00531 * 00532 * @param handle Socket handle. 00533 * @param callback Function to call on state change. 00534 * @param data Argument to pass to callback. 00535 */ 00536 virtual void socket_attach(nsapi_socket_t handle, void (*callback)(void *), 00537 void *data); 00538 00539 /** Listen for connections on a TCP socket. 00540 * 00541 * Marks the socket as a passive socket that can be used to accept 00542 * incoming connections. 00543 * 00544 * @param handle Socket handle. 00545 * @param backlog Number of pending connections that can be queued 00546 * simultaneously, defaults to 1. 00547 * @return 0 on success, negative error code on failure. 00548 */ 00549 virtual nsapi_error_t socket_listen(nsapi_socket_t handle, int backlog); 00550 00551 /** Accepts a connection on a TCP socket. 00552 * 00553 * The server socket must be bound and set to listen for connections. 00554 * On a new connection, creates a network socket and stores it in the 00555 * specified handle. The handle must be passed to following calls on 00556 * the socket. 00557 * 00558 * A stack may have a finite number of sockets, in this case 00559 * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. 00560 * 00561 * This call is non-blocking. If accept would block, 00562 * NSAPI_ERROR_WOULD_BLOCK is returned immediately. 00563 * 00564 * @param server Socket handle to server to accept from. 00565 * @param handle Destination for a handle to the newly created socket. 00566 * @param address Destination for the remote address or NULL. 00567 * @return 0 on success, negative error code on failure. 00568 */ 00569 virtual nsapi_error_t socket_accept(nsapi_socket_t server, 00570 nsapi_socket_t *handle, 00571 SocketAddress *address = 0); 00572 00573 /** Set stack-specific socket options. 00574 * 00575 * The setsockopt allow an application to pass stack-specific hints 00576 * to the underlying stack. For unsupported options, 00577 * NSAPI_ERROR_UNSUPPORTED is returned and the socket is unmodified. 00578 * 00579 * @param handle Socket handle. 00580 * @param level Stack-specific protocol level. 00581 * @param optname Stack-specific option identifier. 00582 * @param optval Option value. 00583 * @param optlen Length of the option value. 00584 * @return 0 on success, negative error code on failure. 00585 */ 00586 virtual nsapi_error_t setsockopt(nsapi_socket_t handle, int level, 00587 int optname, const void *optval, 00588 unsigned optlen); 00589 00590 /** Get stack-specific socket options. 00591 * 00592 * The getstackopt allow an application to retrieve stack-specific hints 00593 * from the underlying stack. For unsupported options, 00594 * NSAPI_ERROR_UNSUPPORTED is returned and optval is unmodified. 00595 * 00596 * @param handle Socket handle. 00597 * @param level Stack-specific protocol level. 00598 * @param optname Stack-specific option identifier. 00599 * @param optval Destination for option value. 00600 * @param optlen Length of the option value. 00601 * @return 0 on success, negative error code on failure. 00602 */ 00603 virtual nsapi_error_t getsockopt(nsapi_socket_t handle, int level, 00604 int optname, void *optval, 00605 unsigned *optlen); 00606 00607 private: 00608 00609 // u_ added to namespace us somewhat as this darned macro 00610 // is defined by everyone and their dog 00611 #define u_stringify(a) str(a) 00612 #define str(a) #a 00613 00614 bool _sim_pin_check_change_pending; 00615 bool _sim_pin_check_change_pending_enabled_value; 00616 bool _sim_pin_change_pending; 00617 const char *_sim_pin_change_pending_new_pin_value; 00618 Thread event_thread; 00619 volatile bool _run_event_thread; 00620 void handle_event(); 00621 SockCtrl * find_socket(int modem_handle = SOCKET_UNUSED); 00622 void clear_socket(SockCtrl * socket); 00623 bool check_socket(SockCtrl * socket); 00624 int nsapi_security_to_modem_security(nsapi_security_t nsapi_security); 00625 Callback<void(nsapi_error_t)> _connection_status_cb; 00626 void UUSORD_URC(); 00627 void UUSORF_URC(); 00628 void UUSOCL_URC(); 00629 void UUPSDD_URC(); 00630 #ifdef TARGET_UBLOX_C030_R41XM 00631 bool activate_context(); 00632 bool define_context(); 00633 #endif 00634 }; 00635 00636 #endif // _UBLOX_AT_CELLULAR_INTERFACE_ 00637
Generated on Wed Jul 13 2022 14:24:30 by 1.7.2