├── LICENSE.md ├── README.md ├── install.sh ├── jetcard ├── __init__.py ├── create_display_service.py ├── create_jupyter_service.py ├── display_server.py ├── heartbeat.py ├── stats.py └── utils.py ├── run-install-and-save-log.sh ├── scripts ├── archive │ ├── nvresizefs.sh │ └── system │ │ └── nvresizefs.service ├── host_make_expandable_image.sh ├── jetson_add_ntp_server.sh ├── jetson_fix_usb_device_mode.sh └── jetson_install_nvresizefs_service.sh └── setup.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JetCard 2 | 3 | JetCard is a system configuration that makes it easy to get started with AI. It comes pre-loaded with 4 | 5 | * A Jupyter Lab server that starts on boot for easy web programming 6 | 7 | * A script to display the Jetson's IP address (and other stats) 8 | * The popular deep learning frameworks PyTorch and TensorFlow 9 | 10 | After configuring your system using JetCard, you can get started prototyping AI projects from your web browser in Python. 11 | 12 | If you find an issue, please [let us know](../..//issues)! 13 | 14 | ## Setup 15 | 16 | Follow the steps below to download JetCard directly or create it from scratch. 17 | 18 | ### Option 1 - Download JetCard directly 19 | 20 | 1. Download a JetCard SD card image listed in below table onto a Windows, Linux or Mac *desktop machine* 21 | 2. Insert a 32GB+ SD card into the desktop machine 22 | 3. Using [Etcher](https://www.balena.io/etcher/) select the downloaded zip file and flash it onto the SD card 23 | 4. Remove the SD card from the desktop machinem the desktop machine 24 | 25 | You may now insert the SD card into the Jetson Nano, power on, and enjoy the pre-configured system! 26 | 27 | #### Latest Release (** **but not yet fully verified** ** ) 28 | 29 | > Please note, the password for the pre-built SD card is ``jetson`` 30 | 31 | | Platform | Board revision | JetPack Version | Download | MD5 Checksum | branch | 32 | | -------- | -------------- | --------------- | -------- |------------- | ------ | 33 | | Jetson Nano (4GB) | `A02` and `B01` | 4.5.1 | [jetcard_nano-4gb-jp451.zip](https://nvidia.box.com/shared/static/6glxmdasyhsk1d85o8koojwqcytdx0ha.zip) 34 | 35 | #### Old Release 36 | 37 | > Please note that this image is only for the older `A02` revision of Jetson Nano board, which has only one camera (CSI) connector onboard. 38 | 39 | | Platform | Board revision | JetPack Version | Download | MD5 Checksum | branch | 40 | | -------- | -------------- | --------------- | -------- |------------- | ------ | 41 | | Jetson Nano (4GB) | `A02` | 4.2| jetcard_v0p0p0.zip | f7b635a651e4a2228e3812360cce74e3 | [`jetpack_4.2`](https://github.com/NVIDIA-AI-IOT/jetcard/tree/jetpack_4.2) 42 | 43 | ### Option 2 - Create JetCard from scratch 44 | 45 | 1. Flash Jetson Nano following the [Getting Started Guide](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit) 46 | 47 | > For Jetson TX2 / Xavier, use the [JetPack](https://developer.nvidia.com/embedded/jetpack) SDK manager 48 | 49 | 2. On the Jetson, run the JetCard installation script 50 | 51 | ```bash 52 | git clone https://github.com/NVIDIA-AI-IOT/jetcard 53 | cd jetcard 54 | ./install.sh 55 | ``` 56 | 57 | Once the ``install.sh`` script finishes, your system should be configured identically to the SD card image mentioned above. 58 | 59 | ## Usage 60 | 61 | ### Connecting 62 | 63 | Pick an option below and follow the instructions to begin web programming Jetson from a desktop computer using Jupyter Lab. 64 | 65 | #### Option 1 - Ethernet / WiFi 66 | 67 | 1. Power on the Jetson platform configured using JetCard 68 | 69 | 2. Connect the Jetson to the same network as your desktop computer via Ethernet or WiFi 70 | 71 | > If you want to connect your Jetson to WiFi, but don't have a monitor and keyboard, you can connect via device mode (see below), open a terminal, and then use the ``nmcli`` tool to connect to a WiFi network. Find more details [below](#extras). 72 | 73 | 3. Determine the IP address ``jetson_ip_address`` 74 | 75 | > If you have the PiOLED display attached, it will display on that screen. Otherwise, you will need to connect a monitor, open a terminal, and read the IP using ``ifconfig``. 76 | 4. Connect to the Jetson platform from a desktop computer by navigating to ``http://:8888`` 77 | 5. Sign in using the default password ``jetson`` 78 | 79 | #### Option 2 - USB device mode 80 | 81 | If you do not occupy the Jetson Nano's micro USB port for power, you can use it to connect directly from a desktop PC! The USB device mode IP address is ``192.168.55.1`` 82 | 83 | 1. Power on the Jetson platform configured using JetCard 84 | 85 | 2. Connect the Jetson platform to the desktop computer via micro USB 86 | 3. On the desktop computer, navigate to ``http://192.168.55.1:8888`` from a web browser 87 | 4. Sign in using the default password ``jetson`` 88 | 89 | ## Extras 90 | 91 | ### Connect to WiFi from terminal 92 | 93 | To connect your Jetson to a WiFi network from a terminal, follow these steps 94 | 95 | 1. Re-scan available WiFi networks 96 | 97 | ```bash 98 | nmcli device wifi rescan 99 | ``` 100 | 101 | 2. List available WiFi networks, and find the ``ssid_name`` of your network. 102 | 103 | ```bash 104 | nmcli device wifi list 105 | ``` 106 | 3. Connect to a selected WiFi network 107 | 108 | ```bash 109 | nmcli device wifi connect password 110 | ``` 111 | 112 | ### Create SD card snapshot 113 | 114 | If you've applied modifications to the base SD card image that you want to re-use, do the following to create a compressed SD card image 115 | 116 | 1. Remove the SD card from your Jetson Nano 117 | 118 | 2. Insert the SD card into a Linux host computer 119 | 3. Determine where the SD card is located using ``sudo fdisk -l``. We'll assume this is at ``/dev/sdb`` 120 | 4. Copy the contents of the SD card to a file named ``jetcard_image.img`` 121 | 122 | ```bash 123 | sudo dd bs=4M if=/dev/sdb of=jetcard_image.img status=progress 124 | ``` 125 | 5. Compress the SD card image using zip 126 | 127 | ```bash 128 | zip jetcard_image.zip jetcard_image.img 129 | ``` 130 | 131 | ## See also 132 | 133 | - [JetBot](http://github.com/NVIDIA-AI-IOT/jetbot) - An educational AI robot based on NVIDIA Jetson Nano 134 | 135 | - [JetRacer](http://github.com/NVIDIA-AI-IOT/jetracer) - An educational AI racecar using NVIDIA Jetson Nano 136 | - [JetCam](http://github.com/NVIDIA-AI-IOT/jetcam) - An easy to use Python camera interface for NVIDIA Jetson 137 | - [torch2trt](http://github.com/NVIDIA-AI-IOT/torch2trt) - An easy to use PyTorch to TensorRT converter 138 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | password='jetson' 6 | 7 | # Record the time this script starts 8 | date 9 | 10 | # Get the full dir name of this script 11 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 12 | 13 | # Keep updating the existing sudo time stamp 14 | sudo -v 15 | while true; do sudo -n true; sleep 120; kill -0 "$$" || exit; done 2>/dev/null & 16 | 17 | # Install pip and some python dependencies 18 | echo "\e[104m Install pip and some python dependencies \e[0m" 19 | sudo apt-get update 20 | sudo apt install -y python3-pip python3-setuptools python3-pil python3-smbus python3-matplotlib cmake curl 21 | sudo -H pip3 install --upgrade pip 22 | 23 | # Install jtop 24 | echo "\e[100m Install jtop \e[0m" 25 | sudo -H pip3 install jetson-stats 26 | 27 | 28 | 29 | # Install the pre-built PyTorch pip wheel 30 | echo "\e[45m Install the pre-built PyTorch pip wheel \e[0m" 31 | cd 32 | wget -N https://nvidia.box.com/shared/static/9eptse6jyly1ggt9axbja2yrmj6pbarc.whl -O torch-1.6.0-cp36-cp36m-linux_aarch64.whl 33 | sudo apt-get install -y python3-pip libopenblas-base libopenmpi-dev 34 | sudo -H pip3 install Cython 35 | sudo -H pip3 install numpy==1.19.4 torch-1.6.0-cp36-cp36m-linux_aarch64.whl 36 | 37 | # Install torchvision package 38 | echo "\e[45m Install torchvision package \e[0m" 39 | cd 40 | git clone https://github.com/pytorch/vision torchvision 41 | cd torchvision 42 | sudo apt-get install -y libavcodec-dev libavformat-dev libswscale-dev 43 | git checkout tags/v0.7.0 44 | sudo -H python3 setup.py install 45 | cd ../ 46 | sudo -H pip3 install pillow 47 | 48 | # pip dependencies for pytorch-ssd 49 | echo "\e[45m Install dependencies for pytorch-ssd \e[0m" 50 | sudo -H pip3 install --verbose --upgrade Cython && \ 51 | sudo -H pip3 install --verbose boto3 pandas 52 | 53 | 54 | 55 | # Install the pre-built TensorFlow pip wheel 56 | echo "\e[48;5;202m Install the pre-built TensorFlow pip wheel \e[0m" 57 | sudo apt-get update 58 | sudo apt-get install -y libhdf5-serial-dev hdf5-tools libhdf5-dev zlib1g-dev zip libjpeg8-dev liblapack-dev libblas-dev gfortran 59 | 60 | sudo apt-get install -y python3-pip 61 | sudo -H pip3 install -U pip testresources setuptools==49.6.0 62 | sudo -H pip3 install -U numpy==1.19.4 future==0.18.2 mock==3.0.5 h5py==2.10.0 keras_preprocessing==1.1.1 keras_applications==1.0.8 gast==0.2.2 futures protobuf pybind11 63 | sudo -H pip3 install --pre --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v45 'tensorflow<2' 64 | 65 | # Install TensorFlow models repository 66 | echo "\e[48;5;202m Install TensorFlow models repository \e[0m" 67 | cd 68 | url="https://github.com/tensorflow/models" 69 | tf_models_dir="TF-models" 70 | if [ ! -d "$tf_models_dir" ] ; then 71 | git clone $url $tf_models_dir 72 | cd "$tf_models_dir"/research 73 | git checkout 5f4d34fc 74 | wget -O protobuf.zip https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-aarch_64.zip 75 | # wget -O protobuf.zip https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_64.zip 76 | unzip protobuf.zip 77 | ./bin/protoc object_detection/protos/*.proto --python_out=. 78 | sudo -H python3 setup.py install 79 | cd slim 80 | sudo -H python3 setup.py install 81 | fi 82 | 83 | 84 | 85 | # Install traitlets (master, to support the unlink() method) 86 | echo "\e[48;5;172m Install traitlets \e[0m" 87 | #sudo -H python3 -m pip install git+https://github.com/ipython/traitlets@master 88 | sudo -H python3 -m pip install git+https://github.com/ipython/traitlets@dead2b8cdde5913572254cf6dc70b5a6065b86f8 89 | 90 | # Install JupyterLab (lock to 2.2.6, latest as of Sept 2020) 91 | echo "\e[48;5;172m Install Jupyter Lab 2.2.6 \e[0m" 92 | curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 93 | sudo apt install -y nodejs libffi-dev libssl1.0-dev 94 | sudo -H pip3 install jupyter jupyterlab==2.2.6 --verbose 95 | sudo -H jupyter labextension install @jupyter-widgets/jupyterlab-manager 96 | 97 | jupyter lab --generate-config 98 | python3 -c "from notebook.auth.security import set_password; set_password('$password', '$HOME/.jupyter/jupyter_notebook_config.json')" 99 | 100 | 101 | # Install jupyter_clickable_image_widget 102 | echo "\e[42m Install jupyter_clickable_image_widget \e[0m" 103 | cd 104 | git clone https://github.com/jaybdub/jupyter_clickable_image_widget 105 | cd jupyter_clickable_image_widget 106 | git checkout tags/v0.1 107 | sudo -H pip3 install -e . 108 | sudo -H jupyter labextension install js 109 | sudo -H jupyter lab build 110 | 111 | # fix for permission error 112 | sudo chown -R jetson:jetson /usr/local/share/jupyter/lab/settings/build_config.json 113 | 114 | # install version of traitlets with dlink.link() feature 115 | # (added after 4.3.3 and commits after the one below only support Python 3.7+) 116 | # 117 | sudo -H python3 -m pip install git+https://github.com/ipython/traitlets@dead2b8cdde5913572254cf6dc70b5a6065b86f8 118 | sudo -H jupyter lab build 119 | 120 | 121 | # ================= 122 | # INSTALL jetcam 123 | # ================= 124 | cd $HOME 125 | git clone https://github.com/NVIDIA-AI-IOT/jetcam 126 | cd jetcam 127 | sudo -H python3 setup.py install 128 | 129 | # ================= 130 | # INSTALL torch2trt 131 | # ================= 132 | cd 133 | git clone https://github.com/NVIDIA-AI-IOT/torch2trt 134 | cd torch2trt 135 | sudo -H python3 setup.py install --plugins 136 | 137 | # ================= 138 | # INSTALL jetracer 139 | # ================= 140 | cd $HOME 141 | git clone https://github.com/NVIDIA-AI-IOT/jetracer 142 | cd jetracer 143 | sudo -H python3 setup.py install 144 | 145 | # ======================================== 146 | # Install other misc packages for trt_pose 147 | # ======================================== 148 | sudo -H pip3 install tqdm cython pycocotools 149 | sudo apt-get install python3-matplotlib 150 | sudo -H pip3 install traitlets 151 | sudo -H pip3 install -U scikit-learn 152 | 153 | # ============================================== 154 | # Install other misc packages for point_detector 155 | # ============================================== 156 | sudo -H pip3 install tensorboard 157 | sudo -H pip3 install segmentation-models-pytorch 158 | 159 | 160 | # Install jetcard 161 | echo "\e[44m Install jetcard \e[0m" 162 | cd $DIR 163 | pwd 164 | sudo apt-get install python3-pip python3-setuptools python3-pil python3-smbus 165 | sudo -H pip3 install flask 166 | sudo -H python3 setup.py install 167 | 168 | # Install jetcard display service 169 | echo "\e[44m Install jetcard display service \e[0m" 170 | python3 -m jetcard.create_display_service 171 | sudo mv jetcard_display.service /etc/systemd/system/jetcard_display.service 172 | sudo systemctl enable jetcard_display 173 | sudo systemctl start jetcard_display 174 | 175 | # Install jetcard jupyter service 176 | echo "\e[44m Install jetcard jupyter service \e[0m" 177 | python3 -m jetcard.create_jupyter_service 178 | sudo mv jetcard_jupyter.service /etc/systemd/system/jetcard_jupyter.service 179 | sudo systemctl enable jetcard_jupyter 180 | sudo systemctl start jetcard_jupyter 181 | 182 | # Make swapfile 183 | echo "\e[46m Make swapfile \e[0m" 184 | cd 185 | if [ ! -f /var/swapfile ]; then 186 | sudo fallocate -l 4G /var/swapfile 187 | sudo chmod 600 /var/swapfile 188 | sudo mkswap /var/swapfile 189 | sudo swapon /var/swapfile 190 | sudo bash -c 'echo "/var/swapfile swap swap defaults 0 0" >> /etc/fstab' 191 | else 192 | echo "Swapfile already exists" 193 | fi 194 | 195 | 196 | 197 | # Install remaining dependencies for projects 198 | echo "\e[104m Install remaining dependencies for projects \e[0m" 199 | sudo apt-get install python-setuptools 200 | 201 | 202 | 203 | echo "\e[42m All done! \e[0m" 204 | 205 | #record the time this script ends 206 | date 207 | 208 | -------------------------------------------------------------------------------- /jetcard/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVIDIA-AI-IOT/jetcard/5cb6816ba87a0b5552392bbf900a09739eb31fcf/jetcard/__init__.py -------------------------------------------------------------------------------- /jetcard/create_display_service.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import getpass 3 | import os 4 | 5 | STATS_SERVICE_TEMPLATE = """ 6 | [Unit] 7 | Description=JetCard display service 8 | 9 | [Service] 10 | Type=simple 11 | User=%s 12 | ExecStart=/bin/sh -c "python3 -m jetcard.display_server" 13 | WorkingDirectory=%s 14 | Restart=always 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | """ 19 | 20 | STATS_SERVICE_NAME = 'jetcard_display' 21 | 22 | 23 | def get_stats_service(): 24 | return STATS_SERVICE_TEMPLATE % ("root", os.environ['HOME']) 25 | 26 | 27 | if __name__ == '__main__': 28 | parser = argparse.ArgumentParser() 29 | parser.add_argument('--output', default='jetcard_display.service') 30 | args = parser.parse_args() 31 | 32 | with open(args.output, 'w') as f: 33 | f.write(get_stats_service()) 34 | -------------------------------------------------------------------------------- /jetcard/create_jupyter_service.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import getpass 3 | import os 4 | 5 | 6 | JUPYTER_SERVICE_TEMPLATE = """ 7 | [Unit] 8 | Description=Jupyter Notebook Service 9 | 10 | [Service] 11 | Type=simple 12 | User=%s 13 | ExecStart=/bin/sh -c "jupyter lab --ip=0.0.0.0 --no-browser" 14 | WorkingDirectory=%s 15 | Restart=always 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | """ 20 | 21 | 22 | JUPYTER_SERVICE_NAME = 'jetcard_jupyter' 23 | 24 | 25 | def get_jupyter_service(working_directory): 26 | assert(os.path.isdir(working_directory)) 27 | service_str = JUPYTER_SERVICE_TEMPLATE % (getpass.getuser(), working_directory) 28 | return service_str 29 | 30 | 31 | if __name__ == '__main__': 32 | 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument( 35 | '--working_directory', 36 | type=str, 37 | help='The directory for Jupyter Lab', 38 | default=os.path.expanduser('~')) 39 | parser.add_argument('--output', default='jetcard_jupyter.service') 40 | args = parser.parse_args() 41 | 42 | with open(args.output, 'w') as f: 43 | f.write(get_jupyter_service(args.working_directory)) 44 | -------------------------------------------------------------------------------- /jetcard/display_server.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import Adafruit_SSD1306 3 | import time 4 | import PIL.Image 5 | import PIL.ImageFont 6 | import PIL.ImageDraw 7 | from flask import Flask 8 | from .utils import ip_address, power_mode, power_usage, cpu_usage, gpu_usage, memory_usage, disk_usage 9 | 10 | 11 | class DisplayServer(object): 12 | 13 | def __init__(self, *args, **kwargs): 14 | self.display = Adafruit_SSD1306.SSD1306_128_32(rst=None, i2c_bus=1, gpio=1) 15 | self.display.begin() 16 | self.display.clear() 17 | self.display.display() 18 | self.font = PIL.ImageFont.load_default() 19 | self.image = PIL.Image.new('1', (self.display.width, self.display.height)) 20 | self.draw = PIL.ImageDraw.Draw(self.image) 21 | self.draw.rectangle((0, 0, self.image.width, self.image.height), outline=0, fill=0) 22 | self.stats_enabled = False 23 | self.stats_thread = None 24 | self.stats_interval = 1.0 25 | self.enable_stats() 26 | 27 | def _run_display_stats(self): 28 | while self.stats_enabled: 29 | self.draw.rectangle((0, 0, self.image.width, self.image.height), outline=0, fill=0) 30 | 31 | # set IP address 32 | top = -2 33 | if ip_address('eth0') is not None: 34 | self.draw.text((4, top), 'IP: ' + str(ip_address('eth0')), font=self.font, fill=255) 35 | elif ip_address('wlan0') is not None: 36 | self.draw.text((4, top), 'IP: ' + str(ip_address('wlan0')), font=self.font, fill=255) 37 | else: 38 | self.draw.text((4, top), 'IP: not available') 39 | 40 | top = 6 41 | power_mode_str = power_mode() 42 | self.draw.text((4, top), 'MODE: ' + power_mode_str, font=self.font, fill=255) 43 | 44 | # set stats headers 45 | top = 14 46 | offset = 3 * 8 47 | headers = ['PWR', 'CPU', 'GPU', 'RAM', 'DSK'] 48 | for i, header in enumerate(headers): 49 | self.draw.text((i * offset + 4, top), header, font=self.font, fill=255) 50 | 51 | # set stats fields 52 | top = 22 53 | power_watts = '%.1f' % power_usage() 54 | gpu_percent = '%02d%%' % int(round(gpu_usage() * 100.0, 1)) 55 | cpu_percent = '%02d%%' % int(round(cpu_usage() * 100.0, 1)) 56 | ram_percent = '%02d%%' % int(round(memory_usage() * 100.0, 1)) 57 | disk_percent = '%02d%%' % int(round(disk_usage() * 100.0, 1)) 58 | 59 | entries = [power_watts, cpu_percent, gpu_percent, ram_percent, disk_percent] 60 | for i, entry in enumerate(entries): 61 | self.draw.text((i * offset + 4, top), entry, font=self.font, fill=255) 62 | 63 | self.display.image(self.image) 64 | self.display.display() 65 | 66 | time.sleep(self.stats_interval) 67 | 68 | def enable_stats(self): 69 | # start stats display thread 70 | if not self.stats_enabled: 71 | self.stats_enabled = True 72 | self.stats_thread = threading.Thread(target=self._run_display_stats) 73 | self.stats_thread.start() 74 | 75 | def disable_stats(self): 76 | self.stats_enabled = False 77 | if self.stats_thread is not None: 78 | self.stats_thread.join() 79 | self.draw.rectangle((0, 0, self.image.width, self.image.height), outline=0, fill=0) 80 | self.display.image(self.image) 81 | self.display.display() 82 | 83 | def set_text(self, text): 84 | self.disable_stats() 85 | self.draw.rectangle((0, 0, self.image.width, self.image.height), outline=0, fill=0) 86 | 87 | lines = text.split('\n') 88 | top = 2 89 | for line in lines: 90 | self.draw.text((4, top), line, font=self.font, fill=255) 91 | top += 10 92 | 93 | self.display.image(self.image) 94 | self.display.display() 95 | 96 | 97 | server = DisplayServer() 98 | app = Flask(__name__) 99 | 100 | 101 | @app.route('/stats/on') 102 | def enable_stats(): 103 | global server 104 | server.enable_stats() 105 | return "stats enabled" 106 | 107 | 108 | @app.route('/stats/off') 109 | def disable_stats(): 110 | global server 111 | server.disable_stats() 112 | return "stats disabled" 113 | 114 | 115 | @app.route('/text/') 116 | def set_text(text): 117 | global server 118 | server.set_text(text) 119 | return 'set text: \n\n%s' % text 120 | 121 | 122 | if __name__ == '__main__': 123 | app.run(host='0.0.0.0', port='8000', debug=False) 124 | 125 | -------------------------------------------------------------------------------- /jetcard/heartbeat.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import traitlets 3 | from traitlets.config.configurable import Configurable 4 | import ipywidgets.widgets as widgets 5 | import time 6 | import threading 7 | 8 | 9 | class Heartbeat(Configurable): 10 | class Status(enum.Enum): 11 | dead = 0 12 | alive = 1 13 | 14 | status = traitlets.UseEnum(Status, default_value=Status.dead) 15 | running = traitlets.Bool(default_value=False) 16 | 17 | # config 18 | period = traitlets.Float(default_value=0.5).tag(config=True) 19 | 20 | def __init__(self, *args, **kwargs): 21 | super(Heartbeat, self).__init__(*args, 22 | **kwargs) # initializes traitlets 23 | 24 | self.pulseout = widgets.FloatText(value=time.time()) 25 | self.pulsein = widgets.FloatText(value=time.time()) 26 | self.link = widgets.jsdlink((self.pulseout, 'value'), 27 | (self.pulsein, 'value')) 28 | self.start() 29 | 30 | def _run(self): 31 | while True: 32 | if not self.running: 33 | break 34 | if self.pulseout.value - self.pulsein.value >= self.period: 35 | self.status = Heartbeat.Status.dead 36 | else: 37 | self.status = Heartbeat.Status.alive 38 | self.pulseout.value = time.time() 39 | time.sleep(self.period) 40 | 41 | def start(self): 42 | if self.running: 43 | return 44 | self.running = True 45 | self.thread = threading.Thread(target=self._run) 46 | self.thread.start() 47 | 48 | def stop(self): 49 | self.running = False -------------------------------------------------------------------------------- /jetcard/stats.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Adafruit Industries 2 | # Author: Tony DiCola & James DeVito 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | import time 22 | 23 | import Adafruit_SSD1306 24 | 25 | from PIL import Image 26 | from PIL import ImageDraw 27 | from PIL import ImageFont 28 | from .utils import get_ip_address 29 | 30 | import subprocess 31 | 32 | # 128x32 display with hardware I2C: 33 | disp = Adafruit_SSD1306.SSD1306_128_32(rst=None, i2c_bus=1, gpio=1) # setting gpio to 1 is hack to avoid platform detection 34 | 35 | # Initialize library. 36 | disp.begin() 37 | 38 | # Clear display. 39 | disp.clear() 40 | disp.display() 41 | 42 | # Create blank image for drawing. 43 | # Make sure to create image with mode '1' for 1-bit color. 44 | width = disp.width 45 | height = disp.height 46 | image = Image.new('1', (width, height)) 47 | 48 | # Get drawing object to draw on image. 49 | draw = ImageDraw.Draw(image) 50 | 51 | # Draw a black filled box to clear the image. 52 | draw.rectangle((0,0,width,height), outline=0, fill=0) 53 | 54 | # Draw some shapes. 55 | # First define some constants to allow easy resizing of shapes. 56 | padding = -2 57 | top = padding 58 | bottom = height-padding 59 | # Move left to right keeping track of the current x position for drawing shapes. 60 | x = 0 61 | 62 | # Load default font. 63 | font = ImageFont.load_default() 64 | 65 | 66 | while True: 67 | 68 | # Draw a black filled box to clear the image. 69 | draw.rectangle((0,0,width,height), outline=0, fill=0) 70 | 71 | # Shell scripts for system monitoring from here : https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load 72 | cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" 73 | CPU = subprocess.check_output(cmd, shell = True ) 74 | cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'" 75 | MemUsage = subprocess.check_output(cmd, shell = True ) 76 | cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%dGB %s\", $3,$2,$5}'" 77 | Disk = subprocess.check_output(cmd, shell = True ) 78 | 79 | # Write two lines of text. 80 | 81 | draw.text((x, top), "eth0: " + str(get_ip_address('eth0')), font=font, fill=255) 82 | draw.text((x, top+8), "wlan0: " + str(get_ip_address('wlan0')), font=font, fill=255) 83 | draw.text((x, top+16), str(MemUsage.decode('utf-8')), font=font, fill=255) 84 | draw.text((x, top+25), str(Disk.decode('utf-8')), font=font, fill=255) 85 | 86 | # Display image. 87 | disp.image(image) 88 | disp.display() 89 | time.sleep(1) 90 | -------------------------------------------------------------------------------- /jetcard/utils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import pkg_resources 3 | import platform 4 | import os 5 | 6 | 7 | def notebooks_dir(): 8 | return pkg_resources.resource_filename('jetbot', 'notebooks') 9 | 10 | 11 | def platform_notebooks_dir(): 12 | if 'aarch64' in platform.machine(): 13 | return os.path.join(notebooks_dir(), 'robot') 14 | else: 15 | return os.path.join(notebooks_dir(), 'host') 16 | 17 | 18 | def platform_model_str(): 19 | with open('/proc/device-tree/model', 'r') as f: 20 | return str(f.read()[:-1]) 21 | 22 | 23 | def platform_is_nano(): 24 | return 'jetson-nano' in platform_model_str() 25 | 26 | 27 | def ip_address(interface): 28 | try: 29 | if network_interface_state(interface) == 'down': 30 | return None 31 | cmd = "ifconfig %s | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'" % interface 32 | return subprocess.check_output(cmd, shell=True).decode('ascii')[:-1] 33 | except: 34 | return None 35 | 36 | 37 | def network_interface_state(interface): 38 | try: 39 | with open('/sys/class/net/%s/operstate' % interface, 'r') as f: 40 | return f.read() 41 | except: 42 | return 'down' # default to down 43 | 44 | 45 | def power_mode(): 46 | """Gets the Jetson's current power mode 47 | 48 | Gets the current power mode as set by the tool ``nvpmodel``. 49 | 50 | Returns: 51 | str: The current power mode. Either 'MAXN' or '5W'. 52 | """ 53 | return subprocess.check_output("nvpmodel -q | grep -o '5W\|MAXN'", shell = True ).decode('utf-8').strip('\n') 54 | 55 | 56 | def power_usage(): 57 | """Gets the Jetson's current power usage in Watts 58 | 59 | Returns: 60 | float: The current power usage in Watts. 61 | """ 62 | with open("/sys/devices/50000000.host1x/546c0000.i2c/i2c-6/6-0040/iio:device0/in_power0_input", 'r') as f: 63 | return float(f.read()) / 1000.0 64 | 65 | 66 | def cpu_usage(): 67 | """Gets the Jetson's current CPU usage fraction 68 | 69 | Returns: 70 | float: The current CPU usage fraction. 71 | """ 72 | return float(subprocess.check_output("top -bn1 | grep load | awk '{printf \"%.2f\", $(NF-2)}'", shell = True ).decode('utf-8')) 73 | 74 | 75 | def gpu_usage(): 76 | """Gets the Jetson's current GPU usage fraction 77 | 78 | Returns: 79 | float: The current GPU usage fraction. 80 | """ 81 | with open('/sys/devices/gpu.0/load', 'r') as f: 82 | return float(f.read().strip('\n')) / 1000.0 83 | 84 | 85 | def memory_usage(): 86 | """Gets the Jetson's current RAM memory usage fraction 87 | 88 | Returns: 89 | float: The current RAM usage fraction. 90 | """ 91 | return float(subprocess.check_output("free -m | awk 'NR==2{printf \"%.2f\", $3*100/$2 }'", shell = True ).decode('utf-8')) / 100.0 92 | 93 | 94 | def disk_usage(): 95 | """Gets the Jetson's current disk memory usage fraction 96 | 97 | Returns: 98 | float: The current disk usage fraction. 99 | """ 100 | return float(subprocess.check_output("df -h | awk '$NF==\"/\"{printf \"%s\", $5}'", shell = True ).decode('utf-8').strip('%')) / 100.0 -------------------------------------------------------------------------------- /run-install-and-save-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./install.sh 2>&1 | tee ~/jetcard-install.log 4 | -------------------------------------------------------------------------------- /scripts/archive/nvresizefs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions 7 | # are met: 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # * Neither the name of NVIDIA CORPORATION nor the names of its 14 | # contributors may be used to endorse or promote products derived 15 | # from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 18 | # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # This is a script to resize partition and filesystem on the root partition 30 | # This will consume all un-allocated sapce on SD card after boot. 31 | 32 | set -e 33 | 34 | function cleanup() 35 | { 36 | # Delete nvresizefs.sh, nvresizefs.service and its symlink 37 | rm "/etc/systemd/nvresizefs.sh" 38 | rm "/etc/systemd/system/nvresizefs.service" 39 | rm "/etc/systemd/system/multi-user.target.wants/nvresizefs.service" 40 | } 41 | 42 | #if [ -e "/proc/device-tree/compatible" ]; then 43 | # model="$(tr -d '\0' < /proc/device-tree/compatible)" 44 | # if [[ "${model}" =~ "jetson-nano" ]]; then 45 | # model="jetson-nano" 46 | # fi 47 | #fi 48 | # 49 | #if [ "${model}" != "jetson-nano" ]; then 50 | # cleanup 51 | # exit 0 52 | #fi 53 | 54 | # Move backup GPT header to end of disk 55 | sgdisk --move-second-header /dev/mmcblk0 56 | 57 | # Get root partition name i.e partition No. 1 58 | partition_name="$(sgdisk -i 1 /dev/mmcblk0 | \ 59 | grep "Partition name" | cut -d\' -f2)" 60 | 61 | partition_type="$(sgdisk -i 1 /dev/mmcblk0 | \ 62 | grep "Partition GUID code:" | cut -d' ' -f4)" 63 | 64 | partition_uuid="$(sgdisk -i 1 /dev/mmcblk0 | \ 65 | grep "Partition unique GUID:" | cut -d' ' -f4)" 66 | 67 | # Get start sector of the root partition 68 | start_sector="$(cat /sys/block/mmcblk0/mmcblk0p1/start)" 69 | 70 | # Delete and re-create the root partition 71 | # This will resize the root partition. 72 | sgdisk -d 1 -n 1:"${start_sector}":0 -c 1:"${partition_name}" \ 73 | -t 1:"${partition_type}" -u 1:"${partition_uuid}" /dev/mmcblk0 74 | 75 | # Inform kernel and OS about change in partitin table and root 76 | # partition size 77 | partprobe /dev/mmcblk0 78 | 79 | # Resize filesystem on root partition to consume all un-allocated 80 | # space on disk 81 | resize2fs /dev/mmcblk0p1 82 | 83 | # Clean up 84 | cleanup 85 | -------------------------------------------------------------------------------- /scripts/archive/system/nvresizefs.service: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. 3 | # 4 | 5 | [Unit] 6 | Description=Resize SD Card root partition and filesystem 7 | Before=nvfb.service 8 | 9 | [Service] 10 | Type=oneshot 11 | ExecStart=/etc/systemd/nvresizefs.sh 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /scripts/host_make_expandable_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [ $# -eq 0 ] && { echo "Usage: $0 "; exit 1; } 4 | 5 | sudo -v 6 | 7 | echo "### sudo dd if=$1 of=$3.img bs=512 count=$2 status=progress" 8 | sudo dd if=$1 of=$3.img bs=512 count=$2 status=progress 9 | 10 | echo "### sudo dd if=/dev/zero of=$3.img bs=512 count=34 oflag=append conv=notrunc" 11 | sudo dd if=/dev/zero of=$3.img bs=512 count=34 oflag=append conv=notrunc 12 | 13 | echo "### sudo dd if=$3.img of=$3_last34_init.bin skip=$2 count=34" 14 | sudo dd if=$3.img of=$3_last34_init.bin skip=$2 count=34 15 | 16 | echo "### sudo sgdisk --move-second-header $3.img" 17 | sudo sgdisk --move-second-header $3.img 18 | 19 | echo "### sudo partprobe $3.img" 20 | sudo partprobe $3.img 21 | 22 | echo "### sudo dd if=$3.img of=$3_last34_mod.bin skip=$2 count=34" 23 | sudo dd if=$3.img of=$3_last34_mod.bin skip=$2 count=34 24 | 25 | echo "### hexdump -C $3_last34_init.bin" 26 | hexdump -C $3_last34_init.bin 27 | 28 | echo "### hexdump -C $3_last34_mod.bin" 29 | hexdump -C $3_last34_mod.bin 30 | 31 | rm $3_last34_*.bin 32 | 33 | echo "zip $3.zip $3.img" 34 | zip $3.zip $3.img 35 | -------------------------------------------------------------------------------- /scripts/jetson_add_ntp_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE="/etc/systemd/timesyncd.conf" 4 | 5 | sudo -v 6 | 7 | sudo bash -c "echo 'NTP=0.arch.pool.ntp.org 1.arch.pool.ntp.org 2.arch.pool.ntp.org 3.arch.pool.ntp.org' >> $FILE" 8 | sudo bash -c "echo 'FallbackNTP=0.pool.ntp.org 1.pool.ntp.org 0.us.pool.ntp.org' >> $FILE" 9 | 10 | echo $FILE" updated" 11 | cat $FILE 12 | 13 | echo "### Restarting systemd-timesyncd.service ..." 14 | sudo systemctl restart systemd-timesyncd.service 15 | -------------------------------------------------------------------------------- /scripts/jetson_fix_usb_device_mode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="/opt/nvidia/l4t-usb-device-mode/" 4 | 5 | sudo -v 6 | 7 | sudo cp $DIR/nv-l4t-usb-device-mode.sh $DIR/nv-l4t-usb-device-mode.sh.orig 8 | sudo cp $DIR/nv-l4t-usb-device-mode-stop.sh $DIR/nv-l4t-usb-device-mode-stop.sh.orig 9 | 10 | echo "### Before" 11 | cat $DIR/nv-l4t-usb-device-mode.sh | grep dhcpd_.*= 12 | cat $DIR/nv-l4t-usb-device-mode-stop.sh | grep dhcpd_.*= 13 | 14 | sudo sed -i 's|${script_dir}/dhcpd.leases|/run/dhcpd.leases|g' $DIR/nv-l4t-usb-device-mode.sh 15 | sudo sed -i 's|${script_dir}/dhcpd.pid|/run/dhcpd.pid|g' $DIR/nv-l4t-usb-device-mode.sh 16 | 17 | sudo sed -i 's|${script_dir}/dhcpd.leases|/run/dhcpd.leases|g' $DIR/nv-l4t-usb-device-mode-stop.sh 18 | sudo sed -i 's|${script_dir}/dhcpd.pid|/run/dhcpd.pid|g' $DIR/nv-l4t-usb-device-mode-stop.sh 19 | 20 | echo "### After" 21 | cat $DIR/nv-l4t-usb-device-mode.sh | grep dhcpd_.*= 22 | cat $DIR/nv-l4t-usb-device-mode-stop.sh | grep dhcpd_.*= 23 | -------------------------------------------------------------------------------- /scripts/jetson_install_nvresizefs_service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo -v 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | echo ' ' 7 | echo '> ${DIR} set to '${DIR} 8 | 9 | sudo cp ${DIR}/archive/nvresizefs.sh /etc/systemd/nvresizefs.sh 10 | sudo cp ${DIR}/archive/system/nvresizefs.service /etc/systemd/system/nvresizefs.service 11 | 12 | echo ' ' 13 | echo '> Check /etc/systemd' 14 | ls -l /etc/systemd | grep nvresizefs 15 | 16 | echo ' ' 17 | echo '> Check /etc/systemd/system' 18 | ls -l /etc/systemd/system | grep nvresizefs 19 | 20 | echo ' ' 21 | echo "> Executing 'sudo systemctl enable nvresizefs'..." 22 | sudo systemctl enable nvresizefs 23 | sudo systemctl list-unit-files | grep enabled | grep nvresizefs 24 | 25 | echo ' ' 26 | echo '> Check the current disk usage' 27 | df 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='jetcard', 5 | version='0.0.0', 6 | description='Easily make projects with NVIDIA Jetson Nano', 7 | packages=find_packages(), 8 | install_requires=[ 9 | 'Adafruit_SSD1306' 10 | ], 11 | ) 12 | --------------------------------------------------------------------------------