url.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2020 Scott Shawcroft for Adafruit Industries LLC
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_ble_eddystone.url`
 24  ================================================================================
 25  
 26  Eddystone URL advertisement. Documented by Google here:
 27  https://github.com/google/eddystone/tree/master/eddystone-url
 28  
 29  """
 30  
 31  from . import EddystoneAdvertisement, EddystoneFrameStruct, EddystoneFrameBytes
 32  
 33  __version__ = "0.0.0-auto.0"
 34  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_Eddystone.git"
 35  
 36  # These prefixes are replaced with a single one-byte scheme number.
 37  _URL_SCHEMES = (b"http://www.", b"https://www.", b"http://", b"https://")
 38  
 39  # These common domains are replaced with a single non-printing byte.
 40  # Byte value is 0-6 for these with a '/' suffix.
 41  # Byte value is 7-13 for these without the '/' suffix.
 42  _SUBSTITUTIONS = (
 43      b".com",
 44      b".org",
 45      b".edu",
 46      b".net",
 47      b".info",
 48      b".biz",
 49      b".gov",
 50  )
 51  
 52  
 53  class _EncodedEddystoneUrl(EddystoneFrameBytes):
 54      """Packs and unpacks an encoded url"""
 55  
 56      def __get__(self, obj, cls):
 57          if obj is None:
 58              return self
 59          short_url = bytes(super().__get__(obj, cls))
 60  
 61          if short_url[0] < len(_URL_SCHEMES):
 62              short_url = _URL_SCHEMES[short_url[0]] + short_url[1:]
 63  
 64          for code, subst in enumerate(_SUBSTITUTIONS):
 65              code = bytes(chr(code), "ascii")
 66              short_url = short_url.replace(code, subst + b"/")
 67          for code, subst in enumerate(_SUBSTITUTIONS, 7):
 68              code = bytes(chr(code), "ascii")
 69              short_url = short_url.replace(code, subst)
 70  
 71          return str(short_url, "ascii")
 72  
 73      def __set__(self, obj, url):
 74          short_url = None
 75          url = bytes(url, "ascii")
 76          for idx, prefix in enumerate(_URL_SCHEMES):
 77              if url.startswith(prefix):
 78                  short_url = url[len(prefix) :]
 79                  short_url = bytes(chr(idx), "ascii") + short_url
 80                  break
 81          if not short_url:
 82              raise ValueError("url does not start with one of: ", _URL_SCHEMES)
 83          for code, subst in enumerate(_SUBSTITUTIONS):
 84              code = bytes(chr(code), "ascii")
 85              short_url = short_url.replace(subst + b"/", code)
 86          for code, subst in enumerate(_SUBSTITUTIONS, 7):
 87              code = bytes(chr(code), "ascii")
 88              short_url = short_url.replace(subst, code)
 89  
 90          super().__set__(obj, short_url)
 91  
 92  
 93  class EddystoneURL(EddystoneAdvertisement):
 94      """Eddystone URL broadcast.
 95  
 96      :param str url: Target url
 97      :param int tx_power: TX power in dBm"""
 98  
 99      match_prefixes = (b"\x03\xaa\xfe", b"\x16\xaa\xfe\x10")
100      frame_type = b"\x10"
101      tx_power = EddystoneFrameStruct("<B", offset=0)
102      """TX power in dBm"""
103  
104      url = _EncodedEddystoneUrl(offset=1)
105      """Target url"""
106  
107      def __init__(self, url=None, *, tx_power=0):
108          super().__init__(minimum_size=1)
109          self.tx_power = tx_power
110          self.url = url