Setting up a Raspberry Pi with Python 3.7+ using Ansible

Here’s a set of files to automatically setup bricknil on a Raspberry Pi. These Ansible playbooks will help you take a clean Raspbian image and compile Python 3.7+ along with all the Bricknil required libraries. You just need a Raspberry Pi; these instructions do not require you to plug in a monitor and works over Wifi. This compilation of Python is necessary because the raspbian distro does not ship with the latest version of Python at the time of this writing. And it took me a little bit of effort to figure out how to compile Python with the SSL libraries (so pip works), so I hope this might be of use to some folks.

The steps required are listed below in order, and you can find the Ansible yaml playbooks in the Bricknil source under pi_setup.

Steps

Download a Raspbian image and burn it to a SD card

  1. Download the Raspbian Stretch Lite image

  2. Burn it to a suitable SD card. On OS X, Balena Etcher is a good tool to do this.

  3. On the newly imaged SD card’s /boot folder:

    1. Make an empty file called ssh to enable SSH access.

    2. Make wpa_supplicant.conf file to supply your wifi credentials:

      country=US
      ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
      update_config=1
      
      network={
            ssid="NETWORK_NAME
            psk="WIFI PASSWORD"
      }
      
  4. Now, you should be able to use this SD card to boot your raspberry pi. After giving it a few minutes to boot, you can login in by doing the following on your local machine:

    ssh pi@raspberrypi.local
    <enter 'raspberry' as the default password
    

Install Ansbile on your local machine

You can just type pip install ansible

Change the default password on the Pi

  1. You may need to modify the hosts file in pi_setup directory to put in your own IP address for the Pi.

  2. You will need to install the python library passlib by running pip install passlib on your local machine (not the Pi)

  3. Use the change_password.yml playbook to test your ansible install and change the default password on the pi (use raspberry for the ssh password when prompted, and then enter your new password):

    ansible-playbook -i hosts change_password.yml --ask-pass
    

    The playbook being run is simply:

- hosts: all
  remote_user: pi
  become_method: sudo
  vars_prompt:
      - name: new_password
        prompt: "Enter the password you would like to use for the user pi"
        confirm: yes

  tasks:
    - name: backup shadow file
      copy:
        remote_src: yes
        src: /etc/shadow
        dest: /tmp/shadow
      become: yes

    - name: generate hash pass
      delegate_to: localhost
      command:  python -c "from passlib.hash import md5_crypt; import getpass; print (md5_crypt.hash('{{new_password}}'))"
      register: hash

    - debug:
        var: hash.stdout

    - name: update password
      user:
        name: pi
        password:  '{{hash.stdout}}'
      become: yes

Run the setup playbook

  1. The final step is to just run the the install playbook. This will install all the packages, download Python 3.7, compile it, and set up a virtualenv to run bricknil from. The actual playbook is in tasks.yml, shown below and included in the source, and the command to execute it on your local machine is:

    ansible-playbook -i hosts tasks.yml --ask-pass
    

    The install will take around two hours, so please be patient. Most of the time is spent compiling and installing Python from source. Here’s the playbook being executed:

    - hosts: all
      remote_user: pi
      become_method: sudo
      vars:
        PYTHON: "3.7.2"
        OPEN_SSL: openssl-1.0.2g
        build_path: "{{ansible_env.HOME}}/build"
        HOME: "{{ansible_env.HOME}}"
    
      tasks:
        - name: Update APT package cache
          apt: update_cache=yes
          become: yes
    
        - name: Update packages
          apt: upgrade=dist
          become: yes
    
        - name: Install build-essential
          package: name="build-essential" state=present
          become: yes
    
        - name: Install build packages
          apt: 
              state: present
              name: 
                  - tk-dev
                  - libncurses5-dev 
                  - libncursesw5-dev 
                  - libreadline6-dev 
                  - libdb5.3-dev 
                  - libgdbm-dev 
                  - libsqlite3-dev 
                  - libssl-dev 
                  - libbz2-dev 
                  - libexpat1-dev 
                  - liblzma-dev 
                  - zlib1g-dev 
                  - libffi-dev
                  - uuid-dev
                  - vim
                  - git
                  - python-pip
                  - screen
          become: yes
          
        - name: Ensure build directory exists
          file:
              path: "{{ build_path }}"
              state: directory
              recurse: yes
    
        - name: Download openssl
          get_url:
              url: "https://www.openssl.org/source/{{ OPEN_SSL }}.tar.gz"
              dest: "{{ build_path }}/{{ OPEN_SSL }}.tar.gz"
              mode: "755"
          register: openssl_download
    
        - name: Untar openssl
          unarchive:
              remote_src: yes
              src: "{{ build_path }}/{{ OPEN_SSL }}.tar.gz"
              dest: "{{ build_path }}"
          when: openssl_download.changed
    
        - name: configure openssl
          command: ./config shared --prefix=/usr/local
          args: 
              chdir: "{{ build_path }}/{{ OPEN_SSL }}"
          when: openssl_download.changed
    
        - name: make openssl
          command: make -j 4
          args: 
              chdir: "{{ build_path }}/{{ OPEN_SSL }}"
          register: make_openssl
          when: openssl_download.changed
    
        - name: install openssl
          command: make install
          args:
              chdir: "{{ build_path }}/{{ OPEN_SSL }}"
          become: yes
          when: make_openssl.changed
    
        - name: set /etc/ld.so.conf.d to add /usr/local/
          lineinfile:
              path: /etc/ld.so.conf.d/openssl.conf
              create: yes
              line: "/usr/local/lib"
          become: yes
    
        - name: run ldconfig to update dynamic link lib path to use /usr/local/lib
          command: ldconfig
          become: yes
    
        - name: Download python 3.7
          get_url:
              url: "https://www.python.org/ftp/python/{{PYTHON}}/Python-{{PYTHON}}.tgz"
              dest: "{{ build_path }}/Python-{{ PYTHON }}.tgz"
              mode: "755"
          register: python_download
    
        - name: Untar python
          unarchive:
              remote_src: yes
              src: "{{ build_path }}/Python-{{ PYTHON }}.tgz"
              dest: "{{ build_path }}"
          when: python_download.changed
          
        - name: Uncomment SSL
          blockinfile:
              path: "{{ build_path}}/Python-{{ PYTHON }}/Modules/Setup.dist"
              insertbefore: "#SSL=/usr/local/ssl"
              block: |
                  SSL=/usr/local
                  _ssl _ssl.c \
                      -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
                          -L$(SSL)/lib -lssl -lcrypto
    
        - name: configure python
          command: ./configure --enable-optimizations --with-openssl=/usr/local --prefix=/usr/local
          args: 
              chdir: "{{ build_path }}/Python-{{ PYTHON }}"
          when: python_download.changed
          register: python_configure
    
        - name: make python
          shell: 'make -j 4'
          args: 
              chdir: "{{ build_path }}/Python-{{ PYTHON }}"
          register: make_python
          when: python_configure.changed
    
        - name: install python
          shell: 'make altinstall'
          args:
              chdir: "{{ build_path }}/Python-{{ PYTHON }}"
          become: yes
          when: python_configure.changed
    
        - name: Setup .bashrc
          blockinfile:
              path: "{{ HOME }}/.bashrc"
              block: |
                  export VIRTUALENVWRAPPER_PYTHON="/usr/local/bin/python3.7"
                  source /usr/local/bin/virtualenvwrapper.sh
    
        - name: Install pip virtualenv
          pip: 
            executable: /usr/local/bin/pip3.7
            name:
                - virtualenv
                - virtualenvwrapper
          become: yes
    
        - name: pip install bricknil
          pip: 
            name:
                - bricknil
            virtualenv: "{{ HOME }}/.virtualenvs/bricknil"
            virtualenv_python: python3.7
    
        - name: git clone bricknil
          git:
              repo: 'https://github.com/virantha/bricknil.git'
              dest: '{{ HOME }}/bricknil'
              clone: yes
    
  2. And that’s it! Your raspberry pi should be ready to go as a networked appliance to run your Lego controller scripts from.

Running Bricknil examples on the Pi

  1. The script installs a virtualenv called bricknil, so you can activate it in the normal way to get access to Python 3.7+ and the python dependencies for running bricknil.

  2. The script also installed the bricknil source from github, so all the examples should be ready to go. Just ssh into the Pi and:

    cd bricknil
    
  3. On linux, you need to run as sudo to access the bluetooth libraries; after you login to the Pi over ssh you can do the following to run the virtualenv installed Python as sudo:

    sudo ~/.virtualenvs/bricknil/bin/python examples/train_all.py