├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── new-module-request.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── NOTES.md ├── README.md ├── _crontab.py ├── _listvars.py ├── _listvars.sh ├── autowx2.py ├── autowx2_conf.py.example ├── autowx2_functions.py ├── bin ├── aprs.sh ├── calibrate.sh ├── calibrate_full.sh ├── crontab │ ├── daily.sh │ └── weekly.sh ├── dump1090-draw_heatmap.sh ├── dump1090-stream-parser.py ├── dump1090.sh ├── gen-static-page.sh ├── kill_rtl.sh ├── multidemodulator.sh ├── pymultimonaprs.sh ├── radiosonde_auto_rx.sh ├── rtl_433.sh ├── update-keps.sh └── wspr.sh ├── configure.sh ├── docs ├── NOAA19-HVCT.jpg ├── NOAA19-therm.jpg ├── Python-2.7-lightgreen.png ├── badge-email.png ├── meteor │ ├── meteor-ch2.jpg │ └── meteor-combo.jpg ├── nextpass.html ├── nextpass.png ├── passtable.jpg ├── sstv │ ├── iss1.jpeg │ └── iss3.jpeg ├── www-dynamic+shadow.jpg ├── www-dynamic.png ├── www-static+shadow.jpg └── www-static.jpg ├── environment.yml ├── genpasstable.py ├── install.sh ├── modules ├── _template │ └── fm.sh ├── fm │ └── fm.sh ├── iss │ ├── iss_voice_iq.sh │ ├── iss_voice_mp3.sh │ └── iss_voice_wav.sh ├── meteor-m2 │ ├── meteor.conf.example │ ├── meteor.sh │ ├── meteor_gallery.sh │ └── meteor_record.sh ├── noaa │ ├── _loop_noaa_gallery.sh │ ├── noaa-test.sh │ ├── noaa.conf.example │ ├── noaa.sh │ ├── noaa_gallery.sh │ ├── noaa_process.sh │ ├── noaa_record.sh │ └── tests │ │ └── 20180118-1504_NOAA-19.wav └── radiosonde │ └── run_radiosonde_scanner.sh ├── requirements.txt └── var ├── flask ├── static │ ├── bootstrap.bundle.min.js │ ├── bootstrap.bundle.min.js.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── fancybox │ │ ├── jquery.fancybox.min.css │ │ ├── jquery.fancybox.min.js │ │ └── source.md │ ├── jquery.min.js │ ├── logo-nav.css │ ├── socket.io.min.js │ └── socket.io.min.js.map └── templates │ └── index.html └── www ├── css ├── bootstrap.bundle.min.js ├── bootstrap.min.css ├── fancybox │ ├── jquery.fancybox.min.css │ ├── jquery.fancybox.min.js │ └── source.md ├── jquery.min.js └── logo-nav.css └── index.tpl /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-module-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New module request 3 | about: Suggest a new module for autowx2 4 | 5 | --- 6 | 7 | **Please briefly describe the aim of the proposed module** 8 | A clear and concise description of what the module is for. 9 | 10 | **Is the tool/program available for running in command line?** 11 | What are the parameters to run the program (ie, the frequency, input file, output file, etc), eg: 12 | `satellite_capture.py $input_freq $output_directory` 13 | 14 | **Where can I get the program from?** 15 | Please provide url for the tool's homepage, download section etc 16 | 17 | **Is the program free and/or open source** 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | **What hardware is require to use this module?** 21 | Special antenna or a regular broadband is OK? Any other, if applicable. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | bin/tests/*.png 3 | *.kate-swp 4 | tests 5 | var/shifthistory.csv 6 | var/tle/* 7 | bin/sources/ 8 | basedir_conf.py 9 | autowx2_conf.py 10 | recordings 11 | bin/pymultimonaprs.confs/ 12 | bin/heatmap.py 13 | var/www/nextpass.png 14 | autowx2_conf.py 15 | git-merge-devel.sh 16 | git-wyslij-github.sh 17 | var/dongleshift.txt 18 | var/gsm_channel.txt 19 | var/www/*.html 20 | var/www/*.tmp 21 | modules/noaa/noaa.conf 22 | modules/meteor-m2/meteor.conf 23 | *.bak 24 | satellites.conf 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: trusty 3 | sudo: required 4 | cache: 5 | pip: true 6 | directories: 7 | - bin/sources/ 8 | 9 | python: 10 | - 2.7 11 | 12 | install: 13 | - bash ./install.sh 14 | 15 | script: 16 | - wxtoimg -h 17 | - sox --version 18 | - lame --version 19 | - wxtoimg -V 20 | - wxmap -V 21 | - convert -help 22 | - ./genpasstable.py 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ![badge-email](docs/badge-email.png). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Random 2 | 3 | ``` 4 | 5 | # convert images and add a gray shadow: 6 | # zmniejsz_ramka.sh 7 | 8 | convert xxx.png \( -clone 0 -background gray -shadow 80x3+10+10 \) \( -clone 0 -background gray -shadow 80x3-5-5 \) -reverse -background white -layers merge -resize 600 out.jpg 9 | 10 | # autopep8 11 | autopep8 --in-place --aggressive filename.py 12 | ``` 13 | 14 | 15 | 16 | # MLRPT - meteor decoder 17 | 18 | - decoder webpage: http://5b4az.org/ 19 | - tested for `mlrpt-1.1`, next versions: 1.2, 1.3 - I was unable to compile on my system (Debian like - tips needed) 20 | - compilation: 21 | 22 | ``` 23 | cd ~/Downloads/ 24 | tar jxvf mlrpt-1.1.tar.bz2 25 | cd mlrpt-1.1 26 | ./autogen.sh 27 | #./configure 28 | ./configure CFLAGS="-g -O2" 29 | make -j4 30 | sudo make install 31 | ``` 32 | - images will go to `/home/user/mlrpt/` - edit `/home/user/mlrpt/mlrptrc` for fine tuning 33 | 34 | - `autowx2_conf.py` entry: 35 | 36 | ``` 37 | 'METEOR-M2': { 38 | 'freq': '137900000', # not so important, as mlrpt uses its own fixed value 39 | 'processWith': 'modules/meteor-m2/meteor.sh', 40 | 'priority': 1}, 41 | ``` 42 | 43 | 44 | 45 | # errors 46 | 47 | /usr/local/lib/python2.7/dist-packages/matplotlib/pyplot.py:524: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`). 48 | max_open_warning, RuntimeWarning) 49 | 50 | 51 | plt.close()? 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | autowx2 2 | ============== 3 | 4 | **autowx2** is a set of programs and scripts for schedule satellite and ground recordings with SDR dongle. Bundled plugins include scripts for processing weather APT images from NOAA or METEOR satellites, ISS voice communication recordings and some others. 5 | 6 | ![image](docs/NOAA19-HVCT.jpg) ![image](docs/NOAA19-therm.jpg) 7 | 8 | 9 | 10 | - [autowx2](#autowx2) 11 | - [Introduction](#introduction) 12 | - [Used libraries and acknowledgements](#used-libraries-and-acknowledgements) 13 | - [Hardware requirements](#hardware-requirements) 14 | - [System requirements](#system-requirements) 15 | - [Installation](#installation) 16 | - [x86 and amd64](#x86-and-amd64) 17 | - [Configuration files and other programs](#configuration-files-and-other-programs) 18 | - [Files, subprograms and configs](#files-subprograms-and-configs) 19 | - [autowx2.py](#autowx2py) 20 | - [autowx2\_conf.py](#autowx2_confpy) 21 | - [genpasstable.py](#genpasstablepy) 22 | - [bin directory](#bin-directory) 23 | - [modules](#modules) 24 | - [/modules/noaa](#modulesnoaa) 25 | - [/modules/iss](#modulesiss) 26 | - [/modules/meteor-m2](#modulesmeteor-m2) 27 | - [var directory](#var-directory) 28 | - [Static web pages](#static-web-pages) 29 | - [Webserver](#webserver) 30 | - [Working instances of autowx2](#working-instances-of-autowx2) 31 | - [Issues? Comments? Suggestions?](#issues-comments-suggestions) 32 | 33 | 34 | 35 | # Introduction 36 | 37 | This is a rewritten and fine-tuned version of tools for the automatic weather satellite images capturing. Most directly it bases on cyber-atomus' [autowx](https://github.com/cyber-atomus/autowx) and my fork of autowx. The main differences between this project **autowx2** and previously created tools: 38 | - **high modularity** - all recording and processing are done by separate scripts (modules? plugins?), which can be easily configured to meet one's needs. The main module (called *noaa*) is devoted to the capture of weather data from the NOAA satellites, but with other modules (i.e., *ISS* voice) one can record voice communication from the satellite (tested for ISS :tada: !). 39 | - **configurability** - most (all?) variables can be set up in the config file. For both: the main program and the NOAA module. 40 | - **flexibility** - it can be set up to record satellite transmissions based on the passing predictions as well as fixed time recordings, configured via cron-like syntax. 41 | - **simplicity** (not sure if this is true). List of observed satellites is defined in one place only (the config file) and can be easily modified. 42 | - **time efficiency** - it can perform sdr-related tasks to do between scheduled transmissions (see below) 43 | - other features include: autocallibration between recordings with [kalibrate-rtl](https://github.com/viraptor/kalibrate-rtl); prioritization of recordings (eg., when passing time overlaps, choose one with a higher priority); generation of the [passing/recording table](#genpasstablepy) for the next few hours (static html + image); 44 | 45 | 46 | 47 | [![badge-travis](https://api.travis-ci.org/filipsPL/autowx2.svg?branch=master)](https://travis-ci.org/filipsPL/autowx2) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FfilipsPL%2Fautowx2.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FfilipsPL%2Fautowx2?ref=badge_shield) [![Updates](https://pyup.io/repos/github/filipsPL/autowx2/shield.svg)](https://pyup.io/repos/github/filipsPL/autowx2/) [![release version](https://img.shields.io/github/release/filipsPL/autowx2.svg)](https://github.com/filipsPL/autowx2/releases) 48 | 49 | **autowx2** was tested and successfully applied to schedule recordings of: 50 | - [x] NOAA weather satellites 51 | - [x] METEOR-M2 weather satellite :globe_with_meridians: 52 | - [x] ISS transmissions (voice, [SSTV](https://github.com/filipsPL/autowx2/issues/35)) 53 | - [x] Fox-1B satellite transmissions 54 | - [x] Fixed-time radiosondes (meteo baloons :balloon:) observations (with [radiosonde_auto_rx](https://github.com/projecthorus/radiosonde_auto_rx/wiki)) 55 | - [x] Fixed-time FM recordings 56 | 57 | **autowx2** can be easily configured to do other useful things (with SDR dongle) while waiting for the next scheduled transmissions. Tested and available "plugins" include: 58 | - [x] APRS decoding (just log or act as a iGate) 59 | - [x] ADS-B with `dump1090`, heatmap plotting 60 | - [x] Radiosonde monitoring with [radiosonde_auto_rx](https://github.com/projecthorus/radiosonde_auto_rx/wiki) 61 | 62 | 63 | ## Used libraries and acknowledgements 64 | 65 | - [autowx](https://github.com/cyber-atomus/autowx) by cyber-atomus 66 | - [parse-crontab](https://github.com/josiahcarlson/parse-crontab) - the python crontab parser 67 | - [pypredict](https://github.com/nsat/pypredict) 68 | - [wxtoimg](http://www.wxtoimg.com/downloads/) 69 | - [kalibrate-rtl](https://github.com/viraptor/kalibrate-rtl) 70 | - [bootstrap logo-nav](https://startbootstrap.com/template-overviews/logo-nav/) www template, Copyright 2013-2018 Blackrock Digital LLC. Code released under the [MIT](https://github.com/BlackrockDigital/startbootstrap-logo-nav/blob/gh-pages/LICENSE) license 71 | - [https://github.com/fancyapps/fancyBox](fancyBox) jQuery lightbox script for displaying images, videos and more 72 | - Special acknowledgements: [Stack Overflow community](https://stackoverflow.com/) 73 | 74 | These scripts may be used by the autowx2 in the free time, e.g., to track airplanes, capture APRS signals etc: 75 | 76 | - [dump1090](https://github.com/MalcolmRobb/dump1090) - tested with MalcolmRobb's fork 77 | - [dump1090-stream-parser.py](https://github.com/yanofsky/dump1090-stream-parser) by yanofsky 78 | - [heatmap](https://github.com/filipsPL/heatmap) a fork of the great heatmap by sethoscope, modified by filipsPL to support sqlite 79 | - [osmviz](http://cbick.github.io/osmviz/html/) 80 | - [multimon-ng](https://github.com/sq5bpf/multimon-ng-stqc) fork by sq5bpf with STQC decoding support 81 | - [radiosonde_auto_rx](https://github.com/projecthorus/radiosonde_auto_rx/wiki) - Radiosonde monitoring 82 | 83 | 84 | # Hardware requirements 85 | 86 | - usb dvbt dongle, like RTL2832 DVB-T tuner 87 | - antenna good enough to capture the signal of your interest. For antenna dedicated for capturing NOAAs telemetry, see a [simple 137 MHz V-Dipole](https://www.rtl-sdr.com/simple-noaameteor-weather-satellite-antenna-137-mhz-v-dipole/), for example. If you want to use the script _also_ for other applications (capturing APRS signals, using `dump1090` for tracking airplanes), any broadband antenna should be good. 88 | 89 | # System requirements 90 | 91 | - python 3 - I recommend conda environment 92 | - bash (sh, csh will be also OK) 93 | - installed and working DVB-T dongle; to make this long story short: 94 | - adding the following statement to `/etc/udev/rules.d/20.rtlsdr.rules`: 95 | 96 | ``` 97 | SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr" 98 | ``` 99 | - blacklisting the `dvb_usb_rtl28xxu` module: 100 | ``` 101 | echo "blacklist dvb_usb_rtl28xxu" >> /etc/modprobe.d/rtl-sdr-blacklist.conf 102 | ``` 103 | - for more details, [see here](http://www.instructables.com/id/rtl-sdr-on-Ubuntu/) 104 | - for installation script on the Debian, Debian-like systems and Raspberry Pi, see [the installation script](install.sh) :warning: use with care! inspect and tune before execution! 105 | 106 | # Installation 107 | 108 | :warning: Tested for x86, amd64 and [Raspberry Pi](https://github.com/filipsPL/autowx2/issues/29) 109 | 110 | ## x86 and amd64 111 | 112 | 0. Check the section [hardware](#hardware-requirements) and [system requirements](#system-requirements) 113 | 1. Fetch sources: `git clone --depth 1 git@github.com:filipsPL/autowx2.git` 114 | - alternatively: `git clone --depth 1 https://github.com/filipsPL/autowx2.git` 115 | 2. Inspect the script `install.sh`, modify if needed. In most cases, it should work out of the box (for debian and debian-like systems; tested on debian, ubuntu, mint and travis debian like linux). Modify *wxtoimg* section to fetch sources that matches your architecture. 116 | - uncomment section `pip` or `conda` depending on your preferences of installing python packages 117 | 3. If you are fine with the above script, run it with `bash install.sh`. :warning: use at your own risk! 118 | 4. Edit the main config file `autowx2_conf.py` 119 | 5. Edit your system's crontab file and add the `bin/update-keps.sh` script to it, eg:
`0 4 * * * path/to/autowx2/bin/update-keps.sh 1> /dev/null 2>/dev/null`
you can also trigger it manually from time to time. 120 | 6. Run the main program `autowx2.py`, wait for the next transit and marvel at the beautiful images (or other recordings) 121 | 122 | 123 | # Configuration files and other programs 124 | 125 | ## Files, subprograms and configs 126 | 127 | ### autowx2.py 128 | 129 | The main program to do all calculation, pass predictions and launch modules. 130 | 131 | ``` 132 | autowx2.py - the main program, the governor of all other programs and scripts 133 | autowx2_conf.py - the config file 134 | ``` 135 | 136 | ### autowx2_conf.py 137 | 138 | The config file of the main program, may be used also by the decoding modules. It is used by te *noaa* module. 139 | 140 | **satellitesData** - the dictionary (in the python style) of satellites to be observed and processed (eg., weather satellites) OR fixed times for recordings (eg., listening to the WeatherFax transmissions). 141 | - for the *satellites*, three values must be set: 142 | - the satellite name (eg., 'NOAA 18'), must be the same as one found in TLE file 143 | - `freq` - the frequency to listen at 144 | - `processWith` - the path to the script/module to run during the transit 145 | - `priority` - priority of the recording (if two or more overlaps); the lower number - the higher priority 146 | - for the fixed time recordings: 147 | - the id of the entry (any arbitrary string is ok) 148 | - `freq` - the frequency to listen at 149 | - `processWith` - the path to the script/module to run 150 | - `fixedTime` - the fixed time of recording in the [cron](https://en.wikipedia.org/wiki/Cron#Overview) style. 151 | - `fixedDuration` - the duration of the recording 152 | - `priority` - priority of the recording (if two or more overlaps); the lower number - the higher priority 153 | 154 | Sample `satellitesData` dictionary: 155 | 156 | ``` 157 | satellitesData = { 158 | 'NOAA-18': { 159 | 'freq': '137912500', 160 | 'processWith': 'modules/noaa/noaa.sh', 161 | 'priority': 1}, 162 | 'NOAA-15': { 163 | 'freq': '137620000', 164 | 'processWith': 'modules/noaa/noaa.sh', 165 | 'priority': 1}, 166 | 'NOAA-19': { 167 | 'freq': '137100000', 168 | 'processWith': 'modules/noaa/noaa.sh', 169 | 'priority': 1}, 170 | 'ISS': { 171 | # voice channel 172 | 'freq': '145800000', 173 | 'processWith': 'modules/iss/iss_voice_mp3.sh', 174 | 'priority': 5}, 175 | 'PR3_NEWS': { 176 | 'freq': '98988000', 177 | 'processWith': 'modules/fm/fm.sh', 178 | 'fixedTime': '0 7-23 * * *', 179 | 'fixedDuration': 300, 180 | 'priority': 2}, 181 | 'LILACSAT-1': { 182 | 'freq': '436510000', 183 | 'processWith': 'modules/iss/iss_voice_mp3.sh', 184 | 'priority': 3}, 185 | 'Radiosonde': { # twice a day watch for meteo balloons 186 | 'freq': '98796500', 187 | 'processWith': 'modules/radiosonde/run_radiosonde_scanner.sh', 188 | 'fixedTime': '20 0,12 * * *', 189 | 'fixedDuration': 12000, 190 | 'priority': 1}, 191 | 192 | } 193 | 194 | ``` 195 | 196 | - :warning: TODO: dynamic priority calculation basing on the transit features, like azimuth or altitude. 197 | 198 | ### genpasstable.py 199 | 200 | A utility to generate transit plan for the next few hours. Two elements are generated: 201 | - a static html page (or actually the html table to be included in the page), [see example](docs/nextpass.html) 202 | - a static png (or svg) image showing the transit plan in the form of the Gantt chart; this, combined with above gives beautiful pass info page (glued together by `gen-static-page.sh` to `autowx2/var/www/table.html`): 203 | 204 | ![image](docs/passtable.jpg) 205 | 206 | 207 | ### bin directory 208 | 209 | Various auxiliary programs. 210 | 211 | ``` 212 | aprs.sh - aprs script to listen to and decode APRS data; to be run in free time 213 | pymultimonaprs.sh - aprs iGate launcher (pymultimonaprs must be installed) 214 | kalibruj_initial.sh - calibrating script - initialization 215 | kalibruj.sh - calibrating script - getting the drift 216 | update-keps.sh - keplers updated; can/should be run from cron 217 | crontab/ - proposal of scripts to be run periodicaly 218 | ``` 219 | 220 | ### modules 221 | 222 | Modules/plugins to capture various types of data. Can be customized by any type of script (here bash scripts were used in most cases). 223 | 224 | ``` 225 | fm - sample module to record FM radio to mp3 file 226 | iss - module for capturing voice data from ISS and others. Tested and works for ISS :tada: 227 | noaa - module for capturing weather data from NOAA satellites (see below) 228 | radiosonde - module for running the radiosonde scanner (to be installed separately) 229 | meteor-m2 - module for running the meteor m2 capturing and processing programm 230 | ``` 231 | 232 | #### /modules/noaa 233 | 234 | Module for capturing weather data from NOAA satellites. 235 | 236 | ``` 237 | noaa.sh - the main module file (bash) - launches below files: 238 | noaa_record.sh - recordinf of a sound via rtl_fm 239 | noaa_process.sh - processing of the recorded wav file, generates maps etc. 240 | 241 | _loop_noaa_gallery.sh - generate gallery 242 | noaa_gallery.sh - generate gallery 243 | 244 | tests - directory with a test data 245 | ``` 246 | 247 | 248 | #### /modules/iss 249 | 250 | Generic module to capture various audio and raw packages (e.g., from ISS, but not only): 251 | 252 | ``` 253 | iss_voice_iq.sh - record audio in iq/raw format 254 | iss_voice_mp3.sh - record audio in mp3 format 255 | iss_voice_wav.sh - record audio in wav format 256 | ``` 257 | 258 | #### /modules/meteor-m2 259 | 260 | - see: [wiki page](https://github.com/filipsPL/autowx2/wiki/meteor-m2) 261 | 262 | 263 | ### var directory 264 | 265 | Variable data. 266 | 267 | ``` 268 | dongleshift.txt - current dongle shift 269 | nextpass.* - list and plot of the next passes 270 | tle/ - directory with tle data 271 | flask/ - flask webserver stuff (see flask documentation): 272 | static/ - css, js 273 | templates/ - html template(s) 274 | www/ - stuff for static webpages; templates and output data 275 | css/ - css, js 276 | ``` 277 | 278 | # Static web pages 279 | 280 | Modules may generate static webpages - snippets (see: noaa `noaa_gallery.sh` script). Then, the `bin/gen-static-page.sh` script collects these snippets and build a static webpage with all the data. 281 | 282 | ![static web page](docs/www-static+shadow.jpg) 283 | 284 | The web page is generated into `/var/www/` (the default location). 285 | 286 | # Webserver 287 | 288 | autowx2 is equipped with a simple flask webserver showing what is going on - displaying current logs (with some limitations, i.e., not showing logs of external programs - solution needed) and updated pass list. 289 | 290 | ![static web page](docs/www-dynamic+shadow.jpg) 291 | 292 | The default address is `http://localhost:5010/` (the port may be changed in the config file via `webInterfacePort` variable) 293 | 294 | # Working instances of autowx2 295 | 296 | If you have a working and publically accesible autowx2 instance, please share with us! 297 | 298 | - [Tim SA7BNT](https://sa7bnt.funkerportal.de/autowx2/) 299 | 300 | 301 | # Issues? Comments? Suggestions? 302 | 303 | - [Report an issue here](https://github.com/filipsPL/autowx2/issues/) 304 | -------------------------------------------------------------------------------- /_crontab.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | crontab.py 4 | 5 | Written July 15, 2011 by Josiah Carlson 6 | Copyright 2011-2021 Josiah Carlson 7 | Released under the GNU LGPL v2.1 and v3 8 | available: 9 | http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 10 | http://www.gnu.org/licenses/lgpl.html 11 | 12 | Other licenses may be available upon request. 13 | 14 | ''' 15 | 16 | from collections import namedtuple 17 | from datetime import datetime, timedelta 18 | import random 19 | import sys 20 | import warnings 21 | 22 | _ranges = [ 23 | (0, 59), 24 | (0, 59), 25 | (0, 23), 26 | (1, 31), 27 | (1, 12), 28 | (0, 6), 29 | (1970, 2099), 30 | ] 31 | 32 | ENTRIES = len(_ranges) 33 | SECOND_OFFSET, MINUTE_OFFSET, HOUR_OFFSET, DAY_OFFSET, MONTH_OFFSET, WEEK_OFFSET, YEAR_OFFSET = range(ENTRIES) 34 | 35 | _attribute = [ 36 | 'second', 37 | 'minute', 38 | 'hour', 39 | 'day', 40 | 'month', 41 | 'isoweekday', 42 | 'year' 43 | ] 44 | _alternate = { 45 | MONTH_OFFSET: {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 46 | 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov':11, 'dec':12}, 47 | WEEK_OFFSET: {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 48 | 'sat': 6}, 49 | } 50 | _aliases = { 51 | '@yearly': '0 0 1 1 *', 52 | '@annually': '0 0 1 1 *', 53 | '@monthly': '0 0 1 * *', 54 | '@weekly': '0 0 * * 0', 55 | '@daily': '0 0 * * *', 56 | '@hourly': '0 * * * *', 57 | } 58 | 59 | WARNING_CHANGE_MESSAGE = '''\ 60 | Version 0.22.0+ of crontab will use datetime.utcnow() and 61 | datetime.utcfromtimestamp() instead of datetime.now() and 62 | datetime.fromtimestamp() as was previous. This had been a bug, which will be 63 | remedied. If you would like to keep the *old* behavior: 64 | `ct.next(..., default_utc=False)` . If you want to use the new behavior *now*: 65 | `ct.next(..., default_utc=True)`. If you pass a datetime object with a tzinfo 66 | attribute that is not None, timezones will *just work* to the best of their 67 | ability. There are tests...''' 68 | 69 | 70 | if sys.version_info >= (3, 0): 71 | _number_types = (int, float) 72 | xrange = range 73 | else: 74 | _number_types = (int, long, float) 75 | 76 | SECOND = timedelta(seconds=1) 77 | MINUTE = timedelta(minutes=1) 78 | HOUR = timedelta(hours=1) 79 | DAY = timedelta(days=1) 80 | WEEK = timedelta(days=7) 81 | MONTH = timedelta(days=28) 82 | YEAR = timedelta(days=365) 83 | 84 | WARN_CHANGE = object() 85 | 86 | # find the next scheduled time 87 | def _end_of_month(dt): 88 | ndt = dt + DAY 89 | while dt.month == ndt.month: 90 | ndt += DAY 91 | return ndt.replace(day=1) - DAY 92 | 93 | def _month_incr(dt, m): 94 | odt = dt 95 | dt += MONTH 96 | while dt.month == odt.month: 97 | dt += DAY 98 | # get to the first of next month, let the backtracking handle it 99 | dt = dt.replace(day=1) 100 | return dt - odt 101 | 102 | def _year_incr(dt, m): 103 | # simple leapyear stuff works for 1970-2099 :) 104 | mod = dt.year % 4 105 | if mod == 0 and (dt.month, dt.day) < (2, 29): 106 | return YEAR + DAY 107 | if mod == 3 and (dt.month, dt.day) > (2, 29): 108 | return YEAR + DAY 109 | return YEAR 110 | 111 | _increments = [ 112 | lambda *a: SECOND, 113 | lambda *a: MINUTE, 114 | lambda *a: HOUR, 115 | lambda *a: DAY, 116 | _month_incr, 117 | lambda *a: DAY, 118 | _year_incr, 119 | lambda dt,x: dt.replace(second=0), 120 | lambda dt,x: dt.replace(minute=0), 121 | lambda dt,x: dt.replace(hour=0), 122 | lambda dt,x: dt.replace(day=1) if x > DAY else dt, 123 | lambda dt,x: dt.replace(month=1) if x > DAY else dt, 124 | lambda dt,x: dt, 125 | ] 126 | 127 | # find the previously scheduled time 128 | def _day_decr(dt, m): 129 | if m.day.input != 'l': 130 | return -DAY 131 | odt = dt 132 | ndt = dt = dt - DAY 133 | while dt.month == ndt.month: 134 | dt -= DAY 135 | return dt - odt 136 | 137 | def _month_decr(dt, m): 138 | odt = dt 139 | # get to the last day of last month, let the backtracking handle it 140 | dt = dt.replace(day=1) - DAY 141 | return dt - odt 142 | 143 | def _year_decr(dt, m): 144 | # simple leapyear stuff works for 1970-2099 :) 145 | mod = dt.year % 4 146 | if mod == 0 and (dt.month, dt.day) > (2, 29): 147 | return -(YEAR + DAY) 148 | if mod == 1 and (dt.month, dt.day) < (2, 29): 149 | return -(YEAR + DAY) 150 | return -YEAR 151 | 152 | def _day_decr_reset(dt, x): 153 | if x >= -DAY: 154 | return dt 155 | cur = dt.month 156 | while dt.month == cur: 157 | dt += DAY 158 | return dt - DAY 159 | 160 | _decrements = [ 161 | lambda *a: -SECOND, 162 | lambda *a: -MINUTE, 163 | lambda *a: -HOUR, 164 | _day_decr, 165 | _month_decr, 166 | lambda *a: -DAY, 167 | _year_decr, 168 | lambda dt,x: dt.replace(second=59), 169 | lambda dt,x: dt.replace(minute=59), 170 | lambda dt,x: dt.replace(hour=23), 171 | _day_decr_reset, 172 | lambda dt,x: dt.replace(month=12) if x < -DAY else dt, 173 | lambda dt,x: dt, 174 | _year_decr, 175 | ] 176 | 177 | Matcher = namedtuple('Matcher', 'second, minute, hour, day, month, weekday, year') 178 | 179 | def _assert(condition, message, *args): 180 | if not condition: 181 | raise ValueError(message%args) 182 | 183 | class _Matcher(object): 184 | __slots__ = 'allowed', 'end', 'any', 'input', 'which', 'split', 'loop' 185 | def __init__(self, which, entry, loop=False): 186 | """ 187 | input: 188 | `which` - index into the increment / validation lookup tables 189 | `entry` - the value of the column 190 | `loop` - do we loop when we validate / construct counts 191 | (turning 55-5,1 -> 0,1,2,3,4,5,55,56,57,58,59 in a "minutes" column) 192 | """ 193 | _assert(0 <= which <= YEAR_OFFSET, 194 | "improper number of cron entries specified") 195 | self.input = entry.lower() 196 | self.split = self.input.split(',') 197 | self.which = which 198 | self.allowed = set() 199 | self.end = None 200 | self.any = '*' in self.split or '?' in self.split 201 | self.loop = loop 202 | 203 | for it in self.split: 204 | al, en = self._parse_crontab(which, it) 205 | if al is not None: 206 | self.allowed.update(al) 207 | self.end = en 208 | _assert(self.end is not None, 209 | "improper item specification: %r", entry.lower() 210 | ) 211 | self.allowed = frozenset(self.allowed) 212 | 213 | def __call__(self, v, dt): 214 | for i, x in enumerate(self.split): 215 | if x == 'l': 216 | if v == _end_of_month(dt).day: 217 | return True 218 | 219 | elif x.startswith('l'): 220 | # We have to do this in here, otherwise we can end up, for 221 | # example, accepting *any* Friday instead of the *last* Friday. 222 | if dt.month == (dt + WEEK).month: 223 | continue 224 | 225 | x = x[1:] 226 | if x.isdigit(): 227 | x = int(x) if x != '7' else 0 228 | if v == x: 229 | return True 230 | continue 231 | 232 | start, end = map(int, x.partition('-')[::2]) 233 | allowed = set(range(start, end+1)) 234 | if 7 in allowed: 235 | allowed.add(0) 236 | if v in allowed: 237 | return True 238 | 239 | return self.any or v in self.allowed 240 | 241 | def __lt__(self, other): 242 | if self.any: 243 | return self.end < other 244 | return all(item < other for item in self.allowed) 245 | 246 | def __gt__(self, other): 247 | if self.any: 248 | return _ranges[self.which][0] > other 249 | return all(item > other for item in self.allowed) 250 | 251 | def __eq__(self, other): 252 | if self.any: 253 | return other.any 254 | return self.allowed == other.allowed 255 | 256 | def __hash__(self): 257 | return hash((self.any, self.allowed)) 258 | 259 | def _parse_crontab(self, which, entry): 260 | ''' 261 | This parses a single crontab field and returns the data necessary for 262 | this matcher to accept the proper values. 263 | 264 | See the README for information about what is accepted. 265 | ''' 266 | 267 | # this handles day of week/month abbreviations 268 | def _fix(it): 269 | if which in _alternate and not it.isdigit(): 270 | if it in _alternate[which]: 271 | return _alternate[which][it] 272 | _assert(it.isdigit(), 273 | "invalid range specifier: %r (%r)", it, entry) 274 | it = int(it, 10) 275 | _assert(_start <= it <= _end_limit, 276 | "item value %r out of range [%r, %r]", 277 | it, _start, _end_limit) 278 | return it 279 | 280 | # this handles individual items/ranges 281 | def _parse_piece(it): 282 | if '-' in it: 283 | start, end = map(_fix, it.split('-')) 284 | # Allow "sat-sun" 285 | if which in (DAY_OFFSET, WEEK_OFFSET) and end == 0: 286 | end = 7 287 | elif it == '*': 288 | start = _start 289 | end = _end 290 | else: 291 | start = _fix(it) 292 | end = _end 293 | if increment is None: 294 | return set([start]) 295 | 296 | _assert(_start <= start <= _end_limit, 297 | "%s range start value %r out of range [%r, %r]", 298 | _attribute[which], start, _start, _end_limit) 299 | _assert(_start <= end <= _end_limit, 300 | "%s range end value %r out of range [%r, %r]", 301 | _attribute[which], end, _start, _end_limit) 302 | if not self.loop: 303 | _assert(start <= end, 304 | "%s range start value %r > end value %r", 305 | _attribute[which], start, end) 306 | 307 | if increment and not self.loop: 308 | next_value = start + increment 309 | _assert(next_value <= _end_limit, 310 | "first next value %r is out of range [%r, %r]", 311 | next_value, start, _end_limit) 312 | 313 | if start <= end: 314 | return set(range(start, end+1, increment or 1)) 315 | 316 | right = set(range(end, _end_limit + 1, increment or 1)) 317 | first = max(right, default=end + (increment or 1)) % _end_limit 318 | return set(range(first, start+1, increment or 1)) | right 319 | 320 | _start, _end = _ranges[which] 321 | _end_limit = _end 322 | # wildcards 323 | if entry in ('*', '?'): 324 | if entry == '?': 325 | _assert(which in (DAY_OFFSET, WEEK_OFFSET), 326 | "cannot use '?' in the %r field", _attribute[which]) 327 | return None, _end 328 | 329 | # last day of the month 330 | if entry == 'l': 331 | _assert(which == DAY_OFFSET, 332 | "you can only specify a bare 'L' in the 'day' field") 333 | return None, _end 334 | 335 | # for the last 'friday' of the month, for example 336 | elif entry.startswith('l'): 337 | _assert(which == WEEK_OFFSET, 338 | "you can only specify a leading 'L' in the 'weekday' field") 339 | es, _, ee = entry[1:].partition('-') 340 | _assert((entry[1:].isdigit() and 0 <= int(es) <= 7) or 341 | (_ and es.isdigit() and ee.isdigit() and 0 <= int(es) <= 7 and 0 <= int(ee) <= 7), 342 | "last specifier must include a day number or range in the 'weekday' field, you entered %r", entry) 343 | return None, _end 344 | 345 | # allow Sunday to be specified as weekday 7 346 | if which == WEEK_OFFSET: 347 | _end_limit = 7 348 | 349 | increment = None 350 | # increments 351 | if '/' in entry: 352 | entry, increment = entry.split('/') 353 | increment = int(increment, 10) 354 | _assert(increment > 0, 355 | "you can only use positive increment values, you provided %r", 356 | increment) 357 | _assert(increment <= _end_limit, 358 | "increment value must be less than %r, you provided %r", 359 | _end_limit, increment) 360 | 361 | # handle singles and ranges 362 | good = _parse_piece(entry) 363 | 364 | # change Sunday to weekday 0 365 | if which == WEEK_OFFSET and 7 in good: 366 | good.discard(7) 367 | good.add(0) 368 | 369 | return good, _end 370 | 371 | 372 | _gv = lambda: str(random.randrange(60)) 373 | 374 | 375 | class CronTab(object): 376 | __slots__ = 'matchers', 'rs' 377 | def __init__(self, crontab, loop=False, random_seconds=False): 378 | """ 379 | inputs: 380 | `crontab` - crontab specification of "[S=0] Mi H D Mo DOW [Y=*]" 381 | `loop` - do we loop when we validate / construct counts 382 | (turning 55-5,1 -> 0,1,2,3,4,5,55,56,57,58,59 in a "minutes" column) 383 | `random_seconds` - randomly select starting second for tasks 384 | """ 385 | self.rs = random_seconds 386 | self.matchers = self._make_matchers(crontab, loop, random_seconds) 387 | 388 | def __eq__(self, other): 389 | if not isinstance(other, CronTab): 390 | return False 391 | match_last = self.matchers[1:] == other.matchers[1:] 392 | return match_last and ((self.rs and other.rs) or (not self.rs and 393 | not other.rs and self.matchers[0] == other.matchers[0])) 394 | 395 | def _make_matchers(self, crontab, loop, random_seconds): 396 | ''' 397 | This constructs the full matcher struct. 398 | ''' 399 | crontab = _aliases.get(crontab, crontab) 400 | ct = crontab.split() 401 | 402 | if len(ct) == 5: 403 | ct.insert(0, _gv() if random_seconds else '0') 404 | ct.append('*') 405 | elif len(ct) == 6: 406 | ct.insert(0, _gv() if random_seconds else '0') 407 | _assert(len(ct) == 7, 408 | "improper number of cron entries specified; got %i need 5 to 7"%(len(ct,))) 409 | 410 | matchers = [_Matcher(which, entry, loop) for which, entry in enumerate(ct)] 411 | 412 | return Matcher(*matchers) 413 | 414 | def _test_match(self, index, dt): 415 | ''' 416 | This tests the given field for whether it matches with the current 417 | datetime object passed. 418 | ''' 419 | at = _attribute[index] 420 | attr = getattr(dt, at) 421 | if index == WEEK_OFFSET: 422 | attr = attr() % 7 423 | return self.matchers[index](attr, dt) 424 | 425 | def next(self, now=None, increments=_increments, delta=True, default_utc=WARN_CHANGE, return_datetime=False): 426 | ''' 427 | How long to wait in seconds before this crontab entry can next be 428 | executed. 429 | ''' 430 | if default_utc is WARN_CHANGE and (isinstance(now, _number_types) or (now and not now.tzinfo) or now is None): 431 | warnings.warn(WARNING_CHANGE_MESSAGE, FutureWarning, 2) 432 | default_utc = False 433 | 434 | now = now or (datetime.utcnow() if default_utc and default_utc is not WARN_CHANGE else datetime.now()) 435 | if isinstance(now, _number_types): 436 | now = datetime.utcfromtimestamp(now) if default_utc else datetime.fromtimestamp(now) 437 | 438 | # handle timezones if the datetime object has a timezone and get a 439 | # reasonable future/past start time 440 | onow, now = now, now.replace(tzinfo=None) 441 | tz = onow.tzinfo 442 | future = now.replace(microsecond=0) + increments[0]() 443 | if future < now: 444 | # we are going backwards... 445 | _test = lambda: future.year < self.matchers.year 446 | if now.microsecond: 447 | future = now.replace(microsecond=0) 448 | else: 449 | # we are going forwards 450 | _test = lambda: self.matchers.year < future.year 451 | 452 | # Start from the year and work our way down. Any time we increment a 453 | # higher-magnitude value, we reset all lower-magnitude values. This 454 | # gets us performance without sacrificing correctness. Still more 455 | # complicated than a brute-force approach, but also orders of 456 | # magnitude faster in basically all cases. 457 | to_test = ENTRIES - 1 458 | while to_test >= 0: 459 | if not self._test_match(to_test, future): 460 | inc = increments[to_test](future, self.matchers) 461 | future += inc 462 | for i in xrange(0, to_test): 463 | future = increments[ENTRIES+i](future, inc) 464 | try: 465 | if _test(): 466 | return None 467 | except: 468 | print(future, type(future), type(inc)) 469 | raise 470 | to_test = ENTRIES-1 471 | continue 472 | to_test -= 1 473 | 474 | # verify the match 475 | match = [self._test_match(i, future) for i in xrange(ENTRIES)] 476 | _assert(all(match), 477 | "\nYou have discovered a bug with crontab, please notify the\n" \ 478 | "author with the following information:\n" \ 479 | "crontab: %r\n" \ 480 | "now: %r", ' '.join(m.input for m in self.matchers), now) 481 | 482 | if return_datetime: 483 | return future.replace(tzinfo=tz) 484 | 485 | if not delta: 486 | onow = now = datetime(1970, 1, 1) 487 | 488 | delay = future - now 489 | if tz: 490 | delay += _fix_none(onow.utcoffset()) 491 | if hasattr(tz, 'localize'): 492 | delay -= _fix_none(tz.localize(future).utcoffset()) 493 | else: 494 | delay -= _fix_none(future.replace(tzinfo=tz).utcoffset()) 495 | 496 | return delay.days * 86400 + delay.seconds + delay.microseconds / 1000000. 497 | 498 | def previous(self, now=None, delta=True, default_utc=WARN_CHANGE): 499 | return self.next(now, _decrements, delta, default_utc) 500 | 501 | def test(self, entry): 502 | if isinstance(entry, _number_types): 503 | entry = datetime.utcfromtimestamp(entry) 504 | for index in xrange(ENTRIES): 505 | if not self._test_match(index, entry): 506 | return False 507 | return True 508 | 509 | def _fix_none(d, _=timedelta(0)): 510 | if d is None: 511 | return _ 512 | return d 513 | -------------------------------------------------------------------------------- /_listvars.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | get the config file autowx2_conf and reports variables in a bash-like style 5 | ''' 6 | 7 | from autowx2_conf import * # configuration 8 | 9 | d = locals().copy() 10 | for var in d: 11 | val=d[var] 12 | if not var.startswith("__"): # not global variables 13 | if not str(val).startswith( ("{", "<") ): # not dictionary 14 | print("%s=%s" % (var, val)) 15 | -------------------------------------------------------------------------------- /_listvars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # get the config file autowx2_conf and reports variables in a bash-like style 4 | 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | 8 | echo $baseDir 9 | 10 | for line in $(python $baseDir/_listvars.py); do 11 | if echo $line | grep -F = &>/dev/null; then 12 | varname=$(echo "$line" | cut -d '=' -f 1) 13 | value=$(echo "$line" | cut -d '=' -f 2-) 14 | eval "$varname=$value" 15 | fi 16 | done 17 | 18 | echo $stationName 19 | 20 | # 21 | # add some environmental variables 22 | # 23 | 24 | export autowx2version=$(cd $baseDir && git describe --tags) 25 | -------------------------------------------------------------------------------- /autowx2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # autowx2 - The program for scheduling recordings and processing of the 6 | # satellite and ground radio transmissions (like capturing of the weather 7 | # APT images from NOAA satellites, voice messages from ISS, fixed time 8 | # recordings of WeatherFaxes etc.) 9 | # https://github.com/filipsPL/autowx2/ 10 | # 11 | 12 | # from autowx2_conf import * # configuration 13 | from autowx2_functions import * # all functions and magic hidden here 14 | 15 | # ------------------------------------------------------------------------------------------------------ # 16 | 17 | if __name__ == "__main__": 18 | log("⚡ Program start") 19 | saveToFile("%s/start.tmp" % (wwwDir), str(time.time())) # saves program start date to file 20 | 21 | if cleanupRtl: 22 | log("Killing all remaining rtl_* processes...") 23 | justRun(["bin/kill_rtl.sh"], loggingDir) 24 | 25 | while True: 26 | t1 = Thread(target = mainLoop) 27 | t1.setDaemon(True) 28 | t1.start() 29 | # app.run(debug=True, port=webInterfacePort) 30 | 31 | socketio.run(app, port=webInterfacePort, debug=False) 32 | -------------------------------------------------------------------------------- /autowx2_conf.py.example: -------------------------------------------------------------------------------- 1 | # 2 | # autowx2 - config file 3 | # variables used by autowx2 4 | # 5 | 6 | # 7 | # baseDir is taken from this script 8 | # do not change, run configure.sh to configure 9 | # 10 | from basedir_conf import * 11 | 12 | 13 | # satellites to record 14 | # for list of active satellites and used frequences, see 15 | # http://www.dk3wn.info/p/?page_id=29535 16 | 17 | satellitesData = { 18 | 'NOAA-18': { 19 | 'freq': '137912500', 20 | 'processWith': 'modules/noaa/noaa.sh', 21 | 'priority': 2}, 22 | 'NOAA-15': { 23 | 'freq': '137620000', 24 | 'processWith': 'modules/noaa/noaa.sh', 25 | 'priority': 2}, 26 | 'NOAA-19': { 27 | 'freq': '137100000', 28 | 'processWith': 'modules/noaa/noaa.sh', 29 | 'priority': 2}, 30 | 'METEOR-M2': { 31 | 'freq': '137900000', 32 | 'processWith': 'modules/meteor-m2/meteor.sh', 33 | 'priority': 1}, 34 | 'ISS': { 35 | #'freq': '145800000', # FM U/v VOICE Repeater (Worldwide) and FM SSTV downlink (Worldwide) [OK] 36 | 'freq': '143625000', # FM VHF-1 downlink. Main Russian communications channel. Often active over Moskow. [ ] 37 | #'freq': '145825000', # APRS -- AX.25 1200 Bd AFSK Packet Radio (Worldwide) Downlink [ ] 38 | #'freq': '437800000', # Cross band FM amateur radio repeater with a downlink on 437.800 MHz at the International Space Station (Worldwide) Downlink [ ] 39 | 'processWith': 'modules/iss/iss_voice_mp3.sh', 40 | 'priority': 3}, 41 | # 'PR3_NEWS': { 42 | # 'freq': '98796500', 43 | # 'processWith': 'modules/fm/fm.sh', 44 | # 'fixedTime': '0 12 * * *', 45 | # 'fixedDuration': 300, 46 | # 'priority': 10}, 47 | #'FOX-1A': { # http://www.dk3wn.info/p/?cat=80 48 | #'freq': '145980000', 49 | #'processWith': 'modules/iss/iss_voice_mp3.sh', 50 | #'priority': 4}, 51 | #'FOX-1D': { # http://www.dk3wn.info/p/?cat=80 52 | #'freq': '145880000', 53 | #'processWith': 'modules/iss/iss_voice_mp3.sh', 54 | #'priority': 4}, 55 | #'FOX-1B': { # http://www.dk3wn.info/p/?cat=80 56 | #'freq': '145960000', 57 | #'processWith': 'modules/iss/iss_voice_mp3.sh', 58 | #'priority': 4}, 59 | 60 | } 61 | 62 | 63 | # 64 | # priority time margin - time margin between two overlapping transits with different priorities, in seconds 65 | # 66 | priorityTimeMargin = 240 67 | 68 | 69 | # 70 | # minimal elevation of pass to capture satellite 71 | # 72 | minElev = 20 73 | 74 | # 75 | # skip first n seconds of the pass 76 | # 77 | skipFirst = 20 78 | 79 | # 80 | # skip last n seconds of the pass 81 | # 82 | skipLast = 20 83 | 84 | 85 | # staion information 86 | # lon: positive values for W, negative for E 87 | stationLat = '50.34' 88 | stationLon = '-22.06' 89 | stationAlt = '179' 90 | stationName = 'San Escobar' 91 | 92 | # 93 | # dongle gain 94 | # 95 | dongleGain = '49.6' 96 | 97 | 98 | # 99 | # Kill all rtl_ related processes after/berofr recordings? 100 | # This should be enabled when the user doesn't use another instances which may run rtl software 101 | # 102 | 103 | cleanupRtl = True 104 | 105 | 106 | # 107 | # where to place the www data (static html pages) 108 | # 109 | wwwDir=baseDir + 'var/www/' 110 | 111 | 112 | # 113 | # recording direcotry - where to put recorded files, processed images etc. - the core 114 | # 115 | #recordingDir = baseDir + "recordings/" # will goto recording dir in local dir 116 | recordingDir = wwwDir + "recordings/" # will go to www dir 117 | 118 | 119 | # 120 | # location of the HTML file with a list of next passes 121 | # 122 | htmlNextPassList = wwwDir + 'nextpass.tmp' 123 | 124 | # 125 | # location of the Gantt chart file with a plot of next passes; PNG or SVG file extension possible 126 | # 127 | ganttNextPassList = wwwDir + 'nextpass.png' 128 | 129 | # 130 | # WWW root path - where is your main index.html file 131 | # this could be: 132 | # / - for page in the root www directory, eg. www.domain.org/index.html 133 | # /~filipsPL/ - for page located in filipsPL's home directory 134 | # file:///home/filipsPL/github/autowx2/var/www/ - for files stored locally in the given local directory 135 | 136 | wwwRootPath='/' 137 | 138 | 139 | # 140 | # galleries to include in the static index page 141 | # 1 = inclcude, 0 = don't include 142 | # 143 | includeGalleryNoaa=1 # NOAA recordings 144 | includeGalleryISS=0 # ISS recordings 145 | includeGalleryMeteor=0 # Meteor recordings 146 | includeGalleryLogs=1 # logs in plain text 147 | includeGalleryDump1090=0 # dump1090 data 148 | 149 | 150 | 151 | # 152 | # port at which webserver will be listening 153 | # 154 | webInterfacePort = 5010 155 | 156 | # script tha will be used whle waiting for the next pass; set False if we just want to sleep 157 | # by default, this script will get the parameter of duration of the time to be run and the recent dongleShift 158 | # scriptToRunInFreeTime = False # does nothing 159 | scriptToRunInFreeTime = baseDir + "bin/aprs.sh" # APRS monitor 160 | #scriptToRunInFreeTime = baseDir + "bin/radiosonde_auto_rx.sh" # radiosonde tracker, see https://github.com/projecthorus/radiosonde_auto_rx 161 | #scriptToRunInFreeTime = baseDir + "bin/pymultimonaprs.sh" # APRS iGate, 162 | #scriptToRunInFreeTime = baseDir + "bin/wspr.sh" # Weak Signal Propagation Reporter 163 | 164 | # pymultimonaprs must be installed, see: https://github.com/asdil12/pymultimonaprs/ 165 | #scriptToRunInFreeTime = baseDir + "bin/dump1090.sh" 166 | 167 | scriptToRunInFreeTime = baseDir + "bin/rtl_433.sh" # monitor traffic on 433 MHz; see: rtl_433 168 | 169 | 170 | ### Logging to file (enter the path) or False to disable 171 | # loggingDir = False 172 | loggingDir = recordingDir + "/logs/" 173 | 174 | 175 | # Dongle PPM shift, hopefully this will change to reflect different PPM on freq 176 | dongleShift = '0' 177 | 178 | 179 | # 180 | # wether to turn bias t power on (i.e., for LNA) 181 | # see: rtl_fm --help | grep "-T" 182 | # biast="" 183 | # biast="-T" 184 | # 185 | biast="" 186 | 187 | 188 | # 189 | tleDir = baseDir + 'var/tle/' 190 | tleFile = 'all.txt' 191 | 192 | tleFileName = tleDir + tleFile 193 | 194 | # dongle shift file 195 | dongleShiftFile = baseDir + "var/dongleshift.txt" 196 | 197 | # dongle calibration program 198 | # should return the dongle ppm shift 199 | 200 | #calibrationTool = False # don't calibrate the dongle, use old/fixed shift 201 | calibrationTool = baseDir + "bin/calibrate.sh" # uses predefined GSM channel 202 | #calibrationTool = baseDir + "bin/calibrate_full.sh" # check for the best GSM channel and calibrates 203 | 204 | 205 | # DERIVATIVES ############################# 206 | 207 | # 208 | # latlonalt for use with wxmap 209 | # 210 | latlonalt = "%s/%s/%s" % (stationLat, -1 * float(stationLon), stationAlt) 211 | -------------------------------------------------------------------------------- /autowx2_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # autowx2 - function and classes used by autowx2 and auxiliary programs 4 | # 5 | # GANTT Chart with Matplotlib 6 | # Sukhbinder 7 | # inspired by: 8 | # https://sukhbinder.wordpress.com/2016/05/10/quick-gantt-chart-with-matplotlib/ 9 | # taken from 10 | # https://github.com/fialhocoelho/test/blob/master/plot/gantt.py 11 | # 12 | 13 | # for autowx2 itself 14 | import predict 15 | import time 16 | from datetime import datetime 17 | from time import strftime 18 | import subprocess 19 | import os 20 | from _crontab import * 21 | import re 22 | import sys 23 | 24 | 25 | # for plotting 26 | import matplotlib 27 | matplotlib.use('Agg') # Force matplotlib to not use any Xwindows backend. 28 | import matplotlib.pyplot as plt 29 | # import matplotlib.font_manager as font_manager 30 | import matplotlib.dates 31 | from matplotlib.dates import DateFormatter 32 | import numpy as np 33 | 34 | # webserver 35 | from flask import render_template, Flask 36 | from flask_socketio import SocketIO, emit 37 | import codecs 38 | from threading import Thread 39 | 40 | # configuration 41 | from autowx2_conf import * 42 | 43 | # ---------------------------------------------------------------------------- # 44 | 45 | satellites = list(satellitesData) 46 | qth = (stationLat, stationLon, stationAlt) 47 | 48 | 49 | def mkdir_p(outdir): 50 | ''' bash "mkdir -p" analog''' 51 | if not os.path.exists(outdir): 52 | os.makedirs(outdir) 53 | 54 | 55 | class bc(): 56 | 57 | """Colors made easy""" 58 | HEADER = '\033[95m' 59 | CYAN = '\033[96m' 60 | YELLOW = '\033[93m' 61 | RED = '\033[91m' 62 | OKBLUE = '\033[94m' 63 | OKGREEN = '\033[97m' 64 | WARNING = '\033[93m' 65 | FAIL = '\033[91m' 66 | ENDC = '\033[0m' 67 | BOLD = '\033[1m' 68 | GRAY = '\033[37m' 69 | UNDERLINE = '\033[4m' 70 | 71 | 72 | def is_number(s): 73 | try: 74 | float(s) 75 | return True 76 | except ValueError: 77 | return False 78 | 79 | 80 | def getTleData(satellite): 81 | '''Open tle file and return a TLE record for the given sat''' 82 | tlefile = open(tleFileName, 'r') 83 | tledata = tlefile.readlines() 84 | tlefile.close() 85 | 86 | tleData = [] 87 | 88 | for i, line in enumerate(tledata): 89 | if satellite in line: 90 | for n in tledata[i:i + 3]: 91 | tleData.append(n.strip('\r\n').rstrip()) 92 | break 93 | if len(tleData) > 0: 94 | return tleData 95 | else: 96 | return False 97 | 98 | 99 | def parseCron(cron): 100 | entry = CronTab(cron).next(default_utc=False) 101 | return entry + time.time() # timestamp of the next occurence 102 | 103 | 104 | def getFixedRecordingTime(satellite): 105 | '''Reads from the config the fixed recording time''' 106 | try: 107 | fixedTime = satellitesData[satellite]["fixedTime"] 108 | fixedDuration = satellitesData[satellite]["fixedDuration"] 109 | return {"fixedTime": parseCron(fixedTime), "fixedDuration": fixedDuration} 110 | except KeyError: 111 | return False 112 | 113 | 114 | def genPassTable(satellites, qth, howmany=20): 115 | '''generate a table with pass list, sorted''' 116 | 117 | passTable = {} 118 | for satellite in satellites: 119 | 120 | tleData = getTleData(satellite) 121 | priority = satellitesData[satellite]['priority'] 122 | 123 | if tleData: # if tle data was there in the file :: SATELLITES 124 | 125 | czasStart = time.time() 126 | 127 | p = predict.transits(tleData, qth, czasStart) 128 | 129 | # d = predict.observe(tleData, qth) 130 | # print d['doppler'] ## doppler : doppler shift between groundstation and satellite. 131 | # exit(1) 132 | 133 | for i in range(1, howmany): 134 | transit = next(p) 135 | 136 | # transitEnd = transit.start + transit.duration() - skipLast 137 | 138 | if not time.time() > transit.start + transit.duration() - skipLast - 1: # esttimate the end of the transit, minus last 10 seconds 139 | if int(transit.peak()['elevation']) >= minElev: 140 | passTable[transit.start] = [satellite, int( 141 | transit.start + skipFirst), int( 142 | transit.duration() - skipFirst - skipLast), 143 | int(transit.peak()['elevation']), int( 144 | transit.peak()['azimuth']), priority 145 | ] 146 | # transit.start - unix timestamp 147 | 148 | elif 'fixedTime' in satellitesData[satellite]: # if ['fixedTime'] exists in satellitesData => time recording 149 | # cron = getFixedRecordingTime(satellite)["fixedTime"] 150 | cron = satellitesData[satellite]['fixedTime'] 151 | duration = getFixedRecordingTime(satellite)["fixedDuration"] 152 | 153 | delta = 0 154 | for i in range(0, howmany): 155 | entry = CronTab( 156 | cron).next(now=time.time() + delta, 157 | default_utc=False) 158 | delta += entry 159 | start = delta + time.time() 160 | passTable[start] = [ 161 | satellite, int(start), int(duration), '0', '0', priority] 162 | else: 163 | log("✖ Can't find TLE data (in keplers) nor fixed time schedule (in config) for " + 164 | satellite, style=bc.FAIL) 165 | 166 | # Sort pass table 167 | passTableSorted = [] 168 | for start in sorted(passTable): 169 | passTableSorted.append(passTable[start]) 170 | 171 | # Clean the pass table according to the priority. If any pass overlaps, 172 | # remove one with less priority (lower priority number). 173 | passTableSortedPrioritized = passTableSorted[:] 174 | passCount = len(passTableSorted) 175 | for i in range(0, passCount - 1): # -1 or -2 :BUG? 176 | satelliteI, startI, durationI, peakI, azimuthI, priorityI = passTableSorted[ 177 | i] 178 | satelliteJ, startJ, durationJ, peakJ, azimuthJ, priorityJ = passTableSorted[ 179 | i + 1] 180 | endTimeI = startI + durationI 181 | 182 | if priorityI != priorityJ: 183 | if (startJ + priorityTimeMargin < endTimeI): 184 | # print "End pass:", satelliteI, t2human(endTimeI), "--- Start 185 | # time:", satelliteJ, t2human(startJ) 186 | if priorityJ < priorityI: 187 | log(" 1. discard %s, keep %s" % (satelliteI, satelliteJ)) 188 | passTableSortedPrioritized[i] = '' 189 | elif priorityJ > priorityI: 190 | log(" 2. discard %s, keep %s" % (satelliteJ, satelliteI)) 191 | passTableSortedPrioritized[i + 1] = '' 192 | 193 | # let's clean the table and remove empty (removed) records 194 | # and remove the priority record, it will not be useful later -- x[:5] 195 | passTableSortedPrioritized = [x[:5] 196 | for x in passTableSortedPrioritized if x != ''] 197 | 198 | return passTableSortedPrioritized 199 | 200 | 201 | def t2human(timestamp): 202 | '''converts unix timestamp to human readable format''' 203 | return strftime('%Y-%m-%d %H:%M', time.localtime(timestamp)) 204 | 205 | 206 | def t2humanMS(seconds): 207 | '''converts unix timestamp to human readable format MM:SS''' 208 | mm = int(seconds / 60) 209 | ss = seconds % 60 210 | return "%02i:%02i" % (mm, ss) 211 | 212 | 213 | def printPass(satellite, start, duration, peak, azimuth, freq, processWith): 214 | return "● " + bc.OKGREEN + "%10s" % (satellite) + bc.ENDC + " :: " \ 215 | + bc.OKGREEN + t2human(start) + bc.ENDC + " to " + bc.OKGREEN + t2human(start + int(duration)) + bc.ENDC \ 216 | + ", dur: " + t2humanMS(duration) \ 217 | + ", max el. " + str(int(peak)) + "°" + "; azimuth: " + str(int(azimuth)) + \ 218 | "° (" + azimuth2dir(azimuth) + ") f=" + str( 219 | freq) + "Hz; Decoding: " + str(processWith) 220 | 221 | 222 | def listNextPases(passTable, howmany): 223 | i = 1 224 | for satelitePass in passTable[0:howmany]: 225 | satellite, start, duration, peak, azimuth = satelitePass 226 | freq = satellitesData[satellite]['freq'] 227 | processWith = satellitesData[satellite]['processWith'] 228 | log(printPass(satellite, start, duration, 229 | peak, azimuth, freq, processWith)) 230 | i += 1 231 | 232 | 233 | def runForDuration(cmdline, duration, loggingDir): 234 | outLogFile = logFile(loggingDir) 235 | teeCommand = ['tee', '-a', outLogFile ] # quick and dirty hack to get log to file 236 | 237 | cmdline = [str(x) for x in cmdline] 238 | print(cmdline) 239 | try: 240 | p1 = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 241 | _ = subprocess.Popen(teeCommand, stdin=p1.stdout) 242 | time.sleep(duration) 243 | p1.terminate() 244 | except OSError as e: 245 | log("✖ OS Error during command: " + " ".join(cmdline), style=bc.FAIL) 246 | log("✖ OS Error: " + e.strerror, style=bc.FAIL) 247 | 248 | 249 | def justRun(cmdline, loggingDir): 250 | '''Just run the command as long as necesary and return the output''' 251 | outLogFile = logFile(loggingDir) 252 | teeCommand = ['tee', '-a', outLogFile ] # quick and dirty hack to get log to file 253 | 254 | cmdline = [str(x) for x in cmdline] 255 | try: 256 | p1 = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 257 | p2 = subprocess.Popen(teeCommand, stdin=p1.stdout, close_fds=True) # stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 258 | result = p1.communicate()[0] 259 | return result 260 | except OSError as e: 261 | log("✖ OS Error during command: " + " ".join(cmdline), style=bc.FAIL) 262 | log("✖ OS Error: " + e.strerror, style=bc.FAIL) 263 | 264 | 265 | def runTest(duration=3): 266 | '''Check, if RTL_SDR dongle is connected''' 267 | child = subprocess.Popen('rtl_test', stdout=subprocess.PIPE, 268 | stderr=subprocess.PIPE) 269 | time.sleep(duration) 270 | child.terminate() 271 | _, err = child.communicate() 272 | # if no device: ['No', 'supported', 'devices', 'found.'] 273 | # if OK: ['Found', '1', 'device(s):', '0:', 'Realtek,', 274 | # 'RTL2838UHIDIR,',... 275 | info = err.split()[0] 276 | if info == "No": 277 | log("✖ No SDR device found!", style=bc.FAIL) 278 | return False 279 | elif info == "Found": 280 | log("SDR device found!") 281 | return True 282 | else: 283 | log("Not sure, if SDR device is there...") 284 | return True 285 | 286 | 287 | def getDefaultDongleShift(dongleShift=dongleShift): 288 | log("Reading the default dongle shift") 289 | if os.path.exists(dongleShiftFile): 290 | f = open(dongleShiftFile, "r") 291 | newdongleShift = f.read().strip() 292 | f.close() 293 | 294 | if newdongleShift != '' and is_number(newdongleShift): # WARNING and newdongleShift is numeric: 295 | dongleShift = str(float(newdongleShift)) 296 | log("Recently used dongle shift is: " + str(dongleShift) + " ppm") 297 | else: 298 | log("Using the default dongle shift: " + str(dongleShift) + " ppm") 299 | 300 | return dongleShift 301 | 302 | 303 | def calibrate(dongleShift=dongleShift): 304 | '''calculate the ppm for the device''' 305 | if (calibrationTool): 306 | cmdline = [calibrationTool] 307 | newdongleShift = justRun(cmdline, loggingDir).strip() 308 | if newdongleShift != '' and is_number(newdongleShift): 309 | log("Recalculated dongle shift is: " + 310 | str(newdongleShift) + " ppm") 311 | return str(float(newdongleShift)) 312 | else: 313 | log("Using the good old dongle shift (a): " + 314 | str(dongleShift) + " ppm") 315 | return dongleShift 316 | else: 317 | log("Using the good old dongle shift: " + str(dongleShift) + " ppm") 318 | return dongleShift 319 | 320 | 321 | def azimuth2dir(azimuth): 322 | ''' convert azimuth in degrees to wind rose directions (16 wings)''' 323 | dirs = ["N↑", "NNE↑↗", "NE↗", "ENE→↗", 324 | "E→", "ESE→↘", "SE↘", "SSE↓↘", 325 | "S↓", "SSW↓↙", "SW↙", "WSW←↙", 326 | "W←", "WNW←↖", "NW↖", "NNW↑↖", ] 327 | 328 | part = int(float(azimuth) / 360 * 16) 329 | return dirs[part] 330 | 331 | 332 | def escape_ansi(line): 333 | '''remove ansi colors from the given string''' 334 | ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]') 335 | return ansi_escape.sub('', line) 336 | 337 | 338 | def log(string, style=bc.CYAN): 339 | message = "%s%s%s %s %s %s " % ( 340 | bc.BOLD, 341 | datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M'), 342 | bc.ENDC, 343 | style, 344 | str(string), 345 | bc.ENDC) 346 | # socketio.emit('log', {'data': message}, namespace='/') 347 | handle_my_custom_event(escape_ansi(message) + "
\n") 348 | print(message) 349 | 350 | # logging to file, if not Flase 351 | if loggingDir: 352 | logToFile(escape_ansi(message) + "\n", loggingDir) 353 | 354 | 355 | def logFile(logDir): 356 | '''Create output logging dir, returns name of the logfile''' 357 | mkdir_p(logDir) 358 | outfile = "%s/%s.txt" % ( 359 | logDir, 360 | datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d')) 361 | return outfile 362 | 363 | 364 | def logToFile(message, logDir): 365 | # save message to the file 366 | outfile = logFile(logDir) 367 | file_append(outfile, message) 368 | 369 | 370 | def file_append(filename, content): 371 | f = open(filename, 'a') 372 | f.write(content) 373 | f.close() 374 | 375 | 376 | # ------------ functions for generation of pass table, saving, png image etc... 377 | 378 | 379 | def t2humanHM(timestamp): 380 | '''converts unix timestamp to human readable format''' 381 | # oggingDir 382 | return strftime('%H:%M', time.localtime(timestamp)) 383 | 384 | 385 | def _create_date(timestamp): 386 | """Creates the date from timestamp""" 387 | mdate = matplotlib.dates.date2num(datetime.fromtimestamp(timestamp)) 388 | return mdate 389 | 390 | 391 | def assignColorsToEvent(uniqueEvents): 392 | ''' returns dict of event -> color''' 393 | 394 | eventsColors = {'METEOR-M2': 'red', 395 | 'NOAA-15': '#0040FF', 396 | 'NOAA-18': '#0890FF', 397 | 'NOAA-19': '#07CAFF', 398 | 'ISS': '#38FF06', 399 | } 400 | 401 | colors = ['#F646FF', 'yellow', 'orange', 'silver', 'magenta', 'red', 'white', '#FF2894', '#FF6E14'] 402 | 403 | fixedColorsEvents = [event for event in eventsColors] # events that have assigned fixed colors 404 | 405 | # generate list of events with not assigned fixed color 406 | uniqueEventsToAssignColors = [] 407 | for uniqueEvent in uniqueEvents: 408 | if uniqueEvent not in fixedColorsEvents: 409 | uniqueEventsToAssignColors += [uniqueEvent] 410 | 411 | # assign colors to the events with not assigne fixed colrs and add them to dict 412 | for event, color in zip(uniqueEventsToAssignColors, colors): 413 | eventsColors[event] = color 414 | 415 | 416 | return eventsColors 417 | 418 | 419 | def CreateGanttChart(listNextPasesListList): 420 | """ 421 | Create gantt charts with matplotlib 422 | """ 423 | 424 | ylabels = [] 425 | customDates = [] 426 | 427 | i = 1 428 | for tx in listNextPasesListList: 429 | ylabel, startdate, enddate = tx 430 | # ylabels.append("%s (%1i)" % (ylabel, i) ) 431 | ylabels.append("(%1i)" % (i)) 432 | # ylabels.append("%s" % (ylabel) ) 433 | customDates.append([_create_date(startdate), _create_date(enddate)]) 434 | i += 1 435 | 436 | now = _create_date(time.time()) 437 | 438 | uniqueEvents = list(set([x[0] for x in listNextPasesListList])) # unique list of satellites 439 | colorDict = assignColorsToEvent(uniqueEvents) 440 | 441 | ilen = len(ylabels) 442 | pos = np.arange(0.5, ilen * 0.5 + 0.5, 0.5) 443 | task_dates = {} 444 | for i, task in enumerate(ylabels): 445 | task_dates[task] = customDates[i] 446 | fig = plt.figure(figsize=(10, 5)) 447 | ax = fig.add_subplot(111) 448 | for i, ylabel in enumerate(ylabels): 449 | ylabelIN, startdateIN, enddateIN = listNextPasesListList[i] 450 | start_date, end_date = task_dates[ylabels[i]] 451 | 452 | if i < (ilen/2): 453 | labelAlign = 'left' 454 | factor = 1 455 | else: 456 | labelAlign = 'right' 457 | factor = -1 458 | 459 | 460 | ax.barh( 461 | (i * 0.5) + 0.5, 462 | end_date - start_date, 463 | left=start_date, 464 | height=0.3, 465 | align='center', 466 | edgecolor='black', 467 | color=colorDict[ylabelIN], 468 | label='', 469 | alpha=0.95) 470 | ax.text( 471 | end_date, 472 | (i * 0.5) + 0.55, 473 | ' %s | %s ' % (t2humanHM(startdateIN), 474 | ylabelIN), 475 | ha=labelAlign, 476 | va='center', 477 | fontsize=8, 478 | color='#7B7B7B') 479 | 480 | locsy, labelsy = plt.yticks(pos, ylabels) 481 | plt.setp(labelsy, fontsize=8) 482 | ax.axis('tight') 483 | ax.set_ylim(ymin=-0.1, ymax=ilen * 0.5 + 0.5) 484 | ax.set_xlim(xmin=now) 485 | ax.grid(color='silver', linestyle=':') 486 | ax.xaxis_date() 487 | 488 | # FAKE,startdate,FAKE=listNextPasesListList[0] 489 | # minutOdPelnej = int(datetime.fromtimestamp(time.time()).strftime('%M')) 490 | # plotStart = int(time.time() - minutOdPelnej*60) 491 | # print t2human(plotStart) 492 | # ax.set_xlim(_create_date(plotStart), _create_date(enddate+600)) 493 | 494 | Majorformatter = DateFormatter("%H:%M\n%d-%b") 495 | ax.xaxis.set_major_formatter(Majorformatter) 496 | labelsx = ax.get_xticklabels() 497 | # plt.setp(labelsx, rotation=30, fontsize=10) 498 | plt.setp(labelsx, rotation=0, fontsize=7) 499 | plt.title( 500 | 'Transit plan for %s, generated %s' % 501 | (stationName, t2human(time.time()))) 502 | 503 | ax.invert_yaxis() 504 | plt.tight_layout() 505 | plt.savefig(ganttNextPassList) 506 | 507 | if ylabel == enddateIN: 508 | print(locsy) # "This is done only to satisfy the codacy.com. Sorry for that." 509 | 510 | 511 | def listNextPasesHtml(passTable, howmany): 512 | i = 1 513 | output = "\n" 514 | output += "\n" 515 | 516 | # uniqueEvents 517 | # colorDict = assignColorsToEvent(listNextPasesListList) 518 | # print passTable[0:howmany] 519 | uniqueEvents = list(set([x[0] for x in passTable[0:howmany]])) # unique list of satellites 520 | colorDict = assignColorsToEvent(uniqueEvents) 521 | 522 | for satelitePass in passTable[0:howmany]: 523 | satellite, start, duration, peak, azimuth = satelitePass 524 | freq = satellitesData[satellite]['freq'] 525 | processWith = satellitesData[satellite]['processWith'] 526 | 527 | color = colorDict[satellite] 528 | output += "\n" % ( 529 | color, i, satellite, t2human(start), t2humanMS(duration), peak, azimuth, azimuth2dir(azimuth), freq, processWith) 530 | i += 1 531 | 532 | output += "
#satellitestartdurationpeakazimuthfreqprocess with
%i%s%s%s%s°%s° (%s)%sHz%s
\n" 533 | 534 | return output #.decode('utf-8') 535 | 536 | 537 | def listNextPasesTxt(passTable, howmany): 538 | 539 | txtTemplate = "%3s\t%10s\t%16s\t%9s\t%4s\t%3s\t%6s\t%10s\t%20s\n" 540 | 541 | i = 1 542 | output = "" 543 | output += txtTemplate % ( 544 | "#", 545 | "satellite", 546 | "start", 547 | "dur MM:SS", 548 | "peak", 549 | "az", 550 | "az", 551 | "freq", 552 | "process with") 553 | 554 | for satelitePass in passTable[0:howmany]: 555 | satellite, start, duration, peak, azimuth = satelitePass 556 | freq = satellitesData[satellite]['freq'] 557 | processWith = satellitesData[satellite]['processWith'] 558 | 559 | output += txtTemplate % ( 560 | i, 561 | satellite, 562 | t2human(start), 563 | t2humanMS(duration), 564 | peak, 565 | azimuth, 566 | azimuth2dir(azimuth), 567 | freq, 568 | processWith) 569 | i += 1 570 | 571 | output += "\n" 572 | 573 | return output 574 | 575 | def listNextPasesShort(passTable, howmany=4): 576 | ''' list next passes in a sentence ''' 577 | 578 | output = "" 579 | 580 | for satelitePass in passTable[0:howmany]: 581 | satellite, start, duration, peak, azimuth = satelitePass 582 | 583 | output += "%s (%s); " % (satellite, strftime('%H:%M', time.localtime(start)) ) 584 | 585 | return output 586 | 587 | def listNextPasesList(passTable, howmany): 588 | output = [] 589 | for satelitePass in passTable[0:howmany]: 590 | satellite, start, duration, peak, azimuth = satelitePass 591 | # freq = satellitesData[satellite]['freq'] 592 | # processWith = satellitesData[satellite]['processWith'] 593 | 594 | output.append([satellite, start, start + duration]) 595 | if peak: 596 | print("This is a miracle!") # codacy cheating, sorry. 597 | return output 598 | 599 | 600 | def saveToFile(filename, data): 601 | # print filename 602 | # print data 603 | # exit(1) 604 | plik = open(filename, "w") 605 | plik.write(data) # .encode("utf-8") 606 | plik.close() 607 | 608 | 609 | def generatePassTableAndSaveFiles(satellites, qth, verbose=True): 610 | # recalculate table of next passes 611 | log("Recalculating the new pass table and saving to disk.") 612 | passTable = genPassTable(satellites, qth, howmany=50) 613 | listNextPasesHtmlOut = listNextPasesHtml(passTable, 100) 614 | saveToFile(htmlNextPassList, listNextPasesHtmlOut) 615 | 616 | saveToFile(wwwDir + 'nextpassshort.tmp', listNextPasesShort(passTable, 6)) 617 | 618 | listNextPasesListList = listNextPasesList(passTable, 20) 619 | CreateGanttChart(listNextPasesListList) 620 | 621 | if verbose: 622 | print(listNextPasesTxt(passTable, 100)) 623 | 624 | 625 | 626 | # --------------------------------------------------------------------------- # 627 | # --------- THE WEBSERVER --------------------------------------------------- # 628 | # --------------------------------------------------------------------------- # 629 | 630 | app = Flask(__name__, template_folder="var/flask/templates/", static_folder='var/flask/static') 631 | socketio = SocketIO(app) 632 | 633 | def file_read(filename): 634 | with codecs.open(filename, 'r', encoding='utf-8', errors='replace') as f: 635 | lines = f.read() 636 | linesBr = "
".join( lines.split("\n") ) 637 | return linesBr 638 | 639 | 640 | @app.route('/') 641 | def homepage(): 642 | logfile = logFile(loggingDir) 643 | logs = file_read(logfile) 644 | 645 | body ="" 646 | # log window 647 | body += "

Recent logs

File: %s

%s
" % (logfile, logs) 648 | 649 | # next pass table 650 | passTable = genPassTable(satellites, qth) 651 | body += "

Next passes

%s" % ( listNextPasesHtml(passTable, 10) ) 652 | return render_template('index.html', title="Home page", body=body) 653 | 654 | @socketio.on('my event') 655 | def handle_my_custom_event(text): 656 | socketio.emit('my response', { 'tekst': text } ) 657 | 658 | @socketio.on('next pass table') 659 | def handle_next_pass_list(text): 660 | socketio.emit('response next pass table', { 'tekst': text } ) 661 | 662 | 663 | # 664 | # show pass table 665 | # 666 | 667 | @app.route('/table') 668 | def passTable(): 669 | body=file_read(htmlNextPassList) 670 | return render_template('index.html', title="Pass table", body=body) 671 | 672 | 673 | # --------------------------------------------------------------------------- # 674 | # --------- THE MAIN LOOP --------------------------------------------------- # 675 | # --------------------------------------------------------------------------- # 676 | 677 | def mainLoop(): 678 | dongleShift = getDefaultDongleShift() 679 | 680 | while True: 681 | 682 | # each loop - reads the config file in case it has changed 683 | from autowx2_conf import satellitesData, scriptToRunInFreeTime 684 | 685 | # recalculate table of next passes 686 | passTable = genPassTable(satellites, qth) 687 | 688 | # save table to disk 689 | generatePassTableAndSaveFiles(satellites, qth, verbose=False) 690 | 691 | # show next five passes 692 | log("Next five passes:") 693 | listNextPases(passTable, 5) 694 | 695 | # pass table for webserver 696 | handle_next_pass_list(listNextPasesHtml(passTable, 10)) 697 | 698 | # get the very next pass 699 | satelitePass = passTable[0] 700 | satellite, start, duration, peak, azimuth = satelitePass 701 | satelliteNoSpaces = satellite.replace(" ", "-") #remove spaces from the satellite name 702 | 703 | freq = satellitesData[satellite]['freq'] 704 | processWith = satellitesData[satellite]['processWith'] 705 | 706 | fileNameCore = datetime.fromtimestamp( 707 | start).strftime( 708 | '%Y%m%d-%H%M') + "_" + satelliteNoSpaces 709 | 710 | log("Next pass:") 711 | log(printPass(satellite, start, duration, 712 | peak, azimuth, freq, processWith)) 713 | 714 | towait = int(start - time.time()) 715 | 716 | if cleanupRtl: 717 | log("Killing all remaining rtl_* processes...") 718 | justRun(["bin/kill_rtl.sh"], loggingDir) 719 | 720 | # test if SDR dongle is available 721 | if towait > 15: # if we have time to perform the test? 722 | while not runTest(): 723 | log("Waiting for the SDR dongle...") 724 | time.sleep(10) 725 | 726 | # It's a high time to record! 727 | if towait <= 1 and duration > 0: 728 | # here the recording happens 729 | log("!! Recording " + printPass(satellite, start, duration, 730 | peak, azimuth, freq, processWith), style=bc.WARNING) 731 | 732 | processCmdline = [ 733 | processWith, 734 | fileNameCore, 735 | satellite, 736 | start, 737 | duration + towait, 738 | peak, 739 | azimuth, 740 | freq] 741 | print(justRun(processCmdline, loggingDir)) 742 | time.sleep(10.0) 743 | 744 | # still some time before recording 745 | else: 746 | # recalculating waiting time 747 | if towait > 300: 748 | log("Recalibrating the dongle...") 749 | dongleShift = calibrate(dongleShift) # replace the global value 750 | 751 | towait = int(start - time.time()) 752 | if scriptToRunInFreeTime: 753 | if towait >= 120: # if we have more than [some] minutes spare time, let's do something useful 754 | log("We have still %ss free time to the next pass. Let's do something useful!" % 755 | (t2humanMS(towait - 1))) 756 | log("Running: %s for %ss" % 757 | (scriptToRunInFreeTime, t2humanMS(towait - 1))) 758 | runForDuration( 759 | [scriptToRunInFreeTime, 760 | towait - 1, 761 | dongleShift], 762 | towait - 1, loggingDir) 763 | # scrript with run time and dongle shift as 764 | # arguments 765 | else: 766 | log("Sleeping for: " + t2humanMS(towait - 1) + "s") 767 | time.sleep(towait - 1) 768 | else: 769 | towait = int(start - time.time()) 770 | log("Sleeping for: " + t2humanMS(towait - 1) + "s") 771 | time.sleep(towait - 1) 772 | 773 | logfile = logFile(loggingDir) 774 | -------------------------------------------------------------------------------- /bin/aprs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | logfile="$recordingDir/aprs/"$(date +"%Y/%m")/$(date +"%Y%m%d")".txt" 10 | 11 | mkdir -p $(dirname $logfile) 12 | 13 | duration=$1 14 | dongleShift=$2 15 | 16 | mkdir -p $(dirname $logfile) 17 | 18 | 19 | timeout --kill-after=1 $duration rtl_fm $biast -f 144800000 -s 22050 -o 4 -p $dongleShift -g 49.6 | multimon-ng -a AFSK1200 -A -t raw - | tee -a $logfile 20 | -------------------------------------------------------------------------------- /bin/calibrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## calibrating of the dongle 4 | 5 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 6 | # do not change the following three lines 7 | scriptDir="$(dirname "$(realpath "$0")")" 8 | source $scriptDir/basedir_conf.py > /dev/null 9 | source $baseDir/_listvars.sh > /dev/null 10 | 11 | 12 | shiftFile="$baseDir/var/dongleshift.txt" 13 | channelFile="$baseDir/var/gsm_channel.txt" 14 | shiftHistory="$baseDir/var/shifthistory.csv" 15 | 16 | channel=$(cat $channelFile) 17 | 18 | 19 | #-----------------------------------------------# 20 | 21 | mkdir -p $(dirname $shiftHistory) 22 | recentShift=$(cat $shiftFile) 23 | 24 | re='^-?[0-9]+([.][0-9]+)?$' 25 | if ! [[ $recentShift =~ $re ]] ; then 26 | recentShift=0 27 | fi 28 | 29 | newShift=$(timeout 100 kal -c $channel -g 49.6 -e $recentShift 2> /dev/null | tail -1 | cut -d " " -f 4) 30 | echo $newShift | tee $shiftFile 31 | 32 | echo $(date +"%Y%m%d_%H:%M:%S") $(date +"%s") $newShift >> $shiftHistory 33 | -------------------------------------------------------------------------------- /bin/calibrate_full.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | 6 | scriptDir="$(dirname "$(realpath "$0")")" 7 | source $scriptDir/basedir_conf.py > /dev/null 8 | source $baseDir/_listvars.sh > /dev/null 9 | 10 | 11 | channel=$(timeout --kill-after=1 300 kal -s GSM900 -e 0 -g 49.6 2> /dev/null | sed 's/ \+/\t/g' | cut -f 3,8 | sort -k2 -n -r | head -1 | cut -f 1) 12 | echo "$channel" > "$baseDir/var/gsm_channel.txt" 13 | 14 | 15 | source $baseDir/bin/calibrate.sh 16 | 17 | -------------------------------------------------------------------------------- /bin/crontab/daily.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # tasks to perform daily 4 | 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py > /dev/null 7 | source $baseDir/_listvars.sh > /dev/null 8 | 9 | # generate home page from template 10 | $baseDir/genpasstable.py 1>/dev/null 2>&1 11 | $baseDir/bin/gen-static-page.sh 1>/dev/null 2>&1 12 | 13 | 14 | # generate heatmap for dump1090 15 | # $baseDir/bin/dump1090-draw_heatmap.sh 1>/dev/null 2>&1 16 | 17 | 18 | 19 | # compress and rotate old log files 20 | find $recordingDir/logs/*.txt -mtime +7 -exec bzip2 {} \; 21 | mkdir -p $recordingDir/logs/old/ 22 | mv $recordingDir/logs/*.bz2 $recordingDir/logs/old/ -------------------------------------------------------------------------------- /bin/crontab/weekly.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # tasks to perform daily 4 | 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py > /dev/null 7 | source $baseDir/_listvars.sh > /dev/null 8 | 9 | # generate home page from template 10 | $baseDir/genpasstable.py 1>/dev/null 2>&1 11 | $baseDir/bin/gen-static-page.sh 1>/dev/null 2>&1 12 | 13 | 14 | 15 | 16 | # update Keplers 17 | $baseDir/bin/update-keps.sh 1>$recordingDir/logs/keps-update.txt 2>&1 18 | 19 | -------------------------------------------------------------------------------- /bin/dump1090-draw_heatmap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | 10 | database="$recordingDir/dump1090/adsb_messages.db" 11 | outdir="$recordingDir/dump1090/" # $(date +"%Y/%m")/$(date +"%Y%m%d").txt" 12 | tempdir="/tmp" 13 | 14 | mkdir -p $(dirname $outdir) 15 | 16 | # draw data on the map 17 | $baseDir/bin/heatmap.py -m 000ffffff -M 000ffffff -o $outdir/heatmap-osm.png -r 4 --margin 25 -k gaussian --height 900 --osm --sqlite_table squitters $database 18 | 19 | 20 | # draw data on the map 21 | $baseDir/bin/heatmap.py -o $outdir/heatmap-osm2.png -r 40 --height 900 --osm --margin 25 --sqlite_table squitters $database 22 | 23 | # draw data on the black canvas 24 | $baseDir/bin/heatmap.py -b black -r 30 -W 1200 -o $tempdir/h1.png -k gaussian --sqlite_table squitters $database 25 | $baseDir/bin/heatmap.py -r 5 -W 1200 -o $tempdir/h2.png --decay 0.3 --margin 25 -k gaussian --sqlite_table squitters $database 26 | 27 | # convert to jpg 28 | 29 | convert -quality 83 $outdir/heatmap-osm.png $outdir/heatmap-osm.jpg 30 | convert -quality 83 $outdir/heatmap-osm2.png $outdir/heatmap-osm2.jpg 31 | 32 | 33 | # remove unused files 34 | rm $tempdir/h1.png $tempdir/h2.png $outdir/heatmap-osm.png $outdir/heatmap-osm2.png -------------------------------------------------------------------------------- /bin/dump1090-stream-parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import socket 5 | import datetime 6 | import sqlite3 7 | import argparse 8 | import time 9 | 10 | #defaults 11 | HOST = "localhost" 12 | PORT = 30003 13 | DB = "adsb_messages.db" 14 | BUFFER_SIZE = 100 15 | BATCH_SIZE = 1 16 | CONNECT_ATTEMPT_LIMIT = 10000 17 | CONNECT_ATTEMPT_DELAY = 5.0 18 | 19 | 20 | def main(): 21 | 22 | #set up command line options 23 | parser = argparse.ArgumentParser(description="A program to process dump1090 messages then insert them into a database") 24 | parser.add_argument("-l", "--location", type=str, default=HOST, help="This is the network location of your dump1090 broadcast. Defaults to %s" % (HOST,)) 25 | parser.add_argument("-p", "--port", type=int, default=PORT, help="The port broadcasting in SBS-1 BaseStation format. Defaults to %s" % (PORT,)) 26 | parser.add_argument("-d", "--database", type=str, default=DB, help="The location of a database file to use or create. Defaults to %s" % (DB,)) 27 | parser.add_argument("--buffer-size", type=int, default=BUFFER_SIZE, help="An integer of the number of bytes to read at a time from the stream. Defaults to %s" % (BUFFER_SIZE,)) 28 | parser.add_argument("--batch-size", type=int, default=BATCH_SIZE, help="An integer of the number of rows to write to the database at a time. If you turn off WAL mode, a lower number makes it more likely that your database will be locked when you try to query it. Defaults to %s" % (BATCH_SIZE,)) 29 | parser.add_argument("--connect-attempt-limit", type=int, default=CONNECT_ATTEMPT_LIMIT, help="An integer of the number of times to try (and fail) to connect to the dump1090 broadcast before qutting. Defaults to %s" % (CONNECT_ATTEMPT_LIMIT,)) 30 | parser.add_argument("--connect-attempt-delay", type=float, default=CONNECT_ATTEMPT_DELAY, help="The number of seconds to wait after a failed connection attempt before trying again. Defaults to %s" % (CONNECT_ATTEMPT_DELAY,)) 31 | 32 | # parse command line options 33 | args = parser.parse_args() 34 | 35 | # print args.accumulate(args.in) 36 | count_since_commit = 0 37 | count_total = 0 38 | count_failed_connection_attempts = 1 39 | 40 | # connect to database or create if it doesn't exist 41 | conn = sqlite3.connect(args.database) 42 | cur = conn.cursor() 43 | cur.execute('PRAGMA journal_mode=wal') 44 | 45 | # set up the table if neccassary 46 | cur.execute("""CREATE TABLE IF NOT EXISTS 47 | squitters( 48 | message_type TEXT, 49 | transmission_type INT, 50 | session_id TEXT, 51 | aircraft_id TEXT, 52 | hex_ident TEXT, 53 | flight_id TEXT, 54 | generated_date TEXT, 55 | generated_time TEXT, 56 | logged_date TEXT, 57 | logged_time TEXT, 58 | callsign TEXT, 59 | altitude INT, 60 | ground_speed INT, 61 | track INT, 62 | lat REAL, 63 | lon REAL, 64 | vertical_rate REAL, 65 | squawk TEXT, 66 | alert INT, 67 | emergency INT, 68 | spi INT, 69 | is_on_ground INT, 70 | parsed_time TEXT 71 | ) 72 | """) 73 | 74 | start_time = datetime.datetime.utcnow() 75 | 76 | # open a socket connection 77 | while count_failed_connection_attempts < args.connect_attempt_limit: 78 | try: 79 | s = connect_to_socket(args.location, args.port) 80 | count_failed_connection_attempts = 1 81 | print "Connected to dump1090 broadcast" 82 | break 83 | except socket.error: 84 | count_failed_connection_attempts += 1 85 | print "Cannot connect to dump1090 broadcast. Making attempt %s." % (count_failed_connection_attempts) 86 | time.sleep(args.connect_attempt_delay) 87 | else: 88 | quit() 89 | 90 | data_str = "" 91 | 92 | try: 93 | #loop until an exception 94 | while True: 95 | #get current time 96 | cur_time = datetime.datetime.utcnow() 97 | ds = cur_time.isoformat() 98 | ts = cur_time.strftime("%H:%M:%S") 99 | 100 | # receive a stream message 101 | try: 102 | message = "" 103 | message = s.recv(args.buffer_size) 104 | data_str += message.strip("\n") 105 | except socket.error: 106 | # this happens if there is no connection and is delt with below 107 | pass 108 | 109 | if len(message) == 0: 110 | print ts, "No broadcast received. Attempting to reconnect" 111 | time.sleep(args.connect_attempt_delay) 112 | s.close() 113 | 114 | while count_failed_connection_attempts < args.connect_attempt_limit: 115 | try: 116 | s = connect_to_socket(args.location, args.port) 117 | count_failed_connection_attempts = 1 118 | print "Reconnected!" 119 | break 120 | except socket.error: 121 | count_failed_connection_attempts += 1 122 | print "The attempt failed. Making attempt %s." % (count_failed_connection_attempts) 123 | time.sleep(args.connect_attempt_delay) 124 | else: 125 | quit() 126 | 127 | continue 128 | 129 | # it is possible that more than one line has been received 130 | # so split it then loop through the parts and validate 131 | 132 | data = data_str.split("\n") 133 | 134 | for d in data: 135 | line = d.split(",") 136 | 137 | #if the line has 22 items, it's valid 138 | if len(line) == 22: 139 | lat = line[14] 140 | lon = line[15] 141 | if lat != '' and lon != '': 142 | #print lat, lon 143 | #print d 144 | 145 | # add the current time to the row 146 | line.append(ds) 147 | 148 | try: 149 | # add the row to the db 150 | cur.executemany("""INSERT INTO squitters 151 | ( 152 | message_type, 153 | transmission_type, 154 | session_id, 155 | aircraft_id, 156 | hex_ident, 157 | flight_id, 158 | generated_date, 159 | generated_time, 160 | logged_date, 161 | logged_time, 162 | callsign, 163 | altitude, 164 | ground_speed, 165 | track, 166 | lat, 167 | lon, 168 | vertical_rate, 169 | squawk, 170 | alert, 171 | emergency, 172 | spi, 173 | is_on_ground, 174 | parsed_time 175 | ) 176 | VALUES (""" + ", ".join(["?"] * len(line)) + ")", (line,)) 177 | 178 | # increment counts 179 | count_total += 1 180 | count_since_commit += 1 181 | 182 | # commit the new rows to the database in batches 183 | if count_since_commit % args.batch_size == 0: 184 | conn.commit() 185 | print "averging %s rows per second" % (float(count_total) / (cur_time - start_time).total_seconds(),) 186 | if count_since_commit > args.batch_size: 187 | print ts, "All caught up, %s rows, successfully written to database" % (count_since_commit) 188 | count_since_commit = 0 189 | 190 | except sqlite3.OperationalError: 191 | print ts, "Could not write to database, will try to insert %s rows on next commit" % (count_since_commit + args.batch_size,) 192 | 193 | 194 | # since everything was valid we reset the stream message 195 | data_str = "" 196 | else: 197 | # the stream message is too short, prepend to the next stream message 198 | data_str = d 199 | continue 200 | 201 | except KeyboardInterrupt: 202 | print "\n%s Closing connection" % (ts,) 203 | s.close() 204 | 205 | conn.commit() 206 | conn.close() 207 | print ts, "%s squitters added to your database" % (count_total,) 208 | 209 | except sqlite3.ProgrammingError: 210 | print "Error with ", line 211 | quit() 212 | 213 | def connect_to_socket(loc,port): 214 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 215 | s.connect((loc, port)) 216 | return s 217 | 218 | if __name__ == '__main__': 219 | main() 220 | -------------------------------------------------------------------------------- /bin/dump1090.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | 10 | #### microconfiguration 11 | 12 | database="$recordingDir/dump1090/adsb_messages.db" 13 | mkdir -p $(dirname $database) 14 | 15 | duration=$1 16 | dongleShift=$2 17 | 18 | 19 | timeout --kill-after=1 $duration dump1090 --quiet --metric --net --aggressive --ppm $dongleShift & 20 | timeout --kill-after=1 $duration $baseDir/bin/dump1090-stream-parser.py --database $database -------------------------------------------------------------------------------- /bin/gen-static-page.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # loop over dirs, crate list of files, generates static file 4 | 5 | # disable the warning 6 | # shellcheck disable=SC2034 7 | 8 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 9 | # do not change the following three lines 10 | scriptDir="$(dirname "$(realpath "$0")")" 11 | source $scriptDir/basedir_conf.py 12 | source $baseDir/_listvars.sh 13 | 14 | ################## FINE TUNING ##################### 15 | 16 | # $recordingDir - real path on the system 17 | # $wwwRootPath/recordings/logs/ - www path 18 | 19 | # static values for tests 20 | # imgdir=/home/filips/github/autowx2/recordings/noaa/img/2018/09/08/ 21 | # fileNameCore="20180908-1626_NOAA-19" 22 | # wwwDir="/home/filips/github/autowx2/var/www/" 23 | 24 | 25 | noaaDir=$recordingDir/noaa 26 | meteorDir=$recordingDir/meteor 27 | 28 | dirList="$wwwDir/noaa_dirlist.tmp" 29 | htmlTemplate="$wwwDir/index.tpl" 30 | htmlOutput="$wwwDir/index.html" 31 | htmlOutputTable="$wwwDir/table.html" 32 | 33 | currentDate=$(date -R) 34 | echo $currentDate 35 | echo "" > $dirList 36 | 37 | 38 | ##### keplers updated? ##### 39 | lastkeps=$(cat "$wwwDir/keps.tmp") 40 | lastkepsU=$(cat "$wwwDir/kepsU.tmp") 41 | 42 | keplerDays=$(echo "(($(date +"%s") - $lastkepsU ) / (60*60*24))" | bc ) 43 | echo $keplerDays 44 | if [ $keplerDays -le 7 ]; then keplerInfo="OK" 45 | else keplerInfo="outdated"; fi 46 | 47 | echo "lastkeps: $lastkeps" 48 | 49 | keplerInfo="Keplers updated: $lastkeps ($keplerDays days old) $keplerInfo
" 50 | 51 | ##### autowx2 uptime ######## 52 | 53 | autowxStart=$(cat "$wwwDir/start.tmp") 54 | 55 | autowxUptimeH=$(echo "(($(date +"%s") - $autowxStart ) / (60*60))" | bc ) 56 | autowxUptimeD=$(echo "(($(date +"%s") - $autowxStart ) / (60*60*24))" | bc ) 57 | 58 | echo $autowxUptimeH 59 | 60 | autowxUptime="autowx2 uptime: $autowxUptimeH h (~$autowxUptimeD d)
" 61 | 62 | ### short list of next passes 63 | 64 | shortlistofnextpassess="
Next passes: $(cat "$wwwDir/nextpassshort.tmp")
" 65 | 66 | 67 | # ---- NOAA list all dates and times -------------------------------------------------# 68 | 69 | function gallery_noaa { 70 | 71 | howManyToday=$(ls $noaaDir/img/$(date +"%Y/%m/%d")/*.log 2> /dev/null| wc -l) 72 | 73 | noaaLastDir=$(dirname $(cat $wwwDir/noaa-last-recording.tmp)) 74 | 75 | echo "

NOAA recordings

" >> $dirList 76 | echo "

Recent pass

" >> $dirList 77 | echo "" >> $dirList 78 | echo "recent recording" >> $dirList 79 | echo "recent recording" >> $dirList 80 | echo "recent recording" >> $dirList 81 | echo "recent recording" >> $dirList 82 | echo "" >> $dirList 83 | echo "

" >> $dirList 84 | 85 | echo "

Archive

" >> $dirList 86 | echo "" >> $dirList 105 | 106 | } # end function gallery noaa 107 | 108 | # ---- METEOR list all dates and times -------------------------------------------------# 109 | 110 | 111 | function gallery_meteor { 112 | 113 | howManyToday=$(ls $meteorDir/img/$(date +"%Y/%m/%d")/*-Ch0.jpg 2> /dev/null| wc -l) 114 | meteorLastDir=$(dirname $(cat $wwwDir/meteor-last-recording.tmp)) 115 | 116 | 117 | echo "

METEOR-M2 recordings

" >> $dirList 118 | echo "

Recent pass

" >> $dirList 119 | echo "" >> $dirList 120 | echo "recent recording" >> $dirList 121 | echo "recent recording" >> $dirList 122 | echo "recent recording" >> $dirList 123 | echo "recent recording" >> $dirList 124 | echo "" >> $dirList 125 | 126 | echo "

" >> $dirList 127 | 128 | echo "

Archive

" >> $dirList 129 | echo "" >> $dirList 148 | 149 | } # end function gallery meteor 150 | 151 | 152 | 153 | # ---- ISS loop all dates and times -------------------------------------------------# 154 | function gallery_iss { 155 | 156 | howManyToday=$(ls $recordingDir/iss/rec/$(date +"%Y/%m/")/*.log 2> /dev/null| wc -l) 157 | 158 | echo "

ISS recordings

" >> $dirList 159 | echo "" >> $dirList 172 | 173 | } 174 | 175 | # ---- LOGS -------------------------------------------------# 176 | 177 | function gallery_logs { 178 | 179 | 180 | echo "

Logs

" >> $dirList 181 | echo "" >> $dirList 185 | } 186 | 187 | # ---- dump1090 -------------------------------------------------# 188 | 189 | function gallery_dump1090 { 190 | echo "

dump1090 heatmap

" >> $dirList 191 | echo "dump1090 heatmap" >> $dirList 192 | echo "dump1090 heatmap" >> $dirList 193 | } 194 | 195 | 196 | # ------------------------------------------template engine --------------------- # 197 | 198 | # ----- RENDER APPROPRIATE PAGES ---- # 199 | 200 | if [ "$includeGalleryNoaa" = '1' ]; then 201 | gallery_noaa 202 | fi 203 | 204 | if [ "$includeGalleryMeteor" = '1' ]; then 205 | gallery_meteor 206 | fi 207 | 208 | if [ "$includeGalleryLogs" = '1' ]; then 209 | gallery_logs 210 | fi 211 | 212 | if [ "$includeGalleryISS" = '1' ]; then 213 | gallery_iss 214 | fi 215 | 216 | if [ "$includeGalleryDump1090" = '1' ]; then 217 | gallery_dump1090 218 | fi 219 | 220 | 221 | # ----- MAIN PAGE ---- # 222 | 223 | htmlTitle="Main page" 224 | htmlBody=$(cat $dirList) 225 | source $htmlTemplate > $htmlOutput 226 | 227 | # ----- PASS LIST ---- # 228 | 229 | htmlTitle="Pass table" 230 | htmlBody=$(cat $htmlNextPassList) 231 | htmlBody="

pass table plot

"$htmlBody 232 | 233 | source $htmlTemplate > $htmlOutputTable 234 | -------------------------------------------------------------------------------- /bin/kill_rtl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | killall -9 rtl_fm 4 | killall -9 rtl_power 5 | -------------------------------------------------------------------------------- /bin/multidemodulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | infile="$1" 4 | 5 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 6 | # do not change the following three lines 7 | scriptDir="$(dirname "$(realpath "$0")")" 8 | source $scriptDir/basedir_conf.py 9 | source $baseDir/_listvars.sh 10 | 11 | 12 | 13 | sox "$infile" -b 16 --encoding signed-integer --endian little -t raw - | multimon-ng -a AFSK1200 -A -t raw - -------------------------------------------------------------------------------- /bin/pymultimonaprs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | 10 | logfile="$baseDir/recordings/aprs/$(date +"%Y/%m")/$(date +"%Y%m%d").txt" 11 | 12 | mkdir -p $(dirname $logfile) 13 | 14 | duration=$1 15 | dongleShift=$2 16 | 17 | echo "Running for $duration with dongleshift $dongleShift" 18 | 19 | mkdir -p $(dirname $logfile) 20 | 21 | timeout --kill-after=1 $duration pymultimonaprs -v -c $baseDir/bin/pymultimonaprs.confs/pymultimonaprs.json | tee -a $logfile 22 | 23 | -------------------------------------------------------------------------------- /bin/radiosonde_auto_rx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | 10 | # Track radiosondes 11 | # more info: see https://github.com/projecthorus/radiosonde_auto_rx 12 | 13 | duration=$1 14 | dongleShift=$2 15 | 16 | echo "$dongleShift" 17 | 18 | cd ~/progs/radiosonde_auto_rx/auto_rx/ 19 | timeout --kill-after=20 --signal=SIGINT $duration python auto_rx.py 20 | -------------------------------------------------------------------------------- /bin/rtl_433.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # runs rtl_433 and scans for 433 MHz data transfers 4 | 5 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 6 | # do not change the following three lines 7 | scriptDir="$(dirname "$(realpath "$0")")" 8 | source $scriptDir/basedir_conf.py 9 | source $baseDir/_listvars.sh 10 | 11 | #--------------------------------------# 12 | 13 | logfile="$recordingDir/rtl_433/"$(date +"%Y/%m")/$(date +"%Y%m%d")".txt" 14 | 15 | mkdir -p $(dirname $logfile) 16 | 17 | duration=$1 18 | dongleShift=$2 19 | 20 | mkdir -p $(dirname $logfile) 21 | 22 | timeout --kill-after=1 $duration rtl_433 -p $dongleShift -G | tee -a $logfile 23 | -------------------------------------------------------------------------------- /bin/update-keps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 4 | # do not change the following three lines 5 | scriptDir="$(dirname "$(realpath "$0")")" 6 | source $scriptDir/basedir_conf.py 7 | source $baseDir/_listvars.sh 8 | 9 | ### 10 | 11 | TLEDIR=$baseDir/var/tle/ 12 | mkdir -p $TLEDIR 13 | 14 | #rm $TLEDIR/weather.txt 15 | #wget --no-check-certificate -r http://www.celestrak.com/NORAD/elements/weather.txt -O $TLEDIR/weather.txt 16 | 17 | #rm $TLEDIR/noaa.txt 18 | #wget -r http://www.celestrak.com/NORAD/elements/noaa.txt -O $TLEDIR/noaa.txt 19 | 20 | rm $TLEDIR/nasa.txt 21 | wget -r https://www.amsat.org/amsat/ftp/keps/current/nasa.all -O $TLEDIR/nasa.txt 22 | 23 | rm $TLEDIR/amateur.txt 24 | wget -r http://www.celestrak.com/NORAD/elements/amateur.txt -O $TLEDIR/amateur.txt 25 | 26 | rm $TLEDIR/cubesat.txt 27 | wget -r http://www.celestrak.com/NORAD/elements/cubesat.txt -O $TLEDIR/cubesat.txt 28 | 29 | 30 | rm $TLEDIR/multi.txt 31 | wget -r http://www.pe0sat.vgnet.nl/kepler/mykepler.txt -O $TLEDIR/multi.txt 32 | 33 | rm $TLEDIR/all.txt 34 | cat $TLEDIR/*.txt > $TLEDIR/all.txt 35 | 36 | 37 | echo "$wwwDir" 38 | date 39 | date -R > $wwwDir/keps.tmp 40 | date +"%s" > $wwwDir/kepsU.tmp 41 | echo Updated 42 | -------------------------------------------------------------------------------- /bin/wspr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # runs WSPR receiver 4 | 5 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 6 | # do not change the following three lines 7 | scriptDir="$(dirname "$(realpath "$0")")" 8 | source $scriptDir/basedir_conf.py 9 | source $baseDir/_listvars.sh 10 | 11 | #--------------------------------------# 12 | 13 | # # # # Edit following variable with your correct data # # # # 14 | call="SA7BNT" 15 | gain="40" 16 | locator="JO77PP" 17 | hz="28.1246M" 18 | info_rx="Start reception 10 meters" 19 | sampling="0" #direct sampling [0,1,2] (default: 0 for Quadrature, 1 for I branch, 2 for Q branch) 20 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## 21 | 22 | logfile="$recordingDir/rtlsdr_wspr/"$(date +"%Y/%m")/$(date +"%Y%m%d")".txt" 23 | 24 | mkdir -p $(dirname $logfile) 25 | 26 | duration=$1 27 | dongleShift=$2 28 | 29 | mkdir -p $(dirname $logfile) 30 | 31 | sleep 1 32 | pgrep rtlsdr_wsprd > /dev/null 2>&1 33 | if [ $? -eq 0 ]; then 34 | echo $'\n'"---Kill rtlsdr_wsprd pid---" >> $logfile 35 | killall rtlsdr_wsprd &>> $logfile 36 | fi 37 | echo $'\n'"$(date)" >> $logfile 38 | echo "$info_rx"$'\n' >> $logfile 39 | sleep 1 40 | 41 | 42 | cd ~/rtlsdr-wsprd 43 | timeout --kill-after=1 $duration ./rtlsdr_wsprd -p "$dongleShift" -f "$hz" -c "$call" -l "$locator" -g "$gain" -d "$sampling" | tee -a $logfile 44 | -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | outfile="basedir_conf.py" 4 | pwdir=$(pwd) 5 | 6 | echo "# this is automaticaly generated script" > $outfile 7 | echo "# use configure.sh to generate the file" >> $outfile 8 | echo >> $outfile 9 | echo "baseDir=\"$pwdir/\"" >> $outfile 10 | 11 | 12 | ln -r -f -s $outfile "bin/$outfile" 13 | ln -r -f -s $outfile "bin/crontab/$outfile" 14 | 15 | echo "Creating symlinks to the config file..." 16 | 17 | for d in modules/*/ 18 | do 19 | ln -r -f -s $outfile "$d/$outfile" 20 | done -------------------------------------------------------------------------------- /docs/NOAA19-HVCT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/NOAA19-HVCT.jpg -------------------------------------------------------------------------------- /docs/NOAA19-therm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/NOAA19-therm.jpg -------------------------------------------------------------------------------- /docs/Python-2.7-lightgreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/Python-2.7-lightgreen.png -------------------------------------------------------------------------------- /docs/badge-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/badge-email.png -------------------------------------------------------------------------------- /docs/meteor/meteor-ch2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/meteor/meteor-ch2.jpg -------------------------------------------------------------------------------- /docs/meteor/meteor-combo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/meteor/meteor-combo.jpg -------------------------------------------------------------------------------- /docs/nextpass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
#satellitestartdurationpeakazimuthfreqprocess with
1PR3_NEWS2018-01-29 21:0005:000° (N)98988000Hzmodules/fm/fm.sh
2ISS2018-01-29 21:0609:5967°191° (S)145800000Hzmodules/iss/iss_voice.sh
3PR3_NEWS2018-01-29 22:0005:000° (N)98988000Hzmodules/fm/fm.sh
4ISS2018-01-29 22:4209:3024°210° (SSW)145800000Hzmodules/iss/iss_voice.sh
5PR3_NEWS2018-01-29 23:0005:000° (N)98988000Hzmodules/fm/fm.sh
6NOAA-192018-01-30 02:5414:2230°94° (E)137100000Hzmodules/noaa/noaa.sh
7NOAA-192018-01-30 04:3414:5453°294° (WNW)137100000Hzmodules/noaa/noaa.sh
8NOAA-152018-01-30 05:2013:1622°90° (E)137620000Hzmodules/noaa/noaa.sh
9NOAA-152018-01-30 06:5914:3568°290° (W)137620000Hzmodules/noaa/noaa.sh
10NOAA-182018-01-30 07:2514:5761°102° (E)137912500Hzmodules/noaa/noaa.sh
11PR3_NEWS2018-01-30 08:0005:000° (N)98988000Hzmodules/fm/fm.sh
12PR3_NEWS2018-01-30 09:0005:000° (N)98988000Hzmodules/fm/fm.sh
13NOAA-182018-01-30 09:0613:4928°302° (WNW)137912500Hzmodules/noaa/noaa.sh
14PR3_NEWS2018-01-30 10:0005:000° (N)98988000Hzmodules/fm/fm.sh
15PR3_NEWS2018-01-30 11:0005:000° (N)98988000Hzmodules/fm/fm.sh
16PR3_NEWS2018-01-30 12:0005:000° (N)98988000Hzmodules/fm/fm.sh
17NOAA-192018-01-30 12:4713:0320°52° (NE)137100000Hzmodules/noaa/noaa.sh
18PR3_NEWS2018-01-30 13:0005:000° (N)98988000Hzmodules/fm/fm.sh
19PR3_NEWS2018-01-30 14:0005:000° (N)98988000Hzmodules/fm/fm.sh
20NOAA-192018-01-30 14:2615:0488°252° (WSW)137100000Hzmodules/noaa/noaa.sh
21PR3_NEWS2018-01-30 15:0005:000° (N)98988000Hzmodules/fm/fm.sh
22PR3_NEWS2018-01-30 16:0005:000° (N)98988000Hzmodules/fm/fm.sh
23NOAA-152018-01-30 16:4614:2960°68° (ENE)137620000Hzmodules/noaa/noaa.sh
24PR3_NEWS2018-01-30 17:0005:000° (N)98988000Hzmodules/fm/fm.sh
25ISS2018-01-30 17:0109:1121°146° (SE)145800000Hzmodules/iss/iss_voice.sh
26NOAA-182018-01-30 17:1714:2537°61° (NE)137912500Hzmodules/noaa/noaa.sh
27PR3_NEWS2018-01-30 18:0005:000° (N)98988000Hzmodules/fm/fm.sh
28NOAA-152018-01-30 18:2613:3825°268° (WSW)137620000Hzmodules/noaa/noaa.sh
29ISS2018-01-30 18:3709:5862°165° (SSE)145800000Hzmodules/iss/iss_voice.sh
30NOAA-182018-01-30 18:5814:4043°261° (WSW)137912500Hzmodules/noaa/noaa.sh
31PR3_NEWS2018-01-30 20:0005:000° (N)98988000Hzmodules/fm/fm.sh
32ISS2018-01-30 20:1310:0177°185° (S)145800000Hzmodules/iss/iss_voice.sh
33PR3_NEWS2018-01-30 21:0005:000° (N)98988000Hzmodules/fm/fm.sh
34ISS2018-01-30 21:5009:4235°205° (SSW)145800000Hzmodules/iss/iss_voice.sh
35PR3_NEWS2018-01-30 22:0005:000° (N)98988000Hzmodules/fm/fm.sh
36PR3_NEWS2018-01-30 23:0005:000° (N)98988000Hzmodules/fm/fm.sh
37NOAA-192018-01-31 02:4214:0325°91° (E)137100000Hzmodules/noaa/noaa.sh
38NOAA-192018-01-31 04:2315:0464°291° (W)137100000Hzmodules/noaa/noaa.sh
39NOAA-152018-01-31 06:3414:4079°105° (E)137620000Hzmodules/noaa/noaa.sh
40PR3_NEWS2018-01-31 07:0005:000° (N)98988000Hzmodules/fm/fm.sh
41NOAA-182018-01-31 07:1314:5050°100° (E)137912500Hzmodules/noaa/noaa.sh
42PR3_NEWS2018-01-31 08:0005:000° (N)98988000Hzmodules/fm/fm.sh
43NOAA-152018-01-31 08:1412:5422°305° (WNW)137620000Hzmodules/noaa/noaa.sh
44NOAA-182018-01-31 08:5414:0633°300° (WNW)137912500Hzmodules/noaa/noaa.sh
45PR3_NEWS2018-01-31 10:0005:000° (N)98988000Hzmodules/fm/fm.sh
46PR3_NEWS2018-01-31 11:0005:000° (N)98988000Hzmodules/fm/fm.sh
47PR3_NEWS2018-01-31 12:0005:000° (N)98988000Hzmodules/fm/fm.sh
48PR3_NEWS2018-01-31 13:0005:000° (N)98988000Hzmodules/fm/fm.sh
49PR3_NEWS2018-01-31 14:0005:000° (N)98988000Hzmodules/fm/fm.sh
50NOAA-192018-01-31 14:1515:0177°70° (ENE)137100000Hzmodules/noaa/noaa.sh
51PR3_NEWS2018-01-31 15:0005:000° (N)98988000Hzmodules/fm/fm.sh
52NOAA-192018-01-31 15:5713:2220°270° (W)137100000Hzmodules/noaa/noaa.sh
53NOAA-152018-01-31 16:2114:0540°63° (NE)137620000Hzmodules/noaa/noaa.sh
54PR3_NEWS2018-01-31 17:0005:000° (N)98988000Hzmodules/fm/fm.sh
55NOAA-182018-01-31 17:0614:0632°58° (NE)137912500Hzmodules/noaa/noaa.sh
56ISS2018-01-31 17:4509:5947°160° (SSE)145800000Hzmodules/iss/iss_voice.sh
57PR3_NEWS2018-01-31 18:0005:000° (N)98988000Hzmodules/fm/fm.sh
58NOAA-152018-01-31 18:0114:1438°263° (WSW)137620000Hzmodules/noaa/noaa.sh
59NOAA-182018-01-31 18:4614:5953°258° (WSW)137912500Hzmodules/noaa/noaa.sh
60PR3_NEWS2018-01-31 19:0005:000° (N)98988000Hzmodules/fm/fm.sh
61ISS2018-01-31 19:2110:0781°179° (SSE)145800000Hzmodules/iss/iss_voice.sh
62PR3_NEWS2018-01-31 20:0005:000° (N)98988000Hzmodules/fm/fm.sh
63PR3_NEWS2018-01-31 21:0005:000° (N)98988000Hzmodules/fm/fm.sh
64PR3_NEWS2018-01-31 22:0005:000° (N)98988000Hzmodules/fm/fm.sh
65PR3_NEWS2018-01-31 23:0005:000° (N)98988000Hzmodules/fm/fm.sh
66NOAA-192018-02-01 02:3113:3320°89° (ENE)137100000Hzmodules/noaa/noaa.sh
67NOAA-192018-02-01 04:1115:1077°289° (W)137100000Hzmodules/noaa/noaa.sh
68NOAA-152018-02-01 06:0914:3051°100° (E)137620000Hzmodules/noaa/noaa.sh
69PR3_NEWS2018-02-01 07:0005:000° (N)98988000Hzmodules/fm/fm.sh
70NOAA-182018-02-01 07:0214:3741°98° (E)137912500Hzmodules/noaa/noaa.sh
71NOAA-152018-02-01 07:4913:4131°300° (WNW)137620000Hzmodules/noaa/noaa.sh
72PR3_NEWS2018-02-01 08:0005:000° (N)98988000Hzmodules/fm/fm.sh
73NOAA-182018-02-01 08:4314:2239°298° (WNW)137912500Hzmodules/noaa/noaa.sh
74PR3_NEWS2018-02-01 09:0005:000° (N)98988000Hzmodules/fm/fm.sh
75PR3_NEWS2018-02-01 10:0005:000° (N)98988000Hzmodules/fm/fm.sh
76PR3_NEWS2018-02-01 11:0005:000° (N)98988000Hzmodules/fm/fm.sh
77PR3_NEWS2018-02-01 12:0005:000° (N)98988000Hzmodules/fm/fm.sh
78PR3_NEWS2018-02-01 13:0005:000° (N)98988000Hzmodules/fm/fm.sh
79PR3_NEWS2018-02-01 14:0005:000° (N)98988000Hzmodules/fm/fm.sh
80NOAA-192018-02-01 14:0314:5664°68° (ENE)137100000Hzmodules/noaa/noaa.sh
81PR3_NEWS2018-02-01 15:0005:000° (N)98988000Hzmodules/fm/fm.sh
82NOAA-192018-02-01 15:4513:5824°268° (WSW)137100000Hzmodules/noaa/noaa.sh
83NOAA-152018-02-01 15:5613:3427°58° (NE)137620000Hzmodules/noaa/noaa.sh
84NOAA-182018-02-01 16:5413:4627°56° (NE)137912500Hzmodules/noaa/noaa.sh
85NOAA-152018-02-01 17:3514:4160°258° (WSW)137620000Hzmodules/noaa/noaa.sh
86PR3_NEWS2018-02-01 18:0005:000° (N)98988000Hzmodules/fm/fm.sh
87NOAA-182018-02-01 18:3415:0565°256° (WSW)137912500Hzmodules/noaa/noaa.sh
88PR3_NEWS2018-02-01 19:0005:000° (N)98988000Hzmodules/fm/fm.sh
89ISS2018-02-01 20:0509:5962°193° (S)145800000Hzmodules/iss/iss_voice.sh
90ISS2018-02-01 21:4209:1221°212° (SSW)145800000Hzmodules/iss/iss_voice.sh
91NOAA-192018-02-02 04:0015:1388°107° (E)137100000Hzmodules/noaa/noaa.sh
92NOAA-192018-02-02 05:4113:1221°307° (WNW)137100000Hzmodules/noaa/noaa.sh
93NOAA-152018-02-02 05:4414:0233°95° (E)137620000Hzmodules/noaa/noaa.sh
94NOAA-182018-02-02 06:5014:2233°95° (E)137912500Hzmodules/noaa/noaa.sh
95NOAA-152018-02-02 07:2414:1445°295° (WNW)137620000Hzmodules/noaa/noaa.sh
96NOAA-182018-02-02 08:3114:3647°295° (WNW)137912500Hzmodules/noaa/noaa.sh
97NOAA-192018-02-02 13:5214:4753°65° (NE)137100000Hzmodules/noaa/noaa.sh
98NOAA-192018-02-02 15:3314:1430°265° (WSW)137100000Hzmodules/noaa/noaa.sh
99ISS2018-02-02 16:0109:2024°148° (SE)145800000Hzmodules/iss/iss_voice.sh
100NOAA-182018-02-02 16:4313:2223°54° (NE)137912500Hzmodules/noaa/noaa.sh
104 | -------------------------------------------------------------------------------- /docs/nextpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/nextpass.png -------------------------------------------------------------------------------- /docs/passtable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/passtable.jpg -------------------------------------------------------------------------------- /docs/sstv/iss1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/sstv/iss1.jpeg -------------------------------------------------------------------------------- /docs/sstv/iss3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/sstv/iss3.jpeg -------------------------------------------------------------------------------- /docs/www-dynamic+shadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/www-dynamic+shadow.jpg -------------------------------------------------------------------------------- /docs/www-dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/www-dynamic.png -------------------------------------------------------------------------------- /docs/www-static+shadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/www-static+shadow.jpg -------------------------------------------------------------------------------- /docs/www-static.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/docs/www-static.jpg -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # run: conda env create --file environment.yml 2 | name: autowx2 3 | channels: 4 | - conda-forge 5 | - defaults 6 | dependencies: 7 | - python=3.9 8 | - numpy 9 | - matplotlib 10 | - scikit-learn 11 | - pip 12 | - pip: 13 | - Flask 14 | - Flask-SocketIO 15 | - pypredict 16 | -------------------------------------------------------------------------------- /genpasstable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # autowx2 - genpastable.py 6 | # generates the pass table and recording plan for tne next few days and the appropriate plot 7 | # 8 | # GANTT Chart with Matplotlib 9 | # Sukhbinder 10 | # inspired by: 11 | # https://sukhbinder.wordpress.com/2016/05/10/quick-gantt-chart-with-matplotlib/ 12 | # taken from 13 | # https://github.com/fialhocoelho/test/blob/master/plot/gantt.py 14 | # 15 | 16 | from autowx2_functions import * 17 | 18 | if __name__ == "__main__": 19 | satellites = list(satellitesData) 20 | qth = (stationLat, stationLon, stationAlt) 21 | generatePassTableAndSaveFiles(satellites, qth) 22 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### created for and tested at the debian-like systems (tested on debian, ubuntu and mint) 4 | 5 | ### for installing the dongle 6 | ### for details, see: http://www.instructables.com/id/rtl-sdr-on-Ubuntu/ 7 | #sudo echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr"' >> /etc/udev/rules.d/20.rtlsdr.rules 8 | #sudo echo "blacklist dvb_usb_rtl28xxu" >> /etc/modprobe.d/rtl-sdr-blacklist.conf 9 | 10 | MACHINE_TYPE=$(uname -m) 11 | echo $MACHINE_TYPE 12 | 13 | bash ./configure.sh 14 | 15 | #echo "copy sample config file, but don't overwrite" 16 | cp --no-clobber autowx2_conf.py.example autowx2_conf.py 17 | 18 | 19 | echo "basedir_conf.py:" 20 | cat basedir_conf.py 21 | 22 | source basedir_conf.py 23 | echo $baseDir 24 | 25 | echo 26 | echo 27 | echo "******** Installing required packages" 28 | echo 29 | echo 30 | sudo apt update 31 | sudo apt install -y rtl-sdr git libpulse-dev fftw3 libc6 libfontconfig1 libx11-6 libxext6 libxft2 libusb-1.0-0-dev \ 32 | libavahi-client-dev libavahi-common-dev libdbus-1-dev libfftw3-single3 libpulse-mainloop-glib0 librtlsdr0 librtlsdr-dev \ 33 | libfftw3-dev libfftw3-double3 lame sox libsox-fmt-mp3 libtool automake imagemagick \ 34 | bc imagemagick 35 | 36 | 37 | if [ ${MACHINE_TYPE} == 'armv6l' ] || [ ${MACHINE_TYPE} == 'armv7l' ]; then 38 | echo 39 | echo 40 | echo "******** Installing Rpi required packages" 41 | echo 42 | echo 43 | sudo apt-get install -y libtool qt4-default automake autotools-dev m4 44 | curl https://bootstrap.pypa.io/get-pip.py > get-pip.py 45 | sudo python get-pip.py 46 | else 47 | sudo apt-get install -y libfftw3-long3 48 | sudo apt-get install -y libfftw3-quad3 49 | fi 50 | 51 | 52 | PIP_OPTIONS="" 53 | if [ ${MACHINE_TYPE} == 'armv6l' ] || [ ${MACHINE_TYPE} == 'armv7l' ]; then 54 | PIP_OPTIONS="--no-cache-dir" 55 | fi 56 | 57 | # echo 58 | # echo 59 | # echo "******** Installing python requirements" 60 | # echo 61 | # echo 62 | 63 | # use pip: 64 | # pip $PIP_OPTIONS install -r requirements.txt 65 | 66 | # or conda: 67 | # conda env create --file environment.yml 68 | 69 | 70 | mkdir -p $baseDir/bin/sources/ 71 | 72 | cd $baseDir/bin/sources/ 73 | 74 | echo 75 | echo 76 | echo "******** Installing wxtoimg" 77 | echo 78 | echo 79 | 80 | if [ ${MACHINE_TYPE} == 'x86_64' ]; then 81 | echo "64-bit system" 82 | wget https://wxtoimgrestored.xyz/beta/wxtoimg-linux-amd64-2.11.2-beta.tar.gz 83 | gunzip < wxtoimg-linux-amd64-2.11.2-beta.tar.gz | sudo sh -c "(cd /; tar -xvf -)" 84 | elif [ ${MACHINE_TYPE} == 'armv6l' ] || [ ${MACHINE_TYPE} == 'armv7l' ]; then 85 | wget https://wxtoimgrestored.xyz/beta/wxtoimg-armhf-2.11.2-beta.deb 86 | sudo dpkg -i wxtoimg-armhf-2.11.2-beta.deb 87 | else 88 | echo "32-bit system" 89 | wget https://wxtoimgrestored.xyz/beta/wxtoimg-i386-2.11.2-beta.deb 90 | sudo dpkg -i wxtoimg_2.10.11-1_i386.deb # may generate some dependencies errors; if not, stop here 91 | # sudo apt-get -f install 92 | fi 93 | 94 | wxtoimg -h 95 | 96 | 97 | echo 98 | echo 99 | echo "******** Installing multimon-ng-stqc" 100 | echo 101 | echo 102 | 103 | cd $baseDir/bin/sources/ 104 | 105 | git clone https://github.com/sq5bpf/multimon-ng-stqc.git 106 | cd multimon-ng-stqc 107 | mkdir build 108 | cd build 109 | qmake ../multimon-ng.pro 110 | make 111 | sudo make install 112 | 113 | 114 | multimon-ng -h 115 | 116 | 117 | 118 | echo 119 | echo 120 | echo "******** Installing kalibrate" 121 | echo 122 | echo 123 | 124 | cd $baseDir/bin/sources/ 125 | 126 | git clone https://github.com/viraptor/kalibrate-rtl.git 127 | cd kalibrate-rtl 128 | ./bootstrap 129 | ./configure 130 | make 131 | sudo make install 132 | 133 | kal -h 134 | 135 | 136 | echo 137 | echo 138 | echo "******** Getting auxiliary programs" 139 | echo 140 | echo 141 | 142 | cd $baseDir/bin/ 143 | wget https://raw.githubusercontent.com/filipsPL/heatmap/master/heatmap.py -O $baseDir/bin/heatmap.py 144 | 145 | 146 | 147 | echo 148 | echo 149 | echo "******** Getting fresh keplers" 150 | echo 151 | echo 152 | 153 | cd $baseDir 154 | bin/update-keps.sh 155 | 156 | 157 | 158 | echo "***************** default dongle shift...." 159 | 160 | echo -n "0" > var/dongleshift.txt 161 | 162 | echo 163 | echo "-------------------------------------------------------------------------" 164 | echo "The installation script seems to be finished." 165 | echo "please inspect the output. If there are no errors, your system is" 166 | echo "installed correctly." 167 | echo "Edit autowx2_conf.py to suit your needs and have fun!" 168 | echo "-------------------------------------------------------------------------" 169 | echo 170 | 171 | exit 0 172 | -------------------------------------------------------------------------------- /modules/_template/fm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # test file to record fm radio for a given period of time 4 | 5 | 6 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 7 | # do not change the following three lines 8 | scriptDir="$(dirname "$(realpath "$0")")" 9 | source $scriptDir/basedir_conf.py 10 | source $baseDir/_listvars.sh 11 | 12 | 13 | #### microconfiguration 14 | 15 | recdir="$recordingDir/fm/rec/"$(date +"%Y/%m/") 16 | 17 | ### doing the job 18 | 19 | mkdir -p $recdir 20 | 21 | fileNameCore="$1" 22 | satellite="$2" 23 | start="$3" 24 | duration="$4" 25 | peak="$5" 26 | azimuth="$6" 27 | freq="$7" 28 | 29 | echo "fileNameCore=$fileNameCore" 30 | echo "satellite=$satellite" 31 | echo "start=$start" 32 | echo "duration=$duration" 33 | echo "peak=$peak" 34 | echo "azimuth=$azimuth" 35 | echo "freq=$freq" 36 | 37 | 38 | # fixed values for tests 39 | #freq="98988000" 40 | #duration="10s" 41 | #fileNameCore="trojka" 42 | 43 | 44 | timeout $duration rtl_fm -f $freq -M wbfm -g 49.6 | lame -r -s 32 -m m - $recdir/$fileNameCore.mp3 45 | 46 | -------------------------------------------------------------------------------- /modules/fm/fm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # test file to record fm radio for a given period of time 4 | 5 | 6 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 7 | # do not change the following three lines 8 | scriptDir="$(dirname "$(realpath "$0")")" 9 | source $scriptDir/basedir_conf.py 10 | source $baseDir/_listvars.sh 11 | 12 | 13 | #### microconfiguration 14 | 15 | recdir="$recordingDir/fm/rec/"$(date +"%Y/%m/") 16 | 17 | ### doing the job 18 | 19 | mkdir -p $recdir 20 | 21 | fileNameCore="$1" 22 | satellite="$2" 23 | start="$3" 24 | duration="$4" 25 | peak="$5" 26 | azimuth="$6" 27 | freq="$7" 28 | 29 | 30 | echo "fileNameCore=$fileNameCore" 31 | echo "satellite=$satellite" 32 | echo "start=$start" 33 | echo "duration=$duration" 34 | echo "peak=$peak" 35 | echo "azimuth=$azimuth" 36 | echo "freq=$freq" 37 | 38 | 39 | 40 | # fixed values for tests 41 | # freq="98796500" 42 | # duration="10s" 43 | # fileNameCore="trojka" 44 | 45 | 46 | echo 47 | echo "Recording to:" $recdir/$fileNameCore.mp3 48 | echo 49 | 50 | timeout $duration rtl_fm -f $freq -M wfm -g 49.6 -l 0 -s 220k -A std -E deemp -r 44.1k | sox -t raw -e signed -c 1 -b 16 -r 44000 -V1 - "$recdir/$fileNameCore.mp3" 51 | -------------------------------------------------------------------------------- /modules/iss/iss_voice_iq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # file to record fm radio for a given period of time 4 | # audio files saved to iq/raw files! 5 | 6 | 7 | # variable(s) to adjust: 8 | 9 | 10 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 11 | # do not change the following three lines 12 | scriptDir="$(dirname "$(realpath "$0")")" 13 | source $scriptDir/basedir_conf.py 14 | source $baseDir/_listvars.sh 15 | 16 | 17 | #### microconfiguration 18 | 19 | recdir="$recordingDir/iss/rec/"$(date +"%Y/%m/") 20 | sample="44000" 21 | 22 | ### doing the job 23 | 24 | mkdir -p $recdir 25 | 26 | fileNameCore="$1" 27 | satellite="$2" 28 | start="$3" 29 | duration="$4" 30 | peak="$5" 31 | azimuth="$6" 32 | freq="$7" 33 | 34 | 35 | echo "fileNameCore=$fileNameCore" 36 | echo "satellite=$satellite" 37 | echo "start=$start" 38 | echo "duration=$duration" 39 | echo "peak=$peak" 40 | echo "azimuth=$azimuth" 41 | echo "freq=$freq" 42 | 43 | 44 | ### fixed values for tests 45 | #freq="98988000" 46 | #duration="10s" 47 | #fileNameCore="ISS" 48 | 49 | ### RECORDING TO WAV, for further processing, eg, for SSTV 50 | echo "Recording to dat/iq format" 51 | 52 | # timeout $duration rtl_fm -f $freq -s $sample -g $dongleGain -F 9 -A fast -E offset -p $dongleShift $recdir/$fileNameCore.raw | tee -a $logFile 53 | # sox -t raw -r $sample -es -b 16 -c 1 -V1 $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav rate $wavrate | tee -a $logFile 54 | # touch -r $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav 55 | # rm $recdir/$fileNameCore.raw 56 | 57 | #sample="2500000" 58 | sample="44000" 59 | 60 | timeout $duration rtl_fm -f $freq -g $dongleGain -s $sample -F 9 -A fast -E offset -p $dongleShift $recdir/$fileNameCore.iq | tee -a $logFile 61 | sox -t raw -r $sample -es -b 16 -c 1 -V1 $recdir/$fileNameCore.iq $recdir/$fileNameCore.wav rate $wavrate | tee -a $logFile 62 | touch -r $recdir/$fileNameCore.iq $recdir/$fileNameCore.wav 63 | sox "$recdir/$fileNameCore.wav" -n spectrogram -o "$recdir/$fileNameCore-spectrogram.png" 64 | -------------------------------------------------------------------------------- /modules/iss/iss_voice_mp3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # file to record fm radio for a given period of time 4 | # may be used to record ISS voice channel 5 | # *audio saved to mp3* 6 | 7 | # variable(s) to adjust: 8 | 9 | 10 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 11 | # do not change the following three lines 12 | scriptDir="$(dirname "$(realpath "$0")")" 13 | source $scriptDir/basedir_conf.py 14 | source $baseDir/_listvars.sh 15 | 16 | 17 | #### microconfiguration 18 | 19 | recdir="$recordingDir/iss/rec/"$(date +"%Y/%m/") 20 | 21 | ### doing the job 22 | 23 | mkdir -p $recdir 24 | 25 | fileNameCore="$1" 26 | satellite="$2" 27 | start="$3" 28 | duration="$4" 29 | peak="$5" 30 | azimuth="$6" 31 | freq="$7" 32 | 33 | 34 | echo "fileNameCore=$fileNameCore" 35 | echo "satellite=$satellite" 36 | echo "start=$start" 37 | echo "duration=$duration" 38 | echo "peak=$peak" 39 | echo "azimuth=$azimuth" 40 | echo "freq=$freq" 41 | 42 | 43 | 44 | # fixed values for tests 45 | # freq="98988000" 46 | # duration="10s" 47 | # fileNameCore="ISS" 48 | 49 | 50 | ### RECORDING TO MP3 51 | echo "Recording to mp3" 52 | timeout $duration rtl_fm -f $freq -M fm -g 49.6 -l 0 | lame -r -s 32k -m m - "$recdir/$fileNameCore.mp3" 53 | sox "$recdir/$fileNameCore.mp3" -n spectrogram -o "$recdir/$fileNameCore-spectrogram.png" 54 | sox "$recdir/$fileNameCore.mp3" "$recdir/$fileNameCore-silence.mp3" silence -l 1 0.3 10% -1 2.0 10% 55 | $baseDir/bin/multidemodulator.sh "$recdir/$fileNameCore.mp3" > "$recdir/$fileNameCore-demodulated.log" 56 | -------------------------------------------------------------------------------- /modules/iss/iss_voice_wav.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # file to record fm radio for a given period of time 4 | # may be used to record ISS voice channel, or wav to SSTV conversion 5 | # audio files saved to wav! 6 | 7 | 8 | # variable(s) to adjust: 9 | 10 | 11 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 12 | # do not change the following three lines 13 | scriptDir="$(dirname "$(realpath "$0")")" 14 | source $scriptDir/basedir_conf.py 15 | source $baseDir/_listvars.sh 16 | 17 | 18 | #### microconfiguration 19 | 20 | recdir="$recordingDir/iss/rec/"$(date +"%Y/%m/") 21 | sample="44000" 22 | 23 | ### doing the job 24 | 25 | mkdir -p $recdir 26 | 27 | fileNameCore="$1" 28 | satellite="$2" 29 | start="$3" 30 | duration="$4" 31 | peak="$5" 32 | azimuth="$6" 33 | freq="$7" 34 | 35 | 36 | echo "fileNameCore=$fileNameCore" 37 | echo "satellite=$satellite" 38 | echo "start=$start" 39 | echo "duration=$duration" 40 | echo "peak=$peak" 41 | echo "azimuth=$azimuth" 42 | echo "freq=$freq" 43 | 44 | 45 | ### fixed values for tests 46 | # freq="98988000" 47 | # duration="10s" 48 | # fileNameCore="ISS" 49 | 50 | 51 | ### RECORDING TO WAV, for further processing, eg, for SSTV 52 | echo "Recording to wav" 53 | timeout $duration rtl_fm -f $freq -s $sample -g $dongleGain -F 9 -A fast -E offset -p $dongleShift $recdir/$fileNameCore.raw | tee -a $logFile 54 | sox -t raw -r $sample -es -b 16 -c 1 -V1 $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav rate $wavrate | tee -a $logFile 55 | touch -r $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav 56 | rm $recdir/$fileNameCore.raw 57 | -------------------------------------------------------------------------------- /modules/meteor-m2/meteor.conf.example: -------------------------------------------------------------------------------- 1 | # 2 | # meteor m2 module configuration file 3 | # 4 | 5 | # where images from mrlpt are saved 6 | # (as declared in mlrptrc config file - check it!) 7 | # eg: /var/lrpt/images/ 8 | rawImageDir="/meteor/img/raw/" 9 | 10 | 11 | # directory with meteor stuff 12 | meteorDir="$recordingDir/meteor/" 13 | 14 | # directory where the images finally will go 15 | imgdir="$meteorDir/img/"$(date +"%Y/%m/%d/") 16 | 17 | # resize images to the given size to avoid growing of the repository; in px 18 | resizeimageto=1024 19 | -------------------------------------------------------------------------------- /modules/meteor-m2/meteor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # module to record meteor m2 transmission 4 | # for configuration, see meteor.conf 5 | 6 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 7 | # do not change the following three lines 8 | scriptDir="$(dirname "$(realpath "$0")")" 9 | source $scriptDir/basedir_conf.py 10 | source $baseDir/_listvars.sh 11 | 12 | # read module config 13 | 14 | source $scriptDir/meteor.conf 15 | 16 | ### doing the job 17 | 18 | mkdir -p $imgdir 19 | 20 | # 21 | # Assigning variables 22 | # 23 | 24 | fileNameCore="$1" 25 | satellite="$2" 26 | start="$3" 27 | duration="$4" 28 | peak="$5" 29 | azimuth="$6" 30 | freq="$7" 31 | 32 | # 33 | # Saving to log file 34 | # 35 | 36 | logFile=$imgdir/$fileNameCore.log 37 | echo $logFile 38 | 39 | date > $logFile # initialize log file 40 | echo $fileNameCore >> $logFile 41 | echo $satellite >> $logFile 42 | echo $start >> $logFile 43 | echo $duration >> $logFile 44 | echo $peak >> $logFile 45 | echo $freq >> $logFile 46 | 47 | 48 | echo "fileNameCore=$fileNameCore" 49 | echo "satellite=$satellite" 50 | echo "start=$start" 51 | echo "duration=$duration" 52 | echo "peak=$peak" 53 | echo "azimuth=$azimuth" 54 | echo "freq=$freq" 55 | 56 | 57 | 58 | # 59 | # recording sumbodule 60 | # 61 | 62 | source $scriptDir/meteor_record.sh 63 | 64 | # 65 | # meteor gallery 66 | # 67 | 68 | source $scriptDir/meteor_gallery.sh 69 | -------------------------------------------------------------------------------- /modules/meteor-m2/meteor_gallery.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # moving recorded images to the appropriate final dir 5 | # 6 | 7 | # value for some tests: 8 | # fileNameCore="20190118-1012_METEOR-M2" 9 | # rawImageDir="./" 10 | 11 | 12 | 13 | outHtml="$imgdir/$fileNameCore.html" # html for this single pass 14 | indexHtml="$imgdir/index.html" # main index file for a given day 15 | htmlTemplate="$wwwDir/index.tpl" 16 | 17 | 18 | # ---single gallery preparation------------------------------------------------# 19 | 20 | makethumb() { 21 | obrazek="$1" 22 | local thumbnail=$(basename "$obrazek" .jpg)".th.jpg" 23 | convert -define jpeg:size=200x200 "$obrazek" -thumbnail '200x200^' granite: +swap -gravity center -extent 200x200 -composite -quality 82 "$thumbnail" 24 | echo "$thumbnail" 25 | } 26 | 27 | # -----------------------------------------------------------------------------# 28 | 29 | logFile="$imgdir/$fileNameCore.log" # log file to read from 30 | 31 | varDate=$(sed '1q;d' $logFile) 32 | varSat=$(sed '3q;d' $logFile) 33 | varStart=$(sed '4q;d' $logFile) # unused 34 | varDur=$(sed '5q;d' $logFile) 35 | varPeak=$(sed '6q;d' $logFile) 36 | varFreq=$(sed '7q;d' $logFile) 37 | 38 | dateTime=$(date -d @$varStart +"%Y-%m-%d") 39 | dateTimeDir=$(date -d @$varStart +"%Y/%m/%d") # directory format of date, eg. 2018/11/22/ 40 | wwwPath=$wwwRootPath/recordings/meteor/img/$dateTimeDir 41 | 42 | 43 | 44 | 45 | # -----------------------------------------------------------------------------# 46 | 47 | 48 | cd $rawImageDir 49 | 50 | if [ $(ls *.jpg 2> /dev/null | wc -l) = 0 ]; 51 | then 52 | echo "no images"; 53 | else 54 | 55 | # 56 | # should we resize images? 57 | # 58 | 59 | if [ "$resizeimageto" != "" ]; then 60 | echo "Resizing images to $resizeimageto px" 61 | mogrify -resize ${resizeimageto}x${resizeimageto}\> *.jpg 62 | fi 63 | 64 | # 65 | # some headers 66 | # 67 | 68 | echo "

$varSat | $varDate

" > $outHtml 69 | echo "

f=${varFreq}Hz, peak: ${varPeak}°, duration: ${varDur}s

" >> $outHtml 70 | 71 | # 72 | # loop over images and generate thumbnails 73 | # 74 | 75 | 76 | 77 | for obrazek in *.jpg 78 | do 79 | echo "Thumb for $obrazek" 80 | base=$(basename $obrazek .jpg) 81 | sizeof=$(du -sh "$obrazek" | cut -f 1) 82 | # generate thumbnail 83 | thumbnail=$(makethumb "$obrazek") 84 | echo $thumbnail 85 | echo "meteor image " >> $outHtml 86 | done 87 | 88 | 89 | # 90 | # get image core name 91 | # 92 | 93 | meteorcorename=$(ls *.jpg | head -1 | cut -d "-" -f 1-2) 94 | echo $wwwPath/$meteorcorename > $wwwDir/meteor-last-recording.tmp 95 | 96 | # 97 | # move images to their destination 98 | # 99 | 100 | mv $rawImageDir/* $imgdir/ 101 | # cp $rawImageDir/* $imgdir/ 102 | 103 | 104 | # ----consolidate data from the given day ------------------------------------# 105 | # generates neither headers nor footer of the html file 106 | 107 | echo "" > $indexHtml.tmp 108 | for htmlfile in $(ls $imgdir/*.html | grep -v "index.html") 109 | do 110 | cat $htmlfile >> $indexHtml.tmp 111 | done 112 | 113 | # ---------- generates pages according to the template file ------------------- 114 | 115 | currentDate=$(date) 116 | echo $currentDate 117 | 118 | htmlTitle="METEOR-M2 images | $dateTime" 119 | htmlBody=$(cat $indexHtml.tmp) 120 | 121 | source $htmlTemplate > $indexHtml 122 | 123 | # 124 | # generate static main page(s) 125 | # 126 | 127 | $baseDir/bin/gen-static-page.sh 128 | 129 | 130 | fi # there are images 131 | -------------------------------------------------------------------------------- /modules/meteor-m2/meteor_record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### WARNING: all dates and times must be in the UTC! 4 | 5 | startT=$(date +%H%M -d "$DATE + 1 min" -u) 6 | stopT=$(date +%H%M -d "$DATE + $duration sec" -u) 7 | durationMin=$(bc <<< "$duration/60 +2") 8 | 9 | # 10 | # recording 11 | # 12 | echo "$startT-$stopT, duration: $durationMin min" 13 | mlrpt -s $startT-$stopT -t $durationMin 14 | -------------------------------------------------------------------------------- /modules/noaa/_loop_noaa_gallery.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # NOAA sat pass gallery preparation 4 | # loop over passes in the given directory 5 | 6 | scriptDir="$(dirname "$(realpath "$0")")" 7 | source $scriptDir/basedir_conf.py 8 | source $baseDir/_listvars.sh 9 | 10 | # fileNameCore="$1" 11 | # satellite="$2" 12 | # start="$3" 13 | # duration="$4" 14 | # peak="$5" 15 | # azimuth="$6" 16 | # freq="$7" 17 | 18 | #imgdir=/home/filips/bin/autowx2/var/www/recordings/noaa/img/2018/11/14/ 19 | #fileNameCore="20181114-1818_NOAA-18" 20 | #noaaDir="/home/filips/bin/autowx2/var/www/recordings/noaa/" 21 | 22 | imgdir="$1" 23 | # curdir=$(pwd) 24 | 25 | for logfile in $(ls $imgdir/*.log) 26 | do 27 | fileNameCore=$(basename $logfile .log) 28 | echo $fileNameCore 29 | source $scriptDir/noaa_gallery.sh 30 | done 31 | -------------------------------------------------------------------------------- /modules/noaa/noaa-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # main file for recording and processing NOAA telementry data 4 | # configuration file: noaa.conf in the current directory 5 | 6 | 7 | 8 | scriptDir="$(dirname "$(realpath "$0")")" 9 | 10 | # read config from the NOAA config file 11 | source $scriptDir/noaa.conf 12 | 13 | #fileNameCore, satellite, start, duration+towait, peak, freq 14 | 15 | fileNameCore="$1" 16 | satellite="$2" 17 | start="$3" 18 | duration="$4" 19 | peak="$5" 20 | azimuth="$6" 21 | freq="$7" 22 | 23 | #-------------------------------# 24 | # to test the processing part: 25 | # comment the recording part and uncomment the following part 26 | # ./noaa.sh tests/20180118-1504_NOAA-19.wav tests/20180118-1504_NOAA-19 NOAA-19 1516284265 926 27 | fileNameCore="20180118-1504_NOAA-19" 28 | satellite="NOAA-19" 29 | start="1516284265" 30 | duration="926" 31 | peak="82" 32 | freq="144" 33 | imgdir="tests/" 34 | recdir="tests/" 35 | #-------------------------------# 36 | 37 | 38 | echo "fileNameCore=$fileNameCore" 39 | echo "satellite=$satellite" 40 | echo "start=$start" 41 | echo "duration=$duration" 42 | echo "peak=$peak" 43 | echo "azimuth=$azimuth" 44 | echo "freq=$freq" 45 | 46 | echo "imgdir=$imgdir" 47 | echo "recdir=$recdir" 48 | 49 | 50 | # 51 | # execute processing script and passing all arguments to the script 52 | # 53 | 54 | source $scriptDir/noaa_process.sh 55 | -------------------------------------------------------------------------------- /modules/noaa/noaa.conf.example: -------------------------------------------------------------------------------- 1 | # 2 | # NOAA module configuration fine tuning 3 | # usually there is no need to modify anything 4 | # 5 | 6 | 7 | # directory with noaa stuff 8 | noaaDir="$recordingDir/noaa/" 9 | 10 | # directory for generated images 11 | imgdir="$noaaDir/img/"$(date +"%Y/%m/%d/") 12 | 13 | # directory for recorded raw and wav files 14 | recdir="$noaaDir/rec/"$(date +"%Y/%m/%d/") 15 | 16 | 17 | # 18 | # Sample rate, width of recorded signal - should include few kHz for doppler shift 19 | sample='48000' 20 | # Sample rate of the wav file. Shouldn't be changed 21 | wavrate='11025' 22 | 23 | # 24 | # Dongle index, is there any rtl_fm allowing passing serial of dongle? 25 | dongleIndex='0' 26 | 27 | # enchancements to apply to the pocessed images. See wxtoimg manual for available options 28 | enchancements=('MCIR-precip' 'HVC' 'MSA' 'therm' 'HVCT-precip' 'NO') 29 | 30 | # resize images to the given size to avoid growing of the repository; in px 31 | # otherwise, comment out the line 32 | resizeimageto=1024 33 | -------------------------------------------------------------------------------- /modules/noaa/noaa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # main file for recording and processing NOAA telementry data 4 | # for configuration, see noaa.conf 5 | 6 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 7 | # do not change the following three lines 8 | scriptDir="$(dirname "$(realpath "$0")")" 9 | source $scriptDir/basedir_conf.py 10 | source $baseDir/_listvars.sh 11 | 12 | # 13 | # read configuration file 14 | # 15 | source $scriptDir/noaa.conf 16 | 17 | ################################################## 18 | 19 | #fileNameCore, satellite, start, duration+towait, peak, freq 20 | 21 | fileNameCore="$1" 22 | satellite="$2" 23 | start="$3" 24 | duration="$4" 25 | peak="$5" 26 | azimuth="$6" 27 | freq="$7" 28 | 29 | echo "fileNameCore=$fileNameCore" 30 | echo "satellite=$satellite" 31 | echo "start=$start" 32 | echo "duration=$duration" 33 | echo "peak=$peak" 34 | echo "azimuth=$azimuth" 35 | echo "freq=$freq" 36 | 37 | echo "sample=$sample" 38 | echo "wavrate=$wavrate" 39 | echo "dongleIndex=$dongleIndex" 40 | echo "enchancements=${enchancements}" 41 | 42 | #-------------------------------# 43 | # to test the processing part: 44 | # comment the recording part and uncomment the following part 45 | # ./noaa.sh tests/20180118-1504_NOAA-19.wav tests/20180118-1504_NOAA-19 NOAA-19 1516284265 926 46 | # fileNameCore="20180118-1504_NOAA-19" 47 | # satellite="NOAA-19" 48 | # start="1516284265" 49 | # duration="926" 50 | # peak="82" 51 | # freq="144" 52 | # imgdir="tests/" 53 | # recdir="tests/" 54 | #-------------------------------# 55 | 56 | # 57 | # create directories 58 | # 59 | 60 | mkdir -p $imgdir 61 | mkdir -p $recdir 62 | 63 | # 64 | # logs are very important! 65 | # 66 | logFile=$imgdir/$fileNameCore.log 67 | 68 | date >$logFile # initialize log file 69 | echo $fileNameCore >>$logFile 70 | echo $satellite >>$logFile 71 | echo $start >>$logFile 72 | echo $duration >>$logFile 73 | echo $peak >>$logFile 74 | echo $freq >>$logFile 75 | 76 | # 77 | # execute recordigng scriptDir and passing all arguments to the script 78 | # 79 | 80 | source $scriptDir/noaa_record.sh 81 | 82 | # 83 | # execute processing script and passing all arguments to the script 84 | # 85 | 86 | source $scriptDir/noaa_process.sh 87 | 88 | # 89 | # generate gallery for a given pass 90 | # 91 | 92 | source $scriptDir/noaa_gallery.sh 93 | 94 | # 95 | # generate static pages 96 | # 97 | 98 | $baseDir/bin/gen-static-page.sh 99 | -------------------------------------------------------------------------------- /modules/noaa/noaa_gallery.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # NOAA sat pass gallery preparation 4 | # generates a single html snippet with a current pass 5 | 6 | # fileNameCore="$1" 7 | # satellite="$2" 8 | # start="$3" 9 | # duration="$4" 10 | # peak="$5" 11 | # azimuth="$6" 12 | # freq="$7" 13 | 14 | # static values for tests 15 | # imgdir=/home/filips/bin/autowx2/var/www/recordings/noaa/img/2018/09/08/ 16 | #imgdir=/home/filips/github/autowx2/var/www/recordings/noaa/img/2018/09/08/ 17 | #fileNameCore="20181114-1818_NOAA-18" 18 | #fileNameCore=20180908-1626_NOAA-19 19 | #wwwDir="/home/filips/github/autowx2/var/www/" 20 | #wwwRootPath='file:///home/filips/github/autowx2/var/www/' 21 | 22 | 23 | # 24 | # config file 25 | # 26 | source $scriptDir/noaa.conf 27 | 28 | # prorgam itself - variables 29 | outHtml="$imgdir/$fileNameCore.html" # html for this single pass 30 | indexHtml="$imgdir/index.html" # main index file for a given day 31 | htmlTemplate="$wwwDir/index.tpl" 32 | 33 | 34 | # ---single gallery preparation------------------------------------------------# 35 | 36 | makethumb() { 37 | obrazek="$1" 38 | local thumbnail=$(basename "$obrazek" .jpg)".th.jpg" 39 | convert -define jpeg:size=200x200 "$obrazek" -thumbnail '200x200^' granite: +swap -gravity center -extent 200x200 -composite -quality 82 "$thumbnail" 40 | echo "$thumbnail" 41 | } 42 | 43 | # -----------------------------------------------------------------------------# 44 | 45 | logFile="$imgdir/$fileNameCore.log" # log file to read from 46 | 47 | varDate=$(sed '1q;d' $logFile) 48 | varSat=$(sed '3q;d' $logFile) 49 | varStart=$(sed '4q;d' $logFile) # unused 50 | varDur=$(sed '5q;d' $logFile) 51 | varPeak=$(sed '6q;d' $logFile) 52 | varFreq=$(sed '7q;d' $logFile) 53 | 54 | dateTime=$(date -d @$varStart +"%Y-%m-%d") 55 | dateTimeDir=$(date -d @$varStart +"%Y/%m/%d") # directory format of date, eg. 2018/11/22/ 56 | wwwPath=$wwwRootPath/recordings/noaa/img/$dateTimeDir 57 | 58 | echo $wwwPath/$fileNameCore > $wwwDir/noaa-last-recording.tmp 59 | 60 | cd $imgdir 61 | 62 | echo "

$varSat | $varDate

" > $outHtml 63 | echo "

f=${varFreq}Hz, peak: ${varPeak}°, duration: ${varDur}s

" >> $outHtml 64 | 65 | for enchancement in "${enchancements[@]}" 66 | do 67 | echo "**** $enchancement" 68 | obrazek="$fileNameCore-$enchancement+map.jpg" 69 | sizeof=$(du -sh "$obrazek" | cut -f 1) 70 | # generate thumbnail 71 | thumbnail=$(makethumb "$obrazek") 72 | echo "$enchancement " >> $outHtml 73 | done 74 | 75 | thumbnail=$(makethumb "$fileNameCore-spectrogram.jpg") 76 | echo "spectrogram" >> $outHtml 77 | 78 | 79 | 80 | # ----consolidate data from the given day ------------------------------------# 81 | # generates neither headers nor footer of the html file 82 | 83 | echo "" > $indexHtml.tmp 84 | for htmlfile in $(ls $imgdir/*.html | grep -v "index.html") 85 | do 86 | cat $htmlfile >> $indexHtml.tmp 87 | done 88 | 89 | # ---------- generates pages according to the template file ------------------- 90 | 91 | currentDate=$(date) 92 | echo $currentDate 93 | 94 | htmlTitle="NOAA images | $dateTime" 95 | htmlBody=$(cat $indexHtml.tmp) 96 | 97 | source $htmlTemplate > $indexHtml 98 | -------------------------------------------------------------------------------- /modules/noaa/noaa_process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # file to process NOAA wav file to produce weather images 4 | # all variables are provided by noaa.sh 5 | 6 | 7 | 8 | # 9 | # generate map 10 | # 11 | wxmap -T "$satellite" -a -H $tleFileName -o -O $duration -L "$latlonalt" $start $imgdir/$fileNameCore-mapa.png | tee -a $logFile 12 | 13 | # 14 | # should we resize images? 15 | # 16 | 17 | if [ "$resizeimageto" != "" ]; then 18 | echo "Resizing images to $resizeimageto px" 19 | resizeSwitch="-resize ${resizeimageto}x${resizeimageto}>" 20 | fi 21 | 22 | # 23 | # process wav file with various enchancements 24 | # 25 | 26 | for enchancement in "${enchancements[@]}" 27 | do 28 | echo "**** $enchancement" 29 | # wxtoimg -e $enchancement $recdir/$fileNameCore.wav $imgdir/$fileNameCore-${enchancement}.png | tee -a $logFile 30 | wxtoimg -e $enchancement -m $imgdir/$fileNameCore-mapa.png $recdir/$fileNameCore.wav $imgdir/$fileNameCore-${enchancement}+map.png | tee -a $logFile 31 | convert -quality 91 $resizeSwitch $imgdir/$fileNameCore-${enchancement}+map.png $imgdir/$fileNameCore-${enchancement}+map.jpg 32 | rm $imgdir/$fileNameCore-${enchancement}+map.png 33 | done 34 | 35 | sox $recdir/$fileNameCore.wav -n spectrogram -o $imgdir/$fileNameCore-spectrogram.png 36 | convert -quality 90 $imgdir/$fileNameCore-spectrogram.png $imgdir/$fileNameCore-spectrogram.jpg 37 | 38 | rm $imgdir/$fileNameCore-mapa.png 39 | rm $imgdir/$fileNameCore-spectrogram.png 40 | rm $recdir/$fileNameCore.wav 41 | -------------------------------------------------------------------------------- /modules/noaa/noaa_record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # file to record NOAA satellites via rtl_fm 4 | # all variables are provided by noaa.sh 5 | 6 | 7 | # 8 | # recording here 9 | # 10 | 11 | timeout $duration rtl_fm $biast -f $freq -s $sample -g $dongleGain -F 9 -A fast -E offset -p $dongleShift $recdir/$fileNameCore.raw | tee -a $logFile 12 | 13 | # 14 | # transcoding here 15 | # 16 | 17 | sox -t raw -r $sample -es -b 16 -c 1 -V1 $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav rate $wavrate | tee -a $logFile 18 | touch -r $recdir/$fileNameCore.raw $recdir/$fileNameCore.wav 19 | rm $recdir/$fileNameCore.raw 20 | -------------------------------------------------------------------------------- /modules/noaa/tests/20180118-1504_NOAA-19.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipsPL/autowx2/c91722c64afb2f018d0e6fcc0f7398aaaa2edfad/modules/noaa/tests/20180118-1504_NOAA-19.wav -------------------------------------------------------------------------------- /modules/radiosonde/run_radiosonde_scanner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # RUN radiosonde scanner for a given time 4 | # details, see: https://github.com/projecthorus/radiosonde_auto_rx/wiki 5 | 6 | 7 | # read the global configuration file autowx2_conf.py via the bash/python configuration parser 8 | # do not change the following three lines 9 | scriptDir="$(dirname "$(realpath "$0")")" 10 | source $scriptDir/basedir_conf.py 11 | source $baseDir/_listvars.sh 12 | 13 | 14 | #### microconfiguration 15 | 16 | radiosondeBin="/home/filips/progs/radiosonde_auto_rx/auto_rx" 17 | logfile="$recordingDir/radiosonde/log/$(date +"%Y%m%d").txt" 18 | mkdir -p "$recordingDir/radiosonde/log/" 19 | 20 | ### doing the job 21 | 22 | 23 | fileNameCore="$1" 24 | satellite="$2" 25 | start="$3" 26 | duration="$4" 27 | peak="$5" 28 | azimuth="$6" 29 | freq="$7" 30 | 31 | echo "fileNameCore=$fileNameCore" 32 | echo "satellite=$satellite" 33 | echo "start=$start" 34 | echo "duration=$duration" 35 | echo "peak=$peak" 36 | echo "azimuth=$azimuth" 37 | echo "freq=$freq" 38 | 39 | cd $radiosondeBin 40 | timeout $duration python auto_rx.py | tee -a $logfile 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pypredict 2 | matplotlib 3 | numpy 4 | Flask 5 | "Flask-SocketIO<=5.2.0" 6 | -------------------------------------------------------------------------------- /var/flask/static/fancybox/jquery.fancybox.min.css: -------------------------------------------------------------------------------- 1 | body.compensate-for-scrollbar{overflow:hidden}.fancybox-active{height:auto}.fancybox-is-hidden{left:-9999px;margin:0;position:absolute!important;top:-9999px;visibility:hidden}.fancybox-container{-webkit-backface-visibility:hidden;height:100%;left:0;outline:none;position:fixed;-webkit-tap-highlight-color:transparent;top:0;-ms-touch-action:manipulation;touch-action:manipulation;-webkit-transform:translateZ(0);transform:translateZ(0);width:100%;z-index:99992}.fancybox-container *{box-sizing:border-box}.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-stage{bottom:0;left:0;position:absolute;right:0;top:0}.fancybox-outer{-webkit-overflow-scrolling:touch;overflow-y:auto}.fancybox-bg{background:#1e1e1e;opacity:0;transition-duration:inherit;transition-property:opacity;transition-timing-function:cubic-bezier(.47,0,.74,.71)}.fancybox-is-open .fancybox-bg{opacity:.9;transition-timing-function:cubic-bezier(.22,.61,.36,1)}.fancybox-caption,.fancybox-infobar,.fancybox-navigation .fancybox-button,.fancybox-toolbar{direction:ltr;opacity:0;position:absolute;transition:opacity .25s ease,visibility 0s ease .25s;visibility:hidden;z-index:99997}.fancybox-show-caption .fancybox-caption,.fancybox-show-infobar .fancybox-infobar,.fancybox-show-nav .fancybox-navigation .fancybox-button,.fancybox-show-toolbar .fancybox-toolbar{opacity:1;transition:opacity .25s ease 0s,visibility 0s ease 0s;visibility:visible}.fancybox-infobar{color:#ccc;font-size:13px;-webkit-font-smoothing:subpixel-antialiased;height:44px;left:0;line-height:44px;min-width:44px;mix-blend-mode:difference;padding:0 10px;pointer-events:none;top:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fancybox-toolbar{right:0;top:0}.fancybox-stage{direction:ltr;overflow:visible;-webkit-transform:translateZ(0);transform:translateZ(0);z-index:99994}.fancybox-is-open .fancybox-stage{overflow:hidden}.fancybox-slide{-webkit-backface-visibility:hidden;display:none;height:100%;left:0;outline:none;overflow:auto;-webkit-overflow-scrolling:touch;padding:44px;position:absolute;text-align:center;top:0;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;white-space:normal;width:100%;z-index:99994}.fancybox-slide:before{content:"";display:inline-block;font-size:0;height:100%;vertical-align:middle;width:0}.fancybox-is-sliding .fancybox-slide,.fancybox-slide--current,.fancybox-slide--next,.fancybox-slide--previous{display:block}.fancybox-slide--image{overflow:hidden;padding:44px 0}.fancybox-slide--image:before{display:none}.fancybox-slide--html{padding:6px}.fancybox-content{background:#fff;display:inline-block;margin:0;max-width:100%;overflow:auto;-webkit-overflow-scrolling:touch;padding:44px;position:relative;text-align:left;vertical-align:middle}.fancybox-slide--image .fancybox-content{-webkit-animation-timing-function:cubic-bezier(.5,0,.14,1);animation-timing-function:cubic-bezier(.5,0,.14,1);-webkit-backface-visibility:hidden;background:transparent;background-repeat:no-repeat;background-size:100% 100%;left:0;max-width:none;overflow:visible;padding:0;position:absolute;top:0;-webkit-transform-origin:top left;transform-origin:top left;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:99995}.fancybox-can-zoomOut .fancybox-content{cursor:zoom-out}.fancybox-can-zoomIn .fancybox-content{cursor:zoom-in}.fancybox-can-pan .fancybox-content,.fancybox-can-swipe .fancybox-content{cursor:-webkit-grab;cursor:grab}.fancybox-is-grabbing .fancybox-content{cursor:-webkit-grabbing;cursor:grabbing}.fancybox-container [data-selectable=true]{cursor:text}.fancybox-image,.fancybox-spaceball{background:transparent;border:0;height:100%;left:0;margin:0;max-height:none;max-width:none;padding:0;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.fancybox-spaceball{z-index:1}.fancybox-slide--iframe .fancybox-content,.fancybox-slide--map .fancybox-content,.fancybox-slide--pdf .fancybox-content,.fancybox-slide--video .fancybox-content{height:100%;overflow:visible;padding:0;width:100%}.fancybox-slide--video .fancybox-content{background:#000}.fancybox-slide--map .fancybox-content{background:#e5e3df}.fancybox-slide--iframe .fancybox-content{background:#fff}.fancybox-iframe,.fancybox-video{background:transparent;border:0;display:block;height:100%;margin:0;overflow:hidden;padding:0;width:100%}.fancybox-iframe{left:0;position:absolute;top:0}.fancybox-error{background:#fff;cursor:default;max-width:400px;padding:40px;width:100%}.fancybox-error p{color:#444;font-size:16px;line-height:20px;margin:0;padding:0}.fancybox-button{background:rgba(30,30,30,.6);border:0;border-radius:0;box-shadow:none;cursor:pointer;display:inline-block;height:44px;margin:0;padding:10px;position:relative;transition:color .2s;vertical-align:top;visibility:inherit;width:44px}.fancybox-button,.fancybox-button:link,.fancybox-button:visited{color:#ccc}.fancybox-button:hover{color:#fff}.fancybox-button:focus{outline:none}.fancybox-button.fancybox-focus{outline:1px dotted}.fancybox-button[disabled],.fancybox-button[disabled]:hover{color:#888;cursor:default;outline:none}.fancybox-button div{height:100%}.fancybox-button svg{display:block;height:100%;overflow:visible;position:relative;width:100%}.fancybox-button svg path{fill:currentColor;stroke-width:0}.fancybox-button--fsenter svg:nth-child(2),.fancybox-button--fsexit svg:nth-child(1),.fancybox-button--pause svg:nth-child(1),.fancybox-button--play svg:nth-child(2){display:none}.fancybox-progress{background:#ff5268;height:2px;left:0;position:absolute;right:0;top:0;-webkit-transform:scaleX(0);transform:scaleX(0);-webkit-transform-origin:0;transform-origin:0;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-timing-function:linear;z-index:99998}.fancybox-close-small{background:transparent;border:0;border-radius:0;color:#ccc;cursor:pointer;opacity:.8;padding:8px;position:absolute;right:-12px;top:-44px;z-index:401}.fancybox-close-small:hover{color:#fff;opacity:1}.fancybox-slide--html .fancybox-close-small{color:currentColor;padding:10px;right:0;top:0}.fancybox-slide--image.fancybox-is-scaling .fancybox-content{overflow:hidden}.fancybox-is-scaling .fancybox-close-small,.fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small{display:none}.fancybox-navigation .fancybox-button{background-clip:content-box;height:100px;opacity:0;position:absolute;top:calc(50% - 50px);width:70px}.fancybox-navigation .fancybox-button div{padding:7px}.fancybox-navigation .fancybox-button--arrow_left{left:0;padding:31px 26px 31px 6px}.fancybox-navigation .fancybox-button--arrow_right{padding:31px 6px 31px 26px;right:0}.fancybox-caption{bottom:0;color:#eee;font-size:14px;font-weight:400;left:0;line-height:1.5;padding:25px 44px;right:0;text-align:center;z-index:99996}.fancybox-caption:before{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAD6CAQAAADKSeXYAAAAYklEQVQoz42RwQ3AMAgDjfcfup8WoRykfBAK5mQHKSz5rbXJPis1hjiV3CIqgG0hLZPkVkA4p4x5oR1bVeDrdCLrW2Q0D5bcwY3TGMHbdw3mPRuOtaspYP1w//G1OIcW148H0DMCqI/3mMMAAAAASUVORK5CYII=);background-repeat:repeat-x;background-size:contain;bottom:0;content:"";display:block;left:0;pointer-events:none;position:absolute;right:0;top:-44px;z-index:-1}.fancybox-caption a,.fancybox-caption a:link,.fancybox-caption a:visited{color:#ccc;text-decoration:none}.fancybox-caption a:hover{color:#fff;text-decoration:underline}.fancybox-loading{-webkit-animation:a 1s linear infinite;animation:a 1s linear infinite;background:transparent;border:4px solid #888;border-bottom-color:#fff;border-radius:50%;height:50px;left:50%;margin:-25px 0 0 -25px;opacity:.7;padding:0;position:absolute;top:50%;width:50px;z-index:99999}@-webkit-keyframes a{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes a{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fancybox-animated{transition-timing-function:cubic-bezier(0,0,.25,1)}.fancybox-fx-slide.fancybox-slide--previous{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.fancybox-fx-slide.fancybox-slide--next{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.fancybox-fx-slide.fancybox-slide--current{opacity:1;-webkit-transform:translateZ(0);transform:translateZ(0)}.fancybox-fx-fade.fancybox-slide--next,.fancybox-fx-fade.fancybox-slide--previous{opacity:0;transition-timing-function:cubic-bezier(.19,1,.22,1)}.fancybox-fx-fade.fancybox-slide--current{opacity:1}.fancybox-fx-zoom-in-out.fancybox-slide--previous{opacity:0;-webkit-transform:scale3d(1.5,1.5,1.5);transform:scale3d(1.5,1.5,1.5)}.fancybox-fx-zoom-in-out.fancybox-slide--next{opacity:0;-webkit-transform:scale3d(.5,.5,.5);transform:scale3d(.5,.5,.5)}.fancybox-fx-zoom-in-out.fancybox-slide--current{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}.fancybox-fx-rotate.fancybox-slide--previous{opacity:0;-webkit-transform:rotate(-1turn);transform:rotate(-1turn)}.fancybox-fx-rotate.fancybox-slide--next{opacity:0;-webkit-transform:rotate(1turn);transform:rotate(1turn)}.fancybox-fx-rotate.fancybox-slide--current{opacity:1;-webkit-transform:rotate(0deg);transform:rotate(0deg)}.fancybox-fx-circular.fancybox-slide--previous{opacity:0;-webkit-transform:scale3d(0,0,0) translate3d(-100%,0,0);transform:scale3d(0,0,0) translate3d(-100%,0,0)}.fancybox-fx-circular.fancybox-slide--next{opacity:0;-webkit-transform:scale3d(0,0,0) translate3d(100%,0,0);transform:scale3d(0,0,0) translate3d(100%,0,0)}.fancybox-fx-circular.fancybox-slide--current{opacity:1;-webkit-transform:scaleX(1) translateZ(0);transform:scaleX(1) translateZ(0)}.fancybox-fx-tube.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0) scale(.1) skew(-10deg);transform:translate3d(-100%,0,0) scale(.1) skew(-10deg)}.fancybox-fx-tube.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0) scale(.1) skew(10deg);transform:translate3d(100%,0,0) scale(.1) skew(10deg)}.fancybox-fx-tube.fancybox-slide--current{-webkit-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1)}@media (max-height:576px){.fancybox-caption{padding:12px}.fancybox-slide{padding-left:6px;padding-right:6px}.fancybox-slide--image{padding:6px 0}.fancybox-close-small{right:-6px}.fancybox-slide--image .fancybox-close-small{background:#4e4e4e;color:#f2f4f6;height:36px;opacity:1;padding:6px;right:0;top:0;width:36px}}.fancybox-share{background:#f4f4f4;border-radius:3px;max-width:90%;padding:30px;text-align:center}.fancybox-share h1{color:#222;font-size:35px;font-weight:700;margin:0 0 20px}.fancybox-share p{margin:0;padding:0}.fancybox-share__button{border:0;border-radius:3px;display:inline-block;font-size:14px;font-weight:700;line-height:40px;margin:0 5px 10px;min-width:130px;padding:0 15px;text-decoration:none;transition:all .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}.fancybox-share__button:link,.fancybox-share__button:visited{color:#fff}.fancybox-share__button:hover{text-decoration:none}.fancybox-share__button--fb{background:#3b5998}.fancybox-share__button--fb:hover{background:#344e86}.fancybox-share__button--pt{background:#bd081d}.fancybox-share__button--pt:hover{background:#aa0719}.fancybox-share__button--tw{background:#1da1f2}.fancybox-share__button--tw:hover{background:#0d95e8}.fancybox-share__button svg{height:25px;margin-right:7px;position:relative;top:-1px;vertical-align:middle;width:25px}.fancybox-share__button svg path{fill:#fff}.fancybox-share__input{background:transparent;border:0;border-bottom:1px solid #d7d7d7;border-radius:0;color:#5d5b5b;font-size:14px;margin:10px 0 0;outline:none;padding:10px 15px;width:100%}.fancybox-thumbs{background:#ddd;bottom:0;display:none;margin:0;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;padding:2px 2px 4px;position:absolute;right:0;-webkit-tap-highlight-color:transparent;top:0;width:212px;z-index:99995}.fancybox-thumbs-x{overflow-x:auto;overflow-y:hidden}.fancybox-show-thumbs .fancybox-thumbs{display:block}.fancybox-show-thumbs .fancybox-inner{right:212px}.fancybox-thumbs__list{font-size:0;height:100%;list-style:none;margin:0;overflow-x:hidden;overflow-y:auto;padding:0;position:absolute;position:relative;white-space:nowrap;width:100%}.fancybox-thumbs-x .fancybox-thumbs__list{overflow:hidden}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar{width:7px}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-track{background:#fff;border-radius:10px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-thumb{background:#2a2a2a;border-radius:10px}.fancybox-thumbs__list a{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:rgba(0,0,0,.1);background-position:50%;background-repeat:no-repeat;background-size:cover;cursor:pointer;float:left;height:75px;margin:2px;max-height:calc(100% - 8px);max-width:calc(50% - 4px);outline:none;overflow:hidden;padding:0;position:relative;-webkit-tap-highlight-color:transparent;width:100px}.fancybox-thumbs__list a:before{border:6px solid #ff5268;bottom:0;content:"";left:0;opacity:0;position:absolute;right:0;top:0;transition:all .2s cubic-bezier(.25,.46,.45,.94);z-index:99991}.fancybox-thumbs__list a:focus:before{opacity:.5}.fancybox-thumbs__list a.fancybox-thumbs-active:before{opacity:1}@media (max-width:576px){.fancybox-thumbs{width:110px}.fancybox-show-thumbs .fancybox-inner{right:110px}.fancybox-thumbs__list a{max-width:calc(100% - 10px)}} -------------------------------------------------------------------------------- /var/flask/static/fancybox/source.md: -------------------------------------------------------------------------------- 1 | https://github.com/fancyapps/fancyBox 2 | -------------------------------------------------------------------------------- /var/flask/static/logo-nav.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Logo Nav (https://startbootstrap.com/template-overviews/logo-nav) 3 | * Copyright 2013-2017 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-logo-nav/blob/master/LICENSE) 5 | */ 6 | 7 | body { 8 | padding-top: 94px; 9 | } 10 | 11 | @media (min-width: 992px) { 12 | body { 13 | padding-top: 76px; 14 | } 15 | } 16 | 17 | 18 | h2 { 19 | padding-top: 16px; 20 | } 21 | 22 | img { padding: 10px;} 23 | 24 | h2:after 25 | { 26 | content:' '; 27 | display:block; 28 | border:1px dotted silver; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /var/flask/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | autowx2 automated satellite receiving station :: {{title}} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 82 | 83 | 84 |
85 |

{{title}}

86 | 87 | {% autoescape false %} 88 | {{ body }} 89 | {% endautoescape %} 90 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /var/www/css/fancybox/jquery.fancybox.min.css: -------------------------------------------------------------------------------- 1 | body.compensate-for-scrollbar{overflow:hidden}.fancybox-active{height:auto}.fancybox-is-hidden{left:-9999px;margin:0;position:absolute!important;top:-9999px;visibility:hidden}.fancybox-container{-webkit-backface-visibility:hidden;height:100%;left:0;outline:none;position:fixed;-webkit-tap-highlight-color:transparent;top:0;-ms-touch-action:manipulation;touch-action:manipulation;-webkit-transform:translateZ(0);transform:translateZ(0);width:100%;z-index:99992}.fancybox-container *{box-sizing:border-box}.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-stage{bottom:0;left:0;position:absolute;right:0;top:0}.fancybox-outer{-webkit-overflow-scrolling:touch;overflow-y:auto}.fancybox-bg{background:#1e1e1e;opacity:0;transition-duration:inherit;transition-property:opacity;transition-timing-function:cubic-bezier(.47,0,.74,.71)}.fancybox-is-open .fancybox-bg{opacity:.9;transition-timing-function:cubic-bezier(.22,.61,.36,1)}.fancybox-caption,.fancybox-infobar,.fancybox-navigation .fancybox-button,.fancybox-toolbar{direction:ltr;opacity:0;position:absolute;transition:opacity .25s ease,visibility 0s ease .25s;visibility:hidden;z-index:99997}.fancybox-show-caption .fancybox-caption,.fancybox-show-infobar .fancybox-infobar,.fancybox-show-nav .fancybox-navigation .fancybox-button,.fancybox-show-toolbar .fancybox-toolbar{opacity:1;transition:opacity .25s ease 0s,visibility 0s ease 0s;visibility:visible}.fancybox-infobar{color:#ccc;font-size:13px;-webkit-font-smoothing:subpixel-antialiased;height:44px;left:0;line-height:44px;min-width:44px;mix-blend-mode:difference;padding:0 10px;pointer-events:none;top:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fancybox-toolbar{right:0;top:0}.fancybox-stage{direction:ltr;overflow:visible;-webkit-transform:translateZ(0);transform:translateZ(0);z-index:99994}.fancybox-is-open .fancybox-stage{overflow:hidden}.fancybox-slide{-webkit-backface-visibility:hidden;display:none;height:100%;left:0;outline:none;overflow:auto;-webkit-overflow-scrolling:touch;padding:44px;position:absolute;text-align:center;top:0;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;white-space:normal;width:100%;z-index:99994}.fancybox-slide:before{content:"";display:inline-block;font-size:0;height:100%;vertical-align:middle;width:0}.fancybox-is-sliding .fancybox-slide,.fancybox-slide--current,.fancybox-slide--next,.fancybox-slide--previous{display:block}.fancybox-slide--image{overflow:hidden;padding:44px 0}.fancybox-slide--image:before{display:none}.fancybox-slide--html{padding:6px}.fancybox-content{background:#fff;display:inline-block;margin:0;max-width:100%;overflow:auto;-webkit-overflow-scrolling:touch;padding:44px;position:relative;text-align:left;vertical-align:middle}.fancybox-slide--image .fancybox-content{-webkit-animation-timing-function:cubic-bezier(.5,0,.14,1);animation-timing-function:cubic-bezier(.5,0,.14,1);-webkit-backface-visibility:hidden;background:transparent;background-repeat:no-repeat;background-size:100% 100%;left:0;max-width:none;overflow:visible;padding:0;position:absolute;top:0;-webkit-transform-origin:top left;transform-origin:top left;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:99995}.fancybox-can-zoomOut .fancybox-content{cursor:zoom-out}.fancybox-can-zoomIn .fancybox-content{cursor:zoom-in}.fancybox-can-pan .fancybox-content,.fancybox-can-swipe .fancybox-content{cursor:-webkit-grab;cursor:grab}.fancybox-is-grabbing .fancybox-content{cursor:-webkit-grabbing;cursor:grabbing}.fancybox-container [data-selectable=true]{cursor:text}.fancybox-image,.fancybox-spaceball{background:transparent;border:0;height:100%;left:0;margin:0;max-height:none;max-width:none;padding:0;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.fancybox-spaceball{z-index:1}.fancybox-slide--iframe .fancybox-content,.fancybox-slide--map .fancybox-content,.fancybox-slide--pdf .fancybox-content,.fancybox-slide--video .fancybox-content{height:100%;overflow:visible;padding:0;width:100%}.fancybox-slide--video .fancybox-content{background:#000}.fancybox-slide--map .fancybox-content{background:#e5e3df}.fancybox-slide--iframe .fancybox-content{background:#fff}.fancybox-iframe,.fancybox-video{background:transparent;border:0;display:block;height:100%;margin:0;overflow:hidden;padding:0;width:100%}.fancybox-iframe{left:0;position:absolute;top:0}.fancybox-error{background:#fff;cursor:default;max-width:400px;padding:40px;width:100%}.fancybox-error p{color:#444;font-size:16px;line-height:20px;margin:0;padding:0}.fancybox-button{background:rgba(30,30,30,.6);border:0;border-radius:0;box-shadow:none;cursor:pointer;display:inline-block;height:44px;margin:0;padding:10px;position:relative;transition:color .2s;vertical-align:top;visibility:inherit;width:44px}.fancybox-button,.fancybox-button:link,.fancybox-button:visited{color:#ccc}.fancybox-button:hover{color:#fff}.fancybox-button:focus{outline:none}.fancybox-button.fancybox-focus{outline:1px dotted}.fancybox-button[disabled],.fancybox-button[disabled]:hover{color:#888;cursor:default;outline:none}.fancybox-button div{height:100%}.fancybox-button svg{display:block;height:100%;overflow:visible;position:relative;width:100%}.fancybox-button svg path{fill:currentColor;stroke-width:0}.fancybox-button--fsenter svg:nth-child(2),.fancybox-button--fsexit svg:nth-child(1),.fancybox-button--pause svg:nth-child(1),.fancybox-button--play svg:nth-child(2){display:none}.fancybox-progress{background:#ff5268;height:2px;left:0;position:absolute;right:0;top:0;-webkit-transform:scaleX(0);transform:scaleX(0);-webkit-transform-origin:0;transform-origin:0;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-timing-function:linear;z-index:99998}.fancybox-close-small{background:transparent;border:0;border-radius:0;color:#ccc;cursor:pointer;opacity:.8;padding:8px;position:absolute;right:-12px;top:-44px;z-index:401}.fancybox-close-small:hover{color:#fff;opacity:1}.fancybox-slide--html .fancybox-close-small{color:currentColor;padding:10px;right:0;top:0}.fancybox-slide--image.fancybox-is-scaling .fancybox-content{overflow:hidden}.fancybox-is-scaling .fancybox-close-small,.fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small{display:none}.fancybox-navigation .fancybox-button{background-clip:content-box;height:100px;opacity:0;position:absolute;top:calc(50% - 50px);width:70px}.fancybox-navigation .fancybox-button div{padding:7px}.fancybox-navigation .fancybox-button--arrow_left{left:0;padding:31px 26px 31px 6px}.fancybox-navigation .fancybox-button--arrow_right{padding:31px 6px 31px 26px;right:0}.fancybox-caption{bottom:0;color:#eee;font-size:14px;font-weight:400;left:0;line-height:1.5;padding:25px 44px;right:0;text-align:center;z-index:99996}.fancybox-caption:before{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAD6CAQAAADKSeXYAAAAYklEQVQoz42RwQ3AMAgDjfcfup8WoRykfBAK5mQHKSz5rbXJPis1hjiV3CIqgG0hLZPkVkA4p4x5oR1bVeDrdCLrW2Q0D5bcwY3TGMHbdw3mPRuOtaspYP1w//G1OIcW148H0DMCqI/3mMMAAAAASUVORK5CYII=);background-repeat:repeat-x;background-size:contain;bottom:0;content:"";display:block;left:0;pointer-events:none;position:absolute;right:0;top:-44px;z-index:-1}.fancybox-caption a,.fancybox-caption a:link,.fancybox-caption a:visited{color:#ccc;text-decoration:none}.fancybox-caption a:hover{color:#fff;text-decoration:underline}.fancybox-loading{-webkit-animation:a 1s linear infinite;animation:a 1s linear infinite;background:transparent;border:4px solid #888;border-bottom-color:#fff;border-radius:50%;height:50px;left:50%;margin:-25px 0 0 -25px;opacity:.7;padding:0;position:absolute;top:50%;width:50px;z-index:99999}@-webkit-keyframes a{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes a{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fancybox-animated{transition-timing-function:cubic-bezier(0,0,.25,1)}.fancybox-fx-slide.fancybox-slide--previous{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.fancybox-fx-slide.fancybox-slide--next{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.fancybox-fx-slide.fancybox-slide--current{opacity:1;-webkit-transform:translateZ(0);transform:translateZ(0)}.fancybox-fx-fade.fancybox-slide--next,.fancybox-fx-fade.fancybox-slide--previous{opacity:0;transition-timing-function:cubic-bezier(.19,1,.22,1)}.fancybox-fx-fade.fancybox-slide--current{opacity:1}.fancybox-fx-zoom-in-out.fancybox-slide--previous{opacity:0;-webkit-transform:scale3d(1.5,1.5,1.5);transform:scale3d(1.5,1.5,1.5)}.fancybox-fx-zoom-in-out.fancybox-slide--next{opacity:0;-webkit-transform:scale3d(.5,.5,.5);transform:scale3d(.5,.5,.5)}.fancybox-fx-zoom-in-out.fancybox-slide--current{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}.fancybox-fx-rotate.fancybox-slide--previous{opacity:0;-webkit-transform:rotate(-1turn);transform:rotate(-1turn)}.fancybox-fx-rotate.fancybox-slide--next{opacity:0;-webkit-transform:rotate(1turn);transform:rotate(1turn)}.fancybox-fx-rotate.fancybox-slide--current{opacity:1;-webkit-transform:rotate(0deg);transform:rotate(0deg)}.fancybox-fx-circular.fancybox-slide--previous{opacity:0;-webkit-transform:scale3d(0,0,0) translate3d(-100%,0,0);transform:scale3d(0,0,0) translate3d(-100%,0,0)}.fancybox-fx-circular.fancybox-slide--next{opacity:0;-webkit-transform:scale3d(0,0,0) translate3d(100%,0,0);transform:scale3d(0,0,0) translate3d(100%,0,0)}.fancybox-fx-circular.fancybox-slide--current{opacity:1;-webkit-transform:scaleX(1) translateZ(0);transform:scaleX(1) translateZ(0)}.fancybox-fx-tube.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0) scale(.1) skew(-10deg);transform:translate3d(-100%,0,0) scale(.1) skew(-10deg)}.fancybox-fx-tube.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0) scale(.1) skew(10deg);transform:translate3d(100%,0,0) scale(.1) skew(10deg)}.fancybox-fx-tube.fancybox-slide--current{-webkit-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1)}@media (max-height:576px){.fancybox-caption{padding:12px}.fancybox-slide{padding-left:6px;padding-right:6px}.fancybox-slide--image{padding:6px 0}.fancybox-close-small{right:-6px}.fancybox-slide--image .fancybox-close-small{background:#4e4e4e;color:#f2f4f6;height:36px;opacity:1;padding:6px;right:0;top:0;width:36px}}.fancybox-share{background:#f4f4f4;border-radius:3px;max-width:90%;padding:30px;text-align:center}.fancybox-share h1{color:#222;font-size:35px;font-weight:700;margin:0 0 20px}.fancybox-share p{margin:0;padding:0}.fancybox-share__button{border:0;border-radius:3px;display:inline-block;font-size:14px;font-weight:700;line-height:40px;margin:0 5px 10px;min-width:130px;padding:0 15px;text-decoration:none;transition:all .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}.fancybox-share__button:link,.fancybox-share__button:visited{color:#fff}.fancybox-share__button:hover{text-decoration:none}.fancybox-share__button--fb{background:#3b5998}.fancybox-share__button--fb:hover{background:#344e86}.fancybox-share__button--pt{background:#bd081d}.fancybox-share__button--pt:hover{background:#aa0719}.fancybox-share__button--tw{background:#1da1f2}.fancybox-share__button--tw:hover{background:#0d95e8}.fancybox-share__button svg{height:25px;margin-right:7px;position:relative;top:-1px;vertical-align:middle;width:25px}.fancybox-share__button svg path{fill:#fff}.fancybox-share__input{background:transparent;border:0;border-bottom:1px solid #d7d7d7;border-radius:0;color:#5d5b5b;font-size:14px;margin:10px 0 0;outline:none;padding:10px 15px;width:100%}.fancybox-thumbs{background:#ddd;bottom:0;display:none;margin:0;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;padding:2px 2px 4px;position:absolute;right:0;-webkit-tap-highlight-color:transparent;top:0;width:212px;z-index:99995}.fancybox-thumbs-x{overflow-x:auto;overflow-y:hidden}.fancybox-show-thumbs .fancybox-thumbs{display:block}.fancybox-show-thumbs .fancybox-inner{right:212px}.fancybox-thumbs__list{font-size:0;height:100%;list-style:none;margin:0;overflow-x:hidden;overflow-y:auto;padding:0;position:absolute;position:relative;white-space:nowrap;width:100%}.fancybox-thumbs-x .fancybox-thumbs__list{overflow:hidden}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar{width:7px}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-track{background:#fff;border-radius:10px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-thumb{background:#2a2a2a;border-radius:10px}.fancybox-thumbs__list a{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:rgba(0,0,0,.1);background-position:50%;background-repeat:no-repeat;background-size:cover;cursor:pointer;float:left;height:75px;margin:2px;max-height:calc(100% - 8px);max-width:calc(50% - 4px);outline:none;overflow:hidden;padding:0;position:relative;-webkit-tap-highlight-color:transparent;width:100px}.fancybox-thumbs__list a:before{border:6px solid #ff5268;bottom:0;content:"";left:0;opacity:0;position:absolute;right:0;top:0;transition:all .2s cubic-bezier(.25,.46,.45,.94);z-index:99991}.fancybox-thumbs__list a:focus:before{opacity:.5}.fancybox-thumbs__list a.fancybox-thumbs-active:before{opacity:1}@media (max-width:576px){.fancybox-thumbs{width:110px}.fancybox-show-thumbs .fancybox-inner{right:110px}.fancybox-thumbs__list a{max-width:calc(100% - 10px)}} -------------------------------------------------------------------------------- /var/www/css/fancybox/source.md: -------------------------------------------------------------------------------- 1 | https://github.com/fancyapps/fancyBox 2 | -------------------------------------------------------------------------------- /var/www/css/logo-nav.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Logo Nav (https://startbootstrap.com/template-overviews/logo-nav) 3 | * Copyright 2013-2017 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-logo-nav/blob/master/LICENSE) 5 | */ 6 | 7 | body { 8 | padding-top: 94px; 9 | } 10 | 11 | @media (min-width: 992px) { 12 | body { 13 | padding-top: 76px; 14 | } 15 | } 16 | 17 | 18 | h2 { 19 | padding-top: 16px; 20 | } 21 | 22 | img { padding: 10px;} 23 | 24 | h2:after 25 | { 26 | content:' '; 27 | display:block; 28 | border:1px dotted silver; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /var/www/index.tpl: -------------------------------------------------------------------------------- 1 | cat < 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | autowx2 automated satellite receiving station $stationName :: $htmlTitle 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 53 | 54 | 55 |
56 | 57 |
58 |
59 |

$htmlTitle

60 | Page generated: $currentDate
61 | $keplerInfo 62 | $autowxUptime 63 | Software version: $autowx2version
64 | $shortlistofnextpassess 65 | 66 | $htmlBody 67 |
68 |
69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | EOF 82 | --------------------------------------------------------------------------------