[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [martinohanlon]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Having trouble with BlueDot?\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nFill in the details or delete as appropriate :)\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\nor \n\nRun this program\n```python\n\n```\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**System (please complete the following information):**\n - OS: [e.g. Raspbian]\n - Version [e.g. Buster]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Got an idea for BlueDot?\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the feature you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cdo]\npythonhosted/\n.pytest_cache/\n\n# Editor detritus\n*.vim\n*.swp\ntags\n.vscode\n.idea\n\n# Packaging detritus\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\ncoverage\n.coverage\n.tox\n.cache\n\n# Generated documentation\ndocs/_build\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Martin O'Hanlon\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "PRIVACYPOLICY.MD",
    "content": "# BlueDot Privacy Policy\n\nThe BlueDot Python library and Android app do not store your personal data. That is it!\n\nThere is nowhere to store your data, no database, no server. Even if Rick Astley asked [really really nicely](https://www.youtube.com/watch?v=dQw4w9WgXcQ) to see your data it couldn't be given it to him.\n\nFor the purposes of utmost transparency...  The android app will store 2 items of non-personal data, [preferences for using the default port and a preferred port](https://github.com/martinohanlon/BlueDot/blob/master/clients/android/app/src/main/java/com/stuffaboutcode/bluedot/SettingsActivity.java) (if you change them from the default) in a [shared preferences file](https://developer.android.com/training/data-storage/shared-preferences).\n"
  },
  {
    "path": "README.rst",
    "content": "Blue Dot\n========\n\n|pypibadge| |docsbadge|\n\n.. raw:: html\n\n    <iframe allowtransparency=\"true\" style=\"background-color: white;\" src=\"https://github.com/sponsors/martinohanlon/button\" title=\"Sponsor martinohanlon\" height=\"35\" width=\"116\" style=\"border: 0;\"></iframe>\n\nBlue Dot allows you to control your Raspberry Pi projects wirelessly - it's a Bluetooth remote and zero boiler plate (super simple to use :) Python library.\n\n|bluedotfeature|\n\n|bluedotapp| |bluedotmanybuttons| |bluedotpython|\n\nCreated by `Martin O'Hanlon`_ (`@martinohanlon`_, `stuffaboutco.de`_).\n\nGetting Started\n---------------\n\n`Install and usage`_ is really simple:\n\n1. Install the Python library::\n\n       sudo pip3 install bluedot\n\n2. Get the `Android Blue Dot app`_ or use the `Python Blue Dot app`_\n\n3. Pair your Raspberry Pi\n\n4. Write some code::\n\n       from bluedot import BlueDot\n       bd = BlueDot()\n       bd.wait_for_press()\n       print(\"You pressed the blue dot!\")\n\n5. Press the Blue Dot\n\nSee the `getting started`_ guide to 'get started'!\n\nMore\n----\n\nBlue Dot is more than just one `button_`. You can create as many buttons as you want and change their appearance to create your own controller.\n\n|bluedotjoypad|\n\nEvery `button`_ is also a `joystick`_. You can tell if a button was pressed in the middle, on the top, bottom, left or right. You can easily create a `BlueDot controlled Robot`_.\n\nWhy be restricted by such vague positions like top and bottom though: you can get the exact (x, y) position or even the angle and distance from centre where the button was pressed.\n\nIts not all about when the button was pressed either - pressed, released or moved they all work.\n\nA button can be any colour, square, given give or hidden!\n\nYou can press it, `slide it`_, `swipe it`_, `rotate it`_ - one blue circle can do a lot!\n\nEven more\n---------\n\nThe `online documentation`_ describes how to use Blue Dot and the `Python library`_ including `Recipes`_ and ideas.\n\nStatus\n------\n\nProduction - under active development. Be sure to raise an `issue`_ if you have a feature request or experience problems.\n\n.. _Martin O'Hanlon: https://github.com/martinohanlon\n.. _stuffaboutco.de: http://stuffaboutco.de\n.. _@martinohanlon: https://twitter.com/martinohanlon\n.. _getting started: http://bluedot.readthedocs.io/en/latest/gettingstarted.html\n.. _Install and usage: http://bluedot.readthedocs.io/en/latest/gettingstarted.html\n.. _online documentation: http://bluedot.readthedocs.io/en/latest/\n.. _Python library: http://bluedot.readthedocs.io/en/latest/dotapi.html\n.. _examples: https://github.com/martinohanlon/BlueDot/tree/master/examples\n.. _Recipes: http://bluedot.readthedocs.io/en/latest/recipes.html\n.. _Android Blue Dot app: http://play.google.com/store/apps/details?id=com.stuffaboutcode.bluedot\n.. _Python Blue Dot app: http://bluedot.readthedocs.io/en/latest/bluedotpythonapp.html\n.. _issue: https://github.com/martinohanlon/bluedot/issues\n.. _BlueDot controlled Robot: https://youtu.be/eW9oEPySF58\n.. _joystick: http://bluedot.readthedocs.io/en/latest/recipes.html#joystick\n.. _button: http://bluedot.readthedocs.io/en/latest/recipes.html#button\n.. _slide it: http://bluedot.readthedocs.io/en/latest/recipes.html#slider\n.. _swipe it: http://bluedot.readthedocs.io/en/latest/recipes.html#swiping\n.. _rotate it: http://bluedot.readthedocs.io/en/latest/recipes.html#rotating\n\n.. |bluedotapp| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotandroid_small.png\n   :height: 270 px\n   :width: 144 px\n   :scale: 100 %\n   :alt: blue dot app\n\n.. |bluedotpython| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotpython.png\n   :height: 247 px\n   :width: 294 px\n   :scale: 100 %\n   :alt: blue dot python app\n\n.. |bluedotjoypad| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/layout_joypad_smaller.png\n   :height: 147 px\n   :width: 294 px\n   :scale: 100 %\n   :alt: blue dot app as a joy pad controller\n\n.. |bluedotmanybuttons| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/layout_many_buttons_smaller.png\n   :height: 270 px\n   :width: 144 px\n   :scale: 100 %\n   :alt: blue dot app with 10 buttons in a 2x5 grid\n\n.. |bluedotfeature| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/blue_dot_feature_small.png\n   :height: 247 px\n   :width: 506 px\n   :scale: 100 %\n   :alt: blue dot feature\n\n.. |pypibadge| image:: https://badge.fury.io/py/bluedot.svg\n   :target: https://badge.fury.io/py/bluedot\n   :alt: Latest Version\n\n.. |docsbadge| image:: https://readthedocs.org/projects/bluedot/badge/\n   :target: https://readthedocs.org/projects/bluedot/\n   :alt: Docs\n"
  },
  {
    "path": "bluedot/__init__.py",
    "content": "from .dot import BlueDot, BlueDotButton\nfrom .interactions import BlueDotInteraction, BlueDotPosition, BlueDotRotation, BlueDotSwipe\nfrom .colors import COLORS\nfrom .mock import MockBlueDot\n"
  },
  {
    "path": "bluedot/app.py",
    "content": "import os\nos.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'\n\nfrom argparse import ArgumentParser\nimport pygame\nimport sys\nfrom .btcomm import BluetoothAdapter, BluetoothClient\nfrom .constants import PROTOCOL_VERSION\nfrom .colors import BLUE, GRAY43, GRAY86, RED, parse_color\n\nDEFAULTSIZE = (320, 240)\nBORDER = 7\nFONT = \"monospace\"\nFONTSIZE = 18\nFONTPAD = 3\n\nCLIENT_NAME = \"Blue Dot Python app\"\nBORDER_THICKNESS = 0.025\n\nclass BlueDotClient:\n    def __init__(self, device, server, port, fullscreen, width, height):\n\n        self._device = device\n        self._server = server\n        self._port = 1 if port is None else port\n        self._fullscreen = fullscreen\n        \n        #init pygame\n        pygame.init()\n\n        #load font\n        self._font = pygame.font.SysFont(FONT, FONTSIZE)\n\n        #setup the screen\n        #set the screen caption\n        pygame.display.set_caption(\"Blue Dot\")\n\n        #create the screen\n        screenflags = 0\n\n        if fullscreen:\n            screenflags = pygame.FULLSCREEN\n            if width == None and height == None:\n                display_info = pygame.display.Info()\n                width = display_info.current_w\n                height = display_info.current_h\n\n        if width == None: width = DEFAULTSIZE[0]\n        if height == None: height = DEFAULTSIZE[1]\n\n        self._screen = pygame.display.set_mode((width, height), screenflags)\n\n        self._width = width\n        self._height = height\n\n        self._run()\n\n        pygame.quit()\n\n    def _run(self):\n        # has a server been specified?  If so connected directly\n        if self._server:\n            button_screen = ButtonScreen(self._screen, self._font, self._device, self._server, self._port, self._width, self._height)\n            button_screen.run()\n        else:\n            # start the devices screen\n            devices_screen = DevicesScreen(self._screen, self._font, self._device, self._port, self._width, self._height)\n            devices_screen.run()\n\n\nclass BlueDotScreen:\n    def __init__(self, screen, font, width, height):\n        self.screen = screen\n        self.font = font\n        self.width = width\n        self.height = height\n\n        # setup screen attributes\n        self.frame_rect = pygame.Rect(BORDER, BORDER, self.width - (BORDER * 2) - FONTSIZE - FONTPAD, self.height - (BORDER * 2))\n        self.close_rect = pygame.Rect(self.width - FONTSIZE - FONTPAD - BORDER, BORDER, FONTSIZE + FONTPAD, FONTSIZE + FONTPAD)\n\n        self.draw_screen()\n\n    def draw_screen(self):\n        # set the screen background\n        self.screen.fill(GRAY86.rgb)\n\n        self.draw_close_button()\n\n    def draw_close_button(self):\n        # draw close button\n        pygame.draw.rect(self.screen, BLUE.rgb, self.close_rect, 2)\n        pygame.draw.line(self.screen, BLUE.rgb,\n                        (self.close_rect[0], self.close_rect[1]),\n                        (self.close_rect[0] + self.close_rect[2], self.close_rect[1] + self.close_rect[3]),\n                        1)\n        pygame.draw.line(self.screen, BLUE.rgb,\n                        (self.close_rect[0], self.close_rect[1] + self.close_rect[3]),\n                        (self.close_rect[0] + self.close_rect[2], self.close_rect[1]),\n                        1)\n\n    def draw_error(self, e):\n        message = \"Error: {}\".format(e)\n        print(message)\n        self.draw_status_message(message, colour = RED.rgb)\n\n    def draw_status_message(self, message, colour = BLUE.rgb):\n        self.screen.fill(GRAY86.rgb, self.frame_rect)\n        self.draw_close_button()\n        self.draw_text(message, colour, self.frame_rect.height / 2, border = True, border_pad = FONTPAD)\n        pygame.display.update()\n\n    def draw_text(self, text, colour, start_y, antiaalias=False, background=None, border=False, border_width=1, border_pad=0):\n        rect = pygame.Rect(self.frame_rect)\n        y = rect.top + start_y + border_pad\n        lineSpacing = -2\n\n        # get the height of the font\n        fontHeight = self.font.size(\"Tg\")[1]\n\n        while text:\n            i = 1\n\n            # determine if the row of text will be outside our area\n            if y + fontHeight > rect.bottom:\n                break\n\n            # determine maximum width of line\n            while self.font.size(text[:i])[0] < (rect.width - (border_pad * 2)) and i < len(text):\n                i += 1\n\n            # if we've wrapped the text, then adjust the wrap to the last word\n            if i < len(text):\n                i = text.rfind(\" \", 0, i) + 1\n\n            # render the line and blit it to the surface\n            if background:\n                image = self.font.render(text[:i], 1, colour, background)\n                image.set_colorkey(background)\n            else:\n                image = self.font.render(text[:i], antiaalias, colour)\n\n            self.screen.blit(image, (rect.left + border_pad, y))\n            y += fontHeight + lineSpacing + border_pad\n\n            # remove the text we just blitted\n            text = text[i:]\n\n        #return the rect the text was drawn in\n        rect.top = rect.top + start_y\n        rect.height = y - start_y\n\n        if border:\n            pygame.draw.rect(self.screen, colour, rect, border_width)\n\n        return rect\n\n\nclass DevicesScreen(BlueDotScreen):\n    def __init__(self, screen, font, device, port, width, height):\n        self.bt_adapter = BluetoothAdapter(device = device)\n        self.port = port\n\n        super(DevicesScreen, self).__init__(screen, font, width, height)\n        # self.draw_screen()\n\n    def draw_screen(self):\n        self.device_rects = []\n\n        super(DevicesScreen, self).draw_screen()\n\n        #title\n        title_rect = self.draw_text(\"Connect\", RED.rgb, 0)\n\n        y = title_rect.bottom\n        for d in self.bt_adapter.paired_devices:\n            device_rect = self.draw_text(\"{} ({})\".format(d[1], d[0]), BLUE.rgb, y, border = True, border_pad = FONTPAD)\n\n            self.device_rects.append(pygame.Rect(device_rect))\n\n            y = device_rect.bottom\n\n    def run(self):\n        clock = pygame.time.Clock()\n        running = True\n        while running:\n            clock.tick(50)\n\n            ev = pygame.event.get()\n\n            for event in ev:\n\n                if event.type == pygame.MOUSEBUTTONDOWN:\n                    pos = pygame.mouse.get_pos()\n\n                    #has a device been clicked?\n                    for d in range(len(self.device_rects)):\n                        if self.device_rects[d].collidepoint(pos):\n                            # show the button\n                            self.draw_status_message(\"Connecting\")\n                            button_screen = ButtonScreen(self.screen, self.font, self.bt_adapter.device, self.bt_adapter.paired_devices[d][0], self.port, self.width, self.height)\n                            button_screen.run()\n\n                            #redraw the screen\n                            self.draw_screen()\n\n                    if self.close_rect.collidepoint(pos):\n                        running = False\n\n                if event.type == pygame.KEYDOWN:\n                    if event.key == pygame.K_ESCAPE:\n                        running = False\n\n                if event.type == pygame.QUIT:\n                    running = False\n\n            pygame.display.update()\n\n\nclass ButtonScreen(BlueDotScreen):\n    def __init__(self, screen, font, device, server, port, width, height):        \n        self.device = device\n        self.server = server\n        self.port = port\n        self._data_buffer = \"\"\n\n        self.last_x = 0\n        self.last_y = 0\n\n        self._colour = BLUE\n        self._border = False\n        self._square = False\n        self._visible = True\n        self._pressed = False\n\n        super(ButtonScreen, self).__init__(screen, font, width, height)\n\n    def draw_screen(self):\n        super(ButtonScreen, self).draw_screen()\n\n        # work out dot position\n        self.dot_centre = (int(self.frame_rect.top + (self.frame_rect.width / 2)), int(self.frame_rect.left + (self.frame_rect.height / 2)))\n        \n        if self.frame_rect.width > self.frame_rect.height:\n            self.dot_rect = pygame.Rect(self.frame_rect.left + int((self.frame_rect.width - self.frame_rect.height) / 2), self.frame_rect.top, self.frame_rect.height, self.frame_rect.height)\n            self.dot_radius = int(self.dot_rect.height / 2)\n        else:\n            self.dot_rect = pygame.Rect(self.frame_rect.left, self.frame_rect.top + int((self.frame_rect.height - self.frame_rect.width) / 2), self.frame_rect.width, self.frame_rect.width)\n            self.dot_radius = int(self.dot_rect.width / 2)\n\n        self.border_width = max(int(self.dot_rect.width * BORDER_THICKNESS), 1)\n        self.border_height = max(int(self.dot_rect.height * BORDER_THICKNESS), 1)\n\n        self._draw_dot()\n\n    def _draw_dot(self):\n\n        # clear the dot\n\n        pygame.draw.rect(\n            self.screen,\n            GRAY86.rgb, \n            (\n                self.dot_rect.left - self.border_width, \n                self.dot_rect.top - self.border_height, \n                self.dot_rect.width + (self.border_width * 2), \n                self.dot_rect.height + (self.border_height * 2), \n            )\n        )\n        colour = self._colour if not self._pressed else self._colour.get_adjusted_color(0.85)\n\n        # draw the dot\n        if self._square:\n            if self._visible:\n                pygame.draw.rect(self.screen, colour.rgb, self.dot_rect)\n            if self._border:\n                pygame.draw.rect(self.screen, GRAY43.rgb, self.dot_rect, max(int(self.dot_radius * BORDER_THICKNESS), 1))\n        else:\n            if self._visible:\n                pygame.draw.ellipse(self.screen, colour.rgb, self.dot_rect)\n            if self._border:\n                pygame.draw.ellipse(self.screen, GRAY43.rgb, self.dot_rect, max(int(self.dot_radius * BORDER_THICKNESS), 1))\n\n    def _process(self, op, pos):\n        if self.bt_client.connected:\n            x = (pos[0] - self.dot_centre[0]) / float(self.dot_radius)\n            x = round(x, 4)\n            y = ((pos[1] - self.dot_centre[1]) / float(self.dot_radius)) * -1\n            y = round(y, 4)\n            message = \"{},0,0,{},{}\\n\".format(op, x, y)\n            if op == 2:\n                if x != self.last_x or y != self.last_y:\n                    self._send_message(message)\n            else:\n                self._send_message(message)\n            self.last_x = x\n            self.last_y = y\n        else:\n            self.draw_error(\"Blue Dot not connected\")\n\n    def _send_protocol_version(self):\n        if self.bt_client.connected:\n            self._send_message(\"3,{},{}\\n\".format(PROTOCOL_VERSION, CLIENT_NAME))\n            \n    def _send_message(self, message):\n        try:\n            self.bt_client.send(message)\n        except:\n            e = str(sys.exc_info()[1])\n            self.draw_error(e)\n\n    def _data_received(self, data):\n        # add the data received to the buffer\n        self._data_buffer += data\n\n        # get any full commands ended by \\n\n        last_command = self._data_buffer.rfind(\"\\n\")\n        if last_command != -1:\n            commands = self._data_buffer[:last_command].split(\"\\n\")\n            # remove the processed commands from the buffer\n            self._data_buffer = self._data_buffer[last_command + 1:]\n            self._process_commands(commands)\n\n    def _process_commands(self, commands):\n        for command in commands:\n            params = command.split(\",\")\n\n            invalid_command = False\n            if len(params) == 7:\n\n                if params[0] == \"4\":\n                    # currently the python blue dot client only supports 1 button\n                    if params[5] != \"1\" or params[6] != \"1\":\n                        print(\"Error - The BlueDot python client only supports a single button.\")\n\n                    self._colour = parse_color(params[1])\n                    self._square = True if params[2] == \"1\" else False\n                    self._border = True if params[3] == \"1\" else False\n                    self._visible = True if params[4] == \"1\" else False\n\n                    self._draw_dot()\n\n                elif params[0] == \"5\":\n                    if params[5] == \"0\" and params[6] == \"0\":\n                        \n                        self._colour = parse_color(params[1])\n                        self._square = True if params[2] == \"1\" else False\n                        self._border = True if params[3] == \"1\" else False\n                        self._visible = True if params[4] == \"1\" else False                    \n                    \n                        self._draw_dot()\n\n            else:\n                invalid_command = True\n\n            if invalid_command:\n                print(\"Error - Invalid message received '{}'\".format(command))\n                \n    def run(self):\n\n        self._connect()\n        self._send_protocol_version()\n    \n        clock = pygame.time.Clock()\n        pygame.event.clear()\n\n        self._pressed = False\n        running = True\n\n        while running:\n            clock.tick(50)\n\n            ev = pygame.event.get()\n\n            for event in ev:\n\n                # handle mouse\n                if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP or (event.type == pygame.MOUSEMOTION and self._pressed):\n                    pos = pygame.mouse.get_pos()\n\n                    #circle clicked?\n                    if self.dot_rect.collidepoint(pos):\n\n                        if event.type == pygame.MOUSEBUTTONDOWN:\n                            self._pressed = True\n                            self._draw_dot()\n                            self._process(1, pos)\n\n                        elif event.type == pygame.MOUSEBUTTONUP:\n                            self._pressed = False\n                            self._draw_dot()\n                            self._process(0, pos)\n\n                        elif event.type == pygame.MOUSEMOTION:\n                            self._process(2, pos)\n\n                    #close clicked?\n                    if self.close_rect.collidepoint(pos):\n                        running = False\n\n                if event.type == pygame.KEYDOWN:\n                    if event.key == pygame.K_ESCAPE:\n                        running = False\n\n                if event.type == pygame.QUIT:\n                    running = False\n\n            pygame.display.update()\n\n        self.bt_client.disconnect()\n\n    def _connect(self):\n        self.bt_client = BluetoothClient(self.server, self._data_received, port = self.port, device = self.device, auto_connect = False)\n        try:\n            self.bt_client.connect()\n        except:\n            e = str(sys.exc_info()[1])\n            self.draw_error(e)\n        \ndef main():\n    #read command line options\n    parser = ArgumentParser(description=\"Blue Dot Python App\")\n    parser.add_argument(\"--device\", help=\"The name of the bluetooth device to use (default is hci0)\")\n    parser.add_argument(\"--server\", help=\"The name or mac address of the bluedot server\")\n    parser.add_argument(\"--port\", help=\"The port number to use when connecting (default is 1)\", type=int)\n    parser.add_argument(\"--fullscreen\", help=\"Fullscreen app\", action=\"store_true\")\n    parser.add_argument(\"--width\", type=int, help=\"A custom screen width (default is {})\".format(DEFAULTSIZE[0]))\n    parser.add_argument(\"--height\", type=int, help=\"A customer screen height (default is {})\".format(DEFAULTSIZE[1]))\n    args = parser.parse_args()\n\n    #start the blue dot client\n    blue_dot_client = BlueDotClient(args.device, args.server, args.port, args.fullscreen, args.width, args.height)\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "bluedot/btcomm.py",
    "content": "from __future__ import unicode_literals\n\nimport socket\nimport sys\nimport errno\n\nfrom .utils import (\n    register_spp,\n    get_mac,\n    get_adapter_powered_status,\n    get_adapter_discoverable_status,\n    get_adapter_pairable_status,\n    get_paired_devices,\n    device_pairable,\n    device_discoverable,\n    device_powered,\n)\n\nfrom .threads import WrapThread\n\nBLUETOOTH_TIMEOUT = 0.01\n\n\nclass BluetoothAdapter:\n    \"\"\"\n    Represents and allows interaction with a Bluetooth Adapter.\n\n    The following example will get the Bluetooth adapter, print its powered status\n    and any paired devices::\n\n        a = BluetoothAdapter()\n        print(\"Powered = {}\".format(a.powered))\n        print(a.paired_devices)\n\n    :param str device:\n        The Bluetooth device to be used, the default is \"hci0\", if your device\n        only has 1 Bluetooth adapter this shouldn't need to be changed.\n    \"\"\"\n    def __init__(self, device = \"hci0\"):\n        self._device = device\n        self._address = get_mac(self._device)\n        self._pairing_thread = None\n\n    @property\n    def device(self):\n        \"\"\"\n        The Bluetooth device name. This defaults to \"hci0\".\n        \"\"\"\n        return self._device\n\n    @property\n    def address(self):\n        \"\"\"\n        The `MAC address`_ of the Bluetooth adapter.\n\n        .. _MAC address: https://en.wikipedia.org/wiki/MAC_address\n        \"\"\"\n        return self._address\n\n    @property\n    def powered(self):\n        \"\"\"\n        Set to ``True`` to power on the Bluetooth adapter.\n\n        Depending on how Bluetooth has been powered down, you may need to use\n        :command:`rfkill` to unblock Bluetooth to give permission to bluez to power on Bluetooth::\n\n            sudo rfkill unblock bluetooth\n        \"\"\"\n        return get_adapter_powered_status(self._device)\n\n    @powered.setter\n    def powered(self, value):\n        device_powered(self._device, value)\n\n    @property\n    def discoverable(self):\n        \"\"\"\n        Set to ``True`` to make the Bluetooth adapter discoverable.\n        \"\"\"\n        return get_adapter_discoverable_status(self._device)\n\n    @discoverable.setter\n    def discoverable(self, value):\n        device_discoverable(self._device, value)\n\n    @property\n    def pairable(self):\n        \"\"\"\n        Set to ``True`` to make the Bluetooth adapter pairable.\n        \"\"\"\n        return get_adapter_pairable_status(self._device)\n\n    @pairable.setter\n    def pairable(self, value):\n        device_pairable(self._device, value)\n\n    @property\n    def paired_devices(self):\n        \"\"\"\n        Returns a sequence of devices paired with this adapater\n        :code:`[(mac_address, name), (mac_address, name), ...]`::\n\n            a = BluetoothAdapter()\n            devices = a.paired_devices\n            for d in devices:\n                device_address = d[0]\n                device_name = d[1]\n        \"\"\"\n        return get_paired_devices(self._device)\n\n    def allow_pairing(self, timeout = 60):\n        \"\"\"\n        Put the adapter into discoverable and pairable mode.\n\n        :param int timeout:\n            The time in seconds the adapter will remain pairable. If set to ``None``\n            the device will be discoverable and pairable indefinetly.\n        \"\"\"\n        #if a pairing thread is already running, stop it and restart\n        if self._pairing_thread:\n            if self._pairing_thread.is_alive:\n                self._pairing_thread.stop()\n\n        #make the adapter pairable\n        self.pairable = True\n        self.discoverable = True\n\n        if timeout != None:\n            #start the pairing thread\n            self._pairing_thread = WrapThread(target=self._expire_pairing, args=(timeout, ))\n            self._pairing_thread.start()\n\n    def _expire_pairing(self, timeout):\n        #wait till the timeout or the thread is stopped\n        self._pairing_thread.stopping.wait(timeout)\n        self.discoverable = False\n        self.pairable = False\n\n\nclass BluetoothServer:\n    \"\"\"\n    Creates a Bluetooth server which will allow connections and accept incoming\n    RFCOMM serial data.\n\n    When data is received by the server it is passed to a callback function\n    which must be specified at initiation.\n\n    The following example will create a Bluetooth server which will wait for a\n    connection and print any data it receives and send it back to the client::\n\n        from bluedot.btcomm import BluetoothServer\n        from signal import pause\n\n        def data_received(data):\n            print(data)\n            s.send(data)\n\n        s = BluetoothServer(data_received)\n        pause()\n\n    :param data_received_callback:\n        A function reference should be passed, this function will be called when\n        data is received by the server.  The function should accept a single parameter\n        which when called will hold the data received. Set to ``None`` if  received\n        data is not required.\n\n    :param bool auto_start:\n        If ``True`` (the default), the Bluetooth server will be automatically started\n        on initialisation, if ``False``, the method ``start`` will need to be called\n        before connections will be accepted.\n\n    :param str device:\n        The Bluetooth device the server should use, the default is \"hci0\", if\n        your device only has 1 Bluetooth adapter this shouldn't need to be changed.\n\n    :param int port:\n        The Bluetooth port the server should use, the default is 1.\n\n    :param str encoding:\n        The encoding standard to be used when sending and receiving byte data. The default is\n        \"utf-8\".  If set to ``None`` no encoding is done and byte data types should be used.\n\n    :param bool power_up_device:\n        If ``True``, the Bluetooth device will be powered up (if required) when the\n        server starts. The default is ``False``.\n\n        Depending on how Bluetooth has been powered down, you may need to use :command:`rfkill`\n        to unblock Bluetooth to give permission to bluez to power on Bluetooth::\n\n            sudo rfkill unblock bluetooth\n\n    :param when_client_connects:\n        A function reference which will be called when a client connects. If ``None``\n        (the default), no notification will be given when a client connects\n\n    :param when_client_disconnects:\n        A function reference which will be called when a client disconnects. If ``None``\n        (the default), no notification will be given when a client disconnects\n\n    \"\"\"\n    def __init__(self,\n        data_received_callback,\n        auto_start = True,\n        device = \"hci0\",\n        port = 1,\n        encoding = \"utf-8\",\n        power_up_device = False,\n        when_client_connects = None,\n        when_client_disconnects = None):\n\n        self._setup_adapter(device)\n\n        self._data_received_callback = data_received_callback\n        self._port = port\n        self._encoding = encoding\n        self._power_up_device = power_up_device\n        self._when_client_connects = when_client_connects\n        self._when_client_disconnects = when_client_disconnects\n\n        self._running = False\n        self._client_connected = False\n        self._server_sock = None\n        self._client_info = None\n        self._client_sock = None\n\n        self._conn_thread = None\n\n        if auto_start:\n            self.start()\n\n    @property\n    def device(self):\n        \"\"\"\n        The Bluetooth device the server is using. This defaults to \"hci0\".\n        \"\"\"\n        return self.adapter.device\n\n    @property\n    def adapter(self):\n        \"\"\"\n        A :class:`BluetoothAdapter` object which represents the Bluetooth device\n        the server is using.\n        \"\"\"\n        return self._adapter\n\n    @property\n    def port(self):\n        \"\"\"\n        The port the server is using. This defaults to 1.\n        \"\"\"\n        return self._port\n\n    @property\n    def encoding(self):\n        \"\"\"\n        The encoding standard the server is using. This defaults to \"utf-8\".\n        \"\"\"\n        return self._encoding\n\n    @property\n    def running(self):\n        \"\"\"\n        Returns a ``True`` if the server is running.\n        \"\"\"\n        return self._running\n\n    @property\n    def server_address(self):\n        \"\"\"\n        The `MAC address`_ of the device the server is using.\n\n        .. _MAC address: https://en.wikipedia.org/wiki/MAC_address\n        \"\"\"\n        return self.adapter.address\n\n    @property\n    def client_address(self):\n        \"\"\"\n        The `MAC address`_ of the client connected to the server. Returns\n        ``None`` if no client is connected.\n\n        .. _MAC address: https://en.wikipedia.org/wiki/MAC_address\n        \"\"\"\n        if self._client_info:\n            return self._client_info[0]\n        else:\n            return None\n\n    @property\n    def client_connected(self):\n        \"\"\"\n        Returns ``True`` if a client is connected.\n        \"\"\"\n        return self._client_connected\n\n    @property\n    def data_received_callback(self):\n        \"\"\"\n        Sets or returns the function which is called when data is received by the server.\n\n        The function should accept a single parameter which when called will hold\n        the data received. Set to ``None`` if received data is not required.\n        \"\"\"\n        return self._data_received_callback\n\n    @data_received_callback.setter\n    def data_received_callback(self, value):\n        self._data_received_callback = value\n\n    @property\n    def when_client_connects(self):\n        \"\"\"\n        Sets or returns the function which is called when a client connects.\n        \"\"\"\n        return self._when_client_connects\n\n    @when_client_connects.setter\n    def when_client_connects(self, value):\n        self._when_client_connects = value\n\n    @property\n    def when_client_disconnects(self):\n        \"\"\"\n        Sets or returns the function which is called when a client disconnects.\n        \"\"\"\n        return self._when_client_disconnects\n\n    @when_client_disconnects.setter\n    def when_client_disconnects(self, value):\n        self._when_client_disconnects = value\n\n    def start(self):\n        \"\"\"\n        Starts the Bluetooth server if its not already running. The server needs to be started before\n        connections can be made.\n        \"\"\"\n        if not self._running:\n\n            if self._power_up_device:\n                self.adapter.powered = True\n\n            if not self.adapter.powered:\n                raise Exception(\"Bluetooth device {} is turned off\".format(self.adapter.device))\n\n            #register the serial port profile with Bluetooth\n            register_spp(self._port)\n\n            #start Bluetooth server\n            #open the Bluetooth socket\n            self._server_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)\n            self._server_sock.settimeout(BLUETOOTH_TIMEOUT)\n            try:\n                self._server_sock.bind((self.server_address, self.port))\n            except (socket.error, OSError) as e:\n                if e.errno == errno.EADDRINUSE:\n                    print(\"Bluetooth address {} is already in use - is the server already running?\".format(self.server_address))\n                raise e\n            self._server_sock.listen(1)\n\n            #wait for client connection\n            self._conn_thread = WrapThread(target=self._wait_for_connection)\n            self._conn_thread.start()\n\n            self._running = True\n\n    def stop(self):\n        \"\"\"\n        Stops the Bluetooth server if its running.\n        \"\"\"\n        if self._running:\n            if self._conn_thread:\n                self._conn_thread.stop()\n                self._conn_thread = None\n\n    def send(self, data):\n        \"\"\"\n        Send data to a connected Bluetooth client\n\n        :param str data:\n            The data to be sent.\n        \"\"\"\n        # print(data)\n        if self._client_connected:\n            if self._encoding is not None:\n                data = data.encode(self._encoding)\n            try:\n                self._send_data(data)\n            except IOError as e:\n                self._handle_bt_error(e)\n\n    def _send_data(self, data):\n        \"\"\"\n        Send raw data to the client.\n        \n        :param bytes data:\n            The data to be sent.\n        \"\"\"\n        self._client_sock.sendall(data)\n\n    def disconnect_client(self):\n        \"\"\"\n        Disconnects the client if connected. Returns `True` if a client was disconnected.\n        \"\"\"\n        if self._client_connected:    \n            self._client_connected = False\n            \n            # call the callback\n            if self.when_client_disconnects:\n                WrapThread(target=self.when_client_disconnects).start()\n            \n            return True\n        \n        else:\n            return False\n\n    def _setup_adapter(self, device):\n        self._adapter = BluetoothAdapter(device)\n    \n    def _wait_for_connection(self):\n        #keep going until the server is stopped\n        while not self._conn_thread.stopping.is_set():\n            #wait for connection\n            self._client_connected = False\n            while not self._conn_thread.stopping.is_set():\n                try:\n                    # accept() will timeout after BLUETOOTH_TIMEOUT seconds\n                    self._client_sock, self._client_info = self._server_sock.accept()\n                    self._client_connected = True\n                    break\n                except socket.timeout as e:\n                    self._handle_bt_error(e)\n\n            #did a client connect?\n            if self._client_connected:\n                #call the call back\n                if self.when_client_connects:\n                    WrapThread(target=self.when_client_connects).start()\n\n                #read data\n                self._read()\n\n        #server has been stopped\n        self._server_sock.close()\n        self._server_sock = None\n        self._running = False\n\n    def _read(self):\n        #read until the server is stopped or the client disconnects\n        while self._client_connected:\n            #read data from Bluetooth socket\n            try:\n                data = self._client_sock.recv(1024, socket.MSG_DONTWAIT)\n            except IOError as e:\n                self._handle_bt_error(e)\n                data = b\"\"\n            if data:\n                if self._data_received_callback:\n                    if self._encoding:\n                        data = data.decode(self._encoding)\n                    self.data_received_callback(data)\n            if self._conn_thread.stopping.wait(BLUETOOTH_TIMEOUT):\n                break\n\n        #close the client socket\n        self._client_sock.close()\n        self._client_sock = None\n        self._client_info = None\n        self._client_connected = False\n\n    def _handle_bt_error(self, bt_error):\n        assert isinstance(bt_error, IOError)\n        #'timed out' is caused by the wait_for_connection loop\n        if isinstance(bt_error, socket.timeout):\n            pass\n        #'resource unavailable' is when data cannot be read because there is nothing in the buffer\n        elif bt_error.errno == errno.EAGAIN:\n            pass\n        #'connection reset' is caused when the client disconnects\n        elif bt_error.errno == errno.ECONNRESET:\n            self.disconnect_client()\n        #'conection timeout' is caused when the server can no longer connect to read from the client\n        # (perhaps the client has gone out of range)\n        elif bt_error.errno == errno.ETIMEDOUT:\n            self.disconnect_client()\n        else:\n            raise bt_error\n\n\nclass BluetoothClient():\n    \"\"\"\n    Creates a Bluetooth client which can send data to a server using RFCOMM Serial Data.\n\n    The following example will create a Bluetooth client which will connect to a paired\n    device called \"raspberrypi\", send \"helloworld\" and print any data is receives::\n\n        from bluedot.btcomm import BluetoothClient\n        from signal import pause\n\n        def data_received(data):\n            print(data)\n\n        c = BluetoothClient(\"raspberrypi\", data_received)\n        c.send(\"helloworld\")\n\n        pause()\n\n    :param str server:\n        The server name (\"raspberrypi\") or server MAC address\n        (\"11:11:11:11:11:11\") to connect to. The server must be a paired device.\n\n    :param data_received_callback:\n        A function reference should be passed, this function will be called when\n        data is received by the client.  The function should accept a single parameter\n        which when called will hold the data received. Set to ``None`` if data\n        received is not required.\n\n    :param int port:\n        The Bluetooth port the client should use, the default is 1.\n\n    :param str device:\n        The Bluetooth device to be used, the default is \"hci0\", if your device\n        only has 1 Bluetooth adapter this shouldn't need to be changed.\n\n    :param str encoding:\n        The encoding standard to be used when sending and receiving byte data. The default is\n        \"utf-8\".  If set to ``None`` no encoding is done and byte data types should be used.\n\n    :param bool power_up_device:\n        If ``True``, the Bluetooth device will be powered up (if required) when the\n        server starts. The default is ``False``.\n\n        Depending on how Bluetooth has been powered down, you may need to use :command:`rfkill`\n        to unblock Bluetooth to give permission to Bluez to power on Bluetooth::\n\n            sudo rfkill unblock bluetooth\n\n    :param bool auto_connect:\n        If ``True`` (the default), the Bluetooth client will automatically try\n        to connect to the server at initialisation, if ``False``, the\n        :meth:`connect` method will need to be called.\n\n    \"\"\"\n    def __init__(self,\n        server,\n        data_received_callback,\n        port = 1,\n        device = \"hci0\",\n        encoding = \"utf-8\",\n        power_up_device = False,\n        auto_connect = True):\n\n        self._server = server\n        self._data_received_callback = data_received_callback\n        self._port = port\n        self._power_up_device = power_up_device\n        self._encoding = encoding\n\n        self._setup_adapter(device)\n\n        self._connected = False\n        self._client_sock = None\n\n        self._conn_thread = None\n\n        if auto_connect:\n            self.connect()\n\n    @property\n    def device(self):\n        \"\"\"\n        The Bluetooth device the client is using. This defaults to \"hci0\".\n        \"\"\"\n        return self.adapter.device\n\n    @property\n    def server(self):\n        \"\"\"\n        The server name (\"raspberrypi\") or server `MAC address`_\n        (\"11:11:11:11:11:11\") to connect to.\n\n        .. _MAC address: https://en.wikipedia.org/wiki/MAC_address\n        \"\"\"\n        return self._server\n\n    @property\n    def port(self):\n        \"\"\"\n        The port the client is using. This defaults to 1.\n        \"\"\"\n        return self._port\n\n    @property\n    def adapter(self):\n        \"\"\"\n        A :class:`BluetoothAdapter` object which represents the Bluetooth\n        device the client is using.\n        \"\"\"\n        return self._adapter\n\n    @property\n    def encoding(self):\n        \"\"\"\n        The encoding standard the client is using. The default is \"utf-8\".\n        \"\"\"\n        return self._encoding\n\n    @property\n    def client_address(self):\n        \"\"\"\n        The MAC address of the device being used.\n        \"\"\"\n        return self.adapter.address\n\n    @property\n    def connected(self):\n        \"\"\"\n        Returns ``True`` when connected.\n        \"\"\"\n        return self._connected\n\n    @property\n    def data_received_callback(self):\n        \"\"\"\n        Sets or returns the function which is called when data is received by the client.\n\n        The function should accept a single parameter which when called will hold\n        the data received. Set to ``None`` if data received is not required.\n        \"\"\"\n        return self._data_received_callback\n\n    @data_received_callback.setter\n    def data_received_callback(self, value):\n        self._data_received_callback = value\n\n    def connect(self):\n        \"\"\"\n        Connect to a Bluetooth server.\n        \"\"\"\n        if not self._connected:\n\n            if self._power_up_device:\n                self.adapter.powered = True\n\n            if not self.adapter.powered:\n                raise Exception(\"Bluetooth device {} is turned off\".format(self.adapter.device))\n\n            #try and find the server name or MAC address in the paired devices list\n            server_mac = None\n            for device in self.adapter.paired_devices:\n                if self._server == device[0] or self._server == device[1]:\n                    server_mac = device[0]\n                    break\n            if server_mac == None:\n                raise Exception(\"Server {} not found in paired devices\".format(self._server))\n\n            #create a socket\n            self._client_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)\n            self._client_sock.bind((self.adapter.address, self._port))\n            self._client_sock.connect((server_mac, self._port))\n\n            self._connected = True\n\n            self._conn_thread = WrapThread(target=self._read)\n            self._conn_thread.start()\n\n    def disconnect(self):\n        \"\"\"\n        Disconnect from a Bluetooth server.\n        \"\"\"\n        if self._connected:\n\n            #stop the connection thread\n            if self._conn_thread:\n                self._conn_thread.stop()\n                self._conn_thread = None\n\n            #close the socket\n            try:\n                self._client_sock.close()\n            finally:\n                self._client_sock = None\n                self._connected = False\n\n    def send(self, data):\n        \"\"\"\n        Send data to a Bluetooth server.\n\n        :param str data:\n            The data to be sent.\n        \"\"\"\n        if self._connected:\n            if self._encoding is not None:\n                data = data.encode(self._encoding)\n            try:\n                self._send_data(data)\n            except IOError as e:\n                self._handle_bt_error(e)\n\n    def _send_data(self, data):\n        \"\"\"\n        Send raw data to the client.\n        \n        :param bytes data:\n            The data to be sent.\n        \"\"\"\n        self._client_sock.sendall(data)\n\n    def _read(self):\n        #read until the client is stopped or the client disconnects\n        while self._connected:\n            #read data from Bluetooth socket\n            try:\n                data = self._client_sock.recv(1024, socket.MSG_DONTWAIT)\n            except IOError as e:\n                self._handle_bt_error(e)\n                data = b\"\"\n            if data:\n                #print(\"received [%s]\" % data)\n                if self._data_received_callback:\n                    if self._encoding:\n                        data = data.decode(self._encoding)\n                    self.data_received_callback(data)\n            if self._conn_thread.stopping.wait(BLUETOOTH_TIMEOUT):\n                break\n\n    def _setup_adapter(self, device):\n        self._adapter = BluetoothAdapter(device)\n    \n    def _handle_bt_error(self, bt_error):\n        assert isinstance(bt_error, IOError)\n        #'resource unavailable' is when data cannot be read because there is nothing in the buffer\n        if bt_error.errno == errno.EAGAIN:\n            pass\n        #'connection reset' is caused when the client disconnects\n        elif bt_error.errno == errno.ECONNRESET:\n            self._connected = False\n        #'conection timeout' is caused when the server can no longer connect to read from the client\n        # (perhaps the client has gone out of range)\n        elif bt_error.errno == errno.ETIMEDOUT:\n            self._connected = False\n        else:\n            raise bt_error\n"
  },
  {
    "path": "bluedot/colors.py",
    "content": "# color codes obtained from https://www.webucator.com/blog/2015/03/python-color-constants-module/\r\n\r\nclass Color:\r\n    \"\"\"\r\n    Represents a color within bluedot. Used to change the color of the dot.\r\n\r\n    Color objects are immutable.\r\n\r\n    :param int red:\r\n        The red value of the color `0 - 255`. Default is `255`.\r\n\r\n    :param int green:\r\n        The green value of the color `0 - 255`. Default is `255`.\r\n\r\n    :param int green:\r\n        The blue value of the color `0 - 255`. Default is `255`.\r\n\r\n    :param int green:\r\n        The alpha value of the color `0 - 255`. `0` is transparent. Default \r\n        is `255`.\r\n    \"\"\"\r\n    def __init__(self, red = 255, green = 255, blue = 255, alpha = 255):\r\n        self._red = red\r\n        self._green = green\r\n        self._blue = blue\r\n        self._alpha = alpha\r\n\r\n    @property\r\n    def red(self):\r\n        \"\"\"\r\n        Returns the red value of the color.\r\n        \"\"\"\r\n        return self._red\r\n\r\n    @property\r\n    def green(self):\r\n        \"\"\"\r\n        Returns the green value of the color.\r\n        \"\"\"\r\n        return self._green\r\n\r\n    @property\r\n    def blue(self):\r\n        \"\"\"\r\n        Returns the blue value of the color.\r\n        \"\"\"\r\n        return self._blue\r\n\r\n    @property\r\n    def alpha(self):\r\n        \"\"\"\r\n        Returns the alpha value of the color.\r\n        \"\"\"\r\n        return self._alpha\r\n\r\n    @property\r\n    def rgb(self):\r\n        \"\"\"\r\n        Returns a tuple of `(red, green, blue)` values.\r\n        \"\"\"\r\n        return (self._red, self._green, self._blue)\r\n\r\n    @property\r\n    def rgba(self):\r\n        \"\"\"\r\n        Returns a tuple of `(red, green, blue, alpha)` values.\r\n        \"\"\"\r\n        return (self._red, self._green, self._blue, self._alpha)\r\n\r\n    @property\r\n    def str_rgb(self):\r\n        \"\"\"\r\n        Returns a string of red, green, blue hex values in the format\r\n        `#rrggbb`.\r\n        \"\"\"\r\n        return '#%02x%02x%02x' % (self._red, self._green, self._blue)\r\n\r\n    @property\r\n    def str_rgba(self):\r\n        \"\"\"\r\n        Returns a string of red, green, blue, alpha hex values in the format\r\n        `#rrggbbaa`.\r\n        \"\"\"\r\n        return '#%02x%02x%02x%02x' % (self._red, self._green, self._blue, self._alpha)\r\n\r\n    @property\r\n    def str_argb(self):\r\n        \"\"\"\r\n        Returns a string of alpha, red, green, blue hex values in the format\r\n        `#aarrggbb`.\r\n        \"\"\"\r\n        return '#%02x%02x%02x%02x' % (self._alpha, self._red, self._green, self._blue)\r\n\r\n    def get_adjusted_color(self, factor):\r\n        \"\"\"\r\n        Returns a new Color object based on this Color adjusted by a factor\r\n\r\n        :param float factor:\r\n            The value to adjust this color by. \r\n        \"\"\"\r\n        return Color(self.red * factor, self.green * factor, self.blue * factor)\r\n\r\n    def __eq__(self, other):\r\n        other = parse_color(other)\r\n        return self._red == other._red and self._green == other._green and self._blue == other._blue and self._alpha == other._alpha\r\n\r\n    def __str__(self):\r\n        return self.str_rgba\r\n\r\nALICEBLUE = Color(240, 248, 255)\r\nANTIQUEWHITE = Color(250, 235, 215)\r\nANTIQUEWHITE1 = Color(255, 239, 219)\r\nANTIQUEWHITE2 = Color(238, 223, 204)\r\nANTIQUEWHITE3 = Color(205, 192, 176)\r\nANTIQUEWHITE4 = Color(139, 131, 120)\r\nAQUA = Color(0, 255, 255)\r\nAQUAMARINE1 = Color(127, 255, 212)\r\nAQUAMARINE2 = Color(118, 238, 198)\r\nAQUAMARINE3 = Color(102, 205, 170)\r\nAQUAMARINE4 = Color(69, 139, 116)\r\nAZURE1 = Color(240, 255, 255)\r\nAZURE2 = Color(224, 238, 238)\r\nAZURE3 = Color(193, 205, 205)\r\nAZURE4 = Color(131, 139, 139)\r\nBANANA = Color(227, 207, 87)\r\nBEIGE = Color(245, 245, 220)\r\nBISQUE1 = Color(255, 228, 196)\r\nBISQUE2 = Color(238, 213, 183)\r\nBISQUE3 = Color(205, 183, 158)\r\nBISQUE4 = Color(139, 125, 107)\r\nBLACK = Color(0, 0, 0)\r\nBLANCHEDALMOND = Color(255, 235, 205)\r\nBLUE = Color(0, 0, 255)\r\nBLUE2 = Color(0, 0, 238)\r\nBLUE3 = Color(0, 0, 205)\r\nBLUE4 = Color(0, 0, 139)\r\nBLUEVIOLET = Color(138, 43, 226)\r\nBRICK = Color(156, 102, 31)\r\nBROWN = Color(165, 42, 42)\r\nBROWN1 = Color(255, 64, 64)\r\nBROWN2 = Color(238, 59, 59)\r\nBROWN3 = Color(205, 51, 51)\r\nBROWN4 = Color(139, 35, 35)\r\nBURLYWOOD = Color(222, 184, 135)\r\nBURLYWOOD1 = Color(255, 211, 155)\r\nBURLYWOOD2 = Color(238, 197, 145)\r\nBURLYWOOD3 = Color(205, 170, 125)\r\nBURLYWOOD4 = Color(139, 115, 85)\r\nBURNTSIENNA = Color(138, 54, 15)\r\nBURNTUMBER = Color(138, 51, 36)\r\nCADETBLUE = Color(95, 158, 160)\r\nCADETBLUE1 = Color(152, 245, 255)\r\nCADETBLUE2 = Color(142, 229, 238)\r\nCADETBLUE3 = Color(122, 197, 205)\r\nCADETBLUE4 = Color(83, 134, 139)\r\nCADMIUMORANGE = Color(255, 97, 3)\r\nCADMIUMYELLOW = Color(255, 153, 18)\r\nCARROT = Color(237, 145, 33)\r\nCHARTREUSE1 = Color(127, 255, 0)\r\nCHARTREUSE2 = Color(118, 238, 0)\r\nCHARTREUSE3 = Color(102, 205, 0)\r\nCHARTREUSE4 = Color(69, 139, 0)\r\nCHOCOLATE = Color(210, 105, 30)\r\nCHOCOLATE1 = Color(255, 127, 36)\r\nCHOCOLATE2 = Color(238, 118, 33)\r\nCHOCOLATE3 = Color(205, 102, 29)\r\nCHOCOLATE4 = Color(139, 69, 19)\r\nCOBALT = Color(61, 89, 171)\r\nCOBALTGREEN = Color(61, 145, 64)\r\nCOLDGREY = Color(128, 138, 135)\r\nCORAL = Color(255, 127, 80)\r\nCORAL1 = Color(255, 114, 86)\r\nCORAL2 = Color(238, 106, 80)\r\nCORAL3 = Color(205, 91, 69)\r\nCORAL4 = Color(139, 62, 47)\r\nCORNFLOWERBLUE = Color(100, 149, 237)\r\nCORNSILK1 = Color(255, 248, 220)\r\nCORNSILK2 = Color(238, 232, 205)\r\nCORNSILK3 = Color(205, 200, 177)\r\nCORNSILK4 = Color(139, 136, 120)\r\nCRIMSON = Color(220, 20, 60)\r\nCYAN1 = Color(0, 238, 238)\r\nCYAN2 = Color(0, 205, 205)\r\nCYAN3 = Color(0, 139, 139)\r\nDARKGOLDENROD = Color(184, 134, 11)\r\nDARKGOLDENROD1 = Color(255, 185, 15)\r\nDARKGOLDENROD2 = Color(238, 173, 14)\r\nDARKGOLDENROD3 = Color(205, 149, 12)\r\nDARKGOLDENROD4 = Color(139, 101, 8)\r\nDARKGRAY = Color(169, 169, 169)\r\nDARKGREEN = Color(0, 100, 0)\r\nDARKKHAKI = Color(189, 183, 107)\r\nDARKOLIVEGREEN = Color(85, 107, 47)\r\nDARKOLIVEGREEN1 = Color(202, 255, 112)\r\nDARKOLIVEGREEN2 = Color(188, 238, 104)\r\nDARKOLIVEGREEN3 = Color(162, 205, 90)\r\nDARKOLIVEGREEN4 = Color(110, 139, 61)\r\nDARKORANGE = Color(255, 140, 0)\r\nDARKORANGE1 = Color(255, 127, 0)\r\nDARKORANGE2 = Color(238, 118, 0)\r\nDARKORANGE3 = Color(205, 102, 0)\r\nDARKORANGE4 = Color(139, 69, 0)\r\nDARKORCHID = Color(153, 50, 204)\r\nDARKORCHID1 = Color(191, 62, 255)\r\nDARKORCHID2 = Color(178, 58, 238)\r\nDARKORCHID3 = Color(154, 50, 205)\r\nDARKORCHID4 = Color(104, 34, 139)\r\nDARKSALMON = Color(233, 150, 122)\r\nDARKSEAGREEN = Color(143, 188, 143)\r\nDARKSEAGREEN1 = Color(193, 255, 193)\r\nDARKSEAGREEN2 = Color(180, 238, 180)\r\nDARKSEAGREEN3 = Color(155, 205, 155)\r\nDARKSEAGREEN4 = Color(105, 139, 105)\r\nDARKSLATEBLUE = Color(72, 61, 139)\r\nDARKSLATEGRAY = Color(47, 79, 79)\r\nDARKSLATEGRAY1 = Color(151, 255, 255)\r\nDARKSLATEGRAY2 = Color(141, 238, 238)\r\nDARKSLATEGRAY3 = Color(121, 205, 205)\r\nDARKSLATEGRAY4 = Color(82, 139, 139)\r\nDARKTURQUOISE = Color(0, 206, 209)\r\nDARKVIOLET = Color(148, 0, 211)\r\nDEEPPINK1 = Color(255, 20, 147)\r\nDEEPPINK2 = Color(238, 18, 137)\r\nDEEPPINK3 = Color(205, 16, 118)\r\nDEEPPINK4 = Color(139, 10, 80)\r\nDEEPSKYBLUE1 = Color(0, 191, 255)\r\nDEEPSKYBLUE2 = Color(0, 178, 238)\r\nDEEPSKYBLUE3 = Color(0, 154, 205)\r\nDEEPSKYBLUE4 = Color(0, 104, 139)\r\nDIMGRAY = Color(105, 105, 105)\r\nDODGERBLUE1 = Color(30, 144, 255)\r\nDODGERBLUE2 = Color(28, 134, 238)\r\nDODGERBLUE3 = Color(24, 116, 205)\r\nDODGERBLUE4 = Color(16, 78, 139)\r\nEGGSHELL = Color(252, 230, 201)\r\nEMERALDGREEN = Color(0, 201, 87)\r\nFIREBRICK = Color(178, 34, 34)\r\nFIREBRICK1 = Color(255, 48, 48)\r\nFIREBRICK2 = Color(238, 44, 44)\r\nFIREBRICK3 = Color(205, 38, 38)\r\nFIREBRICK4 = Color(139, 26, 26)\r\nFLESH = Color(255, 125, 64)\r\nFLORALWHITE = Color(255, 250, 240)\r\nFORESTGREEN = Color(34, 139, 34)\r\nGAINSBORO = Color(220, 220, 220)\r\nGHOSTWHITE = Color(248, 248, 255)\r\nGOLD1 = Color(255, 215, 0)\r\nGOLD2 = Color(238, 201, 0)\r\nGOLD3 = Color(205, 173, 0)\r\nGOLD4 = Color(139, 117, 0)\r\nGOLDENROD = Color(218, 165, 32)\r\nGOLDENROD1 = Color(255, 193, 37)\r\nGOLDENROD2 = Color(238, 180, 34)\r\nGOLDENROD3 = Color(205, 155, 29)\r\nGOLDENROD4 = Color(139, 105, 20)\r\nGRAY = Color(128, 128, 128)\r\nGRAY1 = Color(3, 3, 3)\r\nGRAY10 = Color(26, 26, 26)\r\nGRAY11 = Color(28, 28, 28)\r\nGRAY12 = Color(31, 31, 31)\r\nGRAY13 = Color(33, 33, 33)\r\nGRAY14 = Color(36, 36, 36)\r\nGRAY15 = Color(38, 38, 38)\r\nGRAY16 = Color(41, 41, 41)\r\nGRAY17 = Color(43, 43, 43)\r\nGRAY18 = Color(46, 46, 46)\r\nGRAY19 = Color(48, 48, 48)\r\nGRAY2 = Color(5, 5, 5)\r\nGRAY20 = Color(51, 51, 51)\r\nGRAY21 = Color(54, 54, 54)\r\nGRAY22 = Color(56, 56, 56)\r\nGRAY23 = Color(59, 59, 59)\r\nGRAY24 = Color(61, 61, 61)\r\nGRAY25 = Color(64, 64, 64)\r\nGRAY26 = Color(66, 66, 66)\r\nGRAY27 = Color(69, 69, 69)\r\nGRAY28 = Color(71, 71, 71)\r\nGRAY29 = Color(74, 74, 74)\r\nGRAY3 = Color(8, 8, 8)\r\nGRAY30 = Color(77, 77, 77)\r\nGRAY31 = Color(79, 79, 79)\r\nGRAY32 = Color(82, 82, 82)\r\nGRAY33 = Color(84, 84, 84)\r\nGRAY34 = Color(87, 87, 87)\r\nGRAY35 = Color(89, 89, 89)\r\nGRAY36 = Color(92, 92, 92)\r\nGRAY37 = Color(94, 94, 94)\r\nGRAY38 = Color(97, 97, 97)\r\nGRAY39 = Color(99, 99, 99)\r\nGRAY4 = Color(10, 10, 10)\r\nGRAY40 = Color(102, 102, 102)\r\nGRAY42 = Color(107, 107, 107)\r\nGRAY43 = Color(110, 110, 110)\r\nGRAY44 = Color(112, 112, 112)\r\nGRAY45 = Color(115, 115, 115)\r\nGRAY46 = Color(117, 117, 117)\r\nGRAY47 = Color(120, 120, 120)\r\nGRAY48 = Color(122, 122, 122)\r\nGRAY49 = Color(125, 125, 125)\r\nGRAY5 = Color(13, 13, 13)\r\nGRAY50 = Color(127, 127, 127)\r\nGRAY51 = Color(130, 130, 130)\r\nGRAY52 = Color(133, 133, 133)\r\nGRAY53 = Color(135, 135, 135)\r\nGRAY54 = Color(138, 138, 138)\r\nGRAY55 = Color(140, 140, 140)\r\nGRAY56 = Color(143, 143, 143)\r\nGRAY57 = Color(145, 145, 145)\r\nGRAY58 = Color(148, 148, 148)\r\nGRAY59 = Color(150, 150, 150)\r\nGRAY6 = Color(15, 15, 15)\r\nGRAY60 = Color(153, 153, 153)\r\nGRAY61 = Color(156, 156, 156)\r\nGRAY62 = Color(158, 158, 158)\r\nGRAY63 = Color(161, 161, 161)\r\nGRAY64 = Color(163, 163, 163)\r\nGRAY65 = Color(166, 166, 166)\r\nGRAY66 = Color(168, 168, 168)\r\nGRAY67 = Color(171, 171, 171)\r\nGRAY68 = Color(173, 173, 173)\r\nGRAY69 = Color(176, 176, 176)\r\nGRAY7 = Color(18, 18, 18)\r\nGRAY70 = Color(179, 179, 179)\r\nGRAY71 = Color(181, 181, 181)\r\nGRAY72 = Color(184, 184, 184)\r\nGRAY73 = Color(186, 186, 186)\r\nGRAY74 = Color(189, 189, 189)\r\nGRAY75 = Color(191, 191, 191)\r\nGRAY76 = Color(194, 194, 194)\r\nGRAY77 = Color(196, 196, 196)\r\nGRAY78 = Color(199, 199, 199)\r\nGRAY79 = Color(201, 201, 201)\r\nGRAY8 = Color(20, 20, 20)\r\nGRAY80 = Color(204, 204, 204)\r\nGRAY81 = Color(207, 207, 207)\r\nGRAY82 = Color(209, 209, 209)\r\nGRAY83 = Color(212, 212, 212)\r\nGRAY84 = Color(214, 214, 214)\r\nGRAY85 = Color(217, 217, 217)\r\nGRAY86 = Color(219, 219, 219)\r\nGRAY87 = Color(222, 222, 222)\r\nGRAY88 = Color(224, 224, 224)\r\nGRAY89 = Color(227, 227, 227)\r\nGRAY9 = Color(23, 23, 23)\r\nGRAY90 = Color(229, 229, 229)\r\nGRAY91 = Color(232, 232, 232)\r\nGRAY92 = Color(235, 235, 235)\r\nGRAY93 = Color(237, 237, 237)\r\nGRAY94 = Color(240, 240, 240)\r\nGRAY95 = Color(242, 242, 242)\r\nGRAY97 = Color(247, 247, 247)\r\nGRAY98 = Color(250, 250, 250)\r\nGRAY99 = Color(252, 252, 252)\r\nGREEN = Color(0, 128, 0)\r\nGREEN1 = Color(0, 255, 0)\r\nGREEN2 = Color(0, 238, 0)\r\nGREEN3 = Color(0, 205, 0)\r\nGREEN4 = Color(0, 139, 0)\r\nGREENYELLOW = Color(173, 255, 47)\r\nHONEYDEW1 = Color(240, 255, 240)\r\nHONEYDEW2 = Color(224, 238, 224)\r\nHONEYDEW3 = Color(193, 205, 193)\r\nHONEYDEW4 = Color(131, 139, 131)\r\nHOTPINK = Color(255, 105, 180)\r\nHOTPINK1 = Color(255, 110, 180)\r\nHOTPINK2 = Color(238, 106, 167)\r\nHOTPINK3 = Color(205, 96, 144)\r\nHOTPINK4 = Color(139, 58, 98)\r\nINDIANRED = Color(176, 23, 31)\r\nINDIANRED = Color(205, 92, 92)\r\nINDIANRED1 = Color(255, 106, 106)\r\nINDIANRED2 = Color(238, 99, 99)\r\nINDIANRED3 = Color(205, 85, 85)\r\nINDIANRED4 = Color(139, 58, 58)\r\nINDIGO = Color(75, 0, 130)\r\nIVORY1 = Color(255, 255, 240)\r\nIVORY2 = Color(238, 238, 224)\r\nIVORY3 = Color(205, 205, 193)\r\nIVORY4 = Color(139, 139, 131)\r\nIVORYBLACK = Color(41, 36, 33)\r\nKHAKI = Color(240, 230, 140)\r\nKHAKI1 = Color(255, 246, 143)\r\nKHAKI2 = Color(238, 230, 133)\r\nKHAKI3 = Color(205, 198, 115)\r\nKHAKI4 = Color(139, 134, 78)\r\nLAVENDER = Color(230, 230, 250)\r\nLAVENDERBLUSH1 = Color(255, 240, 245)\r\nLAVENDERBLUSH2 = Color(238, 224, 229)\r\nLAVENDERBLUSH3 = Color(205, 193, 197)\r\nLAVENDERBLUSH4 = Color(139, 131, 134)\r\nLAWNGREEN = Color(124, 252, 0)\r\nLEMONCHIFFON1 = Color(255, 250, 205)\r\nLEMONCHIFFON2 = Color(238, 233, 191)\r\nLEMONCHIFFON3 = Color(205, 201, 165)\r\nLEMONCHIFFON4 = Color(139, 137, 112)\r\nLIGHTBLUE = Color(173, 216, 230)\r\nLIGHTBLUE1 = Color(191, 239, 255)\r\nLIGHTBLUE2 = Color(178, 223, 238)\r\nLIGHTBLUE3 = Color(154, 192, 205)\r\nLIGHTBLUE4 = Color(104, 131, 139)\r\nLIGHTCORAL = Color(240, 128, 128)\r\nLIGHTCYAN1 = Color(224, 255, 255)\r\nLIGHTCYAN2 = Color(209, 238, 238)\r\nLIGHTCYAN3 = Color(180, 205, 205)\r\nLIGHTCYAN4 = Color(122, 139, 139)\r\nLIGHTGOLDENROD1 = Color(255, 236, 139)\r\nLIGHTGOLDENROD2 = Color(238, 220, 130)\r\nLIGHTGOLDENROD3 = Color(205, 190, 112)\r\nLIGHTGOLDENROD4 = Color(139, 129, 76)\r\nLIGHTGOLDENRODYELLOW = Color(250, 250, 210)\r\nLIGHTGREY = Color(211, 211, 211)\r\nLIGHTPINK = Color(255, 182, 193)\r\nLIGHTPINK1 = Color(255, 174, 185)\r\nLIGHTPINK2 = Color(238, 162, 173)\r\nLIGHTPINK3 = Color(205, 140, 149)\r\nLIGHTPINK4 = Color(139, 95, 101)\r\nLIGHTSALMON1 = Color(255, 160, 122)\r\nLIGHTSALMON2 = Color(238, 149, 114)\r\nLIGHTSALMON3 = Color(205, 129, 98)\r\nLIGHTSALMON4 = Color(139, 87, 66)\r\nLIGHTSEAGREEN = Color(32, 178, 170)\r\nLIGHTSKYBLUE = Color(135, 206, 250)\r\nLIGHTSKYBLUE1 = Color(176, 226, 255)\r\nLIGHTSKYBLUE2 = Color(164, 211, 238)\r\nLIGHTSKYBLUE3 = Color(141, 182, 205)\r\nLIGHTSKYBLUE4 = Color(96, 123, 139)\r\nLIGHTSLATEBLUE = Color(132, 112, 255)\r\nLIGHTSLATEGRAY = Color(119, 136, 153)\r\nLIGHTSTEELBLUE = Color(176, 196, 222)\r\nLIGHTSTEELBLUE1 = Color(202, 225, 255)\r\nLIGHTSTEELBLUE2 = Color(188, 210, 238)\r\nLIGHTSTEELBLUE3 = Color(162, 181, 205)\r\nLIGHTSTEELBLUE4 = Color(110, 123, 139)\r\nLIGHTYELLOW1 = Color(255, 255, 224)\r\nLIGHTYELLOW2 = Color(238, 238, 209)\r\nLIGHTYELLOW3 = Color(205, 205, 180)\r\nLIGHTYELLOW4 = Color(139, 139, 122)\r\nLIMEGREEN = Color(50, 205, 50)\r\nLINEN = Color(250, 240, 230)\r\nMAGENTA = Color(255, 0, 255)\r\nMAGENTA2 = Color(238, 0, 238)\r\nMAGENTA3 = Color(205, 0, 205)\r\nMAGENTA4 = Color(139, 0, 139)\r\nMANGANESEBLUE = Color(3, 168, 158)\r\nMAROON = Color(128, 0, 0)\r\nMAROON1 = Color(255, 52, 179)\r\nMAROON2 = Color(238, 48, 167)\r\nMAROON3 = Color(205, 41, 144)\r\nMAROON4 = Color(139, 28, 98)\r\nMEDIUMORCHID = Color(186, 85, 211)\r\nMEDIUMORCHID1 = Color(224, 102, 255)\r\nMEDIUMORCHID2 = Color(209, 95, 238)\r\nMEDIUMORCHID3 = Color(180, 82, 205)\r\nMEDIUMORCHID4 = Color(122, 55, 139)\r\nMEDIUMPURPLE = Color(147, 112, 219)\r\nMEDIUMPURPLE1 = Color(171, 130, 255)\r\nMEDIUMPURPLE2 = Color(159, 121, 238)\r\nMEDIUMPURPLE3 = Color(137, 104, 205)\r\nMEDIUMPURPLE4 = Color(93, 71, 139)\r\nMEDIUMSEAGREEN = Color(60, 179, 113)\r\nMEDIUMSLATEBLUE = Color(123, 104, 238)\r\nMEDIUMSPRINGGREEN = Color(0, 250, 154)\r\nMEDIUMTURQUOISE = Color(72, 209, 204)\r\nMEDIUMVIOLETRED = Color(199, 21, 133)\r\nMELON = Color(227, 168, 105)\r\nMIDNIGHTBLUE = Color(25, 25, 112)\r\nMINT = Color(189, 252, 201)\r\nMINTCREAM = Color(245, 255, 250)\r\nMISTYROSE1 = Color(255, 228, 225)\r\nMISTYROSE2 = Color(238, 213, 210)\r\nMISTYROSE3 = Color(205, 183, 181)\r\nMISTYROSE4 = Color(139, 125, 123)\r\nMOCCASIN = Color(255, 228, 181)\r\nNAVAJOWHITE1 = Color(255, 222, 173)\r\nNAVAJOWHITE2 = Color(238, 207, 161)\r\nNAVAJOWHITE3 = Color(205, 179, 139)\r\nNAVAJOWHITE4 = Color(139, 121, 94)\r\nNAVY = Color(0, 0, 128)\r\nOLDLACE = Color(253, 245, 230)\r\nOLIVE = Color(128, 128, 0)\r\nOLIVEDRAB = Color(107, 142, 35)\r\nOLIVEDRAB1 = Color(192, 255, 62)\r\nOLIVEDRAB2 = Color(179, 238, 58)\r\nOLIVEDRAB3 = Color(154, 205, 50)\r\nOLIVEDRAB4 = Color(105, 139, 34)\r\nORANGE = Color(255, 128, 0)\r\nORANGE1 = Color(255, 165, 0)\r\nORANGE2 = Color(238, 154, 0)\r\nORANGE3 = Color(205, 133, 0)\r\nORANGE4 = Color(139, 90, 0)\r\nORANGERED1 = Color(255, 69, 0)\r\nORANGERED2 = Color(238, 64, 0)\r\nORANGERED3 = Color(205, 55, 0)\r\nORANGERED4 = Color(139, 37, 0)\r\nORCHID = Color(218, 112, 214)\r\nORCHID1 = Color(255, 131, 250)\r\nORCHID2 = Color(238, 122, 233)\r\nORCHID3 = Color(205, 105, 201)\r\nORCHID4 = Color(139, 71, 137)\r\nPALEGOLDENROD = Color(238, 232, 170)\r\nPALEGREEN = Color(152, 251, 152)\r\nPALEGREEN1 = Color(154, 255, 154)\r\nPALEGREEN2 = Color(144, 238, 144)\r\nPALEGREEN3 = Color(124, 205, 124)\r\nPALEGREEN4 = Color(84, 139, 84)\r\nPALETURQUOISE1 = Color(187, 255, 255)\r\nPALETURQUOISE2 = Color(174, 238, 238)\r\nPALETURQUOISE3 = Color(150, 205, 205)\r\nPALETURQUOISE4 = Color(102, 139, 139)\r\nPALEVIOLETRED = Color(219, 112, 147)\r\nPALEVIOLETRED1 = Color(255, 130, 171)\r\nPALEVIOLETRED2 = Color(238, 121, 159)\r\nPALEVIOLETRED3 = Color(205, 104, 137)\r\nPALEVIOLETRED4 = Color(139, 71, 93)\r\nPAPAYAWHIP = Color(255, 239, 213)\r\nPEACHPUFF1 = Color(255, 218, 185)\r\nPEACHPUFF2 = Color(238, 203, 173)\r\nPEACHPUFF3 = Color(205, 175, 149)\r\nPEACHPUFF4 = Color(139, 119, 101)\r\nPEACOCK = Color(51, 161, 201)\r\nPINK = Color(255, 192, 203)\r\nPINK1 = Color(255, 181, 197)\r\nPINK2 = Color(238, 169, 184)\r\nPINK3 = Color(205, 145, 158)\r\nPINK4 = Color(139, 99, 108)\r\nPLUM = Color(221, 160, 221)\r\nPLUM1 = Color(255, 187, 255)\r\nPLUM2 = Color(238, 174, 238)\r\nPLUM3 = Color(205, 150, 205)\r\nPLUM4 = Color(139, 102, 139)\r\nPOWDERBLUE = Color(176, 224, 230)\r\nPURPLE = Color(128, 0, 128)\r\nPURPLE1 = Color(155, 48, 255)\r\nPURPLE2 = Color(145, 44, 238)\r\nPURPLE3 = Color(125, 38, 205)\r\nPURPLE4 = Color(85, 26, 139)\r\nRASPBERRY = Color(135, 38, 87)\r\nRAWSIENNA = Color(199, 97, 20)\r\nRED = Color(255, 0, 0)\r\nRED1 = Color(238, 0, 0)\r\nRED2 = Color(205, 0, 0)\r\nRED3 = Color(139, 0, 0)\r\nRED4 = Color(125, 0, 0)\r\nROSYBROWN = Color(188, 143, 143)\r\nROSYBROWN1 = Color(255, 193, 193)\r\nROSYBROWN2 = Color(238, 180, 180)\r\nROSYBROWN3 = Color(205, 155, 155)\r\nROSYBROWN4 = Color(139, 105, 105)\r\nROYALBLUE = Color(65, 105, 225)\r\nROYALBLUE1 = Color(72, 118, 255)\r\nROYALBLUE2 = Color(67, 110, 238)\r\nROYALBLUE3 = Color(58, 95, 205)\r\nROYALBLUE4 = Color(39, 64, 139)\r\nSALMON = Color(250, 128, 114)\r\nSALMON1 = Color(255, 140, 105)\r\nSALMON2 = Color(238, 130, 98)\r\nSALMON3 = Color(205, 112, 84)\r\nSALMON4 = Color(139, 76, 57)\r\nSANDYBROWN = Color(244, 164, 96)\r\nSAPGREEN = Color(48, 128, 20)\r\nSEAGREEN1 = Color(84, 255, 159)\r\nSEAGREEN2 = Color(78, 238, 148)\r\nSEAGREEN3 = Color(67, 205, 128)\r\nSEAGREEN4 = Color(46, 139, 87)\r\nSEASHELL1 = Color(255, 245, 238)\r\nSEASHELL2 = Color(238, 229, 222)\r\nSEASHELL3 = Color(205, 197, 191)\r\nSEASHELL4 = Color(139, 134, 130)\r\nSEPIA = Color(94, 38, 18)\r\nSGIBEET = Color(142, 56, 142)\r\nSGIBRIGHTGRAY = Color(197, 193, 170)\r\nSGICHARTREUSE = Color(113, 198, 113)\r\nSGIDARKGRAY = Color(85, 85, 85)\r\nSGIGRAY12 = Color(30, 30, 30)\r\nSGIGRAY16 = Color(40, 40, 40)\r\nSGIGRAY32 = Color(81, 81, 81)\r\nSGIGRAY36 = Color(91, 91, 91)\r\nSGIGRAY52 = Color(132, 132, 132)\r\nSGIGRAY56 = Color(142, 142, 142)\r\nSGIGRAY72 = Color(183, 183, 183)\r\nSGIGRAY76 = Color(193, 193, 193)\r\nSGIGRAY92 = Color(234, 234, 234)\r\nSGIGRAY96 = Color(244, 244, 244)\r\nSGILIGHTBLUE = Color(125, 158, 192)\r\nSGILIGHTGRAY = Color(170, 170, 170)\r\nSGIOLIVEDRAB = Color(142, 142, 56)\r\nSGISALMON = Color(198, 113, 113)\r\nSGISLATEBLUE = Color(113, 113, 198)\r\nSGITEAL = Color(56, 142, 142)\r\nSIENNA = Color(160, 82, 45)\r\nSIENNA1 = Color(255, 130, 71)\r\nSIENNA2 = Color(238, 121, 66)\r\nSIENNA3 = Color(205, 104, 57)\r\nSIENNA4 = Color(139, 71, 38)\r\nSILVER = Color(192, 192, 192)\r\nSKYBLUE = Color(135, 206, 235)\r\nSKYBLUE1 = Color(135, 206, 255)\r\nSKYBLUE2 = Color(126, 192, 238)\r\nSKYBLUE3 = Color(108, 166, 205)\r\nSKYBLUE4 = Color(74, 112, 139)\r\nSLATEBLUE = Color(106, 90, 205)\r\nSLATEBLUE1 = Color(131, 111, 255)\r\nSLATEBLUE2 = Color(122, 103, 238)\r\nSLATEBLUE3 = Color(105, 89, 205)\r\nSLATEBLUE4 = Color(71, 60, 139)\r\nSLATEGRAY = Color(112, 128, 144)\r\nSLATEGRAY1 = Color(198, 226, 255)\r\nSLATEGRAY2 = Color(185, 211, 238)\r\nSLATEGRAY3 = Color(159, 182, 205)\r\nSLATEGRAY4 = Color(108, 123, 139)\r\nSNOW1 = Color(255, 250, 250)\r\nSNOW2 = Color(238, 233, 233)\r\nSNOW3 = Color(205, 201, 201)\r\nSNOW4 = Color(139, 137, 137)\r\nSPRINGGREEN = Color(0, 255, 127)\r\nSPRINGGREEN1 = Color(0, 238, 118)\r\nSPRINGGREEN2 = Color(0, 205, 102)\r\nSPRINGGREEN3 = Color(0, 139, 69)\r\nSTEELBLUE = Color(70, 130, 180)\r\nSTEELBLUE1 = Color(99, 184, 255)\r\nSTEELBLUE2 = Color(92, 172, 238)\r\nSTEELBLUE3 = Color(79, 148, 205)\r\nSTEELBLUE4 = Color(54, 100, 139)\r\nTAN = Color(210, 180, 140)\r\nTAN1 = Color(255, 165, 79)\r\nTAN2 = Color(238, 154, 73)\r\nTAN3 = Color(205, 133, 63)\r\nTAN4 = Color(139, 90, 43)\r\nTEAL = Color(0, 128, 128)\r\nTHISTLE = Color(216, 191, 216)\r\nTHISTLE1 = Color(255, 225, 255)\r\nTHISTLE2 = Color(238, 210, 238)\r\nTHISTLE3 = Color(205, 181, 205)\r\nTHISTLE4 = Color(139, 123, 139)\r\nTOMATO1 = Color(255, 99, 71)\r\nTOMATO2 = Color(238, 92, 66)\r\nTOMATO3 = Color(205, 79, 57)\r\nTOMATO4 = Color(139, 54, 38)\r\nTURQUOISE = Color(64, 224, 208)\r\nTURQUOISE1 = Color(0, 245, 255)\r\nTURQUOISE2 = Color(0, 229, 238)\r\nTURQUOISE3 = Color(0, 197, 205)\r\nTURQUOISE4 = Color(0, 134, 139)\r\nTURQUOISEBLUE = Color(0, 199, 140)\r\nVIOLET = Color(238, 130, 238)\r\nVIOLETRED = Color(208, 32, 144)\r\nVIOLETRED1 = Color(255, 62, 150)\r\nVIOLETRED2 = Color(238, 58, 140)\r\nVIOLETRED3 = Color(205, 50, 120)\r\nVIOLETRED4 = Color(139, 34, 82)\r\nWARMGREY = Color(128, 128, 105)\r\nWHEAT = Color(245, 222, 179)\r\nWHEAT1 = Color(255, 231, 186)\r\nWHEAT2 = Color(238, 216, 174)\r\nWHEAT3 = Color(205, 186, 150)\r\nWHEAT4 = Color(139, 126, 102)\r\nWHITE = Color(255, 255, 255)\r\nWHITESMOKE = Color(245, 245, 245)\r\nWHITESMOKE = Color(245, 245, 245)\r\nYELLOW = Color(255, 255, 0)\r\nYELLOW1 = Color(255, 255, 0)\r\nYELLOW2 = Color(238, 238, 0)\r\nYELLOW3 = Color(205, 205, 0)\r\nYELLOW4 = Color(139, 139, 0)\r\n\r\nCOLORS = {}\r\n\r\n# Add colors to colors dictionary\r\nCOLORS[\"aliceblue\"] = ALICEBLUE\r\nCOLORS[\"antiquewhite\"] = ANTIQUEWHITE\r\nCOLORS[\"antiquewhite1\"] = ANTIQUEWHITE1\r\nCOLORS[\"antiquewhite2\"] = ANTIQUEWHITE2\r\nCOLORS[\"antiquewhite3\"] = ANTIQUEWHITE3\r\nCOLORS[\"antiquewhite4\"] = ANTIQUEWHITE4\r\nCOLORS[\"aqua\"] = AQUA\r\nCOLORS[\"aquamarine\"] = AQUAMARINE1\r\nCOLORS[\"aquamarine1\"] = AQUAMARINE1\r\nCOLORS[\"aquamarine2\"] = AQUAMARINE2\r\nCOLORS[\"aquamarine3\"] = AQUAMARINE3\r\nCOLORS[\"aquamarine4\"] = AQUAMARINE4\r\nCOLORS[\"azure\"] = AZURE1\r\nCOLORS[\"azure1\"] = AZURE1\r\nCOLORS[\"azure2\"] = AZURE2\r\nCOLORS[\"azure3\"] = AZURE3\r\nCOLORS[\"azure4\"] = AZURE4\r\nCOLORS[\"banana\"] = BANANA\r\nCOLORS[\"beige\"] = BEIGE\r\nCOLORS[\"bisque\"] = BISQUE1\r\nCOLORS[\"bisque1\"] = BISQUE1\r\nCOLORS[\"bisque2\"] = BISQUE2\r\nCOLORS[\"bisque3\"] = BISQUE3\r\nCOLORS[\"bisque4\"] = BISQUE4\r\nCOLORS[\"black\"] = BLACK\r\nCOLORS[\"blanchedalmond\"] = BLANCHEDALMOND\r\nCOLORS[\"blue\"] = BLUE\r\nCOLORS[\"blue1\"] = BLUE\r\nCOLORS[\"blue2\"] = BLUE2\r\nCOLORS[\"blue3\"] = BLUE3\r\nCOLORS[\"blue4\"] = BLUE4\r\nCOLORS[\"blueviolet\"] = BLUEVIOLET\r\nCOLORS[\"brick\"] = BRICK\r\nCOLORS[\"brown\"] = BROWN\r\nCOLORS[\"brown1\"] = BROWN1\r\nCOLORS[\"brown2\"] = BROWN2\r\nCOLORS[\"brown3\"] = BROWN3\r\nCOLORS[\"brown4\"] = BROWN4\r\nCOLORS[\"burlywood\"] = BURLYWOOD\r\nCOLORS[\"burlywood1\"] = BURLYWOOD1\r\nCOLORS[\"burlywood2\"] = BURLYWOOD2\r\nCOLORS[\"burlywood3\"] = BURLYWOOD3\r\nCOLORS[\"burlywood4\"] = BURLYWOOD4\r\nCOLORS[\"burntsienna\"] = BURNTSIENNA\r\nCOLORS[\"burntumber\"] = BURNTUMBER\r\nCOLORS[\"cadetblue\"] = CADETBLUE\r\nCOLORS[\"cadetblue1\"] = CADETBLUE1\r\nCOLORS[\"cadetblue2\"] = CADETBLUE2\r\nCOLORS[\"cadetblue3\"] = CADETBLUE3\r\nCOLORS[\"cadetblue4\"] = CADETBLUE4\r\nCOLORS[\"cadmiumorange\"] = CADMIUMORANGE\r\nCOLORS[\"cadmiumyellow\"] = CADMIUMYELLOW\r\nCOLORS[\"carrot\"] = CARROT\r\nCOLORS[\"chartreuse\"] = CHARTREUSE1\r\nCOLORS[\"chartreuse1\"] = CHARTREUSE1\r\nCOLORS[\"chartreuse2\"] = CHARTREUSE2\r\nCOLORS[\"chartreuse3\"] = CHARTREUSE3\r\nCOLORS[\"chartreuse4\"] = CHARTREUSE4\r\nCOLORS[\"chocolate\"] = CHOCOLATE\r\nCOLORS[\"chocolate1\"] = CHOCOLATE1\r\nCOLORS[\"chocolate2\"] = CHOCOLATE2\r\nCOLORS[\"chocolate3\"] = CHOCOLATE3\r\nCOLORS[\"chocolate4\"] = CHOCOLATE4\r\nCOLORS[\"cobalt\"] = COBALT\r\nCOLORS[\"cobaltgreen\"] = COBALTGREEN\r\nCOLORS[\"coldgrey\"] = COLDGREY\r\nCOLORS[\"coral\"] = CORAL\r\nCOLORS[\"coral1\"] = CORAL1\r\nCOLORS[\"coral2\"] = CORAL2\r\nCOLORS[\"coral3\"] = CORAL3\r\nCOLORS[\"coral4\"] = CORAL4\r\nCOLORS[\"cornflowerblue\"] = CORNFLOWERBLUE\r\nCOLORS[\"cornsilk\"] = CORNSILK1\r\nCOLORS[\"cornsilk1\"] = CORNSILK1\r\nCOLORS[\"cornsilk2\"] = CORNSILK2\r\nCOLORS[\"cornsilk3\"] = CORNSILK3\r\nCOLORS[\"cornsilk4\"] = CORNSILK4\r\nCOLORS[\"crimson\"] = CRIMSON\r\nCOLORS[\"cyan\"] = CYAN1\r\nCOLORS[\"cyan1\"] = CYAN1\r\nCOLORS[\"cyan2\"] = CYAN2\r\nCOLORS[\"cyan3\"] = CYAN3\r\nCOLORS[\"darkgoldenrod\"] = DARKGOLDENROD\r\nCOLORS[\"darkgoldenrod1\"] = DARKGOLDENROD1\r\nCOLORS[\"darkgoldenrod2\"] = DARKGOLDENROD2\r\nCOLORS[\"darkgoldenrod3\"] = DARKGOLDENROD3\r\nCOLORS[\"darkgoldenrod4\"] = DARKGOLDENROD4\r\nCOLORS[\"darkgray\"] = DARKGRAY\r\nCOLORS[\"darkgreen\"] = DARKGREEN\r\nCOLORS[\"darkkhaki\"] = DARKKHAKI\r\nCOLORS[\"darkolivegreen\"] = DARKOLIVEGREEN\r\nCOLORS[\"darkolivegreen1\"] = DARKOLIVEGREEN1\r\nCOLORS[\"darkolivegreen2\"] = DARKOLIVEGREEN2\r\nCOLORS[\"darkolivegreen3\"] = DARKOLIVEGREEN3\r\nCOLORS[\"darkolivegreen4\"] = DARKOLIVEGREEN4\r\nCOLORS[\"darkorange\"] = DARKORANGE\r\nCOLORS[\"darkorange1\"] = DARKORANGE1\r\nCOLORS[\"darkorange2\"] = DARKORANGE2\r\nCOLORS[\"darkorange3\"] = DARKORANGE3\r\nCOLORS[\"darkorange4\"] = DARKORANGE4\r\nCOLORS[\"darkorchid\"] = DARKORCHID\r\nCOLORS[\"darkorchid1\"] = DARKORCHID1\r\nCOLORS[\"darkorchid2\"] = DARKORCHID2\r\nCOLORS[\"darkorchid3\"] = DARKORCHID3\r\nCOLORS[\"darkorchid4\"] = DARKORCHID4\r\nCOLORS[\"darksalmon\"] = DARKSALMON\r\nCOLORS[\"darkseagreen\"] = DARKSEAGREEN\r\nCOLORS[\"darkseagreen1\"] = DARKSEAGREEN1\r\nCOLORS[\"darkseagreen2\"] = DARKSEAGREEN2\r\nCOLORS[\"darkseagreen3\"] = DARKSEAGREEN3\r\nCOLORS[\"darkseagreen4\"] = DARKSEAGREEN4\r\nCOLORS[\"darkslateblue\"] = DARKSLATEBLUE\r\nCOLORS[\"darkslategray\"] = DARKSLATEGRAY\r\nCOLORS[\"darkslategray1\"] = DARKSLATEGRAY1\r\nCOLORS[\"darkslategray2\"] = DARKSLATEGRAY2\r\nCOLORS[\"darkslategray3\"] = DARKSLATEGRAY3\r\nCOLORS[\"darkslategray4\"] = DARKSLATEGRAY4\r\nCOLORS[\"darkturquoise\"] = DARKTURQUOISE\r\nCOLORS[\"darkviolet\"] = DARKVIOLET\r\nCOLORS[\"deeppink\"] = DEEPPINK1\r\nCOLORS[\"deeppink1\"] = DEEPPINK1\r\nCOLORS[\"deeppink2\"] = DEEPPINK2\r\nCOLORS[\"deeppink3\"] = DEEPPINK3\r\nCOLORS[\"deeppink4\"] = DEEPPINK4\r\nCOLORS[\"deepskyblue\"] = DEEPSKYBLUE1\r\nCOLORS[\"deepskyblue1\"] = DEEPSKYBLUE1\r\nCOLORS[\"deepskyblue2\"] = DEEPSKYBLUE2\r\nCOLORS[\"deepskyblue3\"] = DEEPSKYBLUE3\r\nCOLORS[\"deepskyblue4\"] = DEEPSKYBLUE4\r\nCOLORS[\"dimgray\"] = DIMGRAY\r\nCOLORS[\"dodgerblue\"] = DODGERBLUE1\r\nCOLORS[\"dodgerblue1\"] = DODGERBLUE1\r\nCOLORS[\"dodgerblue2\"] = DODGERBLUE2\r\nCOLORS[\"dodgerblue3\"] = DODGERBLUE3\r\nCOLORS[\"dodgerblue4\"] = DODGERBLUE4\r\nCOLORS[\"eggshell\"] = EGGSHELL\r\nCOLORS[\"emeraldgreen\"] = EMERALDGREEN\r\nCOLORS[\"firebrick\"] = FIREBRICK\r\nCOLORS[\"firebrick1\"] = FIREBRICK1\r\nCOLORS[\"firebrick2\"] = FIREBRICK2\r\nCOLORS[\"firebrick3\"] = FIREBRICK3\r\nCOLORS[\"firebrick4\"] = FIREBRICK4\r\nCOLORS[\"flesh\"] = FLESH\r\nCOLORS[\"floralwhite\"] = FLORALWHITE\r\nCOLORS[\"forestgreen\"] = FORESTGREEN\r\nCOLORS[\"gainsboro\"] = GAINSBORO\r\nCOLORS[\"ghostwhite\"] = GHOSTWHITE\r\nCOLORS[\"gold\"] = GOLD1\r\nCOLORS[\"gold1\"] = GOLD1\r\nCOLORS[\"gold2\"] = GOLD2\r\nCOLORS[\"gold3\"] = GOLD3\r\nCOLORS[\"gold4\"] = GOLD4\r\nCOLORS[\"goldenrod\"] = GOLDENROD\r\nCOLORS[\"goldenrod1\"] = GOLDENROD1\r\nCOLORS[\"goldenrod2\"] = GOLDENROD2\r\nCOLORS[\"goldenrod3\"] = GOLDENROD3\r\nCOLORS[\"goldenrod4\"] = GOLDENROD4\r\nCOLORS[\"gray\"] = GRAY\r\nCOLORS[\"gray1\"] = GRAY1\r\nCOLORS[\"gray10\"] = GRAY10\r\nCOLORS[\"gray11\"] = GRAY11\r\nCOLORS[\"gray12\"] = GRAY12\r\nCOLORS[\"gray13\"] = GRAY13\r\nCOLORS[\"gray14\"] = GRAY14\r\nCOLORS[\"gray15\"] = GRAY15\r\nCOLORS[\"gray16\"] = GRAY16\r\nCOLORS[\"gray17\"] = GRAY17\r\nCOLORS[\"gray18\"] = GRAY18\r\nCOLORS[\"gray19\"] = GRAY19\r\nCOLORS[\"gray2\"] = GRAY2\r\nCOLORS[\"gray20\"] = GRAY20\r\nCOLORS[\"gray21\"] = GRAY21\r\nCOLORS[\"gray22\"] = GRAY22\r\nCOLORS[\"gray23\"] = GRAY23\r\nCOLORS[\"gray24\"] = GRAY24\r\nCOLORS[\"gray25\"] = GRAY25\r\nCOLORS[\"gray26\"] = GRAY26\r\nCOLORS[\"gray27\"] = GRAY27\r\nCOLORS[\"gray28\"] = GRAY28\r\nCOLORS[\"gray29\"] = GRAY29\r\nCOLORS[\"gray3\"] = GRAY3\r\nCOLORS[\"gray30\"] = GRAY30\r\nCOLORS[\"gray31\"] = GRAY31\r\nCOLORS[\"gray32\"] = GRAY32\r\nCOLORS[\"gray33\"] = GRAY33\r\nCOLORS[\"gray34\"] = GRAY34\r\nCOLORS[\"gray35\"] = GRAY35\r\nCOLORS[\"gray36\"] = GRAY36\r\nCOLORS[\"gray37\"] = GRAY37\r\nCOLORS[\"gray38\"] = GRAY38\r\nCOLORS[\"gray39\"] = GRAY39\r\nCOLORS[\"gray4\"] = GRAY4\r\nCOLORS[\"gray40\"] = GRAY40\r\nCOLORS[\"gray42\"] = GRAY42\r\nCOLORS[\"gray43\"] = GRAY43\r\nCOLORS[\"gray44\"] = GRAY44\r\nCOLORS[\"gray45\"] = GRAY45\r\nCOLORS[\"gray46\"] = GRAY46\r\nCOLORS[\"gray47\"] = GRAY47\r\nCOLORS[\"gray48\"] = GRAY48\r\nCOLORS[\"gray49\"] = GRAY49\r\nCOLORS[\"gray5\"] = GRAY5\r\nCOLORS[\"gray50\"] = GRAY50\r\nCOLORS[\"gray51\"] = GRAY51\r\nCOLORS[\"gray52\"] = GRAY52\r\nCOLORS[\"gray53\"] = GRAY53\r\nCOLORS[\"gray54\"] = GRAY54\r\nCOLORS[\"gray55\"] = GRAY55\r\nCOLORS[\"gray56\"] = GRAY56\r\nCOLORS[\"gray57\"] = GRAY57\r\nCOLORS[\"gray58\"] = GRAY58\r\nCOLORS[\"gray59\"] = GRAY59\r\nCOLORS[\"gray6\"] = GRAY6\r\nCOLORS[\"gray60\"] = GRAY60\r\nCOLORS[\"gray61\"] = GRAY61\r\nCOLORS[\"gray62\"] = GRAY62\r\nCOLORS[\"gray63\"] = GRAY63\r\nCOLORS[\"gray64\"] = GRAY64\r\nCOLORS[\"gray65\"] = GRAY65\r\nCOLORS[\"gray66\"] = GRAY66\r\nCOLORS[\"gray67\"] = GRAY67\r\nCOLORS[\"gray68\"] = GRAY68\r\nCOLORS[\"gray69\"] = GRAY69\r\nCOLORS[\"gray7\"] = GRAY7\r\nCOLORS[\"gray70\"] = GRAY70\r\nCOLORS[\"gray71\"] = GRAY71\r\nCOLORS[\"gray72\"] = GRAY72\r\nCOLORS[\"gray73\"] = GRAY73\r\nCOLORS[\"gray74\"] = GRAY74\r\nCOLORS[\"gray75\"] = GRAY75\r\nCOLORS[\"gray76\"] = GRAY76\r\nCOLORS[\"gray77\"] = GRAY77\r\nCOLORS[\"gray78\"] = GRAY78\r\nCOLORS[\"gray79\"] = GRAY79\r\nCOLORS[\"gray8\"] = GRAY8\r\nCOLORS[\"gray80\"] = GRAY80\r\nCOLORS[\"gray81\"] = GRAY81\r\nCOLORS[\"gray82\"] = GRAY82\r\nCOLORS[\"gray83\"] = GRAY83\r\nCOLORS[\"gray84\"] = GRAY84\r\nCOLORS[\"gray85\"] = GRAY85\r\nCOLORS[\"gray86\"] = GRAY86\r\nCOLORS[\"gray87\"] = GRAY87\r\nCOLORS[\"gray88\"] = GRAY88\r\nCOLORS[\"gray89\"] = GRAY89\r\nCOLORS[\"gray9\"] = GRAY9\r\nCOLORS[\"gray90\"] = GRAY90\r\nCOLORS[\"gray91\"] = GRAY91\r\nCOLORS[\"gray92\"] = GRAY92\r\nCOLORS[\"gray93\"] = GRAY93\r\nCOLORS[\"gray94\"] = GRAY94\r\nCOLORS[\"gray95\"] = GRAY95\r\nCOLORS[\"gray97\"] = GRAY97\r\nCOLORS[\"gray98\"] = GRAY98\r\nCOLORS[\"gray99\"] = GRAY99\r\nCOLORS[\"green\"] = GREEN\r\nCOLORS[\"green1\"] = GREEN1\r\nCOLORS[\"green2\"] = GREEN2\r\nCOLORS[\"green3\"] = GREEN3\r\nCOLORS[\"green4\"] = GREEN4\r\nCOLORS[\"greenyellow\"] = GREENYELLOW\r\nCOLORS[\"honeydew\"] = HONEYDEW1\r\nCOLORS[\"honeydew1\"] = HONEYDEW1\r\nCOLORS[\"honeydew2\"] = HONEYDEW2\r\nCOLORS[\"honeydew3\"] = HONEYDEW3\r\nCOLORS[\"honeydew4\"] = HONEYDEW4\r\nCOLORS[\"hotpink\"] = HOTPINK\r\nCOLORS[\"hotpink1\"] = HOTPINK1\r\nCOLORS[\"hotpink2\"] = HOTPINK2\r\nCOLORS[\"hotpink3\"] = HOTPINK3\r\nCOLORS[\"hotpink4\"] = HOTPINK4\r\nCOLORS[\"indianred\"] = INDIANRED\r\nCOLORS[\"indianred\"] = INDIANRED\r\nCOLORS[\"indianred1\"] = INDIANRED1\r\nCOLORS[\"indianred2\"] = INDIANRED2\r\nCOLORS[\"indianred3\"] = INDIANRED3\r\nCOLORS[\"indianred4\"] = INDIANRED4\r\nCOLORS[\"indigo\"] = INDIGO\r\nCOLORS[\"ivory\"] = IVORY1\r\nCOLORS[\"ivory1\"] = IVORY1\r\nCOLORS[\"ivory2\"] = IVORY2\r\nCOLORS[\"ivory3\"] = IVORY3\r\nCOLORS[\"ivory4\"] = IVORY4\r\nCOLORS[\"ivoryblack\"] = IVORYBLACK\r\nCOLORS[\"khaki\"] = KHAKI\r\nCOLORS[\"khaki1\"] = KHAKI1\r\nCOLORS[\"khaki2\"] = KHAKI2\r\nCOLORS[\"khaki3\"] = KHAKI3\r\nCOLORS[\"khaki4\"] = KHAKI4\r\nCOLORS[\"lavender\"] = LAVENDER\r\nCOLORS[\"lavenderblush\"] = LAVENDERBLUSH1\r\nCOLORS[\"lavenderblush1\"] = LAVENDERBLUSH1\r\nCOLORS[\"lavenderblush2\"] = LAVENDERBLUSH2\r\nCOLORS[\"lavenderblush3\"] = LAVENDERBLUSH3\r\nCOLORS[\"lavenderblush4\"] = LAVENDERBLUSH4\r\nCOLORS[\"lawngreen\"] = LAWNGREEN\r\nCOLORS[\"lemonchiffon\"] = LEMONCHIFFON1\r\nCOLORS[\"lemonchiffon1\"] = LEMONCHIFFON1\r\nCOLORS[\"lemonchiffon2\"] = LEMONCHIFFON2\r\nCOLORS[\"lemonchiffon3\"] = LEMONCHIFFON3\r\nCOLORS[\"lemonchiffon4\"] = LEMONCHIFFON4\r\nCOLORS[\"lightblue\"] = LIGHTBLUE\r\nCOLORS[\"lightblue1\"] = LIGHTBLUE1\r\nCOLORS[\"lightblue2\"] = LIGHTBLUE2\r\nCOLORS[\"lightblue3\"] = LIGHTBLUE3\r\nCOLORS[\"lightblue4\"] = LIGHTBLUE4\r\nCOLORS[\"lightcoral\"] = LIGHTCORAL\r\nCOLORS[\"lightcyan\"] = LIGHTCYAN1\r\nCOLORS[\"lightcyan1\"] = LIGHTCYAN1\r\nCOLORS[\"lightcyan2\"] = LIGHTCYAN2\r\nCOLORS[\"lightcyan3\"] = LIGHTCYAN3\r\nCOLORS[\"lightcyan4\"] = LIGHTCYAN4\r\nCOLORS[\"lightgoldenrod\"] = LIGHTGOLDENROD1\r\nCOLORS[\"lightgoldenrod1\"] = LIGHTGOLDENROD1\r\nCOLORS[\"lightgoldenrod2\"] = LIGHTGOLDENROD2\r\nCOLORS[\"lightgoldenrod3\"] = LIGHTGOLDENROD3\r\nCOLORS[\"lightgoldenrod4\"] = LIGHTGOLDENROD4\r\nCOLORS[\"lightgoldenrodyellow\"] = LIGHTGOLDENRODYELLOW\r\nCOLORS[\"lightgrey\"] = LIGHTGREY\r\nCOLORS[\"lightpink\"] = LIGHTPINK\r\nCOLORS[\"lightpink1\"] = LIGHTPINK1\r\nCOLORS[\"lightpink2\"] = LIGHTPINK2\r\nCOLORS[\"lightpink3\"] = LIGHTPINK3\r\nCOLORS[\"lightpink4\"] = LIGHTPINK4\r\nCOLORS[\"lightsalmon\"] = LIGHTSALMON1\r\nCOLORS[\"lightsalmon1\"] = LIGHTSALMON1\r\nCOLORS[\"lightsalmon2\"] = LIGHTSALMON2\r\nCOLORS[\"lightsalmon3\"] = LIGHTSALMON3\r\nCOLORS[\"lightsalmon4\"] = LIGHTSALMON4\r\nCOLORS[\"lightseagreen\"] = LIGHTSEAGREEN\r\nCOLORS[\"lightskyblue\"] = LIGHTSKYBLUE\r\nCOLORS[\"lightskyblue1\"] = LIGHTSKYBLUE1\r\nCOLORS[\"lightskyblue2\"] = LIGHTSKYBLUE2\r\nCOLORS[\"lightskyblue3\"] = LIGHTSKYBLUE3\r\nCOLORS[\"lightskyblue4\"] = LIGHTSKYBLUE4\r\nCOLORS[\"lightslateblue\"] = LIGHTSLATEBLUE\r\nCOLORS[\"lightslategray\"] = LIGHTSLATEGRAY\r\nCOLORS[\"lightsteelblue\"] = LIGHTSTEELBLUE\r\nCOLORS[\"lightsteelblue1\"] = LIGHTSTEELBLUE1\r\nCOLORS[\"lightsteelblue2\"] = LIGHTSTEELBLUE2\r\nCOLORS[\"lightsteelblue3\"] = LIGHTSTEELBLUE3\r\nCOLORS[\"lightsteelblue4\"] = LIGHTSTEELBLUE4\r\nCOLORS[\"lightyellow\"] = LIGHTYELLOW1\r\nCOLORS[\"lightyellow1\"] = LIGHTYELLOW1\r\nCOLORS[\"lightyellow2\"] = LIGHTYELLOW2\r\nCOLORS[\"lightyellow3\"] = LIGHTYELLOW3\r\nCOLORS[\"lightyellow4\"] = LIGHTYELLOW4\r\nCOLORS[\"limegreen\"] = LIMEGREEN\r\nCOLORS[\"linen\"] = LINEN\r\nCOLORS[\"magenta\"] = MAGENTA\r\nCOLORS[\"magenta1\"] = MAGENTA2\r\nCOLORS[\"magenta2\"] = MAGENTA2\r\nCOLORS[\"magenta3\"] = MAGENTA3\r\nCOLORS[\"magenta4\"] = MAGENTA4\r\nCOLORS[\"manganeseblue\"] = MANGANESEBLUE\r\nCOLORS[\"maroon\"] = MAROON\r\nCOLORS[\"maroon1\"] = MAROON1\r\nCOLORS[\"maroon2\"] = MAROON2\r\nCOLORS[\"maroon3\"] = MAROON3\r\nCOLORS[\"maroon4\"] = MAROON4\r\nCOLORS[\"mediumorchid\"] = MEDIUMORCHID\r\nCOLORS[\"mediumorchid1\"] = MEDIUMORCHID1\r\nCOLORS[\"mediumorchid2\"] = MEDIUMORCHID2\r\nCOLORS[\"mediumorchid3\"] = MEDIUMORCHID3\r\nCOLORS[\"mediumorchid4\"] = MEDIUMORCHID4\r\nCOLORS[\"mediumpurple\"] = MEDIUMPURPLE\r\nCOLORS[\"mediumpurple1\"] = MEDIUMPURPLE1\r\nCOLORS[\"mediumpurple2\"] = MEDIUMPURPLE2\r\nCOLORS[\"mediumpurple3\"] = MEDIUMPURPLE3\r\nCOLORS[\"mediumpurple4\"] = MEDIUMPURPLE4\r\nCOLORS[\"mediumseagreen\"] = MEDIUMSEAGREEN\r\nCOLORS[\"mediumslateblue\"] = MEDIUMSLATEBLUE\r\nCOLORS[\"mediumspringgreen\"] = MEDIUMSPRINGGREEN\r\nCOLORS[\"mediumturquoise\"] = MEDIUMTURQUOISE\r\nCOLORS[\"mediumvioletred\"] = MEDIUMVIOLETRED\r\nCOLORS[\"melon\"] = MELON\r\nCOLORS[\"midnightblue\"] = MIDNIGHTBLUE\r\nCOLORS[\"mint\"] = MINT\r\nCOLORS[\"mintcream\"] = MINTCREAM\r\nCOLORS[\"mistyrose\"] = MISTYROSE1\r\nCOLORS[\"mistyrose1\"] = MISTYROSE1\r\nCOLORS[\"mistyrose2\"] = MISTYROSE2\r\nCOLORS[\"mistyrose3\"] = MISTYROSE3\r\nCOLORS[\"mistyrose4\"] = MISTYROSE4\r\nCOLORS[\"moccasin\"] = MOCCASIN\r\nCOLORS[\"navajowhite\"] = NAVAJOWHITE1\r\nCOLORS[\"navajowhite1\"] = NAVAJOWHITE1\r\nCOLORS[\"navajowhite2\"] = NAVAJOWHITE2\r\nCOLORS[\"navajowhite3\"] = NAVAJOWHITE3\r\nCOLORS[\"navajowhite4\"] = NAVAJOWHITE4\r\nCOLORS[\"navy\"] = NAVY\r\nCOLORS[\"oldlace\"] = OLDLACE\r\nCOLORS[\"olive\"] = OLIVE\r\nCOLORS[\"olivedrab\"] = OLIVEDRAB\r\nCOLORS[\"olivedrab1\"] = OLIVEDRAB1\r\nCOLORS[\"olivedrab2\"] = OLIVEDRAB2\r\nCOLORS[\"olivedrab3\"] = OLIVEDRAB3\r\nCOLORS[\"olivedrab4\"] = OLIVEDRAB4\r\nCOLORS[\"orange\"] = ORANGE\r\nCOLORS[\"orange1\"] = ORANGE1\r\nCOLORS[\"orange2\"] = ORANGE2\r\nCOLORS[\"orange3\"] = ORANGE3\r\nCOLORS[\"orange4\"] = ORANGE4\r\nCOLORS[\"orangered\"] = ORANGERED1\r\nCOLORS[\"orangered1\"] = ORANGERED1\r\nCOLORS[\"orangered2\"] = ORANGERED2\r\nCOLORS[\"orangered3\"] = ORANGERED3\r\nCOLORS[\"orangered4\"] = ORANGERED4\r\nCOLORS[\"orchid\"] = ORCHID\r\nCOLORS[\"orchid1\"] = ORCHID1\r\nCOLORS[\"orchid2\"] = ORCHID2\r\nCOLORS[\"orchid3\"] = ORCHID3\r\nCOLORS[\"orchid4\"] = ORCHID4\r\nCOLORS[\"palegoldenrod\"] = PALEGOLDENROD\r\nCOLORS[\"palegreen\"] = PALEGREEN\r\nCOLORS[\"palegreen1\"] = PALEGREEN1\r\nCOLORS[\"palegreen2\"] = PALEGREEN2\r\nCOLORS[\"palegreen3\"] = PALEGREEN3\r\nCOLORS[\"palegreen4\"] = PALEGREEN4\r\nCOLORS[\"paleturquoise1\"] = PALETURQUOISE1\r\nCOLORS[\"paleturquoise2\"] = PALETURQUOISE2\r\nCOLORS[\"paleturquoise3\"] = PALETURQUOISE3\r\nCOLORS[\"paleturquoise4\"] = PALETURQUOISE4\r\nCOLORS[\"palevioletred\"] = PALEVIOLETRED\r\nCOLORS[\"palevioletred1\"] = PALEVIOLETRED1\r\nCOLORS[\"palevioletred2\"] = PALEVIOLETRED2\r\nCOLORS[\"palevioletred3\"] = PALEVIOLETRED3\r\nCOLORS[\"palevioletred4\"] = PALEVIOLETRED4\r\nCOLORS[\"papayawhip\"] = PAPAYAWHIP\r\nCOLORS[\"peachpuff1\"] = PEACHPUFF1\r\nCOLORS[\"peachpuff2\"] = PEACHPUFF2\r\nCOLORS[\"peachpuff3\"] = PEACHPUFF3\r\nCOLORS[\"peachpuff4\"] = PEACHPUFF4\r\nCOLORS[\"peacock\"] = PEACOCK\r\nCOLORS[\"pink\"] = PINK\r\nCOLORS[\"pink1\"] = PINK1\r\nCOLORS[\"pink2\"] = PINK2\r\nCOLORS[\"pink3\"] = PINK3\r\nCOLORS[\"pink4\"] = PINK4\r\nCOLORS[\"plum\"] = PLUM\r\nCOLORS[\"plum1\"] = PLUM1\r\nCOLORS[\"plum2\"] = PLUM2\r\nCOLORS[\"plum3\"] = PLUM3\r\nCOLORS[\"plum4\"] = PLUM4\r\nCOLORS[\"powderblue\"] = POWDERBLUE\r\nCOLORS[\"purple\"] = PURPLE\r\nCOLORS[\"purple1\"] = PURPLE1\r\nCOLORS[\"purple2\"] = PURPLE2\r\nCOLORS[\"purple3\"] = PURPLE3\r\nCOLORS[\"purple4\"] = PURPLE4\r\nCOLORS[\"raspberry\"] = RASPBERRY\r\nCOLORS[\"rawsienna\"] = RAWSIENNA\r\nCOLORS[\"red\"] = RED\r\nCOLORS[\"red1\"] = RED1\r\nCOLORS[\"red2\"] = RED2\r\nCOLORS[\"red3\"] = RED3\r\nCOLORS[\"red4\"] = RED4\r\nCOLORS[\"rosybrown\"] = ROSYBROWN\r\nCOLORS[\"rosybrown1\"] = ROSYBROWN1\r\nCOLORS[\"rosybrown2\"] = ROSYBROWN2\r\nCOLORS[\"rosybrown3\"] = ROSYBROWN3\r\nCOLORS[\"rosybrown4\"] = ROSYBROWN4\r\nCOLORS[\"royalblue\"] = ROYALBLUE\r\nCOLORS[\"royalblue1\"] = ROYALBLUE1\r\nCOLORS[\"royalblue2\"] = ROYALBLUE2\r\nCOLORS[\"royalblue3\"] = ROYALBLUE3\r\nCOLORS[\"royalblue4\"] = ROYALBLUE4\r\nCOLORS[\"salmon\"] = SALMON\r\nCOLORS[\"salmon1\"] = SALMON1\r\nCOLORS[\"salmon2\"] = SALMON2\r\nCOLORS[\"salmon3\"] = SALMON3\r\nCOLORS[\"salmon4\"] = SALMON4\r\nCOLORS[\"sandybrown\"] = SANDYBROWN\r\nCOLORS[\"sapgreen\"] = SAPGREEN\r\nCOLORS[\"seagreen1\"] = SEAGREEN1\r\nCOLORS[\"seagreen2\"] = SEAGREEN2\r\nCOLORS[\"seagreen3\"] = SEAGREEN3\r\nCOLORS[\"seagreen4\"] = SEAGREEN4\r\nCOLORS[\"seashell1\"] = SEASHELL1\r\nCOLORS[\"seashell2\"] = SEASHELL2\r\nCOLORS[\"seashell3\"] = SEASHELL3\r\nCOLORS[\"seashell4\"] = SEASHELL4\r\nCOLORS[\"sepia\"] = SEPIA\r\nCOLORS[\"sgibeet\"] = SGIBEET\r\nCOLORS[\"sgibrightgray\"] = SGIBRIGHTGRAY\r\nCOLORS[\"sgichartreuse\"] = SGICHARTREUSE\r\nCOLORS[\"sgidarkgray\"] = SGIDARKGRAY\r\nCOLORS[\"sgigray12\"] = SGIGRAY12\r\nCOLORS[\"sgigray16\"] = SGIGRAY16\r\nCOLORS[\"sgigray32\"] = SGIGRAY32\r\nCOLORS[\"sgigray36\"] = SGIGRAY36\r\nCOLORS[\"sgigray52\"] = SGIGRAY52\r\nCOLORS[\"sgigray56\"] = SGIGRAY56\r\nCOLORS[\"sgigray72\"] = SGIGRAY72\r\nCOLORS[\"sgigray76\"] = SGIGRAY76\r\nCOLORS[\"sgigray92\"] = SGIGRAY92\r\nCOLORS[\"sgigray96\"] = SGIGRAY96\r\nCOLORS[\"sgilightblue\"] = SGILIGHTBLUE\r\nCOLORS[\"sgilightgray\"] = SGILIGHTGRAY\r\nCOLORS[\"sgiolivedrab\"] = SGIOLIVEDRAB\r\nCOLORS[\"sgisalmon\"] = SGISALMON\r\nCOLORS[\"sgislateblue\"] = SGISLATEBLUE\r\nCOLORS[\"sgiteal\"] = SGITEAL\r\nCOLORS[\"sienna\"] = SIENNA\r\nCOLORS[\"sienna1\"] = SIENNA1\r\nCOLORS[\"sienna2\"] = SIENNA2\r\nCOLORS[\"sienna3\"] = SIENNA3\r\nCOLORS[\"sienna4\"] = SIENNA4\r\nCOLORS[\"silver\"] = SILVER\r\nCOLORS[\"skyblue\"] = SKYBLUE\r\nCOLORS[\"skyblue1\"] = SKYBLUE1\r\nCOLORS[\"skyblue2\"] = SKYBLUE2\r\nCOLORS[\"skyblue3\"] = SKYBLUE3\r\nCOLORS[\"skyblue4\"] = SKYBLUE4\r\nCOLORS[\"slateblue\"] = SLATEBLUE\r\nCOLORS[\"slateblue1\"] = SLATEBLUE1\r\nCOLORS[\"slateblue2\"] = SLATEBLUE2\r\nCOLORS[\"slateblue3\"] = SLATEBLUE3\r\nCOLORS[\"slateblue4\"] = SLATEBLUE4\r\nCOLORS[\"slategray\"] = SLATEGRAY\r\nCOLORS[\"slategray1\"] = SLATEGRAY1\r\nCOLORS[\"slategray2\"] = SLATEGRAY2\r\nCOLORS[\"slategray3\"] = SLATEGRAY3\r\nCOLORS[\"slategray4\"] = SLATEGRAY4\r\nCOLORS[\"snow\"] = SNOW1\r\nCOLORS[\"snow1\"] = SNOW1\r\nCOLORS[\"snow2\"] = SNOW2\r\nCOLORS[\"snow3\"] = SNOW3\r\nCOLORS[\"snow4\"] = SNOW4\r\nCOLORS[\"springgreen\"] = SPRINGGREEN\r\nCOLORS[\"springgreen1\"] = SPRINGGREEN1\r\nCOLORS[\"springgreen2\"] = SPRINGGREEN2\r\nCOLORS[\"springgreen3\"] = SPRINGGREEN3\r\nCOLORS[\"steelblue\"] = STEELBLUE\r\nCOLORS[\"steelblue1\"] = STEELBLUE1\r\nCOLORS[\"steelblue2\"] = STEELBLUE2\r\nCOLORS[\"steelblue3\"] = STEELBLUE3\r\nCOLORS[\"steelblue4\"] = STEELBLUE4\r\nCOLORS[\"tan\"] = TAN\r\nCOLORS[\"tan1\"] = TAN1\r\nCOLORS[\"tan2\"] = TAN2\r\nCOLORS[\"tan3\"] = TAN3\r\nCOLORS[\"tan4\"] = TAN4\r\nCOLORS[\"teal\"] = TEAL\r\nCOLORS[\"thistle\"] = THISTLE\r\nCOLORS[\"thistle1\"] = THISTLE1\r\nCOLORS[\"thistle2\"] = THISTLE2\r\nCOLORS[\"thistle3\"] = THISTLE3\r\nCOLORS[\"thistle4\"] = THISTLE4\r\nCOLORS[\"tomato\"] = TOMATO1\r\nCOLORS[\"tomato1\"] = TOMATO1\r\nCOLORS[\"tomato2\"] = TOMATO2\r\nCOLORS[\"tomato3\"] = TOMATO3\r\nCOLORS[\"tomato4\"] = TOMATO4\r\nCOLORS[\"turquoise\"] = TURQUOISE\r\nCOLORS[\"turquoise1\"] = TURQUOISE1\r\nCOLORS[\"turquoise2\"] = TURQUOISE2\r\nCOLORS[\"turquoise3\"] = TURQUOISE3\r\nCOLORS[\"turquoise4\"] = TURQUOISE4\r\nCOLORS[\"turquoiseblue\"] = TURQUOISEBLUE\r\nCOLORS[\"violet\"] = VIOLET\r\nCOLORS[\"violetred\"] = VIOLETRED\r\nCOLORS[\"violetred1\"] = VIOLETRED1\r\nCOLORS[\"violetred2\"] = VIOLETRED2\r\nCOLORS[\"violetred3\"] = VIOLETRED3\r\nCOLORS[\"violetred4\"] = VIOLETRED4\r\nCOLORS[\"warmgrey\"] = WARMGREY\r\nCOLORS[\"wheat\"] = WHEAT\r\nCOLORS[\"wheat1\"] = WHEAT1\r\nCOLORS[\"wheat2\"] = WHEAT2\r\nCOLORS[\"wheat3\"] = WHEAT3\r\nCOLORS[\"wheat4\"] = WHEAT4\r\nCOLORS[\"white\"] = WHITE\r\nCOLORS[\"whitesmoke\"] = WHITESMOKE\r\nCOLORS[\"whitesmoke\"] = WHITESMOKE\r\nCOLORS[\"yellow\"] = YELLOW\r\nCOLORS[\"yellow1\"] = YELLOW1\r\nCOLORS[\"yellow2\"] = YELLOW2\r\nCOLORS[\"yellow3\"] = YELLOW3\r\nCOLORS[\"yellow4\"] = YELLOW4\r\n\r\ndef parse_color(value):\r\n    if value is not None:\r\n\r\n        # is it a Color object?\r\n        if isinstance(value, Color):\r\n            return value\r\n\r\n        # is the color a string\r\n        elif isinstance(value, str):\r\n            # strip the color of white space\r\n            value = value.strip()\r\n\r\n            # if it starts with a # check it is a valid color\r\n            if value[0] == \"#\":\r\n\r\n                # check its format\r\n                if len(value) != 7 and len(value) != 9:\r\n                    raise ValueError(\"{} is not a valid # color, it must be in the format #rrggbb or #rrggbbaa\".format(value))\r\n                else:\r\n                    # add the alpha if required\r\n                    if len(value) == 7:\r\n                        value = value + \"ff\"\r\n\r\n                    hex_values = (value[1:3], value[3:5], value[5:7], value[7:9])\r\n                \r\n                    # check hex values are between 00 and ff\r\n                    int_values = []\r\n                    for hex_color in hex_values:\r\n                        try:\r\n                            int_color = int(hex_color, 16)\r\n                            int_values.append(int_color)\r\n                        except: \r\n                            raise ValueError(\"{} is not a valid value, it must be hex 00 - ff\".format(hex_color))\r\n\r\n                        if not (0 <= int_color <= 255):\r\n                            raise ValueError(\"{} is not a valid color value, it must be 00 - ff\".format(hex_color))\r\n\r\n                    return Color(red=int_values[0], green=int_values[1], blue=int_values[2], alpha=int_values[3])\r\n            \r\n            else:\r\n                # does the color exist in the dictionary of colors\r\n                if value.lower() in COLORS:\r\n                    return COLORS[value.lower()]\r\n                else:\r\n                    raise ValueError(\"'{}' is not a valid color value.\")\r\n\r\n        # if the color is not a string or a Color object, maybe its a list or tuple - try and convert it\r\n        else:\r\n            # get the number of colors and check it is iterable\r\n            try:\r\n                no_of_colors = len(value)\r\n            except:\r\n                raise ValueError(\"A color must be a Color object, string or list of values (red, green, blue) or (alpha, red, green, blue)\") \r\n\r\n            if not (3 <= no_of_colors <= 4):\r\n                raise ValueError(\"A color must contain 3 or 4 values (red, green, blue) or (red, green, blue, alpha)\")\r\n            \r\n            # check the color values are between 0 and 255\r\n            for c in value:\r\n                if not (0 <= c <= 255):\r\n                    raise ValueError(\"{} is not a valid color value, it must be 0 - 255\")\r\n                    \r\n            if no_of_colors == 3:\r\n                return Color(red=value[0], green=value[1], blue=value[2])\r\n            else:\r\n                return Color(red=value[0], green=value[1], blue=value[2], alpha=value[3])\r\n\r\n"
  },
  {
    "path": "bluedot/constants.py",
    "content": "PROTOCOL_VERSION = 2\r\nCHECK_PROTOCOL_TIMEOUT = 2"
  },
  {
    "path": "bluedot/dot.py",
    "content": "from __future__ import division\r\n\r\nimport sys\r\nimport warnings\r\nfrom time import sleep, time\r\nfrom threading import Event\r\nfrom inspect import getfullargspec\r\n\r\nfrom .btcomm import BluetoothServer\r\nfrom .threads import WrapThread\r\nfrom .constants import PROTOCOL_VERSION, CHECK_PROTOCOL_TIMEOUT\r\nfrom .interactions import BlueDotInteraction, BlueDotPosition, BlueDotRotation, BlueDotSwipe\r\nfrom .colors import parse_color, BLUE\r\nfrom .exceptions import ButtonDoesNotExist\r\n\r\n\r\nclass Dot:\r\n    \"\"\"\r\n    The internal base class for the implementation of a \"button\" or \"buttons\".\r\n    \"\"\"\r\n    def __init__(self, color, square, border, visible):\r\n        self._color = color\r\n        self._square = square\r\n        self._border = border\r\n        self._visible = visible\r\n\r\n        self._is_pressed_event = Event()\r\n        self._is_released_event = Event()\r\n        self._is_moved_event = Event()\r\n        self._is_swiped_event = Event()\r\n        self._is_double_pressed_event = Event()\r\n\r\n        self._when_pressed = None\r\n        self._when_pressed_background = False\r\n        self._when_double_pressed = None\r\n        self._when_double_pressed_background = False\r\n        self._when_released = None\r\n        self._when_released_background = False\r\n        self._when_moved = None\r\n        self._when_moved_background = False\r\n        self._when_swiped = None\r\n        self._when_swiped_background = False\r\n        self._when_rotated = None\r\n        self._when_rotated_background = False\r\n        \r\n        self._is_pressed = False\r\n        self._position = None\r\n        self._double_press_time = 0.3\r\n        self._rotation_segments = 8\r\n\r\n    @property\r\n    def is_pressed(self):\r\n        \"\"\"\r\n        Returns ``True`` if the button is pressed (or held).\r\n        \"\"\"\r\n        return self._is_pressed\r\n\r\n    @property\r\n    def value(self):\r\n        \"\"\"\r\n        Returns a 1 if ``.is_pressed``, 0 if not.\r\n        \"\"\"\r\n        return 1 if self.is_pressed else 0\r\n\r\n    @property\r\n    def values(self):\r\n        \"\"\"\r\n        Returns an infinite generator constantly yielding the current value.\r\n        \"\"\"\r\n        while True:\r\n            yield self.value\r\n\r\n    @property\r\n    def position(self):\r\n        \"\"\"\r\n        Returns an instance of :class:`BlueDotPosition` representing the\r\n        current or last position the button was pressed, held or\r\n        released.\r\n\r\n        .. note::\r\n\r\n            If the button is released (and inactive), :attr:`position` will\r\n            return the position where it was released, until it is pressed\r\n            again. If the button has never been pressed :attr:`position` will\r\n            return ``None``.\r\n        \"\"\"\r\n        return self._position\r\n\r\n    @property\r\n    def when_pressed(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the button is pressed.\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotPosition` will be returned representing where the button was pressed.\r\n\r\n        The following example will print a message to the screen when the button is pressed::\r\n\r\n            from bluedot import BlueDot\r\n\r\n            def dot_was_pressed():\r\n                print(\"The button was pressed\")\r\n\r\n            bd = BlueDot()\r\n            bd.when_pressed = dot_was_pressed\r\n\r\n        This example shows how the position of where the button was pressed can be obtained::\r\n\r\n            from bluedot import BlueDot\r\n\r\n            def dot_was_pressed(pos):\r\n                print(\"The button was pressed at pos x={} y={}\".format(pos.x, pos.y))\r\n\r\n            bd = BlueDot()\r\n            bd.when_pressed = dot_was_pressed\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_pressed(function, background=True)`\r\n        \"\"\"\r\n        return self._when_pressed\r\n\r\n    @when_pressed.setter\r\n    def when_pressed(self, value):\r\n        self.set_when_pressed(value)\r\n        \r\n    def set_when_pressed(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the button is pressed.\r\n        \r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_pressed = callback\r\n        self._when_pressed_background = background\r\n\r\n    @property\r\n    def when_double_pressed(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the button is double pressed.\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotPosition` will be returned representing where the button was\r\n        pressed the second time.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_double_pressed(function, background=True)`\r\n\r\n        .. note::\r\n            The double press event is fired before the 2nd press event e.g. events would be\r\n            appear in the order, pressed, released, double pressed, pressed.\r\n        \"\"\"\r\n        return self._when_double_pressed\r\n\r\n    @when_double_pressed.setter\r\n    def when_double_pressed(self, value):\r\n        self.set_when_double_pressed(value)\r\n\r\n    def set_when_double_pressed(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the button is double pressed.\r\n        \r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_double_pressed = callback\r\n        self._when_double_pressed_background = background\r\n\r\n    @property\r\n    def double_press_time(self):\r\n        \"\"\"\r\n        Sets or returns the time threshold in seconds for a double press. Defaults to 0.3.\r\n        \"\"\"\r\n        return self._double_press_time\r\n\r\n    @double_press_time.setter\r\n    def double_press_time(self, value):\r\n        self._double_press_time = value\r\n\r\n    @property\r\n    def when_released(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the button is released.\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotPosition` will be returned representing where the button was held\r\n        when it was released.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_released(function, background=True)`\r\n        \"\"\"\r\n        return self._when_released\r\n\r\n    @when_released.setter\r\n    def when_released(self, value):\r\n        self.set_when_released(value)\r\n\r\n    def set_when_released(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the button is released.\r\n        \r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_released = callback\r\n        self._when_released_background = background\r\n\r\n    @property\r\n    def when_moved(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the position the button is pressed is moved.\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotPosition` will be returned representing the new position of where the\r\n        Blue Dot is held.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_moved(function, background=True)`\r\n        \"\"\"\r\n        return self._when_moved\r\n\r\n    @when_moved.setter\r\n    def when_moved(self, value):\r\n        self.set_when_moved(value)\r\n\r\n    def set_when_moved(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the position the button is pressed is moved.\r\n\r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_moved = callback\r\n        self._when_moved_background = background\r\n\r\n    @property\r\n    def when_swiped(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the button is swiped.\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotSwipe` will be returned representing the how the button was\r\n        swiped.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_swiped(function, background=True)`\r\n        \"\"\"\r\n        return self._when_swiped\r\n\r\n    @when_swiped.setter\r\n    def when_swiped(self, value):\r\n        self.set_when_swiped(value)\r\n\r\n    def set_when_swiped(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the position the button is swiped.\r\n\r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_swiped = callback\r\n        self._when_swiped_background = background\r\n\r\n    @property\r\n    def rotation_segments(self):\r\n        \"\"\"\r\n        Sets or returns the number of virtual segments the button is split into for rotating.\r\n        Defaults to 8.\r\n        \"\"\"\r\n        return self._rotation_segments\r\n\r\n    @rotation_segments.setter\r\n    def rotation_segments(self, value):\r\n        self._rotation_segments = value\r\n\r\n    @property\r\n    def when_rotated(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when the button is rotated (like an\r\n        iPod clock wheel).\r\n\r\n        The function should accept 0 or 1 parameters, if the function accepts 1 parameter an\r\n        instance of :class:`BlueDotRotation` will be returned representing how the button was\r\n        rotated.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_rotated(function, background=True)`\r\n        \"\"\"\r\n        return self._when_rotated\r\n\r\n    @when_rotated.setter\r\n    def when_rotated(self, value):\r\n        self.set_when_rotated(value)\r\n\r\n    def set_when_rotated(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when the position the button is rotated (like an\r\n        iPod clock wheel).\r\n\r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_rotated = callback\r\n        self._when_rotated_background = background\r\n\r\n    @property\r\n    def color(self):\r\n        \"\"\"\r\n        Sets or returns the color of the dot. Defaults to BLUE.\r\n        \r\n        An instance of :class:`.colors.Color` is returned.\r\n\r\n        Value can be set as a :class:`.colors.Color` object, a hex color value\r\n        in the format `#rrggbb` or `#rrggbbaa`, a tuple of `(red, green, blue)`\r\n        or `(red, green, blue, alpha)` values between `0` & `255` or a text \r\n        description of the color, e.g. \"red\". \r\n        \r\n        A dictionary of available colors can be obtained from `bluedot.COLORS`.\r\n        \"\"\"\r\n        return self._color\r\n\r\n    @color.setter\r\n    def color(self, value):\r\n        self._color = parse_color(value)\r\n        \r\n    @property\r\n    def square(self):\r\n        \"\"\"\r\n        When set to `True` the 'dot' is made square. Default is `False`.\r\n        \"\"\"\r\n        return self._square\r\n\r\n    @square.setter\r\n    def square(self, value):\r\n        self._square = value\r\n\r\n    @property\r\n    def border(self):\r\n        \"\"\"\r\n        When set to `True` adds a border to the dot. Default is `False`.\r\n        \"\"\"\r\n        return self._border\r\n\r\n    @border.setter\r\n    def border(self, value):\r\n        self._border = value\r\n\r\n    @property\r\n    def visible(self):\r\n        \"\"\"\r\n        When set to `False` the dot will be hidden. Default is `True`.\r\n\r\n        .. note::\r\n\r\n            Events (press, release, moved) are still sent from the dot\r\n            when it is not visible.\r\n        \"\"\"\r\n        return self._visible\r\n\r\n    @visible.setter\r\n    def visible(self, value):\r\n        self._visible = value\r\n\r\n    def wait_for_press(self, timeout = None):\r\n        \"\"\"\r\n        Waits until a Blue Dot is pressed.\r\n        Returns ``True`` if the button was pressed.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for a Blue Dot to be pressed, if ``None``\r\n            (the default), it will wait indefinetly.\r\n        \"\"\"\r\n        return self._is_pressed_event.wait(timeout)\r\n\r\n    def wait_for_double_press(self, timeout = None):\r\n        \"\"\"\r\n        Waits until a Blue Dot is double pressed.\r\n        Returns ``True`` if the button was double pressed.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for a Blue Dot to be double pressed, if ``None``\r\n            (the default), it will wait indefinetly.\r\n        \"\"\"\r\n        return self._is_double_pressed_event.wait(timeout)\r\n\r\n    def wait_for_release(self, timeout = None):\r\n        \"\"\"\r\n        Waits until a Blue Dot is released.\r\n        Returns ``True`` if the button was released.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for a Blue Dot to be released, if ``None``\r\n            (the default), it will wait indefinetly.\r\n        \"\"\"\r\n        return self._is_released_event.wait(timeout)\r\n\r\n    def wait_for_move(self, timeout = None):\r\n        \"\"\"\r\n        Waits until the position where the button is pressed is moved.\r\n        Returns ``True`` if the position pressed on the button was moved.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for the position that the button\r\n            is pressed to move, if ``None`` (the default), it will wait indefinetly.\r\n        \"\"\"\r\n        return self._is_moved_event.wait(timeout)\r\n\r\n    def wait_for_swipe(self, timeout = None):\r\n        \"\"\"\r\n        Waits until the button is swiped.\r\n        Returns ``True`` if the button was swiped.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for the button to be swiped, if ``None``\r\n            (the default), it will wait indefinetly.\r\n        \"\"\"\r\n        return self._is_swiped_event.wait(timeout)\r\n\r\n    def press(self, position):\r\n        \"\"\"\r\n        Processes any \"pressed\" events associated with this dot.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the dot was pressed.\r\n        \"\"\"\r\n        self._position = position\r\n        self._is_pressed = True\r\n        self._is_pressed_event.set()\r\n        self._is_pressed_event.clear()\r\n\r\n        self._process_callback(self.when_pressed, position, self._when_pressed_background)\r\n\r\n    def release(self, position):\r\n        \"\"\"\r\n        Processes any \"released\" events associated with this dot.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        self._position = position\r\n        self._is_pressed = False\r\n        self._is_released_event.set()\r\n        self._is_released_event.clear()\r\n\r\n        self._process_callback(self.when_released, position, self._when_released_background)\r\n\r\n    def move(self, position):\r\n        \"\"\"\r\n        Processes any \"released\" events associated with this dot.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        self._is_moved_event.set()\r\n        self._is_moved_event.clear()\r\n\r\n        self._process_callback(self.when_moved, position, self._when_moved_background)\r\n\r\n    def double_press(self, position):\r\n        \"\"\"\r\n        Processes any \"double press\" events associated with this dot.\r\n        \r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        self._is_double_pressed_event.set()\r\n        self._is_double_pressed_event.clear()\r\n\r\n        self._process_callback(self.when_double_pressed, position, self._when_double_pressed_background)\r\n\r\n    def swipe(self, swipe):\r\n        \"\"\"\r\n        Processes any \"swipe\" events associated with this dot.\r\n        \r\n        :param BlueDotSwipe swipe:\r\n            The BlueDotSwipe representing how the dot was swiped.\r\n        \"\"\"\r\n        self._is_swiped_event.set()\r\n        self._is_swiped_event.clear()\r\n\r\n        self._process_callback(self.when_swiped, swipe, self._when_swiped_background)\r\n\r\n    def rotate(self, rotation):\r\n        \"\"\"\r\n        Processes any \"rotation\" events associated with this dot.\r\n        \r\n        :param BlueDotRotation rotation:\r\n            The BlueDotRotation representing how the dot was rotated.\r\n        \"\"\"\r\n        # print(\"rotating - when_rotated {}\")\r\n        self._process_callback(self.when_rotated, rotation, self._when_rotated_background)\r\n        \r\n    def _process_callback(self, callback, arg, background):\r\n        if callback:\r\n            args_expected = getfullargspec(callback).args\r\n            no_args_expected = len(args_expected)\r\n            if len(args_expected) > 0:\r\n                # if someone names the first arg of a class function to something\r\n                # other than self, this will fail! or if they name the first argument\r\n                # of a non class function to self this will fail!\r\n                if args_expected[0] == \"self\":\r\n                    no_args_expected -= 1\r\n\r\n            if no_args_expected == 0:\r\n                call_back_t = WrapThread(target=callback)\r\n            else:\r\n                call_back_t = WrapThread(target=callback, args=(arg, ))\r\n            call_back_t.start()\r\n\r\n            # if this callback is not running in the background wait for it\r\n            if not background:\r\n                call_back_t.join()\r\n\r\n\r\nclass BlueDotButton(Dot):\r\n    \"\"\"\r\n    Represents a single button on the button client applications. It keeps \r\n    tracks of when and where the button has been pressed and processes any \r\n    events.\r\n\r\n    This class is intended for use via :class:`BlueDot` and should not be \r\n    instantiated \"manually\".\r\n\r\n    A button can be interacted with individually via :class:`BlueDot` by \r\n    stating its position in the grid e.g. ::\r\n\r\n        from bluedot import BlueDot\r\n        bd = BlueDot()\r\n\r\n        first_button = bd[0,0].wait_for_press\r\n\r\n        first_button.wait_for_press()\r\n        print(\"The first button was pressed\")\r\n\r\n    :param BlueDot bd:\r\n        The BlueDot object this button belongs too.\r\n\r\n    :param int col:\r\n        The column position for this button in the grid.\r\n\r\n    :param int col:\r\n        The row position for this button in the grid.\r\n\r\n    :param string color\r\n        The color of the button.\r\n        \r\n        Can be set as a :class:`.colors.Color` object, a hex color value\r\n        in the format `#rrggbb` or `#rrggbbaa`, a tuple of `(red, green, blue)`\r\n        or `(red, green, blue, alpha)` values between `0` & `255` or a text \r\n        description of the color, e.g. \"red\". \r\n        \r\n        A dictionary of available colors can be obtained from `bluedot.COLORS`.\r\n\r\n    :param bool square:\r\n        When set to `True` the button is made square.\r\n\r\n    :param bool border:\r\n        When set to `True` adds a border to the button.\r\n\r\n    :param bool visible:\r\n        When set to `False` the button will be hidden.\r\n    \"\"\"\r\n    def __init__(self, bd, col, row, color, square, border, visible):\r\n        self._bd = bd\r\n        self.col = col\r\n        self.row = row\r\n        \r\n        self._interaction = None\r\n   \r\n        # setup the \"dot\"\r\n        super().__init__(color, square, border, visible)\r\n\r\n    @property\r\n    def color(self):\r\n        return super(BlueDotButton, self.__class__).color.fget(self)\r\n        \r\n    @color.setter\r\n    def color(self, value):\r\n        super(BlueDotButton, self.__class__).color.fset(self, value)\r\n        self._send_config()\r\n\r\n    @property\r\n    def square(self):\r\n        return super(BlueDotButton, self.__class__).square.fget(self)\r\n\r\n    @square.setter\r\n    def square(self, value):\r\n        super(BlueDotButton, self.__class__).square.fset(self, value)\r\n        self._send_config()\r\n\r\n    @property\r\n    def border(self):\r\n        return super(BlueDotButton, self.__class__).border.fget(self)\r\n\r\n    @border.setter\r\n    def border(self, value):\r\n        super(BlueDotButton, self.__class__).border.fset(self, value)\r\n        self._send_config()\r\n\r\n    @property\r\n    def visible(self):\r\n        return super(BlueDotButton, self.__class__).visible.fget(self)\r\n\r\n    @visible.setter\r\n    def visible(self, value):\r\n        super(BlueDotButton, self.__class__).visible.fset(self, value)\r\n        self._send_config()\r\n\r\n    @property\r\n    def modified(self):\r\n        \"\"\"\r\n        Returns `True` if the button's appearance has been modified [is \r\n        different] from the default.  \r\n        \"\"\"\r\n        return not (\r\n            self.color == self._bd.color and \r\n            self.visible == self._bd.visible and\r\n            self.border == self._bd.border and\r\n            self.square == self._bd.square\r\n            )\r\n\r\n    @property\r\n    def interaction(self):\r\n        \"\"\"\r\n        Returns an instance of :class:`BlueDotInteraction` representing the\r\n        current or last interaction with the button.\r\n\r\n        .. note::\r\n\r\n            If the button is released (and inactive), :attr:`interaction`\r\n            will return the interaction when it was released, until it is\r\n            pressed again.  If the button has never been pressed\r\n            :attr:`interaction` will return ``None``.\r\n        \"\"\"\r\n        return self._interaction\r\n\r\n    def press(self, position):\r\n        \"\"\"\r\n        Processes any \"pressed\" events associated with this button.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the dot was pressed.\r\n        \"\"\"\r\n        super().press(position)\r\n\r\n        # create new interaction\r\n        self._interaction = BlueDotInteraction(position)\r\n\r\n    def release(self, position):\r\n        \"\"\"\r\n        Processes any \"released\" events associated with this button.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        super().release(position)\r\n\r\n        self._interaction.released(position)\r\n\r\n    def move(self, position):\r\n        \"\"\"\r\n        Processes any \"released\" events associated with this button.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        super().move(position)\r\n\r\n        self._interaction.moved(position)\r\n\r\n    def is_double_press(self, position):\r\n        \"\"\"\r\n        Returns True if the position passed represents a double press.\r\n\r\n        i.e. The last interaction was the button was to release it, and\r\n        the time to press is less than the double_press_time.\r\n\r\n        :param BlueDotPosition position:\r\n            The BlueDotPosition where the Dot was pressed.\r\n        \"\"\"\r\n        double_press = False\r\n        #was there a previous interaction\r\n        if self._interaction:\r\n            # was the previous interaction complete (i.e. had it been released)\r\n            if not self._interaction.active:\r\n                # was it less than the time threshold (0.3 seconds)\r\n                if self._interaction.duration < self._double_press_time:\r\n                    #was the dot pressed again in less than the threshold\r\n                    if time() - self._interaction.released_position.time < self._double_press_time:\r\n                        double_press = True\r\n        \r\n        return double_press\r\n\r\n    def get_swipe(self):\r\n        \"\"\"\r\n        Returns an instance of :class:`BlueDotSwipe` if the last interaction\r\n        with the button was a swipe. Returns `None` if the button was not \r\n        swiped. \r\n        \"\"\"\r\n        swipe = BlueDotSwipe(self.interaction)\r\n        if swipe.valid:\r\n            return swipe\r\n\r\n    def get_rotation(self):\r\n        \"\"\"\r\n        Returns an instance of :class:`BlueDotRotation` if the last interaction\r\n        with the button was a rotation. Returns `None` if the button was not \r\n        rotated. \r\n        \"\"\"\r\n        # only bother checking to see if its a rotation if `when_rotated`\r\n        # as been set. Performance thang!\r\n        if self.when_rotated or self._bd.when_rotated:\r\n            rotation = BlueDotRotation(self._interaction, self._rotation_segments)\r\n            if rotation.valid:\r\n                return rotation\r\n\r\n    def _build_config_msg(self):\r\n        return \"5,{},{},{},{},{},{}\\n\".format(\r\n                    self.color,\r\n                    int(self.square),\r\n                    int(self.border),\r\n                    int(self.visible),\r\n                    self.col,\r\n                    self.row\r\n                    )\r\n\r\n    def _send_config(self):\r\n        if self._bd.is_connected:\r\n            self._bd._server.send(self._build_config_msg())\r\n\r\nclass BlueDot(Dot):\r\n    \"\"\"\r\n    Interacts with a Blue Dot client application, communicating when and where a \r\n    button has been pressed, released or held.\r\n\r\n    This class starts an instance of :class:`.btcomm.BluetoothServer`\r\n    which manages the connection with the Blue Dot client.\r\n\r\n    This class is intended for use with a Blue Dot client application.\r\n\r\n    The following example will print a message when the Blue Dot button is pressed::\r\n\r\n        from bluedot import BlueDot\r\n        bd = BlueDot()\r\n        bd.wait_for_press()\r\n        print(\"The button was pressed\")\r\n\r\n    Multiple buttons can be created, by changing the number of columns and rows. Each button can be referenced using its [col, row]::\r\n\r\n        bd = BlueDot(cols=2, rows=2)\r\n        bd[0,0].wait_for_press()\r\n        print(\"Top left button pressed\")\r\n        bd[1,1].wait_for_press()\r\n        print(\"Bottom right button pressed\")\r\n\r\n    :param str device:\r\n        The Bluetooth device the server should use, the default is \"hci0\", if\r\n        your device only has 1 Bluetooth adapter this shouldn't need to be changed.\r\n\r\n    :param int port:\r\n        The Bluetooth port the server should use, the default is 1, and under\r\n        normal use this should never need to change.\r\n\r\n    :param bool auto_start_server:\r\n        If ``True`` (the default), the Bluetooth server will be automatically\r\n        started on initialisation; if ``False``, the method :meth:`start` will\r\n        need to be called before connections will be accepted.\r\n\r\n    :param bool power_up_device:\r\n        If ``True``, the Bluetooth device will be powered up (if required) when the\r\n        server starts. The default is ``False``.\r\n\r\n        Depending on how Bluetooth has been powered down, you may need to use :command:`rfkill`\r\n        to unblock Bluetooth to give permission to bluez to power on Bluetooth::\r\n\r\n            sudo rfkill unblock bluetooth\r\n\r\n    :param bool print_messages:\r\n        If ``True`` (the default), server status messages will be printed stating\r\n        when the server has started and when clients connect / disconnect.\r\n\r\n    :param int cols:\r\n        The number of columns in the grid of buttons. Defaults to ``1``.\r\n\r\n    :param int rows:\r\n        The number of rows in the grid of buttons. Defaults to ``1``.\r\n\r\n    \"\"\"\r\n    def __init__(self,\r\n        device = \"hci0\",\r\n        port = 1,\r\n        auto_start_server = True,\r\n        power_up_device = False,\r\n        print_messages = True,\r\n        cols = 1,\r\n        rows = 1):\r\n\r\n        self._data_buffer = \"\"\r\n        self._device = device\r\n        self._port = port\r\n        self._power_up_device = power_up_device\r\n        self._print_messages = print_messages\r\n\r\n        self._check_protocol_event = Event()\r\n        self._is_connected_event = Event()\r\n        self._when_client_connects = None\r\n        self._when_client_connects_background = False\r\n        self._when_client_disconnects = None\r\n        self._when_client_disconnects_background = False\r\n\r\n        # setup the main \"dot\"\r\n        super().__init__(BLUE, False, False, True)\r\n\r\n        # setup the grid\r\n        self._buttons = {}\r\n        self.resize(cols, rows)\r\n\r\n        self._create_server()\r\n\r\n        if auto_start_server:\r\n            self.start()\r\n\r\n    @property\r\n    def buttons(self):\r\n        \"\"\"\r\n        A list of :class:`BlueDotButton` objects in the \"grid\". \r\n        \"\"\"\r\n        return self._buttons.values()\r\n\r\n    @property\r\n    def cols(self):\r\n        \"\"\"\r\n        Sets or returns the number of columns in the grid of buttons.\r\n        \"\"\"\r\n        return self._cols\r\n    \r\n    @cols.setter\r\n    def cols(self, value):\r\n        self.resize(value, self._rows)\r\n\r\n    @property\r\n    def rows(self):\r\n        \"\"\"\r\n        Sets or returns the number of rows in the grid of buttons.\r\n        \"\"\"\r\n        return self._rows\r\n    \r\n    @rows.setter\r\n    def rows(self, value):\r\n        self.resize(self._cols, value)\r\n\r\n    @property\r\n    def device(self):\r\n        \"\"\"\r\n        The Bluetooth device the server is using. This defaults to \"hci0\".\r\n        \"\"\"\r\n        return self._device\r\n\r\n    @property\r\n    def port(self):\r\n        \"\"\"\r\n        The port the server is using. This defaults to 1.\r\n        \"\"\"\r\n        return self._port\r\n\r\n    @property\r\n    def server(self):\r\n        \"\"\"\r\n        The :class:`.btcomm.BluetoothServer` instance that is being used to communicate\r\n        with clients.\r\n        \"\"\"\r\n        return self._server\r\n\r\n    @property\r\n    def adapter(self):\r\n        \"\"\"\r\n        The :class:`.btcomm.BluetoothAdapter` instance that is being used.\r\n        \"\"\"\r\n        return self._server.adapter\r\n\r\n    @property\r\n    def paired_devices(self):\r\n        \"\"\"\r\n        Returns a sequence of devices paired with this adapter\r\n        :code:`[(mac_address, name), (mac_address, name), ...]`::\r\n\r\n            bd = BlueDot()\r\n            devices = bd.paired_devices\r\n            for d in devices:\r\n                device_address = d[0]\r\n                device_name = d[1]\r\n        \"\"\"\r\n        return self._server.adapter.paired_devices\r\n\r\n    @property\r\n    def print_messages(self):\r\n        \"\"\"\r\n        When set to ``True`` messages relating to the status of the Bluetooth server\r\n        will be printed.\r\n        \"\"\"\r\n        return self._print_messages\r\n\r\n    @print_messages.setter\r\n    def print_messages(self, value):\r\n        self._print_messages = value\r\n\r\n    @property\r\n    def running(self):\r\n        \"\"\"\r\n        Returns a ``True`` if the server is running.\r\n        \"\"\"\r\n        return self._server.running\r\n\r\n    @property\r\n    def is_connected(self):\r\n        \"\"\"\r\n        Returns ``True`` if a Blue Dot client is connected.\r\n        \"\"\"\r\n        return self._is_connected_event.is_set()\r\n\r\n    @property\r\n    def is_pressed(self):\r\n        \"\"\"\r\n        Returns ``True`` if the button is pressed (or held).\r\n\r\n        .. note::\r\n\r\n            If there are multiple buttons, if any button is pressed, `True`\r\n            will be returned.\r\n        \"\"\"\r\n        for button in self.buttons:\r\n            if button._is_pressed:\r\n                return True\r\n\r\n        return False\r\n\r\n    @property\r\n    def interaction(self):\r\n        \"\"\"\r\n        Returns an instance of :class:`BlueDotInteraction` representing the\r\n        current or last interaction with the Blue Dot.\r\n\r\n        .. note::\r\n\r\n            If the Blue Dot is released (and inactive), :attr:`interaction`\r\n            will return the interaction when it was released, until it is\r\n            pressed again.  If the Blue Dot has never been pressed\r\n            :attr:`interaction` will return ``None``.\r\n\r\n            If there are multiple buttons, the interaction will only be \r\n            returned for button [0,0]\r\n\r\n        .. deprecated:: 2.0.0\r\n\r\n        \"\"\"\r\n        return self._get_button((0,0)).interaction\r\n\r\n    @property\r\n    def rotation_segments(self):\r\n        \"\"\"\r\n        Sets or returns the number of virtual segments the button is split into for rotating.\r\n        Defaults to 8.\r\n\r\n        .. note::\r\n        \r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).rotation_segments.fget(self)\r\n\r\n    @rotation_segments.setter\r\n    def rotation_segments(self, value):\r\n        super(BlueDot, self.__class__).rotation_segments.fset(self, value)\r\n        for button in self.buttons:\r\n            button.rotation_segments = value\r\n\r\n    @property\r\n    def double_press_time(self):\r\n        \"\"\"\r\n        Sets or returns the time threshold in seconds for a double press. Defaults to 0.3.\r\n\r\n        .. note::\r\n        \r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).double_press_time.fget(self)\r\n\r\n    @double_press_time.setter\r\n    def double_press_time(self, value):\r\n        super(BlueDot, self.__class__).double_press_time.fset(self, value)\r\n        for button in self.buttons:\r\n            button.double_press_time = value\r\n\r\n    @property\r\n    def color(self):\r\n        \"\"\"\r\n        Sets or returns the color of the button. Defaults to BLUE.\r\n\r\n        An instance of :class:`.colors.Color` is returned.\r\n\r\n        Value can be set as a :class:`.colors.Color` object, a hex color value\r\n        in the format `#rrggbb` or `#rrggbbaa`, a tuple of `(red, green, blue)`\r\n        or `(red, green, blue, alpha)` values between `0` & `255` or a text \r\n        description of the color, e.g. \"red\". \r\n        \r\n        A dictionary of available colors can be obtained from `bluedot.COLORS`.\r\n\r\n        .. note::\r\n        \r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).color.fget(self)\r\n        \r\n    @color.setter\r\n    def color(self, value):\r\n        super(BlueDot, self.__class__).color.fset(self, value)\r\n        for button in self.buttons:\r\n            button.color = value\r\n\r\n    @property\r\n    def square(self):\r\n        \"\"\"\r\n        When set to `True` the 'dot' is made square. Default is `False`.\r\n\r\n        .. note::\r\n        \r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).square.fget(self)\r\n\r\n    @square.setter\r\n    def square(self, value):\r\n        super(BlueDot, self.__class__).square.fset(self, value)\r\n        for button in self.buttons:\r\n            button.square = value\r\n\r\n    @property\r\n    def border(self):\r\n        \"\"\"\r\n        When set to `True` adds a border to the dot. Default is `False`.\r\n\r\n        .. note::\r\n        \r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).border.fget(self)\r\n\r\n    @border.setter\r\n    def border(self, value):\r\n        super(BlueDot, self.__class__).border.fset(self, value)\r\n        for button in self.buttons:\r\n            button.border = value\r\n\r\n    @property\r\n    def visible(self):\r\n        \"\"\"\r\n        When set to `False` the dot will be hidden. Default is `True`.\r\n\r\n        .. note::\r\n\r\n            Events (press, release, moved) are still sent from the dot\r\n            when it is not visible.\r\n\r\n            If there are multiple buttons in the grid, the 'default' value\r\n            will be returned and when set all buttons will be updated.\r\n        \"\"\"\r\n        return super(BlueDot, self.__class__).visible.fget(self)\r\n\r\n    @visible.setter\r\n    def visible(self, value):\r\n        super(BlueDot, self.__class__).visible.fset(self, value)\r\n        for button in self.buttons:\r\n            button.visible = value\r\n\r\n    @property\r\n    def when_client_connects(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when a Blue Dot \r\n        application connects.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_client_connects(function, background=True)`\r\n        \"\"\"\r\n        return self._when_client_connects\r\n\r\n    @when_client_connects.setter\r\n    def when_client_connects(self, value):\r\n        self.set_when_client_connects(value)\r\n\r\n    def set_when_client_connects(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when a Blue Dot connects.\r\n        \r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_client_connects = callback\r\n        self._when_client_connects_background = background\r\n\r\n    @property\r\n    def when_client_disconnects(self):\r\n        \"\"\"\r\n        Sets or returns the function which is called when a Blue Dot disconnects.\r\n\r\n        The function will be run in the same thread and block, to run in a separate \r\n        thread use `set_when_client_disconnects(function, background=True)`\r\n        \"\"\"\r\n        return self._when_client_disconnects\r\n\r\n    @when_client_disconnects.setter\r\n    def when_client_disconnects(self, value):\r\n        self.set_when_client_disconnects(value)\r\n\r\n    def set_when_client_disconnects(self, callback, background=False):\r\n        \"\"\"\r\n        Sets the function which is called when a Blue Dot disconnects.\r\n        \r\n        :param Callable callback:\r\n            The function to call, setting to `None` will stop the callback.\r\n\r\n        :param bool background:\r\n            If set to `True` the function will be run in a separate thread \r\n            and it will return immediately. The default is `False`.\r\n        \"\"\"\r\n        self._when_client_disconnects = callback\r\n        self._when_client_disconnects_background = background\r\n\r\n    def wait_for_connection(self, timeout = None):\r\n        \"\"\"\r\n        Waits until a Blue Dot client connects.\r\n        Returns ``True`` if a client connects.\r\n\r\n        :param float timeout:\r\n            Number of seconds to wait for a wait connections, if ``None`` (the default),\r\n            it will wait indefinetly for a connection from a Blue Dot client.\r\n        \"\"\"\r\n        return self._is_connected_event.wait(timeout)\r\n\r\n    def start(self):\r\n        \"\"\"\r\n        Start the :class:`.btcomm.BluetoothServer` if it is not already \r\n        running. By default the server is started at initialisation.\r\n        \"\"\"\r\n        self._server.start()\r\n        self._print_message(\"Server started {}\".format(self.server.server_address))\r\n        self._print_message(\"Waiting for connection\")\r\n\r\n    def _create_server(self):\r\n        self._server = BluetoothServer(\r\n                self._data_received,\r\n                when_client_connects = self._client_connected,\r\n                when_client_disconnects = self._client_disconnected,\r\n                device = self.device,\r\n                port = self.port,\r\n                power_up_device = self._power_up_device,\r\n                auto_start = False)\r\n\r\n    def stop(self):\r\n        \"\"\"\r\n        Stop the Bluetooth server.\r\n        \"\"\"\r\n        self._server.stop()\r\n\r\n    def allow_pairing(self, timeout = 60):\r\n        \"\"\"\r\n        Allow a Bluetooth device to pair with your Raspberry Pi by putting\r\n        the adapter into discoverable and pairable mode.\r\n\r\n        :param int timeout:\r\n            The time in seconds the adapter will remain pairable. If set to ``None``\r\n            the device will be discoverable and pairable indefinetly.\r\n        \"\"\"\r\n        self.server.adapter.allow_pairing(timeout = timeout)\r\n\r\n    def resize(self, cols, rows):\r\n        \"\"\"\r\n        Resizes the grid of buttons. \r\n\r\n        :param int cols:\r\n            The number of columns in the grid of buttons.\r\n\r\n        :param int rows:\r\n            The number of rows in the grid of buttons.\r\n\r\n        .. note::\r\n            Existing buttons will retain their state (color, border, etc) when \r\n            resized. New buttons will be created with the default values set \r\n            by the :class:`BlueDot`.\r\n        \"\"\"\r\n        self._cols = cols\r\n        self._rows = rows        \r\n\r\n        # create new buttons\r\n        new_buttons = {}\r\n\r\n        for c in range(cols):\r\n            for r in range(rows):\r\n                # if button already exist, reuse it\r\n                if (c,r) in self._buttons.keys():\r\n                    new_buttons[c,r] = self._buttons[c,r]\r\n                else:   \r\n                    new_buttons[c,r] = BlueDotButton(self, c, r, self._color, self._square, self._border, self._visible)\r\n                \r\n        self._buttons = new_buttons\r\n\r\n        self._send_bluedot_config()\r\n\r\n    def _get_button(self, key):\r\n        try:\r\n            return self._buttons[key]\r\n        except KeyError:\r\n            raise ButtonDoesNotExist(\"The button `{}` does not exist\".format(key))\r\n\r\n    def _client_connected(self):\r\n        self._is_connected_event.set()\r\n        self._print_message(\"Client connected {}\".format(self.server.client_address))\r\n        self._send_bluedot_config()\r\n        if self.when_client_connects:\r\n            self._process_callback(self.when_client_connects, None, self._when_client_connects_background)\r\n        \r\n        # wait for the protocol version to be checked.\r\n        if not self._check_protocol_event.wait(CHECK_PROTOCOL_TIMEOUT):\r\n            self._print_message(\"Protocol version not received from client - do you need to update the client to the latest version?\")\r\n            self._server.disconnect_client()\r\n\r\n    def _client_disconnected(self):\r\n        self._is_connected_event.clear()\r\n        self._check_protocol_event.clear()\r\n        self._print_message(\"Client disconnected\")\r\n        if self.when_client_disconnects:\r\n            self._process_callback(self.when_client_disconnects, None, self._when_client_disconnects_background)\r\n\r\n    def _data_received(self, data):\r\n        #add the data received to the buffer\r\n        self._data_buffer += data\r\n\r\n        #get any full commands ended by \\n\r\n        last_command = self._data_buffer.rfind(\"\\n\")\r\n        if last_command != -1:\r\n            commands = self._data_buffer[:last_command].split(\"\\n\")\r\n            #remove the processed commands from the buffer\r\n            self._data_buffer = self._data_buffer[last_command + 1:]\r\n            self._process_commands(commands)\r\n\r\n    def _process_commands(self, commands):\r\n        for command in commands:\r\n            # debug - print each command\r\n            # print(command)\r\n\r\n            operation = command.split(\",\")[0]\r\n            params = command.split(\",\")[1:]\r\n            \r\n            # dot change operation?\r\n            if operation in [\"0\", \"1\", \"2\"]:\r\n\r\n                position = None\r\n                try:\r\n                    button, position = self._parse_interaction_msg(operation, params)\r\n                    self._position = position\r\n                except ValueError:\r\n                    # warn about the occasional corrupt command\r\n                    warnings.warn(\"Data received which could not be parsed.\\n{}\".format(command))\r\n                except ButtonDoesNotExist:\r\n                    # data received for a button which could not be found\r\n                    warnings.warn(\"Data received for a button which does not exist.\\n{}\".format(command))\r\n                else:\r\n                    # dot released\r\n                    if operation == \"0\":\r\n                        self._process_release(button, position)\r\n                        \r\n                    # dot pressed\r\n                    elif operation == \"1\":\r\n                        self._process_press(button, position)\r\n                        \r\n                    # dot pressed position moved \r\n                    elif operation == \"2\":\r\n                        self._process_move(button, position)\r\n                        \r\n            # protocol check\r\n            elif operation == \"3\":\r\n                self._check_protocol_version(params[0], params[1])\r\n\r\n            else:\r\n                # operation not identified...  \r\n                warnings.warn(\"Data received for an unknown operation.\\n{}\".format(command))\r\n\r\n    def _parse_interaction_msg(self, operation, params):\r\n        \"\"\"\r\n        Parses an interaction (press, move, release) message and returns \r\n        the component parts\r\n        \"\"\"\r\n        # parse message\r\n        col = int(params[0])\r\n        row = int(params[1])\r\n        position = BlueDotPosition(col, row, params[2], params[3])\r\n        button = self._get_button((col, row))\r\n        \r\n        return button, position\r\n\r\n    def _process_press(self, button, position):\r\n        # was the button double pressed?\r\n        if button.is_double_press(position):\r\n            self.double_press(position)\r\n            button.double_press(position)\r\n        \r\n        # set the blue dot and button as pressed\r\n        self.press(position)\r\n        button.press(position)\r\n\r\n    def _process_move(self, button, position):\r\n        # set the blue dot as moved\r\n        self.move(position)\r\n        # set the button as moved\r\n        button.move(position)\r\n        # was it a rotation\r\n        rotation = button.get_rotation()\r\n        if rotation is not None:\r\n            self.rotate(rotation)\r\n            button.rotate(rotation)\r\n\r\n    def _process_release(self, button, position):\r\n        # set the blue dot as released\r\n        self.release(position)\r\n        # set the button as released\r\n        button.release(position)\r\n        \r\n        # was it a swipe?\r\n        swipe = button.get_swipe()\r\n        if swipe is not None:\r\n            self.swipe(swipe)\r\n            button.swipe(swipe)\r\n                    \r\n    def _check_protocol_version(self, protocol_version, client_name):\r\n        try:\r\n            version_no = int(protocol_version)\r\n        except ValueError:\r\n            raise ValueError(\"protocol version number must be numeric, received {}.\".format(protocol_version)) \r\n        self._check_protocol_event.set()\r\n        \r\n        if version_no != PROTOCOL_VERSION:\r\n            msg = \"Client '{}' was using protocol version {}, bluedot python library is using version {}. \"\r\n            if version_no > PROTOCOL_VERSION:\r\n                msg += \"Update the bluedot python library, using 'sudo pip3 --upgrade install bluedot'.\"\r\n                msg = msg.format(client_name, protocol_version, PROTOCOL_VERSION)\r\n            else:\r\n                msg += \"Update the {}.\"\r\n                msg = msg.format(client_name, protocol_version, PROTOCOL_VERSION, client_name)\r\n            self._server.disconnect_client()\r\n            print(msg)\r\n        \r\n    # called whenever the BlueDot configuration is changed or a client connects\r\n    def _send_bluedot_config(self):\r\n        if self.is_connected:\r\n            self._server.send(\r\n                \"4,{},{},{},{},{},{}\\n\".format(\r\n                    self._color.str_rgba, \r\n                    int(self._square),\r\n                    int(self._border),\r\n                    int(self._visible),\r\n                    self._cols,\r\n                    self._rows\r\n                    )\r\n                )\r\n\r\n            # send the configuration for the individual buttons\r\n            button_config_msg = \"\"\r\n            for button in self.buttons:\r\n                if button.modified:\r\n                    button_config_msg += button._build_config_msg()\r\n\r\n            if button_config_msg != \"\":\r\n                self._server.send(button_config_msg)\r\n\r\n    def _print_message(self, message):\r\n        if self.print_messages:\r\n            print(message)\r\n\r\n    def __getitem__(self, key):\r\n        return self._get_button(key)\r\n"
  },
  {
    "path": "bluedot/exceptions.py",
    "content": "class ButtonDoesNotExist(Exception):\r\n    pass"
  },
  {
    "path": "bluedot/interactions.py",
    "content": "from time import time\r\nfrom math import atan2, degrees, hypot\r\n\r\nclass BlueDotPosition:\r\n    \"\"\"\r\n    Represents a position of where the blue dot is pressed, released or held.\r\n\r\n    :param float x:\r\n        The x position of the Blue Dot, 0 being centre, -1 being far left\r\n        and 1 being far right.\r\n\r\n    :param float y:\r\n        The y position of the Blue Dot, 0 being centre, -1 being at the\r\n        bottom and 1 being at the top.\r\n    \"\"\"\r\n    def __init__(self, col, row, x, y):\r\n        self._time = time()\r\n        self._col = int(col)\r\n        self._row = int(row)\r\n        self._x = self._clamped(float(x))\r\n        self._y = self._clamped(float(y))\r\n        self._angle = None\r\n        self._distance = None\r\n\r\n    def _clamped(self, v):\r\n        return max(-1, min(1, v))\r\n\r\n    @property\r\n    def col(self):\r\n        \"\"\"\r\n        The column.\r\n        \"\"\"\r\n        return self._col\r\n\r\n    @property\r\n    def row(self):\r\n        \"\"\"\r\n        The row.\r\n        \"\"\"\r\n        return self._row\r\n\r\n    @property\r\n    def x(self):\r\n        \"\"\"\r\n        The x position of the Blue Dot, 0 being centre, -1 being far\r\n        left and 1 being far right.\r\n        \"\"\"\r\n        return self._x\r\n\r\n    @property\r\n    def y(self):\r\n        \"\"\"\r\n        The y position of the Blue Dot, 0 being centre, -1 being at\r\n        the bottom and 1 being at the top.\r\n        \"\"\"\r\n        return self._y\r\n\r\n    @property\r\n    def angle(self):\r\n        \"\"\"\r\n        The angle from centre of where the Blue Dot is pressed, held or released.\r\n        0 degrees is up, 0..180 degrees clockwise, -180..0 degrees anti-clockwise.\r\n        \"\"\"\r\n        if self._angle is None:\r\n            self._angle = degrees(atan2(self.x, self.y))\r\n        return self._angle\r\n\r\n    @property\r\n    def distance(self):\r\n        \"\"\"\r\n        The distance from centre of where the Blue Dot is pressed, held or released.\r\n        The radius of the Blue Dot is 1.\r\n        \"\"\"\r\n        if self._distance is None:\r\n            self._distance = self._clamped(hypot(self.x, self.y))\r\n        return self._distance\r\n\r\n    @property\r\n    def middle(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot is pressed, held or released in the middle.\r\n        \"\"\"\r\n        return self.distance <= 0.5\r\n\r\n    @property\r\n    def top(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot is pressed, held or released at the top.\r\n        \"\"\"\r\n        return self.distance > 0.5 and (-45 < self.angle <= 45)\r\n\r\n    @property\r\n    def right(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot is pressed, held or released on the right.\r\n        \"\"\"\r\n        return self.distance > 0.5 and (45 < self.angle <= 135)\r\n\r\n    @property\r\n    def bottom(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot is pressed, held or released at the bottom.\r\n        \"\"\"\r\n        return self.distance > 0.5 and (self.angle > 135 or self.angle <= -135)\r\n\r\n    @property\r\n    def left(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot is pressed, held or released on the left.\r\n        \"\"\"\r\n        return self.distance > 0.5 and (-135 < self.angle <= -45)\r\n\r\n    @property\r\n    def time(self):\r\n        \"\"\"\r\n        The time the blue dot was at this position.\r\n\r\n        .. note::\r\n\r\n            This is the time the message was received from the Blue Dot app,\r\n            not the time it was sent.\r\n        \"\"\"\r\n        return self._time\r\n\r\n    def __str__(self):\r\n        return \"BlueDotPosition - col={}, row={}, x={}, y={}\".format(\r\n            self.col, self.row, self.x, self.y\r\n        )\r\n\r\n\r\nclass BlueDotInteraction:\r\n    \"\"\"\r\n    Represents an interaction with the Blue Dot, from when it was pressed to\r\n    when it was released.\r\n\r\n    A :class:`BlueDotInteraction` can be active or inactive, i.e. it is active\r\n    because the Blue Dot has not been released, or inactive because the Blue\r\n    Dot was released and the interaction finished.\r\n\r\n    :param BlueDotPosition pressed_position:\r\n        The BlueDotPosition when the Blue Dot was pressed.\r\n    \"\"\"\r\n    def __init__(self, pressed_position):\r\n        self._active = True\r\n        self._positions = []\r\n        self._positions.append(pressed_position)\r\n\r\n    @property\r\n    def active(self):\r\n        \"\"\"\r\n        Returns ``True`` if the interaction is still active, i.e. the Blue Dot\r\n        hasnt been released.\r\n        \"\"\"\r\n        return self._active\r\n\r\n    @property\r\n    def positions(self):\r\n        \"\"\"\r\n        A sequence of :class:`BlueDotPosition` instances for all the positions\r\n        which make up this interaction.\r\n\r\n        The first position is where the Blue Dot was pressed, the last is where\r\n        the Blue Dot was released, all position in between are where the position\r\n        Blue Dot changed (i.e. moved) when it was held down.\r\n        \"\"\"\r\n        return self._positions\r\n\r\n    @property\r\n    def pressed_position(self):\r\n        \"\"\"\r\n        Returns the position when the Blue Dot was pressed i.e. where the\r\n        interaction started.\r\n        \"\"\"\r\n        return self._positions[0]\r\n\r\n    @property\r\n    def released_position(self):\r\n        \"\"\"\r\n        Returns the position when the Blue Dot was released i.e. where the\r\n        interaction ended.\r\n\r\n        If the interaction is still active it returns ``None``.\r\n        \"\"\"\r\n        return self._positions[-1] if not self.active else None\r\n\r\n    @property\r\n    def current_position(self):\r\n        \"\"\"\r\n        Returns the current position for the interaction.\r\n\r\n        If the interaction is inactive, it will return the position when the\r\n        Blue Dot was released.\r\n        \"\"\"\r\n        return self._positions[-1]\r\n\r\n    @property\r\n    def previous_position(self):\r\n        \"\"\"\r\n        Returns the previous position for the interaction.\r\n\r\n        If the interaction contains only 1 position, None will be returned.\r\n        \"\"\"\r\n        return self._positions[-2] if len(self._positions) > 1 else None\r\n\r\n    @property\r\n    def duration(self):\r\n        \"\"\"\r\n        Returns the duration in seconds of the interaction, i.e. the amount time\r\n        between when the Blue Dot was pressed and now or when it was released.\r\n        \"\"\"\r\n        if self.active:\r\n            return time() - self.pressed_position.time\r\n        else:\r\n            return self.released_position.time - self.pressed_position.time\r\n\r\n    @property\r\n    def distance(self):\r\n        \"\"\"\r\n        Returns the total distance of the Blue Dot interaction\r\n        \"\"\"\r\n        dist = 0\r\n        for i in range(1, len(self._positions)):\r\n            p1 = self._positions[i-1]\r\n            p2 = self._positions[i]\r\n            dist += hypot(p2.x - p1.x, p2.y - p1.y)\r\n\r\n        return dist\r\n\r\n    def moved(self, moved_position):\r\n        \"\"\"\r\n        Adds an additional position to the interaction, called when the position\r\n        the Blue Dot is pressed moves.\r\n        \"\"\"\r\n        if self._active:\r\n            self._positions.append(moved_position)\r\n\r\n    def released(self, released_position):\r\n        \"\"\"\r\n        Called when the Blue Dot is released and completes a Blue Dot interaction\r\n\r\n        :param BlueDotPosition released_position:\r\n            The BlueDotPosition when the Blue Dot was released.\r\n        \"\"\"\r\n        self._active = False\r\n        self._positions.append(released_position)\r\n\r\n\r\nclass BlueDotSwipe:\r\n    \"\"\"\r\n    Represents a Blue Dot swipe interaction.\r\n\r\n    A :class:`BlueDotSwipe` can be valid or invalid based on whether the Blue Dot\r\n    interaction was a swipe or not.\r\n\r\n    :param BlueDotInteraction interaction:\r\n        The BlueDotInteraction object to be used to determine whether the interaction\r\n        was a swipe.\r\n    \"\"\"\r\n    def __init__(self, interaction):\r\n        self._interaction = interaction\r\n        self._col = interaction.current_position.col\r\n        self._col = interaction.current_position.col\r\n        self._speed_threshold = 2\r\n        self._angle = None\r\n        self._distance = None\r\n        self._valid = self._is_valid_swipe()\r\n\r\n    def _is_valid_swipe(self):\r\n        #the validity of a swipe is based on the speed of the interaction,\r\n        # so a short fast swipe is valid as well as a long slow swipe\r\n        #self._speed = self.distance / self.interaction.duration\r\n        self._speed = self.distance / self.interaction.duration\r\n        if not self.interaction.active and self._speed > self._speed_threshold:\r\n            return True\r\n        else:\r\n            return False\r\n\r\n    @property\r\n    def col(self):\r\n        \"\"\"\r\n        The column.\r\n        \"\"\"\r\n        return self.interaction.current_position.col\r\n\r\n    @property\r\n    def row(self):\r\n        \"\"\"\r\n        The row.\r\n        \"\"\"\r\n        return self.interaction.current_position.row\r\n\r\n    @property\r\n    def interaction(self):\r\n        \"\"\"\r\n        The :class:`BlueDotInteraction` object relating to this swipe.\r\n        \"\"\"\r\n        return self._interaction\r\n\r\n    @property\r\n    def valid(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot interaction is a swipe.\r\n        \"\"\"\r\n        return self._valid\r\n\r\n    @property\r\n    def distance(self):\r\n        \"\"\"\r\n        Returns the distance of the swipe (i.e. the distance between the pressed\r\n        and released positions)\r\n        \"\"\"\r\n        # should this be the total length of the swipe. All the points? It might be slow to calculate\r\n        if self._distance == None:\r\n            self._distance = hypot(\r\n                self.interaction.released_position.x - self.interaction.pressed_position.x,\r\n                self.interaction.released_position.y - self.interaction.pressed_position.y)\r\n\r\n        return self._distance\r\n\r\n    @property\r\n    def angle(self):\r\n        \"\"\"\r\n        Returns the angle of the swipe (i.e. the angle between the pressed\r\n        and released positions)\r\n        \"\"\"\r\n        if self._angle == None:\r\n            self._angle = degrees(atan2(\r\n                self.interaction.released_position.x - self.interaction.pressed_position.x,\r\n                self.interaction.released_position.y - self.interaction.pressed_position.y))\r\n\r\n        return self._angle\r\n\r\n    @property\r\n    def speed(self):\r\n        \"\"\"\r\n        Returns the speed of the swipe in Blue Dot radius / second.\r\n        \"\"\"\r\n        return self._speed\r\n\r\n    @property\r\n    def up(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was swiped up.\r\n        \"\"\"\r\n        return self.valid and (-45 < self.angle <= 45)\r\n\r\n    @property\r\n    def down(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was swiped down.\r\n        \"\"\"\r\n        return self.valid and (self.angle > 135 or self.angle <= -135)\r\n\r\n    @property\r\n    def left(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was swiped left.\r\n        \"\"\"\r\n        return self.valid and (-135 < self.angle <= -45)\r\n\r\n    @property\r\n    def right(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was swiped right.\r\n        \"\"\"\r\n        return self.valid and (45 < self.angle <= 135)\r\n\r\n    @property\r\n    def direction(self):\r\n        \"\"\"\r\n        Returns the direction (\"up\", \"down\", \"left\", \"right\") of the swipe.\r\n        If the swipe is not valid `None` is returned. \r\n        \"\"\"\r\n        if self.up:\r\n            return \"up\"\r\n        elif self.down:\r\n            return \"down\"\r\n        elif self.right:\r\n            return \"right\"\r\n        elif self.left:\r\n            return \"left\"\r\n        else:\r\n            return None\r\n\r\n    def __str__(self):\r\n        return \"BlueDotSwipe - col={}, row={}, direction={}\".format(\r\n            self.col, self.row, self.direction\r\n        )\r\n\r\n\r\nclass BlueDotRotation:\r\n    def __init__(self, interaction, no_of_segments):\r\n        \"\"\"\r\n        Represents a Blue Dot rotation.\r\n\r\n        A :class:`BlueDotRotation` can be valid or invalid based on whether the Blue Dot\r\n        interaction was a rotation or not.\r\n\r\n        :param BlueDotInteraction interaction:\r\n            The object to be used to determine whether the interaction\r\n            was a rotation.\r\n        \"\"\"\r\n        self._interaction = interaction\r\n        self._value = 0\r\n        self._clockwise = False\r\n        self._anti_clockwise = False\r\n        self._previous_segment = 0\r\n        self._current_segment = 0\r\n\r\n        prev_pos = interaction.previous_position\r\n        pos = interaction.current_position\r\n\r\n        # was there a previous position (i.e. the interaction has more than 2 positions)\r\n        if prev_pos != None:\r\n\r\n            # were both positions in the 'outer circle'\r\n            if prev_pos.distance > 0.5 and pos.distance > 0.5:\r\n\r\n                # what segments are the positions in\r\n                deg_per_seg = (360 / no_of_segments)\r\n                self._previous_segment = int((prev_pos.angle + 180) / deg_per_seg) + 1\r\n                self._current_segment = int((pos.angle + 180) / deg_per_seg) + 1\r\n\r\n                # were the positions in different segments\r\n                if self._previous_segment != self._current_segment:\r\n                    # calculate the rotation\r\n                    diff = self._previous_segment - self._current_segment\r\n                    if diff != 0:\r\n                        if diff == -1:\r\n                            self._value = 1\r\n                        elif diff == 1:\r\n                            self._value = -1\r\n                        elif diff == (no_of_segments - 1):\r\n                            self._value = 1\r\n                        elif diff == (1 - no_of_segments):\r\n                            self._value = -1\r\n\r\n    @property\r\n    def col(self):\r\n        \"\"\"\r\n        The column.\r\n        \"\"\"\r\n        return self.interaction.current_position.col\r\n\r\n    @property\r\n    def row(self):\r\n        \"\"\"\r\n        The row.\r\n        \"\"\"\r\n        return self.interaction.current_position.row\r\n\r\n    @property\r\n    def valid(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was rotated.\r\n        \"\"\"\r\n        return self._value != 0\r\n\r\n    @property\r\n    def interaction(self):\r\n        \"\"\"\r\n        The :class:`BlueDotInteraction` object relating to this rotation.\r\n        \"\"\"\r\n        return self._interaction\r\n\r\n    @property\r\n    def value(self):\r\n        \"\"\"\r\n        Returns 0 if the Blue Dot wasn't rotated, -1 if rotated anti-clockwise and 1 if rotated clockwise.\r\n        \"\"\"\r\n        return self._value\r\n\r\n    @property\r\n    def anti_clockwise(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was rotated anti-clockwise.\r\n        \"\"\"\r\n        return self._value == -1\r\n\r\n    @property\r\n    def clockwise(self):\r\n        \"\"\"\r\n        Returns ``True`` if the Blue Dot was rotated clockwise.\r\n        \"\"\"\r\n        return self._value == 1\r\n\r\n    def __str__(self):\r\n        return \"BlueDotRotation - col={}, row={}, value={}\".format(\r\n            self.col, self.row, self.value\r\n        )\r\n"
  },
  {
    "path": "bluedot/mock.py",
    "content": "from .btcomm import BluetoothServer, BluetoothClient, BluetoothAdapter\nfrom .dot import BlueDot\nfrom .threads import WrapThread\nfrom .constants import PROTOCOL_VERSION\n\nCLIENT_NAME = \"Mock client\"\n\nclass MockBluetoothAdapter(BluetoothAdapter):\n    def __init__(self, device = \"mock0\", address = \"00:00:00:00:00:00\"):\n        self._device = device\n        self._address = address\n        self._powered = True\n        self._discoverable = False\n        self._pairable = False\n        self._pairing_thread = None\n\n    @property\n    def powered(self):\n        return self._powered\n\n    @powered.setter\n    def powered(self, value):\n        self._powered = value\n\n    @property\n    def discoverable(self):\n        return self._discoverable\n\n    @discoverable.setter\n    def discoverable(self, value):\n        self._discoverable = value\n\n    @property\n    def pairable(self):\n        return self._pairable\n\n    @pairable.setter\n    def pairable(self, value):\n        self._pairable = value\n\n    @property\n    def paired_devices(self):\n        return [[\"01:01:01:01:01:01\", \"mock_device_1\"], [\"02:02:02:02:02:02\", \"mock_device_2\"]]\n\n\nclass MockBluetoothServer(BluetoothServer):\n    \"\"\"\n    :class:`MockBluetoothServer` inherits from\n    :class:`~.btcomm.BluetoothServer` but overrides ``__init__``, :meth:`start`\n    , :meth:`stop` and :meth:`send_raw` to create a :class:`MockBluetoothServer` which can\n    be used for testing and debugging.\n    \"\"\"\n    def __init__(self,\n        data_received_callback,\n        auto_start = True,\n        device = \"mock0\",\n        port = 1,\n        encoding = \"utf-8\",\n        power_up_device = False,\n        when_client_connects = None,\n        when_client_disconnects = None):\n\n        super(MockBluetoothServer, self).__init__(\n            data_received_callback,\n            auto_start,\n            device,\n            port,\n            encoding,\n            power_up_device,\n            when_client_connects,\n            when_client_disconnects)\n\n        self._mock_client = None\n\n    def start(self):\n        self._running = True\n\n    def stop(self):\n        self._running = False\n\n    def mock_client_connected(self, mock_client = None):\n        \"\"\"\n        Simulates a client connected to the :class:`~.btcomm.BluetoothServer`.\n        \n        :param MockBluetoothClient mock_client:\n            The mock client to interact with, defaults to `None`. If `None`, \n            client address is set to '99:99:99:99:99:99'\n        \"\"\"\n        self._mock_client = mock_client\n\n        if not self._client_connected:\n            if self._mock_client is None:\n                client_address = \"99:99:99:99:99:99\"\n            else:\n                client_address = self._mock_client.adapter.address\n            self._client_connected = True\n            self._client_info = (client_address, self.port)\n            #call the call back\n            if self.when_client_connects:\n                WrapThread(target=self.when_client_connects).start()\n\n    def mock_client_disconnected(self):\n        \"\"\"\n        Simulates a client disconnecting from the\n        :class:`~.btcomm.BluetoothServer`.\n        \"\"\"\n        if self._client_connected:\n            self._client_connected = False\n            self._client_info = None\n            if self._when_client_disconnects:\n                WrapThread(target=self.when_client_disconnects).start()\n\n    def mock_client_sending_data(self, data):\n        \"\"\"\n        Simulates a client sending data to the\n        :class:`~.btcomm.BluetoothServer`.\n        \"\"\"\n        if self._client_connected:\n            self._data_received_callback(data)\n\n    def _send_data(self, data):\n        if self._mock_client is not None:\n            # call the data received callback\n            if self._encoding:\n                data = data.decode(self._encoding)\n            self._mock_client.mock_server_sending_data(data)\n\n    def _setup_adapter(self, device):\n        self._adapter = MockBluetoothAdapter(device)\n\n\nclass MockBluetoothClient(BluetoothClient):\n    \"\"\"\n    :class:`MockBluetoothClient` inherits from\n    :class:`~.btcomm.BluetoothClient` but overrides ``__init__``, :meth:`connect`\n    and :meth:`send_raw` to create a :class:`MockBluetoothServer` which can\n    be used for testing and debugging.\n\n    Note - the `server` parameter should be an instance of :class:`MockBluetoothServer`.\n    \"\"\"\n    def __init__(self,\n        server,\n        data_received_callback,\n        port = 1,\n        device = \"mock1\",\n        encoding = \"utf-8\",\n        power_up_device = False,\n        auto_connect = True):\n\n        super(MockBluetoothClient, self).__init__(\n            server,\n            data_received_callback,\n            port,\n            device,\n            encoding,\n            power_up_device,\n            auto_connect)\n\n    def connect(self):\n        \"\"\"\n        Connect to a Bluetooth server.\n        \"\"\"\n        self._server.mock_client_connected(self)\n        self._connected = True\n\n    def disconnect(self):\n        \"\"\"\n        Disconnect from a Bluetooth server.\n        \"\"\"\n        self._server.mock_client_disconnected()\n        self._connected = False\n\n    def mock_server_sending_data(self, data):\n        \"\"\"\n        Simulates a server sending data to the\n        :class:`~.btcomm.BluetoothClient`.\n        \"\"\"\n        if self._connected:\n            self._data_received_callback(data)\n\n    def _send_data(self, data):\n        # send data to the server\n        # call the data received callback\n        if self._encoding:\n            data = data.decode(self._encoding)\n        self._server.mock_client_sending_data(data)\n\n    def _setup_adapter(self, device):\n        self._adapter = MockBluetoothAdapter(device, address = \"11:11:11:11:11:11\")\n\nclass MockBlueDot(BlueDot):\n    \"\"\"\n    :class:`MockBlueDot` inherits from :class:`BlueDot` but overrides\n    :meth:`_create_server`, to create a :class:`~.mock.MockBluetoothServer`\n    which can be used for testing and debugging.\n    \"\"\"\n    def _create_server(self):\n        self._server = MockBluetoothServer(\n                self._data_received,\n                when_client_connects = self._client_connected,\n                when_client_disconnects = self._client_disconnected,\n                device = self.device,\n                port = self.port,\n                power_up_device = self._power_up_device,\n                auto_start = False)\n\n    def mock_client_connected(self):\n        \"\"\"\n        Simulates a client connecting to the Blue Dot.\n\n        :param string client_address:\n            The mock client mac address, defaults to '11:11:11:11:11:11'\n        \"\"\"\n        self._server.mock_client_connected()\n        # send protocol version to server\n        self._server.mock_client_sending_data(\"3,{},{}\\n\".format(PROTOCOL_VERSION, CLIENT_NAME))\n\n    def mock_client_disconnected(self):\n        \"\"\"\n        Simulates a client disconnecting from the Blue Dot.\n        \"\"\"\n        self._server.mock_client_disconnected()\n\n    def mock_blue_dot_pressed(self, col, row, x, y):\n        \"\"\"\n        Simulates the Blue Dot being pressed.\n\n        :param int col:\n            The column position of the button\n\n        :param int row:\n            The row position of the button \n\n        :param int x:\n            The x position where the button was pressed\n\n        :param int y:\n            The y position where the button was pressed\n        \"\"\"\n        self._server.mock_client_sending_data(\"1,{},{},{},{}\\n\".format(col, row, x, y))\n\n    def mock_blue_dot_released(self, col, row, x, y):\n        \"\"\"\n        Simulates the Blue Dot being released.\n\n        :param int col:\n            The column position of the button\n\n        :param int row:\n            The row position of the button \n\n        :param int x:\n            The x position where the button was released\n\n        :param int y:\n            The y position where the button was released\n\n        \"\"\"\n        self._server.mock_client_sending_data(\"0,{},{},{},{}\\n\".format(col, row, x, y))\n\n    def mock_blue_dot_moved(self, col, row, x, y):\n        \"\"\"\n        Simulates the Blue Dot being moved.\n\n        :param int col:\n            The column position of the button\n\n        :param int row:\n            The row position of the button \n\n        :param int x:\n            The x position where the button was moved too\n\n        :param int y:\n            The y position where the button was moved too\n\n        \"\"\"\n        self._server.mock_client_sending_data(\"2,{},{},{},{}\\n\".format(col, row, x, y))\n\n    def launch_mock_app(self):\n        \"\"\"\n        Launches a mock Blue Dot app.\n\n        The mock app reacts to mouse clicks and movement and calls the mock blue\n        dot methods to simulates presses.\n\n        This is useful for testing, allowing you to interact with Blue Dot without\n        having to script mock functions.\n\n        The mock app uses pygame which will need to be installed.\n        \"\"\"\n        self._mock_app_thread = WrapThread(target=self._launch_mock_app)\n        self._mock_app_thread.start()\n\n    def _launch_mock_app(self):\n        # imported here, so pygame is only a pre-requisite for the mock app\n        from .app import BlueDotClient, ButtonScreen\n\n        class MockBlueDotClient(BlueDotClient):\n            def _run(self):\n                button_screen = MockButtonScreen(self._screen, self._font, self._device, self._server, self._port, self._width, self._height)\n                button_screen.run()\n\n        class MockButtonScreen(ButtonScreen):\n            def _connect(self):\n                self.bt_client = MockBluetoothClient(self.server, self._data_received, device = self.device, auto_connect = True)\n\n        MockBlueDotClient(\"mock2\", self._server, self._port, None, None, None)\n"
  },
  {
    "path": "bluedot/threads.py",
    "content": "import atexit\nfrom threading import Thread, Event\n\n_THREADS = set()\n\ndef _shutdown():\n    while _THREADS:\n        for t in _THREADS.copy():\n            t.stop()\n\natexit.register(_shutdown)\n\n\nclass WrapThread(Thread):\n    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):\n        super(WrapThread, self).__init__(group, target, name, args, kwargs)\n        self.stopping = Event()\n        self.daemon = True\n\n    def start(self):\n        self.stopping.clear()\n        _THREADS.add(self)\n        super(WrapThread, self).start()\n\n    def stop(self):\n        self.stopping.set()\n        self.join()\n\n    def join(self):\n        super(WrapThread, self).join()\n        _THREADS.discard(self)\n"
  },
  {
    "path": "bluedot/utils.py",
    "content": "from __future__ import unicode_literals\n\nimport dbus\nimport time\nimport sys\n\nSERVICE_NAME = \"org.bluez\"\nADAPTER_INTERFACE = SERVICE_NAME + \".Adapter1\"\nDEVICE_INTERFACE = SERVICE_NAME + \".Device1\"\nPROFILE_MANAGER = SERVICE_NAME + \".ProfileManager1\"\n\ndef get_managed_objects():\n    bus = dbus.SystemBus()\n    manager = dbus.Interface(bus.get_object(SERVICE_NAME, \"/\"), \"org.freedesktop.DBus.ObjectManager\")\n    return manager.GetManagedObjects()\n\ndef find_adapter(pattern=None):\n    return find_adapter_in_objects(get_managed_objects(), pattern)\n\ndef find_adapter_in_objects(objects, pattern=None):\n    bus = dbus.SystemBus()\n    for path, ifaces in objects.items():\n        adapter = ifaces.get(ADAPTER_INTERFACE)\n        if adapter is None:\n            continue\n        if not pattern or pattern == adapter[\"Address\"] or path.endswith(pattern):\n            obj = bus.get_object(SERVICE_NAME, path)\n            return dbus.Interface(obj, ADAPTER_INTERFACE)\n    raise Exception(\"Bluetooth adapter {} not found\".format(pattern))\n\ndef get_adapter_property(device_name, prop):\n    bus = dbus.SystemBus()\n    adapter_path = find_adapter(device_name).object_path\n    adapter = dbus.Interface(bus.get_object(SERVICE_NAME, adapter_path),\"org.freedesktop.DBus.Properties\")\n    return adapter.Get(ADAPTER_INTERFACE, prop)\n\ndef get_mac(device_name):\n    return get_adapter_property(device_name, \"Address\")\n\ndef get_adapter_powered_status(device_name):\n    powered = get_adapter_property(device_name, \"Powered\")\n    return bool(powered)\n\ndef get_adapter_discoverable_status(device_name):\n    discoverable = get_adapter_property(device_name, \"Discoverable\")\n    return bool(discoverable)\n\ndef get_adapter_pairable_status(device_name):\n    pairable = get_adapter_property(device_name, \"Pairable\")\n    return bool(pairable)\n\ndef get_paired_devices(device_name):\n    paired_devices = []\n\n    bus = dbus.SystemBus()\n    adapter_path = find_adapter(device_name).object_path\n    om = dbus.Interface(bus.get_object(SERVICE_NAME, \"/\"), \"org.freedesktop.DBus.ObjectManager\")\n    objects = om.GetManagedObjects()\n\n    for path, interfaces in objects.items():\n        if DEVICE_INTERFACE not in interfaces:\n            continue\n        properties = interfaces[DEVICE_INTERFACE]\n        if properties[\"Adapter\"] != adapter_path:\n            continue\n\n        paired_devices.append((str(properties[\"Address\"]), str(properties[\"Alias\"])))\n\n    return paired_devices\n\ndef device_discoverable(device_name, discoverable):\n    bus = dbus.SystemBus()\n    adapter_path = find_adapter(device_name).object_path\n    adapter = dbus.Interface(bus.get_object(SERVICE_NAME, adapter_path),\"org.freedesktop.DBus.Properties\")\n    if discoverable:\n        value = dbus.Boolean(1)\n    else:\n        value = dbus.Boolean(0)\n    adapter.Set(ADAPTER_INTERFACE, \"Discoverable\", value)\n\ndef device_pairable(device_name, pairable):\n    bus = dbus.SystemBus()\n    adapter_path = find_adapter(device_name).object_path\n    adapter = dbus.Interface(bus.get_object(SERVICE_NAME, adapter_path),\"org.freedesktop.DBus.Properties\")\n    if pairable:\n        value = dbus.Boolean(1)\n    else:\n        value = dbus.Boolean(0)\n    adapter.Set(ADAPTER_INTERFACE, \"Pairable\", value)\n\ndef device_powered(device_name, powered):\n    bus = dbus.SystemBus()\n    adapter_path = find_adapter(device_name).object_path\n    adapter = dbus.Interface(bus.get_object(SERVICE_NAME, adapter_path),\"org.freedesktop.DBus.Properties\")\n    if powered:\n        value = dbus.Boolean(1)\n    else:\n        value = dbus.Boolean(0)\n    adapter.Set(ADAPTER_INTERFACE, \"Powered\", value)\n\ndef register_spp(port):\n\n    service_record = \"\"\"\n    <?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n    <record>\n      <attribute id=\"0x0001\">\n        <sequence>\n          <uuid value=\"0x1101\"/>\n        </sequence>\n      </attribute>\n\n      <attribute id=\"0x0004\">\n        <sequence>\n          <sequence>\n            <uuid value=\"0x0100\"/>\n          </sequence>\n          <sequence>\n            <uuid value=\"0x0003\"/>\n            <uint8 value=\"{}\" name=\"channel\"/>\n          </sequence>\n        </sequence>\n      </attribute>\n\n      <attribute id=\"0x0100\">\n        <text value=\"Serial Port\" name=\"name\"/>\n      </attribute>\n    </record>\n    \"\"\".format(port)\n\n    bus = dbus.SystemBus()\n\n    manager = dbus.Interface(bus.get_object(SERVICE_NAME, \"/org/bluez\"), PROFILE_MANAGER)\n\n    path = \"/bluez\"\n    uuid = \"00001101-0000-1000-8000-00805f9b34fb\"\n    opts = {\n#        \"AutoConnect\" : True,\n        \"ServiceRecord\" : service_record\n    }\n\n    try:\n        manager.RegisterProfile(path, uuid, opts)\n    except dbus.exceptions.DBusException as e:\n        #the spp profile has already been registered, ignore\n        if str(e) != \"org.bluez.Error.AlreadyExists: Already Exists\":\n            raise(e)\n"
  },
  {
    "path": "clients/android/.gitignore",
    "content": "*.iml\r\n.gradle\r\n/local.properties\r\n/.idea/workspace.xml\r\n/.idea/libraries\r\n.DS_Store\r\n/build\r\n/captures\r\n.externalNativeBuild\r\n"
  },
  {
    "path": "clients/android/README.rst",
    "content": "Blue Dot Android App\n====================\n\nThe `Blue Dot app`_ is available to download from the Google Play store.\n\nPlease leave a rating and review if you find Blue Dot useful :)\n\n|bluedotapp| |bluedotappdevices|\n\nStart\n-----\n\n1. Download the `Blue Dot app`_ from the Google Play store.\n2. If you havent already done so, pair your raspberry pi as described in the `getting started`_ guide\n3. Run the Blue Dot app\n\n|bluedotappicon|\n\n4. Select your Raspberry Pi from the paired devices list\n\n|bluedotappdevices|\n\n5. Press the Dot\n\n|bluedotapp|\n\n.. _Blue Dot app: http://play.google.com/store/apps/details?id=com.stuffaboutcode.bluedot\n.. _getting started: http://bluedot.readthedocs.io/en/latest/gettingstarted.html\n\n.. |bluedotapp| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotandroid_small.png\n   :height: 247 px\n   :width: 144 px\n   :scale: 100 %\n   :alt: blue dot app\n\n.. |bluedotappdevices| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotandroiddevices_small.png\n   :height: 246 px\n   :width: 144 px\n   :scale: 100 %\n   :alt: blue dot app devices list\n\n.. |bluedotappicon| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotandroidicon.png\n   :height: 143 px\n   :width: 127 px\n   :scale: 100 %\n   :alt: blue dot icon\n"
  },
  {
    "path": "clients/android/app/.gitignore",
    "content": "/build\r\n"
  },
  {
    "path": "clients/android/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\r\n\r\nandroid {\r\n    compileSdkVersion 33\r\n    defaultConfig {\r\n        applicationId \"com.stuffaboutcode.bluedot\"\r\n        minSdkVersion 14\r\n        targetSdkVersion 33\r\n        versionCode 10\r\n        versionName \"2.2.1\"\r\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\r\n    }\r\n    buildTypes {\r\n        release {\r\n            minifyEnabled false\r\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\r\n        }\r\n    }\r\n}\r\n\r\n    dependencies {\r\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\r\n    implementation 'androidx.preference:preference:1.2.0'\r\n    androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {\r\n        exclude group: 'com.android.support', module: 'support-annotations'\r\n    })\r\n    implementation 'androidx.appcompat:appcompat:1.5.1'\r\n    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\r\n    testImplementation 'junit:junit:4.12'\r\n}\r\n\r\nconfigurations {\r\n\r\n    all {\r\n\r\n        exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'\r\n\r\n    }\r\n\r\n}"
  },
  {
    "path": "clients/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\r\n# By default, the flags in this file are appended to flags specified\r\n# in C:\\Users\\Mart\\AppData\\Local\\Android\\Sdk/tools/proguard/proguard-android.txt\r\n# You can edit the include path and order by changing the proguardFiles\r\n# directive in build.gradle.\r\n#\r\n# For more details, see\r\n#   http://developer.android.com/guide/developing/tools/proguard.html\r\n\r\n# Add any project specific keep options here:\r\n\r\n# If your project uses WebView with JS, uncomment the following\r\n# and specify the fully qualified class name to the JavaScript interface\r\n# class:\r\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\r\n#   public *;\r\n#}\r\n\r\n# Uncomment this to preserve the line number information for\r\n# debugging stack traces.\r\n#-keepattributes SourceFile,LineNumberTable\r\n\r\n# If you keep the line number information, uncomment this to\r\n# hide the original source file name.\r\n#-renamesourcefileattribute SourceFile\r\n"
  },
  {
    "path": "clients/android/app/release/output-metadata.json",
    "content": "{\n  \"version\": 3,\n  \"artifactType\": {\n    \"type\": \"APK\",\n    \"kind\": \"Directory\"\n  },\n  \"applicationId\": \"com.stuffaboutcode.bluedot\",\n  \"variantName\": \"release\",\n  \"elements\": [\n    {\n      \"type\": \"SINGLE\",\n      \"filters\": [],\n      \"attributes\": [],\n      \"versionCode\": 10,\n      \"versionName\": \"2.2.1\",\n      \"outputFile\": \"app-release.apk\"\n    }\n  ],\n  \"elementType\": \"File\"\n}"
  },
  {
    "path": "clients/android/app/src/androidTest/java/com/stuffaboutcode/bluedot/ExampleInstrumentedTest.java",
    "content": "package com.stuffaboutcode.bluedot;\r\n\r\nimport android.content.Context;\r\nimport androidx.test.platform.app.InstrumentationRegistry;\r\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\r\n\r\nimport org.junit.Test;\r\nimport org.junit.runner.RunWith;\r\n\r\nimport static org.junit.Assert.*;\r\n\r\n/**\r\n * Instrumentation test, which will execute on an Android device.\r\n *\r\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\r\n */\r\n@RunWith(AndroidJUnit4.class)\r\npublic class ExampleInstrumentedTest {\r\n    @Test\r\n    public void useAppContext() throws Exception {\r\n        // Context of the app under test.\r\n        Context appContext = InstrumentationRegistry.getTargetContext();\r\n\r\n        assertEquals(\"com.stuffaboutcode.bluedot\", appContext.getPackageName());\r\n    }\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    package=\"com.stuffaboutcode.bluedot\">\r\n\r\n    <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"\r\n        android:maxSdkVersion=\"30\" />/>\r\n    <uses-permission android:name=\"android.permission.BLUETOOTH\"\r\n        android:maxSdkVersion=\"30\" />/>\r\n    <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\r\n    <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" />\r\n\r\n    <application\r\n        android:allowBackup=\"true\"\r\n        android:icon=\"@mipmap/ic_launcher_bluebutton\"\r\n        android:label=\"Blue Dot\"\r\n        android:roundIcon=\"@mipmap/ic_launcher_bluebutton\"\r\n        android:supportsRtl=\"true\"\r\n        android:theme=\"@style/AppTheme\">\r\n        <activity\r\n            android:exported=\"true\"\r\n            android:name=\".SettingsActivity\"\r\n            android:label=\"@string/title_activity_settings\"\r\n            android:parentActivityName=\".Devices\">\r\n            <meta-data\r\n                android:name=\"android.support.PARENT_ACTIVITY\"\r\n                android:value=\"com.stuffaboutcode.bluedot.Devices\" />\r\n        </activity>\r\n        <activity\r\n            android:exported=\"true\"\r\n            android:name=\".Devices\"\r\n            android:configChanges=\"orientation|screenSize\">\r\n            <intent-filter>\r\n                <action android:name=\"android.intent.action.VIEW\" />\r\n                <action android:name=\"android.intent.action.MAIN\" />\r\n                <category android:name=\"android.intent.category.LAUNCHER\" />\r\n            </intent-filter>\r\n        </activity>\r\n        <activity\r\n            android:name=\".Button\"\r\n            android:configChanges=\"orientation|screenSize\" />\r\n    </application>\r\n\r\n</manifest>"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/BluetoothChatService.java",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.stuffaboutcode.bluedot;\n\nimport android.bluetooth.BluetoothAdapter;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothServerSocket;\nimport android.bluetooth.BluetoothSocket;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.UUID;\nimport java.lang.reflect.Method;\n\nimport com.stuffaboutcode.logger.Log;\n\n/**\n * This class does all the work for setting up and managing Bluetooth\n * connections with other devices. It has a thread that listens for\n * incoming connections, a thread for connecting with a device, and a\n * thread for performing data transmissions when connected.\n */\npublic class BluetoothChatService {\n    // Debugging\n    private static final String TAG = \"BluetoothChatService\";\n\n    // Name for the SDP record when creating server socket\n    private static final String NAME_SECURE = \"BluetoothChatSecure\";\n    private static final String NAME_INSECURE = \"BluetoothChatInsecure\";\n\n    // Unique UUID for this application\n    private static final UUID MY_UUID_SECURE =\n            UUID.fromString(\"00001101-0000-1000-8000-00805f9b34fb\");\n    private static final UUID MY_UUID_INSECURE =\n            UUID.fromString(\"8ce255c0-200a-11e0-ac64-0800200c9a66\");\n\n    // Member fields\n    private final BluetoothAdapter mAdapter;\n    private final Handler mHandler;\n    private AcceptThread mSecureAcceptThread;\n    private AcceptThread mInsecureAcceptThread;\n    private ConnectThread mConnectThread;\n    private ConnectedThread mConnectedThread;\n    private int mState;\n    private int mNewState;\n\n    // Constants that indicate the current connection state\n    public static final int STATE_NONE = 0;       // we're doing nothing\n    public static final int STATE_LISTEN = 1;     // now listening for incoming connections\n    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection\n    public static final int STATE_CONNECTED = 3;  // now connected to a remote device\n\n    /**\n     * Constructor. Prepares a new BluetoothChat session.\n     *\n     * @param context The UI Activity Context\n     * @param handler A Handler to send messages back to the UI Activity\n     */\n    public BluetoothChatService(Context context, Handler handler) {\n        mAdapter = BluetoothAdapter.getDefaultAdapter();\n        mState = STATE_NONE;\n        mNewState = mState;\n        mHandler = handler;\n    }\n\n    /**\n     * Update UI title according to the current state of the chat connection\n     */\n    private synchronized void updateUserInterfaceTitle() {\n        mState = getState();\n        Log.d(TAG, \"updateUserInterfaceTitle() \" + mNewState + \" -> \" + mState);\n        mNewState = mState;\n\n        // Give the new state to the Handler so the UI Activity can update\n        mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();\n    }\n\n    /**\n     * Return the current connection state.\n     */\n    public synchronized int getState() {\n        return mState;\n    }\n\n    /**\n     * Start the chat service. Specifically start AcceptThread to begin a\n     * session in listening (server) mode. Called by the Activity onResume()\n     */\n    public synchronized void start() {\n        Log.d(TAG, \"start\");\n\n        // Cancel any thread attempting to make a connection\n        if (mConnectThread != null) {\n            mConnectThread.cancel();\n            mConnectThread = null;\n        }\n\n        // Cancel any thread currently running a connection\n        if (mConnectedThread != null) {\n            mConnectedThread.cancel();\n            mConnectedThread = null;\n        }\n\n        // Start the thread to listen on a BluetoothServerSocket\n        if (mSecureAcceptThread == null) {\n            mSecureAcceptThread = new AcceptThread(true);\n            mSecureAcceptThread.start();\n        }\n        if (mInsecureAcceptThread == null) {\n            mInsecureAcceptThread = new AcceptThread(false);\n            mInsecureAcceptThread.start();\n        }\n        // Update UI title\n        updateUserInterfaceTitle();\n    }\n\n    /**\n     * Start the ConnectThread to initiate a connection to a remote device.\n     *\n     * @param device The BluetoothDevice to connect\n     * @param secure Socket Security type - Secure (true) , Insecure (false)\n     */\n    public synchronized void connect(BluetoothDevice device, int port, boolean secure) {\n        Log.d(TAG, \"connect to: \" + device);\n\n        // Cancel any thread attempting to make a connection\n        if (mState == STATE_CONNECTING) {\n            if (mConnectThread != null) {\n                mConnectThread.cancel();\n                mConnectThread = null;\n            }\n        }\n\n        // Cancel any thread currently running a connection\n        if (mConnectedThread != null) {\n            mConnectedThread.cancel();\n            mConnectedThread = null;\n        }\n\n        // Start the thread to connect with the given device\n        mConnectThread = new ConnectThread(device, port, secure);\n        mConnectThread.start();\n        // Update UI title\n        updateUserInterfaceTitle();\n    }\n\n    /**\n     * Start the ConnectedThread to begin managing a Bluetooth connection\n     *\n     * @param socket The BluetoothSocket on which the connection was made\n     * @param device The BluetoothDevice that has been connected\n     */\n    public synchronized void connected(BluetoothSocket socket, BluetoothDevice\n            device, final String socketType) {\n        Log.d(TAG, \"connected, Socket Type:\" + socketType);\n\n        // Cancel the thread that completed the connection\n        if (mConnectThread != null) {\n            mConnectThread.cancel();\n            mConnectThread = null;\n        }\n\n        // Cancel any thread currently running a connection\n        if (mConnectedThread != null) {\n            mConnectedThread.cancel();\n            mConnectedThread = null;\n        }\n\n        // Cancel the accept thread because we only want to connect to one device\n        if (mSecureAcceptThread != null) {\n            mSecureAcceptThread.cancel();\n            mSecureAcceptThread = null;\n        }\n        if (mInsecureAcceptThread != null) {\n            mInsecureAcceptThread.cancel();\n            mInsecureAcceptThread = null;\n        }\n\n        // Start the thread to manage the connection and perform transmissions\n        mConnectedThread = new ConnectedThread(socket, socketType);\n        mConnectedThread.start();\n\n        // Send the name of the connected device back to the UI Activity\n        Message msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME);\n        Bundle bundle = new Bundle();\n        bundle.putString(Constants.DEVICE_NAME, device.getName());\n        msg.setData(bundle);\n        mHandler.sendMessage(msg);\n        // Update UI title\n        updateUserInterfaceTitle();\n    }\n\n    /**\n     * Stop all threads\n     */\n    public synchronized void stop() {\n        Log.d(TAG, \"stop\");\n\n        if (mConnectThread != null) {\n            mConnectThread.cancel();\n            mConnectThread = null;\n        }\n\n        if (mConnectedThread != null) {\n            mConnectedThread.cancel();\n            mConnectedThread = null;\n        }\n\n        if (mSecureAcceptThread != null) {\n            mSecureAcceptThread.cancel();\n            mSecureAcceptThread = null;\n        }\n\n        if (mInsecureAcceptThread != null) {\n            mInsecureAcceptThread.cancel();\n            mInsecureAcceptThread = null;\n        }\n        mState = STATE_NONE;\n        // Update UI title\n        updateUserInterfaceTitle();\n    }\n\n    /**\n     * Write to the ConnectedThread in an unsynchronized manner\n     *\n     * @param out The bytes to write\n     * @see ConnectedThread#write(byte[])\n     */\n    public void write(byte[] out) {\n        // Create temporary object\n        ConnectedThread r;\n        // Synchronize a copy of the ConnectedThread\n        synchronized (this) {\n            if (mState != STATE_CONNECTED) return;\n            r = mConnectedThread;\n        }\n        // Perform the write unsynchronized\n        r.write(out);\n    }\n\n    /**\n     * Indicate that the connection attempt failed and notify the UI Activity.\n     */\n    private void connectionFailed() {\n        // Send a failure message back to the Activity\n        Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);\n        Bundle bundle = new Bundle();\n        bundle.putString(Constants.TOAST, \"Unable to connect\");\n        msg.setData(bundle);\n        mHandler.sendMessage(msg);\n\n        mState = STATE_NONE;\n        // Update UI title\n        updateUserInterfaceTitle();\n\n        // Start the service over to restart listening mode\n        BluetoothChatService.this.start();\n    }\n\n    /**\n     * Indicate that the connection was lost and notify the UI Activity.\n     */\n    private void connectionLost() {\n        // Send a failure message back to the Activity\n        Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);\n        Bundle bundle = new Bundle();\n        bundle.putString(Constants.TOAST, \"Connection lost\");\n        msg.setData(bundle);\n        mHandler.sendMessage(msg);\n\n        mState = STATE_NONE;\n        // Update UI title\n        updateUserInterfaceTitle();\n\n        // Start the service over to restart listening mode\n        //BluetoothChatService.this.start();\n    }\n\n    /**\n     * This thread runs while listening for incoming connections. It behaves\n     * like a server-side client. It runs until a connection is accepted\n     * (or until cancelled).\n     */\n    private class AcceptThread extends Thread {\n        // The local server socket\n        private final BluetoothServerSocket mmServerSocket;\n        private String mSocketType;\n\n        public AcceptThread(boolean secure) {\n            BluetoothServerSocket tmp = null;\n            mSocketType = secure ? \"Secure\" : \"Insecure\";\n\n            // Create a new listening server socket\n            try {\n                if (secure) {\n                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,\n                            MY_UUID_SECURE);\n                } else {\n                    tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(\n                            NAME_INSECURE, MY_UUID_INSECURE);\n                }\n            } catch (IOException e) {\n                Log.e(TAG, \"Socket Type: \" + mSocketType + \"listen() failed\", e);\n            }\n            mmServerSocket = tmp;\n            mState = STATE_LISTEN;\n        }\n\n        public void run() {\n            Log.d(TAG, \"Socket Type: \" + mSocketType +\n                    \"BEGIN mAcceptThread\" + this);\n            setName(\"AcceptThread\" + mSocketType);\n\n            BluetoothSocket socket = null;\n\n            // Listen to the server socket if we're not connected\n            while (mState != STATE_CONNECTED) {\n                try {\n                    // This is a blocking call and will only return on a\n                    // successful connection or an exception\n                    socket = mmServerSocket.accept();\n                } catch (IOException e) {\n                    Log.e(TAG, \"Socket Type: \" + mSocketType + \"accept() failed\", e);\n                    break;\n                }\n\n                // If a connection was accepted\n                if (socket != null) {\n                    synchronized (BluetoothChatService.this) {\n                        switch (mState) {\n                            case STATE_LISTEN:\n                            case STATE_CONNECTING:\n                                // Situation normal. Start the connected thread.\n                                connected(socket, socket.getRemoteDevice(),\n                                        mSocketType);\n                                break;\n                            case STATE_NONE:\n                            case STATE_CONNECTED:\n                                // Either not ready or already connected. Terminate new socket.\n                                try {\n                                    socket.close();\n                                } catch (IOException e) {\n                                    Log.e(TAG, \"Could not close unwanted socket\", e);\n                                }\n                                break;\n                        }\n                    }\n                }\n            }\n            Log.i(TAG, \"END mAcceptThread, socket Type: \" + mSocketType);\n\n        }\n\n        public void cancel() {\n            Log.d(TAG, \"Socket Type\" + mSocketType + \"cancel \" + this);\n            try {\n                mmServerSocket.close();\n            } catch (IOException e) {\n                Log.e(TAG, \"Socket Type\" + mSocketType + \"close() of server failed\", e);\n            }\n        }\n    }\n\n\n    /**\n     * This thread runs while attempting to make an outgoing connection\n     * with a device. It runs straight through; the connection either\n     * succeeds or fails.\n     */\n    private class ConnectThread extends Thread {\n        private final BluetoothSocket mmSocket;\n        private final BluetoothDevice mmDevice;\n        private String mSocketType;\n\n        public ConnectThread(BluetoothDevice device, int port, boolean secure) {\n            mmDevice = device;\n            BluetoothSocket tmp = null;\n            mSocketType = secure ? \"Secure\" : \"Insecure\";\n            mSocketType += (Integer.toString(port));\n\n            // Get a BluetoothSocket for a connection with the\n            // given BluetoothDevice\n            // if the port is 0 use the service record to connect, otherwise connect direct\n            try {\n                if (secure) {\n                    if (port == 0) {\n                        tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);\n                    }\n                    else {\n                        Method createRfcommSocket =\n                                device.getClass().getMethod(\"createRfcommSocket\", new Class[]{int.class});\n                        tmp = (BluetoothSocket) createRfcommSocket.invoke(device, port);\n                    }\n                } else {\n                    if (port == 0) {\n                        tmp = device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);\n                    }\n                    else {\n                        Method createInsecureRfcommSocket =\n                                device.getClass().getMethod(\"createInsecureRfcommSocket\", new Class[]{int.class});\n                        tmp = (BluetoothSocket) createInsecureRfcommSocket.invoke(device, port);\n                    }\n\n                }\n            } catch (IOException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {\n                Log.e(TAG, \"Socket Type: \" + mSocketType + \"create() failed\", e);\n            }\n            mmSocket = tmp;\n            mState = STATE_CONNECTING;\n        }\n\n        public void run() {\n            Log.i(TAG, \"BEGIN mConnectThread SocketType:\" + mSocketType);\n            setName(\"ConnectThread\" + mSocketType);\n\n            // Always cancel discovery because it will slow down a connection\n            mAdapter.cancelDiscovery();\n\n            // Make a connection to the BluetoothSocket\n            try {\n                if (mmSocket != null) {\n                    // This is a blocking call and will only return on a\n                    // successful connection or an exception\n                    mmSocket.connect();\n                }\n                else {\n                    // This shouldn't happen, but was reported as an ANR so sometimes it must!\n                    connectionFailed();\n                    return;\n                }\n            } catch (IOException e) {\n                // Close the socket\n                try {\n                    mmSocket.close();\n                } catch (IOException e2) {\n                    Log.e(TAG, \"unable to close() \" + mSocketType +\n                            \" socket during connection failure\", e2);\n                }\n                connectionFailed();\n                return;\n            }\n\n            // Reset the ConnectThread because we're done\n            synchronized (BluetoothChatService.this) {\n                mConnectThread = null;\n            }\n\n            // Start the connected thread\n            connected(mmSocket, mmDevice, mSocketType);\n        }\n\n        public void cancel() {\n            try {\n                if (mmSocket != null) {\n                    mmSocket.close();\n                }\n            } catch (IOException e) {\n                Log.e(TAG, \"close() of connect \" + mSocketType + \" socket failed\", e);\n            }\n        }\n    }\n\n    /**\n     * This thread runs during a connection with a remote device.\n     * It handles all incoming and outgoing transmissions.\n     */\n    private class ConnectedThread extends Thread {\n        private final BluetoothSocket mmSocket;\n        private final InputStream mmInStream;\n        private final OutputStream mmOutStream;\n\n        public ConnectedThread(BluetoothSocket socket, String socketType) {\n            Log.d(TAG, \"create ConnectedThread: \" + socketType);\n            mmSocket = socket;\n            InputStream tmpIn = null;\n            OutputStream tmpOut = null;\n\n            // Get the BluetoothSocket input and output streams\n            try {\n                tmpIn = socket.getInputStream();\n                tmpOut = socket.getOutputStream();\n            } catch (IOException e) {\n                Log.e(TAG, \"temp sockets not created\", e);\n            }\n\n            mmInStream = tmpIn;\n            mmOutStream = tmpOut;\n            mState = STATE_CONNECTED;\n        }\n\n        public void run() {\n            Log.i(TAG, \"BEGIN mConnectedThread\");\n            //byte[] buffer = new byte[1024];\n            //int bytes;\n\n            // Keep listening to the InputStream while connected\n            while (mState == STATE_CONNECTED) {\n                try {\n                    byte[] buffer = new byte[1024];\n                    int bytes;\n                    // Read from the InputStream\n                    bytes = mmInStream.read(buffer);\n\n                    // Send the obtained bytes to the UI Activity\n                    mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)\n                            .sendToTarget();\n                } catch (IOException e) {\n                    Log.e(TAG, \"Disconnected\", e);\n                    connectionLost();\n                    break;\n                }\n            }\n        }\n\n        /**\n         * Write to the connected OutStream.\n         *\n         * @param buffer The bytes to write\n         */\n        public void write(byte[] buffer) {\n            try {\n                mmOutStream.write(buffer);\n\n                // Share the sent message back to the UI Activity\n                mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer)\n                        .sendToTarget();\n            } catch (IOException e) {\n                Log.e(TAG, \"Exception during write\", e);\n            }\n        }\n\n        public void cancel() {\n            try {\n                mmSocket.close();\n            } catch (IOException e) {\n                Log.e(TAG, \"close() of connect socket failed\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/Button.java",
    "content": "package com.stuffaboutcode.bluedot;\r\n\r\nimport android.graphics.Color;\r\nimport android.os.Handler;\r\nimport android.os.Message;\r\nimport androidx.appcompat.app.AppCompatActivity;\r\nimport androidx.preference.PreferenceManager;\r\nimport android.content.SharedPreferences;\r\nimport android.os.Bundle;\r\nimport android.content.Intent;\r\nimport android.view.MotionEvent;\r\nimport android.view.View;\r\nimport android.widget.TextView;\r\nimport android.widget.Toast;\r\nimport android.bluetooth.BluetoothAdapter;\r\nimport android.bluetooth.BluetoothDevice;\r\n\r\nimport android.app.ProgressDialog;\r\n\r\nimport com.stuffaboutcode.logger.Log;\r\n\r\npublic class Button extends AppCompatActivity {\r\n\r\n    private String mConnectedDeviceName = null;\r\n    private StringBuffer mOutStringBuffer;\r\n    private StringBuffer mInStringBuffer;\r\n\r\n    private BluetoothAdapter mBluetoothAdapter = null;\r\n    private BluetoothChatService mChatService = null;\r\n\r\n    String address = null;\r\n    String deviceName = null;\r\n\r\n    private ProgressDialog progress;\r\n    private double last_x = 0;\r\n    private double last_y = 0;\r\n\r\n    private DynamicMatrix matrix;\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n\r\n        setContentView(R.layout.activity_button);\r\n\r\n        Intent newint = getIntent();\r\n        deviceName = newint.getStringExtra(Devices.EXTRA_NAME);\r\n        address = newint.getStringExtra(Devices.EXTRA_ADDRESS);\r\n\r\n        // Get the bluetooth port number from preferences\r\n        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\r\n\r\n        int port_number = 1;\r\n        // if the default port is not used, get the port\r\n        if (!sharedPreferences.getBoolean(\"default_port\", true)) {\r\n            String port_value = sharedPreferences.getString(\"port\", \"0\");\r\n            port_number = Integer.parseInt(port_value);\r\n        }\r\n\r\n        // Get local Bluetooth adapter\r\n        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();\r\n\r\n        // If the adapter is null, then Bluetooth is not supported\r\n        if (mBluetoothAdapter == null) {\r\n            Toast.makeText(getApplicationContext(), \"Bluetooth is not available\", Toast.LENGTH_LONG).show();\r\n            this.finish();\r\n        }\r\n\r\n        // Initialize the BluetoothChatService to perform bluetooth connections\r\n        mChatService = new BluetoothChatService(this, mHandler);\r\n\r\n        // Initialize the buffer for outgoing messages\r\n        mOutStringBuffer = new StringBuffer(\"\");\r\n        // Initialize the buffer for incoming messages\r\n        mInStringBuffer = new StringBuffer(\"\");\r\n\r\n        // Get the BluetoothDevice object\r\n        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);\r\n        // Attempt to connect to the device\r\n        mChatService.connect(device, port_number,true);\r\n\r\n        matrix = findViewById(R.id.matrix);\r\n\r\n        // Once connected setup the listener\r\n        matrix.setOnUseListener(new DynamicMatrix.DynamicMatrixListener() {\r\n            @Override\r\n            public void onPress(DynamicMatrix.MatrixCell cell, int pointerId, float actual_x, float actual_y) {\r\n                double x = calcX(cell, actual_x);\r\n                double y = calcY(cell, actual_y);\r\n                send(buildMessage(\"1\", cell.getCol(), cell.getRow(), x, y));\r\n                last_x = x;\r\n                last_y = y;\r\n            }\r\n\r\n            @Override\r\n            public void onMove(DynamicMatrix.MatrixCell cell, int pointerId, float actual_x, float actual_y) {\r\n                double x = calcX(cell, actual_x);\r\n                double y = calcY(cell, actual_y);\r\n                if ((x != last_x) || (y != last_y)) {\r\n                    send(buildMessage(\"2\", cell.getCol(), cell.getRow(), x, y));\r\n                    last_x = x;\r\n                    last_y = y;\r\n                }\r\n            }\r\n\r\n            @Override\r\n            public void onRelease(DynamicMatrix.MatrixCell cell, int pointerId, float actual_x, float actual_y) {\r\n                double x = calcX(cell, actual_x);\r\n                double y = calcY(cell, actual_y);\r\n                send(buildMessage(\"0\", cell.getCol(), cell.getRow(), x, y));\r\n                last_x = x;\r\n                last_y = y;\r\n            }\r\n\r\n        });\r\n\r\n    }\r\n\r\n    private double calcX(DynamicMatrix.MatrixCell cell, float actual_x) {\r\n\r\n        double relative_x = actual_x - cell.getBounds().left;\r\n        relative_x = (relative_x - (cell.getWidth() / 2)) / (cell.getWidth() / 2);\r\n        return (double)Math.round(relative_x * 10000d) / 10000d;\r\n    }\r\n\r\n    private double calcY(DynamicMatrix.MatrixCell cell, float actual_y) {\r\n\r\n        double relative_y = actual_y - cell.getBounds().top;\r\n        relative_y = (relative_y - (cell.getHeight() / 2)) / (cell.getHeight() / 2) * -1;\r\n        return (double)Math.round(relative_y * 10000d) / 10000d;\r\n    }\r\n\r\n    private double calcX(View roundButton, MotionEvent event) {\r\n        double x = (event.getX() - (roundButton.getWidth() / 2)) / (roundButton.getWidth() / 2);\r\n        x = (double)Math.round(x * 10000d) / 10000d;\r\n        return x;\r\n    }\r\n\r\n    private double calcY(View roundButton, MotionEvent event) {\r\n        double y = (event.getY() - (roundButton.getHeight() / 2)) / (roundButton.getHeight() /2) * -1;\r\n        y = (double)Math.round(y * 10000d) / 10000d;\r\n        return y;\r\n    }\r\n\r\n    private String buildMessage(String operation, int col, int row, double x, double y) {\r\n        return (operation + \",\" + String.valueOf(col) + \",\" + String.valueOf(row) + \",\" + String.valueOf(x) + \",\" + String.valueOf(y) + \"\\n\");\r\n    }\r\n\r\n    public void send(String message) {\r\n        // Check that we're actually connected before trying anything\r\n        if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {\r\n            Toast.makeText(this, \"cant send message - not connected\", Toast.LENGTH_SHORT).show();\r\n            return;\r\n        }\r\n\r\n        // Check that there's actually something to send\r\n        if (message.length() > 0) {\r\n            // Get the message bytes and tell the BluetoothChatService to write\r\n            byte[] send = message.getBytes();\r\n            mChatService.write(send);\r\n\r\n            // Reset out string buffer to zero and clear the edit text field\r\n            mOutStringBuffer.setLength(0);\r\n        }\r\n    }\r\n\r\n    private void disconnect() {\r\n        if (mChatService != null) {\r\n            mChatService.stop();\r\n        };\r\n\r\n        finish();\r\n    }\r\n\r\n    private void msg(String message) {\r\n        TextView statusView = (TextView)findViewById(R.id.status);\r\n        statusView.setText(message);\r\n    }\r\n\r\n    private void parseData(String data) {\r\n        //msg(data);\r\n\r\n        // add the message to the buffer\r\n        mInStringBuffer.append(data);\r\n\r\n        // debug - log data and buffer\r\n        //Log.d(\"data\", data);\r\n        //Log.d(\"mInStringBuffer\", mInStringBuffer.toString());\r\n        //msg(data.toString());\r\n\r\n        // find any complete messages\r\n        String[] messages = mInStringBuffer.toString().split(\"\\\\n\");\r\n        int noOfMessages = messages.length;\r\n        // does the last message end in a \\n, if not its incomplete and should be ignored\r\n        if (!mInStringBuffer.toString().endsWith(\"\\n\")) {\r\n            noOfMessages = noOfMessages - 1;\r\n        }\r\n\r\n        // clean the data buffer of any processed messages\r\n        if (mInStringBuffer.lastIndexOf(\"\\n\") > -1)\r\n            mInStringBuffer.delete(0, mInStringBuffer.lastIndexOf(\"\\n\") + 1);\r\n\r\n        // process messages\r\n        for (int messageNo = 0; messageNo < noOfMessages; messageNo++) {\r\n            processMessage(messages[messageNo]);\r\n        }\r\n\r\n    }\r\n\r\n    private void processMessage(String message) {\r\n        // Debug\r\n        // msg(message);\r\n        String parameters[] = message.split(\",(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)\");\r\n        boolean invalid = false;\r\n\r\n        // Check the message\r\n        if (parameters.length > 0) {\r\n            switch (parameters[0]) {\r\n                case \"4\":\r\n                    invalid = processSetMatrixMessage(parameters);\r\n                    break;\r\n                case \"5\":\r\n                    invalid = processSetCellMessage(parameters);\r\n                    break;\r\n                default:\r\n                    invalid = true;\r\n            }\r\n        }\r\n\r\n        if (invalid) {\r\n            msg(\"Error - Invalid message received '\" + message +\"'\");\r\n        }\r\n    }\r\n\r\n    private boolean processSetMatrixMessage(String parameters[]) {\r\n        // \"4,[color],[square],[border],[visible],[cols],[rows]\"\r\n        boolean invalid = false;\r\n\r\n        // check length\r\n        if (parameters.length == 7) {\r\n\r\n            // cols\r\n            matrix.setCols(Integer.parseInt(parameters[5]));\r\n\r\n            // rows\r\n            matrix.setRows(Integer.parseInt(parameters[6]));\r\n\r\n            //color\r\n            String color = convertColor(parameters[1]);\r\n            if (!color.equals(\"\")) {\r\n                matrix.setColor(Color.parseColor(color));\r\n            } else {\r\n                invalid = true;\r\n            }\r\n\r\n            matrix.setSquare(parameters[2].equals(\"1\"));\r\n\r\n            matrix.setBorder(parameters[3].equals(\"1\"));\r\n\r\n            matrix.setVisible(parameters[4].equals(\"1\"));\r\n\r\n            matrix.update();\r\n\r\n        } else {\r\n            invalid = true;\r\n        }\r\n        return invalid;\r\n    }\r\n\r\n    private boolean processSetCellMessage(String parameters[]) {\r\n        // \"5,[color],[square],[border],[visible],[col],[row]\"\r\n\r\n        boolean invalid = false;\r\n        int col;\r\n        int row;\r\n\r\n        // check length\r\n        if (parameters.length == 7) {\r\n\r\n            // get the col and row\r\n            col = Integer.parseInt(parameters[5]);\r\n            row = Integer.parseInt(parameters[6]);\r\n\r\n            // get the cell\r\n            DynamicMatrix.MatrixCell cell = matrix.getCell(col, row);\r\n\r\n            String color = convertColor(parameters[1]);\r\n            if (!color.equals(\"\")) {\r\n                cell.setColor(Color.parseColor(color));\r\n            } else {\r\n                invalid = true;\r\n            }\r\n\r\n            cell.setSquare(parameters[2].equals(\"1\"));\r\n\r\n            cell.setBorder(parameters[3].equals(\"1\"));\r\n\r\n            cell.setVisible(parameters[4].equals(\"1\"));\r\n\r\n            matrix.update();\r\n\r\n        } else {\r\n            invalid = true;\r\n        }\r\n        return invalid;\r\n    }\r\n\r\n    private String convertColor(String color) {\r\n        // convert color from #rrggbbaa to #aarrggbb\r\n        String new_color;\r\n        try {\r\n            new_color = color.substring(0, 1) +\r\n                    color.substring(7, 9) +\r\n                    color.substring(1, 7);\r\n        } catch (Exception i) {\r\n            // return an empty string if the color is invalid\r\n            new_color = \"\";\r\n        }\r\n        return new_color;\r\n    }\r\n\r\n\r\n    private final Handler mHandler = new Handler() {\r\n        @Override\r\n        public void handleMessage(Message msg) {\r\n\r\n            switch (msg.what) {\r\n                case Constants.MESSAGE_STATE_CHANGE:\r\n                    switch (msg.arg1) {\r\n                        case BluetoothChatService.STATE_CONNECTED:\r\n                            Log.d(\"status\",\"connected\");\r\n                            msg(\"Connected to \" + deviceName);\r\n                            matrix.setVisibility(View.VISIBLE);\r\n                            // send the protocol version to the server\r\n                            send(\"3,\" + Constants.PROTOCOL_VERSION + \",\" + Constants.CLIENT_NAME + \"\\n\");\r\n                            break;\r\n                        case BluetoothChatService.STATE_CONNECTING:\r\n                            Log.d(\"status\",\"connecting\");\r\n                            msg(\"Connecting to \" + deviceName);\r\n                            matrix.setVisibility(View.INVISIBLE);\r\n                            break;\r\n                        case BluetoothChatService.STATE_LISTEN:\r\n                        case BluetoothChatService.STATE_NONE:\r\n                            Log.d(\"status\",\"not connected\");\r\n                            msg(\"Not connected\");\r\n                            disconnect();\r\n                            break;\r\n                    }\r\n                    break;\r\n                case Constants.MESSAGE_WRITE:\r\n                    byte[] writeBuf = (byte[]) msg.obj;\r\n                    // construct a string from the buffer\r\n                    String writeMessage = new String(writeBuf);\r\n                    break;\r\n                case Constants.MESSAGE_READ:\r\n                    byte[] readBuf = (byte[]) msg.obj;\r\n                    // construct a string from the valid bytes in the buffer\r\n                    String readData = new String(readBuf, 0, msg.arg1);\r\n                    // message received\r\n                    parseData(readData);\r\n                    break;\r\n                case Constants.MESSAGE_DEVICE_NAME:\r\n                    // save the connected device's name\r\n                    mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);\r\n                    if (null != this) {\r\n                        Toast.makeText(getApplicationContext(), \"Connected to \"\r\n                                + mConnectedDeviceName, Toast.LENGTH_SHORT).show();\r\n                    }\r\n                    break;\r\n                case Constants.MESSAGE_TOAST:\r\n                    if (null != this) {\r\n                        Toast.makeText(getApplicationContext(), msg.getData().getString(Constants.TOAST),\r\n                                Toast.LENGTH_SHORT).show();\r\n                    }\r\n                    break;\r\n            }\r\n\r\n        }\r\n    };\r\n\r\n    @Override\r\n    public void onBackPressed() {\r\n        disconnect();\r\n    }\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/Constants.java",
    "content": "/*\r\n * Copyright (C) 2014 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage com.stuffaboutcode.bluedot;\r\n\r\n/**\r\n * Defines several constants used between {@link BluetoothChatService} and the UI.\r\n */\r\npublic interface Constants {\r\n\r\n    // Message types sent from the BluetoothChatService Handler\r\n    public static final int MESSAGE_STATE_CHANGE = 1;\r\n    public static final int MESSAGE_READ = 2;\r\n    public static final int MESSAGE_WRITE = 3;\r\n    public static final int MESSAGE_DEVICE_NAME = 4;\r\n    public static final int MESSAGE_TOAST = 5;\r\n\r\n    // Key names received from the BluetoothChatService Handler\r\n    public static final String DEVICE_NAME = \"device_name\";\r\n    public static final String TOAST = \"toast\";\r\n\r\n    public static final float BORDER_THICKNESS = (float)0.025;\r\n\r\n    public static final String PROTOCOL_VERSION = \"2\";\r\n    public static final String CLIENT_NAME = \"Blue Dot Android app\";\r\n\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/Devices.java",
    "content": "package com.stuffaboutcode.bluedot;\r\n\r\nimport androidx.appcompat.app.AppCompatActivity;\r\n\r\nimport android.content.pm.PackageManager;\r\nimport android.os.Bundle;\r\nimport android.bluetooth.BluetoothAdapter;\r\nimport android.bluetooth.BluetoothDevice;\r\nimport android.widget.ImageButton;\r\nimport android.widget.Toast;\r\nimport android.content.Intent;\r\nimport android.widget.ArrayAdapter;\r\nimport android.widget.AdapterView;\r\nimport android.widget.ListView;\r\nimport android.widget.TextView;\r\nimport android.view.View;\r\nimport android.net.Uri;\r\nimport android.view.Menu;\r\nimport android.view.MenuInflater;\r\nimport android.view.MenuItem;\r\n\r\nimport androidx.core.app.ActivityCompat;\r\nimport androidx.preference.PreferenceManager;\r\nimport android.content.SharedPreferences;\r\nimport android.Manifest;\r\n\r\nimport java.util.Set;\r\nimport java.util.ArrayList;\r\n\r\npublic class Devices\r\n        extends AppCompatActivity\r\n        implements SharedPreferences.OnSharedPreferenceChangeListener {\r\n\r\n    ListView devicelist;\r\n    ImageButton infoButton;\r\n\r\n    private BluetoothAdapter myBluetooth = null;\r\n    private Set<BluetoothDevice> pairedDevices;\r\n    public static String EXTRA_ADDRESS = \"device_address\";\r\n    public static String EXTRA_NAME = \"device_name\";\r\n\r\n    private static String[] PERMISSIONS = {\r\n            Manifest.permission.BLUETOOTH_SCAN,\r\n            Manifest.permission.BLUETOOTH_CONNECT,\r\n\r\n    };\r\n\r\n    private void checkPermissions(){\r\n        int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT);\r\n        if (permission != PackageManager.PERMISSION_GRANTED) {\r\n            // We don't have permission so prompt the user\r\n            ActivityCompat.requestPermissions(\r\n                    this,\r\n                    PERMISSIONS,\r\n                    1\r\n            );\r\n        }\r\n    }\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        setContentView(R.layout.activity_devices);\r\n\r\n        devicelist = (ListView)findViewById(R.id.listView);\r\n\r\n        checkPermissions();\r\n\r\n        //if the device has bluetooth\r\n        myBluetooth = BluetoothAdapter.getDefaultAdapter();\r\n\r\n        if(myBluetooth == null) {\r\n            Toast.makeText(\r\n                    getApplicationContext(),\r\n                    \"Bluetooth Device Not Available\",\r\n                    Toast.LENGTH_LONG).show();\r\n\r\n            //finish apk\r\n            this.finish();\r\n            System.exit(0);\r\n\r\n        } else if(!myBluetooth.isEnabled()) {\r\n            //Ask to the user turn the bluetooth on\r\n            Intent turnBTon = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);\r\n            startActivityForResult(turnBTon,1);\r\n        }\r\n\r\n        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\r\n        sharedPreferences.registerOnSharedPreferenceChangeListener(this);\r\n    }\r\n\r\n    @Override\r\n    protected void onResume() {\r\n        super.onResume();\r\n        setConnectMsg();\r\n        pairedDevicesList();\r\n    }\r\n\r\n    @Override\r\n    public boolean onCreateOptionsMenu(Menu menu) {\r\n        MenuInflater menuInflater = getMenuInflater();\r\n        menuInflater.inflate(R.menu.settings_menu, menu);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean onOptionsItemSelected(MenuItem item) {\r\n        int id = item.getItemId();\r\n        if (id == R.id.settings) {\r\n            Intent intent = new Intent(Devices.this, SettingsActivity.class);\r\n            startActivity(intent);\r\n            return true;\r\n        } else if (id == R.id.help) {\r\n            Uri uri = Uri.parse(\"https://bluedot.readthedocs.io\");\r\n            Intent intent = new Intent(Intent.ACTION_VIEW, uri);\r\n            startActivity(intent);\r\n            return true;\r\n        }\r\n        return super.onOptionsItemSelected(item);\r\n    }\r\n\r\n    @Override\r\n    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\r\n\r\n        if (key.equals(\"port\")) {\r\n            setConnectMsg(sharedPreferences);\r\n        }\r\n    }\r\n\r\n    private void setConnectMsg() {\r\n        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\r\n        setConnectMsg(sharedPreferences);\r\n    }\r\n\r\n    private void setConnectMsg(SharedPreferences sharedPreferences) {\r\n        String message = \"Connect\";\r\n        Boolean default_port = sharedPreferences.getBoolean(\"default_port\", true);\r\n        String port_value = sharedPreferences.getString(\"port\", \"1\");\r\n\r\n        if (!default_port) {\r\n            message = message + \" on port \" + port_value;\r\n        }\r\n\r\n        TextView connectView = findViewById(R.id.connect);\r\n        connectView.setText(message);\r\n    }\r\n\r\n    private void pairedDevicesList() {\r\n        pairedDevices = myBluetooth.getBondedDevices();\r\n        ArrayList<String> list = new ArrayList<String>();\r\n\r\n        if (pairedDevices.size()>0) {\r\n            // create a list of paired bluetooth devices\r\n            for(BluetoothDevice bt : pairedDevices)\r\n            {\r\n                list.add(bt.getName() + \"\\n\" + bt.getAddress()); //Get the device's name and the address\r\n            }\r\n        } else {\r\n            Toast.makeText(\r\n                    getApplicationContext(),\r\n                    \"No Paired Bluetooth Devices Found.\",\r\n                    Toast.LENGTH_LONG).show();\r\n        }\r\n\r\n        final ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1, list);\r\n        devicelist.setAdapter(adapter);\r\n        devicelist.setOnItemClickListener(myListClickListener); //Method called when the device from the list is clicked\r\n    }\r\n\r\n    private AdapterView.OnItemClickListener myListClickListener = new AdapterView.OnItemClickListener()\r\n    {\r\n        public void onItemClick (AdapterView<?> av, View v, int arg2, long arg3)\r\n        {\r\n            // Get the device MAC address, the last 17 chars in the View\r\n            String info = ((TextView) v).getText().toString();\r\n            String deviceName = info.split(\"\\n\")[0];\r\n            String address = info.split(\"\\n\")[1];\r\n\r\n            // Make an intent to start next activity.\r\n            Intent i = new Intent(Devices.this, Button.class);\r\n\r\n            //Change the activity.\r\n            i.putExtra(EXTRA_NAME, deviceName);\r\n            i.putExtra(EXTRA_ADDRESS, address);\r\n            startActivity(i);\r\n        }\r\n    };\r\n\r\n\r\n}\r\n\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/DynamicMatrix.java",
    "content": "package com.stuffaboutcode.bluedot;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.RectF;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\nclass DynamicMatrix extends View {\n\n    private ArrayList<ArrayList<MatrixCell>> mCells;\n    private int mCols, mRows;\n    private Paint mTextPaint, mCellPaint, mBorderPaint, mLinePaint;\n    private float mTextHeight;\n    private int mWidth, mHeight;\n    int mMatrixWidth, mMatrixHeight;\n    int mCellSize;\n    private Context mContext;\n    private RectF mMatrixBounds = new RectF();\n\n    private HashMap<Integer, MatrixPointer> pointers = new HashMap<Integer, MatrixPointer>();\n\n    public interface DynamicMatrixListener {\n        public void onPress(MatrixCell cell, int pointerId, float actual_x, float actual_y);\n        public void onMove(MatrixCell cell, int pointerId, float actual_x, float actual_y);\n        public void onRelease(MatrixCell cell, int pointerId, float actual_x, float actual_y);\n    }\n\n    private DynamicMatrixListener listener;\n\n    public DynamicMatrix(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        listener = null;\n        mContext = context;\n\n        TypedArray a = context.getTheme().obtainStyledAttributes(\n                attrs,\n                R.styleable.DynamicMatrix,\n                0, 0);\n\n        try {\n            mCols = a.getInteger(R.styleable.DynamicMatrix_cols, 0);\n            mRows = a.getInteger(R.styleable.DynamicMatrix_rows, 0);\n        } finally {\n            a.recycle();\n        }\n\n        init();\n    }\n\n    public void setOnUseListener(DynamicMatrixListener listener) {\n        this.listener = listener;\n    }\n\n    // initialise the matrix\n    private void init() {\n\n        mCellPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        mCellPaint.setStyle(Paint.Style.FILL);\n\n        mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        mBorderPaint.setStyle(Paint.Style.STROKE);\n        mBorderPaint.setStrokeWidth(5);\n        mBorderPaint.setColor(mContext.getResources().getColor(R.color.darkgrey));\n\n        setupMatrix();\n    }\n\n    // when the size changes, re-create the matrix\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        int xpad = (getPaddingLeft() + getPaddingRight());\n        int ypad = (getPaddingTop() + getPaddingBottom());\n\n        mWidth = w - xpad;\n        mHeight = h - ypad;\n\n        sizeMatrix();\n    }\n\n    // setup matrix is called on init or when the number of rows or cols changes\n    // and creates a default matrix with a zero size, its not sized until onSizeChanged\n    // is called\n    private void setupMatrix() {\n\n        mMatrixWidth = 0;\n        mMatrixHeight = 0;\n        mCellSize = 0;\n        mMatrixBounds = new RectF(0 ,0,0,0);\n        // create the cells\n        mCells = new ArrayList<ArrayList<MatrixCell>>();\n        int color = mContext.getResources().getColor(R.color.defaultCellColor);\n\n        for(int c = 0; c < getCols(); c++) {\n            mCells.add(new ArrayList<MatrixCell>());\n            for (int r = 0; r < getRows(); r++) {\n                mCells.get(c).add(new MatrixCell(\n                        c,\n                        r,\n                        new RectF(0,0,0,0),\n                        true,\n                        false,\n                        color,\n                        false));\n            }\n        }\n    }\n\n    // called when the screen size of the matrix needs to change\n    private void sizeMatrix() {\n\n        // calc potential size of matrix\n        int left = 0, top = 0;\n        float borderWidth;\n\n        // find out how big each cell can be\n        if ((mWidth / getCols()) < (mHeight / getRows())) {\n            borderWidth = (mWidth * Constants.BORDER_THICKNESS);\n            mCellSize = (int)((mWidth - borderWidth) / getCols());\n            mMatrixWidth = mWidth;\n            mMatrixHeight = getRows() * mCellSize;\n            top = (mHeight - mMatrixHeight) / 2;\n            left = (int)(borderWidth / 2);\n        } else {\n            borderWidth = (mHeight * Constants.BORDER_THICKNESS);\n            mCellSize = (int)((mHeight - borderWidth) / getRows());\n            mMatrixWidth = getCols() * mCellSize;\n            mMatrixHeight = mHeight;\n            top = (int)(borderWidth / 2);\n            left = (mWidth - mMatrixWidth) / 2;\n        }\n\n        // set the bounds for the matrix\n        mMatrixBounds = new RectF(\n                left,\n                top,\n                left + mMatrixWidth,\n                top + mMatrixHeight);\n\n        // set the bound for the cells\n        for(int c = 0; c < getCols(); c++) {\n            for (int r = 0; r < getRows(); r++) {\n                mCells.get(c).get(r).setBounds(sizeCell(c, r));\n            }\n        }\n\n        // set line thickness\n        mBorderPaint.setStrokeWidth((float)Math.max(1, mCellSize * Constants.BORDER_THICKNESS));\n\n    }\n\n\n    private RectF sizeCell(int c, int r) {\n        return new RectF(\n                (int) mMatrixBounds.left + (c * mCellSize),\n                (int) mMatrixBounds.top + (r * mCellSize),\n                (int) mMatrixBounds.left + (c * mCellSize) + mCellSize,\n                (int) mMatrixBounds.top + (r * mCellSize) + mCellSize);\n    }\n\n    // called when the control needs to be drawn\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        // draw matrix\n        for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row ) {\n                if (cell.getVisible()) mCellPaint.setColor(cell.getColor());\n                else mCellPaint.setColor(Color.TRANSPARENT);\n                if (cell.getBorder()) {\n                    if (cell.getSquare()) {\n                        canvas.drawRect(cell.getInnerBounds(), mCellPaint);\n                        canvas.drawRect(cell.getBounds(), mBorderPaint);\n                    } else {\n                        canvas.drawOval(cell.getInnerBounds(), mCellPaint);\n                        canvas.drawOval(cell.getBounds(), mBorderPaint);\n                    }\n                } else {\n                    if (cell.getSquare()) {\n                        canvas.drawRect(cell.getBounds(), mCellPaint);\n                    } else {\n                        canvas.drawOval(cell.getBounds(), mCellPaint);\n                    }\n                }\n            }\n        }\n\n        // fancy animation stuff, but its a bit weird\n        /*for (Integer pointerId : pointers.keySet()){\n            MatrixPointer pointer = pointers.get(pointerId);\n\n            float x = pointer.getX();\n            float y = pointer.getY();\n            RectF cellBounds = pointer.getPressedCell().getBounds();\n            float highlightWidth = cellBounds.width() * 0.1f;\n\n            //draw a line from the centre of the cell to the position\n            mLinePaint.setColor(pointer.getPressedCell().getMovedColor());\n            canvas.drawLine(cellBounds.centerX(), cellBounds.centerY(), x, y, mLinePaint);\n\n            // is pointer inside the pressed cell?\n            //if (cellBounds.contains(x,y)){\n            //    RectF selectedRect = new RectF(x - highlightWidth, y - highlightWidth, x + highlightWidth, y + highlightWidth);\n            //    mCellPaint.setColor(pointer.getPressedCell().getMovedColor());\n            //    canvas.drawRect(selectedRect, mCellPaint);\n            //}\n\n        }*/\n    }\n\n    // manage the touch events\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n\n        int pointerIndex, pointerId;\n        MatrixPointer pointer;\n        MatrixCell cell;\n        float x, y;\n\n        switch(event.getActionMasked()) {\n\n            case MotionEvent.ACTION_DOWN:\n                // do the perform click\n                performClick();\n\n            case MotionEvent.ACTION_POINTER_DOWN:\n\n                // TODO have a look at the acceleration bit\n                pointerIndex = event.getActionIndex();\n                x = event.getX(pointerIndex);\n                y = event.getY(pointerIndex);\n\n                // was it inside the matrix?\n                if (mMatrixBounds.contains(x, y)) {\n                    cell = findCellFromXY(x, y);\n                    if (cell != null) {\n                        // if this cell isnt already pressed, press it\n                        if (!cell.getPressed()) {\n                            pointerId = event.getPointerId(pointerIndex);\n                            pointers.put(pointerId, new MatrixPointer(pointerId, x, y, cell));\n\n                            cell.press();\n                            if (listener != null)\n                                listener.onPress(cell, pointerId, x, y);\n                        }\n                    }\n                }\n                break;\n\n            case MotionEvent.ACTION_UP:\n\n            case MotionEvent.ACTION_POINTER_UP:\n                pointerIndex = event.getActionIndex();\n                x = event.getX(pointerIndex);\n                y = event.getY(pointerIndex);\n                pointerId = event.getPointerId(pointerIndex);\n                pointer = pointers.get(pointerId);\n                if (pointer != null) {\n                    cell = pointer.getPressedCell();\n                    cell.release();\n                    pointers.remove(pointerId);\n                    if (listener != null)\n                        listener.onRelease(cell, pointerId, x, y);\n                }\n                break;\n\n            case MotionEvent.ACTION_MOVE:\n                int numPointers = event.getPointerCount();\n\n                for (pointerIndex = 0; pointerIndex < numPointers; pointerIndex++) {\n                    //pointerIndex = event.getActionIndex();\n                    x = event.getX(pointerIndex);\n                    y = event.getY(pointerIndex);\n\n                    // was it inside the matrix?\n                    if (mMatrixBounds.contains(x, y)) {\n                        // was it inside the pressed cell?\n                        pointerId = event.getPointerId(pointerIndex);\n                        pointer = pointers.get(pointerId);\n                        if (pointer != null) {\n                            // has this pointer moved?\n                            if (pointer.getX() != x && pointer.getY() != y) {\n                                pointer.move(x, y);\n                                // removed - no need to update the cell view when it is moved\n                                // pointer.getPressedCell().moved();\n                                if (listener != null)\n                                    listener.onMove(pointer.getPressedCell(), pointerId, x, y);\n                            }\n                        }\n                    }\n                }\n                break;\n\n        }\n        return true;\n    }\n\n    // finds the cell on the matrix from the xy\n    private MatrixCell findCellFromXY(float x, float y) {\n        /*for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row) {\n                if (cell.getBounds().contains(x, y)) {\n                    return cell\n                }\n            }\n        }*/\n        int col = (int)(x - mMatrixBounds.left) / mCellSize;\n        int row = (int)(y - mMatrixBounds.top) / mCellSize;\n        if (col < mCols && row < mRows) {\n            return mCells.get(col).get(row);\n        } else {\n            return null;\n        }\n\n    }\n\n    @Override\n    public boolean performClick() {\n        super.performClick();\n        return true;\n    }\n\n    // updates the matrix, must be called after each update to the matrix to display the changes\n    public void update() {\n        invalidate();\n        requestLayout();\n    }\n\n    // getters and setters\n    public ArrayList<ArrayList<MatrixCell>> getCells() {\n        return mCells;\n    }\n\n    public MatrixCell getCell(int col, int row) {\n        return mCells.get(col).get(row);\n    }\n\n    public void setSize(int cols, int rows) {\n        cols = Math.max(1, cols);\n        rows = Math.max(1, rows);\n        mCols = cols;\n        mRows = rows;\n        setupMatrix();\n        sizeMatrix();\n\n    }\n\n    public void setColor(int color) {\n        for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row ) {\n                cell.setColor(color);\n            }\n        }\n    }\n\n    public void setVisible(boolean value) {\n        for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row ) {\n                cell.setVisible(value);\n            }\n        }\n    }\n\n    public void setBorder(boolean value) {\n        for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row ) {\n                cell.setBorder(value);\n            }\n        }\n    }\n\n    public void setSquare(boolean value) {\n        for (ArrayList<MatrixCell> row : mCells) {\n            for (MatrixCell cell : row ) {\n                cell.setSquare(value);\n            }\n        }\n    }\n\n    public int getCols() {\n        return mCols;\n    }\n\n    public void setCols(int value) {\n        setSize(value, getRows());\n    }\n\n    public int getRows() {\n        return mRows;\n    }\n\n    public void setRows(int value) {\n        setSize(getCols(), value);\n    }\n\n    // internal classes\n\n    // keeps track of a single pointer on the matrix\n    private class MatrixPointer {\n\n        private int mPointedId;\n        private float mX, mY;\n        private MatrixCell mPressedCell;\n\n        private MatrixPointer(int pointerId, float x, float y, MatrixCell pressedCell) {\n            mPointedId = pointerId;\n            mX = x;\n            mY = y;\n            mPressedCell = pressedCell;\n        }\n        private void move(float x, float y) {\n            mX = x;\n            mY = y;\n        }\n        private MatrixCell getPressedCell() {\n            return mPressedCell;\n        }\n        private float getX() {\n            return mX;\n        }\n        private float getY() {\n            return mY;\n        }\n    }\n\n    // represents a cell on the matrix, used to keep track of the state\n    public class MatrixCell {\n\n        private int mRow, mCol, mCurrentColor, mReleasedColor, mPressedColor, mMovedColor;\n        private RectF mBounds;\n        private boolean mBorder, mPressed, mVisible, mSquare;\n\n        private MatrixCell(int col, int row, RectF bounds, boolean visible, boolean border, int color, boolean square) {\n            mCol = col;\n            mRow = row;\n            mBounds = bounds;\n            mBorder = border;\n            mPressed = false;\n            mVisible = visible;\n            mSquare = square;\n            updateColors(color);\n        }\n        /*public int getRow() { return mRow; }\n        public int getCol() { return mCol; }*/\n        public RectF getBounds() {\n            return mBounds;\n        }\n        private void setBounds(RectF value) {\n            mBounds = value;\n        }\n        public RectF getInnerBounds() {\n            float border = (mCellSize * Constants.BORDER_THICKNESS) / 2;\n            return new RectF(\n                    mBounds.left + border,\n                    mBounds.top + border,\n                    mBounds.right - border,\n                    mBounds.bottom - border);\n        }\n        public int getColor() {\n            return mCurrentColor;\n        }\n        public int getMovedColor() {\n            return mMovedColor;\n        }\n        public void setColor(int value) {\n            updateColors(value);\n        }\n        public boolean getBorder() {\n            return mBorder;\n        }\n        public void setBorder(boolean value) {\n            mBorder = value;\n        }\n        public boolean getVisible() {\n            return mVisible;\n        }\n        public void setVisible(boolean value) {\n            mVisible = value;\n        }\n        public boolean getSquare() {\n            return mSquare;\n        }\n        public void setSquare(boolean value) {\n            mSquare = value;\n        }\n        public int getCol() {\n            return mCol;\n        }\n        public int getRow() {\n            return mRow;\n        }\n        public float getWidth() {\n            return mBounds.right - mBounds.left;\n        }\n        public float getHeight() {\n            return mBounds.bottom - mBounds.top;\n        }\n        public boolean getPressed() {\n            return mPressed;\n        }\n\n        // called when the cell is pressed\n        private void press() {\n            mCurrentColor = mPressedColor;\n            mPressed = true;\n            invalidate();\n            requestLayout();\n        }\n\n        // called when the cell is released\n        private void release() {\n            mCurrentColor = mReleasedColor;\n            mPressed = false;\n            invalidate();\n            requestLayout();\n        }\n\n        // called when the cell is moved\n        private void moved() {\n            invalidate();\n            requestLayout();\n        }\n\n        // manages the colors in the cell\n        private void updateColors(int color) {\n            mReleasedColor = color;\n            mPressedColor = manipulateColor(color, 0.85f);\n            mMovedColor = manipulateColor(color, 0.7f);\n\n            if (mPressed) {\n                mCurrentColor = mPressedColor;\n            } else {\n                mCurrentColor = mReleasedColor;\n            }\n        }\n\n        // manipulates a color\n        private int manipulateColor(int color, float factor) {\n            int a = Color.alpha(color);\n            int r = Math.round(Color.red(color) * factor);\n            int g = Math.round(Color.green(color) * factor);\n            int b = Math.round(Color.blue(color) * factor);\n            return Color.argb(a,\n                    Math.min(r,255),\n                    Math.min(g,255),\n                    Math.min(b,255));\n        }\n    }\n}\n\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/bluedot/SettingsActivity.java",
    "content": "package com.stuffaboutcode.bluedot;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.preference.PreferenceFragmentCompat;\nimport androidx.preference.Preference;\nimport androidx.preference.ListPreference;\nimport androidx.preference.SwitchPreferenceCompat;\nimport android.content.SharedPreferences;\n\npublic class SettingsActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.settings_activity);\n\n        getSupportFragmentManager()\n                .beginTransaction()\n                .replace(R.id.settings, new SettingsFragment())\n                .commit();\n\n        ActionBar actionBar = getSupportActionBar();\n\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n\n    }\n\n    public static class SettingsFragment\n            extends PreferenceFragmentCompat\n            implements SharedPreferences.OnSharedPreferenceChangeListener{\n\n        @Override\n        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {\n            setPreferencesFromResource(R.xml.root_preferences, rootKey);\n\n            setupPreferences();\n        }\n\n        @Override\n        public void onResume() {\n            super.onResume();\n\n            getPreferenceScreen().getSharedPreferences()\n                    .registerOnSharedPreferenceChangeListener(this);\n        }\n\n        @Override\n        public void onPause() {\n            super.onPause();\n\n            getPreferenceScreen().getSharedPreferences()\n                    .unregisterOnSharedPreferenceChangeListener(this);\n        }\n\n        @Override\n        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,String key) {\n            setupPreferences();\n        }\n\n        private void setupPreferences() {\n            Preference default_port = findPreference(\"default_port\");\n            SwitchPreferenceCompat default_port_switch = (SwitchPreferenceCompat)default_port;\n            Preference port = findPreference(\"port\");\n            ListPreference port_list = (ListPreference) port;\n            if (default_port_switch != null) {\n                if (port_list != null) {\n                    // disable the port if the default_port is enabled\n                    if (default_port_switch.isChecked()) port.setVisible(false);\n                    else port.setVisible(true);\n                    // set the port summary\n                    port.setSummary(port_list.getEntry());\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/Log.java",
    "content": "/*\r\n * Copyright (C) 2013 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.stuffaboutcode.logger;\r\n\r\n/**\r\n * Helper class for a list (or tree) of LoggerNodes.\r\n *\r\n * <p>When this is set as the head of the list,\r\n * an instance of it can function as a drop-in replacement for {@link android.util.Log}.\r\n * Most of the methods in this class server only to map a method call in Log to its equivalent\r\n * in LogNode.</p>\r\n */\r\npublic class Log {\r\n    // Grabbing the native values from Android's native logging facilities,\r\n    // to make for easy migration and interop.\r\n    public static final int NONE = -1;\r\n    public static final int VERBOSE = android.util.Log.VERBOSE;\r\n    public static final int DEBUG = android.util.Log.DEBUG;\r\n    public static final int INFO = android.util.Log.INFO;\r\n    public static final int WARN = android.util.Log.WARN;\r\n    public static final int ERROR = android.util.Log.ERROR;\r\n    public static final int ASSERT = android.util.Log.ASSERT;\r\n\r\n    // Stores the beginning of the LogNode topology.\r\n    private static LogNode mLogNode;\r\n\r\n    /**\r\n     * Returns the next LogNode in the linked list.\r\n     */\r\n    public static LogNode getLogNode() {\r\n        return mLogNode;\r\n    }\r\n\r\n    /**\r\n     * Sets the LogNode data will be sent to.\r\n     */\r\n    public static void setLogNode(LogNode node) {\r\n        mLogNode = node;\r\n    }\r\n\r\n    /**\r\n     * Instructs the LogNode to print the log data provided. Other LogNodes can\r\n     * be chained to the end of the LogNode as desired.\r\n     *\r\n     * @param priority Log level of the data being logged. Verbose, Error, etc.\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void println(int priority, String tag, String msg, Throwable tr) {\r\n        if (mLogNode != null) {\r\n            mLogNode.println(priority, tag, msg, tr);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Instructs the LogNode to print the log data provided. Other LogNodes can\r\n     * be chained to the end of the LogNode as desired.\r\n     *\r\n     * @param priority Log level of the data being logged. Verbose, Error, etc.\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged. The actual message to be logged.\r\n     */\r\n    public static void println(int priority, String tag, String msg) {\r\n        println(priority, tag, msg, null);\r\n    }\r\n\r\n   /**\r\n     * Prints a message at VERBOSE priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void v(String tag, String msg, Throwable tr) {\r\n        println(VERBOSE, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at VERBOSE priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void v(String tag, String msg) {\r\n        v(tag, msg, null);\r\n    }\r\n\r\n\r\n    /**\r\n     * Prints a message at DEBUG priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void d(String tag, String msg, Throwable tr) {\r\n        println(DEBUG, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at DEBUG priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void d(String tag, String msg) {\r\n        d(tag, msg, null);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at INFO priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void i(String tag, String msg, Throwable tr) {\r\n        println(INFO, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at INFO priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void i(String tag, String msg) {\r\n        i(tag, msg, null);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at WARN priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void w(String tag, String msg, Throwable tr) {\r\n        println(WARN, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at WARN priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void w(String tag, String msg) {\r\n        w(tag, msg, null);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at WARN priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void w(String tag, Throwable tr) {\r\n        w(tag, null, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at ERROR priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void e(String tag, String msg, Throwable tr) {\r\n        println(ERROR, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at ERROR priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void e(String tag, String msg) {\r\n        e(tag, msg, null);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at ASSERT priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void wtf(String tag, String msg, Throwable tr) {\r\n        println(ASSERT, tag, msg, tr);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at ASSERT priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param msg The actual message to be logged.\r\n     */\r\n    public static void wtf(String tag, String msg) {\r\n        wtf(tag, msg, null);\r\n    }\r\n\r\n    /**\r\n     * Prints a message at ASSERT priority.\r\n     *\r\n     * @param tag Tag for for the log data. Can be used to organize log statements.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public static void wtf(String tag, Throwable tr) {\r\n        wtf(tag, null, tr);\r\n    }\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/LogFragment.java",
    "content": "/*\r\n* Copyright 2013 The Android Open Source Project\r\n*\r\n* Licensed under the Apache License, Version 2.0 (the \"License\");\r\n* you may not use this file except in compliance with the License.\r\n* You may obtain a copy of the License at\r\n*\r\n*     http://www.apache.org/licenses/LICENSE-2.0\r\n*\r\n* Unless required by applicable law or agreed to in writing, software\r\n* distributed under the License is distributed on an \"AS IS\" BASIS,\r\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n* See the License for the specific language governing permissions and\r\n* limitations under the License.\r\n*/\r\n/*\r\n * Copyright 2013 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *     http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage com.stuffaboutcode.logger;\r\n\r\nimport android.graphics.Typeface;\r\nimport android.os.Bundle;\r\nimport androidx.fragment.app.Fragment;\r\nimport android.text.Editable;\r\nimport android.text.TextWatcher;\r\nimport android.view.Gravity;\r\nimport android.view.LayoutInflater;\r\nimport android.view.View;\r\nimport android.view.ViewGroup;\r\nimport android.widget.ScrollView;\r\n\r\n/**\r\n * Simple fraggment which contains a LogView and uses is to output log data it receives\r\n * through the LogNode interface.\r\n */\r\npublic class LogFragment extends Fragment {\r\n\r\n    private LogView mLogView;\r\n    private ScrollView mScrollView;\r\n\r\n    public LogFragment() {}\r\n\r\n    public View inflateViews() {\r\n        mScrollView = new ScrollView(getActivity());\r\n        ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(\r\n                ViewGroup.LayoutParams.MATCH_PARENT,\r\n                ViewGroup.LayoutParams.MATCH_PARENT);\r\n        mScrollView.setLayoutParams(scrollParams);\r\n\r\n        mLogView = new LogView(getActivity());\r\n        ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);\r\n        logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;\r\n        mLogView.setLayoutParams(logParams);\r\n        mLogView.setClickable(true);\r\n        mLogView.setFocusable(true);\r\n        mLogView.setTypeface(Typeface.MONOSPACE);\r\n\r\n        // Want to set padding as 16 dips, setPadding takes pixels.  Hooray math!\r\n        int paddingDips = 16;\r\n        double scale = getResources().getDisplayMetrics().density;\r\n        int paddingPixels = (int) ((paddingDips * (scale)) + .5);\r\n        mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);\r\n        mLogView.setCompoundDrawablePadding(paddingPixels);\r\n\r\n        mLogView.setGravity(Gravity.BOTTOM);\r\n        mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);\r\n\r\n        mScrollView.addView(mLogView);\r\n        return mScrollView;\r\n    }\r\n\r\n    @Override\r\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\r\n                             Bundle savedInstanceState) {\r\n\r\n        View result = inflateViews();\r\n\r\n        mLogView.addTextChangedListener(new TextWatcher() {\r\n            @Override\r\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}\r\n\r\n            @Override\r\n            public void onTextChanged(CharSequence s, int start, int before, int count) {}\r\n\r\n            @Override\r\n            public void afterTextChanged(Editable s) {\r\n                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);\r\n            }\r\n        });\r\n        return result;\r\n    }\r\n\r\n    public LogView getLogView() {\r\n        return mLogView;\r\n    }\r\n}"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/LogNode.java",
    "content": "/*\r\n * Copyright (C) 2012 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.stuffaboutcode.logger;\r\n\r\n/**\r\n * Basic interface for a logging system that can output to one or more targets.\r\n * Note that in addition to classes that will output these logs in some format,\r\n * one can also implement this interface over a filter and insert that in the chain,\r\n * such that no targets further down see certain data, or see manipulated forms of the data.\r\n * You could, for instance, write a \"ToHtmlLoggerNode\" that just converted all the log data\r\n * it received to HTML and sent it along to the next node in the chain, without printing it\r\n * anywhere.\r\n */\r\npublic interface LogNode {\r\n\r\n    /**\r\n     * Instructs first LogNode in the list to print the log data provided.\r\n     * @param priority Log level of the data being logged.  Verbose, Error, etc.\r\n     * @param tag Tag for for the log data.  Can be used to organize log statements.\r\n     * @param msg The actual message to be logged. The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    public void println(int priority, String tag, String msg, Throwable tr);\r\n\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/LogView.java",
    "content": "/*\r\n * Copyright (C) 2013 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.stuffaboutcode.logger;\r\n\r\nimport android.app.Activity;\r\nimport android.content.Context;\r\nimport android.util.*;\r\nimport android.widget.TextView;\r\n\r\n/** Simple TextView which is used to output log data received through the LogNode interface.\r\n*/\r\npublic class LogView extends TextView implements LogNode {\r\n\r\n    public LogView(Context context) {\r\n        super(context);\r\n    }\r\n\r\n    public LogView(Context context, AttributeSet attrs) {\r\n        super(context, attrs);\r\n    }\r\n\r\n    public LogView(Context context, AttributeSet attrs, int defStyle) {\r\n        super(context, attrs, defStyle);\r\n    }\r\n\r\n    /**\r\n     * Formats the log data and prints it out to the LogView.\r\n     * @param priority Log level of the data being logged.  Verbose, Error, etc.\r\n     * @param tag Tag for for the log data.  Can be used to organize log statements.\r\n     * @param msg The actual message to be logged. The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    @Override\r\n    public void println(int priority, String tag, String msg, Throwable tr) {\r\n\r\n        \r\n        String priorityStr = null;\r\n\r\n        // For the purposes of this View, we want to print the priority as readable text.\r\n        switch(priority) {\r\n            case android.util.Log.VERBOSE:\r\n                priorityStr = \"VERBOSE\";\r\n                break;\r\n            case android.util.Log.DEBUG:\r\n                priorityStr = \"DEBUG\";\r\n                break;\r\n            case android.util.Log.INFO:\r\n                priorityStr = \"INFO\";\r\n                break;\r\n            case android.util.Log.WARN:\r\n                priorityStr = \"WARN\";\r\n                break;\r\n            case android.util.Log.ERROR:\r\n                priorityStr = \"ERROR\";\r\n                break;\r\n            case android.util.Log.ASSERT:\r\n                priorityStr = \"ASSERT\";\r\n                break;\r\n            default:\r\n                break;\r\n        }\r\n\r\n        // Handily, the Log class has a facility for converting a stack trace into a usable string.\r\n        String exceptionStr = null;\r\n        if (tr != null) {\r\n            exceptionStr = android.util.Log.getStackTraceString(tr);\r\n        }\r\n\r\n        // Take the priority, tag, message, and exception, and concatenate as necessary\r\n        // into one usable line of text.\r\n        final StringBuilder outputBuilder = new StringBuilder();\r\n\r\n        String delimiter = \"\\t\";\r\n        appendIfNotNull(outputBuilder, priorityStr, delimiter);\r\n        appendIfNotNull(outputBuilder, tag, delimiter);\r\n        appendIfNotNull(outputBuilder, msg, delimiter);\r\n        appendIfNotNull(outputBuilder, exceptionStr, delimiter);\r\n\r\n        // In case this was originally called from an AsyncTask or some other off-UI thread,\r\n        // make sure the update occurs within the UI thread.\r\n        ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {\r\n            @Override\r\n            public void run() {\r\n                // Display the text we just generated within the LogView.\r\n                appendToLog(outputBuilder.toString());\r\n            }\r\n        })));\r\n\r\n        if (mNext != null) {\r\n            mNext.println(priority, tag, msg, tr);\r\n        }\r\n    }\r\n\r\n    public LogNode getNext() {\r\n        return mNext;\r\n    }\r\n\r\n    public void setNext(LogNode node) {\r\n        mNext = node;\r\n    }\r\n\r\n    /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since\r\n     * the logger takes so many arguments that might be null, this method helps cut out some of the\r\n     * agonizing tedium of writing the same 3 lines over and over.\r\n     * @param source StringBuilder containing the text to append to.\r\n     * @param addStr The String to append\r\n     * @param delimiter The String to separate the source and appended strings. A tab or comma,\r\n     *                  for instance.\r\n     * @return The fully concatenated String as a StringBuilder\r\n     */\r\n    private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {\r\n        if (addStr != null) {\r\n            if (addStr.length() == 0) {\r\n                delimiter = \"\";\r\n            }\r\n\r\n            return source.append(addStr).append(delimiter);\r\n        }\r\n        return source;\r\n    }\r\n\r\n    // The next LogNode in the chain.\r\n    LogNode mNext;\r\n\r\n    /** Outputs the string as a new line of log data in the LogView. */\r\n    public void appendToLog(String s) {\r\n        append(\"\\n\" + s);\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/LogWrapper.java",
    "content": "/*\r\n * Copyright (C) 2012 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.stuffaboutcode.logger;\r\n\r\nimport android.util.Log;\r\n\r\n/**\r\n * Helper class which wraps Android's native Log utility in the Logger interface.  This way\r\n * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.\r\n */\r\npublic class LogWrapper implements LogNode {\r\n\r\n    // For piping:  The next node to receive Log data after this one has done its work.\r\n    private LogNode mNext;\r\n\r\n    /**\r\n     * Returns the next LogNode in the linked list.\r\n     */\r\n    public LogNode getNext() {\r\n        return mNext;\r\n    }\r\n\r\n    /**\r\n     * Sets the LogNode data will be sent to..\r\n     */\r\n    public void setNext(LogNode node) {\r\n        mNext = node;\r\n    }\r\n\r\n    /**\r\n     * Prints data out to the console using Android's native log mechanism.\r\n     * @param priority Log level of the data being logged.  Verbose, Error, etc.\r\n     * @param tag Tag for for the log data.  Can be used to organize log statements.\r\n     * @param msg The actual message to be logged. The actual message to be logged.\r\n     * @param tr If an exception was thrown, this can be sent along for the logging facilities\r\n     *           to extract and print useful information.\r\n     */\r\n    @Override\r\n    public void println(int priority, String tag, String msg, Throwable tr) {\r\n        // There actually are log methods that don't take a msg parameter.  For now,\r\n        // if that's the case, just convert null to the empty string and move on.\r\n        String useMsg = msg;\r\n        if (useMsg == null) {\r\n            useMsg = \"\";\r\n        }\r\n\r\n        // If an exeption was provided, convert that exception to a usable string and attach\r\n        // it to the end of the msg method.\r\n        if (tr != null) {\r\n            msg += \"\\n\" + Log.getStackTraceString(tr);\r\n        }\r\n\r\n        // This is functionally identical to Log.x(tag, useMsg);\r\n        // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)\r\n        Log.println(priority, tag, useMsg);\r\n\r\n        // If this isn't the last node in the chain, move things along.\r\n        if (mNext != null) {\r\n            mNext.println(priority, tag, msg, tr);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/java/com/stuffaboutcode/logger/MessageOnlyLogFilter.java",
    "content": "/*\r\n * Copyright (C) 2013 The Android Open Source Project\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.stuffaboutcode.logger;\r\n\r\n/**\r\n * Simple {@link LogNode} filter, removes everything except the message.\r\n * Useful for situations like on-screen log output where you don't want a lot of metadata displayed,\r\n * just easy-to-read message updates as they're happening.\r\n */\r\npublic class MessageOnlyLogFilter implements LogNode {\r\n\r\n    LogNode mNext;\r\n\r\n    /**\r\n     * Takes the \"next\" LogNode as a parameter, to simplify chaining.\r\n     *\r\n     * @param next The next LogNode in the pipeline.\r\n     */\r\n    public MessageOnlyLogFilter(LogNode next) {\r\n        mNext = next;\r\n    }\r\n\r\n    public MessageOnlyLogFilter() {\r\n    }\r\n\r\n    @Override\r\n    public void println(int priority, String tag, String msg, Throwable tr) {\r\n        if (mNext != null) {\r\n            getNext().println(Log.NONE, null, msg, null);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns the next LogNode in the chain.\r\n     */\r\n    public LogNode getNext() {\r\n        return mNext;\r\n    }\r\n\r\n    /**\r\n     * Sets the LogNode data will be sent to..\r\n     */\r\n    public void setNext(LogNode node) {\r\n        mNext = node;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/drawable/round_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n<item android:state_pressed=\"false\">\r\n    <shape android:shape=\"oval\">\r\n        <solid android:color=\"#0000ff\"/>\r\n    </shape>\r\n</item>\r\n<item android:state_pressed=\"true\">\r\n    <shape android:shape=\"oval\">\r\n        <solid android:color=\"#191970\"/>\r\n    </shape>\r\n</item>\r\n</selector>"
  },
  {
    "path": "clients/android/app/src/main/res/layout/activity_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    tools:context=\"com.stuffaboutcode.bluedot.Button\">\r\n\r\n    <TextView\r\n        android:id=\"@+id/status\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"17dp\"\r\n        android:text=\"Status\"\r\n        app:layout_constraintTop_toTopOf=\"parent\"\r\n        android:layout_marginTop=\"16dp\"\r\n        android:layout_marginLeft=\"8dp\"\r\n        app:layout_constraintLeft_toLeftOf=\"parent\"\r\n        android:layout_marginStart=\"8dp\" />\r\n\r\n    <!-- <Button\r\n        android:id=\"@+id/roundButton\"\r\n        android:background=\"@drawable/round_button\"\r\n        android:layout_width=\"0dp\"\r\n        android:layout_height=\"0dp\"\r\n        app:layout_constraintDimensionRatio=\"1:1\"\r\n        android:layout_marginTop=\"0dp\"\r\n        app:layout_constraintTop_toBottomOf=\"@+id/status\"\r\n        app:layout_constraintBottom_toBottomOf=\"parent\"\r\n        android:layout_marginBottom=\"24dp\"\r\n        android:layout_marginLeft=\"16dp\"\r\n        app:layout_constraintLeft_toLeftOf=\"parent\"\r\n        android:layout_marginRight=\"16dp\"\r\n        app:layout_constraintRight_toRightOf=\"parent\"\r\n        app:layout_constraintHorizontal_bias=\"0.0\" /> -->\r\n\r\n    <com.stuffaboutcode.bluedot.DynamicMatrix\r\n        android:id=\"@+id/matrix\"\r\n        android:layout_width=\"0dp\"\r\n        android:layout_height=\"0dp\"\r\n        android:layout_marginTop=\"8dp\"\r\n        android:layout_marginBottom=\"8dp\"\r\n        android:layout_marginLeft=\"8dp\"\r\n        android:layout_marginRight=\"8dp\"\r\n        app:layout_constraintBottom_toBottomOf=\"parent\"\r\n        app:layout_constraintTop_toBottomOf=\"@+id/status\"\r\n        app:layout_constraintLeft_toLeftOf=\"parent\"\r\n        app:layout_constraintRight_toRightOf=\"parent\"\r\n        app:cols=\"1\"\r\n        app:rows=\"1\" />\r\n\r\n</androidx.constraintlayout.widget.ConstraintLayout>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/layout/activity_devices.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    tools:context=\"com.stuffaboutcode.bluedot.Devices\"\r\n    tools:layout_editor_absoluteY=\"80dp\"\r\n    tools:layout_editor_absoluteX=\"0dp\">\r\n\r\n    <TextView\r\n        android:layout_width=\"196dp\"\r\n        android:layout_height=\"17dp\"\r\n        android:text=\"Connect\"\r\n        android:id=\"@+id/connect\"\r\n        android:layout_marginStart=\"8dp\"\r\n        app:layout_constraintTop_toTopOf=\"parent\"\r\n        android:layout_marginTop=\"16dp\"\r\n        android:layout_marginLeft=\"16dp\"\r\n        app:layout_constraintLeft_toLeftOf=\"parent\" />\r\n\r\n    <androidx.constraintlayout.widget.Guideline\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:id=\"@+id/guideline\"\r\n        android:orientation=\"vertical\"\r\n        app:layout_constraintGuide_begin=\"20dp\"\r\n        tools:layout_editor_absoluteY=\"0dp\"\r\n        tools:layout_editor_absoluteX=\"20dp\" />\r\n\r\n    <ListView\r\n        android:id=\"@+id/listView\"\r\n        android:layout_width=\"0dp\"\r\n        android:layout_height=\"0dp\"\r\n        android:layout_marginBottom=\"8dp\"\r\n        android:layout_marginEnd=\"16dp\"\r\n        android:layout_marginLeft=\"8dp\"\r\n        android:layout_marginRight=\"8dp\"\r\n        android:layout_marginStart=\"16dp\"\r\n        android:layout_marginTop=\"8dp\"\r\n        app:layout_constraintBottom_toBottomOf=\"parent\"\r\n        app:layout_constraintLeft_toLeftOf=\"parent\"\r\n        app:layout_constraintRight_toRightOf=\"parent\"\r\n        app:layout_constraintTop_toBottomOf=\"@+id/connect\"\r\n        app:layout_constraintVertical_bias=\"0.0\" />\r\n\r\n</androidx.constraintlayout.widget.ConstraintLayout>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/layout/settings_activity.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/settings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>"
  },
  {
    "path": "clients/android/app/src/main/res/menu/settings_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/settings\"\n        android:icon=\"@android:drawable/ic_menu_preferences\"\n        android:title=\"Settings\" />\n\n    <item\n        android:id=\"@+id/help\"\n        android:icon=\"@android:drawable/ic_menu_help\"\n        android:title=\"Help\" />\n</menu>"
  },
  {
    "path": "clients/android/app/src/main/res/values/arrays.xml",
    "content": "<resources>\n    <string-array name=\"ports\">\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n        <item>4</item>\n        <item>5</item>\n        <item>6</item>\n        <item>7</item>\n        <item>8</item>\n        <item>9</item>\n        <item>10</item>\n        <item>11</item>\n        <item>12</item>\n        <item>13</item>\n        <item>14</item>\n        <item>15</item>\n        <item>16</item>\n        <item>17</item>\n        <item>18</item>\n        <item>19</item>\n        <item>20</item>\n        <item>21</item>\n        <item>22</item>\n        <item>23</item>\n        <item>24</item>\n        <item>25</item>\n        <item>26</item>\n        <item>27</item>\n        <item>28</item>\n        <item>29</item>\n        <item>30</item>\n    </string-array>\n</resources>"
  },
  {
    "path": "clients/android/app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"DynamicMatrix\">\n        <attr name=\"rows\" format=\"integer\" />\n        <attr name=\"cols\" format=\"integer\" />\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "clients/android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<resources>\r\n    <color name=\"colorPrimary\">#3F51B5</color>\r\n    <color name=\"colorPrimaryDark\">#303F9F</color>\r\n    <color name=\"colorAccent\">#FF4081</color>\r\n    <color name=\"white\">#FFFFFF</color>\r\n    <color name=\"red\">#FF0000</color>\r\n    <color name=\"blue\">#0000FF</color>\r\n    <color name=\"darkgrey\">#6d6d6e</color>\r\n    <color name=\"defaultCellColor\">#0000FF</color>\r\n</resources>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/values/dimens.xml",
    "content": "<resources>\r\n    <dimen name=\"fab_margin\">16dp</dimen>\r\n</resources>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\r\n    <string name=\"app_name\">BlueButton</string>\r\n    <string name=\"title_activity_button\">Button</string>\r\n    <string name=\"title_activity_settings\">Settings</string>\r\n\r\n    <!-- Preference Titles -->\r\n    <string name=\"messages_header\">Messages</string>\r\n    <string name=\"sync_header\">Sync</string>\r\n\r\n    <!-- Messages Preferences -->\r\n    <string name=\"signature_title\">Your signature</string>\r\n    <string name=\"reply_title\">Default reply action</string>\r\n\r\n    <!-- Sync Preferences -->\r\n    <string name=\"sync_title\">Sync email periodically</string>\r\n    <string name=\"attachment_title\">Download incoming attachments</string>\r\n    <string name=\"attachment_summary_on\">Automatically download attachments for incoming emails\r\n    </string>\r\n    <string name=\"attachment_summary_off\">Only download attachments when manually requested</string>\r\n</resources>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/values/styles.xml",
    "content": "<resources>\r\n\r\n    <!-- Base application theme. -->\r\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\r\n        <!-- Customize your theme here. -->\r\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\r\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\r\n        <item name=\"colorAccent\">@color/colorAccent</item>\r\n    </style>\r\n\r\n    <style name=\"AppTheme.NoActionBar\">\r\n        <item name=\"windowActionBar\">false</item>\r\n        <item name=\"windowNoTitle\">true</item>\r\n    </style>\r\n\r\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\" />\r\n\r\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\" />\r\n\r\n</resources>\r\n"
  },
  {
    "path": "clients/android/app/src/main/res/xml/root_preferences.xml",
    "content": "<!--\n  ~ Copyright 2018 The app Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<PreferenceScreen xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <PreferenceCategory app:title=\"Advanced configuration\">\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:icon=\"@android:drawable/ic_menu_preferences\"\n            android:key=\"default_port\"\n            android:title=\"Use default port\" />\n        <ListPreference\n            android:defaultValue=\"1\"\n            android:entries=\"@array/ports\"\n            android:entryValues=\"@array/ports\"\n            android:key=\"port\"\n            android:title=\"Bluetooth port\"\n            />\n\n    </PreferenceCategory>\n\n</PreferenceScreen>\n"
  },
  {
    "path": "clients/android/app/src/test/java/com/stuffaboutcode/bluedot/ExampleUnitTest.java",
    "content": "package com.stuffaboutcode.bluedot;\r\n\r\nimport org.junit.Test;\r\n\r\nimport static org.junit.Assert.*;\r\n\r\n/**\r\n * Example local unit test, which will execute on the development machine (host).\r\n *\r\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\r\n */\r\npublic class ExampleUnitTest {\r\n    @Test\r\n    public void addition_isCorrect() throws Exception {\r\n        assertEquals(4, 2 + 2);\r\n    }\r\n}"
  },
  {
    "path": "clients/android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\r\n\r\nbuildscript {\r\n    repositories {\r\n        google()\r\n        jcenter()\r\n    }\r\n    dependencies {\r\n        classpath 'com.android.tools.build:gradle:7.3.1'\r\n\r\n        // NOTE: Do not place your application dependencies here; they belong\r\n        // in the individual module build.gradle files\r\n    }\r\n}\r\n\r\n\r\nallprojects {\r\n    repositories {\r\n        google()\r\n        jcenter()\r\n    }\r\n}\r\n\r\ntask clean(type: Delete) {\r\n    delete rootProject.buildDir\r\n}\r\n"
  },
  {
    "path": "clients/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Dec 23 09:57:24 GMT 2022\r\ndistributionBase=GRADLE_USER_HOME\r\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.4-bin.zip\r\ndistributionPath=wrapper/dists\r\nzipStorePath=wrapper/dists\r\nzipStoreBase=GRADLE_USER_HOME\r\n"
  },
  {
    "path": "clients/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\r\n\r\n# IDE (e.g. Android Studio) users:\r\n# Gradle settings configured through the IDE *will override*\r\n# any settings specified in this file.\r\n\r\n# For more details on how to configure your build environment visit\r\n# http://www.gradle.org/docs/current/userguide/build_environment.html\r\n\r\n# Specifies the JVM arguments used for the daemon process.\r\n# The setting is particularly useful for tweaking memory settings.\r\nandroid.enableJetifier=true\r\nandroid.useAndroidX=true\r\norg.gradle.jvmargs=-Xmx1536m\r\n\r\n# When configured, Gradle will run in incubating parallel mode.\r\n# This option should only be used with decoupled projects. More details, visit\r\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\r\n# org.gradle.parallel=true\r\n"
  },
  {
    "path": "clients/android/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "clients/android/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "clients/android/settings.gradle",
    "content": "include ':app'\r\n"
  },
  {
    "path": "clients/python/README.rst",
    "content": "Blue Dot Python App\n===================\n\nBlue Dot Python app allows you to use another Raspberry Pi (or linux based computer) as the Blue Dot remote.\n\n|bluedotpython| |bluedotpythondevices|\n\nStart\n-----\n\nThe app is included in the bluedot Python library:\n\n1. If you havent already done so, pair your raspberry pi and install the Python library as described in the `getting started`_ guide\n2. Run the Blue Dot app::\n\n    bluedotapp\n\n4. Select your Raspberry Pi from the paired devices list\n\n|bluedotpythondevices|\n\n5. Press the Dot\n\n|bluedotpython|\n\nOptions\n-------\n\nTo get help with the Blue Dot app options::\n\n    bluedotapp --help\n\nIf you have more than 1 bluetooth device you can use ``--device`` to use a particular device::\n\n    bluedotapp --device hci1\n\nYou can specify the server to connect to at startup by using the ``--server`` option::\n\n    bluedotapp --server myraspberrypi\n\nIf you are using a different port you can specify it using the ``--port`` option::\n\n    bluedotapp --port 1\n\nThe screen size of the Blue Dot app can be changed using the ``width`` and ``height`` options and specifying the number of pixels::\n\n    bluedotapp --width 500 --height 500\n\nThe app can also be used full screen, if no ``width`` or ``height`` is given the screen will be sized to the current resolution of the screen::\n\n    bluedotapp --fullscreen\n\n.. _getting started: http://bluedot.readthedocs.io/en/latest/gettingstarted.html\n\n.. |bluedotpython| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotpython.png\n   :height: 274 px\n   :width: 324 px\n   :scale: 100 %\n   :alt: blue dot python app\n\n.. |bluedotpythondevices| image:: https://raw.githubusercontent.com/martinohanlon/BlueDot/master/docs/images/bluedotpythondevices.png\n   :height: 273 px\n   :width: 326 px\n   :scale: 100 %\n   :alt: blue dot devices\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = bluedot\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)"
  },
  {
    "path": "docs/bluedotandroidapp.rst",
    "content": "Blue Dot Android App\n====================\n\nThe `Blue Dot app`_ is available to download from the Google Play store.\n\nPlease leave a rating and review if you find Blue Dot useful :)\n\n|bluedotapp| |bluedotappdevices|\n\nStart\n-----\n\n1. Download the `Blue Dot app`_ from the Google Play store.\n\n2. If you havent already done so, pair your raspberry pi as described in the\n   :doc:`gettingstarted` guide.\n\n3. Run the Blue Dot app\n\n   |bluedotappicon|\n\n4. Select your Raspberry Pi from the paired devices list\n\n   |bluedotappdevices|\n\n5. Press the Dot\n\n   |bluedotapp|\n\n.. _Blue Dot app: http://play.google.com/store/apps/details?id=com.stuffaboutcode.bluedot\n\n.. |bluedotapp| image:: images/bluedotandroid_small.png\n   :alt: Screenshot of Blue Dot app\n\n.. |bluedotappdevices| image:: images/bluedotandroiddevices_small.png\n   :alt: Screenshot of Blue Dot devices list screen\n\n.. |bluedotappicon| image:: images/bluedotandroidicon.png\n   :alt: Blue Dot icon\n"
  },
  {
    "path": "docs/bluedotpythonapp.rst",
    "content": "Blue Dot Python App\n===================\n\nBlue Dot Python app allows you to use another Raspberry Pi (or linux based computer) as the Blue Dot remote.\n\n|bluedotpython| |bluedotpythondevices|\n\nStart\n-----\n\nThe app is included in the bluedot Python library:\n\n1. If you havent already done so, pair your raspberry pi and install the Python\n   library as described in the :doc:`gettingstarted` guide\n\n2. Run the Blue Dot app::\n\n    bluedotapp\n\n3. Select your Raspberry Pi from the paired devices list\n\n   |bluedotpythondevices|\n\n4. Press the Dot\n\n   |bluedotpython|\n\nOptions\n-------\n\nTo get help with the Blue Dot app options::\n\n    bluedotapp --help\n\nIf you have more than 1 bluetooth device you can use ``--device`` to use a particular device::\n\n    bluedotapp --device hci1\n\nYou can specify the server to connect to at startup by using the ``--server`` option::\n\n    bluedotapp --server myraspberrypi\n\nThe screen size of the Blue Dot app can be changed using the ``width`` and ``height`` options and specifying the number of pixels::\n\n    bluedotapp --width 500 --height 500\n\nThe app can also be used full screen, if no ``width`` or ``height`` is given the screen will be sized to the current resolution of the screen::\n\n    bluedotapp --fullscreen\n\n.. |bluedotpython| image:: images/bluedotpython.png\n   :alt: Screenshot of Blue Dot python app\n\n.. |bluedotpythondevices| image:: images/bluedotpythondevices.png\n   :alt: Screenshot of Blue Dot devices list\n"
  },
  {
    "path": "docs/btcommapi.rst",
    "content": "Bluetooth Comm API\n==================\n\nBlue Dot also contains a useful :mod:`btcomm` API for sending and\nreceiving data over Bluetooth.\n\nFor normal use of Blue Dot, this API doesn't need to be used, but its included\nin the documentation for info and for those who might need a simple Bluetooth\ncommunication library.\n\n.. module:: bluedot.btcomm\n\nBluetoothServer\n---------------\n\n.. autoclass:: BluetoothServer\n\nBluetoothClient\n---------------\n\n.. autoclass:: BluetoothClient\n\nBluetoothAdapter\n----------------\n\n.. autoclass:: BluetoothAdapter\n\n"
  },
  {
    "path": "docs/build.rst",
    "content": "Build\r\n=====\r\n\r\nThese are instructions for how to develop, build and deploy Blue Dot.\r\n\r\nDevelop\r\n-------\r\n\r\nInstall / upgrade tools::\r\n\r\n    sudo python3 -m pip install --upgrade pip setuptools wheel twine virtualenv\r\n\r\nCreate a virtual environment (recommended)::\r\n\r\n    virtualenv --system-site-packages bluedot-dev\r\n    cd bluedot-dev\r\n    source bin/activate \r\n\r\nClone repo and install for dev::\r\n\r\n    git clone https://github.com/martinohanlon/BlueDot\r\n    cd BlueDot\r\n    git checkout dev\r\n    python3 setup.py develop\r\n\r\nTest\r\n----\r\n\r\nInstall `pytest`_::\r\n\r\n    pip3 install -U pytest\r\n\r\nRun tests::\r\n\r\n    cd BlueDot/tests\r\n    pytest -v\r\n\r\nDeploy\r\n------\r\n\r\nBuild for deployment::\r\n\r\n    python3 setup.py sdist\r\n    python3 setup.py bdist_wheel\r\n\r\nDeploy to `PyPI`_::\r\n\r\n    twine upload dist/* --skip-existing\r\n\r\n\r\n.. _pytest: https://doc.pytest.org/\r\n.. _PyPI: https://pypi.python.org/pypi\r\n"
  },
  {
    "path": "docs/changelog.rst",
    "content": "Change log\n==========\n\n.. currentmodule:: bluedot\n\nBluedot Python library\n----------------------\n\n2.0.0 - 2020-11-01\n~~~~~~~~~~~~~~~~~~\n\n * implementation of multiple buttons in a matrix\n * refactor of significant portions of the code base\n * improvement to btcomm to manage large messages \n * update to MockBlueDot\n * deprecated BlueDot.interaction\n * added warnings when invalid data is received\n * support for protocol version 2\n * removed support for Python 2, 3.3 & 3.4\n\n1.3.2 - 2019-04-22\n~~~~~~~~~~~~~~~~~~\n\n * change to how callbacks are called\n * added `set_when_pressed`, `set_when_released`, etc to allow callbacks to be called in their own threads.\n\n1.3.1 - 2019-01-01\n~~~~~~~~~~~~~~~~~~\n\n * minor bug fix to launch_mock_app\n\n1.3.0 - 2018-12-30\n~~~~~~~~~~~~~~~~~~\n\n * added ability to change the color, border, shape and visibility of the dot (:attr:`~BlueDot.color`, :attr:`~BlueDot.border`, :attr:`~BlueDot.square`, :attr:`~BlueDot.visible`)\n * added protocol version checking\n * minor threading changes in btcomm\n * updates to the Blue Dot Python app\n * rewrite of the mock app\n * support for protocol version 1\n \n1.2.3 - 2018-02-22\n~~~~~~~~~~~~~~~~~~\n\n * fix to `wait_for_press` and `wait_for_release`\n * when_client_connects and when_client_disconnects callbacks are now threaded\n * The python blue dot app can now be started with the command `bluedotapp`\n * new tests for `wait_for_(events)`\n\n1.2.2 - 2017-12-30\n~~~~~~~~~~~~~~~~~~\n\n * bluetooth comms tests and minor bug fix in :class:`~.btcomm.BluetoothClient`\n\n1.2.1 - 2017-12-18\n~~~~~~~~~~~~~~~~~~\n\n * massive code and docs tidy up by `Dave Jones`_\n\n1.2.0 - 2017-12-10\n~~~~~~~~~~~~~~~~~~\n\n * added when_rotated\n * threaded swipe callbacks\n * exposed new :class:`BlueDot` properties (:attr:`~BlueDot.adapter`, :attr:`~BlueDot.running`, :attr:`~BlueDot.paired_devices`)\n * fixed active bug in interaction\n * automated tests\n\n1.1.0 - 2017-11-05\n~~~~~~~~~~~~~~~~~~\n\n * threaded callbacks\n * python app rounded x,y performance improvements\n\n1.0.4 - 2017-09-10\n~~~~~~~~~~~~~~~~~~\n\n * serial port profile port fix\n * launching multiple blue dots fix\n\n1.0.3 - 2017-07-28\n~~~~~~~~~~~~~~~~~~\n\n * python 2 bug fix\n\n1.0.2 - 2017-07-23\n~~~~~~~~~~~~~~~~~~\n\n * bug fix\n\n1.0.1 - 2017-06-19\n~~~~~~~~~~~~~~~~~~\n\n * bug fixes\n\n1.0.0 - 2017-06-04\n~~~~~~~~~~~~~~~~~~\n\n * production release!\n * added double click\n * doc updates\n * minor changes\n\n0.4.0 - 2017-05-05\n~~~~~~~~~~~~~~~~~~\n\n * added swipes and interactions\n * doc updates\n * bug fix in :attr:`BlueDot.when_moved`\n\n0.3.0 - 2017-05-01\n~~~~~~~~~~~~~~~~~~\n\n * Python Blue Dot app\n * minor bug fix in :class:`~.btcomm.BluetoothClient`\n\n0.2.1 - 2017-04-23\n~~~~~~~~~~~~~~~~~~\n\n * bug fix in :class:`MockBlueDot`\n * doc fixes\n\n0.2.0 - 2017-04-23\n~~~~~~~~~~~~~~~~~~\n\n * added :attr:`~BlueDot.when_client_connects`, :attr:`~BlueDot.when_client_disconnects`\n * added :meth:`~BlueDot.allow_pairing` functions\n * refactored Bluetooth comms\n * added :class:`~.btcomm.BluetoothAdapter`\n\n0.1.2 - 2017-04-14\n~~~~~~~~~~~~~~~~~~\n\n * mock blue dot improvements\n * doc fixes\n\n0.1.1 - 2017-04-08\n~~~~~~~~~~~~~~~~~~\n\n * clamped distance in :class:`BlueDotPosition`\n\n0.1.0 - 2017-04-07\n~~~~~~~~~~~~~~~~~~\n\n * Check Bluetooth adapter is powered\n * Handle client connection timeouts\n * Docs & image updates\n\n0.0.6 - 2017-04-05\n~~~~~~~~~~~~~~~~~~\n\n * Added :class:`MockBlueDot` for testing and debugging\n * more docs\n\n0.0.4 - 2017-03-31\n~~~~~~~~~~~~~~~~~~\n\nUpdates after alpha feedback\n\n * Python 2 compatibility\n * ``.dot_position`` to ``.position``\n * ``.values`` added\n * clamped ``x``, ``y`` to 1\n * loads of doc updates\n\n0.0.2 - 2017-03-29\n~~~~~~~~~~~~~~~~~~\n\nAlpha - initial testing\n\nAndroid app\n-----------\n\n10 (2.2.1) - 2022-01-03\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Android 12+ fixes\n\n9 (2.2) - 2022-12-23\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Android SDK and API version uplift (due to google play store minimum requirements change)\n\n8 (2.1) - 2020-12-28\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * removed \"auto port discovery\" after the introduction of pulseaudio to Raspberry Pi OS broke it\n * introduced the \"default port\" setting as an alternative\n\n7 (2.0) - 2020-11-01\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * implementation of multiple buttons in a matrix\n * support for protocol version 2\n\n6 (1.3.1) - 2019-12-30\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Minor bug fix\n\n5 (1.3) - 2019-12-29\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Added settings menu so a specific bluetooth port can be selected\n * Using specific bluetooth ports, multiple apps can now connect to a single BT devices\n * Minor bugs fixes\n\n4 (1.2) - 2018-12-30\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Rewrite of the Button view\n * Rewrite of the Bluetooth comms layer\n * Support for colours, square and border\n * Landscape (and portrait) views\n * added protocol version checking\n * support for protocol version 1\n\n3 (1.1.1) - 2018-09-21\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * Android SDK version uplift (due to google play store minimum requirements change)\n\n2 (1.1) - 2017-11-05\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * better responsive layout\n * fixed issues with small screen devices\n * rounded x,y values increasing performance\n * new help icon\n * link to https://bluedot.readthedocs.io not http\n\n1 (0.0.2) - 2017-04-05\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * icon transparency\n * connection monitor\n * added info icon to https://bluedot.readthedocs.io\n\n0 (0.0.1) - 2017-03-29\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n * alpha - initial testing\n\n.. _Dave Jones: https://github.com/waveform80"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# bluedot documentation build configuration file, created by\n# sphinx-quickstart on Mon Mar 27 21:11:18 2017.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport sys\nfrom datetime import datetime\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\nimport setup as _setup\n\n# Mock out certain modules while building documentation\nclass Mock:\n    __all__ = []\n    def __init__(self, *args, **kw): pass\n    def __call__(self, *args, **kw): return Mock()\n    def __mul__(self, other): return Mock()\n    def __and__(self, other): return Mock()\n    def __bool__(self): return False\n    def __nonzero__(self): return False\n    @classmethod\n    def __getattr__(cls, name):\n        if name in ('__file__', '__path__'):\n            return '/dev/null'\n        else:\n            return Mock()\n\nsys.modules['dbus'] = Mock()\n\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = _setup.__project__\nauthor = _setup.__author__\ncopyright = \"%d %s\" % (datetime.now().year, author)\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = _setup.__version__\n# The full version, including alpha/beta/rc tags.\nrelease = _setup.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Autodoc configuration ------------------------------------------------\n\nautodoc_member_order = 'groupwise'\nautodoc_default_flags = ['members']\n\n# -- Intersphinx configuration --------------------------------------------\n\nintersphinx_mapping = {\n    'python': ('https://docs.python.org/3.5', None),\n    'gpiozero': ('https://gpiozero.readthedocs.io/en/latest', None),\n    'picamera': ('https://picamera.readthedocs.io/en/latest', None),\n    }\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nif on_rtd:\n    html_theme = 'sphinx_rtd_theme'\n    #html_theme_options = {}\n    html_sidebars = {\n        '**': [\n            'globaltoc.html',\n            'relations.html',\n            'searchbox.html',\n        ],\n    }\nelse:\n    html_theme = 'default'\n    #html_theme_options = {}\n    #html_sidebars = {}\n\nhtml_title = '%s %s Documentation' % (project, version)\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\nsuppress_warnings = ['image.nonlocal_uri']\n\n\n# -- Options for HTMLHelp output ------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = '%sdoc' % project\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n    'papersize': 'a4paper',\n    'pointsize': '10pt',\n    'preamble': r'\\def\\thempfootnote{\\arabic{mpfootnote}}', # workaround sphinx issue #2530\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        'index',                       # source start file\n        '%s.tex' % _setup.__project__, # target filename\n        '%s Documentation' % project,  # title\n        _setup.__author__,             # author\n        'manual',                      # documentclass\n        True,                          # documents ref'd from toctree only\n        ),\n]\n\nlatex_show_pagerefs = True\nlatex_show_urls = 'footnote'\n\n\n# -- Options for epub output ----------------------------------------------\n\nepub_basename = project\n#epub_theme = 'epub'\n#epub_title = html_title\nepub_author = author\nepub_identifier = 'https://bluedot.readthedocs.io/'\n#epub_tocdepth = 3\nepub_show_urls = 'no'\n#epub_use_index = True\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, project, '%s Documentation' % project, [author], 1)\n]\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = []\n"
  },
  {
    "path": "docs/dotapi.rst",
    "content": "Blue Dot API\n============\n\n.. module:: bluedot\n\nBlueDot\n-------\n\n.. autoclass:: BlueDot\n\nBlueDotButton\n-------------\n\n.. autoclass:: BlueDotButton\n\nBlueDotPosition\n---------------\n\n.. autoclass:: BlueDotPosition\n\nBlueDotInteraction\n------------------\n\n.. autoclass:: BlueDotInteraction\n\nBlueDotSwipe\n------------\n\n.. autoclass:: BlueDotSwipe\n\nBlueDotRotation\n---------------\n\n.. autoclass:: BlueDotRotation\n"
  },
  {
    "path": "docs/examples/bt_enumerate.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\n\ndevices = bd.paired_devices\nfor d in devices:\n    device_address = d[0]\n    device_name = d[1]\n"
  },
  {
    "path": "docs/examples/bt_pair_button.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import Button\nfrom signal import pause\n\nbd = BlueDot()\nbutton = Button(27)\n\nbutton.when_pressed = bd.allow_pairing\n\npause()\n"
  },
  {
    "path": "docs/examples/bt_pairing.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\nbd = BlueDot()\nbd.allow_pairing()\n\npause()\n"
  },
  {
    "path": "docs/examples/camera.py",
    "content": "from bluedot import BlueDot\nfrom picamera import PiCamera\nfrom signal import pause\n\nbd = BlueDot()\ncam = PiCamera()\n\ndef take_picture():\n    cam.capture(\"pic.jpg\")\n\nbd.when_pressed = take_picture\n\npause()\n"
  },
  {
    "path": "docs/examples/dpad.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef dpad(pos):\n    if pos.top:\n        print(\"up\")\n    elif pos.bottom:\n        print(\"down\")\n    elif pos.left:\n        print(\"left\")\n    elif pos.right:\n        print(\"right\")\n    elif pos.middle:\n        print(\"fire\")\n\nbd = BlueDot()\nbd.when_pressed = dpad\n\npause()\n"
  },
  {
    "path": "docs/examples/dpad_layout.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef up():\r\n    print(\"up\")\r\n\r\ndef down():\r\n    print(\"down\")\r\n\r\ndef left():\r\n    print(\"left\")\r\n\r\ndef right():\r\n    print(\"right\")\r\n\r\nbd = BlueDot(cols=3, rows=3)\r\nbd.color = \"gray\"\r\nbd.square = True\r\n\r\nbd[0,0].visible = False\r\nbd[2,0].visible = False\r\nbd[0,2].visible = False\r\nbd[2,2].visible = False\r\nbd[1,1].visible = False\r\n\r\nbd[1,0].when_pressed = up\r\nbd[1,2].when_pressed = down\r\nbd[0,1].when_pressed = left\r\nbd[2,1].when_pressed = right\r\n\r\npause()"
  },
  {
    "path": "docs/examples/goodbye_world.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef say_hello():\n    print(\"Hello World\")\n\ndef say_goodbye():\n    print(\"goodbye\")\n\nbd = BlueDot()\nbd.when_pressed = say_hello\nbd.when_released = say_goodbye\n\npause()\n"
  },
  {
    "path": "docs/examples/hello_event.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef say_hello():\n    print(\"Hello World\")\n\nbd = BlueDot()\nbd.when_pressed = say_hello\n\npause()\n"
  },
  {
    "path": "docs/examples/hello_world.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.wait_for_press()\nprint(\"Hello World\")\n"
  },
  {
    "path": "docs/examples/joypad.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef up():\r\n    print(\"up\")\r\n\r\ndef down():\r\n    print(\"down\")\r\n\r\ndef left():\r\n    print(\"left\")\r\n\r\ndef right():\r\n    print(\"right\")\r\n\r\ndef button_A():\r\n    print(\"A\")\r\n\r\ndef button_B():\r\n    print(\"B\")\r\n\r\nbd = BlueDot(cols=5, rows=3)\r\n\r\n# dpad buttons\r\nbd.color = \"gray\"\r\nbd.square = True\r\nbd[0,0].visible = False\r\nbd[2,0].visible = False\r\nbd[0,2].visible = False\r\nbd[2,2].visible = False\r\nbd[1,1].visible = False\r\n\r\nbd[1,0].when_pressed = up\r\nbd[1,2].when_pressed = down\r\nbd[0,1].when_pressed = left\r\nbd[2,1].when_pressed = right\r\n\r\n# buttons\r\nbd[3,0].visible = False\r\nbd[4,0].visible = False\r\nbd[3,2].visible = False\r\nbd[4,2].visible = False\r\n\r\nbd[3,1].color = \"blue\"\r\nbd[3,1].square = False\r\nbd[3,1].when_pressed = button_A\r\n\r\nbd[4,1].color = \"red\"\r\nbd[4,1].when_pressed = button_B\r\n\r\npause()"
  },
  {
    "path": "docs/examples/led1.py",
    "content": "import os\nfrom bluedot import BlueDot\nfrom gpiozero import LED\n\nbd = BlueDot()\nled = LED(27)\n\nbd.wait_for_press()\nled.on()\n\nbd.wait_for_release()\nled.off()\n"
  },
  {
    "path": "docs/examples/led2.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import LED\nfrom signal import pause\n\nbd = BlueDot()\nled = LED(27)\n\nbd.when_pressed = led.on\nbd.when_released = led.off\n\npause()\n"
  },
  {
    "path": "docs/examples/led3.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import LED\nfrom signal import pause\n\nbd = BlueDot()\nled = LED(27)\n\nled.source = bd.values\n\npause()\n"
  },
  {
    "path": "docs/examples/looks_border.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.border = True"
  },
  {
    "path": "docs/examples/looks_color.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.color = \"red\""
  },
  {
    "path": "docs/examples/looks_square.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.square = True"
  },
  {
    "path": "docs/examples/looks_visible.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.visible = False"
  },
  {
    "path": "docs/examples/many_buttons.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=2, rows=5)\r\nbd.when_pressed = pressed\r\n\r\npause()"
  },
  {
    "path": "docs/examples/many_buttons_random_colors.py",
    "content": "from bluedot import BlueDot, COLORS\r\nfrom random import choice\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=2, rows=5)\r\nbd.when_pressed = pressed\r\n\r\nfor button in bd.buttons:\r\n    button.color = choice(list(COLORS.values()))\r\n\r\npause()"
  },
  {
    "path": "docs/examples/mock_app.py",
    "content": "from bluedot import MockBlueDot\nfrom signal import pause\n\ndef say_hello():\n    print(\"Hello World\")\n\nbd = MockBlueDot()\nbd.when_pressed = say_hello\n\nbd.launch_mock_app()\npause()\n"
  },
  {
    "path": "docs/examples/mock_script.py",
    "content": "from bluedot import MockBlueDot\n\ndef say_hello():\n    print(\"Hello World\")\n\nbd = MockBlueDot()\nbd.when_pressed = say_hello\n\nbd.mock_client_connected()\nbd.mock_blue_dot_pressed(0,0)\n"
  },
  {
    "path": "docs/examples/multiple_dots.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef bd1_pressed():\n    print(\"BlueDot 1 pressed\")\n\ndef bd2_pressed():\n    print(\"BlueDot 2 pressed\")\n\nbd1 = BlueDot(port = 1)\nbd2 = BlueDot(port = 2)\n\nbd1.when_pressed = bd1_pressed\nbd2.when_pressed = bd2_pressed\n\npause()\n"
  },
  {
    "path": "docs/examples/robot1.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import Robot\nfrom signal import pause\n\nbd = BlueDot()\nrobot = Robot(left=(17, 18), right=(22, 23))\n\ndef move(pos):\n    if pos.top:\n        robot.forward()\n    elif pos.bottom:\n        robot.backward()\n    elif pos.left:\n        robot.left()\n    elif pos.right:\n        robot.right()\n\ndef stop():\n    robot.stop()\n\nbd.when_pressed = move\nbd.when_moved = move\nbd.when_released = stop\n\npause()\n"
  },
  {
    "path": "docs/examples/robot2.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import Robot\nfrom signal import pause\n\nbd = BlueDot()\nrobot = Robot(left=(lfpin, lbpin), right=(rfpin, rbpin))\n\ndef move(pos):\n    if pos.top:\n        robot.forward(pos.distance)\n    elif pos.bottom:\n        robot.backward(pos.distance)\n    elif pos.left:\n        robot.left(pos.distance)\n    elif pos.right:\n        robot.right(pos.distance)\n\ndef stop():\n    robot.stop()\n\nbd.when_pressed = move\nbd.when_moved = move\nbd.when_released = stop\n\npause()\n"
  },
  {
    "path": "docs/examples/robot3.py",
    "content": "from gpiozero import Robot\nfrom bluedot import BlueDot\nfrom signal import pause\n\ndef pos_to_values(x, y):\n    left = y if x > 0 else y + x\n    right = y if x < 0 else y - x\n    return (clamped(left), clamped(right))\n\ndef clamped(v):\n    return max(-1, min(1, v))\n\ndef drive():\n    while True:\n        if bd.is_pressed:\n            x, y = bd.position.x, bd.position.y\n            yield pos_to_values(x, y)\n        else:\n            yield (0, 0)\n\nrobot = Robot(left=(lfpin, lbpin), right=(rfpin, rbpin))\nbd = BlueDot()\n\nrobot.source = drive()\n\npause()\n"
  },
  {
    "path": "docs/examples/rotation.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ncount = 0\n\ndef rotated(rotation):\n    global count\n    count += rotation.value\n\n    print(\"{} {} {}\".format(count,\n                            rotation.clockwise,\n                            rotation.anti_clockwise))\n\nbd = BlueDot()\nbd.when_rotated = rotated\n\npause()\n"
  },
  {
    "path": "docs/examples/shout_hello.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef shout_hello():\n    print(\"HELLO\")\n\nbd = BlueDot()\nbd.when_double_pressed = shout_hello\n\npause()\n"
  },
  {
    "path": "docs/examples/slider_centre.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef show_percentage(pos):\n    percentage = round(pos.distance * 100, 2)\n    print(\"{}%\".format(percentage))\n\nbd = BlueDot()\nbd.when_moved = show_percentage\n\npause()\n"
  },
  {
    "path": "docs/examples/slider_dimmer.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import PWMLED\nfrom signal import pause\n\ndef set_brightness(pos):\n    brightness = (pos.y + 1) / 2\n    led.value = brightness\n\nled = PWMLED(27)\nbd = BlueDot()\nbd.when_moved = set_brightness\n\npause()\n"
  },
  {
    "path": "docs/examples/slider_left_right.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef show_percentage(pos):\n    horizontal = ((pos.x + 1) / 2)\n    percentage = round(horizontal * 100, 2)\n    print(\"{}%\".format(percentage))\n\nbd = BlueDot()\nbd.when_moved = show_percentage\n\npause()\n"
  },
  {
    "path": "docs/examples/swipe1.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\nbd.wait_for_swipe()\nprint(\"Blue Dot swiped\")\n"
  },
  {
    "path": "docs/examples/swipe2.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef swiped():\n    print(\"Blue Dot swiped\")\n\nbd = BlueDot()\nbd.when_swiped = swiped\n\npause()\n"
  },
  {
    "path": "docs/examples/swipe_direction.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef swiped(swipe):\n    if swipe.up:\n        print(\"up\")\n    elif swipe.down:\n        print(\"down\")\n    elif swipe.left:\n        print(\"left\")\n    elif swipe.right:\n        print(\"right\")\n\nbd = BlueDot()\nbd.when_swiped = swiped\n\npause()\n"
  },
  {
    "path": "docs/examples/swipe_speed_angle.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef swiped(swipe):\n    print(\"Swiped\")\n    print(\"speed={}\".format(swipe.speed))\n    print(\"angle={}\".format(swipe.angle))\n    print(\"distance={}\".format(swipe.distance))\n\nbd = BlueDot()\nbd.when_swiped = swiped\n\npause()\n"
  },
  {
    "path": "docs/examples/two_buttons.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=2)\r\nbd.when_pressed = pressed\r\n\r\npause()"
  },
  {
    "path": "docs/examples/two_buttons_gap.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=3, rows=1)\r\nbd[1,0].visible = False\r\nbd.when_pressed = pressed\r\n\r\npause()"
  },
  {
    "path": "docs/examples/two_buttons_two_events.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed_1(pos):\r\n    print(\"button 1 pressed\")\r\n\r\ndef pressed_2(pos):\r\n    print(\"button 2 pressed\")\r\n\r\nbd = BlueDot(cols=2, rows=1)\r\n\r\nbd[0,0].when_pressed = pressed_1\r\nbd[1,0].when_pressed = pressed_2\r\n\r\npause()"
  },
  {
    "path": "docs/gettingstarted.rst",
    "content": "Getting Started\n===============\n\nIn order to use Blue Dot you will need:\n\n* A Raspberry Pi\n\n  - with built-in Bluetooth (such as the Raspberry Pi 3, 4 or Zero W)\n  - or a USB Bluetooth dongle\n\n* An Android phone or 2nd Raspberry Pi for the remote\n* An Internet connection (for the install)\n\nInstallation\n------------\n\nThese instructions assume your Raspberry Pi is running the latest version of\n`Raspbian`_.\n\nAndroid App\n~~~~~~~~~~~\n\nIf you're using an Android phone, the `Blue Dot app`_ can be installed from the\nGoogle Play Store.\n\nPython Library\n~~~~~~~~~~~~~~\n\nOpen a terminal (click :menuselection:`Menu --> Accessories --> Terminal`),\nthen enter::\n\n    sudo pip3 install bluedot\n\nTo upgrade to the latest version::\n\n    sudo pip3 install bluedot --upgrade\n\nPairing\n-------\n\nIn order to use Blue Dot you will need to pair the Raspberry Pi to the remote\n:doc:`Android phone <pairpiandroid>` or :doc:`2nd Raspberry Pi <pairpipi>`.\n\nCode\n----\n\n1. Start up Python 3 (e.g. :menuselection:`Menu --> Programming --> Thonny Python\n   IDE`)\n\n2. Create a new program\n\n3. Enter the following code::\n\n    from bluedot import BlueDot\n    bd = BlueDot()\n    bd.wait_for_press()\n    print(\"You pressed the blue dot!\")\n\n4. Save your program as :file:`mydot.py`\n\n5. Run the program::\n\n    Server started ##:##:##:##:##:##\n    Waiting for connection\n\n.. warning::\n\n    Do not save your program as :file:`bluedot.py` as Python will try and\n    import your program rather than the bluedot module and you will get the\n    error ``ImportError: cannot import name BlueDot``.\n\nConnecting\n----------\n\nStart-up the `Blue Dot app`_ on your Android phone or run the\n:doc:`bluedotpythonapp` on your 2nd Raspberry Pi:\n\n1. Select your Raspberry Pi from the list\n\n.. note::\n\n    Your python program will need to be running and ``Waiting for connection`` \n    before the BlueDot app will be able to connect to your Raspberry Pi.\n\n2. Press the Blue Dot\n\nWhere next\n----------\n\nCheck out the :doc:`recipes` and the :doc:`dotapi` documentation for more ideas\non using Blue Dot.\n\n.. _Blue Dot app: http://play.google.com/store/apps/details?id=com.stuffaboutcode.bluedot\n.. _Raspbian: https://www.raspberrypi.org/downloads/raspbian/\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. include:: ../README.rst\n\nTable of Contents\n=================\n\n.. toctree::\n   :maxdepth: 2\n\n   gettingstarted\n   pairpiandroid\n   pairpipi\n   recipes\n   bluedotandroidapp\n   bluedotpythonapp\n   dotapi\n   btcommapi\n   mockapi\n   protocol\n   build\n   changelog\n"
  },
  {
    "path": "docs/mockapi.rst",
    "content": "Mock API\n========\nBlue Dot also contains a useful :mod:`mock` API for simulating Blue Dot and\nbluetooth comms. This is useful for testing and allows for prototyping\nwithout having to use a Blue Dot client.\n\n.. module:: bluedot.mock\n\nMockBlueDot\n-----------\n\n.. autoclass:: MockBlueDot\n\nMockBluetoothServer\n-------------------\n\n.. autoclass:: MockBluetoothServer\n\nMockBluetoothClient\n-------------------\n\n.. autoclass:: MockBluetoothClient\n"
  },
  {
    "path": "docs/pairpiandroid.rst",
    "content": "Pair a Raspberry Pi and Android phone\n=====================================\n\nUsing the Desktop\n-----------------\n\nOn your Android phone:\n\n1. Open Settings\n\n2. Select Bluetooth and make your phone \"discoverable\"\n\nOn your Raspberry Pi:\n\n1. Click :menuselection:`Bluetooth --> Turn On Bluetooth` (if it's off)\n\n2. Click :menuselection:`Bluetooth --> Make Discoverable`\n\n3. Click :menuselection:`Bluetooth --> Add Device`\n\n4. Your phone will appear in the list, select it and click :guilabel:`Pair`\n\nOn your Android phone and Raspberry Pi.\n\n1. Confirm the pairing code matches\n\n2. Click OK\n\n.. note::\n\n    You may receive errors relating to services not being able available or being unable to connect: these can be ignored, your phone and Raspberry Pi are now paired.\n\nUsing the Command Line\n----------------------\n\nOn your Android phone:\n\n1. Open Settings\n\n2. Select Bluetooth and make your phone \"discoverable\"\n\nOn your Raspberry Pi:\n\n1. Type :command:`bluetoothctl` and press Enter to open Bluetooth control\n\n2. At the ``[bluetooth]#`` prompt enter the following commands::\n\n       discoverable on\n       pairable on\n       agent on\n       default-agent\n       scan on\n\n3. Wait for a message to appear showing the Android phone has been found::\n\n       [NEW] Device 12:23:34:45:56:67 devicename\n\n4. Type pair with the mac address of your Android phone::\n\n       pair 12:23:34:45:56:67\n\nOn your Android phone and Raspberry Pi.\n\n1. Confirm the passcode.\n\n2. Type :command:`quit` and press Enter to return to the command line"
  },
  {
    "path": "docs/pairpipi.rst",
    "content": "Pair 2 Raspberry Pis\n====================\n\nThe instructions below describe pairing a couple of Raspberry Pis which either\nhave built-in Bluetooth (the Pi 3B or the Pi Zero W) or a USB Bluetooth dongle.\n\nUsing the Desktop\n-----------------\n\nOn the first Raspberry Pi:\n\n1. Click :menuselection:`Bluetooth --> Turn On Bluetooth` (if it's off)\n\n2. Click :menuselection:`Bluetooth --> Make Discoverable`\n\nOn the second Raspberry Pi:\n\n1. Click :menuselection:`Bluetooth --> Turn On Bluetooth` (if it's off)\n\n2. Click :menuselection:`Bluetooth --> Make Discoverable`\n\n3. Click :menuselection:`Bluetooth --> Add Device`\n\n4. The first Pi will appear in the list: select it and click the :guilabel:`Pair` button\n\nOn the first Raspberry Pi again:\n\n1. Accept the pairing request\n\n.. note::\n\n    You may receive errors relating to services not being able available or being unable to connect: these can be ignored.\n\nUsing the Command Line\n----------------------\n\nOn the first Raspberry Pi:\n\n1. Enter :command:`bluetoothctl` to open Bluetooth control\n\n2. At the ``[bluetooth]#`` prompt enter the following commands::\n\n       discoverable on\n       pairable on\n       agent on\n       default-agent\n\nOn the second Raspberry Pi:\n\n1. Enter :command:`bluetoothctl` to open Bluetooth control\n\n2. At the ``[bluetooth]#`` prompt enter the following commands::\n\n       discoverable on\n       pairable on\n       agent on\n       default-agent\n       scan on\n\n3. Wait for a message to appear showing the first Pi has been found::\n\n       [NEW] Device 12:23:34:45:56:67 devicename\n\n4. Type pair with the mac address of the first Pi::\n\n       pair 12:23:34:45:56:67\n\nOn both Raspberry Pi's:\n\n1. Confirm the passcode.\n\n2. Type :command:`quit` and press Enter to return to the command line\n"
  },
  {
    "path": "docs/protocol.rst",
    "content": "Protocol\n========\n\nBlue Dot uses a client/server model. The :class:`BlueDot` class starts a\nBluetooth server, the Blue Dot application connects as a client.\n\nThe detail below can be used to create new applications (clients); if you do\nplease send a pull request :)\n\nBluetooth\n---------\n\nCommunication over Bluetooth is made using a RFCOMM serial port profile using \nUUID \"00001101-0000-1000-8000-00805f9b34fb\".\n\nSpecification\n-------------\n\nThe transmission of data from client to server or server to client is a \nsimple stream no acknowledgements or data is sent in response to commands.\n\nAll messages between conform to the same format::\n\n    [operation],[params],[*]\\n\n\nMessages are sent as utf-8 encoded strings.\n\n*\\\\n* represents the new-line character.\n\nThe following operations are used to communicate between client and server.\n\n+-------------------+-------------------------------------------------------------+-----------------+\n| Operations        | Message format                                              | Direction       |\n+===================+=============================================================+=================+\n| Button released   | ``0,[col],[row],[x],[y]\\n``                                 | Client > Server |\n+-------------------+-------------------------------------------------------------+-----------------+\n| Button pressed    | ``1,[col],[row],[x],[y]\\n``                                 | Client > Server |\n+-------------------+-------------------------------------------------------------+-----------------+\n| Button moved      | ``2,[col],[row],[x],[y]\\n``                                 | Client > Server |\n+-------------------+-------------------------------------------------------------+-----------------+\n| Protocol check    | ``3,[protocol version],[client name]\\n``                    | Client > Server |\n+-------------------+-------------------------------------------------------------+-----------------+\n| Set config        | ``4,[color],[square],[border],[visible],[cols],[rows]\\n``   | Server > Client |\n+-------------------+-------------------------------------------------------------+-----------------+\n| Set button config | ``5,[color],[square],[border],[visible],[col],[row]\\n``     | Server > Client |\n+-------------------+-------------------------------------------------------------+-----------------+\n\nMessages are constructed using the following parameters.\n\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| Parameter         | Description                                                                                                 |\n+===================+=============================================================================================================+\n| cols              | The number of columns in the matrix of buttons                                                              |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| rows              | The number of rows in the matrix of buttons                                                                 |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| col               | The column position of the button (0 is top)                                                                |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| row               | The row position of the button (0 is left)                                                                  |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| x                 | Horizontal position between -1 and +1, with 0 being the centre and +1 being the right radius of the button. |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| y                 | Vertical position between -1 and +1, with 0 being the centre and +1 being the top radius of the button.     |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| protocol version  | The version of protocol the client supports.                                                                |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| client name       | The name of the client e.g. \"Android Blue Dot App\"                                                          |\n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| color             | A hex value in the format ``#rrggbbaa`` representing red, green, blue, alpha values.                        | \n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| square            | 0 or 1, 1 if the dot should be a square.                                                                    | \n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| border            | 0 or 1, 1 if the dot should have a border.                                                                  | \n+-------------------+-------------------------------------------------------------------------------------------------------------+\n| visible           | 0 or 1, 1 if the dot should be visible.                                                                     | \n+-------------------+-------------------------------------------------------------------------------------------------------------+\n\nMessages are sent when:\n\n1. A client connects\n2. When the setup (or appearance) of a button changes\n3. A button is released, pressed or moved\n\n.. image:: images/protocol_state.png\n   :alt: Diagram showing the protocol states\n\nExample\n-------\n\nWhen the Android client connects using protocol version 2::\n\n    3,2,Android Blue Dot app\\n\n\nThe setup of the Blue Dot is sent to the client::\n\n    4,#0000ffff,0,0,1,1,2\\n\n\nIf any buttons are different to the default, the configuration is sent::\n\n    5,#00ff0000,0,0,1,0,1\\n\n\nIf the \"first\" button at position [0,0] is pressed at the top, the following message will be sent::\n\n    1,0,0,0.0,1.0\\n\n\nWhile the button is pressed (held down), the user moves their finger to the\nfar right causing the following message to be sent::\n\n    2,0,0,1.0,0.0\\n\n\nThe button is then released, resulting in the following message::\n\n    0,0,0,1.0,0.0\\n\n\nThe color of the button is changed to \"red\" the server sends to the client::\n\n    5,#ff0000ff,0,0,1,0,0\\n\n\nVersions\n--------\n\n* 0 - initial version\n* 1 - introduction of operation 3, 4\n* 2 - Blue Dot version 2, introduction of col, row for multiple buttons and operation 5"
  },
  {
    "path": "docs/recipes.rst",
    "content": "Recipes\n=======\n\nThe recipes provide examples of how you can use Blue Dot. Don't be restricted\nby these ideas and be sure to have a look at the :doc:`dotapi` as there is more\nto be discovered.\n\nButton\n------\n\nThe simplest way to use the Blue Dot is as a wireless button.\n\nHello World\n~~~~~~~~~~~\n\n.. currentmodule:: bluedot\n\nLet's say \"Hello World\" by creating the :class:`BlueDot` object then waiting\nfor the Blue Dot app to connect and the button be pressed:\n\n.. literalinclude:: examples/hello_world.py\n\nAlternatively you can also use :attr:`~BlueDot.when_pressed` to call a\nfunction:\n\n.. literalinclude:: examples/hello_event.py\n\n:attr:`~BlueDot.wait_for_release` and :attr:`~BlueDot.when_released` also allow\nyou to interact when the button is released:\n\n.. literalinclude:: examples/goodbye_world.py\n\nDouble presses can also be used with :attr:`~BlueDot.wait_for_double_press` and\n:attr:`~BlueDot.when_double_pressed`:\n\n.. literalinclude:: examples/shout_hello.py\n\nFlash an LED\n~~~~~~~~~~~~\n\nUsing Blue Dot in combination with :mod:`gpiozero` you can interact with\nelectronic components, such as LEDs, connected to your Raspberry Pi.\n\nWhen a button is pressed, the LED connected to GPIO 27 will turn on; when\nreleased it will turn off:\n\n.. literalinclude:: examples/led1.py\n\nYou could also use :attr:`~BlueDot.when_pressed` and\n:attr:`~BlueDot.when_released`:\n\n.. literalinclude:: examples/led2.py\n\nAlternatively use :attr:`~gpiozero.SourceMixin.source` and\n:attr:`~BlueDot.values`:\n\n.. literalinclude:: examples/led3.py\n\nRemote Camera\n~~~~~~~~~~~~~\n\nUsing a Raspberry Pi camera module, :class:`picamera.PiCamera` and\n:class:`BlueDot`, you can really easily create a remote camera:\n\n.. literalinclude:: examples/camera.py\n\nJoystick\n--------\n\nThe Blue Dot can also be used as a joystick when the middle, top, bottom, left\nor right areas of the dot are touched.\n\nD-pad\n~~~~~\n\nUsing the position the Blue Dot was pressed you can work out whether it was\npressed to go up, down, left, right like the `D-pad`_ on a joystick:\n\n.. literalinclude:: examples/dpad.py\n\nAt the moment the `D-pad`_ only registers when it is pressed. To get it work\nwhen the position is moved you should add the following line above\n:code:`pause()`::\n\n    bd.when_moved = dpad\n\nRobot\n~~~~~\n\nThese recipes assume your robot is constructed with a pair of H-bridges. The\nforward and backward pins for the H-bridge of the left wheel are 17 and 18\nrespectively, and the forward and backward pins for H-bridge of the right wheel\nare 22 and 23 respectively.\n\nUsing the Blue Dot and :class:`gpiozero.Robot`, you can create a `bluetooth\ncontrolled robot`_ which moves when the dot is pressed and stops when it is\nreleased:\n\n.. literalinclude:: examples/robot1.py\n\nVariable Speed Robot\n~~~~~~~~~~~~~~~~~~~~\n\nYou can change the robot to use variable speeds, so the further towards the\nedge you press the Blue Dot, the faster the robot will go.\n\nThe :attr:`~BlueDotPosition.distance` attribute returns how far from the centre\nthe Blue Dot was pressed, which can be passed to the robot's functions to\nchange its speed:\n\n.. literalinclude:: examples/robot2.py\n\nAlternatively you can use a generator and yield (x, y) values to the\n:attr:`gpiozero.Robot.source` property (courtesy of `Ben Nuttall`_):\n\n.. literalinclude:: examples/robot3.py\n\nAppearance\n----------\n\nThe button doesn't have to be blue or a dot, you can change how it looks, or make it completely invisible.\n\n.. image:: images/bluedot_color_changing_smaller.gif\n   :alt: Animation of blue dot app cycling through colors and changing to a square\n\nColo(u)r\n~~~~~~~~\n\nTo change the color of the button use the :attr:`~BlueDot.color`: property:\n\n.. literalinclude:: examples/looks_color.py\n\nA dictionary of available colors can be obtained from ``bluedot.COLORS``.\n\nThe color can also be set using a hex value of `#rrggbb` or `#rrggbbaa` value::\n\n    bd.color = \"#00ff00\"\n\nOr a tuple of 3 or 4 integers between `0` and `255` either (red, gree, blue) or (red, green, blue, alpha)::\n\n    bd.color = (0, 255, 0)\n\nSquare\n~~~~~~\n\nThe button can also be made square using the :attr:`~BlueDot.square`: property:\n\n.. literalinclude:: examples/looks_square.py\n\nBorder\n~~~~~~\n\nA border can also been added to the button by setting the :attr:`~BlueDot.border`: property to `True`:\n\n.. literalinclude:: examples/looks_border.py\n\n(In)visible\n~~~~~~~~~~~\n\nThe button can be hidden and shown using the :attr:`~BlueDot.visible`: property:\n\n.. literalinclude:: examples/looks_visible.py\n\nLayout\n-------\n\nYou can have as many buttons as you want.\n\nThe Buttons need to be in a grid of columns and rows.\n\n.. image:: images/layout_many_buttons_small.png\n   :alt: Android blue dot app showing 10 buttons in a 2x5 grid\n\nBy hiding specific buttons and being creative with the button's appearance you can create very sophisticated layouts for your controllers using Blue Dot.\n\n.. image:: images/layout_joypad_small.png\n   :alt: Android blue dot app showing buttons layed out like a classic joypad\n\nThe Blue Dot android app supports multi touch allowing you to use multiple buttons simultaneously\n\n.. note::\n\n    Currently only the Android client app supports multi buttons.\n\nTwo Buttons\n~~~~~~~~~~~\n\nCreate 2 buttons side by side, by setting the number of `cols` to `2`:\n\n.. image:: images/layout_2_buttons_small.png\n   :alt: Android blue dot app showing 2 buttons side by side\n\n.. literalinclude:: examples/two_buttons.py\n\nThe buttons could be made verticle by setting the `rows` attribute::\n\n    bd = BlueDot(rows=2)\n\nEach button can be set to call its own function by using the grid position:\n\n.. literalinclude:: examples/two_buttons_two_events.py\n\nTo create a gap in between the buttons you could create a row of 3 buttons and hide the middle button:\n\n.. image:: images/layout_2_buttons_gap_small.png\n   :alt: Android blue dot app showing 2 buttons side by side with a gap in the middle\n\n.. literalinclude:: examples/two_buttons_gap.py\n\nMany Buttons\n~~~~~~~~~~~~\n\nCreate a grid of buttons by setting the `cols` and `rows` e.g. 10 buttons in a 2x5 grid:\n\n.. image:: images/layout_many_buttons_small.png\n   :alt: Android blue dot app showing 10 buttons in a 2x5 grid\n\n.. literalinclude:: examples/many_buttons.py\n\nYou could assign all the buttons random colors:\n\n.. literalinclude:: examples/many_buttons_random_colors.py\n\nD-pad\n~~~~~\n\nCreate a traditional d-pad layout by using a 3x3 grid and hide the buttons at the corners and in the middle:\n\n.. image:: images/layout_dpad_small.png\n   :alt: Android blue dot app showing 4 buttons arranged in a cross\n\n.. literalinclude:: examples/dpad_layout.py\n\nAdd 2 buttons on the right to create a joypad:\n\n.. image:: images/layout_joypad_small.png\n   :alt: Android blue dot app showing buttons layed out like a classic joypad\n\n.. literalinclude:: examples/dpad_layout.py\n\nSlider\n------\n\nBy holding down a button and moving the position you can use it as an\nanalogue slider.\n\nCentre Out\n~~~~~~~~~~\n\nUsing the :attr:`BlueDotPosition.distance` property which is returned when the\nposition is moved you can create a slider which goes from the centre out in any\ndirection:\n\n.. literalinclude:: examples/slider_centre.py\n\nLeft to Right\n~~~~~~~~~~~~~\n\nThe :attr:`BlueDotPosition.x` property returns a value from -1 (far left) to 1\n(far right). Using this value you can create a slider which goes horizontally\nthrough the middle:\n\n.. literalinclude:: examples/slider_left_right.py\n\nTo make a vertical slider you could change the code above to use\n:attr:`BlueDotPosition.y` instead.\n\nDimmer Switch\n~~~~~~~~~~~~~\n\nUsing the :class:`gpiozero.PWMLED` class and :class:`BlueDot` as a vertical\nslider you can create a wireless dimmer switch:\n\n.. literalinclude:: examples/slider_dimmer.py\n\nSwiping\n-------\n\nYou can interact with the Blue Dot by swiping across it, like you would to move\nbetween pages in a mobile app.\n\nSingle\n~~~~~~\n\nDetecting a single swipe is easy using :attr:`~BlueDot.wait_for_swipe`:\n\n.. literalinclude:: examples/swipe1.py\n\nAlternatively you can also use :attr:`~BlueDot.when_swiped` to call a\nfunction:\n\n.. literalinclude:: examples/swipe2.py\n\nDirection\n~~~~~~~~~\n\nYou can tell what direction the Blue Dot is swiped by using the\n:class:`BlueDotSwipe` object passed to the function assigned to\n:attr:`~BlueDot.when_swiped`:\n\n.. literalinclude:: examples/swipe_direction.py\n\nSpeed, Angle, and Distance\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n:class:`BlueDotSwipe` returns more than just the direction. It also includes\nthe speed of the swipe (in Blue Dot radius per second), the angle, and the\ndistance between the start and end positions of the swipe:\n\n.. literalinclude:: examples/swipe_speed_angle.py\n\nRotating\n--------\n\nYou can use Blue Dot like a rotary encoder or \"iPod classic click wheel\" -\nrotating around the outer edge of the Blue Dot will cause it to \"tick\".  The\nBlue Dot is split into a number of virtual segments (the default is 8), when\nthe position moves from one segment to another, it ticks.\n\nCounter\n~~~~~~~\n\nUsing the :attr:`~BlueDot.when_rotated` callback you can create a counter which\nincrements / decrements when the Blue Dot is rotated either clockwise or\nanti-clockwise. A :class:`BlueDotRotation` object is passed to the callback.\nIts :attr:`~BlueDotRotation.value` property will be -1 if rotated\nanti-clockwise and 1 if rotated clockwise:\n\n.. literalinclude:: examples/rotation.py\n\nThe rotation speed can be modified using the :attr:`BlueDot.rotation_segments`\nproperty which changes the number of segments the Blue Dot is split into::\n\n    bd.rotation_segments = 16\n\nMultiple Blue Dot Apps\n----------------------\n\nYou can connect multiple Blue Dot clients (apps) to a single server (python \nprogram) by using different Bluetooth ports for each app.\n\nCreate multiple `BlueDot` servers using specific ports:\n\n.. literalinclude:: examples/multiple_dots.py\n\nChange the BlueDot app to use the specific port by:\n\n1. Opening settings from the menu\n2. Turning *Use default port* off\n3. Selecting the specific *Bluetooth port*\n\n.. image:: images/bluedotandroid_settings.png\n   :alt: Android blue dot app showing the settings option on the menu\n\n.. image:: images/bluedotandroid_settings_defaultport.png\n   :alt: Android blue dot app showing the settings page and use default port turned on\n\n.. image:: images/bluedotandroid_settings_port.png\n   :alt: Android blue dot app showing the settings page, use default port turned off and bluetooth port 1 selected\n\nBluetooth\n---------\n\nYou can interact with the Bluetooth adapter using :class:`BlueDot`.\n\nPairing\n~~~~~~~\n\nYou can put your Raspberry Pi into pairing mode which will allow pairing from\nother devices for 60 seconds:\n\n.. literalinclude:: examples/bt_pairing.py\n\nOr connect up a physical button up to start the pairing (the button is assumed\nto be wired to GPIO 27):\n\n.. literalinclude:: examples/bt_pair_button.py\n\nPaired Devices\n~~~~~~~~~~~~~~\n\nYou can iterate over the devices that your Raspberry Pi is paired too:\n\n.. literalinclude:: examples/bt_enumerate.py\n\nTesting\n-------\n\nBlue Dot includes a :class:`MockBlueDot` class to allow you to test and debug\nyour program without having to use Bluetooth or a Blue Dot client.\n\n:class:`MockBlueDot` inherits from :class:`BlueDot` and is used in the same\nway, but you have the option of launching a mock app which you can click with a\nmouse or writing scripts to simulate the Blue Dot being used.\n\n.. image:: images/mockbluedot.png\n   :alt: Screenshot of the mock Blue Dot app\n\nMock App\n~~~~~~~~\n\nLaunch the mock Blue Dot app to test by clicking the on-screen dot with the\nmouse:\n\n.. literalinclude:: examples/mock_app.py\n\nScripted Tests\n~~~~~~~~~~~~~~\n\nTests can also be scripted using :class:`MockBlueDot`:\n\n.. literalinclude:: examples/mock_script.py\n\n\n.. _Ben Nuttall: https://github.com/bennuttall\n.. _bluetooth controlled robot: https://youtu.be/eW9oEPySF58\n.. _D-pad: https://en.wikipedia.org/wiki/D-pad\n"
  },
  {
    "path": "examples/adapter_details.py",
    "content": "from bluedot import BlueDot\nbd = BlueDot()\n\nprint(\"Address {}\".format(bd.adapter.address))\nprint(\"powered {}\".format(bd.adapter.powered))\nprint(\"discoverable {}\".format(bd.adapter.discoverable))\nprint(\"pairable {}\".format(bd.adapter.pairable))\n\nprint(\"paired devices:\")\npaired_devices = bd.paired_devices\nfor d in paired_devices:\n    print(\" mac {} name {}\".format(d[0], d[1]))\n\n"
  },
  {
    "path": "examples/click_wheel.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ncount = 0\n\ndef rotated(rotation):\n    global count\n    count += rotation.value\n\n    print(\"{} {} {}\".format(count, rotation.clockwise, rotation.anti_clockwise))\n\nbd = BlueDot()\nbd.when_rotated = rotated\n\npause()\n"
  },
  {
    "path": "examples/client_connects.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef client_connected():\r\n    print(\"callback - a blue dot client connected\")\r\n\r\ndef client_disconnected():\r\n    print(\"callback - the blue dot client disconnected\")\r\n\r\nbd = BlueDot()\r\nbd.when_client_connects = client_connected\r\nbd.when_client_disconnects = client_disconnected\r\n\r\npause()"
  },
  {
    "path": "examples/client_debug.py",
    "content": "from bluedot.btcomm import BluetoothClient\nfrom datetime import datetime\nfrom time import sleep\nfrom signal import pause\n\ndef data_received(data):\n    print(\"recv - {}\".format(data))\n\nprint(\"Connecting\")\nc = BluetoothClient(\"devpi\", data_received)\n\nprint(\"Sending\")\ntry:\n    while True:\n        c.send(\"hi {} \\n\".format(str(datetime.now())))\n        sleep(1)\nfinally:\n    c.disconnect()\n\n"
  },
  {
    "path": "examples/color_changer.py",
    "content": "from bluedot import BlueDot\n# color zero can be installed using sudo pip3 install colorzero\nfrom colorzero import Color\nfrom signal import pause\n\ndef on(pos):\n    hue = (pos.angle + 180) / 360\n    c = Color(h=hue, s=1, v=pos.distance)\n    bd.color = c.rgb_bytes\n\ndef off():\n    bd.color = \"blue\"\n\nbd = BlueDot()\nbd.when_pressed = on\nbd.when_moved = on\nbd.when_released = off\n\npause()"
  },
  {
    "path": "examples/connect_multiple_apps.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef bd1_pressed():\r\n    print(\"BlueDot 1 pressed\")\r\n\r\ndef bd2_pressed():\r\n    print(\"BlueDot 2 pressed\")\r\n\r\nbd1 = BlueDot(port = 1)\r\nbd2 = BlueDot(port = 2)\r\n\r\nbd1.when_pressed = bd1_pressed\r\nbd2.when_pressed = bd2_pressed\r\n\r\npause()\r\n"
  },
  {
    "path": "examples/dot_changer.py",
    "content": "from bluedot import BlueDot\r\nfrom bluedot.colors import RED, GREEN\r\n\r\nfrom signal import pause\r\n\r\ndef change_dot(pos):\r\n    if pos.top:\r\n        if bd.color == \"red\":\r\n            bd.color = GREEN\r\n        else:\r\n            bd.color = \"#ff0000\"\r\n    elif pos.bottom:\r\n        if bd.border:\r\n            bd.border = False\r\n        else:\r\n            bd.border = True\r\n    elif pos.left:\r\n        if bd.visible:\r\n            bd.visible = False\r\n        else:\r\n            bd.visible = True\r\n    elif pos.right:\r\n        if bd.square:\r\n            bd.square = False\r\n        else:\r\n            bd.square = True\r\n\r\nbd = BlueDot()\r\nbd.color=\"pink\"\r\nbd.border = False\r\nbd.square = True\r\nbd.when_pressed = change_dot\r\n\r\npause()"
  },
  {
    "path": "examples/dot_debug.py",
    "content": "from bluedot import BlueDot\nfrom time import sleep, time\n\ndot = BlueDot(auto_start_server = False)\ndot.resize(1,2)\ndot.allow_pairing()\n\ndef pressed(pos):\n    print(\"Pressed: x={} y={} angle={} distance={} middle={} top={} bottom={} left={} right={} time={}\".format(pos.x, pos.y, pos.angle, pos.distance, pos.middle, pos.top, pos.bottom, pos.left, pos.right, time()))\n\ndef pressed_two(pos):\n    print(\"Second dot:\" + str(pos))\n\ndef released():\n    print(\"Released: x={} y={}\".format(dot.position.x, dot.position.y))\n\ndef moved(pos):\n    print(\"Moved: x={} y={}\".format(pos.x, pos.y))\n\ndef swiped(swipe):\n    print(\"Swiped: up={} down={} left={} right={} speed={}\".format(swipe.up, swipe.down, swipe.left, swipe.right, swipe.speed))\n\ndef double_presed(pos):\n    print(\"Double pressed: x={} y={}\".format(pos.x, pos.y))\n\ndef client_connected():\n    print(\"connected callback\")\n\ndef client_disconnected():\n    print(\"disconnected callback\")\n\ndef rotated(rotation):\n    print(\"rotated: direction={}\".format(rotation.value))\n\ndot.when_client_connects = client_connected\ndot.when_client_disconnects = client_disconnected\ndot.when_pressed = pressed\ndot.when_released = released\ndot.when_moved = moved\ndot.when_swiped = swiped\ndot.when_double_pressed = double_presed\ndot.when_rotated = rotated\ndot[0,1].when_pressed = pressed_two\n\ndot.start()\n\ndot.wait_for_press()\nprint(\"wait for press\")\ndot.wait_for_move()\nprint(\"wait for move\")\ndot.wait_for_release()\nprint(\"wait for release\")\ndot.wait_for_double_press()\nprint(\"wait for double press\")\ndot.wait_for_swipe()\nprint(\"wait for swipe\")\n\ntry:\n    while True:\n        sleep(0.1)\nfinally:\n    dot.stop()\n"
  },
  {
    "path": "examples/dot_single_button_debug.py",
    "content": "from bluedot import BlueDot\r\nfrom time import sleep, time\r\n\r\ndot = BlueDot(auto_start_server = False)\r\ndot.allow_pairing()\r\n\r\ndef pressed(pos):\r\n    print(\"Pressed: x={} y={} angle={} distance={} middle={} top={} bottom={} left={} right={} time={}\".format(pos.x, pos.y, pos.angle, pos.distance, pos.middle, pos.top, pos.bottom, pos.left, pos.right, time()))\r\n\r\ndef released():\r\n    print(\"Released: x={} y={}\".format(dot.position.x, dot.position.y))\r\n\r\ndef moved(pos):\r\n    print(\"Moved: x={} y={}\".format(pos.x, pos.y))\r\n\r\ndef swiped(swipe):\r\n    print(\"Swiped: up={} down={} left={} right={} speed={}\".format(swipe.up, swipe.down, swipe.left, swipe.right, swipe.speed))\r\n\r\ndef double_presed(pos):\r\n    print(\"Double pressed: x={} y={}\".format(pos.x, pos.y))\r\n\r\ndef client_connected():\r\n    print(\"connected callback\")\r\n\r\ndef client_disconnected():\r\n    print(\"disconnected callback\")\r\n\r\ndot.when_client_connects = client_connected\r\ndot.when_client_disconnects = client_disconnected\r\ndot.when_pressed = pressed\r\ndot.when_released = released\r\ndot.when_moved = moved\r\ndot.when_swiped = swiped\r\ndot.when_double_pressed = double_presed\r\n\r\ndot.start()\r\n\r\ndot.wait_for_press()\r\nprint(\"wait for press\")\r\ndot.wait_for_move()\r\nprint(\"wait for move\")\r\ndot.wait_for_release()\r\nprint(\"wait for release\")\r\ndot.wait_for_double_press()\r\nprint(\"wait for double press\")\r\ndot.wait_for_swipe()\r\nprint(\"wait for swipe\")\r\n\r\ntry:\r\n    while True:\r\n        sleep(0.1)\r\nfinally:\r\n    dot.stop()\r\n"
  },
  {
    "path": "examples/double_press.py",
    "content": "from signal import pause\r\nfrom bluedot import BlueDot\r\nbd = BlueDot()\r\n\r\ndef double_pressed():\r\n    print(\"double pressed\")\r\n\r\nbd.when_double_pressed = double_pressed\r\n\r\npause()"
  },
  {
    "path": "examples/dpad.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef dpad(pos):\n    if pos.top:\n        print(\"up\")\n    elif pos.bottom:\n        print(\"down\")\n    elif pos.left:\n        print(\"left\")\n    elif pos.right:\n        print(\"right\")\n\nbd = BlueDot()\nbd.when_pressed = dpad\nbd.when_moved = dpad\n\npause()\n"
  },
  {
    "path": "examples/dpad_layout.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef up():\r\n    print(\"up\")\r\n\r\ndef down():\r\n    print(\"down\")\r\n\r\ndef left():\r\n    print(\"left\")\r\n\r\ndef right():\r\n    print(\"right\")\r\n\r\nbd = BlueDot(cols=3, rows=3)\r\nbd.color = \"gray\"\r\nbd.square = True\r\n\r\nbd[0,0].visible = False\r\nbd[2,0].visible = False\r\nbd[0,2].visible = False\r\nbd[2,2].visible = False\r\nbd[1,1].visible = False\r\n\r\nbd[1,0].when_pressed = up\r\nbd[1,2].when_pressed = down\r\nbd[0,1].when_pressed = left\r\nbd[2,1].when_pressed = right\r\n\r\npause()"
  },
  {
    "path": "examples/hello_world.py",
    "content": "from bluedot import BlueDot\n\ndot = BlueDot()\ndot.wait_for_press()\nprint(\"You pressed the blue dot - Hello world\")\n"
  },
  {
    "path": "examples/joypad.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef up():\r\n    print(\"up\")\r\n\r\ndef down():\r\n    print(\"down\")\r\n\r\ndef left():\r\n    print(\"left\")\r\n\r\ndef right():\r\n    print(\"right\")\r\n\r\ndef button_A():\r\n    print(\"A\")\r\n\r\ndef button_B():\r\n    print(\"B\")\r\n\r\nbd = BlueDot(cols=5, rows=3)\r\n\r\n# dpad buttons\r\nbd.color = \"gray\"\r\nbd.square = True\r\nbd[0,0].visible = False\r\nbd[2,0].visible = False\r\nbd[0,2].visible = False\r\nbd[2,2].visible = False\r\nbd[1,1].visible = False\r\n\r\nbd[1,0].when_pressed = up\r\nbd[1,2].when_pressed = down\r\nbd[0,1].when_pressed = left\r\nbd[2,1].when_pressed = right\r\n\r\n# buttons\r\nbd[3,0].visible = False\r\nbd[4,0].visible = False\r\nbd[3,2].visible = False\r\nbd[4,2].visible = False\r\n\r\nbd[3,1].color = \"blue\"\r\nbd[3,1].square = False\r\nbd[3,1].when_pressed = button_A\r\n\r\nbd[4,1].color = \"red\"\r\nbd[4,1].when_pressed = button_B\r\n\r\npause()"
  },
  {
    "path": "examples/many_buttons.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=2, rows=5)\r\nbd.when_pressed = pressed\r\n\r\npause()"
  },
  {
    "path": "examples/matrix_of_dots.py",
    "content": "from bluedot import BlueDot, COLORS\r\nfrom signal import pause\r\nfrom random import choice\r\n\r\nbd = BlueDot()\r\nbd.resize(1,2)\r\n\r\ndef pressed(pos):\r\n    print(\"Pressed      : {}\".format(pos))\r\n\r\ndef moved(pos):\r\n    print(\"Moved        : {}\".format(pos))\r\n\r\ndef released(pos):\r\n    print(\"Released     : {}\".format(pos))\r\n\r\ndef double_press(pos):\r\n    print(\"Double press : {}\".format(pos))\r\n\r\ndef swipe(swipe):\r\n    print(\"Swipe        : {}\".format(swipe))\r\n\r\ndef rotation(rotation):\r\n    print(\"Rotation      : {}\".format(rotation))\r\n\r\ndef increase_matrix():\r\n    bd.resize(bd._cols + 1, bd._rows + 1)\r\n    # bd._send_cell_config(2, 2, \"#ff0000ff\", False, False, True)\r\n\r\ndef change_color():\r\n    # increase_matrix()\r\n    # bd[bd.cols - 1, bd.rows - 1].color = \"green\"\r\n    for c in range(bd.cols):\r\n        for r in range(bd.rows):\r\n            bd[c,r].color = choice(list(COLORS.keys()))\r\n\r\n    #print(bd.cells)\r\n\r\nchange_color()\r\n\r\n# bd.when_pressed = increase_matrix\r\n\r\n\r\nbd[0,0].when_pressed = pressed\r\nbd[0,0].when_moved = moved\r\nbd[0,0].when_released = released\r\nbd[0,0].when_double_pressed = double_press\r\nbd[0,0].when_swiped = swipe\r\nbd[0,0].when_rotated = rotation\r\nbd.when_pressed = pressed\r\nbd.when_moved = moved\r\nbd.when_released = released\r\nbd.when_double_pressed = double_press\r\nbd.when_swiped = swipe\r\nbd.when_rotated = rotation\r\n\r\npause()"
  },
  {
    "path": "examples/mock_app_debug.py",
    "content": "from bluedot import MockBlueDot\nfrom time import sleep, time\n\nmbd = MockBlueDot(auto_start_server = False)\n\ndef pressed(pos):\n    print(\"Pressed: x={} y={} angle={} distance={} middle={} top={} bottom={} left={} right={} time={}\".format(pos.x, pos.y, pos.angle, pos.distance, pos.middle, pos.top, pos.bottom, pos.left, pos.right, time()))\n\ndef released():\n    print(\"Released: x={} y={}\".format(mbd.position.x, mbd.position.y))\n    print()\n\ndef moved(pos):\n    print(\"Moved: x={} y={}\".format(pos.x, pos.y))\n\nmbd.when_pressed = pressed\nmbd.when_released = released\nmbd.when_moved = moved\nmbd.start()\n\n#launch a mock app\nmbd.launch_mock_app()\n\ntry:\n    while True:\n        sleep(1)\nfinally:\n    mbd.mock_client_disconnected()\n    mbd.stop()\n"
  },
  {
    "path": "examples/mock_btcomm_debug.py",
    "content": "from bluedot.mock import MockBluetoothClient, MockBluetoothServer\n\ndef s_client_connected():\n    print(\"s: c connected\")\n    \ndef s_data_received(data):\n    print(\"s: recv - {}\".format(data))\n    \nprint(\"s: creating\")\ns = MockBluetoothServer(\n    s_data_received,\n    when_client_connects=s_client_connected\n    )\n\ndef c_data_received(data):\n    print(\"c: recv - {}\".format(data))\n\nprint(\"c: creating\")\nc = MockBluetoothClient(\n    s, \n    c_data_received\n    )\n\nc.send(\"hi\")\ns.send(\"bye\")\n"
  },
  {
    "path": "examples/mock_debug.py",
    "content": "from bluedot import MockBlueDot\nfrom time import sleep, time\n\nmbd = MockBlueDot(auto_start_server = False)\n\ndef pressed(pos):\n    print(\"Pressed: x={} y={} angle={} distance={} middle={} top={} bottom={} left={} right={} time={}\".format(pos.x, pos.y, pos.angle, pos.distance, pos.middle, pos.top, pos.bottom, pos.left, pos.right, time()))\n\ndef released():\n    print(\"Released: x={} y={}\".format(mbd.position.x, mbd.position.y))\n    print()\n\ndef moved(pos):\n    print(\"Moved: x={} y={}\".format(pos.x, pos.y))\n\nmbd.when_pressed = pressed\nmbd.when_released = released\nmbd.when_moved = moved\nmbd.start()\nmbd.mock_client_connected()\nmbd.wait_for_connection()\n\ntry:\n    while True:\n        mbd.mock_blue_dot_pressed(1.0,1.0)\n        mbd.mock_blue_dot_moved(0.5,1.0)\n        mbd.mock_blue_dot_released(0.5,1.0)\n        sleep(0.1)\nfinally:\n    mbd.mock_client_disconnected()\n    mbd.stop()\n"
  },
  {
    "path": "examples/remote_camera.py",
    "content": "from bluedot import BlueDot\nfrom picamera import PiCamera\nfrom signal import pause\n\ndot = BlueDot()\ncam = PiCamera()\n\ndef take_picture():\n    cam.capture(\"pic.jpg\")\n\ndot.when_pressed = take_picture\npause()\n"
  },
  {
    "path": "examples/server_debug.py",
    "content": "from bluedot.btcomm import BluetoothServer\nfrom time import sleep\nfrom signal import pause\n\ndef data_received(data):\n    print(\"recv - {}\".format(data))\n    server.send(data)\n\ndef client_connected():\n    print(\"client connected\")\n\ndef client_disconnected():\n    print(\"client disconnected\")\n\nprint(\"init\")\nserver = BluetoothServer(\n    data_received,\n    auto_start = False,\n    when_client_connects = client_connected,\n    when_client_disconnects = client_disconnected)\n\nprint(\"starting\")\nserver.start()\nprint(server.server_address)\nprint(\"waiting for connection\")\n\ntry:\n    pause()\nexcept KeyboardInterrupt as e:\n    print(\"cancelled by user\")\nfinally:\n    print(\"stopping\")\n    server.stop()\nprint(\"stopped\")\n"
  },
  {
    "path": "examples/simple_robot.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import Robot\nfrom signal import pause\n\nbd = BlueDot()\nrobot = Robot(left=(10, 9), right=(8, 7))\n\ndef move(pos):\n    if pos.top:\n        robot.forward()\n    elif pos.bottom:\n        robot.backward()\n    elif pos.left:\n        robot.left()\n    elif pos.right:\n        robot.right()\n\ndef stop():\n    robot.stop()\n\nbd.when_pressed = move\nbd.when_moved = move\nbd.when_released = stop\n\npause()\n"
  },
  {
    "path": "examples/simple_variable_robot.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import Robot\nfrom signal import pause\n\nbd = BlueDot()\nrobot = Robot(left=(10, 9), right=(8, 7))\n\ndef move(pos):\n    if pos.top:\n        robot.forward(pos.distance)\n    elif pos.bottom:\n        robot.backward(pos.distance)\n    elif pos.left:\n        robot.left(pos.distance)\n    elif pos.right:\n        robot.right(pos.distance)\n\ndef stop():\n    robot.stop()\n\nbd.when_pressed = move\nbd.when_moved = move\nbd.when_released = stop\n\npause()\n"
  },
  {
    "path": "examples/slider_centre.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef show_percentage(pos):\n    percentage = round(pos.distance * 100, 2)\n    print(\"{}%\".format(percentage))\n\nbd = BlueDot()\nbd.when_moved = show_percentage\n\npause()\n"
  },
  {
    "path": "examples/slider_horizontal.py",
    "content": "from bluedot import BlueDot\nfrom signal import pause\n\ndef show_percentage(pos):\n    horizontal = ((pos.x + 1) / 2)\n    percentage = round(horizontal * 100, 2)\n    print(\"{}%\".format(percentage))\n\nbd = BlueDot()\nbd.when_moved = show_percentage\n\npause()\n"
  },
  {
    "path": "examples/slider_led_dimmer.py",
    "content": "from bluedot import BlueDot\nfrom gpiozero import PWMLED\nfrom signal import pause\n\ndef set_brightness(pos):\n    brightness = ((pos.y + 1) / 2)\n    led.value = brightness\n\nbd = BlueDot()\nbd.when_moved = set_brightness\nled = PWMLED(17)\n\npause()\n"
  },
  {
    "path": "examples/source_robot.py",
    "content": "from gpiozero import Robot\nfrom bluedot import BlueDot\nfrom signal import pause\n\ndef pos_to_values(x, y):\n    left = y if x > 0 else y + x\n    right = y if x < 0 else y - x\n    return (clamped(left), clamped(right))\n\ndef clamped(v):\n    return max(-1, min(1, v))\n\ndef drive():\n    while True:\n        if bd.is_pressed:\n            x, y = bd.position.x, bd.position.y\n            yield pos_to_values(x, y)\n        else:\n            yield (0, 0)\n\nif __name__ == '__main__':\n    robot = Robot(left=(10, 9), right=(8, 7))\n    bd = BlueDot()\n\n    robot.source = drive()\n\n    pause()\n"
  },
  {
    "path": "examples/swipe_debug.py",
    "content": "from bluedot import BlueDot, BlueDotSwipe\nfrom signal import pause\n\nbd = BlueDot()\n\nprint(\"waiting for swipe\")\nbd.wait_for_swipe()\nprint(\"swiped\")\n\ndef released():\n    swipe = BlueDotSwipe(bd.interaction)\n    if not swipe.valid:\n        print(\"Invalid swipe - speed {}\".format(swipe.speed))\n\ndef valid_swipe(swipe):\n    #swipe = BlueDotSwipe(bd.interaction)\n    #print(\"valid {} duration {} distance {} angle {}\".format(swipe.valid, swipe.interaction.duration, swipe.distance, swipe.angle))\n    #print(\"up {} down {} left {} right {}\".format(swipe.up, swipe.down, swipe.left, swipe.right))\n    if swipe.up:\n        print(\"UP {}\".format(swipe.speed))\n    elif swipe.down:\n        print(\"DOWN {}\".format(swipe.speed))\n    elif swipe.left:\n        print(\"LEFT {}\".format(swipe.speed))\n    elif swipe.right:\n        print(\"RIGHT {}\".format(swipe.speed))\n\nbd.when_released = released\nbd.when_swiped = valid_swipe\n\npause()\n"
  },
  {
    "path": "examples/threaded_callbacks.py",
    "content": "from bluedot import BlueDot\r\nfrom time import sleep\r\nfrom signal import pause\r\nbd = BlueDot()\r\n\r\ndef pressed():\r\n    print(\"pressed - waiting\")\r\n    sleep(3)\r\n    print(\"pressed - complete\")\r\n\r\ndef released():\r\n    print(\"released\")\r\n\r\nbd.set_when_pressed(pressed, background=True)\r\nbd.when_released = released\r\n\r\npause()\r\n"
  },
  {
    "path": "examples/two_buttons.py",
    "content": "from bluedot import BlueDot\r\nfrom signal import pause\r\n\r\ndef pressed(pos):\r\n    print(\"button {}.{} pressed\".format(pos.col, pos.row))\r\n\r\nbd = BlueDot(cols=2)\r\nbd.when_pressed = pressed\r\n\r\npause()"
  },
  {
    "path": "examples/wait_for_events.py",
    "content": "from bluedot import BlueDot\nfrom turtle import Turtle\n\nbd = BlueDot()\n\nwhile True:\n    bd.wait_for_press()\n    print(\"pressed\")\n\n    bd.wait_for_move()\n    print(\"moved\")\n    \n    bd.wait_for_release()\n    print(\"released\")\n\n    bd.wait_for_double_press()\n    print(\"double press\")\n    \n    bd.wait_for_swipe()\n    print(\"swipe\")\n    \n    \n    \n"
  },
  {
    "path": "setup.py",
    "content": "import sys\nfrom setuptools import setup\n\nif sys.version_info.major == 2:\n    raise ValueError('This package does not support Python 2')\nelif sys.version_info.major == 3:\n    if sys.version_info.minor < 5:\n        raise ValueError('This package requires Python 3.5 or newer')\nelse:\n    raise ValueError('Python version not identified')\n\n__project__ = 'bluedot'\n__desc__ = 'A zero boiler plate bluetooth remote'\n__version__ = '2.0.0'\n__author__ = \"Martin O'Hanlon\"\n__author_email__ = 'martin@ohanlonweb.com'\n__license__ = 'MIT'\n__url__ = 'https://github.com/martinohanlon/BlueDot'\n__python_requires__ = '>=3.5'\n# __requires__ = ['dbus-python',]\n__keywords__ = [\n    \"raspberry\",\n    \"pi\",\n    \"raspberry pi\",\n    \"bluetooth\",\n    \"remote\",\n    \"android\",\n]\n__classifiers__ = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Education\",\n    \"Intended Audience :: Developers\",\n    \"Topic :: Education\",\n    \"Topic :: Communications\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.5\",\n    \"Programming Language :: Python :: 3.6\",\n    \"Programming Language :: Python :: 3.7\",\n    \"Programming Language :: Python :: 3.8\",\n]\n__long_description__ = \"\"\"# Blue Dot\n\n[Blue Dot](http://bluedot.readthedocs.io/en/latest/) allows you to control your Raspberry Pi projects wirelessly - it's a Bluetooth remote and zero boiler plate (super simple to use :) Python library.\n\n## Getting started\n\n1. Install\n\n```\nsudo pip3 install bluedot\n```\n\n2. Get the [Android Blue Dot app](http://play.google.com/store/apps/details?id=com.stuffaboutcode.bluedot) or use the [Python Blue Dot app](http://bluedot.readthedocs.io/en/latest/bluedotpythonapp.html)\n\n3. Pair your Raspberry Pi\n\n4. Write some code\n\n```python\nfrom bluedot import BlueDot\nbd = BlueDot()\nbd.wait_for_press()\nprint(\"You pressed the blue dot!\")\n```\n\n5. Press the Blue Dot\n\nSee the [getting started guide](http://bluedot.readthedocs.io/en/latest/gettingstarted.html) to 'get started'!\n\n## More\n\nBlue Dot is more than just one button. You can create as many buttons as you want and change their appearance to create your own controller.\n\nEvery button is also a joystick. You can tell if a button was pressed in the middle, on the top, bottom, left or right. You can easily create a BlueDot controlled Robot.\n\nWhy be restricted by such vague positions like top and bottom though: you can get the exact (x, y) position or even the angle and distance from centre where the button was pressed.\n\nIts not all about when the button was pressed either - pressed, released or moved they all work.\n\nA button can be any colour, square, given give or hidden!\n\nYou can press it, slide it, swipe it, rotate it - one blue circle can do a lot!\n\n## Even more\n\nThe [online documentation](http://bluedot.readthedocs.io/en/latest/) describes how to use Blue Dot and the Python library including recipes and ideas.\n\n\"\"\"\nif __name__ == '__main__':\n    setup(name='bluedot',\n        version = __version__,\n        description = __desc__,\n        long_description=__long_description__,\n        long_description_content_type='text/markdown',\n        url = __url__,\n        author = __author__,\n        author_email = __author_email__,\n        license= __license__,\n        keywords=__keywords__,\n        classifiers=__classifiers__,\n        packages = [__project__],\n        # install_requires = __requires__,\n        entry_points={\n            'console_scripts': [\n                'bluedotapp = bluedot.app:main'\n                ]},\n        )\n"
  },
  {
    "path": "tests/test_blue_dot.py",
    "content": "import pytest\nfrom bluedot import MockBlueDot, BlueDotSwipe, BlueDotRotation\nfrom bluedot.exceptions import ButtonDoesNotExist\nfrom time import sleep\nfrom threading import Event, Thread\n\ndef test_default_values():\n    mbd = MockBlueDot()\n    assert mbd.device == \"hci0\"\n    assert mbd.port == 1\n    assert mbd.running\n    assert mbd.cols == 1\n    assert mbd.rows == 1\n\n    assert mbd.print_messages\n    assert mbd.double_press_time == 0.3\n    assert mbd.rotation_segments == 8\n\n    assert mbd.when_client_connects == None\n    assert mbd.when_client_disconnects == None\n    assert mbd.when_pressed == None\n    assert mbd.when_double_pressed == None\n    assert mbd.when_moved == None\n    assert mbd.when_released == None\n    assert mbd.when_swiped == None\n\n    assert len(mbd.buttons) == 1\n\ndef test_modify_values():\n    mbd = MockBlueDot(device = \"hci1\", port = 2, auto_start_server = False, print_messages = False, cols = 3, rows = 2)\n    assert mbd.device == \"hci1\"\n    assert mbd.port == 2\n    assert not mbd.running\n    assert mbd.cols == 3\n    assert mbd.rows == 2\n\n    assert not mbd.print_messages\n    mbd.print_messages = True\n    assert mbd.print_messages\n\n    assert mbd.double_press_time == 0.3\n    mbd.double_press_time = 0.4\n    assert mbd.double_press_time == 0.4\n\n    assert mbd.rotation_segments == 8\n    mbd.rotation_segments = 16\n    assert mbd.rotation_segments == 16\n\n    assert len(mbd.buttons) == 6\n\ndef test_start_stop():\n    mbd = MockBlueDot(auto_start_server = False)\n    assert not mbd.running\n    mbd.start()\n    assert mbd.running\n    mbd.stop()\n    assert not mbd.running\n\ndef test_connect_disconnect():\n    mbd = MockBlueDot()\n    assert not mbd.is_connected\n    mbd.mock_client_connected()\n    assert mbd.wait_for_connection(1)\n    assert mbd.is_connected\n    mbd.mock_client_disconnected()\n    assert not mbd.is_connected\n\ndef test_when_connect_disconnect():\n    mbd = MockBlueDot()\n\n    event_connect = Event()\n    mbd.when_client_connects = lambda: event_connect.set()\n\n    event_disconnect = Event()\n    mbd.when_client_disconnects = lambda: event_disconnect.set()\n\n    assert not event_connect.is_set()\n    mbd.mock_client_connected()\n    assert event_connect.wait(1)\n\n    assert not event_disconnect.is_set()\n    mbd.mock_client_disconnected()\n    assert event_disconnect.wait(1)\n\ndef test_when_connect_disconnect_background():\n    mbd = MockBlueDot()\n\n    event_connect = Event()\n    mbd.set_when_client_connects(lambda: delay_function(event_connect.set, 0.2), background=True)\n    \n    event_disconnect = Event()\n    mbd.set_when_client_disconnects(lambda: delay_function(event_disconnect.set, 0.2), background=True)\n    mbd.when_client_disconnects = lambda: event_disconnect.set()\n\n    assert not event_connect.is_set()\n    mbd.mock_client_connected()\n    assert not event_connect.is_set()\n    assert event_connect.wait(1)\n\n    assert not event_disconnect.is_set()\n    mbd.mock_client_disconnected()\n    assert not event_disconnect.is_set()\n    assert event_disconnect.wait(1)\n\ndef test_resize():\n    mbd = MockBlueDot()\n    mbd.resize(4,3)\n\n    assert mbd.cols == 4\n    assert mbd.rows == 3\n    assert len(mbd.buttons) == 12\n\ndef test_pressed_moved_released():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def pressed_moved_released(dot, col, row):\n        \n        #initial value\n        assert not mbd.is_pressed\n        assert dot.value == 0\n\n        #pressed\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert dot.is_pressed\n        assert dot.value == 1\n\n        #released\n        mbd.mock_blue_dot_released(col,row,0,0)\n        assert not dot.is_pressed\n        assert dot.value == 0\n\n        #wait_for_press\n        delay_function(lambda: mbd.mock_blue_dot_pressed(col,row,0,0), 0.5)\n        assert dot.wait_for_press(1)\n        assert not dot.wait_for_release(0)\n\n        #wait_for_release\n        delay_function(lambda: mbd.mock_blue_dot_released(col,row,0,0), 0.5)\n        assert dot.wait_for_release(1)\n        assert not dot.wait_for_press(0)\n\n    def not_pressed(dot, col, row):\n        assert not dot.is_pressed\n        assert not dot.value == 1\n\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert not dot.is_pressed\n        assert not dot.value == 1\n\n    # single button\n    pressed_moved_released(mbd, 0, 0)\n    pressed_moved_released(mbd[0,0], 0, 0)\n\n    # resize to 2 buttons\n    mbd.resize(2, 1)\n\n    # test second button and main\n    pressed_moved_released(mbd, 1, 0)\n    pressed_moved_released(mbd[1,0], 1, 0)\n\n    # test second button isn't pressed by first\n    not_pressed(mbd[1,0], 0, 0)\n\ndef test_double_press():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def simulate_double_press(col, row):\n        #sleep longer than the double press time, to clear any past double presses!\n        sleep(mbd.double_press_time + 0.1)\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        mbd.mock_blue_dot_released(col,row,0,0)\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        mbd.mock_blue_dot_released(col,row,0,0)\n\n    def simulate_failed_double_press(col, row):\n        sleep(mbd.double_press_time + 0.1)\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        mbd.mock_blue_dot_released(col,row,0,0)\n        sleep(mbd.double_press_time + 0.1)\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        mbd.mock_blue_dot_released(col,row,0,0)\n\n    def double_press(dot, col, row):\n        # when_double_pressed\n        event_double_pressed = Event()\n        dot.when_double_pressed = lambda: event_double_pressed.set()\n\n        simulate_failed_double_press(col, row)\n        assert not event_double_pressed.is_set()\n\n        simulate_double_press(col, row)\n        assert event_double_pressed.is_set()\n\n        # wait for double press\n        # double press the blue dot\n        delay_function(lambda: simulate_double_press(col, row), 0.2)\n\n        # wait for double press\n        assert dot.wait_for_double_press(1)\n\n        # dont double press the blue dot\n        delay_function(lambda: simulate_failed_double_press(col, row), 0.2)\n        assert not dot.wait_for_double_press(1)\n\n    def not_double_press(dot, col, row):\n        # when_double_pressed\n        event_double_pressed = Event()\n        dot.when_double_pressed = lambda: event_double_pressed.set()\n\n        simulate_double_press(col, row)\n        assert not event_double_pressed.is_set()\n\n    # single button\n    double_press(mbd, 0, 0)\n    double_press(mbd[0,0], 0, 0)\n\n    mbd.resize(2, 1)\n\n    # two buttons\n    double_press(mbd, 1, 0)\n    double_press(mbd[1,0], 1, 0)\n\n    # first button doesnt double press second button\n    not_double_press(mbd[1,0], 0, 0)\n\n\ndef test_when_pressed_moved_released():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def when_pressed_moved_released(dot, col, row):\n        #when_pressed\n        event_pressed = Event()\n        dot.when_pressed = lambda: event_pressed.set()\n\n        #when_double_pressed\n        event_double_pressed = Event()\n        dot.when_double_pressed = lambda: event_double_pressed.set()\n\n        #when_moved\n        event_moved = Event()\n        dot.when_moved = lambda: event_moved.set()\n\n        #when_released\n        event_released = Event()\n        dot.when_released = lambda: event_released.set()\n\n        assert not event_pressed.is_set()\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert event_pressed.is_set()\n\n        assert not event_moved.is_set()\n        mbd.mock_blue_dot_moved(col,row,1,1)\n        assert event_moved.is_set()\n\n        assert not event_released.is_set()\n        mbd.mock_blue_dot_released(col,row,0,0)\n        assert event_released.is_set()\n\n        assert not event_double_pressed.is_set()\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert event_double_pressed.is_set()\n\n    when_pressed_moved_released(mbd, 0, 0)\n    when_pressed_moved_released(mbd[0,0], 0, 0)\n\n    mbd.resize(2,1)\n\n    when_pressed_moved_released(mbd, 1, 0)\n    when_pressed_moved_released(mbd[1,0], 1, 0)\n\ndef test_when_pressed_moved_released_background():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def when_pressed_moved_released_background(dot, col, row):\n\n        #when_pressed\n        event_pressed = Event()\n        dot.set_when_pressed(lambda: delay_function(event_pressed.set, 0.2), background=True)\n\n        #when_double_pressed\n        event_double_pressed = Event()\n        dot.set_when_double_pressed(lambda: delay_function(event_double_pressed.set, 0.2), background=True)\n        \n        #when_moved\n        event_moved = Event()\n        dot.set_when_moved(lambda: delay_function(event_moved.set, 0.2), background=True)\n\n        #when_released\n        event_released = Event()\n        dot.set_when_released(lambda: delay_function(event_released.set, 0.2), background=True)\n        \n        # test that the events dont block\n        assert not event_pressed.is_set()\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert not event_pressed.is_set()\n        assert event_pressed.wait(1)\n\n        assert not event_moved.is_set()\n        mbd.mock_blue_dot_moved(col,row,1,1)\n        assert not event_moved.is_set()\n        assert event_moved.wait(1)\n\n        assert not event_released.is_set()\n        mbd.mock_blue_dot_released(col,row,0,0)\n        assert not event_released.is_set()\n        assert event_released.wait(1)\n\n        # set pressed, moved, released to None so they dont wait\n        mbd.set_when_pressed(None)\n        mbd.set_when_moved(None)\n        mbd.set_when_released(None)\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        mbd.mock_blue_dot_moved(col,row,1,1)\n        mbd.mock_blue_dot_released(col,row,0,0)\n        assert not event_double_pressed.is_set()\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert not event_double_pressed.is_set()\n        assert event_double_pressed.wait(1)\n\n    when_pressed_moved_released_background(mbd, 0, 0)\n    when_pressed_moved_released_background(mbd[0,0], 0, 0)\n\n    mbd.resize(2,1)\n\n    when_pressed_moved_released_background(mbd, 1, 0)\n    when_pressed_moved_released_background(mbd[1,0], 1, 0)\n\ndef test_position():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def position(dot, col, row):\n        mbd.mock_blue_dot_pressed(col,row,0,0)\n        assert not mbd.position.top\n        assert mbd.position.middle\n        assert not mbd.position.bottom\n        assert not mbd.position.left\n        assert not mbd.position.right\n\n        mbd.mock_blue_dot_moved(col,row,1,0)\n        assert not mbd.position.top\n        assert not mbd.position.middle\n        assert not mbd.position.bottom\n        assert not mbd.position.left\n        assert mbd.position.right\n\n        mbd.mock_blue_dot_moved(col,row,-1,0)\n        assert not mbd.position.top\n        assert not mbd.position.middle\n        assert not mbd.position.bottom\n        assert mbd.position.left\n        assert not mbd.position.right\n\n        mbd.mock_blue_dot_moved(col,row,0,1)\n        assert mbd.position.top\n        assert not mbd.position.middle\n        assert not mbd.position.bottom\n        assert not mbd.position.left\n        assert not mbd.position.right\n\n        mbd.mock_blue_dot_moved(col,row,0,-1)\n        assert not mbd.position.top\n        assert not mbd.position.middle\n        assert mbd.position.bottom\n        assert not mbd.position.left\n        assert not mbd.position.right\n\n        mbd.mock_blue_dot_moved(col,row,0.1234, -0.4567)\n        assert mbd.position.x == 0.1234\n        assert mbd.position.y == -0.4567\n\n        mbd.mock_blue_dot_moved(col,row,1, 0)\n        assert mbd.position.distance == 1\n        assert mbd.position.angle == 90\n\n    position(mbd, 0, 0)\n    position(mbd[0,0], 0, 0)\n    mbd.resize(2,1)\n    position(mbd[1,0], 1, 0)\n\ndef test_interaction():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n    \n    def interaction(dot, col, row):\n        assert dot.interaction == None\n\n        mbd.mock_blue_dot_pressed(col,row,-1,0)\n        assert dot.interaction.active\n        assert len(dot.interaction.positions) == 1\n        assert dot.interaction.distance == 0\n        assert dot.interaction.pressed_position.x == -1\n        assert dot.interaction.pressed_position.y == 0\n        assert dot.interaction.current_position.x == -1\n        assert dot.interaction.current_position.y == 0\n        assert dot.interaction.previous_position == None\n        assert dot.interaction.released_position == None\n\n        mbd.mock_blue_dot_moved(col,row,0,0)\n        assert dot.interaction.active\n        assert len(dot.interaction.positions) == 2\n        assert dot.interaction.distance == 1\n        assert dot.interaction.pressed_position.x == -1\n        assert dot.interaction.pressed_position.y == 0\n        assert dot.interaction.current_position.x == 0\n        assert dot.interaction.current_position.y == 0\n        assert dot.interaction.previous_position.x == -1\n        assert dot.interaction.previous_position.y == 0\n        assert dot.interaction.released_position == None\n\n        mbd.mock_blue_dot_released(col,row,1,0)\n        assert not dot.interaction.active\n        assert len(dot.interaction.positions) == 3\n        assert dot.interaction.distance == 2\n        assert dot.interaction.pressed_position.x == -1\n        assert dot.interaction.pressed_position.y == 0\n        assert dot.interaction.current_position.x == 1\n        assert dot.interaction.current_position.y == 0\n        assert dot.interaction.previous_position.x == 0\n        assert dot.interaction.previous_position.y == 0\n        assert dot.interaction.released_position.x == 1\n        assert dot.interaction.released_position.y == 0\n\n    interaction(mbd[0,0], 0, 0)\n\n    mbd.resize(2,1)\n    \n    interaction(mbd[1,0], 1, 0)\n\ndef test_swipe():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def swipe(dot, col, row):\n        \n        def simulate_swipe(\n            col, row,\n            pressed_x, pressed_y, \n            moved_x, moved_y, \n            released_x, released_y):\n\n            mbd.mock_blue_dot_pressed(col,row,pressed_x, pressed_y)\n            mbd.mock_blue_dot_moved(col,row,moved_x, moved_y)\n            mbd.mock_blue_dot_released(col,row,released_x, released_y)\n\n        #wait_for_swipe\n        delay_function(lambda: simulate_swipe(col,row,-1,0,0,0,1,0), 0.5)\n        assert dot.wait_for_swipe(1)\n\n        #when_swiped\n        event_swiped = Event()\n        dot.when_swiped = lambda: event_swiped.set()\n        assert not event_swiped.is_set()\n\n        #simulate swipe left to right\n        simulate_swipe(col,row,-1,0,0,0,1,0)\n        #check event\n        assert event_swiped.is_set()\n        #get the swipe\n        swipe = BlueDotSwipe(mbd[col, row].interaction)\n        assert swipe.right\n        assert not swipe.left\n        assert not swipe.up\n        assert not swipe.down\n\n        #right to left\n        event_swiped.clear()\n        simulate_swipe(col,row,1,0,0,0,-1,0)\n        assert event_swiped.is_set()\n        swipe = BlueDotSwipe(mbd[col, row].interaction)\n        assert not swipe.right\n        assert swipe.left\n        assert not swipe.up\n        assert not swipe.down\n\n        #bottom to top\n        event_swiped.clear()\n        simulate_swipe(col,row,0,-1,0,0,0,1)\n        assert event_swiped.is_set()\n        swipe = BlueDotSwipe(mbd[col, row].interaction)\n        assert not swipe.right\n        assert not swipe.left\n        assert swipe.up\n        assert not swipe.down\n\n        #top to bottom\n        event_swiped.clear()\n        simulate_swipe(col,row,0,1,0,0,0,-1)\n        assert event_swiped.is_set()\n        swipe = BlueDotSwipe(mbd[col, row].interaction)\n        assert not swipe.right\n        assert not swipe.left\n        assert not swipe.up\n        assert swipe.down\n\n        # background\n        event_swiped.clear()\n        dot.set_when_swiped(lambda: delay_function(event_swiped.set, 0.2), background=True)\n        simulate_swipe(col,row,0,1,0,0,0,-1)\n        assert not event_swiped.is_set()\n        assert event_swiped.wait(1)\n\n    swipe(mbd, 0, 0)\n    swipe(mbd[0,0], 0, 0)\n\n    mbd.resize(2,1)\n\n    swipe(mbd, 1, 0)\n    swipe(mbd[1,0], 1, 0)\n\n\ndef test_callback_in_class():\n\n    class CallbackClass():\n        def __init__(self):\n            self.event = Event()\n\n        def no_pos(self):\n            self.event.set()\n            self.pos = None\n\n        def with_pos(self, pos):\n            self.event.set()\n            self.pos = pos\n\n    cc = CallbackClass()\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    mbd.when_pressed = cc.no_pos\n    mbd.mock_blue_dot_pressed(0,0,0,0)\n    assert cc.event.is_set()\n    assert cc.pos is None\n    \n    mbd.mock_blue_dot_released(0,0,0,0)\n    cc.event.clear()\n\n    mbd.when_pressed = cc.with_pos\n    mbd.mock_blue_dot_pressed(0,0,0,0)\n    assert cc.event.is_set()\n    assert cc.pos.middle\n\ndef test_rotation():\n    mbd = MockBlueDot()\n    mbd.mock_client_connected()\n\n    def rotation(dot, col, row):\n        event_rotated = Event()\n        dot.when_rotated = lambda: event_rotated.set()\n        assert not event_rotated.is_set()\n\n        #press the blue dot, no rotation\n        mbd.mock_blue_dot_pressed(col,row,-0.1,1)\n        assert not event_rotated.is_set()\n        r = BlueDotRotation(mbd[col,row].interaction, mbd[0,0].rotation_segments)\n        assert not r.valid\n        assert r.value == 0\n        assert not r.clockwise\n        assert not r.anti_clockwise\n\n        #rotate clockwise\n        event_rotated.clear()\n        mbd.mock_blue_dot_moved(col,row,0.1,1)\n        assert event_rotated.is_set()\n        r = BlueDotRotation(mbd[col,row].interaction, mbd[col,row].rotation_segments)\n        assert r.value == 1\n        assert r.valid\n        assert r.clockwise\n        assert not r.anti_clockwise\n\n        #rotate anti-clockwise\n        event_rotated.clear()\n        mbd.mock_blue_dot_moved(col,row,-0.1,1)\n        assert event_rotated.is_set()\n        r = BlueDotRotation(mbd[col,row].interaction, mbd[col,row].rotation_segments)\n        assert r.value == -1\n        assert r.valid\n        assert not r.clockwise\n        assert r.anti_clockwise\n\n        # background\n        # rotate clockwise again\n        event_rotated.clear()\n        dot.set_when_rotated(lambda: delay_function(event_rotated.set, 0.2), background=True)\n        mbd.mock_blue_dot_moved(col,row,0.1,1)\n        assert not event_rotated.is_set()\n        assert event_rotated.wait(1)\n\n    rotation(mbd, 0, 0)\n    rotation(mbd[0,0], 0, 0)\n\n    mbd.resize(2,1)\n\n    rotation(mbd, 1, 0)\n    rotation(mbd[1,0], 1, 0)\n    \ndef test_button_does_not_exist():\n    mbd = MockBlueDot()\n    \n    with pytest.raises(ButtonDoesNotExist):\n        button = mbd[0,1]\n    \ndef test_allow_pairing():\n    mbd = MockBlueDot()\n    assert not mbd.adapter.discoverable\n    assert not mbd.adapter.pairable\n\n    mbd.allow_pairing()\n    assert mbd.adapter.discoverable\n    assert mbd.adapter.pairable\n\ndef test_dot_appearance():\n    mbd = MockBlueDot()\n    assert mbd.color == \"blue\"\n    assert mbd.border == False\n    assert mbd.square == False\n    assert mbd.visible == True\n\n    mbd.resize(2,1)\n\n    for button in mbd.buttons:\n        assert button.color == \"blue\"\n        assert button.border == False\n        assert button.square == False\n        assert button.visible == True\n\n    mbd[1,0].color = \"red\"\n    mbd[1,0].border = True\n    mbd[1,0].square = True\n    mbd[1,0].visible = False\n\n    assert mbd.color == \"blue\"\n    assert mbd.border == False\n    assert mbd.square == False\n    assert mbd.visible == True\n    \n    assert mbd[0,0].color == \"blue\"\n    assert mbd[0,0].border == False\n    assert mbd[0,0].square == False\n    assert mbd[0,0].visible == True\n\n    assert mbd[1,0].color == \"red\"\n    assert mbd[1,0].border == True\n    assert mbd[1,0].square == True\n    assert mbd[1,0].visible == False\n\n    mbd.color = \"red\"\n    mbd.border = True\n    mbd.square = True\n    mbd.visible = False\n\n    assert mbd.color == \"red\"\n    assert mbd.border == True\n    assert mbd.square == True\n    assert mbd.visible == False\n\n    assert mbd[0,0].color == \"red\"\n    assert mbd[0,0].border == True\n    assert mbd[0,0].square == True\n    assert mbd[0,0].visible == False\n\ndef test_dot_colors():\n    from bluedot.colors import BLUE, RED, GREEN, YELLOW\n\n    mbd = MockBlueDot()\n    assert mbd.color == \"blue\"\n    assert mbd.color == (0,0,255)\n    assert mbd.color == BLUE\n    assert mbd.color == \"#0000ff\"\n    assert mbd.color == \"#0000ffff\"\n\n    mbd.color = RED\n    assert mbd.color == (255,0,0)\n    assert mbd.color == \"red\"\n    assert mbd.color == \"#ff0000\"\n    assert mbd.color == \"#ff0000ff\"\n\n    mbd.color = \"green\"\n    assert mbd.color == GREEN\n    assert mbd.color == (0,128,0)\n    assert mbd.color == \"#008000\"\n    assert mbd.color == \"#008000ff\"\n\n    mbd.color = \"#ffff00\"\n    assert mbd.color == YELLOW\n    assert mbd.color == \"yellow\"\n    assert mbd.color == (255,255,0)\n    assert mbd.color == \"#ffff00ff\"\n\n    mbd.color = \"#ffffff11\"\n    assert mbd.color == \"#ffffff11\"\n\ndef delay_function(func, time):\n    delayed_thread = Thread(target = _delayed_function, args = (func, time))\n    delayed_thread.start()\n\ndef _delayed_function(func, time):\n    sleep(time)\n    func()"
  },
  {
    "path": "tests/test_bluetooth_adapter.py",
    "content": "import pytest\r\nfrom bluedot.btcomm import BluetoothAdapter\r\n\r\ntry:\r\n    bta = BluetoothAdapter(\"hci0\")\r\nexcept Exception as e:\r\n    if str(e) == \"Bluetooth adapter hci0 not found\":\r\n        pytest.skip(\"Bluetooth adapter hci0 not found - skipping test\", allow_module_level = True)\r\n\r\ndef test_bluetooth_adapter():\r\n    bta = BluetoothAdapter()\r\n    assert bta.device == \"hci0\"\r\n    assert len(bta.address) == 17\r\n\r\n    # get the initial state of the adapter\r\n    powered = bta.powered\r\n    discoverable = bta.discoverable\r\n    pairable = bta.pairable\r\n\r\n    # flip the values back and forward\r\n    bta.powered = not powered\r\n    assert bta.powered == (not powered)\r\n    bta.powered = powered\r\n    assert bta.powered == powered\r\n\r\n    # power up the adapter so discoverable and pairable can be tested\r\n    bta.powered = True\r\n    assert bta.powered == True\r\n\r\n    bta.discoverable = not discoverable\r\n    assert bta.discoverable == (not discoverable)\r\n    bta.discoverable = discoverable\r\n    assert bta.discoverable == discoverable\r\n\r\n    bta.pairable = not pairable\r\n    assert bta.pairable == (not pairable)\r\n    bta.pairable = pairable\r\n    assert bta.pairable == pairable\r\n\r\n    bta.allow_pairing()\r\n    assert bta.pairable == True\r\n    assert bta.discoverable == True\r\n\r\n    # reset the adapter back\r\n    bta.powered = powered\r\n    bta.discoverable = discoverable\r\n    bta.pairable = pairable"
  },
  {
    "path": "tests/test_bluetooth_comms.py",
    "content": "import pytest\r\nfrom bluedot.btcomm import BluetoothAdapter, BluetoothServer, BluetoothClient\r\nfrom threading import Event\r\n\r\n# have we got 2 adapters\r\ntry:\r\n    bta0 = BluetoothAdapter(\"hci0\")\r\n    bta1 = BluetoothAdapter(\"hci1\")\r\nexcept Exception as e:\r\n    pytest.skip(str(e) + \" - skipping test\", allow_module_level = True)\r\n\r\n# is adapter 0 paired with adapter 1 and vice versa\r\npaired = False\r\nfor device in bta0.paired_devices:\r\n    if device[0] == bta1.address:\r\n        paired = True\r\nif not paired:\r\n    pytest.skip(\"hci0 is not paired with hci1\", allow_module_level = True)\r\n\r\npaired = False\r\nfor device in bta1.paired_devices:\r\n    if device[0] == bta0.address:\r\n        paired = True\r\nif not paired:\r\n    pytest.skip(\"hci1 is not paired with hci0\", allow_module_level = True)\r\n\r\ndef test_server_default_values():\r\n    def data_received(data):\r\n        pass\r\n\r\n    bts = BluetoothServer(data_received)\r\n    assert bts.data_received_callback == data_received\r\n    assert bts.device == \"hci0\"\r\n    assert bts.server_address == bta0.address\r\n    assert bts.running\r\n    assert bts.port == 1\r\n    assert bts.encoding == \"utf-8\"\r\n    assert bts.when_client_connects == None\r\n    assert bts.when_client_disconnects == None \r\n\r\n    bts.stop()\r\n\r\ndef test_server_alt_values():\r\n    def data_received(data):\r\n        pass\r\n\r\n    def when_client_connects():\r\n        pass\r\n\r\n    def when_client_disconnects():\r\n        pass\r\n\r\n    bts = BluetoothServer(\r\n        data_received, \r\n        device = \"hci1\", \r\n        auto_start = False, \r\n        port = 2, \r\n        encoding = None, \r\n        when_client_connects = when_client_connects, \r\n        when_client_disconnects = when_client_disconnects)\r\n\r\n    assert bts.data_received_callback == data_received\r\n    assert bts.device == \"hci1\"\r\n    assert bts.server_address == bta1.address\r\n    assert not bts.running\r\n    assert bts.port == 2\r\n    assert bts.encoding == None\r\n    assert bts.when_client_connects == when_client_connects\r\n    assert bts.when_client_disconnects == when_client_disconnects\r\n\r\ndef test_server_start_stop():\r\n\r\n    bts = BluetoothServer(None, auto_start = False)\r\n\r\n    bts.start()\r\n    assert bts.running\r\n\r\n    bts.stop()\r\n    assert not bts.running\r\n\r\ndef test_client_default_values():\r\n    def data_received(data):\r\n        pass\r\n\r\n    bts = BluetoothServer(data_received, device = \"hci1\")\r\n    btc = BluetoothClient(bta1.address, data_received)\r\n    \r\n    assert btc.data_received_callback == data_received\r\n    assert btc.device == \"hci0\"\r\n    assert btc.server == bta1.address\r\n    assert btc.client_address == bta0.address\r\n    assert btc.connected\r\n    assert btc.port == 1\r\n    assert btc.encoding == \"utf-8\"\r\n\r\n    btc.disconnect()\r\n    bts.stop()\r\n\r\ndef test_client_alt_values():\r\n    def data_received(data):\r\n        pass\r\n\r\n    bts = BluetoothServer(None, port = 2, encoding = None)\r\n    btc = BluetoothClient(bta0.address, data_received, device = \"hci1\", auto_connect = False, port = 2, encoding = None)\r\n    \r\n    assert btc.data_received_callback == data_received\r\n    assert btc.device == \"hci1\"\r\n    assert btc.client_address == bta1.address\r\n    assert not btc.connected\r\n    assert btc.port == 2\r\n    assert btc.encoding == None\r\n\r\n    btc.connect()\r\n    assert btc.connected\r\n    \r\n    btc.disconnect()\r\n    assert not btc.connected\r\n\r\n    bts.stop()\r\n\r\ndef test_client_connect_disconnect():\r\n\r\n    client_connected = Event()\r\n    client_disconnected = Event()\r\n\r\n    def when_client_connects():\r\n        client_connected.set()\r\n\r\n    def when_client_disconnects():\r\n        client_disconnected.set()\r\n\r\n    bts = BluetoothServer(None)\r\n    btc = BluetoothClient(bta0.address, None, device = \"hci1\", auto_connect = False)\r\n    bts.when_client_connects = when_client_connects\r\n    bts.when_client_disconnects = when_client_disconnects\r\n\r\n    btc.connect()\r\n    assert btc.connected\r\n    assert bts.client_address == btc.client_address\r\n    assert client_connected.wait(1)\r\n    \r\n    btc.disconnect()\r\n    assert not btc.connected\r\n    assert client_disconnected.wait(1)\r\n\r\n    bts.stop()\r\n\r\ndef test_send_receive():\r\n\r\n    data_received_at_server = Event()\r\n    data_received_at_client = Event()\r\n    \r\n    def data_received_server(data):\r\n        assert data == \"hiserver\"\r\n        data_received_at_server.set()\r\n\r\n    def data_received_client(data):\r\n        assert data == \"hiclient\"\r\n        data_received_at_client.set()\r\n\r\n    bts = BluetoothServer(data_received_server, device = \"hci0\")\r\n    btc = BluetoothClient(bta0.address, data_received_client, device = \"hci1\")\r\n\r\n    btc.send(\"hiserver\")\r\n    assert data_received_at_server.wait(1)\r\n    bts.send(\"hiclient\")\r\n    assert data_received_at_server.wait(1)\r\n    \r\n    btc.disconnect()\r\n    bts.stop()\r\n\r\ndef test_volume_data():\r\n\r\n    from random import choice, randint\r\n    import string\r\n\r\n    no_of_transmissions = randint(100, 200)\r\n    test_data = ''\r\n    received = Event()\r\n\r\n    def data_received_server(data):\r\n        assert data == test_data\r\n        bts.send(test_data)\r\n\r\n    def data_received_client(data):\r\n        assert data == test_data\r\n        received.set()\r\n\r\n    bts = BluetoothServer(data_received_server, device = \"hci0\")\r\n    btc = BluetoothClient(bta0.address, data_received_client, device = \"hci1\")\r\n\r\n    for test_no in range(no_of_transmissions):\r\n        test_data = ''.join(choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(randint(30,100)))\r\n        print(test_data)\r\n        btc.send(test_data)\r\n        assert received.wait(1)\r\n        received.clear()\r\n        \r\n    btc.disconnect()\r\n    bts.stop()\r\n"
  }
]