{"action":"created","comment":{"url":"https://api.github.com/repos/project-rig/rig/pulls/comments/25848830","id":25848830,"diff_hunk":"@@ -0,0 +1,770 @@\n+import collections\n+from six import iteritems\n+import socket\n+import struct\n+\n+from .consts import SCPCommands, DataType, NNCommands, NNConstants\n+from . import boot, consts, regions\n+from .scp_connection import SCPConnection\n+from rig.utils.contexts import ContextMixin, Required\n+\n+\n+class MachineController(ContextMixin):\n+    \"\"\"TODO\n+\n+    Attributes\n+    ----------\n+    \"\"\"\n+    def __init__(self, initial_host, n_tries=5, timeout=0.5,\n+                 initial_context={\"app_id\": 30, \"app_flags\": {}}):\n+        \"\"\"Create a new controller for a SpiNNaker machine.\n+\n+        Parameters\n+        ----------\n+        initial_host : string\n+            Hostname or IP address of the SpiNNaker chip to connect to. If the\n+            board has not yet been booted, this will become chip (0, 0).\n+        n_tries : int\n+            Number of SDP packet retransmission attempts.\n+        timeout : float\n+        initial_context : `{argument: value}`\n+            Dictionary of default arguments to pass to methods in this class.\n+        \"\"\"\n+        # Initialise the context stack\n+        ContextMixin.__init__(self, initial_context)\n+\n+        # Store the initial parameters\n+        self.initial_host = initial_host\n+        self.n_tries = n_tries\n+        self.timeout = timeout\n+        self._nn_id = 0  # ID for nearest neighbour packets\n+\n+        # Empty structs until booted, or otherwise set\n+        self.structs = {}\n+\n+        # Create the initial connection\n+        self.connections = [\n+            SCPConnection(initial_host, n_tries, timeout)\n+        ]\n+\n+    def __call__(self, **context_args):\n+        \"\"\"Create a new context for use with `with`.\n+\n+        E.g::\n+\n+            with controller(x=3, y=4):\n+                # All commands will now communicate with chip (3, 4)\n+        \"\"\"\n+        return self.get_new_context(**context_args)\n+\n+    @ContextMixin.require_named_contextual_arguments(\"x\", \"y\", \"p\")\n+    def send_scp(self, *args, **kwargs):\n+        \"\"\"Transmit an SCP Packet.\n+\n+        See the arguments for\n+        :py:method:`~rig.machine_control.scp_connection.SCPConnection` for\n+        details.\n+        \"\"\"\n+        # Retrieve contextual arguments from the keyword arguments.  The\n+        # context system ensures that these values are present.\n+        x = kwargs.pop(\"x\")\n+        y = kwargs.pop(\"y\")\n+        p = kwargs.pop(\"p\")\n+        return self._send_scp(x, y, p, *args, **kwargs)\n+\n+    def _send_scp(self, x, y, p, *args, **kwargs):\n+        \"\"\"Determine the best connection to use to send an SCP packet and use\n+        it to transmit.\n+\n+        See the arguments for\n+        :py:method:`~rig.machine_control.scp_connection.SCPConnection` for\n+        details.\n+        \"\"\"\n+        return self.connections[0].send_scp(x, y, p, *args, **kwargs)\n+\n+    def boot(self, width, height, **boot_kwargs):\n+        \"\"\"Boot a SpiNNaker machine of the given size.\n+\n+        Parameters\n+        ----------\n+        width : int\n+            Width of the machine (0 < w < 256)\n+        height : int\n+            Height of the machine (0 < h < 256)\n+\n+        For further boot arguments see\n+        :py:func:`~rig.machine_control.boot.boot`.\n+\n+        Notes\n+        -----\n+        The constants `rig.machine_control.boot.spinX_boot_options` can be used\n+        to specify boot parameters, for example::\n+\n+            controller.boot(**spin5_boot_options)\n+\n+        Will boot the Spin5 board connected to by `controller`.\n+        \"\"\"\n+        self.structs = boot.boot(self.initial_host, width=width, height=height,\n+                                 **boot_kwargs)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def get_software_version(self, x=Required, y=Required, processor=0):\n+        \"\"\"Get the software version for a given SpiNNaker core.\n+\n+        Returns\n+        -------\n+        :py:class:`CoreInfo`\n+            Information about the software running on a core.\n+        \"\"\"\n+        sver = self._send_scp(x, y, processor, SCPCommands.sver)\n+\n+        # Format the result\n+        # arg1 => p2p address, physical cpu, virtual cpu\n+        p2p = sver.arg1 >> 16\n+        pcpu = (sver.arg1 >> 8) & 0xff\n+        vcpu = sver.arg1 & 0xff\n+\n+        # arg2 => version number and buffer size\n+        version = (sver.arg2 >> 16) / 100.\n+        buffer_size = (sver.arg2 & 0xffff)\n+\n+        return CoreInfo(p2p, pcpu, vcpu, version, buffer_size, sver.arg3,\n+                        sver.data)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def write(self, address, data, x=Required, y=Required, p=0):\n+        \"\"\"Write a bytestring to an address in memory.\n+\n+        Parameters\n+        ----------\n+        address : int\n+            The address at which to start writing the data.\n+        data : :py:class:`bytes`\n+            Data to write into memory.\n+        \"\"\"\n+        # While there is still data perform a write: get the block to write\n+        # this time around, determine the data type, perform the write and\n+        # increment the address\n+        while len(data) > 0:\n+            block, data = (data[:consts.SCP_DATA_LENGTH],\n+                           data[consts.SCP_DATA_LENGTH:])\n+            dtype = address_length_dtype[(address % 4, len(block) % 4)]\n+            self._write(x, y, p, address, block, dtype)\n+            address += len(block)\n+\n+    def _write(self, x, y, p, address, data, data_type=DataType.byte):\n+        \"\"\"Write an SCP command's worth of data to an address in memory.\n+\n+        It is better to use :py:func:`~.write` which wraps this method and\n+        allows writing bytestrings of arbitrary length.\n+\n+        Parameters\n+        ----------\n+        address : int\n+            The address at which to start writing the data.\n+        data : :py:class:`bytes`\n+            Data to write into memory.  Must be <= the amount accepted by the\n+            receiving core.\n+        data_type : :py:class:`~rig.machine_control.consts.DataType`\n+            The size of the data to write into memory.\n+        \"\"\"\n+        length_bytes = len(data)\n+        self._send_scp(x, y, p, SCPCommands.write, address, length_bytes,\n+                       int(data_type), data, expected_args=0)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def read(self, address, length_bytes, x=Required, y=Required, p=0):\n+        \"\"\"Read a bytestring from an address in memory.\n+\n+        Parameters\n+        ----------\n+        address : int\n+            The address at which to start reading the data.\n+        length_bytes : int\n+            The number of bytes to read from memory.\n+\n+        Returns\n+        -------\n+        :py:class:`bytes`\n+            The data is read back from memory as a bytestring.\n+        \"\"\"\n+        # Make calls to the lower level read method until we have read\n+        # sufficient bytes.\n+        data = b''\n+        while len(data) < length_bytes:\n+            # Determine the number of bytes to read\n+            reads = min(consts.SCP_DATA_LENGTH, length_bytes - len(data))\n+\n+            # Determine the data type to use\n+            dtype = address_length_dtype[(address % 4, reads % 4)]\n+\n+            # Perform the read and increment the address\n+            data += self._read(x, y, p, address, reads, dtype)\n+            address += reads\n+\n+        return data\n+\n+    def _read(self, x, y, p, address, length_bytes, data_type=DataType.byte):\n+        \"\"\"Read an SCP command's worth of data from an address in memory.\n+\n+        It is better to use :py:func:`~.read` which wraps this method and\n+        allows reading bytestrings of arbitrary length.\n+\n+        Parameters\n+        ----------\n+        address : int\n+            The address at which to start reading the data.\n+        length_bytes : int\n+            The number of bytes to read from memory, must be <= SCP_DATA_LENGTH\n+        data_type : DataType\n+            The size of the data to write into memory.\n+\n+        Returns\n+        -------\n+        :py:class:`bytes`\n+            The data is read back from memory as a bytestring.\n+        \"\"\"\n+        read_scp = self._send_scp(x, y, p, SCPCommands.read, address,\n+                                  length_bytes, int(data_type),\n+                                  expected_args=0)\n+        return read_scp.data\n+\n+    @ContextMixin.use_contextual_arguments\n+    def read_struct_field(self, struct_name, field_name,\n+                          x=Required, y=Required, p=0):\n+        \"\"\"Read the value out of a struct maintained by SARK.\n+\n+        Parameters\n+        ----------\n+        struct_name : :py:class:`bytes`\n+            Name of the struct to read from, e.g., `b\"sv\"`\n+        field_name : :py:class:`bytes`\n+            Name of the field to read, e.g., `b\"eth_addr\"`\n+\n+        .. note::\n+            The value returned is unpacked given the struct specification.\n+\n+        .. warning::\n+            This feature is only available if this machine controller was used\n+            to boot a board OR an appropriate struct definition has been\n+            provided.  To do this use, e.g::\n+\n+                from rig.machine_controller.struct_file import read_struct_file\n+\n+                with open(\"/path/to/struct/spec\", \"rb\") as f:\n+                    data = f.read()\n+\n+                cn.structs = read_struct_file(data)\n+\n+            Currently arrays are returned as tuples, e.g.::\n+\n+                # Returns a 20-tuple.\n+                cn.read_struct_field(b\"sv\", b\"status_map\")\n+\n+                # Fails\n+                cn.read_struct_field(b\"sv\", b\"status_map[1]\")\n+        \"\"\"\n+        # Look up the struct and field\n+        field = self.structs[struct_name][field_name]\n+\n+        address = self.structs[struct_name].base + field.offset\n+        pack_chars = field.length * field.pack_chars  # NOTE Python 2 and 3 fix\n+        length = struct.calcsize(pack_chars)\n+\n+        # Perform the read\n+        data = self.read(address, length, x, y, p)\n+\n+        # Unpack the data\n+        unpacked = struct.unpack(pack_chars, data)\n+\n+        if field.length == 1:\n+            return unpacked[0]\n+        else:\n+            return unpacked\n+\n+    @ContextMixin.use_contextual_arguments\n+    def iptag_set(self, iptag, addr, port, x=Required, y=Required):\n+        \"\"\"Set the value of an IPTag.\n+\n+        Parameters\n+        ----------\n+        iptag : int\n+            Index of the IPTag to set\n+        addr : string\n+            IP address or hostname that the IPTag should point at.\n+        port : int\n+            Port that the IPTag should direct packets to.\n+        \"\"\"\n+        # Format the IP address\n+        ip_addr = struct.pack('!4B',\n+                              *map(int, socket.gethostbyname(addr).split('.')))\n+        self._send_scp(x, y, 0, SCPCommands.iptag,\n+                       int(consts.IPTagCommands.set) << 16 | iptag,\n+                       port, struct.unpack('<I', ip_addr)[0])\n+\n+    @ContextMixin.use_contextual_arguments\n+    def iptag_get(self, iptag, x=Required, y=Required):\n+        \"\"\"Get the value of an IPTag.\n+\n+        Parameters\n+        ----------\n+        iptag : int\n+            Index of the IPTag to get\n+\n+        Returns\n+        -------\n+        :py:class:`IPTag`\n+            The IPTag returned from SpiNNaker.\n+        \"\"\"\n+        ack = self._send_scp(x, y, 0, SCPCommands.iptag,\n+                             int(consts.IPTagCommands.get) << 16 | iptag, 1,\n+                             expected_args=0)\n+        return IPTag.from_bytestring(ack.data)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def iptag_clear(self, iptag, x=Required, y=Required):\n+        \"\"\"Clear an IPTag.\n+\n+        Parameters\n+        ----------\n+        iptag : int\n+            Index of the IPTag to clear.\n+        \"\"\"\n+        self._send_scp(x, y, 0, SCPCommands.iptag,\n+                       int(consts.IPTagCommands.clear) << 16 | iptag)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def set_led(self, led, action=consts.LEDAction.toggle,\n+                x=Required, y=Required):\n+        \"\"\"Toggle the state of an LED.\n+\n+        Parameters\n+        ----------\n+        led : int\n+            Number of the LED to set the state of (0-3)\n+        action : :py:class:`rig.machine_control.consts.LEDAction`\n+            Action to perform on the LED (on, off or toggle [default])\n+        \"\"\"\n+        arg1 = int(action) << (led * 2)\n+        self._send_scp(x, y, 0, SCPCommands.led, arg1=arg1, expected_args=0)\n+\n+    @ContextMixin.use_contextual_arguments\n+    def sdram_alloc(self, size, tag=0, x=Required, y=Required,\n+                    app_id=Required):\n+        \"\"\"Allocate a region of SDRAM for the given application.\n+\n+        Parameters\n+        ----------\n+        size : int\n+            Number of bytes to attempt to allocate in SDRAM.\n+        tag : int\n+            8-bit tag that can be used identify the region of memory later.  If\n+            `0` then no tag is applied.\n+\n+        Returns\n+        -------\n+        int\n+            Address of the start of the region.\n+\n+        Raises\n+        ------\n+        SpiNNakerMemoryError\n+            If the memory cannot be allocated, or the tag is already taken or\n+            invalid.\n+        \"\"\"\n+        assert 0 <= tag < 256\n+\n+        # Construct arg1 (op_code << 8) | app_id\n+        arg1 = consts.AllocOperations.alloc_sdram << 8 | app_id\n+\n+        # Send the packet and retrieve the address\n+        rv = self._send_scp(x, y, 0, SCPCommands.alloc_free, arg1, size, tag)\n+        if rv.arg1 == 0:\n+            # Allocation failed\n+            raise SpiNNakerMemoryError(size, x, y)\n+        return rv.arg1\n+\n+    @ContextMixin.use_contextual_arguments\n+    def sdram_alloc_as_io(self, size, tag=0, x=Required, y=Required,\n+                          app_id=Required):\n+        \"\"\"Like :py:meth:`.sdram_alloc` but a file-like object which allows\n+        reading and writing to the region is returned.\n+\n+        Returns\n+        -------\n+        :py:class:`~MemoryIO`\n+            File-like object which allows accessing the newly allocated region\n+            of memory.\n+\n+        Raises\n+        ------\n+        SpiNNakerMemoryError\n+            If the memory cannot be allocated, or the tag is already taken or\n+            invalid.\n+        \"\"\"\n+        # Perform the malloc\n+        start_address = self.sdram_alloc(size, tag, x, y, app_id)\n+\n+        return MemoryIO(self, x, y, start_address, start_address + size)\n+\n+    def _get_next_nn_id(self):\n+        \"\"\"Get the next nearest neighbour ID.\"\"\"\n+        self._nn_id = self._nn_id + 1 if self._nn_id < 126 else 1\n+        return self._nn_id * 2\n+\n+    def _send_ffs(self, pid, region, n_blocks, fr):\n+        \"\"\"Send a flood-fill start packet.\"\"\"\n+        sfr = fr | (1 << 31)\n+        self._send_scp(\n+            0, 0, 0, SCPCommands.nearest_neighbour_packet,\n+            (NNCommands.flood_fill_start << 24) | (pid << 16) |\n+            (n_blocks << 8),\n+            region, sfr\n+        )\n+\n+    def _send_ffd(self, pid, aplx_data, address):\n+        \"\"\"Send flood-fill data packets.\"\"\"\n+        block = 0\n+        while len(aplx_data) > 0:\n+            # Get the next block of data, send and progress the block\n+            # counter and the address\n+            data, aplx_data = (aplx_data[:consts.SCP_DATA_LENGTH],\n+                               aplx_data[consts.SCP_DATA_LENGTH:])\n+            size = len(data) // 4 - 1\n+\n+            arg1 = (NNConstants.forward << 24 | NNConstants.retry << 16 | pid)\n+            arg2 = (block << 16) | (size << 8)\n+            self._send_scp(0, 0, 0, SCPCommands.flood_fill_data,\n+                           arg1, arg2, address, data)\n+\n+            # Increment the address and the block counter\n+            block += 1\n+            address += len(data)\n+\n+    def _send_ffe(self, pid, app_id, app_flags, cores, fr):\n+        \"\"\"Send a flood-fill end packet.\"\"\"\n+        arg1 = (NNCommands.flood_fill_end << 24) | pid\n+        arg2 = (app_id << 24) | (app_flags << 18) | (cores & 0x3fff)\n+        self._send_scp(0, 0, 0, SCPCommands.nearest_neighbour_packet,\n+                       arg1, arg2, fr)\n+\n+    @ContextMixin.require_named_contextual_arguments(\"app_id\", \"app_flags\")\n+    def load_aplx(self, *args, **kwargs):\n+        \"\"\"Load an APLX to a set of application cores.","path":"rig/machine_control/machine_controller.py","position":453,"original_position":453,"commit_id":"c1f54b36bb1a3c056c3ca82db8fc4cd7549e6363","original_commit_id":"c1f54b36bb1a3c056c3ca82db8fc4cd7549e6363","user":{"login":"mossblaser","id":193514,"avatar_url":"https://avatars.githubusercontent.com/u/193514?v=3","gravatar_id":"","url":"https://api.github.com/users/mossblaser","html_url":"https://github.com/mossblaser","followers_url":"https://api.github.com/users/mossblaser/followers","following_url":"https://api.github.com/users/mossblaser/following{/other_user}","gists_url":"https://api.github.com/users/mossblaser/gists{/gist_id}","starred_url":"https://api.github.com/users/mossblaser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mossblaser/subscriptions","organizations_url":"https://api.github.com/users/mossblaser/orgs","repos_url":"https://api.github.com/users/mossblaser/repos","events_url":"https://api.github.com/users/mossblaser/events{/privacy}","received_events_url":"https://api.github.com/users/mossblaser/received_events","type":"User","site_admin":false},"body":"I'd be tempted to make the \"safe\" loading of aphlexen a wrapper function which calls this with wait enabled and then counts the waits.","created_at":"2015-03-05T09:09:20Z","updated_at":"2015-03-05T09:09:20Z","html_url":"https://github.com/project-rig/rig/pull/29#discussion_r25848830","pull_request_url":"https://api.github.com/repos/project-rig/rig/pulls/29","_links":{"self":{"href":"https://api.github.com/repos/project-rig/rig/pulls/comments/25848830"},"html":{"href":"https://github.com/project-rig/rig/pull/29#discussion_r25848830"},"pull_request":{"href":"https://api.github.com/repos/project-rig/rig/pulls/29"}}},"pull_request":{"url":"https://api.github.com/repos/project-rig/rig/pulls/29","id":30086833,"html_url":"https://github.com/project-rig/rig/pull/29","diff_url":"https://github.com/project-rig/rig/pull/29.diff","patch_url":"https://github.com/project-rig/rig/pull/29.patch","issue_url":"https://api.github.com/repos/project-rig/rig/issues/29","number":29,"state":"open","locked":false,"title":"Refactor of SCP code","user":{"login":"mossblaser","id":193514,"avatar_url":"https://avatars.githubusercontent.com/u/193514?v=3","gravatar_id":"","url":"https://api.github.com/users/mossblaser","html_url":"https://github.com/mossblaser","followers_url":"https://api.github.com/users/mossblaser/followers","following_url":"https://api.github.com/users/mossblaser/following{/other_user}","gists_url":"https://api.github.com/users/mossblaser/gists{/gist_id}","starred_url":"https://api.github.com/users/mossblaser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mossblaser/subscriptions","organizations_url":"https://api.github.com/users/mossblaser/orgs","repos_url":"https://api.github.com/users/mossblaser/repos","events_url":"https://api.github.com/users/mossblaser/events{/privacy}","received_events_url":"https://api.github.com/users/mossblaser/received_events","type":"User","site_admin":false},"body":"- [x] Application loading\r\n  - [x] Generating regions\r\n    - [x] Use lazy tree structure  -- __AM__: I don't remember this being a _requirement_\r\n    - [x] Have insane amounts of fun doing so -- This, of course, is\r\n  - [x] Loading from sets of applications\r\n  - [ ] **IMPORTANT** Test APLX and test suite with live board\r\n- [ ] Signal applications\r\n - [x] Change state\r\n - [ ] Query state\r\n- [ ] Load and retrieve router table entries\r\n - [x] Load\r\n - [ ] Retrieve\r\n- [x] Utility method for _reading_ struct values.","created_at":"2015-02-26T15:29:01Z","updated_at":"2015-03-05T09:09:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9e329becaa81e07b8e664ada7fbc1768e7e7f142","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/project-rig/rig/pulls/29/commits","review_comments_url":"https://api.github.com/repos/project-rig/rig/pulls/29/comments","review_comment_url":"https://api.github.com/repos/project-rig/rig/pulls/comments{/number}","comments_url":"https://api.github.com/repos/project-rig/rig/issues/29/comments","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/c1f54b36bb1a3c056c3ca82db8fc4cd7549e6363","head":{"label":"project-rig:scp_refactor2","ref":"scp_refactor2","sha":"c1f54b36bb1a3c056c3ca82db8fc4cd7549e6363","user":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"repo":{"id":29497314,"name":"rig","full_name":"project-rig/rig","owner":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/project-rig/rig","description":"A set of tools for rigging SpiNNaker programs.","fork":false,"url":"https://api.github.com/repos/project-rig/rig","forks_url":"https://api.github.com/repos/project-rig/rig/forks","keys_url":"https://api.github.com/repos/project-rig/rig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/project-rig/rig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/project-rig/rig/teams","hooks_url":"https://api.github.com/repos/project-rig/rig/hooks","issue_events_url":"https://api.github.com/repos/project-rig/rig/issues/events{/number}","events_url":"https://api.github.com/repos/project-rig/rig/events","assignees_url":"https://api.github.com/repos/project-rig/rig/assignees{/user}","branches_url":"https://api.github.com/repos/project-rig/rig/branches{/branch}","tags_url":"https://api.github.com/repos/project-rig/rig/tags","blobs_url":"https://api.github.com/repos/project-rig/rig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/project-rig/rig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/project-rig/rig/git/refs{/sha}","trees_url":"https://api.github.com/repos/project-rig/rig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/{sha}","languages_url":"https://api.github.com/repos/project-rig/rig/languages","stargazers_url":"https://api.github.com/repos/project-rig/rig/stargazers","contributors_url":"https://api.github.com/repos/project-rig/rig/contributors","subscribers_url":"https://api.github.com/repos/project-rig/rig/subscribers","subscription_url":"https://api.github.com/repos/project-rig/rig/subscription","commits_url":"https://api.github.com/repos/project-rig/rig/commits{/sha}","git_commits_url":"https://api.github.com/repos/project-rig/rig/git/commits{/sha}","comments_url":"https://api.github.com/repos/project-rig/rig/comments{/number}","issue_comment_url":"https://api.github.com/repos/project-rig/rig/issues/comments{/number}","contents_url":"https://api.github.com/repos/project-rig/rig/contents/{+path}","compare_url":"https://api.github.com/repos/project-rig/rig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/project-rig/rig/merges","archive_url":"https://api.github.com/repos/project-rig/rig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/project-rig/rig/downloads","issues_url":"https://api.github.com/repos/project-rig/rig/issues{/number}","pulls_url":"https://api.github.com/repos/project-rig/rig/pulls{/number}","milestones_url":"https://api.github.com/repos/project-rig/rig/milestones{/number}","notifications_url":"https://api.github.com/repos/project-rig/rig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/project-rig/rig/labels{/name}","releases_url":"https://api.github.com/repos/project-rig/rig/releases{/id}","created_at":"2015-01-19T21:20:40Z","updated_at":"2015-02-27T22:24:14Z","pushed_at":"2015-03-04T17:27:21Z","git_url":"git://github.com/project-rig/rig.git","ssh_url":"git@github.com:project-rig/rig.git","clone_url":"https://github.com/project-rig/rig.git","svn_url":"https://github.com/project-rig/rig","homepage":"","size":976,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"base":{"label":"project-rig:master","ref":"master","sha":"cbbc13dfc286685109fbf8715b8d4cc895609c1c","user":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"repo":{"id":29497314,"name":"rig","full_name":"project-rig/rig","owner":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/project-rig/rig","description":"A set of tools for rigging SpiNNaker programs.","fork":false,"url":"https://api.github.com/repos/project-rig/rig","forks_url":"https://api.github.com/repos/project-rig/rig/forks","keys_url":"https://api.github.com/repos/project-rig/rig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/project-rig/rig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/project-rig/rig/teams","hooks_url":"https://api.github.com/repos/project-rig/rig/hooks","issue_events_url":"https://api.github.com/repos/project-rig/rig/issues/events{/number}","events_url":"https://api.github.com/repos/project-rig/rig/events","assignees_url":"https://api.github.com/repos/project-rig/rig/assignees{/user}","branches_url":"https://api.github.com/repos/project-rig/rig/branches{/branch}","tags_url":"https://api.github.com/repos/project-rig/rig/tags","blobs_url":"https://api.github.com/repos/project-rig/rig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/project-rig/rig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/project-rig/rig/git/refs{/sha}","trees_url":"https://api.github.com/repos/project-rig/rig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/{sha}","languages_url":"https://api.github.com/repos/project-rig/rig/languages","stargazers_url":"https://api.github.com/repos/project-rig/rig/stargazers","contributors_url":"https://api.github.com/repos/project-rig/rig/contributors","subscribers_url":"https://api.github.com/repos/project-rig/rig/subscribers","subscription_url":"https://api.github.com/repos/project-rig/rig/subscription","commits_url":"https://api.github.com/repos/project-rig/rig/commits{/sha}","git_commits_url":"https://api.github.com/repos/project-rig/rig/git/commits{/sha}","comments_url":"https://api.github.com/repos/project-rig/rig/comments{/number}","issue_comment_url":"https://api.github.com/repos/project-rig/rig/issues/comments{/number}","contents_url":"https://api.github.com/repos/project-rig/rig/contents/{+path}","compare_url":"https://api.github.com/repos/project-rig/rig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/project-rig/rig/merges","archive_url":"https://api.github.com/repos/project-rig/rig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/project-rig/rig/downloads","issues_url":"https://api.github.com/repos/project-rig/rig/issues{/number}","pulls_url":"https://api.github.com/repos/project-rig/rig/pulls{/number}","milestones_url":"https://api.github.com/repos/project-rig/rig/milestones{/number}","notifications_url":"https://api.github.com/repos/project-rig/rig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/project-rig/rig/labels{/name}","releases_url":"https://api.github.com/repos/project-rig/rig/releases{/id}","created_at":"2015-01-19T21:20:40Z","updated_at":"2015-02-27T22:24:14Z","pushed_at":"2015-03-04T17:27:21Z","git_url":"git://github.com/project-rig/rig.git","ssh_url":"git@github.com:project-rig/rig.git","clone_url":"https://github.com/project-rig/rig.git","svn_url":"https://github.com/project-rig/rig","homepage":"","size":976,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/project-rig/rig/pulls/29"},"html":{"href":"https://github.com/project-rig/rig/pull/29"},"issue":{"href":"https://api.github.com/repos/project-rig/rig/issues/29"},"comments":{"href":"https://api.github.com/repos/project-rig/rig/issues/29/comments"},"review_comments":{"href":"https://api.github.com/repos/project-rig/rig/pulls/29/comments"},"review_comment":{"href":"https://api.github.com/repos/project-rig/rig/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/project-rig/rig/pulls/29/commits"},"statuses":{"href":"https://api.github.com/repos/project-rig/rig/statuses/c1f54b36bb1a3c056c3ca82db8fc4cd7549e6363"}}}}