hat.drivers.smpp

 1from hat.drivers.smpp.client import (connect,
 2                                     Client)
 3from hat.drivers.smpp.common import (MessageId,
 4                                     Priority,
 5                                     TypeOfNumber,
 6                                     DataCoding)
 7
 8
 9__all__ = ['connect',
10           'Client',
11           'MessageId',
12           'Priority',
13           'TypeOfNumber',
14           'DataCoding']
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    try:
43        client.async_group.spawn(aio.call_on_cancel, client._on_close,
44                                 close_timeout)
45        client.async_group.spawn(aio.call_on_done, conn.wait_closing(),
46                                 client.close)
47
48        req = transport.BindReq(
49            bind_type=transport.BindType.TRANSCEIVER,
50            system_id=system_id,
51            password=password,
52            system_type='',
53            interface_version=0x34,
54            addr_ton=transport.TypeOfNumber.UNKNOWN,
55            addr_npi=transport.NumericPlanIndicator.UNKNOWN,
56            address_range='')
57        await client._send(req)
58
59        client._bound = True
60
61        if enquire_link_delay is not None:
62            client.async_group.spawn(client._enquire_link_loop,
63                                     enquire_link_delay, enquire_link_timeout)
64
65    except BaseException:
66        await aio.uncancellable(client.async_close())
67        raise
68
69    return client

Connect to remote SMPP server

Additional arguments are passed directly to tcp.connect.

class Client(hat.aio.group.Resource):
 72class Client(aio.Resource):
 73
 74    @property
 75    def async_group(self) -> aio.Group:
 76        """Async group"""
 77        return self._async_group
 78
 79    async def send_message(self,
 80                           dst_addr: str,
 81                           msg: util.Bytes,
 82                           *,
 83                           short_message: bool = True,
 84                           priority: common.Priority = common.Priority.BULK,
 85                           udhi: bool = False,
 86                           dst_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN,  # NOQA
 87                           src_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN,  # NOQA
 88                           src_addr: str = '',
 89                           data_coding: common.DataCoding = common.DataCoding.DEFAULT  # NOQA
 90                           ) -> common.MessageId:
 91        """Send message"""
 92        optional_params = {}
 93        gsm_features = set()
 94
 95        if not short_message:
 96            optional_params[transport.OptionalParamTag.MESSAGE_PAYLOAD] = msg
 97
 98        if udhi:
 99            gsm_features.add(transport.GsmFeature.UDHI)
100
101        req = transport.SubmitSmReq(
102            service_type='',
103            source_addr_ton=src_ton,
104            source_addr_npi=transport.NumericPlanIndicator.UNKNOWN,
105            source_addr=src_addr,
106            dest_addr_ton=dst_ton,
107            dest_addr_npi=transport.NumericPlanIndicator.UNKNOWN,
108            destination_addr=dst_addr,
109            esm_class=transport.EsmClass(
110                messaging_mode=transport.MessagingMode.DEFAULT,
111                message_type=transport.MessageType.DEFAULT,
112                gsm_features=gsm_features),
113            protocol_id=0,
114            priority_flag=priority,
115            schedule_delivery_time=None,
116            validity_period=None,
117            registered_delivery=transport.RegisteredDelivery(
118                delivery_receipt=transport.DeliveryReceipt.NO_RECEIPT,
119                acknowledgements=set(),
120                intermediate_notification=False),
121            replace_if_present_flag=False,
122            data_coding=data_coding,
123            sm_default_msg_id=0,
124            short_message=(msg if short_message else b''),
125            optional_params=optional_params)
126
127        res = await self._send(req)
128
129        return res.message_id
130
131    async def _on_close(self, timeout):
132        if self._bound:
133            with contextlib.suppress(Exception):
134                await aio.wait_for(self._conn.send(transport.UnbindReq()),
135                                   timeout)
136
137        await self._conn.async_close()
138
139    async def _on_request(self, req):
140        self._equire_link_event.set()
141
142        if isinstance(req, transport.UnbindReq):
143            self._bound = False
144            self.async_group.spawn(aio.call, self.close)
145            return transport.UnbindRes()
146
147        if isinstance(req, transport.DataSmReq):
148            # TODO
149            return transport.CommandStatus.ESME_RINVCMDID
150
151        if isinstance(req, transport.DeliverSmReq):
152            # TODO
153            return transport.DeliverSmRes()
154
155        if isinstance(req, transport.EnquireLinkReq):
156            return transport.EnquireLinkRes()
157
158        return transport.CommandStatus.ESME_RINVCMDID
159
160    async def _on_notification(self, notification):
161        self._equire_link_event.set()
162
163        if isinstance(notification, transport.OutbindNotification):
164            # TODO
165            pass
166
167        if isinstance(notification, transport.AlertNotification):
168            # TODO
169            pass
170
171    async def _enquire_link_loop(self, delay, timeout):
172        try:
173            while True:
174                self._equire_link_event.clear()
175
176                with contextlib.suppress(asyncio.TimeoutError):
177                    await aio.wait_for(self._equire_link_event.wait(), delay)
178                    continue
179
180                if self._bound:
181                    await aio.wait_for(
182                        self._conn.send(transport.EnquireLinkReq()), timeout)
183
184        except ConnectionError:
185            pass
186
187        except asyncio.TimeoutError:
188            mlog.warning('enquire link timeout')
189
190        except Exception as e:
191            mlog.error('equire link loop error: %s', e, exc_info=e)
192
193        finally:
194            self.close()
195
196    async def _send(self, req):
197        res = await self._conn.send(req)
198        self._equire_link_event.set()
199
200        if isinstance(res, transport.CommandStatus):
201            error_str = transport.command_status_descriptions[res]
202            raise Exception(f'command error response: {error_str}')
203
204        return res

Resource with lifetime control based on Group.

async_group: hat.aio.group.Group
74    @property
75    def async_group(self) -> aio.Group:
76        """Async group"""
77        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:
 79    async def send_message(self,
 80                           dst_addr: str,
 81                           msg: util.Bytes,
 82                           *,
 83                           short_message: bool = True,
 84                           priority: common.Priority = common.Priority.BULK,
 85                           udhi: bool = False,
 86                           dst_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN,  # NOQA
 87                           src_ton: common.TypeOfNumber = common.TypeOfNumber.UNKNOWN,  # NOQA
 88                           src_addr: str = '',
 89                           data_coding: common.DataCoding = common.DataCoding.DEFAULT  # NOQA
 90                           ) -> common.MessageId:
 91        """Send message"""
 92        optional_params = {}
 93        gsm_features = set()
 94
 95        if not short_message:
 96            optional_params[transport.OptionalParamTag.MESSAGE_PAYLOAD] = msg
 97
 98        if udhi:
 99            gsm_features.add(transport.GsmFeature.UDHI)
100
101        req = transport.SubmitSmReq(
102            service_type='',
103            source_addr_ton=src_ton,
104            source_addr_npi=transport.NumericPlanIndicator.UNKNOWN,
105            source_addr=src_addr,
106            dest_addr_ton=dst_ton,
107            dest_addr_npi=transport.NumericPlanIndicator.UNKNOWN,
108            destination_addr=dst_addr,
109            esm_class=transport.EsmClass(
110                messaging_mode=transport.MessagingMode.DEFAULT,
111                message_type=transport.MessageType.DEFAULT,
112                gsm_features=gsm_features),
113            protocol_id=0,
114            priority_flag=priority,
115            schedule_delivery_time=None,
116            validity_period=None,
117            registered_delivery=transport.RegisteredDelivery(
118                delivery_receipt=transport.DeliveryReceipt.NO_RECEIPT,
119                acknowledgements=set(),
120                intermediate_notification=False),
121            replace_if_present_flag=False,
122            data_coding=data_coding,
123            sm_default_msg_id=0,
124            short_message=(msg if short_message else b''),
125            optional_params=optional_params)
126
127        res = await self._send(req)
128
129        return res.message_id

Send message

MessageId = bytes | bytearray | memoryview
class Priority(enum.Enum):
12class Priority(enum.Enum):
13    BULK = 0
14    NORMAL = 1
15    URGENT = 2
16    VERY_URGENT = 3
BULK = <Priority.BULK: 0>
NORMAL = <Priority.NORMAL: 1>
URGENT = <Priority.URGENT: 2>
VERY_URGENT = <Priority.VERY_URGENT: 3>
class TypeOfNumber(enum.Enum):
19class TypeOfNumber(enum.Enum):
20    UNKNOWN = 0
21    INTERNATIONAL = 1
22    NATIONAL = 2
23    NETWORK_SPECIFIC = 3
24    SUBSCRIBER_NUMBER = 4
25    ALPHANUMERIC = 5
26    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):
29class DataCoding(enum.Enum):
30    DEFAULT = 0
31    ASCII = 1
32    UNSPECIFIED_1 = 2
33    LATIN_1 = 3
34    UNSPECIFIED_2 = 4
35    JIS = 5
36    CYRLLIC = 6
37    LATIN_HEBREW = 7
38    UCS2 = 8
39    PICTOGRAM = 9
40    MUSIC = 10
41    EXTENDED_KANJI = 13
42    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>