hat.drivers.smpp
async def
connect( addr: hat.drivers.tcp.Address, system_id: str = '', password: str = '', close_timeout: float = 0.1, enquire_link_delay: float | None = None, enquire_link_timeout: float = 10, **kwargs) -> Client:
18async def connect(addr: tcp.Address, 19 system_id: str = '', 20 password: str = '', 21 close_timeout: float = 0.1, 22 enquire_link_delay: float | None = None, 23 enquire_link_timeout: float = 10, 24 **kwargs 25 ) -> 'Client': 26 """Connect to remote SMPP server 27 28 Additional arguments are passed directly to `tcp.connect`. 29 30 """ 31 client = Client() 32 client._async_group = aio.Group() 33 client._equire_link_event = asyncio.Event() 34 client._bound = False 35 36 conn = await tcp.connect(addr, **kwargs) 37 client._conn = transport.Connection( 38 conn=conn, 39 request_cb=client._on_request, 40 notification_cb=client._on_notification) 41 42 client._log = common.create_logger(mlog, conn.info) 43 44 try: 45 client.async_group.spawn(aio.call_on_cancel, client._on_close, 46 close_timeout) 47 client.async_group.spawn(aio.call_on_done, conn.wait_closing(), 48 client.close) 49 50 req = transport.BindReq( 51 bind_type=transport.BindType.TRANSCEIVER, 52 system_id=system_id, 53 password=password, 54 system_type='', 55 interface_version=0x34, 56 addr_ton=transport.TypeOfNumber.UNKNOWN, 57 addr_npi=transport.NumericPlanIndicator.UNKNOWN, 58 address_range='') 59 await client._send(req) 60 61 client._bound = True 62 63 if enquire_link_delay is not None: 64 client.async_group.spawn(client._enquire_link_loop, 65 enquire_link_delay, enquire_link_timeout) 66 67 except BaseException: 68 await aio.uncancellable(client.async_close()) 69 raise 70 71 return client
Connect to remote SMPP server
Additional arguments are passed directly to tcp.connect.
class
Client(hat.aio.group.Resource):
74class Client(aio.Resource): 75 76 @property 77 def async_group(self) -> aio.Group: 78 """Async group""" 79 return self._async_group 80 81 async def send_message(self, 82 dst_addr: str, 83 msg: util.Bytes, 84 *, 85 short_message: bool = True, 86 priority: common.Priority = common.Priority.BULK, 87 udhi: bool = False, 88 dst_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN, # NOQA 89 src_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN, # NOQA 90 src_addr: str = '', 91 data_coding: common.DataCoding = common.DataCoding.DEFAULT # NOQA 92 ) -> common.MessageId: 93 """Send message""" 94 optional_params = {} 95 gsm_features = set() 96 97 if not short_message: 98 optional_params[transport.OptionalParamTag.MESSAGE_PAYLOAD] = msg 99 100 if udhi: 101 gsm_features.add(transport.GsmFeature.UDHI) 102 103 req = transport.SubmitSmReq( 104 service_type='', 105 source_addr_ton=src_ton, 106 source_addr_npi=transport.NumericPlanIndicator.UNKNOWN, 107 source_addr=src_addr, 108 dest_addr_ton=dst_ton, 109 dest_addr_npi=transport.NumericPlanIndicator.UNKNOWN, 110 destination_addr=dst_addr, 111 esm_class=transport.EsmClass( 112 messaging_mode=transport.MessagingMode.DEFAULT, 113 message_type=transport.MessageType.DEFAULT, 114 gsm_features=gsm_features), 115 protocol_id=0, 116 priority_flag=priority, 117 schedule_delivery_time=None, 118 validity_period=None, 119 registered_delivery=transport.RegisteredDelivery( 120 delivery_receipt=transport.DeliveryReceipt.NO_RECEIPT, 121 acknowledgements=set(), 122 intermediate_notification=False), 123 replace_if_present_flag=False, 124 data_coding=data_coding, 125 sm_default_msg_id=0, 126 short_message=(msg if short_message else b''), 127 optional_params=optional_params) 128 129 res = await self._send(req) 130 131 return res.message_id 132 133 async def _on_close(self, timeout): 134 if self._bound: 135 with contextlib.suppress(Exception): 136 await aio.wait_for(self._conn.send(transport.UnbindReq()), 137 timeout) 138 139 await self._conn.async_close() 140 141 async def _on_request(self, req): 142 self._equire_link_event.set() 143 144 if isinstance(req, transport.UnbindReq): 145 self._bound = False 146 self.async_group.spawn(aio.call, self.close) 147 return transport.UnbindRes() 148 149 if isinstance(req, transport.DataSmReq): 150 # TODO 151 return transport.CommandStatus.ESME_RINVCMDID 152 153 if isinstance(req, transport.DeliverSmReq): 154 # TODO 155 return transport.DeliverSmRes() 156 157 if isinstance(req, transport.EnquireLinkReq): 158 return transport.EnquireLinkRes() 159 160 return transport.CommandStatus.ESME_RINVCMDID 161 162 async def _on_notification(self, notification): 163 self._equire_link_event.set() 164 165 if isinstance(notification, transport.OutbindNotification): 166 # TODO 167 pass 168 169 if isinstance(notification, transport.AlertNotification): 170 # TODO 171 pass 172 173 async def _enquire_link_loop(self, delay, timeout): 174 try: 175 while True: 176 self._equire_link_event.clear() 177 178 with contextlib.suppress(asyncio.TimeoutError): 179 await aio.wait_for(self._equire_link_event.wait(), delay) 180 continue 181 182 if self._bound: 183 await aio.wait_for( 184 self._conn.send(transport.EnquireLinkReq()), timeout) 185 186 except ConnectionError: 187 pass 188 189 except asyncio.TimeoutError: 190 self._log.warning('enquire link timeout') 191 192 except Exception as e: 193 self._log.error('equire link loop error: %s', e, exc_info=e) 194 195 finally: 196 self.close() 197 198 async def _send(self, req): 199 res = await self._conn.send(req) 200 self._equire_link_event.set() 201 202 if isinstance(res, transport.CommandStatus): 203 error_str = transport.command_status_descriptions[res] 204 raise Exception(f'command error response: {error_str}') 205 206 return res
Resource with lifetime control based on Group.
async_group: hat.aio.group.Group
76 @property 77 def async_group(self) -> aio.Group: 78 """Async group""" 79 return self._async_group
Async group
async def
send_message( self, dst_addr: str, msg: bytes | bytearray | memoryview, *, short_message: bool = True, priority: Priority = <Priority.BULK: 0>, udhi: bool = False, dst_ton: TypeOfNumber = <TypeOfNumber.UNKNOWN: 0>, src_ton: TypeOfNumber = <TypeOfNumber.UNKNOWN: 0>, src_addr: str = '', data_coding: DataCoding = <DataCoding.DEFAULT: 0>) -> bytes | bytearray | memoryview:
81 async def send_message(self, 82 dst_addr: str, 83 msg: util.Bytes, 84 *, 85 short_message: bool = True, 86 priority: common.Priority = common.Priority.BULK, 87 udhi: bool = False, 88 dst_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN, # NOQA 89 src_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN, # NOQA 90 src_addr: str = '', 91 data_coding: common.DataCoding = common.DataCoding.DEFAULT # NOQA 92 ) -> common.MessageId: 93 """Send message""" 94 optional_params = {} 95 gsm_features = set() 96 97 if not short_message: 98 optional_params[transport.OptionalParamTag.MESSAGE_PAYLOAD] = msg 99 100 if udhi: 101 gsm_features.add(transport.GsmFeature.UDHI) 102 103 req = transport.SubmitSmReq( 104 service_type='', 105 source_addr_ton=src_ton, 106 source_addr_npi=transport.NumericPlanIndicator.UNKNOWN, 107 source_addr=src_addr, 108 dest_addr_ton=dst_ton, 109 dest_addr_npi=transport.NumericPlanIndicator.UNKNOWN, 110 destination_addr=dst_addr, 111 esm_class=transport.EsmClass( 112 messaging_mode=transport.MessagingMode.DEFAULT, 113 message_type=transport.MessageType.DEFAULT, 114 gsm_features=gsm_features), 115 protocol_id=0, 116 priority_flag=priority, 117 schedule_delivery_time=None, 118 validity_period=None, 119 registered_delivery=transport.RegisteredDelivery( 120 delivery_receipt=transport.DeliveryReceipt.NO_RECEIPT, 121 acknowledgements=set(), 122 intermediate_notification=False), 123 replace_if_present_flag=False, 124 data_coding=data_coding, 125 sm_default_msg_id=0, 126 short_message=(msg if short_message else b''), 127 optional_params=optional_params) 128 129 res = await self._send(req) 130 131 return res.message_id
Send message
MessageId =
bytes | bytearray | memoryview
class
Priority(enum.Enum):
BULK =
<Priority.BULK: 0>
NORMAL =
<Priority.NORMAL: 1>
URGENT =
<Priority.URGENT: 2>
VERY_URGENT =
<Priority.VERY_URGENT: 3>
class
TypeOfNumber(enum.Enum):
22class TypeOfNumber(enum.Enum): 23 UNKNOWN = 0 24 INTERNATIONAL = 1 25 NATIONAL = 2 26 NETWORK_SPECIFIC = 3 27 SUBSCRIBER_NUMBER = 4 28 ALPHANUMERIC = 5 29 ABBREVIATED = 6
UNKNOWN =
<TypeOfNumber.UNKNOWN: 0>
INTERNATIONAL =
<TypeOfNumber.INTERNATIONAL: 1>
NATIONAL =
<TypeOfNumber.NATIONAL: 2>
NETWORK_SPECIFIC =
<TypeOfNumber.NETWORK_SPECIFIC: 3>
SUBSCRIBER_NUMBER =
<TypeOfNumber.SUBSCRIBER_NUMBER: 4>
ALPHANUMERIC =
<TypeOfNumber.ALPHANUMERIC: 5>
ABBREVIATED =
<TypeOfNumber.ABBREVIATED: 6>
class
DataCoding(enum.Enum):
32class DataCoding(enum.Enum): 33 DEFAULT = 0 34 ASCII = 1 35 UNSPECIFIED_1 = 2 36 LATIN_1 = 3 37 UNSPECIFIED_2 = 4 38 JIS = 5 39 CYRLLIC = 6 40 LATIN_HEBREW = 7 41 UCS2 = 8 42 PICTOGRAM = 9 43 MUSIC = 10 44 EXTENDED_KANJI = 13 45 KS = 14
DEFAULT =
<DataCoding.DEFAULT: 0>
ASCII =
<DataCoding.ASCII: 1>
UNSPECIFIED_1 =
<DataCoding.UNSPECIFIED_1: 2>
LATIN_1 =
<DataCoding.LATIN_1: 3>
UNSPECIFIED_2 =
<DataCoding.UNSPECIFIED_2: 4>
JIS =
<DataCoding.JIS: 5>
CYRLLIC =
<DataCoding.CYRLLIC: 6>
LATIN_HEBREW =
<DataCoding.LATIN_HEBREW: 7>
UCS2 =
<DataCoding.UCS2: 8>
PICTOGRAM =
<DataCoding.PICTOGRAM: 9>
MUSIC =
<DataCoding.MUSIC: 10>
EXTENDED_KANJI =
<DataCoding.EXTENDED_KANJI: 13>
KS =
<DataCoding.KS: 14>