1# -*- coding: utf-8 -*-
2# Copyright 2019 Oticon A/S
3# SPDX-License-Identifier: Apache-2.0
4
5from enum import IntEnum;
6from components.utils import *;
7from components.basic_commands import *;
8from components.address import *;
9from components.events import *;
10
11class Advertising(IntEnum):
12    CONNECTABLE_UNDIRECTED     = 0 # Connectable undirected advertising (ADV_IND) (default).
13    CONNECTABLE_HDC_DIRECTED   = 1 # Connectable high duty cycle directed advertising (ADV_DIRECT_IND, high duty cycle).
14    SCANNABLE_UNDIRECTED       = 2 # Scannable undirected advertising (ADV_SCAN_IND).
15    NON_CONNECTABLE_UNDIRECTED = 3 # Non connectable undirected advertising (ADV_NONCONN_IND).
16    CONNECTABLE_LDC_DIRECTED   = 4 # Connectable low duty cycle directed advertising (ADV_DIRECT_IND, low duty cycle).
17
18class Advertise(IntEnum):
19    DISABLE = 0                    # Disable Advertising
20    ENABLE  = 1                    # Enable Advertising
21
22class AdvertisingFilterPolicy(IntEnum):
23    FILTER_NONE                = 0 # Process scan and connection requests from all devices (i.e., the Filter Accept List is not in use) (default).
24    FILTER_SCAN_REQUESTS       = 1 # Process connection requests from all devices and only scan requests from devices that are in the Filter Accept List.
25    FILTER_CONNECTION_REQUESTS = 2 # Process scan requests from all devices and only connection requests from devices that are in the Filter Accept List.
26    FILTER_BOTH_REQUESTS       = 3 # Process scan and connection requests only from devices in the Filter Accept List.
27
28class AdvertiseChannel(IntEnum):
29    CHANNEL_37   = 1               # Advertise on channel 37
30    CHANNEL_38   = 2               # Advertise on channel 38
31    CHANNEL_39   = 4               # Advertise on channel 39
32    ALL_CHANNELS = 7               # Advertise on all three channels
33
34class ExtAdvertiseType(IntEnum):
35    CONNECTABLE              =  1  # bit 0 ~ Connectable advertising
36    SCANNABLE                =  2  # bit 1 ~ Scannable advertising
37    DIRECTED                 =  4  # bit 2 ~ Directed advertising
38    CONNECTABLE_HDC_DIRECTED =  8  # bit 3 ~ High Duty Cycle Directed Connectable advertising (≤ 3.75 ms Advertising Interval)
39    LEGACY                   = 16  # bit 4 ~ Use legacy advertising PDUs
40    ANONYMOUS                = 32  # bit 5 ~ Omit advertiser's address from all PDUs ("anonymous advertising")
41    INCLUDE_TX_POWER         = 64  # bit 6 ~ Include TxPower in the extended header of the advertising PDU
42
43class Advertiser:
44    """
45        An Advertiser handles all aspects of advertising.
46        - Set Advertising parameters including Advertising data.
47        - Set Scan response data.
48        - Enable Advertising.
49        - Disable Advertising.
50    """
51    """
52        Constructor:
53            transport     - EDTTT object
54            idx           - Number; Device identifier
55            trace         - Trace object
56            channels      - AdvertiseChannels enum holding the channel or channels to advertise on
57            advertiseType - Advertising enum holding the type of advertising to emit
58            ownAddress    - Address object with an ExtendedAddressType address (only the address type is used)
59            peerAddress   - Address object with a SimpleAddressType address (both address type and address are used)
60            filterPolicy  - AdvertisingFilterPolicy enum
61            advertiseData - Array of Bytes holding advertise data
62            responseData  - Array of Bytes holding response data
63    """
64    def __init__(self, transport, idx, trace, channels, advertiseType, ownAddress, peerAddress, filterPolicy=AdvertisingFilterPolicy.FILTER_NONE, \
65                       advertiseData=None, responseData=None):
66        self.transport = transport;
67        self.idx = idx;
68        self.trace = trace;
69        self.channels = channels;
70        self.advertiseType = advertiseType;
71        """
72            Own_Address_Type parameter indicates the type of address being used in the advertising packets.
73
74            If Own_Address_Type equals 0x02 or 0x03, the Peer_Address parameter contains the peer’s Identity Address and
75            the Peer_Address_Type parameter contains the Peer’s Identity Type (i.e. 0x00 or 0x01).
76            These parameters are used to locate the corresponding local IRK in the resolving list; this IRK is used to generate
77            the own address used in the advertisement.
78
79            If Own_Address_Type equals 0x02 or 0x03, the Controller generates the peer’s Resolvable Private Address using the
80            peer’s IRK corresponding to the peer’s Identity Address contained in the Peer_Address parameter and peer’s Identity
81            Address Type (i.e. 0x00 or 0x01) contained in the Peer_Address_Type parameter.
82
83            If directed advertising is performed, i.e. when Advertising_Type is set to 0x01 (ADV_DIRECT_IND, high duty cycle) or
84            0x04 (ADV_DIRECT_IND, low duty cycle mode), then the Peer_Address_Type and Peer_Address shall be valid.
85        """
86        self.ownAddress = ownAddress;
87        self.peerAddress = peerAddress;
88
89        self.advertiseData = [] if advertiseData is None else advertiseData[ : ];
90        self.responseData = [] if responseData is None else responseData[ : ];
91        """
92            The Advertising_Interval_Min shall be less than or equal to the Advertising_Interval_Max.
93            The Advertising_Interval_Min and Advertising_Interval_Max should not be the same value to enable the Controller to
94            determine the best advertising interval given other activities.
95
96            For high duty cycle directed advertising, i.e. when Advertising_Type is 0x01 (ADV_DIRECT_IND, high duty cycle),
97            the Advertising_Interval_Min and Advertising_Interval_Max parameters are not used and shall be ignored.
98        """
99        self.minInterval = 32; # Minimum Advertise Interval = 32 x 0.625 ms = 20.00 ms
100        self.maxInterval = 32; # Maximum Advertise Interval = 32 x 0.625 ms = 20.00 ms
101        self.filterPolicy = filterPolicy;
102        self.status = 0;
103
104    def __verifyAndShowEvent(self, expectedEvent):
105        event = get_event(self.transport, self.idx, 200);
106        self.trace.trace(7, str(event));
107        return event.event == expectedEvent;
108
109    def __getCommandCompleteEvent(self):
110        return self.__verifyAndShowEvent(Events.BT_HCI_EVT_CMD_COMPLETE);
111
112    def __confined_array(self, data, limit):
113        dataSize = len(data) if len(data) <= limit else limit;
114        dataCopy = data[ : ];
115        if len(data) < limit:
116            dataCopy += [0 for _ in range(limit - dataSize)];
117        elif len(data) > limit:
118            dataCopy = dataCopy[:limit];
119        return dataSize, dataCopy;
120
121    def __set_advertise_parameters(self):
122
123        self.status = le_set_advertising_parameters(self.transport, self.idx, self.minInterval, self.maxInterval, self.advertiseType, self.ownAddress.type, \
124                                                    self.peerAddress.type, self.peerAddress.address, self.channels, self.filterPolicy, 200);
125        self.trace.trace(6, "LE Set Advertising Parameters Command returns status: 0x%02X" % self.status);
126        return self.__getCommandCompleteEvent() and (self.status == 0);
127
128    def __set_advertise_data(self):
129
130        dataSize, advertiseData = self.__confined_array(self.advertiseData, 31);
131
132        self.status = le_set_advertising_data(self.transport, self.idx, dataSize, advertiseData, 200);
133        self.trace.trace(6, "LE Set Advertising Data Command returns status: 0x%02X" % self.status);
134        return self.__getCommandCompleteEvent() and (self.status == 0);
135
136    def __set_scan_response(self):
137
138        dataSize, responseData = self.__confined_array(self.responseData, 31);
139
140        self.status = le_set_scan_response_data(self.transport, self.idx, dataSize, responseData, 200);
141        self.trace.trace(6, "LE Set Scan Response Data Command returns status: 0x%02X" % self.status);
142        return self.__getCommandCompleteEvent() and (self.status == 0);
143
144    def __advertise_enable(self, enable):
145
146        self.status = le_set_advertising_enable(self.transport, self.idx, enable, 200);
147        self.trace.trace(6, "LE Set Advertising Enable Command (%s) returns status: 0x%02X" % ("Enabling" if enable else "Disabling", self.status));
148        return self.__getCommandCompleteEvent() and (self.status == 0);
149
150    """
151        Enable advertising - start emitting Advertise packages
152    """
153    def enable(self, sameSetup=False):
154        if not sameSetup:
155            success = self.__set_advertise_parameters();
156            success = success and self.__set_advertise_data();
157            success = success and self.__set_scan_response();
158        else:
159            success = True;
160        success = success and self.__advertise_enable(Advertise.ENABLE);
161        return success;
162
163    """
164        Disable advertising - stop emitting Advertise packages
165    """
166    def disable(self):
167        return self.__advertise_enable(Advertise.DISABLE);
168
169    """
170        Check for a advertise timeout - when using connectable high duty cycle directed advertising
171    """
172    def timeout(self, timeout=200):
173        self.status = 0;
174        if has_event(self.transport, self.idx, timeout)[0]:
175            event = get_event(self.transport, self.idx, 200);
176            self.trace.trace(7, str(event));
177            if (event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_COMPLETE) or (event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE):
178                self.status = event.decode()[0];
179        return self.status == 0x3C;
180