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    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):
15class Priority(enum.Enum):
16    BULK = 0
17    NORMAL = 1
18    URGENT = 2
19    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):
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>