From aa8d04c66c9c9f1cbe925d15f8d332a162325a0e Mon Sep 17 00:00:00 2001 From: "Ronaldo S.A. Batista" Date: Sun, 9 Oct 2022 13:29:24 -0300 Subject: [PATCH] :sparkles: Basic API of the server ready --- .github/workflows/deploy.yaml | 9 ++ .github/workflows/test.yaml | 7 ++ .gitignore | 4 + 00_core.ipynb | 227 ++++++++++++++++++++++++++++++++++ MANIFEST.in | 5 + README.md | 25 +++- _quarto.yml | 20 +++ client.py | 93 ++++++++++++++ hackrf2tcp/__init__.py | 0 hackrf2tcp/_modidx.py | 8 ++ hackrf2tcp/core.py | 7 ++ index.ipynb | 96 ++++++++++++++ nbdev.yml | 9 ++ server.py | 108 ++++++++++++++++ settings.ini | 42 +++++++ setup.py | 57 +++++++++ styles.css | 37 ++++++ 17 files changed, 752 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/deploy.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 00_core.ipynb create mode 100644 MANIFEST.in create mode 100644 _quarto.yml create mode 100644 client.py create mode 100644 hackrf2tcp/__init__.py create mode 100644 hackrf2tcp/_modidx.py create mode 100644 hackrf2tcp/core.py create mode 100644 index.ipynb create mode 100644 nbdev.yml create mode 100644 server.py create mode 100644 settings.ini create mode 100644 setup.py create mode 100644 styles.css diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..68a7b31 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,9 @@ +name: Deploy to GitHub Pages +on: + push: + branches: [ "main", "master" ] + workflow_dispatch: +jobs: + deploy: + runs-on: ubuntu-latest + steps: [uses: fastai/workflows/quarto-ghp@master] diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..5608592 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,7 @@ +name: CI +on: [workflow_dispatch, pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: [uses: fastai/workflows/nbdev-ci@master] diff --git a/.gitignore b/.gitignore index b6e4761..089a623 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# Quarto Files +_proc +_docs diff --git a/00_core.ipynb b/00_core.ipynb new file mode 100644 index 0000000..0931e71 --- /dev/null +++ b/00_core.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Server\n", + "\n", + "> Server module of output of hackrf_sweep cli" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp server" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "from nbdev.showdoc import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import subprocess\n", + "from subprocess import Popen, STDOUT, PIPE\n", + "import socket\n", + "from time import sleep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "HEADER_LENGTH = 10\n", + "\n", + "IP = \"127.0.0.1\"\n", + "PORT = 1234\n", + "\n", + "Setup our socket\n", + "\n", + "server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + "\n", + "overcome the \"Address already in use\"\n", + "\n", + "server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n", + "\n", + "server_socket.bind((IP, PORT))\n", + "\n", + "server_socket.listen()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['-1', '-f', '88:108', '-w', '10000']" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastcore.basics import listify\n", + "cmds = []\n", + "for c in '-1 -f=88:108 -w 10000'.split():\n", + " cmd = c.split('=') if '=' in c else c\n", + " cmds.extend(listify(cmd))\n", + "cmds" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "sub = subprocess.run([\"hackrf_sweep\", '-1', '-f', '88:108', '-w', '10000'], stdout=subprocess.PIPE)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "lines = sub.stdout.splitlines()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[b'2022-10-01, 16:03:49.493559, 88000000, 93000000, 9980.04, 2004, -83.17, -81.85, -77.86, -76.46, -72.77, -66.79, -65.07, -64.81, -71.74, -84.57, -86.24, -83.66, -82.91, -78.06, -82.32, -99.58, -86.47, -92.70, -78.43, -76.49, -83.87, -83.23, -78.28, -81.42, -81.51, -83.68, -84.81, -90.90, -84.43, -84.63, -83.54, -82.18, -80.91, -81.43, -81.74, -83.75, -88.30, -84.96, -82.90, -83.72, -89.19, -95.58, -81.91, -80.47, -81.88, -79.39, -87.37, -82.38, -78.57, -77.72, -82.65, -85.79, -81.71, -87.14, -95.91, -83.64, -80.28, -79.95, -77.76, -79.74, -80.44, -76.99, -86.61, -89.98, -98.05, -88.44, -91.16, -82.93, -82.11, -79.71, -77.24, -67.98, -69.43, -78.91, -80.54, -89.51, -87.92, -82.67, -81.59, -84.05, -87.06, -82.29, -92.43, -83.34, -86.43, -85.80, -86.13, -92.83, -84.08, -81.49, -79.84, -83.98, -85.93, -85.78, -83.17, -84.14, -92.29, -87.97, -79.58, -79.99, -88.93, -85.26, -87.56, -90.20, -93.56, -81.78, -80.34, -88.06, -79.55, -81.08, -92.83, -80.69, -77.76, -75.21, -72.62, -79.06, -77.13, -77.04, -78.49, -82.31, -84.58, -84.23, -81.05, -85.59, -87.06, -85.34, -90.83, -81.68, -86.08, -94.94, -90.68, -99.11, -90.52, -83.53, -92.78, -81.59, -83.89, -80.13, -82.51, -88.32, -90.32, -86.76, -91.12, -82.65, -86.77, -87.97, -90.60, -80.10, -66.68, -66.69, -80.05, -87.34, -89.46, -88.03, -88.03, -100.08, -83.71, -80.87, -85.08, -88.66, -95.79, -93.62, -88.09, -82.23, -85.49, -85.30, -81.55, -77.20, -65.39, -61.36, -63.24, -83.98, -69.08, -75.60, -81.23, -92.80, -78.73, -82.43, -82.76, -86.11, -92.78, -81.88, -87.71, -83.42, -82.99, -93.71, -99.08, -83.59, -79.76, -79.21, -83.21, -93.80, -89.62, -89.29, -90.50, -83.06, -84.39, -85.11, -82.24, -81.37, -86.58, -88.32, -85.56, -87.50, -109.43, -97.82, -89.83, -80.00, -81.07, -90.99, -77.39, -79.06, -75.45, -72.48, -71.50, -66.84, -65.72, -70.88, -78.41, -85.44, -88.12, -88.88, -80.72, -78.75, -81.54, -89.61, -81.39, -81.84, -80.03, -78.92, -78.59, -86.42, -83.59, -80.61, -82.62, -107.09, -83.16, -80.44, -81.70, -84.53, -84.99, -98.41, -92.51, -86.32, -78.80, -76.06, -88.05, -74.56, -67.00, -59.69, -53.42, -57.51, -69.81, -76.86, -75.95, -76.34, -81.29, -97.05, -97.95, -81.81, -86.80, -97.37, -84.87, -84.07, -80.07, -82.94, -90.86, -89.85, -90.81, -99.45, -98.97, -82.81, -83.99, -85.07, -85.07, -88.82, -81.90, -90.99, -82.35, -82.16, -83.86, -84.20, -86.69, -90.33, -86.65, -79.42, -88.89, -84.68, -83.48, -91.61, -82.95, -86.19, -87.71, -82.25, -86.42, -84.53, -107.67, -96.83, -94.85, -88.58, -81.62, -87.22, -90.57, -85.87, -85.27, -89.27, -85.31, -84.27, -101.37, -82.42, -80.68, -86.97, -86.34, -87.74, -90.50, -98.02, -93.27, -85.96, -78.57, -80.99, -78.31, -81.91, -84.23, -84.04, -82.94, -80.36, -80.73, -82.06, -78.65, -73.79, -72.51, -82.08, -91.41, -87.91, -93.39, -92.89, -86.50, -85.99, -92.05, -86.08, -79.87, -78.49, -84.47, -90.39, -93.30, -83.01, -82.21, -87.01, -88.81, -90.61, -93.95, -89.38, -89.53, -83.93, -83.78, -84.66, -79.07, -79.08, -85.81, -90.05, -86.09, -84.55, -86.18, -85.99, -79.48, -78.82, -78.29, -76.76, -78.35, -82.92, -80.82, -94.03, -82.04, -83.77, -91.36, -87.67, -80.96, -81.66, -89.47, -93.67, -86.68, -82.65, -83.08, -85.94, -85.42, -83.51, -81.25, -78.97, -86.47, -90.61, -92.59, -82.19, -83.46, -95.36, -80.64, -80.92, -85.66, -89.40, -90.01, -81.22, -79.92, -79.37, -78.89, -79.76, -82.23, -82.02, -78.04, -78.10, -84.63, -83.22, -74.92, -69.41, -74.14, -79.50, -80.87, -89.78, -84.38, -85.93, -89.40, -86.97, -84.12, -85.06, -87.92, -88.88, -88.63, -89.19, -100.13, -87.70, -83.65, -82.09, -89.93, -91.34, -87.73, -82.28, -84.32, -81.88, -85.89, -90.16, -79.57, -82.12, -87.79, -94.55, -86.25, -93.37, -86.25, -81.80, -79.92, -83.17, -81.86, -82.90, -70.74, -70.40, -77.97, -92.54, -81.79, -79.18, -85.58, -84.86, -78.83, -82.30, -86.62, -81.37, -87.44, -92.99, -81.74, -79.22, -82.90, -81.62, -78.82, -83.88, -90.55, -87.26, -80.52, -80.44, -81.25, -94.49, -86.41, -87.41, -83.94, -82.60, -89.23, -83.43, -83.29, -91.13, -89.94, -78.22, -70.69, -77.39, -87.84, -74.97, -71.73, -77.50, -74.09, -68.25, -71.47, -82.63, -87.49, -74.92, -75.96, -82.25, -85.16',\n", + " b'2022-10-01, 16:03:49.493559, 98000000, 103000000, 9980.04, 2004, -81.13, -87.30, -104.72, -96.53, -82.96, -79.07, -81.58, -95.63, -82.25, -80.26, -83.43, -82.50, -84.69, -87.87, -86.70, -87.87, -83.34, -81.30, -86.25, -87.54, -84.32, -81.84, -81.23, -80.36, -79.17, -84.16, -90.36, -86.45, -78.01, -79.57, -82.26, -80.01, -79.33, -85.14, -92.59, -103.42, -86.62, -80.91, -83.52, -90.95, -80.17, -79.44, -86.94, -96.87, -83.49, -82.89, -85.93, -81.29, -80.93, -81.54, -74.28, -68.27, -69.09, -79.45, -88.99, -90.32, -82.72, -80.65, -85.71, -85.33, -83.19, -87.65, -94.06, -88.13, -88.55, -84.41, -90.17, -89.18, -83.72, -92.07, -81.84, -91.89, -89.53, -84.58, -88.88, -82.17, -79.85, -80.82, -80.70, -81.91, -82.70, -81.68, -85.77, -84.73, -80.08, -81.93, -91.86, -107.26, -92.22, -86.94, -84.32, -82.69, -88.28, -91.94, -82.66, -83.88, -89.61, -85.07, -89.20, -92.53, -83.92, -84.49, -88.66, -83.68, -82.93, -85.56, -77.88, -76.00, -72.73, -72.89, -79.04, -95.77, -89.16, -93.61, -79.92, -76.96, -82.00, -87.93, -88.86, -89.54, -85.86, -80.34, -80.38, -79.49, -96.69, -97.12, -90.85, -86.80, -92.91, -85.67, -80.80, -83.46, -75.53, -72.54, -74.22, -81.11, -85.11, -79.74, -81.20, -85.10, -93.97, -79.75, -76.74, -79.44, -77.41, -81.47, -81.40, -74.00, -72.16, -60.18, -54.58, -60.49, -70.87, -77.04, -90.48, -82.89, -84.99, -79.54, -84.25, -90.59, -89.18, -90.60, -88.35, -90.34, -97.14, -83.97, -80.42, -80.39, -80.99, -78.72, -79.22, -82.33, -84.21, -83.32, -88.15, -98.95, -80.64, -80.85, -84.87, -80.28, -84.76, -85.37, -85.31, -84.00, -80.60, -80.19, -95.84, -88.17, -88.45, -99.01, -84.46, -87.44, -111.72, -87.37, -77.73, -69.81, -67.43, -60.40, -64.08, -70.68, -76.22, -85.72, -99.96, -96.95, -87.79, -86.79, -89.12, -82.12, -73.99, -74.17, -66.16, -60.37, -65.57, -72.49, -73.87, -76.89, -82.24, -87.45, -85.76, -84.31, -79.84, -84.36, -97.31, -92.46, -87.79, -80.68, -87.48, -89.11, -82.13, -82.10, -86.39, -95.15, -83.19, -81.03, -81.70, -81.91, -86.14, -82.22, -80.15, -79.34, -80.80, -85.77, -86.92, -86.29, -85.58, -80.98, -82.13, -79.72, -79.00, -83.36, -77.98, -83.42, -81.61, -91.74, -78.65, -86.85, -82.69, -82.68, -85.70, -82.47, -99.03, -84.74, -87.51, -85.58, -85.04, -85.35, -87.96, -91.07, -96.19, -88.02, -88.57, -95.44, -98.09, -96.46, -84.29, -91.16, -86.04, -82.73, -82.01, -89.13, -92.91, -93.77, -86.71, -83.22, -84.85, -88.18, -87.84, -62.45, -56.79, -62.90, -94.24, -90.65, -81.87, -81.59, -81.80, -81.59, -79.85, -78.70, -78.98, -91.98, -81.84, -80.11, -84.36, -85.72, -91.65, -87.20, -87.24, -80.78, -79.00, -100.72, -77.42, -77.61, -86.42, -87.42, -86.04, -85.20, -84.51, -84.33, -86.40, -89.09, -84.07, -92.48, -93.51, -84.15, -83.50, -98.07, -86.60, -83.04, -84.27, -90.25, -89.25, -80.75, -80.46, -87.34, -90.08, -85.52, -82.76, -80.13, -81.33, -86.45, -86.12, -88.47, -87.43, -89.04, -92.79, -90.87, -82.23, -80.62, -79.79, -83.24, -93.07, -90.86, -82.24, -82.37, -84.39, -88.96, -87.77, -81.03, -77.69, -77.59, -80.42, -86.57, -78.94, -80.63, -80.20, -90.32, -96.61, -78.87, -77.96, -84.66, -83.78, -81.42, -79.04, -81.71, -87.53, -97.90, -95.01, -82.29, -84.42, -85.00, -88.20, -97.12, -87.16, -82.28, -85.56, -93.15, -87.19, -82.89, -84.50, -87.38, -88.90, -87.64, -87.30, -80.34, -79.01, -87.32, -90.52, -88.30, -97.09, -89.67, -94.73, -88.53, -88.70, -92.51, -83.78, -86.06, -87.84, -83.98, -80.94, -80.24, -83.64, -78.85, -81.18, -79.58, -80.69, -78.70, -81.31, -77.76, -75.84, -77.01, -81.70, -90.31, -81.14, -80.47, -93.08, -97.17, -88.82, -88.99, -97.12, -82.80, -84.15, -86.10, -83.57, -85.42, -81.99, -81.23, -86.84, -85.13, -88.83, -86.97, -80.08, -77.14, -79.41, -91.41, -93.67, -87.63, -80.51, -84.60, -91.56, -97.99, -88.07, -88.49, -85.01, -83.18, -83.31, -92.63, -89.13, -89.62, -80.92, -81.08, -90.94, -87.85, -89.05, -92.14, -86.14, -81.79, -79.52, -85.71, -94.09, -92.54, -87.33, -86.11, -88.44, -88.05, -84.91, -83.22, -88.50, -89.39, -93.20, -88.87, -85.61, -97.23, -89.62, -86.19, -81.12, -88.52, -92.48, -85.34, -93.98, -77.15, -82.45, -86.11, -79.09, -73.27, -76.15, -92.16, -81.92, -82.30, -82.13, -81.68, -86.81',\n", + " b'2022-10-01, 16:03:49.493559, 93000000, 98000000, 9980.04, 2004, -79.22, -79.74, -86.28, -93.38, -87.75, -105.90, -82.50, -78.89, -80.44, -82.69, -83.19, -84.95, -84.27, -95.12, -89.87, -94.14, -87.47, -88.52, -87.30, -85.05, -83.24, -85.26, -84.37, -94.56, -81.80, -89.12, -75.48, -70.38, -61.48, -63.59, -70.36, -86.61, -85.95, -86.54, -101.44, -84.13, -86.75, -87.15, -85.89, -84.00, -92.76, -82.55, -78.06, -79.40, -81.65, -77.81, -79.51, -105.57, -84.27, -89.52, -90.53, -89.40, -95.22, -100.49, -95.18, -88.21, -80.62, -83.03, -92.78, -103.53, -108.15, -84.49, -79.51, -81.34, -86.09, -86.77, -92.77, -94.75, -84.10, -81.49, -79.25, -83.88, -69.10, -64.76, -72.20, -78.12, -78.92, -78.35, -79.30, -83.19, -84.36, -80.70, -90.56, -82.84, -86.48, -85.04, -89.94, -80.30, -82.49, -86.93, -94.94, -92.04, -89.02, -109.73, -86.12, -90.13, -87.02, -92.00, -89.92, -99.07, -83.54, -96.66, -91.35, -81.48, -78.58, -79.34, -82.86, -85.65, -107.86, -83.14, -85.61, -92.28, -87.32, -83.89, -78.73, -81.04, -92.53, -86.89, -90.37, -86.71, -86.68, -85.99, -87.12, -90.72, -81.42, -82.57, -87.10, -89.78, -80.03, -66.73, -65.83, -77.09, -81.53, -82.84, -86.28, -95.01, -88.94, -83.77, -87.26, -82.10, -83.64, -80.58, -78.06, -79.05, -83.19, -88.62, -79.89, -82.23, -86.06, -90.12, -95.35, -90.41, -82.10, -88.91, -85.26, -85.88, -89.24, -82.59, -85.63, -84.66, -84.42, -91.88, -85.85, -87.61, -80.60, -77.25, -81.99, -82.86, -83.20, -68.18, -61.37, -60.47, -67.11, -71.03, -79.80, -78.25, -84.63, -83.38, -84.60, -86.75, -82.23, -87.50, -81.48, -80.37, -91.78, -93.86, -86.49, -89.65, -88.51, -93.07, -84.00, -77.19, -78.60, -98.65, -91.37, -86.67, -92.15, -87.12, -92.55, -82.00, -76.72, -76.84, -81.43, -85.83, -84.12, -85.68, -109.97, -69.47, -63.65, -69.05, -89.43, -81.56, -79.98, -79.80, -82.43, -82.48, -76.39, -82.75, -83.46, -84.44, -85.86, -87.42, -85.64, -83.98, -79.48, -79.21, -83.00, -86.61, -95.57, -82.30, -77.87, -78.16, -95.10, -82.48, -73.33, -72.70, -79.47, -84.13, -77.26, -81.87, -87.20, -85.82, -87.61, -81.21, -101.59, -79.47, -83.56, -85.48, -83.36, -102.06, -89.59, -86.10, -80.94, -83.53, -78.50, -79.01, -79.35, -82.48, -101.02, -85.08, -101.29, -80.31, -80.32, -82.92, -87.48, -91.75, -99.71, -87.13, -83.55, -87.10, -73.39, -71.04, -84.03, -76.07, -79.62, -87.24, -82.17, -88.01, -85.87, -89.30, -89.85, -78.12, -68.51, -63.93, -65.55, -76.59, -83.24, -85.36, -89.64, -85.77, -90.55, -82.02, -86.99, -94.98, -88.85, -88.95, -84.73, -85.25, -86.31, -91.41, -82.97, -80.99, -78.38, -73.72, -74.07, -79.56, -81.57, -75.23, -76.92, -79.90, -87.32, -79.40, -78.19, -88.47, -82.41, -86.93, -84.98, -79.79, -82.06, -82.88, -75.05, -77.67, -85.51, -89.74, -81.46, -84.50, -86.36, -89.58, -95.23, -90.84, -82.78, -79.51, -74.69, -80.50, -83.87, -91.93, -87.08, -80.47, -85.06, -86.14, -81.88, -86.02, -89.49, -83.63, -79.50, -78.35, -80.91, -93.99, -86.97, -91.31, -71.32, -63.99, -62.69, -69.89, -72.51, -84.27, -79.01, -82.41, -86.94, -88.92, -88.36, -84.00, -77.68, -78.49, -85.29, -88.12, -81.87, -79.60, -82.96, -85.64, -91.46, -89.54, -91.34, -89.44, -86.84, -83.21, -81.78, -83.54, -86.10, -83.89, -84.43, -94.00, -89.51, -87.61, -90.26, -80.84, -79.86, -83.89, -84.96, -82.30, -80.14, -71.61, -64.14, -68.40, -76.96, -79.10, -111.21, -78.06, -78.14, -87.06, -89.22, -81.59, -84.10, -91.27, -88.01, -93.80, -94.58, -86.22, -85.24, -96.43, -89.34, -86.68, -90.05, -106.22, -94.50, -85.46, -85.75, -93.05, -89.18, -85.01, -83.75, -81.93, -86.07, -87.66, -83.09, -82.15, -83.90, -82.34, -75.05, -65.65, -66.74, -76.79, -88.29, -81.67, -82.23, -82.06, -80.06, -83.99, -84.87, -84.15, -87.99, -97.57, -91.03, -87.36, -85.54, -85.99, -88.70, -93.04, -89.16, -92.76, -93.11, -90.67, -86.82, -89.85, -82.05, -82.29, -82.52, -82.63, -80.45, -87.74, -92.96, -90.05, -91.06, -88.81, -85.74, -87.72, -94.58, -90.06, -85.56, -86.73, -85.96, -80.40, -80.64, -80.54, -81.29, -75.56, -83.49, -79.80, -73.16, -74.89, -83.19, -91.94, -90.69, -83.48, -81.03, -87.10, -90.28, -88.24, -83.84, -89.05, -81.35, -83.58, -91.41, -91.28, -99.67, -102.95, -87.84, -82.36, -86.47, -90.79, -82.59',\n", + " b'2022-10-01, 16:03:49.493559, 103000000, 108000000, 9980.04, 2004, -79.10, -80.47, -83.16, -81.51, -81.15, -81.99, -71.64, -66.64, -72.91, -81.31, -84.15, -88.71, -91.80, -82.81, -85.53, -79.83, -85.02, -87.84, -81.73, -77.52, -79.57, -89.02, -80.02, -84.34, -83.82, -80.83, -87.38, -93.34, -89.21, -80.31, -75.33, -80.30, -92.64, -90.67, -84.00, -81.06, -91.67, -79.69, -80.84, -87.91, -93.74, -91.92, -85.70, -86.14, -81.47, -83.99, -89.37, -86.01, -84.49, -80.49, -85.18, -89.05, -85.76, -89.47, -84.07, -88.17, -83.71, -89.43, -89.89, -85.54, -85.21, -84.71, -84.34, -80.97, -85.02, -88.08, -82.86, -76.10, -68.20, -64.55, -69.70, -76.36, -83.93, -88.43, -89.00, -82.23, -82.08, -89.71, -88.98, -85.85, -92.10, -79.99, -79.34, -89.98, -90.05, -89.97, -88.30, -89.30, -86.58, -85.54, -75.09, -67.34, -69.19, -83.86, -88.37, -87.23, -85.10, -94.69, -91.01, -83.79, -79.02, -83.57, -95.00, -80.74, -87.02, -76.80, -81.81, -79.30, -79.08, -81.93, -78.07, -76.33, -80.17, -82.98, -82.41, -86.42, -85.34, -80.14, -76.98, -83.91, -85.71, -100.12, -78.44, -79.16, -83.88, -76.07, -75.74, -73.01, -73.03, -76.14, -75.63, -73.52, -74.79, -75.60, -74.67, -73.71, -74.41, -81.81, -86.78, -77.90, -83.25, -90.88, -87.17, -87.31, -88.12, -88.40, -85.10, -79.92, -81.34, -84.03, -88.35, -86.94, -90.67, -88.78, -86.24, -81.61, -77.91, -80.86, -88.43, -86.76, -82.32, -93.54, -82.81, -88.24, -87.24, -81.71, -82.56, -82.40, -77.75, -77.46, -79.03, -83.01, -82.09, -89.31, -89.28, -90.99, -89.06, -85.38, -86.64, -82.26, -84.43, -102.50, -112.78, -86.83, -83.02, -83.02, -78.32, -77.91, -80.82, -99.60, -88.67, -92.71, -80.72, -86.00, -89.56, -88.05, -81.94, -79.50, -86.13, -84.60, -84.09, -95.70, -89.27, -90.10, -82.85, -99.07, -81.80, -76.49, -71.68, -75.45, -84.51, -81.21, -82.38, -83.20, -88.92, -79.03, -80.68, -87.54, -88.65, -88.96, -98.64, -90.03, -88.44, -83.26, -84.18, -86.27, -84.24, -84.49, -82.65, -86.81, -89.40, -89.55, -87.29, -89.87, -82.38, -80.16, -80.93, -94.31, -83.32, -82.51, -85.67, -85.13, -86.32, -84.23, -84.73, -80.68, -82.68, -90.07, -84.63, -98.41, -86.70, -80.42, -79.07, -85.66, -82.90, -82.46, -80.68, -81.22, -86.17, -94.06, -86.47, -83.02, -84.99, -83.40, -88.46, -89.36, -85.80, -78.90, -80.51, -75.26, -68.36, -72.49, -81.93, -84.64, -94.07, -89.19, -85.12, -85.25, -81.02, -78.32, -78.34, -85.64, -91.25, -82.99, -83.81, -83.40, -86.18, -85.75, -87.07, -88.09, -89.51, -87.70, -80.62, -83.67, -87.62, -85.52, -78.78, -83.39, -84.72, -89.51, -84.38, -96.37, -86.47, -81.83, -81.48, -87.26, -81.23, -85.44, -91.27, -82.83, -80.55, -82.17, -81.55, -84.51, -91.47, -89.05, -88.49, -82.87, -78.19, -77.12, -77.56, -81.89, -93.30, -96.23, -88.00, -81.30, -83.79, -79.25, -75.04, -65.28, -64.22, -63.78, -63.95, -67.26, -72.41, -79.56, -84.13, -85.50, -82.67, -82.80, -85.36, -87.14, -86.21, -94.10, -79.78, -86.62, -85.51, -84.01, -87.82, -91.39, -87.50, -86.67, -84.11, -79.60, -90.53, -84.83, -81.40, -81.81, -90.13, -90.60, -83.70, -79.93, -85.48, -95.50, -90.14, -89.31, -85.26, -87.23, -90.96, -78.69, -80.56, -79.44, -86.29, -82.37, -82.71, -86.60, -85.67, -81.58, -79.72, -84.76, -84.20, -86.64, -92.13, -87.06, -85.07, -99.00, -88.31, -86.83, -86.92, -85.13, -94.70, -90.47, -85.57, -84.43, -75.06, -74.28, -82.52, -83.74, -79.25, -82.53, -79.97, -80.50, -100.32, -99.59, -90.54, -84.38, -84.04, -79.57, -82.31, -86.68, -91.12, -81.06, -78.96, -80.61, -84.35, -93.76, -89.69, -89.88, -86.34, -83.31, -82.64, -92.42, -82.43, -83.25, -81.04, -79.61, -78.00, -77.22, -84.05, -88.01, -82.60, -88.71, -84.73, -79.98, -82.12, -84.64, -85.14, -82.29, -79.68, -82.96, -87.79, -86.36, -92.06, -93.07, -91.15, -86.20, -89.66, -82.61, -77.81, -81.92, -82.62, -67.12, -68.10, -80.92, -82.03, -80.65, -81.40, -80.80, -81.49, -78.21, -75.32, -83.56, -86.01, -92.01, -88.31, -84.46, -85.37, -93.23, -81.71, -87.08, -91.21, -91.93, -84.57, -82.60, -81.06, -80.61, -86.22, -102.80, -89.57, -81.00, -82.87, -89.48, -85.28, -92.93, -87.55, -86.76, -84.50, -86.91, -80.11, -85.70, -93.79, -75.29, -77.92, -83.03, -86.09, -92.75, -87.02, -86.78, -83.11, -89.26, -84.76']" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lines[]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4076" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(lines[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def redirectOut(port=port, host=host):\n", + " \"\"\"\n", + " connect caller's standard output stream to a socket for GUI to listen\n", + " start caller after listener started, else connect fails before accept\n", + " \"\"\"\n", + " sock = socket(socket.AF_INET, socket.SOCK_STREAM)\n", + " sock.connect((host, port)) # caller operates in client mode\n", + " file = sock.makefile('w') # file interface: text, buffered\n", + " sys.stdout = file # make prints go to sock.send\n", + " return sock # if caller needs to access it raw\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "import nbdev; nbdev.nbdev_export()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.6 64-bit (system)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + }, + "vscode": { + "interpreter": { + "hash": "275c0bab991b3bbda35e92d9ccf3212909bc270b1dca3dc32c17c55c450cf79d" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..5c0e7ce --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include settings.ini +include LICENSE +include CONTRIBUTING.md +include README.md +recursive-exclude * __pycache__ diff --git a/README.md b/README.md index bc444ad..9aa221c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# hackrf2tcp -This repository holds python wrappers of hackrf_sweep cli +hackrf2tcp +================ + + + +This file will become your README and also the index of your +documentation. + +## Install + +``` sh +pip install hackrf2tcp +``` + +## How to use + +Fill me in please! Don’t forget code examples: + +``` python +1+1 +``` + + 2 diff --git a/_quarto.yml b/_quarto.yml new file mode 100644 index 0000000..0a6dfcb --- /dev/null +++ b/_quarto.yml @@ -0,0 +1,20 @@ +project: + type: website + +format: + html: + theme: cosmo + css: styles.css + toc: true + +website: + twitter-card: true + open-graph: true + repo-actions: [issue] + navbar: + background: primary + search: true + sidebar: + style: floating + +metadata-files: [nbdev.yml, sidebar.yml] \ No newline at end of file diff --git a/client.py b/client.py new file mode 100644 index 0000000..5747a3b --- /dev/null +++ b/client.py @@ -0,0 +1,93 @@ +import socket +import json +from typing import Optional + +HEADER_SIZE = 16 +SERVER = socket.gethostbyname(socket.gethostname()) +PORT = 4242 +FORMAT = 'utf-8' +DISCONNECT_MSG = 'DISCONNECT' +# Create a socket +# socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX +# socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets +client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +# Connect to a given ip and port +client_socket.connect((SERVER, PORT)) + +# Set connection to non-blocking state, so .recv() call won;t block, just return some exception we'll handle +client_socket.setblocking(False) + +def encode_msg(msg): + message = msg.encode(FORMAT) + return bytes(f"{len(message):<{HEADER_SIZE}}", 'utf-8') + message + + +def send(msg): + client_socket.send(encode_msg(msg)) + while msg_length := client_socket.recv(HEADER_SIZE).decode(FORMAT): + msg_length = int(msg_length) + if not msg_length: break + msg = client_socket.recv(msg_length).decode(FORMAT) + print(msg) + if msg == 'DISCONNECT': break + +def fix_limits(value, min_val, max_val): + if value < min_val: + return min_val + return max_val if value > max_val else value + +def sweep(serial_number: Optional[str] = None, # -d: Serial number of desired HackRF + amp_enable: bool = False, # -a: RX RF amplifier True=Enable, False=Disable + freq_min: int = 1, # -f: Minimum frequency in MHz + freq_max: 6000, # -f: Maximum frequency in MHz + antenna_enable: bool = False, # -p: Antenna port power, True=Enable, False=Disable + lna_gain: int = 0, # -l: RX LNA (IF) gain, 0-40dB, 8dB steps + vga_gain: int = 0, # -g: RX VGA (baseband) gain, 0-62dB, 2dB steps + num_samples: int = 8192, # -n: Number of samples per frequency, 8192-4294967296 + bin_width: int 1000000, # -w: FFT bin width (frequency resolution) in Hz + one_shot: Optional[bool] = None, # -1: One shot mode + num_sweeps: Optional[int] = None, # -N: Number of sweeps to perform. If not set it defaults to continous sweeps if not one_shot mode + bin_out: Optional[bool] = None, # -B: binary output. Normalize only desirable if saving the result + bin_invfft_out: Optional[bool] = None, # -I: binary inverse FFT output. Normalize only desirable if saving the result + filename: Optional[str] = None # output file +): + amp_enable = 1 if amp_enable else 0 + + freq_min = fix_limits(freq_min, 0, 6000) + freq_max = fix_limits(freq_max, 0, 6000) + antenna_enable = 1 if antenna_enable else 0 + lna_gain = fix_limits(lna_gain, 0, 40) + vga_gain = fix_limits(vga_gain, 0, 62) + num_samples = fix_limits(num_samples,8192, 4294967296) + + + parameters_dict = {'-d': serial_number, + '-a': 1 if amp_enable else 0, + '-f': f'{freq_min}:{freq_max}', + '-p': antenna_enable, + '-l': lna_gain, + '-g': vga_gain, + '-n': num_samples, + '-w': bin_width, + '-1': one_shot, + '-N': num_sweeps, + '-B': bin_out, + '-I': bin_invfft_out, + '-r': filename, +} + + parameters_dict = {k:v for k,v in parameters_dict.items() if v is not None} + + +# send('-h') + +send('-1') + +send('-f=88:108 -w=10000 -N=10') + +send(DISCONNECT_MSG) + +# while True: +# msg = client_socket.recv(4080) +# print(msg) \ No newline at end of file diff --git a/hackrf2tcp/__init__.py b/hackrf2tcp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hackrf2tcp/_modidx.py b/hackrf2tcp/_modidx.py new file mode 100644 index 0000000..4266f27 --- /dev/null +++ b/hackrf2tcp/_modidx.py @@ -0,0 +1,8 @@ +# Autogenerated by nbdev + +d = { 'settings': { 'branch': 'master', + 'doc_baseurl': '/hackrf2tcp', + 'doc_host': 'https://ronaldokun.github.io', + 'git_url': 'https://github.com/ronaldokun/hackrf2tcp', + 'lib_path': 'hackrf2tcp'}, + 'syms': {'hackrf2tcp.core': {'hackrf2tcp.core.foo': ('core.html#foo', 'hackrf2tcp/core.py')}}} \ No newline at end of file diff --git a/hackrf2tcp/core.py b/hackrf2tcp/core.py new file mode 100644 index 0000000..a909a06 --- /dev/null +++ b/hackrf2tcp/core.py @@ -0,0 +1,7 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb. + +# %% auto 0 +__all__ = ['foo'] + +# %% ../00_core.ipynb 3 +def foo(): pass diff --git a/index.ipynb b/index.ipynb new file mode 100644 index 0000000..42e26e4 --- /dev/null +++ b/index.ipynb @@ -0,0 +1,96 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "from hackrf2tcp.core import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# hackrf2tcp\n", + "\n", + "> This repository holds python wrappers of hackrf_sweep cli" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This file will become your README and also the index of your documentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sh\n", + "pip install hackrf2tcp\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How to use" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fill me in please! Don't forget code examples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1+1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/nbdev.yml b/nbdev.yml new file mode 100644 index 0000000..7effceb --- /dev/null +++ b/nbdev.yml @@ -0,0 +1,9 @@ +project: + output-dir: _docs + +website: + title: "hackrf2tcp" + site-url: "https://ronaldokun.github.io/hackrf2tcp" + description: "This repository holds python wrappers of hackrf_sweep cli" + repo-branch: master + repo-url: "https://github.com/ronaldokun/hackrf2tcp" diff --git a/server.py b/server.py new file mode 100644 index 0000000..8ad1efc --- /dev/null +++ b/server.py @@ -0,0 +1,108 @@ +from errno import ENFILE +import socket +import threading +import subprocess +from fastcore.basics import listify +from time import sleep + +HEADER_SIZE = 16 +SERVER = socket.gethostbyname(socket.gethostname()) +PORT = 4242 +TIMEOUT = 600 +FORMAT = 'utf-8' +DISCONNECT_MSG = bytes(f"{10:<{HEADER_SIZE}}", 'utf-8') + b'DISCONNECT' +END_OF_MESSAGE = '' +CLI = 'hackrf_sweep.exe' + +def parse_command(message): + cmds = [] + for c in message.split(): + cmd = c.split('=') if '=' in c else c + cmds.extend(listify(cmd)) + return cmds + +def process_message(message): + command = parse_command(message) + print(command) + sweep = subprocess.run([CLI] + command, stdout=subprocess.PIPE) + yield from sweep.stdout.splitlines() + +def encode_msg(msg): + message = msg.encode(FORMAT) + return bytes(f"{len(message):<{HEADER_SIZE}}", 'utf-8') + message + + +def handle_client(conn, addr): + print(f'[NEW CONNECTION] {addr} connected.') + try: + while True: # wait to receive information from the client + if msg_length := conn.recv(HEADER_SIZE).decode(FORMAT): + msg_length = int(msg_length) + if not msg_length: break + msg = conn.recv(msg_length).decode(FORMAT) + if msg == "DISCONNECT": break + print(f'[{addr}] {msg}') + for response in process_message(msg): + response = bytes(f"{len(response):<{HEADER_SIZE}}", 'utf-8') + response + conn.send(response) + conn.send(encode_msg(END_OF_MESSAGE)) + finally: + print(f"[DISCONNECTED] {addr} disconnected") + conn.close() + + +def main(server: str = SERVER, # Server IP to listen and receive connection + port: int = PORT, # Port to listen on Server IP +): + print("[STARTING]Server is starting...") + + # Create a socket + # socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX + # socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # SO_ - socket option + # SOL_ - socket option level + # Sets REUSEADDR (as a socket option) to 1 on socket + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + # Bind, so server informs operating system that it's going to use given IP and port + # For a server using 0.0.0.0 means to listen on all available interfaces, useful to connect locally to 127.0.0.1 and remotely to LAN interface IP + server_socket.bind((server, port)) + # This makes server listen to new connections + server_socket.listen() + print(f'[LISTENING] Server is listening on IP {server} PORT {port}') + + timeout = 300 + try: + while True: + # now our endpoint knows about the OTHER endpoint. + clientsocket, address = server_socket.accept() + thread = threading.Thread(target=handle_client, args=(clientsocket, address)) + thread.start() + connections = threading.active_count() - 1 + if not connections: + if not timeout: break + sleep(1) + timeout -= 1 + else: + timeout = TIMEOUT + print(f'[ACTIVE CONNECTIONS] {connections}') + finally: + server_socket.close() + +if __name__ == '__main__': + main() + + + +# print(f'Sweep efetuado com sucesso: {sweep.returncode}') +# for line in sweep.stdout.splitlines(): +# clientsocket.send(line) +# sleep(1) +# server_socket.close() + + + + + diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..15b7b0f --- /dev/null +++ b/settings.ini @@ -0,0 +1,42 @@ +[DEFAULT] +# All sections below are required unless otherwise specified. +# See https://github.com/fastai/nbdev/blob/master/settings.ini for examples. + +### Python library ### +repo = hackrf2tcp +lib_name = %(repo)s +version = 0.0.1 +min_python = 3.7 +license = apache2 + +### nbdev ### +doc_path = _docs +lib_path = hackrf2tcp +nbs_path = . +recursive = False +tst_flags = notest +put_version_in_init = False + +### Docs ### +branch = master +custom_sidebar = False +doc_host = https://%(user)s.github.io +doc_baseurl = /%(repo)s +git_url = https://github.com/%(user)s/%(repo)s +title = %(lib_name)s + +### PyPI ### +audience = Developers +author = Ronaldo S.A. Batista +author_email = ronaldokun@gmail.com +copyright = 2022 onwards, %(author)s +description = This repository holds python wrappers of hackrf_sweep cli +keywords = nbdev jupyter notebook python +language = English +status = 3 +user = ronaldokun + +### Optional ### +# requirements = fastcore pandas +# dev_requirements = +# console_scripts = \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5cf5922 --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +from pkg_resources import parse_version +from configparser import ConfigParser +import setuptools +assert parse_version(setuptools.__version__)>=parse_version('36.2') + +# note: all settings are in settings.ini; edit there, not here +config = ConfigParser(delimiters=['=']) +config.read('settings.ini') +cfg = config['DEFAULT'] + +cfg_keys = 'version description keywords author author_email'.split() +expected = cfg_keys + "lib_name user branch license status min_python audience language".split() +for o in expected: assert o in cfg, "missing expected setting: {}".format(o) +setup_cfg = {o:cfg[o] for o in cfg_keys} + +licenses = { + 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), + 'mit': ('MIT License', 'OSI Approved :: MIT License'), + 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), + 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), + 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), +} +statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', + '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] +py_versions = '3.6 3.7 3.8 3.9 3.10'.split() + +requirements = cfg.get('requirements','').split() +if cfg.get('pip_requirements'): requirements += cfg.get('pip_requirements','').split() +min_python = cfg['min_python'] +lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) +dev_requirements = (cfg.get('dev_requirements') or '').split() + +setuptools.setup( + name = cfg['lib_name'], + license = lic[0], + classifiers = [ + 'Development Status :: ' + statuses[int(cfg['status'])], + 'Intended Audience :: ' + cfg['audience'].title(), + 'Natural Language :: ' + cfg['language'].title(), + ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), + url = cfg['git_url'], + packages = setuptools.find_packages(), + include_package_data = True, + install_requires = requirements, + extras_require={ 'dev': dev_requirements }, + dependency_links = cfg.get('dep_links','').split(), + python_requires = '>=' + cfg['min_python'], + long_description = open('README.md').read(), + long_description_content_type = 'text/markdown', + zip_safe = False, + entry_points = { + 'console_scripts': cfg.get('console_scripts','').split(), + 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] + }, + **setup_cfg) + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..66ccc49 --- /dev/null +++ b/styles.css @@ -0,0 +1,37 @@ +.cell { + margin-bottom: 1rem; +} + +.cell > .sourceCode { + margin-bottom: 0; +} + +.cell-output > pre { + margin-bottom: 0; +} + +.cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { + margin-left: 0.8rem; + margin-top: 0; + background: none; + border-left: 2px solid lightsalmon; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.cell-output > .sourceCode { + border: none; +} + +.cell-output > .sourceCode { + background: none; + margin-top: 0; +} + +div.description { + padding-left: 2px; + padding-top: 5px; + font-style: italic; + font-size: 135%; + opacity: 70%; +}